Several PQ platform patches + Quest complete count + Fast meso drop

Implemented CPQ challenges using the matching system.
Fixed LanguageConstants statically acting for all players.
Fixed OPQ's <On the Way Up> stage sometimes leading players to unexpected platforms.
Fixed EllinPQ fountain not giving Altaire Fragment to players.
Fixed "Lab - Unit" stage on RnJPQ, now using correlated sequences between the units.
Fixed Fredrick handing out negative values of mesos to players.
Improved "goto" command info.
Implemented quest complete count.
Fixed mobs still being "controlled" by players even though it's already dead.
Concurrently protected adding items into inventory.
Concurrently protected EXP gain through Writs of Solomon.
Adjusted smoothly respawn rate of mobs in map (solo players in a map now experiences 75% of mobs spawned).
Fixed mesos not being able to drop so frequently (prior 200ms threshold between drops).
Tweaked matchchecking so that match checking doesn't outright dispose matching members on dismissal (match still sticks to the player until they answer or timeout).
Fixed a dupe case within storage's item store.
Added any-NPC scriptable to the source.
This commit is contained in:
ronancpl
2019-05-02 11:02:07 -03:00
parent b6a91c523f
commit c2fa7883fe
90 changed files with 1454 additions and 611 deletions

View File

@@ -266,6 +266,7 @@ public enum SendOpcode {
REMOVE_NPC(0x102),
SPAWN_NPC_REQUEST_CONTROLLER(0x103),
NPC_ACTION(0x104),
SET_NPC_SCRIPTABLE(0x107),
SPAWN_HIRED_MERCHANT(0x109),
DESTROY_HIRED_MERCHANT(0x10A),
UPDATE_HIRED_MERCHANT(0x10B),

View File

@@ -1648,6 +1648,10 @@ public class Server {
}
public boolean validateCharacteridInTransition(IoSession session, int charId) {
if (!ServerConstants.USE_IP_VALIDATION) {
return true;
}
String remoteIp = getRemoteIp(session);
lgnWLock.lock();
@@ -1660,6 +1664,10 @@ public class Server {
}
public Integer freeCharacteridInTransition(IoSession session) {
if (!ServerConstants.USE_IP_VALIDATION) {
return null;
}
String remoteIp = getRemoteIp(session);
lgnWLock.lock();
@@ -1671,6 +1679,10 @@ public class Server {
}
public boolean hasCharacteridInTransition(IoSession session) {
if (!ServerConstants.USE_IP_VALIDATION) {
return true;
}
String remoteIp = getRemoteIp(session);
lgnRLock.lock();

View File

@@ -64,6 +64,8 @@ public enum MonitoredLockType {
GUILD,
PARTY,
WORLD_PARTY,
WORLD_PARTY_SEARCH_ECHELON,
WORLD_PARTY_SEARCH_STORAGE,
WORLD_SRVMESSAGES,
WORLD_PETS,
WORLD_CHARS,

View File

@@ -296,17 +296,22 @@ public final class Channel {
}
private void closeAllMerchants() {
merchWlock.lock();
try {
final Iterator<MapleHiredMerchant> hmit = hiredMerchants.values().iterator();
while (hmit.hasNext()) {
hmit.next().forceClose();
hmit.remove();
List<MapleHiredMerchant> merchs;
merchWlock.lock();
try {
merchs = new ArrayList<>(hiredMerchants.values());
hiredMerchants.clear();
} finally {
merchWlock.unlock();
}
for (MapleHiredMerchant merch : merchs) {
merch.forceClose();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
merchWlock.unlock();
e.printStackTrace();
}
}

View File

@@ -273,6 +273,17 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl
int totDamageToOneMonster = 0;
List<Integer> onedList = attack.allDamage.get(oned);
if (attack.magic) { // thanks BHB, Alex (CanIGetaPR) for noticing no immunity status check here
if (monster.isBuffed(MonsterStatus.MAGIC_IMMUNITY)) {
Collections.fill(onedList, 1);
}
} else {
if (monster.isBuffed(MonsterStatus.WEAPON_IMMUNITY)) {
Collections.fill(onedList, 1);
}
}
for (Integer eachd : onedList) {
if(eachd < 0) eachd += Integer.MAX_VALUE;
totDamageToOneMonster += eachd;
@@ -453,7 +464,7 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl
int skillLv = player.getSkillLevel(threeSnailsId);
if(skillLv > 0) {
AbstractPlayerInteraction api = player.getClient().getAbstractPlayerInteraction();
AbstractPlayerInteraction api = player.getAbstractPlayerInteraction();
int shellId;
switch(skillLv) {

View File

@@ -77,7 +77,13 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
Set<MapleCharacter> eligibleMembers = new HashSet<>(MapleGuild.getEligiblePlayersForGuild(mc));
if (eligibleMembers.size() < ServerConstants.CREATE_GUILD_MIN_PARTNERS) {
mc.dropMessage(1, "The Guild you are trying to create don't meet the minimum criteria of number of founders.");
if (mc.getMap().getAllPlayers().size() < ServerConstants.CREATE_GUILD_MIN_PARTNERS) {
mc.dropMessage(1, "The Guild you are trying to create don't meet the minimum criteria of number of founders.");
} else {
// players may be unaware of not belonging on a party in order to become eligible, thanks Hair (Legalize) for pointing this out
mc.dropMessage(1, "Please make sure everyone you are trying to invite is neither on a guild nor on a party.");
}
return;
}
@@ -91,7 +97,7 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
eligibleCids.add(chr.getId());
}
c.getWorldServer().getMatchCheckerCoordinator().createMatchConfirmation(MatchCheckerType.GUILD_CREATION, c.getWorld(), mc.getId(), eligibleCids, guildName);
c.getWorldServer().getMatchCheckerCoordinator().createMatchConfirmation(MatchCheckerType.GUILD_CREATION, c.getWorld(), mc.getId(), eligibleCids, guildName);
break;
case 0x05:
if (mc.getGuildId() <= 0 || mc.getGuildRank() > 2) {
@@ -246,12 +252,12 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
int leaderid = wserv.getMatchCheckerCoordinator().getMatchConfirmationLeaderid(mc.getId());
if (leaderid != -1) {
boolean result = slea.readByte() != 0;
if (result) {
if (result && wserv.getMatchCheckerCoordinator().isMatchConfirmationActive(mc.getId())) {
MapleCharacter leader = wserv.getPlayerStorage().getCharacterById(leaderid);
if (leader != null) {
int partyid = leader.getPartyId();
if (partyid != -1) {
MapleParty.joinParty(mc, partyid, true);
MapleParty.joinParty(mc, partyid, true); // GMS gimmick "party to form guild" recalled thanks to Vcoc
}
}
}

View File

@@ -30,29 +30,39 @@ import client.MapleClient;
/**
*
* @author Matze
* @author Ronan - concurrency protection
*/
public final class MesoDropHandler extends AbstractMaplePacketHandler {
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
MapleCharacter player = c.getPlayer();
if (!player.isAlive()) {
MapleCharacter player = c.getPlayer();
if (!player.isAlive()) {
c.announce(MaplePacketCreator.enableActions());
return;
}
slea.skip(4);
int meso = slea.readInt();
if (c.tryacquireClient()) { // thanks imbee for noticing players not being able to throw mesos too fast, dampening gameplay of some classes
try {
if (meso <= player.getMeso() && meso > 9 && meso < 50001) {
player.gainMeso(-meso, false, true, false);
} else {
c.announce(MaplePacketCreator.enableActions());
return;
}
} finally {
c.releaseClient();
}
if (!player.canDropMeso()){
player.announce(MaplePacketCreator.serverNotice(5, "Fast meso drop has been patched, cut that out. ;)"));
return;
}
slea.skip(4);
int meso = slea.readInt();
if (meso <= player.getMeso() && meso > 9 && meso < 50001) {
player.gainMeso(-meso, false, true, false);
if (player.attemptCatchFish(meso)) {
player.getMap().disappearingMesoDrop(meso, player, player, player.getPosition());
} else {
player.getMap().spawnMesoDrop(meso, player.getPosition(), player, player, true, (byte) 2);
}
}
} else {
c.announce(MaplePacketCreator.enableActions());
return;
}
if (player.attemptCatchFish(meso)) {
player.getMap().disappearingMesoDrop(meso, player, player, player.getPosition());
} else {
player.getMap().spawnMesoDrop(meso, player.getPosition(), player, player, true, (byte) 2);
}
}
}

View File

@@ -67,8 +67,8 @@ public final class NewYearCardHandler extends AbstractMaplePacketHandler {
NewYearCardRecord.saveNewYearCard(newyear);
player.addNewYearRecord(newyear);
player.getClient().getAbstractPlayerInteraction().gainItem(2160101, (short)-1);
player.getClient().getAbstractPlayerInteraction().gainItem(4300000, (short) 1);
player.getAbstractPlayerInteraction().gainItem(2160101, (short)-1);
player.getAbstractPlayerInteraction().gainItem(4300000, (short) 1);
Server.getInstance().setNewYearCard(newyear);
newyear.startNewYearCardTask();
@@ -99,7 +99,7 @@ public final class NewYearCardHandler extends AbstractMaplePacketHandler {
newyear.stopNewYearCardTask();
NewYearCardRecord.updateNewYearCard(newyear);
player.getClient().getAbstractPlayerInteraction().gainItem(4301000, (short)1);
player.getAbstractPlayerInteraction().gainItem(4301000, (short)1);
if(!newyear.getMessage().isEmpty()) player.dropMessage(6, "[New Year] " + newyear.getSenderName() + ": " + newyear.getMessage());
player.addNewYearRecord(newyear);

View File

@@ -34,14 +34,9 @@ import constants.ServerConstants;
import net.server.coordinator.MapleInviteCoordinator;
import net.server.coordinator.MapleInviteCoordinator.InviteResult;
import net.server.coordinator.MapleInviteCoordinator.InviteType;
import net.server.coordinator.MapleMatchCheckerCoordinator;
import net.server.coordinator.matchchecker.MatchCheckerListenerFactory.MatchCheckerType;
import scripting.event.EventInstanceManager;
import server.maps.MapleMap;
import tools.Pair;
import java.util.List;
import server.partyquest.MonsterCarnival;
public final class PartyOperationHandler extends AbstractMaplePacketHandler {

View File

@@ -572,11 +572,11 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
}
short perBundle = slea.readShort();
if (ItemConstants.isRechargeable(ivItem.getItemId())) {
perBundle = 1;
bundles = 1;
} else if (chr.getItemQuantity(ivItem.getItemId(), false) < perBundle * bundles) {
} else if (ivItem.getQuantity() < (bundles * perBundle)) { // thanks GabrielSin for finding a dupe here
c.announce(MaplePacketCreator.serverNotice(1, "Could not perform shop operation with that item."));
c.announce(MaplePacketCreator.enableActions());
return;

View File

@@ -58,6 +58,7 @@ import client.inventory.MapleInventory;
import client.inventory.MapleInventoryType;
import client.inventory.MaplePet;
import constants.GameConstants;
import constants.ScriptableNPCConstants;
import constants.ServerConstants;
import java.util.Collections;
import java.util.Comparator;
@@ -404,6 +405,10 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
eim.registerPlayer(player);
}
}
if (ServerConstants.USE_NPCS_SCRIPTABLE) {
c.announce(MaplePacketCreator.setNPCScriptable(ScriptableNPCConstants.SCRIPTABLE_NPCS));
}
} finally {
c.releaseClient();
}

View File

@@ -212,21 +212,30 @@ public final class TakeDamageHandler extends AbstractMaplePacketHandler {
chr.getAutobanManager().resetMisses();
}
if (damage > 0 && !chr.isHidden()) {
if (attacker != null && damagefrom == -1 && chr.getBuffedValue(MapleBuffStat.POWERGUARD) != null) { // PG works on bosses, but only at half of the rate.
int bouncedamage = (int) (damage * (chr.getBuffedValue(MapleBuffStat.POWERGUARD).doubleValue() / (attacker.isBoss() ? 200 : 100)));
bouncedamage = Math.min(bouncedamage, attacker.getMaxHp() / 10);
damage -= bouncedamage;
map.damageMonster(chr, attacker, bouncedamage);
map.broadcastMessage(chr, MaplePacketCreator.damageMonster(oid, bouncedamage), false, true);
attacker.aggroMonsterDamage(chr, bouncedamage);
}
if (attacker != null && damagefrom == -1 && chr.getBuffedValue(MapleBuffStat.BODY_PRESSURE) != null) {
Skill skill = SkillFactory.getSkill(Aran.BODY_PRESSURE);
final MapleStatEffect eff = skill.getEffect(chr.getSkillLevel(skill));
if (!attacker.alreadyBuffedStats().contains(MonsterStatus.NEUTRALISE)) {
if (!attacker.isBoss() && eff.makeChanceResult()) {
attacker.applyStatus(chr, new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.NEUTRALISE, 1), skill, null, false), false, (eff.getDuration()/10) * 2, false);
if (attacker != null) {
if (damagefrom == -1) {
if (chr.getBuffedValue(MapleBuffStat.POWERGUARD) != null) { // PG works on bosses, but only at half of the rate.
int bouncedamage = (int) (damage * (chr.getBuffedValue(MapleBuffStat.POWERGUARD).doubleValue() / (attacker.isBoss() ? 200 : 100)));
bouncedamage = Math.min(bouncedamage, attacker.getMaxHp() / 10);
damage -= bouncedamage;
map.damageMonster(chr, attacker, bouncedamage);
map.broadcastMessage(chr, MaplePacketCreator.damageMonster(oid, bouncedamage), false, true);
attacker.aggroMonsterDamage(chr, bouncedamage);
}
MapleStatEffect bPressure = chr.getBuffEffect(MapleBuffStat.COMBO_BARRIER);
if (bPressure != null) {
Skill skill = SkillFactory.getSkill(Aran.BODY_PRESSURE);
if (!attacker.alreadyBuffedStats().contains(MonsterStatus.NEUTRALISE)) {
if (!attacker.isBoss() && bPressure.makeChanceResult()) {
attacker.applyStatus(chr, new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.NEUTRALISE, 1), skill, null, false), false, (bPressure.getDuration() / 10) * 2, false);
}
}
}
}
MapleStatEffect cBarrier = chr.getBuffEffect(MapleBuffStat.COMBO_BARRIER); // thanks BHB for noticing Combo Barrier buff not working
if (cBarrier != null) {
damage *= (cBarrier.getX() / 1000.0);
}
}
if (damagefrom != -3 && damagefrom != -4) {

View File

@@ -30,14 +30,24 @@ import tools.data.input.SeekableLittleEndianAccessor;
/**
*
* @author kevintjuh93
* @author kevintjuh93; modified by Ronan
*/
public class UseGachaExpHandler extends AbstractMaplePacketHandler {
@Override
public void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
if (c.getPlayer().getGachaExp() == 0) {
AutobanFactory.GACHA_EXP.autoban(c.getPlayer(), "Player tried to redeem GachaEXP, but had none to redeem.");
if (c.tryacquireClient()) {
try {
if (c.getPlayer().getGachaExp() <= 0) {
AutobanFactory.GACHA_EXP.autoban(c.getPlayer(), "Player tried to redeem GachaEXP, but had none to redeem.");
}
c.getPlayer().gainGachaExp();
} finally {
c.releaseClient();
}
}
c.getPlayer().gainGachaExp();
c.announce(MaplePacketCreator.enableActions());
}
}

View File

@@ -31,6 +31,7 @@ import constants.ExpTable;
import net.AbstractMaplePacketHandler;
import client.inventory.manipulator.MapleInventoryManipulator;
import tools.MaplePacketCreator;
import tools.Pair;
import tools.data.input.SeekableLittleEndianAccessor;
/**
@@ -50,6 +51,8 @@ public final class UseMountFoodHandler extends AbstractMaplePacketHandler {
if (c.tryacquireClient()) {
try {
Boolean mountLevelup = null;
useInv.lockInventory();
try {
Item item = useInv.getItem(pos);
@@ -67,7 +70,8 @@ public final class UseMountFoodHandler extends AbstractMaplePacketHandler {
if (levelup) {
mount.setLevel(level + 1);
}
chr.getMap().broadcastMessage(MaplePacketCreator.updateMount(chr.getId(), mount, levelup));
mountLevelup = levelup;
}
MapleInventoryManipulator.removeById(c, MapleInventoryType.USE, itemid, 1, true, false);
@@ -75,6 +79,10 @@ public final class UseMountFoodHandler extends AbstractMaplePacketHandler {
} finally {
useInv.unlockInventory();
}
if (mountLevelup != null) {
chr.getMap().broadcastMessage(MaplePacketCreator.updateMount(chr.getId(), mount, mountLevelup));
}
} finally {
c.releaseClient();
}

View File

@@ -21,7 +21,9 @@
*/
package net.server.channel.handlers;
import client.MapleCharacter;
import client.MapleClient;
import client.inventory.MapleInventory;
import client.inventory.Item;
import client.inventory.MapleInventoryType;
import net.AbstractMaplePacketHandler;
@@ -32,7 +34,7 @@ import tools.data.input.SeekableLittleEndianAccessor;
/**
*
* @author XoticStory; modified by kevintjuh93
* @author XoticStory; modified by kevintjuh93, Ronan
*/
public final class UseSolomonHandler extends AbstractMaplePacketHandler {
@@ -42,16 +44,35 @@ public final class UseSolomonHandler extends AbstractMaplePacketHandler {
short slot = slea.readShort();
int itemId = slea.readInt();
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
Item slotItem = c.getPlayer().getInventory(MapleInventoryType.USE).getItem(slot);
int gachaexp = ii.getExpById(itemId);
if (c.getPlayer().getInventory(MapleInventoryType.USE).countById(itemId) <= 0 || slotItem.getItemId() != itemId || c.getPlayer().getLevel() > ii.getMaxLevelById(itemId)) {
return;
if (c.tryacquireClient()) {
try {
MapleCharacter chr = c.getPlayer();
MapleInventory inv = chr.getInventory(MapleInventoryType.USE);
inv.lockInventory();
try {
Item slotItem = inv.getItem(slot);
if (slotItem == null) {
return;
}
long gachaexp = ii.getExpById(itemId);
if (slotItem.getItemId() != itemId || slotItem.getQuantity() <= 0 || chr.getLevel() > ii.getMaxLevelById(itemId)) {
return;
}
if (gachaexp + chr.getGachaExp() > Integer.MAX_VALUE) {
return;
}
chr.addGachaExp((int) gachaexp);
MapleInventoryManipulator.removeFromSlot(c, MapleInventoryType.USE, slot, (short) 1, false);
} finally {
inv.unlockInventory();
}
} finally {
c.releaseClient();
}
}
if ((c.getPlayer().getGachaExp() + gachaexp) > Integer.MAX_VALUE) {
return;
}
c.getPlayer().gainGachaExp(gachaexp);
MapleInventoryManipulator.removeFromSlot(c, MapleInventoryType.USE, slot, (short) 1, false);
c.announce(MaplePacketCreator.enableActions());
}
}

View File

@@ -29,6 +29,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Semaphore;
@@ -76,6 +77,7 @@ public class MapleMatchCheckerCoordinator {
private Map<Integer, MapleMatchCheckingEntry> confirmingMembers = new HashMap<>();
private int confirmCount;
private boolean active = true;
private String message;
@@ -108,10 +110,30 @@ public class MapleMatchCheckerCoordinator {
return false;
}
private boolean isMatchActive() {
return active;
}
private void setMatchActive(boolean a) {
active = a;
}
private Set<Integer> getMatchPlayers() {
return confirmingMembers.keySet();
}
private Set<Integer> getAcceptedMatchPlayers() {
Set<Integer> s = new HashSet<>();
for (Entry<Integer, MapleMatchCheckingEntry> e : confirmingMembers.entrySet()) {
if (e.getValue().getAccept()) {
s.add(e.getKey());
}
}
return s;
}
private Set<MapleCharacter> getMatchCharacters() {
Set<MapleCharacter> players = new HashSet<>();
@@ -197,6 +219,20 @@ public class MapleMatchCheckerCoordinator {
return true;
}
private void reenablePlayerMatching(Set<Integer> matchPlayers) {
for (Integer cid : matchPlayers) {
MapleMatchCheckingElement mmce = matchEntries.get(cid);
if (mmce != null) {
synchronized (mmce) {
if (!mmce.isMatchActive()) {
matchEntries.remove(cid);
}
}
}
}
}
public int getMatchConfirmationLeaderid(int cid) {
MapleMatchCheckingElement mmce = matchEntries.get(cid);
if (mmce != null) {
@@ -215,6 +251,15 @@ public class MapleMatchCheckerCoordinator {
}
}
public boolean isMatchConfirmationActive(int cid) {
MapleMatchCheckingElement mmce = matchEntries.get(cid);
if (mmce != null) {
return mmce.active;
} else {
return false;
}
}
private void createMatchConfirmationInternal(MatchCheckerType matchType, int world, int leaderCid, AbstractMatchCheckerListener leaderListener, Set<Integer> players, String message) {
MapleMatchCheckingElement mmce = new MapleMatchCheckingElement(matchType, leaderCid, world, leaderListener, players, message);
@@ -224,9 +269,7 @@ public class MapleMatchCheckerCoordinator {
mmce.dispatchMatchCreated();
if (mmce.acceptEntry(leaderCid)) {
acceptMatchElement(mmce, leaderCid);
}
acceptMatchElement(mmce, leaderCid);
}
public boolean createMatchConfirmation(MatchCheckerType matchType, int world, int leaderCid, Set<Integer> players, String message) {
@@ -239,6 +282,8 @@ public class MapleMatchCheckerCoordinator {
AbstractMatchCheckerListener leaderListener = matchType.getListener();
createMatchConfirmationInternal(matchType, world, leaderCid, leaderListener, players, message);
return true;
} else {
reenablePlayerMatching(players);
}
} finally {
unpoolMatchPlayers(players);
@@ -255,7 +300,7 @@ public class MapleMatchCheckerCoordinator {
}
private void disposeMatchElement(MapleMatchCheckingElement mmce) {
Set<Integer> matchPlayers = mmce.getMatchPlayers();
Set<Integer> matchPlayers = mmce.getAcceptedMatchPlayers();
while (!poolMatchPlayers(matchPlayers)) {
try {
Thread.sleep(1000);
@@ -288,6 +333,8 @@ public class MapleMatchCheckerCoordinator {
}
private void dismissMatchElement(MapleMatchCheckingElement mmce, int cid) {
mmce.setMatchActive(false);
unpoolMatchPlayer(cid);
disposeMatchElement(mmce);
@@ -302,14 +349,22 @@ public class MapleMatchCheckerCoordinator {
if (poolMatchPlayer(cid)) {
try {
MapleMatchCheckingElement mmce = matchEntries.get(cid);
if (mmce != null) {
if (accept) {
acceptMatchElement(mmce, cid);
} else {
denyMatchElement(mmce, cid);
synchronized (mmce) {
if (!mmce.isMatchActive()) { // thanks Alex (CanIGetaPR) for noticing that exploiters could stall on match checking
matchEntries.remove(cid);
return false;
}
if (accept) {
acceptMatchElement(mmce, cid);
} else {
denyMatchElement(mmce, cid);
matchEntries.remove(cid);
}
}
return true;
}
} finally {
@@ -337,8 +392,14 @@ public class MapleMatchCheckerCoordinator {
MapleMatchCheckingElement mmce = matchEntries.get(cid);
if (mmce != null) {
dismissMatchElement(mmce, cid);
return true;
synchronized (mmce) {
if (!mmce.isMatchActive()) {
return false;
}
dismissMatchElement(mmce, cid);
return true;
}
}
} finally {
unpoolMatchPlayer(cid);

View File

@@ -305,12 +305,12 @@ public class MapleSessionCoordinator {
}
public void closeLoginSession(IoSession session) {
String remoteIp = getSessionRemoteAddress(session);
Set<IoSession> lrh = loginRemoteHosts.get(remoteIp);
String remoteHost = getSessionRemoteAddress(session);
Set<IoSession> lrh = loginRemoteHosts.get(remoteHost);
if (lrh != null) {
lrh.remove(session);
if (lrh.isEmpty()) {
loginRemoteHosts.remove(remoteIp);
loginRemoteHosts.remove(remoteHost);
}
}

View File

@@ -20,6 +20,7 @@
package net.server.coordinator.matchchecker;
import net.server.coordinator.matchchecker.listener.MatchCheckerGuildCreation;
import net.server.coordinator.matchchecker.listener.MatchCheckerCPQChallenge;
/**
*
@@ -29,7 +30,8 @@ public class MatchCheckerListenerFactory {
public enum MatchCheckerType {
GUILD_CREATION(MatchCheckerGuildCreation.loadListener());
GUILD_CREATION(MatchCheckerGuildCreation.loadListener()),
CPQ_CHALLENGE(MatchCheckerCPQChallenge.loadListener());
private final AbstractMatchCheckerListener listener;

View File

@@ -0,0 +1,119 @@
/*
This file is part of the HeavenMS MapleStory Server
Copyleft (L) 2016 - 2018 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation version 3 as published by
the Free Software Foundation. You may not use, modify or distribute
this program under any other version of the GNU Affero General Public
License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.server.coordinator.matchchecker.listener;
import client.MapleCharacter;
import constants.LanguageConstants;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import net.server.coordinator.matchchecker.AbstractMatchCheckerListener;
import net.server.coordinator.matchchecker.MatchCheckerListenerRecipe;
import net.server.world.MaplePartyCharacter;
import scripting.npc.NPCConversationManager;
import scripting.npc.NPCScriptManager;
/**
*
* @author Ronan
*/
public class MatchCheckerCPQChallenge implements MatchCheckerListenerRecipe {
public static AbstractMatchCheckerListener loadListener() {
return (new MatchCheckerCPQChallenge()).getListener();
}
private static MapleCharacter getChallenger(int leaderid, Set<MapleCharacter> matchPlayers) {
MapleCharacter leader = null;
for (MapleCharacter chr : matchPlayers) {
if (chr.getId() == leaderid && chr.getClient() != null) {
leader = chr;
break;
}
}
return leader;
}
@Override
public AbstractMatchCheckerListener getListener() {
return new AbstractMatchCheckerListener() {
@Override
public void onMatchCreated(MapleCharacter leader, Set<MapleCharacter> nonLeaderMatchPlayers, String message) {
NPCConversationManager cm = leader.getClient().getCM();
int npcid = cm.getNpc();
MapleCharacter ldr = null;
for (MapleCharacter chr : nonLeaderMatchPlayers) {
ldr = chr;
break;
}
MapleCharacter chr = leader;
List<MaplePartyCharacter> chrMembers = new LinkedList<>();
for (MaplePartyCharacter mpc : chr.getParty().getMembers()) {
chrMembers.add(mpc);
}
if (message.contentEquals("cpq1")) {
NPCScriptManager.getInstance().start("cpqchallenge", ldr.getClient(), npcid, chrMembers);
} else {
NPCScriptManager.getInstance().start("cpqchallenge2", ldr.getClient(), npcid, chrMembers);
}
cm.sendOk(LanguageConstants.getMessage(chr, LanguageConstants.CPQChallengeRoomSent));
}
@Override
public void onMatchAccepted(int leaderid, Set<MapleCharacter> matchPlayers, String message) {
MapleCharacter chr = getChallenger(leaderid, matchPlayers);
MapleCharacter ldr = null;
for (MapleCharacter ch : matchPlayers) {
if (ch != chr) {
ldr = ch;
break;
}
}
if (message.contentEquals("cpq1")) {
ldr.getClient().getCM().startCPQ(chr, ldr.getMapId() + 1);
} else {
ldr.getClient().getCM().startCPQ2(chr, ldr.getMapId() + 1);
}
ldr.getParty().setEnemy(chr.getParty());
chr.getParty().setEnemy(ldr.getParty());
chr.setChallenged(false);
}
@Override
public void onMatchDeclined(int leaderid, Set<MapleCharacter> matchPlayers, String message) {
MapleCharacter chr = getChallenger(leaderid, matchPlayers);
chr.dropMessage(5, LanguageConstants.getMessage(chr, LanguageConstants.CPQChallengeRoomDenied));
}
@Override
public void onMatchDismissed(int leaderid, Set<MapleCharacter> matchPlayers, String message) {}
};
}
}

View File

@@ -166,13 +166,29 @@ public class MatchCheckerGuildCreation implements MatchCheckerListenerRecipe {
@Override
public void onMatchDismissed(int leaderid, Set<MapleCharacter> matchPlayers, String message) {
MapleCharacter leader = null;
for (MapleCharacter chr : matchPlayers) {
if (chr.getId() == leaderid) {
leader = chr;
break;
}
}
String msg;
if (leader != null && leader.getParty() == null) {
msg = "The Guild creation has been dismissed since the leader left the founding party.";
} else {
msg = "The Guild creation has been dismissed since a member was already in a party when they answered.";
}
for (MapleCharacter chr : matchPlayers) {
if (chr.getId() == leaderid && chr.getClient() != null) {
MapleParty.leaveParty(chr.getParty(), chr.getClient());
}
if (chr.isLoggedinWorld()) {
chr.message("All cofounders must not be in a party when performing the Guild creation.");
chr.message(msg);
}
}
}

View File

@@ -50,6 +50,7 @@ import net.server.audit.locks.MonitoredLockType;
import net.server.coordinator.MapleInviteCoordinator;
import net.server.coordinator.MapleInviteCoordinator.InviteType;
import net.server.coordinator.MapleInviteCoordinator.InviteResult;
import net.server.coordinator.MapleMatchCheckerCoordinator;
public class MapleGuild {
@@ -752,8 +753,9 @@ public class MapleGuild {
Set<MapleCharacter> guildMembers = new HashSet<>();
guildMembers.add(guildLeader);
MapleMatchCheckerCoordinator mmce = guildLeader.getWorldServer().getMatchCheckerCoordinator();
for (MapleCharacter chr : guildLeader.getMap().getAllPlayers()) {
if (chr.getParty() == null && chr.getGuild() == null) {
if (chr.getParty() == null && chr.getGuild() == null && mmce.getMatchConfirmationLeaderid(chr.getId()) == -1) {
guildMembers.add(chr);
}
}
@@ -765,7 +767,7 @@ public class MapleGuild {
try {
ResultSet rs;
Connection con = DatabaseConnection.getConnection();
try (PreparedStatement ps = con.prepareStatement("SELECT `name`, `GP`, `logoBG`, `logoBGColor`, `logo`, `logoColor` FROM guilds WHERE NOT `guildid` = '1' ORDER BY `GP` DESC LIMIT 50")) {
try (PreparedStatement ps = con.prepareStatement("SELECT `name`, `GP`, `logoBG`, `logoBGColor`, `logo`, `logoColor` FROM guilds ORDER BY `GP` DESC LIMIT 50")) {
rs = ps.executeQuery();
c.announce(MaplePacketCreator.showGuildRanks(npcid, rs));
}

View File

@@ -64,15 +64,17 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
String remoteHost = getRemoteIp(c.getSession());
if (!remoteHost.contentEquals("null")) {
if (remoteHost.startsWith("127.")) {
if (!ServerConstants.LOCALSERVER) { // thanks Mills for noting HOST can also have a field named "localhost"
c.announce(MaplePacketCreator.getLoginFailed(13)); // cannot login as localhost if it's not a local server
return;
}
} else {
if (ServerConstants.LOCALSERVER) {
c.announce(MaplePacketCreator.getLoginFailed(13)); // cannot login as non-localhost if it's a local server
return;
if (ServerConstants.USE_IP_VALIDATION) { // thanks Alex (CanIGetaPR) for suggesting IP validation as a server flag
if (remoteHost.startsWith("127.")) {
if (!ServerConstants.LOCALSERVER) { // thanks Mills for noting HOST can also have a field named "localhost"
c.announce(MaplePacketCreator.getLoginFailed(13)); // cannot login as localhost if it's not a local server
return;
}
} else {
if (ServerConstants.LOCALSERVER) {
c.announce(MaplePacketCreator.getLoginFailed(13)); // cannot login as non-localhost if it's a local server
return;
}
}
}
} else {

View File

@@ -334,7 +334,7 @@ public class MapleParty {
player.dropMessage(5, "You cannot request a party creation while participating the Ariant Battle Arena.");
return false;
}
MaplePartyCharacter partyplayer = new MaplePartyCharacter(player);
party = player.getWorldServer().createParty(partyplayer);
player.setParty(party);
@@ -343,6 +343,7 @@ public class MapleParty {
player.silentPartyUpdate();
player.partyOperationUpdate(party, null);
player.announce(MaplePacketCreator.partyCreated(party, partyplayer.getId()));
return true;
@@ -365,7 +366,7 @@ public class MapleParty {
if (party.getMembers().size() < 6) {
MaplePartyCharacter partyplayer = new MaplePartyCharacter(player);
player.getMap().addPartyMember(player);
world.updateParty(party.getId(), PartyOperation.JOIN, partyplayer);
player.receivePartyMemberHP();
player.updatePartyMemberHP();