diff --git a/build/built-jar.properties b/build/built-jar.properties index 6b2f953da4..318aaf3acf 100644 --- a/build/built-jar.properties +++ b/build/built-jar.properties @@ -1,4 +1,4 @@ -#Sun, 22 Oct 2017 15:23:02 -0200 +#Fri, 27 Oct 2017 11:16:00 -0200 C\:\\Nexon\\MapleSolaxia\\MapleSolaxiaV2= diff --git a/build/classes/constants/ServerConstants.class b/build/classes/constants/ServerConstants.class index 76d1995797..e1d0b47d5f 100644 Binary files a/build/classes/constants/ServerConstants.class and b/build/classes/constants/ServerConstants.class differ diff --git a/build/classes/net/server/channel/handlers/AbstractDealDamageHandler.class b/build/classes/net/server/channel/handlers/AbstractDealDamageHandler.class index 07ffae0055..4adc7278ec 100644 Binary files a/build/classes/net/server/channel/handlers/AbstractDealDamageHandler.class and b/build/classes/net/server/channel/handlers/AbstractDealDamageHandler.class differ diff --git a/build/classes/net/server/channel/handlers/DistributeAPHandler.class b/build/classes/net/server/channel/handlers/DistributeAPHandler.class index f11244c3e8..afc2f5aee5 100644 Binary files a/build/classes/net/server/channel/handlers/DistributeAPHandler.class and b/build/classes/net/server/channel/handlers/DistributeAPHandler.class differ diff --git a/build/classes/net/server/channel/handlers/DistributeSPHandler.class b/build/classes/net/server/channel/handlers/DistributeSPHandler.class index d0286dbf6c..3e7e3f008a 100644 Binary files a/build/classes/net/server/channel/handlers/DistributeSPHandler.class and b/build/classes/net/server/channel/handlers/DistributeSPHandler.class differ diff --git a/build/classes/net/server/channel/handlers/UseCashItemHandler$1.class b/build/classes/net/server/channel/handlers/UseCashItemHandler$1.class index 20cba0b425..ab66f8d400 100644 Binary files a/build/classes/net/server/channel/handlers/UseCashItemHandler$1.class and b/build/classes/net/server/channel/handlers/UseCashItemHandler$1.class differ diff --git a/build/classes/net/server/channel/handlers/UseCashItemHandler.class b/build/classes/net/server/channel/handlers/UseCashItemHandler.class index 5df32d8bd8..941f896101 100644 Binary files a/build/classes/net/server/channel/handlers/UseCashItemHandler.class and b/build/classes/net/server/channel/handlers/UseCashItemHandler.class differ diff --git a/build/classes/scripting/AbstractPlayerInteraction.class b/build/classes/scripting/AbstractPlayerInteraction.class index 5309869df6..9765f7f063 100644 Binary files a/build/classes/scripting/AbstractPlayerInteraction.class and b/build/classes/scripting/AbstractPlayerInteraction.class differ diff --git a/build/classes/server/life/MapleMonster$1.class b/build/classes/server/life/MapleMonster$1.class index 0b90120e43..c87c4af83f 100644 Binary files a/build/classes/server/life/MapleMonster$1.class and b/build/classes/server/life/MapleMonster$1.class differ diff --git a/build/classes/server/life/MapleMonster$2.class b/build/classes/server/life/MapleMonster$2.class index 947aba94f0..d95e96dba1 100644 Binary files a/build/classes/server/life/MapleMonster$2.class and b/build/classes/server/life/MapleMonster$2.class differ diff --git a/build/classes/server/life/MapleMonster$3.class b/build/classes/server/life/MapleMonster$3.class index da2f603b71..08dcc4a127 100644 Binary files a/build/classes/server/life/MapleMonster$3.class and b/build/classes/server/life/MapleMonster$3.class differ diff --git a/build/classes/server/life/MapleMonster$4.class b/build/classes/server/life/MapleMonster$4.class index 2e0760ef23..ebf19e2fd5 100644 Binary files a/build/classes/server/life/MapleMonster$4.class and b/build/classes/server/life/MapleMonster$4.class differ diff --git a/build/classes/server/life/MapleMonster$5.class b/build/classes/server/life/MapleMonster$5.class index 58f188cec9..e5780de744 100644 Binary files a/build/classes/server/life/MapleMonster$5.class and b/build/classes/server/life/MapleMonster$5.class differ diff --git a/build/classes/server/life/MapleMonster$6.class b/build/classes/server/life/MapleMonster$6.class index 295643d0d0..8dc22c01ff 100644 Binary files a/build/classes/server/life/MapleMonster$6.class and b/build/classes/server/life/MapleMonster$6.class differ diff --git a/build/classes/server/life/MapleMonster$DamageTask.class b/build/classes/server/life/MapleMonster$DamageTask.class index f7fd11c750..ce3aea590d 100644 Binary files a/build/classes/server/life/MapleMonster$DamageTask.class and b/build/classes/server/life/MapleMonster$DamageTask.class differ diff --git a/build/classes/server/life/MapleMonster.class b/build/classes/server/life/MapleMonster.class index 73681f144e..eae7027ff4 100644 Binary files a/build/classes/server/life/MapleMonster.class and b/build/classes/server/life/MapleMonster.class differ diff --git a/build/classes/server/maps/MapleMap.class b/build/classes/server/maps/MapleMap.class index 0f76cc8b1e..cb98bf2e39 100644 Binary files a/build/classes/server/maps/MapleMap.class and b/build/classes/server/maps/MapleMap.class differ diff --git a/build/classes/tools/DatabaseConnection.class b/build/classes/tools/DatabaseConnection.class index 096ce3f255..b5d376309d 100644 Binary files a/build/classes/tools/DatabaseConnection.class and b/build/classes/tools/DatabaseConnection.class differ diff --git a/build/classes/tools/FilePrinter.class b/build/classes/tools/FilePrinter.class index 54a30c3c80..38ef879f6c 100644 Binary files a/build/classes/tools/FilePrinter.class and b/build/classes/tools/FilePrinter.class differ diff --git a/dist/MapleSolaxia.jar b/dist/MapleSolaxia.jar index b93b8c14af..7a59496cbf 100644 Binary files a/dist/MapleSolaxia.jar and b/dist/MapleSolaxia.jar differ diff --git a/docs/feature_list.txt b/docs/feature_list.txt index 5acd8fae8b..9c46f20edf 100644 --- a/docs/feature_list.txt +++ b/docs/feature_list.txt @@ -76,6 +76,7 @@ Server potentials: * Owl of Minerva. * Pet item ignore. * Autosaver (periodically saves on DB current state of every player in-game). +* Fixed and randomized versions of HP/MP growth rate, regarding player job. Placeholder for HP/MP washing feature. Admin/GM commands: * Server commands layered by GM levels. diff --git a/docs/mychanges_ptbr.txt b/docs/mychanges_ptbr.txt index 2fd24019d7..d9e57f10a5 100644 --- a/docs/mychanges_ptbr.txt +++ b/docs/mychanges_ptbr.txt @@ -602,4 +602,16 @@ Corrigido Map chair n Corrigido itens com ownership diferente sendo agrupados num mesmo slot, perdendo a referencia de dono. Implementado feature "Arrange Items" do MapleStorage. Ele faz os devidos agrupamentos de itens e organiza os itens do storage. Corrigido storage mesclando itens que deveriam ser únicos (que não poderiam haver mais de um num mesmo slot, ou no inventário do jogador). -Corrigido bug onde colocar um pet equipado no Cash Inventory e voltar ao jogo causaria crash no jogador. \ No newline at end of file +Corrigido bug onde colocar um pet equipado no Cash Inventory e voltar ao jogo causaria crash no jogador. + +23 - 24 Outubro 2017, +Adicionado proteção contra acesso concorrente em módulos de MapleMonster. +Corrigido bug com Venom fazendo aparecer "dano 1" no DOT. +Corrigido sistema de EXP agora contabilizando devidamente HP curado pelo mob na distribuição do EXP. + +25 Outubro 2017, +Corrigido alguns problemas com as configs de inicialização do HikariCP. +Corrigido bug com inicialização de NPC scripts podendo desconectar o jogador se instaciado múltiplas vezes. + +26 Outubro 2017, +Adicionado feature de randomização dos stats ganhos de HP e MP. Stat ganho de MP leva na contabilização INT do jogador. \ No newline at end of file diff --git a/nbproject/private/private.properties b/nbproject/private/private.properties index 2948abe44a..59390d982f 100644 --- a/nbproject/private/private.properties +++ b/nbproject/private/private.properties @@ -7,4 +7,4 @@ file.reference.slf4j-api-1.6.6.jar=C:\\Nexon\\MapleSolaxia\\MapleSolaxiaV2\\core file.reference.slf4j-jdk14-1.7.5.jar=C:\\Nexon\\MapleSolaxia\\MapleSolaxiaV2\\cores\\slf4j-jdk14-1.7.5.jar javac.debug=true javadoc.preview=true -user.properties.file=C:\\Users\\RonanLana\\AppData\\Roaming\\NetBeans\\8.0.2\\build.properties +user.properties.file=C:\\Users\\USER\\AppData\\Roaming\\NetBeans\\8.0.2\\build.properties diff --git a/nbproject/private/private.xml b/nbproject/private/private.xml index 5b4a228d81..3287222d26 100644 --- a/nbproject/private/private.xml +++ b/nbproject/private/private.xml @@ -2,15 +2,6 @@ - - file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/PlayerLoggedinHandler.java - file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/client/command/Commands.java - file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/CashOperationHandler.java - file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/server/quest/requirements/QuestRequirement.java - file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/net/server/channel/handlers/InventoryMergeHandler.java - file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/server/MapleStorage.java - file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/client/MapleCharacter.java - file:/C:/Nexon/MapleSolaxia/MapleSolaxiaV2/src/server/MapleStorageInventory.java - + diff --git a/scripts/npc/9000021.js b/scripts/npc/9000021.js index b20cce22d6..c6d7bbc9d3 100644 --- a/scripts/npc/9000021.js +++ b/scripts/npc/9000021.js @@ -35,6 +35,11 @@ function action(mode, type, selection) { if (mode < 0) cm.dispose(); else { + if (mode == 0 && type > 0) { + cm.dispose(); + return; + } + if (mode == 1) status++; else diff --git a/src/constants/ServerConstants.java b/src/constants/ServerConstants.java index 50097135c9..5e32b4c608 100644 --- a/src/constants/ServerConstants.java +++ b/src/constants/ServerConstants.java @@ -40,6 +40,7 @@ public class ServerConstants { public static final boolean USE_MTS = false; public static final boolean USE_FAMILY_SYSTEM = false; public static final boolean USE_DUEY = true; + public static final boolean USE_RANDOMIZE_HPMP_GAIN = true; //Enables randomizing on MaxHP/MaxMP gains and INT accounting for the MaxMP gain. public static final boolean USE_STORAGE_ITEM_SORT = true; //Enables storage "Arrange Items" feature. public static final boolean USE_ITEM_SORT = true; //Enables inventory "Item Sort/Merge" feature. public static final boolean USE_ITEM_SORT_BY_NAME = false; //Item sorting based on name rather than id. @@ -49,7 +50,7 @@ public class ServerConstants { public static final boolean USE_AUTOSAVE = true; //Enables server autosaving feature (saves characters to DB each 1 hour). public static final boolean USE_SERVER_AUTOASSIGNER = true; //Server-builtin autoassigner, uses algorithm based on distributing AP accordingly with required secondary stat on equipments. public static final boolean USE_REFRESH_RANK_MOVE = true; - public static final boolean USE_ENFORCE_OWL_SUGGESTIONS = false;//Forces the Owl of Minerva to always display the defined item array on GameConstants.OWL_DATA instead of the featured by the players. + public static final boolean USE_ENFORCE_OWL_SUGGESTIONS = false;//Forces the Owl of Minerva to always display the defined item array on GameConstants.OWL_DATA instead of those featured by the players. public static final boolean USE_ENFORCE_UNMERCHABLE_PET = true; //Forces players to not sell pets via merchants. (since non-named pets gets dirty name and other possible DB-related issues) public static final boolean USE_ENFORCE_MDOOR_POSITION = true; //Forces mystic door to be spawned near spawnpoints. (since things bugs out other way, and this helps players to locate the door faster) public static final boolean USE_ERASE_PERMIT_ON_OPENSHOP = true;//Forces "shop permit" item to be consumed when player deploy his/her player shop. @@ -70,9 +71,11 @@ public class ServerConstants { public static final int PARTY_EXPERIENCE_MOD = 1; //Change for event stuff. - public static final byte MAX_MONITORED_BUFFSTATS = 5; //Limits accounting for "dormant" buff effects, that should take place when stronger stat buffs expires. - public static final int MAX_AP = 32767; //Max AP allotted on the auto-assigner. - public static final int MAX_EVENT_LEVELS = 8; //Event has different levels of rewarding system. + //Miscellaneous COnfiguration + public static final byte MIN_UNDERLEVEL_FOR_EXP_GAIN = 5; //Characters are unable to get EXP from a mob if their level are under this threshold, only if "USE_UNDERLEVELED_EXP_BLOCK" is enabled. + public static final byte MAX_MONITORED_BUFFSTATS = 5; //Limits accounting for "dormant" buff effects, that should take place when stronger stat buffs expires. + public static final int MAX_AP = 32767; //Max AP allotted on the auto-assigner. + public static final int MAX_EVENT_LEVELS = 8; //Event has different levels of rewarding system. public static final long BLOCK_NPC_RACE_CONDT = (long)(0.5 * 1000); //Time the player client must wait before reopening a conversation with an NPC. public static final long PET_LOOT_UPON_ATTACK = (long)(0.7 * 1000); //Time the pet must wait before trying to pick items up. diff --git a/src/net/server/channel/handlers/AbstractDealDamageHandler.java b/src/net/server/channel/handlers/AbstractDealDamageHandler.java index cbec85894a..8e12bebde8 100644 --- a/src/net/server/channel/handlers/AbstractDealDamageHandler.java +++ b/src/net/server/channel/handlers/AbstractDealDamageHandler.java @@ -582,19 +582,20 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl ret.speed = lea.readByte(); lea.skip(4); } - int calcDmgMax = 0; - - // Find the base damage to base futher calculations on. - // Several skills have their own formula in this section. + + // Find the base damage to base futher calculations on. + // Several skills have their own formula in this section. + int calcDmgMax = 0; + if(magic && ret.skill != 0) { calcDmgMax = (chr.getTotalMagic() * chr.getTotalMagic() / 1000 + chr.getTotalMagic()) / 30 + chr.getTotalInt() / 200; } else if(ret.skill == 4001344 || ret.skill == NightWalker.LUCKY_SEVEN || ret.skill == NightLord.TRIPLE_THROW) { calcDmgMax = (chr.getTotalLuk() * 5) * chr.getTotalWatk() / 100; } else if(ret.skill == DragonKnight.DRAGON_ROAR) { calcDmgMax = (chr.getTotalStr() * 4 + chr.getTotalDex()) * chr.getTotalWatk() / 100; - } else if(ret.skill == NightLord.VENOMOUS_STAR || ret.skill == Shadower.VENOMOUS_STAB) { - calcDmgMax = (int) (18.5 * (chr.getTotalStr() + chr.getTotalLuk()) + chr.getTotalDex() * 2) / 100 * chr.calculateMaxBaseDamage(chr.getTotalWatk()); - } else { + } else if(ret.skill == NightLord.VENOMOUS_STAR || ret.skill == Shadower.VENOMOUS_STAB) { + calcDmgMax = (int) (18.5 * (chr.getTotalStr() + chr.getTotalLuk()) + chr.getTotalDex() * 2) / 100 * chr.calculateMaxBaseDamage(chr.getTotalWatk()); + } else { calcDmgMax = chr.calculateMaxBaseDamage(chr.getTotalWatk()); } @@ -739,74 +740,74 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl } if(ret.skill != 0) { - Skill skill = SkillFactory.getSkill(ret.skill); - if(skill.getElement() != Element.NEUTRAL && chr.getBuffedValue(MapleBuffStat.ELEMENTAL_RESET) == null) { - // The skill has an element effect, so we need to factor that in. - if(monster != null) { - ElementalEffectiveness eff = monster.getEffectiveness(skill.getElement()); - if(eff == ElementalEffectiveness.WEAK) { + Skill skill = SkillFactory.getSkill(ret.skill); + if(skill.getElement() != Element.NEUTRAL && chr.getBuffedValue(MapleBuffStat.ELEMENTAL_RESET) == null) { + // The skill has an element effect, so we need to factor that in. + if(monster != null) { + ElementalEffectiveness eff = monster.getElementalEffectiveness(skill.getElement()); + if(eff == ElementalEffectiveness.WEAK) { + calcDmgMax *= 1.5; + } else if(eff == ElementalEffectiveness.STRONG) { + //calcDmgMax *= 0.5; + } + } else { + // Since we already know the skill has an elemental attribute, but we dont know if the monster is weak or not, lets + // take the safe approach and just assume they are weak. calcDmgMax *= 1.5; - } else if(eff == ElementalEffectiveness.STRONG) { - //calcDmgMax *= 0.5; } - } else { - // Since we already know the skill has an elemental attribute, but we dont know if the monster is weak or not, lets - // take the safe approach and just assume they are weak. - calcDmgMax *= 1.5; } - } - if(ret.skill == FPWizard.POISON_BREATH || ret.skill == FPMage.POISON_MIST || ret.skill == FPArchMage.FIRE_DEMON || ret.skill == ILArchMage.ICE_DEMON) { - if(monster != null) { - // Turns out poison is completely server side, so I don't know why I added this. >.< - //calcDmgMax = monster.getHp() / (70 - chr.getSkillLevel(skill)); - } - } else if(ret.skill == Hermit.SHADOW_WEB) { - if(monster != null) { - calcDmgMax = monster.getHp() / (50 - chr.getSkillLevel(skill)); - } - } + if(ret.skill == FPWizard.POISON_BREATH || ret.skill == FPMage.POISON_MIST || ret.skill == FPArchMage.FIRE_DEMON || ret.skill == ILArchMage.ICE_DEMON) { + if(monster != null) { + // Turns out poison is completely server side, so I don't know why I added this. >.< + //calcDmgMax = monster.getHp() / (70 - chr.getSkillLevel(skill)); + } + } else if(ret.skill == Hermit.SHADOW_WEB) { + if(monster != null) { + calcDmgMax = monster.getHp() / (50 - chr.getSkillLevel(skill)); + } + } } for (int j = 0; j < ret.numDamage; j++) { - int damage = lea.readInt(); - int hitDmgMax = calcDmgMax; - if(ret.skill == Buccaneer.BARRAGE) { - if(j > 3) - hitDmgMax *= Math.pow(2, (j - 3)); - } - if(shadowPartner) { - // For shadow partner, the second half of the hits only do 50% damage. So calc that - // in for the crit effects. - if(j >= ret.numDamage / 2) { - hitDmgMax *= 0.5; - } - } - - if(ret.skill == Marksman.SNIPE) { - damage = 195000 + Randomizer.nextInt(5000); - hitDmgMax = 200000; - } - - int maxWithCrit = hitDmgMax; - if(canCrit) // They can crit, so up the max. - maxWithCrit *= 2; - - // Warn if the damage is over 1.5x what we calculated above. - if(damage > maxWithCrit * 1.5) { - AutobanFactory.DAMAGE_HACK.alert(chr, "DMG: " + damage + " MaxDMG: " + maxWithCrit + " SID: " + ret.skill + " MobID: " + (monster != null ? monster.getId() : "null") + " Map: " + chr.getMap().getMapName() + " (" + chr.getMapId() + ")"); - } - - // Add a ab point if its over 5x what we calculated. - if(damage > maxWithCrit * 5) { - AutobanFactory.DAMAGE_HACK.addPoint(chr.getAutobanManager(), "DMG: " + damage + " MaxDMG: " + maxWithCrit + " SID: " + ret.skill + " MobID: " + (monster != null ? monster.getId() : "null") + " Map: " + chr.getMap().getMapName() + " (" + chr.getMapId() + ")"); - } - - if (ret.skill == Marksman.SNIPE || (canCrit && damage > hitDmgMax)) { - // If the skill is a crit, inverse the damage to make it show up on clients. - damage = -Integer.MAX_VALUE + damage - 1; - } - - allDamageNumbers.add(damage); + int damage = lea.readInt(); + int hitDmgMax = calcDmgMax; + if(ret.skill == Buccaneer.BARRAGE) { + if(j > 3) + hitDmgMax *= Math.pow(2, (j - 3)); + } + if(shadowPartner) { + // For shadow partner, the second half of the hits only do 50% damage. So calc that + // in for the crit effects. + if(j >= ret.numDamage / 2) { + hitDmgMax *= 0.5; + } + } + + if(ret.skill == Marksman.SNIPE) { + damage = 195000 + Randomizer.nextInt(5000); + hitDmgMax = 200000; + } + + int maxWithCrit = hitDmgMax; + if(canCrit) // They can crit, so up the max. + maxWithCrit *= 2; + + // Warn if the damage is over 1.5x what we calculated above. + if(damage > maxWithCrit * 1.5) { + AutobanFactory.DAMAGE_HACK.alert(chr, "DMG: " + damage + " MaxDMG: " + maxWithCrit + " SID: " + ret.skill + " MobID: " + (monster != null ? monster.getId() : "null") + " Map: " + chr.getMap().getMapName() + " (" + chr.getMapId() + ")"); + } + + // Add a ab point if its over 5x what we calculated. + if(damage > maxWithCrit * 5) { + AutobanFactory.DAMAGE_HACK.addPoint(chr.getAutobanManager(), "DMG: " + damage + " MaxDMG: " + maxWithCrit + " SID: " + ret.skill + " MobID: " + (monster != null ? monster.getId() : "null") + " Map: " + chr.getMap().getMapName() + " (" + chr.getMapId() + ")"); + } + + if (ret.skill == Marksman.SNIPE || (canCrit && damage > hitDmgMax)) { + // If the skill is a crit, inverse the damage to make it show up on clients. + damage = -Integer.MAX_VALUE + damage - 1; + } + + allDamageNumbers.add(damage); } if (ret.skill != Corsair.RAPID_FIRE || ret.skill != Aran.HIDDEN_FULL_DOUBLE || ret.skill != Aran.HIDDEN_FULL_TRIPLE || ret.skill != Aran.HIDDEN_OVER_DOUBLE || ret.skill != Aran.HIDDEN_OVER_TRIPLE) { lea.skip(4); diff --git a/src/net/server/channel/handlers/DistributeAPHandler.java b/src/net/server/channel/handlers/DistributeAPHandler.java index 1f6b6ba010..92274b49eb 100644 --- a/src/net/server/channel/handlers/DistributeAPHandler.java +++ b/src/net/server/channel/handlers/DistributeAPHandler.java @@ -27,6 +27,7 @@ import client.MapleJob; import client.MapleStat; import client.Skill; import client.SkillFactory; +import constants.ServerConstants; import constants.skills.BlazeWizard; import constants.skills.Brawler; import constants.skills.DawnWarrior; @@ -34,24 +35,26 @@ import constants.skills.Magician; import constants.skills.Warrior; import net.AbstractMaplePacketHandler; import tools.MaplePacketCreator; +import tools.Randomizer; import tools.data.input.SeekableLittleEndianAccessor; public final class DistributeAPHandler extends AbstractMaplePacketHandler { private static final int max = 32767; + @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { slea.readInt(); int num = slea.readInt(); if (c.getPlayer().getRemainingAp() > 0) { - if (addStat(c, num)) { + if (addStat(c, num, false)) { c.getPlayer().setRemainingAp(c.getPlayer().getRemainingAp() - 1); c.getPlayer().updateSingleStat(MapleStat.AVAILABLEAP, c.getPlayer().getRemainingAp()); } } c.announce(MaplePacketCreator.enableActions()); } - - static boolean addStat(MapleClient c, int apTo) { + + public static boolean addStat(MapleClient c, int apTo, boolean usedAPReset) { switch (apTo) { case 64: // Str if (c.getPlayer().getStr() >= max) { @@ -90,74 +93,202 @@ public final class DistributeAPHandler extends AbstractMaplePacketHandler { return true; } - static int addHP(MapleClient c) { + private static int addHP(MapleClient c) { MapleCharacter player = c.getPlayer(); MapleJob job = player.getJob(); int MaxHP = player.getMaxHp(); if (player.getHpMpApUsed() > 9999 || MaxHP >= 30000) { return MaxHP; } - if (job.isA(MapleJob.WARRIOR) || job.isA(MapleJob.DAWNWARRIOR1) || job.isA(MapleJob.ARAN1)) { + + return MaxHP + calcHpChange(player, job, false); + } + + public static int calcHpChange(MapleCharacter player, MapleJob job, boolean usedAPReset) { + int MaxHP = 0; + + if (job.isA(MapleJob.WARRIOR) || job.isA(MapleJob.DAWNWARRIOR1)) { Skill increaseHP = SkillFactory.getSkill(job.isA(MapleJob.DAWNWARRIOR1) ? DawnWarrior.MAX_HP_INCREASE : Warrior.IMPROVED_MAXHP); int sLvl = player.getSkillLevel(increaseHP); - + if(sLvl > 0) MaxHP += increaseHP.getEffect(sLvl).getY(); - - MaxHP += 20; + + if(ServerConstants.USE_RANDOMIZE_HPMP_GAIN) { + if (usedAPReset) { + MaxHP += 18; + } else { + MaxHP += Randomizer.rand(18, 22); + } + } else { + MaxHP += 20; + } + } else if(job.isA(MapleJob.ARAN1)) { + if(ServerConstants.USE_RANDOMIZE_HPMP_GAIN) { + if (usedAPReset) { + MaxHP += 26; + } else { + MaxHP += Randomizer.rand(26, 30); + } + } else { + MaxHP += 28; + } } else if (job.isA(MapleJob.MAGICIAN) || job.isA(MapleJob.BLAZEWIZARD1)) { - MaxHP += 6; - } else if (job.isA(MapleJob.BOWMAN) || job.isA(MapleJob.WINDARCHER1) || job.isA(MapleJob.THIEF) || job.isA(MapleJob.NIGHTWALKER1)) { - MaxHP += 16; + if(ServerConstants.USE_RANDOMIZE_HPMP_GAIN) { + if (usedAPReset) { + MaxHP += 5; + } else { + MaxHP += Randomizer.rand(5, 9); + } + } else { + MaxHP += 6; + } + } else if (job.isA(MapleJob.THIEF) || job.isA(MapleJob.NIGHTWALKER1)) { + if(ServerConstants.USE_RANDOMIZE_HPMP_GAIN) { + if (usedAPReset) { + MaxHP += 14; + } else { + MaxHP += Randomizer.rand(14, 18); + } + } else { + MaxHP += 16; + } + } else if(job.isA(MapleJob.BOWMAN) || job.isA(MapleJob.WINDARCHER1)) { + if(ServerConstants.USE_RANDOMIZE_HPMP_GAIN) { + if (usedAPReset) { + MaxHP += 14; + } else { + MaxHP += Randomizer.rand(14, 18); + } + } else { + MaxHP += 16; + } } else if (job.isA(MapleJob.PIRATE) || job.isA(MapleJob.THUNDERBREAKER1)) { Skill increaseHP = SkillFactory.getSkill(Brawler.IMPROVE_MAX_HP); int sLvl = player.getSkillLevel(increaseHP); - + if(sLvl > 0) MaxHP += increaseHP.getEffect(sLvl).getY(); - - MaxHP += 18; - } else { + + if(ServerConstants.USE_RANDOMIZE_HPMP_GAIN) { + if (usedAPReset) { + MaxHP += 16; + } else { + MaxHP += Randomizer.rand(16, 20); + } + } else { + MaxHP += 18; + } + } else if (usedAPReset) { MaxHP += 8; + } else { + if(ServerConstants.USE_RANDOMIZE_HPMP_GAIN) { + MaxHP += Randomizer.rand(8, 12); + } else { + MaxHP += 10; + } } return MaxHP; } - static int addMP(MapleClient c) { + private static int addMP(MapleClient c) { MapleCharacter player = c.getPlayer(); int MaxMP = player.getMaxMp(); MapleJob job = player.getJob(); if (player.getHpMpApUsed() > 9999 || player.getMaxMp() >= 30000) { return MaxMP; } + + return MaxMP + calcMpChange(player, job, false); + } + + public static int calcMpChange(MapleCharacter player, MapleJob job, boolean usedAPReset) { + int MaxMP = 0; + if (job.isA(MapleJob.WARRIOR) || job.isA(MapleJob.DAWNWARRIOR1) || job.isA(MapleJob.ARAN1)) { - MaxMP += 2; + if(ServerConstants.USE_RANDOMIZE_HPMP_GAIN) { + if(!usedAPReset) { + MaxMP += (Randomizer.rand(2, 4) + (player.getInt() / 10)); + } + else { + MaxMP += (2 + (player.getInt() / 10)); + } + } else { + MaxMP += 3; + } } else if (job.isA(MapleJob.MAGICIAN) || job.isA(MapleJob.BLAZEWIZARD1)) { Skill increaseMP = SkillFactory.getSkill(job.isA(MapleJob.BLAZEWIZARD1) ? BlazeWizard.INCREASING_MAX_MP : Magician.IMPROVED_MAX_MP_INCREASE); int sLvl = player.getSkillLevel(increaseMP); - + if(sLvl > 0) MaxMP += increaseMP.getEffect(sLvl).getY(); - - MaxMP += 18; - } else if (job.isA(MapleJob.BOWMAN) || job.isA(MapleJob.WINDARCHER1) || job.isA(MapleJob.THIEF) || job.isA(MapleJob.NIGHTWALKER1)) { - MaxMP += 10; + + if(ServerConstants.USE_RANDOMIZE_HPMP_GAIN) { + if(!usedAPReset) { + MaxMP += (Randomizer.rand(12, 16) + (player.getInt() / 20)); + } + else { + MaxMP += (12 + (player.getInt() / 20)); + } + } else { + MaxMP += 18; + } + } else if (job.isA(MapleJob.BOWMAN) || job.isA(MapleJob.WINDARCHER1)) { + if(ServerConstants.USE_RANDOMIZE_HPMP_GAIN) { + if(!usedAPReset) { + MaxMP += (Randomizer.rand(6, 8) + (player.getInt() / 10)); + } + else { + MaxMP += (6 + (player.getInt() / 10)); + } + } else { + MaxMP += 10; + } + } else if(job.isA(MapleJob.THIEF) || job.isA(MapleJob.NIGHTWALKER1)) { + if(ServerConstants.USE_RANDOMIZE_HPMP_GAIN) { + if(!usedAPReset) { + MaxMP += (Randomizer.rand(6, 8) + (player.getInt() / 10)); + } + else { + MaxMP += (6 + (player.getInt() / 10)); + } + } else { + MaxMP += 10; + } } else if (job.isA(MapleJob.PIRATE) || job.isA(MapleJob.THUNDERBREAKER1)) { - MaxMP += 14; + if(ServerConstants.USE_RANDOMIZE_HPMP_GAIN) { + if(!usedAPReset) { + MaxMP += (Randomizer.rand(7, 9) + (player.getInt() / 10)); + } + else { + MaxMP += (7 + (player.getInt() / 10)); + } + } else { + MaxMP += 14; + } } else { - MaxMP += 6; + if(ServerConstants.USE_RANDOMIZE_HPMP_GAIN) { + if(!usedAPReset) { + MaxMP += (Randomizer.rand(4, 6) + (player.getInt() / 10)); + } + else { + MaxMP += (4 + (player.getInt() / 10)); + } + } else { + MaxMP += 6; + } } return MaxMP; } - static void addHP(MapleCharacter player, int MaxHP) { + private static void addHP(MapleCharacter player, int MaxHP) { MaxHP = Math.min(30000, MaxHP); player.setHpMpApUsed(player.getHpMpApUsed() + 1); player.setMaxHp(MaxHP); player.updateSingleStat(MapleStat.MAXHP, MaxHP); } - static void addMP(MapleCharacter player, int MaxMP) { + private static void addMP(MapleCharacter player, int MaxMP) { MaxMP = Math.min(30000, MaxMP); player.setHpMpApUsed(player.getHpMpApUsed() + 1); player.setMaxMp(MaxMP); diff --git a/src/net/server/channel/handlers/DistributeSPHandler.java b/src/net/server/channel/handlers/DistributeSPHandler.java index 9f9c80342c..fdc0577443 100644 --- a/src/net/server/channel/handlers/DistributeSPHandler.java +++ b/src/net/server/channel/handlers/DistributeSPHandler.java @@ -39,18 +39,19 @@ public final class DistributeSPHandler extends AbstractMaplePacketHandler { public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { slea.readInt(); int skillid = slea.readInt(); - if (skillid == Aran.HIDDEN_FULL_DOUBLE || skillid == Aran.HIDDEN_FULL_TRIPLE || skillid == Aran.HIDDEN_OVER_DOUBLE || skillid == Aran.HIDDEN_OVER_TRIPLE) { - c.getSession().write(MaplePacketCreator.enableActions()); - return; - } + if (skillid == Aran.HIDDEN_FULL_DOUBLE || skillid == Aran.HIDDEN_FULL_TRIPLE || skillid == Aran.HIDDEN_OVER_DOUBLE || skillid == Aran.HIDDEN_OVER_TRIPLE) { + c.getSession().write(MaplePacketCreator.enableActions()); + return; + } + MapleCharacter player = c.getPlayer(); int remainingSp = player.getRemainingSpBySkill(GameConstants.getSkillBook(skillid/10000)); boolean isBeginnerSkill = false; if ((!GameConstants.isPQSkillMap(player.getMapId()) && GameConstants.isPqSkill(skillid)) || (!player.isGM() && GameConstants.isGMSkills(skillid)) || (!GameConstants.isInJobTree(skillid, player.getJob().getId()) && !player.isGM())) { - AutobanFactory.PACKET_EDIT.alert(player, "tried to packet edit in distributing sp."); - FilePrinter.printError(FilePrinter.EXPLOITS + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + " tried to use skill " + skillid + " without it being in their job.\r\n"); - c.disconnect(true, false); - return; + AutobanFactory.PACKET_EDIT.alert(player, "tried to packet edit in distributing sp."); + FilePrinter.printError(FilePrinter.EXPLOITS + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + " tried to use skill " + skillid + " without it being in their job.\r\n"); + c.disconnect(true, false); + return; } if (skillid % 10000000 > 999 && skillid % 10000000 < 1003) { int total = 0; @@ -63,7 +64,7 @@ public final class DistributeSPHandler extends AbstractMaplePacketHandler { Skill skill = SkillFactory.getSkill(skillid); int curLevel = player.getSkillLevel(skill); if ((remainingSp > 0 && curLevel + 1 <= (skill.isFourthJob() ? player.getMasterLevel(skill) : skill.getMaxLevel()))) { - if (!isBeginnerSkill) { + if (!isBeginnerSkill) { player.setRemainingSp(player.getRemainingSpBySkill(GameConstants.getSkillBook(skillid/10000)) - 1, GameConstants.getSkillBook(skillid/10000)); } player.updateSingleStat(MapleStat.AVAILABLESP, player.getRemainingSpBySkill(GameConstants.getSkillBook(skillid/10000))); diff --git a/src/net/server/channel/handlers/UseCashItemHandler.java b/src/net/server/channel/handlers/UseCashItemHandler.java index 6d10f04ed9..b331ff44e6 100644 --- a/src/net/server/channel/handlers/UseCashItemHandler.java +++ b/src/net/server/channel/handlers/UseCashItemHandler.java @@ -63,7 +63,7 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler { public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { final MapleCharacter player = c.getPlayer(); if (System.currentTimeMillis() - player.getLastUsedCashItem() < 3000) { - player.dropMessage(1, "You have used a cash item recently. Wait a moment and try again."); + player.dropMessage(1, "You have used a cash item recently. Wait a moment, then try again."); c.announce(MaplePacketCreator.enableActions()); return; } @@ -83,6 +83,11 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler { return; } if (itemType == 505) { // AP/SP reset + if(!player.isAlive()) { + c.announce(MaplePacketCreator.enableActions()); + return; + } + if (itemId > 5050000) { int SPTo = slea.readInt(); int SPFrom = slea.readInt(); @@ -101,80 +106,76 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler { switch (APFrom) { case 64: // str if (player.getStr() < 5) { + c.getPlayer().message("You don't have the minimum STR required to swap."); + c.announce(MaplePacketCreator.enableActions()); return; } player.addStat(1, -1); break; case 128: // dex if (player.getDex() < 5) { + c.getPlayer().message("You don't have the minimum DEX required to swap."); + c.announce(MaplePacketCreator.enableActions()); return; } player.addStat(2, -1); break; case 256: // int if (player.getInt() < 5) { + c.getPlayer().message("You don't have the minimum INT required to swap."); + c.announce(MaplePacketCreator.enableActions()); return; } player.addStat(3, -1); break; case 512: // luk if (player.getLuk() < 5) { + c.getPlayer().message("You don't have the minimum LUK required to swap."); + c.announce(MaplePacketCreator.enableActions()); return; } player.addStat(4, -1); break; case 2048: // HP if (APTo != 8192) { - c.announce(MaplePacketCreator.enableActions()); - return; + c.getPlayer().message("You can only swap HP ability points to MP."); + c.announce(MaplePacketCreator.enableActions()); + return; } - int hplose = 0; - final int jobid = player.getJob().getId(); - if (jobid == 0 || jobid == 1000 || jobid == 2000 || jobid >= 1200 && jobid <= 1211) { // Beginner - hplose -= 12; - } else if (jobid >= 100 && jobid <= 132) { // Warrior - Skill improvinghplose = SkillFactory.getSkill(1000001); - int improvinghploseLevel = c.getPlayer().getSkillLevel(improvinghplose); - hplose -= 24; - if (improvinghploseLevel >= 1) { - hplose -= improvinghplose.getEffect(improvinghploseLevel).getY(); - } - } else if (jobid >= 200 && jobid <= 232) { // Magician - hplose -= 10; - } else if (jobid >= 500 && jobid <= 522) { // Pirate - Skill improvinghplose = SkillFactory.getSkill(5100000); - int improvinghploseLevel = c.getPlayer().getSkillLevel(improvinghplose); - hplose -= 22; - if (improvinghploseLevel > 0) { - hplose -= improvinghplose.getEffect(improvinghploseLevel).getY(); - } - } else if (jobid >= 1100 && jobid <= 1111) { // Soul Master - Skill improvinghplose = SkillFactory.getSkill(11000000); - int improvinghploseLevel = c.getPlayer().getSkillLevel(improvinghplose); - hplose -= 27; - if (improvinghploseLevel >= 1) { - hplose -= improvinghplose.getEffect(improvinghploseLevel).getY(); - } - } else if ((jobid >= 1300 && jobid <= 1311) || (jobid >= 1400 && jobid <= 1411)) { // Wind Breaker and Night Walker - hplose -= 17; - } else if (jobid >= 300 && jobid <= 322 || jobid >= 400 && jobid <= 422 || jobid >= 2000 && jobid <= 2112) { // Aran - hplose -= 20; - } else { // GameMaster - hplose -= 20; + + int hp = player.getHp(); + int level_ = player.getLevel(); + + boolean canWash_ = true; + if (hp < level_ * 14 + 148) { + canWash_ = false; } - player.setHp(player.getHp() + hplose); - player.setMaxHp(player.getMaxHp() + hplose); - statupdate.add(new Pair<>(MapleStat.HP, player.getHp())); - statupdate.add(new Pair<>(MapleStat.MAXHP, player.getMaxHp())); + + if (!canWash_) { + c.getPlayer().message("You don't have the minimum HP pool required to swap."); + c.announce(MaplePacketCreator.enableActions()); + return; + } + + int hplose = -DistributeAPHandler.calcHpChange(player, player.getJob(), true); + int nextHp = Math.max(1, player.getHp() + hplose), nextMaxHp = Math.max(50, player.getMaxHp() + hplose); + + player.setHp(nextHp); + player.setMaxHp(nextMaxHp); + statupdate.add(new Pair<>(MapleStat.HP, nextHp)); + statupdate.add(new Pair<>(MapleStat.MAXHP, nextMaxHp)); + break; case 8192: // MP if (APTo != 2048) { - c.announce(MaplePacketCreator.enableActions()); - return; + c.getPlayer().message("You can only swap MP ability points to HP."); + c.announce(MaplePacketCreator.enableActions()); + return; } int mp = player.getMp(); int level = player.getLevel(); MapleJob job = player.getJob(); + boolean canWash = true; if (job.isA(MapleJob.SPEARMAN) && mp < 4 * level + 156) { canWash = false; @@ -185,30 +186,26 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler { } else if (mp < level * 14 + 148) { canWash = false; } - if (canWash) { - int minmp = 0; - if (job.isA(MapleJob.WARRIOR) || job.isA(MapleJob.DAWNWARRIOR1) || job.isA(MapleJob.ARAN1)) { - minmp += 4; - } else if (job.isA(MapleJob.MAGICIAN) || job.isA(MapleJob.BLAZEWIZARD1)) { - minmp += 36; - } else if (job.isA(MapleJob.BOWMAN) || job.isA(MapleJob.WINDARCHER1) || job.isA(MapleJob.THIEF) || job.isA(MapleJob.NIGHTWALKER1)) { - minmp += 12; - } else if (job.isA(MapleJob.PIRATE) || job.isA(MapleJob.THUNDERBREAKER1)) { - minmp += 16; - } else { - minmp += 8; - } - player.setMp(player.getMp() - minmp); - player.setMaxMp(player.getMaxMp() - minmp); - statupdate.add(new Pair<>(MapleStat.MP, player.getMp())); - statupdate.add(new Pair<>(MapleStat.MAXMP, player.getMaxMp())); - break; + + if (!canWash) { + c.getPlayer().message("You don't have the minimum MP pool required to swap."); + c.announce(MaplePacketCreator.enableActions()); + return; } + + int mplose = -DistributeAPHandler.calcMpChange(player, job, true); + int nextMp = Math.max(0, player.getMp() + mplose), nextMaxMp = Math.max(5, player.getMaxMp() + mplose); + + player.setHp(nextMp); + player.setMaxHp(nextMaxMp); + statupdate.add(new Pair<>(MapleStat.HP, nextMp)); + statupdate.add(new Pair<>(MapleStat.MAXHP, nextMaxMp)); + break; default: c.announce(MaplePacketCreator.updatePlayerStats(MaplePacketCreator.EMPTY_STATUPDATE, true, c.getPlayer())); return; } - DistributeAPHandler.addStat(c, APTo); + DistributeAPHandler.addStat(c, APTo, true); c.announce(MaplePacketCreator.updatePlayerStats(statupdate, true, c.getPlayer())); } remove(c, itemId); diff --git a/src/scripting/AbstractPlayerInteraction.java b/src/scripting/AbstractPlayerInteraction.java index 73eaa90838..381e5c8c53 100644 --- a/src/scripting/AbstractPlayerInteraction.java +++ b/src/scripting/AbstractPlayerInteraction.java @@ -249,6 +249,8 @@ public class AbstractPlayerInteraction { } public void openNpc(int npcid, String script) { + if(c.getCM() != null) return; + c.removeClickedNPC(); NPCScriptManager.getInstance().dispose(c); NPCScriptManager.getInstance().start(c, npcid, script, null); diff --git a/src/server/life/MapleMonster.java b/src/server/life/MapleMonster.java index 4d8c317e38..6a04b9344b 100644 --- a/src/server/life/MapleMonster.java +++ b/src/server/life/MapleMonster.java @@ -31,7 +31,6 @@ import client.status.MonsterStatus; import client.status.MonsterStatusEffect; import constants.ServerConstants; import constants.skills.FPMage; -import constants.skills.Hermit; import constants.skills.ILMage; import constants.skills.NightLord; import constants.skills.NightWalker; @@ -51,6 +50,7 @@ import java.util.Map.Entry; import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReentrantLock; import net.server.world.MapleParty; import net.server.world.MaplePartyCharacter; @@ -66,12 +66,14 @@ import tools.Randomizer; public class MapleMonster extends AbstractLoadedMapleLife { private ChangeableStats ostats = null; //unused, v83 WZs offers no support for changeable stats. private MapleMonsterStats stats; - private int hp, mp; + private AtomicInteger hp = new AtomicInteger(1); + private AtomicLong maxHpPlusHeal = new AtomicLong(1); + private int mp; private WeakReference controller = new WeakReference<>(null); private boolean controllerHasAggro, controllerKnowsAboutAggro; private Collection listeners = new LinkedList<>(); private EnumMap stati = new EnumMap<>(MonsterStatus.class); - private ArrayList alreadyBuffed = new ArrayList(); + private ArrayList alreadyBuffed = new ArrayList<>(); private MapleMap map; private int VenomMultiplier = 0; private boolean fake = false; @@ -82,7 +84,9 @@ public class MapleMonster extends AbstractLoadedMapleLife { private int team; private final HashMap takenDamage = new HashMap<>(); + private ReentrantLock externalLock = new ReentrantLock(); private ReentrantLock monsterLock = new ReentrantLock(); + private ReentrantLock statiLock = new ReentrantLock(); public MapleMonster(int id, MapleMonsterStats stats) { super(id); @@ -95,18 +99,20 @@ public class MapleMonster extends AbstractLoadedMapleLife { } public void lockMonster() { - monsterLock.lock(); + externalLock.lock(); } public void unlockMonster() { - monsterLock.unlock(); + externalLock.unlock(); } private void initWithStats(MapleMonsterStats stats) { setStance(5); this.stats = stats; - hp = stats.getHp(); + hp.set(stats.getHp()); mp = stats.getMp(); + + maxHpPlusHeal.set(hp.get()); } public void disableDrops() { @@ -122,11 +128,11 @@ public class MapleMonster extends AbstractLoadedMapleLife { } public int getHp() { - return hp; + return hp.get(); } public void setHp(int hp) { - this.hp = hp; + this.hp.set(hp); } public int getMaxHp() { @@ -209,12 +215,13 @@ public class MapleMonster extends AbstractLoadedMapleLife { if (!isAlive()) { return; } - int trueDamage = Math.min(hp, damage); // since magic happens otherwise B^) + int curHp = hp.get(); + int trueDamage = Math.min(curHp, damage); // since magic happens otherwise B^) if(ServerConstants.USE_DEBUG == true) from.dropMessage(5, "Hitted MOB " + this.getId() + ", OID " + this.getObjectId()); dispatchMonsterDamaged(from, trueDamage); - hp -= trueDamage; + hp.set(curHp - trueDamage); if (!takenDamage.containsKey(from.getId())) { takenDamage.put(from.getId(), new AtomicInteger(trueDamage)); } else { @@ -225,7 +232,7 @@ public class MapleMonster extends AbstractLoadedMapleLife { from.setPlayerAggro(this.hashCode()); from.getMap().broadcastBossHpMessage(this, this.hashCode(), makeBossHPBarPacket(), getPosition()); } else if (!isBoss()) { - int remainingHP = (int) Math.max(1, hp * 100f / getMaxHp()); + int remainingHP = (int) Math.max(1, hp.get() * 100f / getMaxHp()); byte[] packet = MaplePacketCreator.showMonsterHP(getObjectId(), remainingHP); if (from.getParty() != null) { for (MaplePartyCharacter mpc : from.getParty().getMembers()) { @@ -241,17 +248,24 @@ public class MapleMonster extends AbstractLoadedMapleLife { } public void heal(int hp, int mp) { + int hpHealed = hp; int hp2Heal = getHp() + hp; int mp2Heal = getMp() + mp; - if (hp2Heal >= getMaxHp()) { - hp2Heal = getMaxHp(); + + int maxHp = getMaxHp(); + int maxMp = getMaxMp(); + if (hp2Heal >= maxHp) { + hpHealed = hp2Heal - maxHp; + hp2Heal = maxHp; } - if (mp2Heal >= getMaxMp()) { - mp2Heal = getMaxMp(); + if (mp2Heal >= maxMp) { + mp2Heal = maxMp; } setHp(hp2Heal); setMp(mp2Heal); getMap().broadcastMessage(MaplePacketCreator.healMonster(getObjectId(), hp)); + + maxHpPlusHeal.addAndGet(hpHealed); } public boolean isAttackedBy(MapleCharacter chr) { @@ -273,7 +287,7 @@ public class MapleMonster extends AbstractLoadedMapleLife { } int partyLevel = 0; - int leechMinLevel = (ServerConstants.USE_UNDERLEVELED_EXP_BLOCK) ? getLevel() - 20 : 0; //NO EXP WILL BE GIVEN for those who are underleveled! + int leechMinLevel = (ServerConstants.USE_UNDERLEVELED_EXP_BLOCK) ? getLevel() - ServerConstants.MIN_UNDERLEVEL_FOR_EXP_GAIN : 0; //NO EXP WILL BE GIVEN for those who are underleveled! int leechCount = 0; for (MapleCharacter mc : members) { @@ -307,15 +321,15 @@ public class MapleMonster extends AbstractLoadedMapleLife { return; } int exp = getExp(); - int totalHealth = getMaxHp(); + long totalHealth = maxHpPlusHeal.get(); Map expDist = new HashMap<>(); Map partyExp = new HashMap<>(); - float exp8 = (0.8f * exp); // 80% of pool is split amongst all the damagers - float exp2 = (0.2f * exp); // 20% of pool goes to the killer or his/her party + float exp8perHp = (0.8f * exp) / totalHealth; // 80% of pool is split amongst all the damagers + float exp2 = (0.2f * exp); // 20% of pool goes to the killer or his/her party for (Entry damage : takenDamage.entrySet()) { - expDist.put(damage.getKey(), (int) (Math.min((exp8 * damage.getValue().get()) / totalHealth, Integer.MAX_VALUE))); + expDist.put(damage.getKey(), (int) (Math.min((exp8perHp * damage.getValue().get()), Integer.MAX_VALUE))); } Collection chrs = map.getCharacters(); @@ -333,7 +347,7 @@ public class MapleMonster extends AbstractLoadedMapleLife { long pXP = (long)xp + (partyExp.containsKey(pID) ? partyExp.get(pID) : 0); partyExp.put(pID, (int)Math.min(pXP, Integer.MAX_VALUE)); } else { - if(!ServerConstants.USE_UNDERLEVELED_EXP_BLOCK || mc.getLevel() >= getLevel() - 20) { + if(!ServerConstants.USE_UNDERLEVELED_EXP_BLOCK || mc.getLevel() >= getLevel() - ServerConstants.MIN_UNDERLEVEL_FOR_EXP_GAIN) { //NO EXP WILL BE GIVEN for those who are underleveled! giveExpToCharacter(mc, xp, isKiller, 1); } else { @@ -379,8 +393,14 @@ public class MapleMonster extends AbstractLoadedMapleLife { personalExp *= 1.0 + (holySymbol.doubleValue() / 100.0); } } - if (stati.containsKey(MonsterStatus.SHOWDOWN)) { - personalExp *= (stati.get(MonsterStatus.SHOWDOWN).getStati().get(MonsterStatus.SHOWDOWN).doubleValue() / 100.0 + 1.0); + + statiLock.lock(); + try { + if (stati.containsKey(MonsterStatus.SHOWDOWN)) { + personalExp *= (stati.get(MonsterStatus.SHOWDOWN).getStati().get(MonsterStatus.SHOWDOWN).doubleValue() / 100.0 + 1.0); + } + } finally { + statiLock.unlock(); } } @@ -393,9 +413,10 @@ public class MapleMonster extends AbstractLoadedMapleLife { public MapleCharacter killBy(final MapleCharacter killer) { distributeExperience(killer != null ? killer.getId() : 0); - if (getController() != null) { // this can/should only happen when a hidden gm attacks the monster - getController().getClient().announce(MaplePacketCreator.stopControllingMonster(this.getObjectId())); - getController().stopControllingMonster(this); + MapleCharacter controller = getController(); + if (controller != null) { // this can/should only happen when a hidden gm attacks the monster + controller.getClient().announce(MaplePacketCreator.stopControllingMonster(this.getObjectId())); + controller.stopControllingMonster(this); } final List toSpawn = this.getRevives(); // this doesn't work (?) @@ -427,24 +448,26 @@ public class MapleMonster extends AbstractLoadedMapleLife { } } - TimerManager.getInstance().schedule(new Runnable() { - @Override - public void run() { - for (Integer mid : toSpawn) { - final MapleMonster mob = MapleLifeFactory.getMonster(mid); - mob.setPosition(getPosition()); - if (dropsDisabled()) { - mob.disableDrops(); - } - reviveMap.spawnMonster(mob); - - if(mob.getId() >= 8810010 && mob.getId() <= 8810017 && reviveMap.isHorntailDefeated()) { - for(int i = 8810018; i >= 8810010; i--) - reviveMap.killMonster(reviveMap.getMonsterById(i), killer, true); + if(toSpawn.size() > 0) { + TimerManager.getInstance().schedule(new Runnable() { + @Override + public void run() { + for (Integer mid : toSpawn) { + final MapleMonster mob = MapleLifeFactory.getMonster(mid); + mob.setPosition(getPosition()); + if (dropsDisabled()) { + mob.disableDrops(); + } + reviveMap.spawnMonster(mob); + + if(mob.getId() >= 8810010 && mob.getId() <= 8810017 && reviveMap.isHorntailDefeated()) { + for(int i = 8810018; i >= 8810010; i--) + reviveMap.killMonster(reviveMap.getMonsterById(i), killer, true); + } } } - } - }, getAnimationTime("die1")); + }, getAnimationTime("die1")); + } } else { // is this even necessary? System.out.println("[CRITICAL LOSS] toSpawn is null for " + this.getName()); @@ -468,7 +491,7 @@ public class MapleMonster extends AbstractLoadedMapleLife { } } - public void dispatchMonsterDamaged(MapleCharacter from, int trueDmg) { + private void dispatchMonsterDamaged(MapleCharacter from, int trueDmg) { for (MonsterListener listener : listeners.toArray(new MonsterListener[listeners.size()])) { listener.monsterDamaged(from, trueDmg); } @@ -488,15 +511,25 @@ public class MapleMonster extends AbstractLoadedMapleLife { } public boolean isAlive() { - return this.hp > 0; + return this.hp.get() > 0; } public MapleCharacter getController() { - return controller.get(); + monsterLock.lock(); + try { + return controller.get(); + } finally { + monsterLock.unlock(); + } } public void setController(MapleCharacter controller) { - this.controller = new WeakReference<>(controller); + monsterLock.lock(); + try { + this.controller = new WeakReference<>(controller); + } finally { + monsterLock.unlock(); + } } public void switchController(MapleCharacter newController, boolean immediateAggro) { @@ -521,25 +554,45 @@ public class MapleMonster extends AbstractLoadedMapleLife { } public boolean isControllerHasAggro() { - return fake ? false : controllerHasAggro; + monsterLock.lock(); + try { + return fake ? false : controllerHasAggro; + } finally { + monsterLock.unlock(); + } } public void setControllerHasAggro(boolean controllerHasAggro) { - if (fake) { - return; + monsterLock.lock(); + try { + if (fake) { + return; + } + this.controllerHasAggro = controllerHasAggro; + } finally { + monsterLock.unlock(); } - this.controllerHasAggro = controllerHasAggro; } public boolean isControllerKnowsAboutAggro() { - return fake ? false : controllerKnowsAboutAggro; + monsterLock.lock(); + try { + return fake ? false : controllerKnowsAboutAggro; + } finally { + monsterLock.unlock(); + } } public void setControllerKnowsAboutAggro(boolean controllerKnowsAboutAggro) { - if (fake) { - return; + monsterLock.lock(); + try { + if (fake) { + return; + } + this.controllerKnowsAboutAggro = controllerKnowsAboutAggro; + } finally { + monsterLock.unlock(); } - this.controllerKnowsAboutAggro = controllerKnowsAboutAggro; } public byte[] makeBossHPBarPacket() { @@ -560,11 +613,17 @@ public class MapleMonster extends AbstractLoadedMapleLife { } else { c.announce(MaplePacketCreator.spawnMonster(this, false)); } - if (stati.size() > 0) { - for (final MonsterStatusEffect mse : this.stati.values()) { - c.announce(MaplePacketCreator.applyMonsterStatus(getObjectId(), mse, null)); + statiLock.lock(); + try { + if (stati.size() > 0) { + for (final MonsterStatusEffect mse : this.stati.values()) { + c.announce(MaplePacketCreator.applyMonsterStatus(getObjectId(), mse, null)); + } } + } finally { + statiLock.unlock(); } + if (hasBossHPBar()) { if (this.getMap().countMonster(8810026) > 0 && this.getMap().getId() == 240060200) { this.getMap().killAllMonsters(); @@ -588,19 +647,52 @@ public class MapleMonster extends AbstractLoadedMapleLife { return stats.isMobile(); } - public ElementalEffectiveness getEffectiveness(Element e) { - if (stati.size() > 0 && stati.get(MonsterStatus.DOOM) != null) { - return ElementalEffectiveness.NORMAL; // like blue snails + public ElementalEffectiveness getElementalEffectiveness(Element e) { + statiLock.lock(); + try { + if (stati.get(MonsterStatus.DOOM) != null) { + return ElementalEffectiveness.NORMAL; // like blue snails + } + } finally { + statiLock.unlock(); + } + + monsterLock.lock(); + try { + return stats.getEffectiveness(e); + } finally { + monsterLock.unlock(); + } + } + + private ElementalEffectiveness getMonsterEffectiveness(Element e) { + monsterLock.lock(); + try { + return stats.getEffectiveness(e); + } finally { + monsterLock.unlock(); } - return stats.getEffectiveness(e); } + private int broadcastStatusEffect(final MonsterStatusEffect status) { + int animationTime = status.getSkill().getAnimationTime(); + byte[] packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), status, null); + map.broadcastMessage(packet, getPosition()); + + MapleCharacter controller = getController(); + if (controller != null && !controller.isMapObjectVisible(this)) { + controller.getClient().announce(packet); + } + + return animationTime; + } + public boolean applyStatus(MapleCharacter from, final MonsterStatusEffect status, boolean poison, long duration) { return applyStatus(from, status, poison, duration, false); } public boolean applyStatus(MapleCharacter from, final MonsterStatusEffect status, boolean poison, long duration, boolean venom) { - switch (stats.getEffectiveness(status.getSkill().getElement())) { + switch (getMonsterEffectiveness(status.getSkill().getElement())) { case IMMUNE: case STRONG: case NEUTRAL: @@ -609,27 +701,27 @@ public class MapleMonster extends AbstractLoadedMapleLife { case WEAK: break; default: { - System.out.println("Unknown elemental effectiveness: " + stats.getEffectiveness(status.getSkill().getElement())); + System.out.println("Unknown elemental effectiveness: " + getMonsterEffectiveness(status.getSkill().getElement())); return false; } } if (status.getSkill().getId() == FPMage.ELEMENT_COMPOSITION) { // fp compo - ElementalEffectiveness effectiveness = stats.getEffectiveness(Element.POISON); + ElementalEffectiveness effectiveness = getMonsterEffectiveness(Element.POISON); if (effectiveness == ElementalEffectiveness.IMMUNE || effectiveness == ElementalEffectiveness.STRONG) { return false; } } else if (status.getSkill().getId() == ILMage.ELEMENT_COMPOSITION) { // il compo - ElementalEffectiveness effectiveness = stats.getEffectiveness(Element.ICE); + ElementalEffectiveness effectiveness = getMonsterEffectiveness(Element.ICE); if (effectiveness == ElementalEffectiveness.IMMUNE || effectiveness == ElementalEffectiveness.STRONG) { return false; } } else if (status.getSkill().getId() == NightLord.VENOMOUS_STAR || status.getSkill().getId() == Shadower.VENOMOUS_STAB || status.getSkill().getId() == NightWalker.VENOM) {// venom - if (stats.getEffectiveness(Element.POISON) == ElementalEffectiveness.WEAK) { + if (getMonsterEffectiveness(Element.POISON) == ElementalEffectiveness.WEAK) { return false; } } - if (poison && getHp() <= 1) { + if (poison && hp.get() <= 1) { return false; } @@ -642,17 +734,24 @@ public class MapleMonster extends AbstractLoadedMapleLife { } } - for (MonsterStatus stat : statis.keySet()) { - final MonsterStatusEffect oldEffect = stati.get(stat); - if (oldEffect != null) { - oldEffect.removeActiveStatus(stat); - if (oldEffect.getStati().isEmpty()) { - oldEffect.cancelTask(); - oldEffect.cancelDamageSchedule(); + if(statis.size() > 0) { + statiLock.lock(); + try { + for (MonsterStatus stat : statis.keySet()) { + final MonsterStatusEffect oldEffect = stati.get(stat); + if (oldEffect != null) { + oldEffect.removeActiveStatus(stat); + if (oldEffect.getStati().isEmpty()) { + oldEffect.cancelTask(); + oldEffect.cancelDamageSchedule(); + } + } } + } finally { + statiLock.unlock(); } } - + TimerManager timerManager = TimerManager.getInstance(); final Runnable cancelTask = new Runnable() { @@ -661,26 +760,38 @@ public class MapleMonster extends AbstractLoadedMapleLife { if (isAlive()) { byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), status.getStati()); map.broadcastMessage(packet, getPosition()); - if (getController() != null && !getController().isMapObjectVisible(MapleMonster.this)) { - getController().getClient().announce(packet); + + MapleCharacter controller = getController(); + if (controller != null && !controller.isMapObjectVisible(MapleMonster.this)) { + controller.getClient().announce(packet); } } - for (MonsterStatus stat : status.getStati().keySet()) { - stati.remove(stat); + + statiLock.lock(); + try { + for (MonsterStatus stat : status.getStati().keySet()) { + stati.remove(stat); + } + } finally { + statiLock.unlock(); } + setVenomMulti(0); status.cancelDamageSchedule(); } }; + + int animationTime; if (poison) { int poisonLevel = from.getSkillLevel(status.getSkill()); int poisonDamage = Math.min(Short.MAX_VALUE, (int) (getMaxHp() / (70.0 - poisonLevel) + 0.999)); status.setValue(MonsterStatus.POISON, Integer.valueOf(poisonDamage)); + animationTime = broadcastStatusEffect(status); status.setDamageSchedule(timerManager.register(new DamageTask(poisonDamage, from, status, cancelTask, 0), 1000, 1000)); } else if (venom) { if (from.getJob() == MapleJob.NIGHTLORD || from.getJob() == MapleJob.SHADOWER || from.getJob().isA(MapleJob.NIGHTWALKER3)) { - int poisonLevel, matk, id = from.getJob().getId(); - int skill = (id == 412 ? NightLord.VENOMOUS_STAR : (id == 422 ? Shadower.VENOMOUS_STAB : NightWalker.VENOM)); + int poisonLevel, matk, jobid = from.getJob().getId(); + int skill = (jobid == 412 ? NightLord.VENOMOUS_STAR : (jobid == 422 ? Shadower.VENOMOUS_STAB : NightWalker.VENOM)); poisonLevel = from.getSkillLevel(SkillFactory.getSkill(skill)); if (poisonLevel <= 0) { return false; @@ -699,13 +810,19 @@ public class MapleMonster extends AbstractLoadedMapleLife { } poisonDamage = Math.min(Short.MAX_VALUE, poisonDamage); status.setValue(MonsterStatus.VENOMOUS_WEAPON, Integer.valueOf(poisonDamage)); + status.setValue(MonsterStatus.POISON, Integer.valueOf(poisonDamage)); + animationTime = broadcastStatusEffect(status); status.setDamageSchedule(timerManager.register(new DamageTask(poisonDamage, from, status, cancelTask, 0), 1000, 1000)); } else { return false; } - + /* } else if (status.getSkill().getId() == Hermit.SHADOW_WEB || status.getSkill().getId() == NightWalker.SHADOW_WEB) { //Shadow Web - status.setDamageSchedule(timerManager.schedule(new DamageTask((int) (getMaxHp() / 50.0 + 0.999), from, status, cancelTask, 1), 3500)); + int webDamage = (int) (getMaxHp() / 50.0 + 0.999); + status.setValue(MonsterStatus.SHADOW_WEB, Integer.valueOf(webDamage)); + animationTime = broadcastStatusEffect(status); + status.setDamageSchedule(timerManager.schedule(new DamageTask(webDamage, from, status, cancelTask, 1), 3500)); + */ } else if (status.getSkill().getId() == 4121004 || status.getSkill().getId() == 4221004) { // Ninja Ambush final Skill skill = SkillFactory.getSkill(status.getSkill().getId()); final byte level = from.getSkillLevel(skill); @@ -715,19 +832,23 @@ public class MapleMonster extends AbstractLoadedMapleLife { }*/ status.setValue(MonsterStatus.NINJA_AMBUSH, Integer.valueOf(damage)); + animationTime = broadcastStatusEffect(status); status.setDamageSchedule(timerManager.register(new DamageTask(damage, from, status, cancelTask, 2), 1000, 1000)); + } else { + animationTime = broadcastStatusEffect(status); } - for (MonsterStatus stat : status.getStati().keySet()) { - stati.put(stat, status); - alreadyBuffed.add(stat); + + statiLock.lock(); + try { + for (MonsterStatus stat : status.getStati().keySet()) { + stati.put(stat, status); + alreadyBuffed.add(stat); + } + } finally { + statiLock.unlock(); } - int animationTime = status.getSkill().getAnimationTime(); - byte[] packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), status, null); - map.broadcastMessage(packet, getPosition()); - if (getController() != null && !getController().isMapObjectVisible(this)) { - getController().getClient().announce(packet); - } - status.setCancelTask(timerManager.schedule(cancelTask, duration + animationTime)); + + status.setCancelTask(timerManager.schedule(cancelTask, duration + animationTime - 100)); return true; } @@ -740,11 +861,19 @@ public class MapleMonster extends AbstractLoadedMapleLife { if (isAlive()) { byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), stats); map.broadcastMessage(packet, getPosition()); - if (getController() != null && !getController().isMapObjectVisible(MapleMonster.this)) { - getController().getClient().announce(packet); + + MapleCharacter controller = getController(); + if (controller != null && !controller.isMapObjectVisible(MapleMonster.this)) { + controller.getClient().announce(packet); } - for (final MonsterStatus stat : stats.keySet()) { - stati.remove(stat); + + statiLock.lock(); + try { + for (final MonsterStatus stat : stats.keySet()) { + stati.remove(stat); + } + } finally { + statiLock.unlock(); } } } @@ -752,12 +881,20 @@ public class MapleMonster extends AbstractLoadedMapleLife { final MonsterStatusEffect effect = new MonsterStatusEffect(stats, null, skill, true); byte[] packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), effect, reflection); map.broadcastMessage(packet, getPosition()); - for (MonsterStatus stat : stats.keySet()) { - stati.put(stat, effect); - alreadyBuffed.add(stat); + + statiLock.lock(); + try { + for (MonsterStatus stat : stats.keySet()) { + stati.put(stat, effect); + alreadyBuffed.add(stat); + } + } finally { + statiLock.unlock(); } - if (getController() != null && !getController().isMapObjectVisible(this)) { - getController().getClient().announce(packet); + + MapleCharacter controller = getController(); + if (controller != null && !controller.isMapObjectVisible(this)) { + controller.getClient().announce(packet); } effect.setCancelTask(timerManager.schedule(cancelTask, duration)); } @@ -765,29 +902,51 @@ public class MapleMonster extends AbstractLoadedMapleLife { public void debuffMob(int skillid) { //skillid is not going to be used for now until I get warrior debuff working MonsterStatus[] stats = {MonsterStatus.WEAPON_ATTACK_UP, MonsterStatus.WEAPON_DEFENSE_UP, MonsterStatus.MAGIC_ATTACK_UP, MonsterStatus.MAGIC_DEFENSE_UP}; - for (int i = 0; i < stats.length; i++) { - if (isBuffed(stats[i])) { - final MonsterStatusEffect oldEffect = stati.get(stats[i]); - byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), oldEffect.getStati()); - map.broadcastMessage(packet, getPosition()); - if (getController() != null && !getController().isMapObjectVisible(MapleMonster.this)) { - getController().getClient().announce(packet); + statiLock.lock(); + try { + for (int i = 0; i < stats.length; i++) { + if (isBuffed(stats[i])) { + final MonsterStatusEffect oldEffect = stati.get(stats[i]); + byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), oldEffect.getStati()); + map.broadcastMessage(packet, getPosition()); + + MapleCharacter controller = getController(); + if (controller != null && !controller.isMapObjectVisible(MapleMonster.this)) { + controller.getClient().announce(packet); + } + stati.remove(stats[i]); } - stati.remove(stats); } + } finally { + statiLock.unlock(); } } public boolean isBuffed(MonsterStatus status) { - return stati.containsKey(status); + statiLock.lock(); + try { + return stati.containsKey(status); + } finally { + statiLock.unlock(); + } } public void setFake(boolean fake) { - this.fake = fake; + monsterLock.lock(); + try { + this.fake = fake; + } finally { + monsterLock.unlock(); + } } public boolean isFake() { - return fake; + monsterLock.lock(); + try { + return fake; + } finally { + monsterLock.unlock(); + } } public MapleMap getMap() { @@ -806,18 +965,30 @@ public class MapleMonster extends AbstractLoadedMapleLife { if (toUse == null) { return false; } - for (Pair skill : usedSkills) { - if (skill.getLeft() == toUse.getSkillId() && skill.getRight() == toUse.getSkillLevel()) { - return false; - } - } - if (toUse.getLimit() > 0) { - if (this.skillsUsed.containsKey(new Pair<>(toUse.getSkillId(), toUse.getSkillLevel()))) { - int times = this.skillsUsed.get(new Pair<>(toUse.getSkillId(), toUse.getSkillLevel())); - if (times >= toUse.getLimit()) { + + monsterLock.lock(); + try { + for (Pair skill : usedSkills) { + if (skill.getLeft() == toUse.getSkillId() && skill.getRight() == toUse.getSkillLevel()) { return false; } } + } finally { + monsterLock.unlock(); + } + + if (toUse.getLimit() > 0) { + monsterLock.lock(); + try { + if (this.skillsUsed.containsKey(new Pair<>(toUse.getSkillId(), toUse.getSkillLevel()))) { + int times = this.skillsUsed.get(new Pair<>(toUse.getSkillId(), toUse.getSkillLevel())); + if (times >= toUse.getLimit()) { + return false; + } + } + } finally { + monsterLock.unlock(); + } } if (toUse.getSkillId() == 200) { Collection mmo = getMap().getMapObjects(); @@ -835,36 +1006,47 @@ public class MapleMonster extends AbstractLoadedMapleLife { } public void usedSkill(final int skillId, final int level, long cooltime) { - this.usedSkills.add(new Pair<>(skillId, level)); - if (this.skillsUsed.containsKey(new Pair<>(skillId, level))) { - int times = this.skillsUsed.get(new Pair<>(skillId, level)) + 1; - this.skillsUsed.remove(new Pair<>(skillId, level)); - this.skillsUsed.put(new Pair<>(skillId, level), times); - } else { - this.skillsUsed.put(new Pair<>(skillId, level), 1); + monsterLock.lock(); + try { + this.usedSkills.add(new Pair<>(skillId, level)); + if (this.skillsUsed.containsKey(new Pair<>(skillId, level))) { + int times = this.skillsUsed.get(new Pair<>(skillId, level)) + 1; + this.skillsUsed.remove(new Pair<>(skillId, level)); + this.skillsUsed.put(new Pair<>(skillId, level), times); + } else { + this.skillsUsed.put(new Pair<>(skillId, level), 1); + } + } finally { + monsterLock.unlock(); } + final MapleMonster mons = this; TimerManager tMan = TimerManager.getInstance(); tMan.schedule( - new Runnable() { + new Runnable() { - @Override - public void run() { - mons.clearSkill(skillId, level); - } - }, cooltime); + @Override + public void run() { + mons.clearSkill(skillId, level); + } + }, cooltime); } public void clearSkill(int skillId, int level) { - int index = -1; - for (Pair skill : usedSkills) { - if (skill.getLeft() == skillId && skill.getRight() == level) { - index = usedSkills.indexOf(skill); - break; + monsterLock.lock(); + try { + int index = -1; + for (Pair skill : usedSkills) { + if (skill.getLeft() == skillId && skill.getRight() == level) { + index = usedSkills.indexOf(skill); + break; + } } - } - if (index != -1) { - usedSkills.remove(index); + if (index != -1) { + usedSkills.remove(index); + } + } finally { + monsterLock.unlock(); } } @@ -900,16 +1082,19 @@ public class MapleMonster extends AbstractLoadedMapleLife { @Override public void run() { + int curHp = hp.get(); + if(curHp <= 0) return; + int damage = dealDamage; - if (damage >= hp) { - damage = hp - 1; + if (damage >= curHp) { + damage = curHp - 1; if (type == 1 || type == 2) { map.broadcastMessage(MaplePacketCreator.damageMonster(getObjectId(), damage), getPosition()); cancelTask.run(); status.getCancelTask().cancel(false); } } - if (hp > 1 && damage > 0) { + if (curHp > 1 && damage > 0) { damage(chr, damage); if (type == 1) { map.broadcastMessage(MaplePacketCreator.damageMonster(getObjectId(), damage), getPosition()); @@ -931,23 +1116,38 @@ public class MapleMonster extends AbstractLoadedMapleLife { } public void setTempEffectiveness(Element e, ElementalEffectiveness ee, long milli) { - final Element fE = e; - final ElementalEffectiveness fEE = stats.getEffectiveness(e); - if (!stats.getEffectiveness(e).equals(ElementalEffectiveness.WEAK)) { - stats.setEffectiveness(e, ee); - TimerManager.getInstance().schedule(new Runnable() { + monsterLock.lock(); + try { + final Element fE = e; + final ElementalEffectiveness fEE = stats.getEffectiveness(e); + if (!fEE.equals(ElementalEffectiveness.WEAK)) { + stats.setEffectiveness(e, ee); + TimerManager.getInstance().schedule(new Runnable() { - @Override - public void run() { - stats.removeEffectiveness(fE); - stats.setEffectiveness(fE, fEE); - } - }, milli); + @Override + public void run() { + monsterLock.lock(); + try { + stats.removeEffectiveness(fE); + stats.setEffectiveness(fE, fEE); + } finally { + monsterLock.unlock(); + } + } + }, milli); + } + } finally { + monsterLock.unlock(); } } public Collection alreadyBuffedStats() { - return Collections.unmodifiableCollection(alreadyBuffed); + statiLock.lock(); + try { + return Collections.unmodifiableCollection(alreadyBuffed); + } finally { + statiLock.unlock(); + } } public BanishInfo getBanish() { @@ -967,7 +1167,21 @@ public class MapleMonster extends AbstractLoadedMapleLife { } public Map getStati() { - return stati; + statiLock.lock(); + try { + return Collections.unmodifiableMap(stati); + } finally { + statiLock.unlock(); + } + } + + public MonsterStatusEffect getStati(MonsterStatus ms) { + statiLock.lock(); + try { + return stati.get(ms); + } finally { + statiLock.unlock(); + } } // ---- one can always have fun trying these pieces of codes below in-game rofl ---- @@ -985,7 +1199,7 @@ public class MapleMonster extends AbstractLoadedMapleLife { public final void setOverrideStats(final OverrideMonsterStats ostats) { this.ostats = new ChangeableStats(stats, ostats); - this.hp = ostats.getHp(); + this.hp.set(ostats.getHp()); this.mp = ostats.getMp(); } @@ -998,7 +1212,7 @@ public class MapleMonster extends AbstractLoadedMapleLife { return; } this.ostats = new ChangeableStats(stats, newLevel, pqMob); - this.hp = ostats.getHp(); + this.hp.set(ostats.getHp()); this.mp = ostats.getMp(); } diff --git a/src/server/maps/MapleMap.java b/src/server/maps/MapleMap.java index 4c404d2389..db59f5f074 100644 --- a/src/server/maps/MapleMap.java +++ b/src/server/maps/MapleMap.java @@ -515,9 +515,9 @@ public class MapleMap { byte d = 1; Point pos = new Point(0, mob.getPosition().y); - Map stati = mob.getStati(); - if (stati.containsKey(MonsterStatus.SHOWDOWN)) { - chRate *= (stati.get(MonsterStatus.SHOWDOWN).getStati().get(MonsterStatus.SHOWDOWN).doubleValue() / 100.0 + 1.0); + MonsterStatusEffect stati = mob.getStati(MonsterStatus.SHOWDOWN); + if (stati != null) { + chRate *= (stati.getStati().get(MonsterStatus.SHOWDOWN).doubleValue() / 100.0 + 1.0); } final MapleMonsterInformationProvider mi = MapleMonsterInformationProvider.getInstance(); diff --git a/src/tools/DatabaseConnection.java b/src/tools/DatabaseConnection.java index 9161e301d2..21c7fac711 100644 --- a/src/tools/DatabaseConnection.java +++ b/src/tools/DatabaseConnection.java @@ -1,6 +1,8 @@ package tools; import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.DriverManager; import java.sql.SQLException; import com.zaxxer.hikari.HikariConfig; @@ -18,13 +20,51 @@ public class DatabaseConnection { public static Connection getConnection() throws SQLException { if(ds != null) { - return ds.getConnection(); - } else { - return DriverManager.getConnection(ServerConstants.DB_URL, ServerConstants.DB_USER, ServerConstants.DB_PASS); + try { + return ds.getConnection(); + } catch (SQLException sqle) {} + } + + int denies = 0; + while(true) { // There is no way it can pass with a null out of here + try { + return DriverManager.getConnection(ServerConstants.DB_URL, ServerConstants.DB_USER, ServerConstants.DB_PASS); + } catch (SQLException sqle) { + denies++; + + if(denies == 3) { + // Give up, return null :3 + FilePrinter.printError(FilePrinter.SQL_EXCEPTION, "SQL Driver refused to give a connection after " + denies + " tries."); + return null; + } + } + } + } + + private static int getNumberOfAccounts() { + try { + Connection con = DriverManager.getConnection(ServerConstants.DB_URL, ServerConstants.DB_USER, ServerConstants.DB_PASS); + try (PreparedStatement ps = con.prepareStatement("SELECT count(*) FROM accounts")) { + try (ResultSet rs = ps.executeQuery()) { + rs.next(); + return rs.getInt(1); + } + } finally { + con.close(); + } + } catch(SQLException sqle) { + return 20; } } public DatabaseConnection() { + try { + Class.forName("com.mysql.jdbc.Driver"); // touch the mysql driver + } catch (ClassNotFoundException e) { + System.out.println("[SEVERE] SQL Driver Not Found. Consider death by clams."); + e.printStackTrace(); + } + ds = null; if(ServerConstants.DB_EXPERIMENTAL_POOL) { @@ -36,22 +76,18 @@ public class DatabaseConnection { config.setUsername(ServerConstants.DB_USER); config.setPassword(ServerConstants.DB_PASS); - config.addDataSourceProperty("connectionTimeout", "30000"); - config.addDataSourceProperty("maximumPoolSize", "100"); + int poolSize = getNumberOfAccounts() * 10; // make sure pool size is comfortable for the worst case scenario + if(poolSize < 100) poolSize = 100; + else if(poolSize > 10000) poolSize = 10000; - config.addDataSourceProperty("cachePrepStmts", "true"); - config.addDataSourceProperty("prepStmtCacheSize", "250"); - config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); + config.setConnectionTimeout(30 * 1000); + config.setMaximumPoolSize(poolSize); + + config.addDataSourceProperty("cachePrepStmts", true); + config.addDataSourceProperty("prepStmtCacheSize", 250); + config.addDataSourceProperty("prepStmtCacheSqlLimit", 2048); ds = new HikariDataSource(config); - } else { - try { - Class.forName("com.mysql.jdbc.Driver"); // touch the mysql driver - } catch (ClassNotFoundException e) { - System.out.println("[SEVERE] SQL Driver Not Found. Consider death by clams."); - e.printStackTrace(); - return; - } } } } diff --git a/src/tools/FilePrinter.java b/src/tools/FilePrinter.java index 81fb33a7bd..340129765c 100644 --- a/src/tools/FilePrinter.java +++ b/src/tools/FilePrinter.java @@ -19,6 +19,7 @@ public class FilePrinter { ERROR38 = "error38.txt", PACKET_LOG = "log.txt", EXCEPTION = "exceptions.txt", + SQL_EXCEPTION = "sqlexceptions.txt", PACKET_HANDLER = "PacketHandler/", PORTAL = "portals/", NPC = "npcs/",