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/",