diff --git a/.gitignore b/.gitignore
index ca7e2ab393..c635df16b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-/logs/**
+/logs/**
.idea/
# build files
diff --git a/docs/issues.txt b/docs/issues.txt
index 6f68216c1f..d28e49a516 100644
--- a/docs/issues.txt
+++ b/docs/issues.txt
@@ -13,6 +13,7 @@ Known issues:
- If there are multiple bosses that shows HPBar on the map, if a player hits more than one the HPBar may start flickering on the screen.
- Sometimes battleship may behave oddly with the enhanced buff system, making the character d/c in certain scenarios.
- Dragon Roar doesn't show the stun effect to players.
+- Some monster status such as freeze and weapon/magic reflect doesn't behave properly in certain scenarios. Freeze seems to not work on mobs with low OID or are starters from server boot time.
---------------------------
---------------------------
diff --git a/docs/mychanges_ptbr.txt b/docs/mychanges_ptbr.txt
index 02f36dc2e2..285c1bb371 100644
--- a/docs/mychanges_ptbr.txt
+++ b/docs/mychanges_ptbr.txt
@@ -1083,4 +1083,14 @@ Adicionado efeito de dispel de weapondef e magicdef up em mobs ao atacar com Sha
Corrigido Shadow Meso consumindo projetil (deveria usar meso somente).
Adicionado recompensa de buff ao completar o HolidayPQ.
Adicionado drops de itens que dão buffs característicos nos reatores da Zakum Prequest.
-Scroll shop custom mudou para a NPC Asia em Neo City (anteriormente estava no Spindle).
\ No newline at end of file
+Scroll shop custom mudou para a NPC Asia em Neo City (anteriormente estava no Spindle).
+
+27 Junho 2018,
+Resolvido um problema no sistema de ganho de EXP que daria ao jogador um valor abaixo do que seria o normal nos casos onde o EXP ganho é baixo.
+Otimizado mob buffs e diseases agora utilizando thread dedicada para lidar com a duração, anteriormente eram criadas novas schedules pra cada instância.
+Refatorado MonitoredLockTypes para agora dar um label específico a cada lock do sistema.
+
+29 - 30 Junho 2018,
+Corrigido quest Milk Jug com NPCs trocados.
+Adicionado um delay na aplicação de efeitos dos skills de mobs, buffs e demais efeitos agora são registrados após o tempo da animação.
+Corrigido Flame Thrower atuando passivamente quando o jogador usa uma skill de ataque.
\ No newline at end of file
diff --git a/handbook/Etc.txt b/handbook/Etc.txt
index 61d29e4940..903b1ba09b 100644
--- a/handbook/Etc.txt
+++ b/handbook/Etc.txt
@@ -241,7 +241,7 @@
4000172 - Three-Tailed Foxtail - A soft foxtail that Three-Tailed Fox removed from itself.
4000173 - Broom - A small broom made from bushes that Blins dropped.
4000174 - Money Envelope - You can sell this envelope for 10,000 meso in the store.
-4000175 - Minature Pianus - A miniature version of Pianus
+4000175 - Miniature Pianus - A miniature version of Pianus
4000176 - Poisonous Mushroom - A poisonous mushroom that lives on the humongous Zombie Mushroom.
4000177 - Mixed Block - A block from Mix Golem that consists of its chest area.
4000178 - Iron Boar Armor - A small piece of the solid armor from Iron Boar.
diff --git a/scripts/npc/1012100.js b/scripts/npc/1012100.js
index 8063e7a86d..a132cfce17 100644
--- a/scripts/npc/1012100.js
+++ b/scripts/npc/1012100.js
@@ -49,7 +49,7 @@ function start() {
if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType))
cm.sendNext("So you decided to become a #rBowman#k?");
else {
- cm.sendOk("Train a bit more and I can show you the way of the #rBowman#k.");
+ cm.sendOk("Train a bit more until you reach #blevel 10, " + cm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rBowman#k.");
cm.dispose();
}
} else if (cm.getLevel() >= 30 && cm.getJobId() == 300) {
diff --git a/scripts/npc/1022000.js b/scripts/npc/1022000.js
index 231c841844..a112e93b0c 100644
--- a/scripts/npc/1022000.js
+++ b/scripts/npc/1022000.js
@@ -50,7 +50,7 @@ function start() {
if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType))
cm.sendNext("Do you want to become a Warrior? You need to meet some criteria in order to do so.#b You should be at least in level 10, with at least 35 in STR#k. Let's see...");
else {
- cm.sendOk("Train a bit more and I can show you the way of the #rWarrior#k.");
+ cm.sendOk("Train a bit more until you reach #blevel 10, " + cm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rWarrior#k.");
cm.dispose();
}
} else if (cm.getLevel() >= 30 && cm.getJobId() == 100) {
diff --git a/scripts/npc/1032001.js b/scripts/npc/1032001.js
index 527b31602a..993eb304fc 100644
--- a/scripts/npc/1032001.js
+++ b/scripts/npc/1032001.js
@@ -50,7 +50,7 @@ function start() {
if (cm.getLevel() >= 8 && cm.canGetFirstJob(jobType))
cm.sendNext("Want to be a magician? There are some standards to meet. because we can't just accept EVERYONE in... #bYour level should be at least 8#k, with getting INT as your top priority. Let's see.");
else {
- cm.sendOk("Train a bit more and I can show you the way of the #rMagician#k.");
+ cm.sendOk("Train a bit more until you reach #blevel 10, " + cm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rMagician#k.");
cm.dispose();
}
} else if (cm.getLevel() >= 30 && cm.getJobId() == 200) {
diff --git a/scripts/npc/1052001.js b/scripts/npc/1052001.js
index 7ef886ac0a..3f76c7fb14 100644
--- a/scripts/npc/1052001.js
+++ b/scripts/npc/1052001.js
@@ -49,7 +49,7 @@ function start() {
if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType))
cm.sendNext("Want to be a thief? There are some standards to meet. because we can't just accept EVERYONE in... #bYour level should be at least 10, with your DEX over 25#k. Let's see.");
else {
- cm.sendOk("Train a bit more and I can show you the way of the #rThief#k.");
+ cm.sendOk("Train a bit more until you reach #blevel 10, " + cm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rThief#k.");
cm.dispose();
}
} else if (cm.getLevel() >= 30 && cm.getJobId() == 400) {
diff --git a/scripts/npc/1090000.js b/scripts/npc/1090000.js
index c0163e07f4..575c7aaffb 100644
--- a/scripts/npc/1090000.js
+++ b/scripts/npc/1090000.js
@@ -49,7 +49,7 @@ function start() {
if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType))
cm.sendNext("Want to be a pirate? There are some standards to meet. because we can't just accept EVERYONE in... #bYour level should be at least 10#k. Let's see.");
else {
- cm.sendOk("Train a bit more and I can show you the way of the #rPirate#k.");
+ cm.sendOk("Train a bit more until you reach #blevel 10, " + cm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rPirate#k.");
cm.dispose();
}
} else if (cm.getLevel() >= 30 && cm.getJobId() == 500) {
diff --git a/scripts/npc/1092090.js b/scripts/npc/1092090.js
index ed89201800..2dac576325 100644
--- a/scripts/npc/1092090.js
+++ b/scripts/npc/1092090.js
@@ -19,34 +19,34 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
-status = -1;
function start() {
- if(cm.haveItem(4031847))
- cm.sendNext("The hungry calf is drinking all the milk! The bottle remains empty...");
- else if(cm.haveItem(4031848) || cm.haveItem(4031849) || cm.haveItem(4031850)){
- cm.sendNext("The hungry calf is drinking all the milk! The bottle is now empty.");
- if(cm.haveItem(4031848))
- cm.gainItem(4031848,-1);
- else if(cm.haveItem(4031849))
- cm.gainItem(4031849, -1);
- else
- cm.gainItem(4031850, -1);
- cm.gainItem(4031847, 1);
+ if(cm.getQuestProgress(2180, 0) == 1) {
+ cm.sendNext("You have taken milk from this cow recently, check another cow.");
cm.dispose();
+ return;
}
-}
-
-function action(mode, type, selection){
- if(mode == -1)
- cm.dispose();
- else if(mode == 0){
- status--;
- start();
- }else
- status++;
- if(status == 0)
- cm.sendPrev("The hungry calf isn't interested in the empty bottle.");
- else if(status == 1)
- cm.dispose();
+
+ if (cm.canHold(4031848) && cm.haveItem(4031847)) {
+ cm.sendNext("Now filling up the bottle with milk. The bottle is now 1/3 full of milk.");
+ cm.gainItem(4031847, -1);
+ cm.gainItem(4031848, 1);
+
+ cm.setQuestProgress(2180, 0, 1);
+ } else if (cm.canHold(4031849, 1) && cm.haveItem(4031848)) {
+ cm.sendNext("Now filling up the bottle with milk. The bottle is now 2/3 full of milk.");
+ cm.gainItem(4031848, -1);
+ cm.gainItem(4031849, 1);
+
+ cm.setQuestProgress(2180, 0, 1);
+ } else if (cm.canHold(4031850) && cm.haveItem(4031849)) {
+ cm.sendNext("Now filling up the bottle with milk. The bottle is now completely full of milk.");
+ cm.gainItem(4031849, -1);
+ cm.gainItem(4031850, 1);
+
+ cm.setQuestProgress(2180, 0, 1);
+ } else {
+ cm.sendNext("Your inventory is full, and there's no room for a milk bottle.");
+ }
+ cm.dispose();
}
\ No newline at end of file
diff --git a/scripts/npc/1092091.js b/scripts/npc/1092091.js
index f6e83bfc2b..5d5db97d54 100644
--- a/scripts/npc/1092091.js
+++ b/scripts/npc/1092091.js
@@ -20,34 +20,33 @@
along with this program. If not, see .
*/
-status = -1;
-
function start() {
- if (cm.haveItem(4031847))
- cm.sendNext("The hungry calf is drinking all the milk! The bottle remains empty...");
- else if (cm.haveItem(4031848) || cm.haveItem(4031849) || cm.haveItem(4031850)) {
- cm.sendNext("The hungry calf is drinking all the milk! The bottle is now empty.");
- if (cm.haveItem(4031848))
- cm.gainItem(4031848,-1);
- else if (cm.haveItem(4031849))
- cm.gainItem(4031849, -1);
- else
- cm.gainItem(4031850, -1);
- cm.gainItem(4031847, 1);
+ if(cm.getQuestProgress(2180, 0) == 2) {
+ cm.sendNext("You have taken milk from this cow recently, check another cow.");
cm.dispose();
+ return;
}
-}
-
-function action(mode, type, selection){
- if (mode == -1)
- cm.dispose();
- else if (mode == 0) {
- status--;
- start();
- } else
- status++;
- if (status == 0)
- cm.sendPrev("The hungry calf isn't interested in the empty bottle.");
- else if (status == 1)
- cm.dispose();
+
+ if (cm.canHold(4031848) && cm.haveItem(4031847)){
+ cm.sendNext("Now filling up the bottle with milk. The bottle is now 1/3 full of milk.");
+ cm.gainItem(4031847, -1);
+ cm.gainItem(4031848, 1);
+
+ cm.setQuestProgress(2180, 0, 2);
+ } else if(cm.canHold(4031849) && cm.haveItem(4031848)){
+ cm.sendNext("Now filling up the bottle with milk. The bottle is now 2/3 full of milk.");
+ cm.gainItem(4031848, -1);
+ cm.gainItem(4031849, 1);
+
+ cm.setQuestProgress(2180, 0, 2);
+ } else if(cm.canHold(4031850) && cm.haveItem(4031849)){
+ cm.sendNext("Now filling up the bottle with milk. The bottle is now completely full of milk.");
+ cm.gainItem(4031849, -1);
+ cm.gainItem(4031850, 1);
+
+ cm.setQuestProgress(2180, 0, 2);
+ } else {
+ cm.sendNext("Your inventory is full, and there's no room for a milk bottle.");
+ }
+ cm.dispose();
}
\ No newline at end of file
diff --git a/scripts/npc/1092094.js b/scripts/npc/1092094.js
index 27d7a951b4..ed89201800 100644
--- a/scripts/npc/1092094.js
+++ b/scripts/npc/1092094.js
@@ -19,21 +19,34 @@
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
+status = -1;
function start() {
- if (cm.canHold(4031848) && cm.haveItem(4031847)) {
- cm.sendNext("Now filling up the bottle with milk. The bottle is now 1/3 full of milk.");
- cm.gainItem(4031847, -1);
- cm.gainItem(4031848, 1);
- } else if (cm.canHold(4031849, 1) && cm.haveItem(4031848)) {
- cm.sendNext("Now filling up the bottle with milk. The bottle is now 2/3 full of milk.");
- cm.gainItem(4031848, -1);
- cm.gainItem(4031849, 1);
- } else if (cm.canHold(4031850) && cm.haveItem(4031849)) {
- cm.sendNext("Now filling up the bottle with milk. The bottle is now completely full of milk.");
- cm.gainItem(4031849, -1);
- cm.gainItem(4031850, 1);
- } else
- cm.sendNext("Your inventory is full, and there's no room for a milk bottle.");
- cm.dispose();
+ if(cm.haveItem(4031847))
+ cm.sendNext("The hungry calf is drinking all the milk! The bottle remains empty...");
+ else if(cm.haveItem(4031848) || cm.haveItem(4031849) || cm.haveItem(4031850)){
+ cm.sendNext("The hungry calf is drinking all the milk! The bottle is now empty.");
+ if(cm.haveItem(4031848))
+ cm.gainItem(4031848,-1);
+ else if(cm.haveItem(4031849))
+ cm.gainItem(4031849, -1);
+ else
+ cm.gainItem(4031850, -1);
+ cm.gainItem(4031847, 1);
+ cm.dispose();
+ }
+}
+
+function action(mode, type, selection){
+ if(mode == -1)
+ cm.dispose();
+ else if(mode == 0){
+ status--;
+ start();
+ }else
+ status++;
+ if(status == 0)
+ cm.sendPrev("The hungry calf isn't interested in the empty bottle.");
+ else if(status == 1)
+ cm.dispose();
}
\ No newline at end of file
diff --git a/scripts/npc/1092095.js b/scripts/npc/1092095.js
index f3b9a6dca8..f6e83bfc2b 100644
--- a/scripts/npc/1092095.js
+++ b/scripts/npc/1092095.js
@@ -20,20 +20,34 @@
along with this program. If not, see .
*/
+status = -1;
+
function start() {
- if (cm.canHold(4031848) && cm.haveItem(4031847)){
- cm.sendNext("Now filling up the bottle with milk. The bottle is now 1/3 full of milk.");
- cm.gainItem(4031847, -1);
- cm.gainItem(4031848, 1);
- } else if(cm.canHold(4031849) && cm.haveItem(4031848)){
- cm.sendNext("Now filling up the bottle with milk. The bottle is now 2/3 full of milk.");
- cm.gainItem(4031848, -1);
- cm.gainItem(4031849, 1);
- } else if(cm.canHold(4031850) && cm.haveItem(4031849)){
- cm.sendNext("Now filling up the bottle with milk. The bottle is now completely full of milk.");
- cm.gainItem(4031849, -1);
- cm.gainItem(4031850, 1);
+ if (cm.haveItem(4031847))
+ cm.sendNext("The hungry calf is drinking all the milk! The bottle remains empty...");
+ else if (cm.haveItem(4031848) || cm.haveItem(4031849) || cm.haveItem(4031850)) {
+ cm.sendNext("The hungry calf is drinking all the milk! The bottle is now empty.");
+ if (cm.haveItem(4031848))
+ cm.gainItem(4031848,-1);
+ else if (cm.haveItem(4031849))
+ cm.gainItem(4031849, -1);
+ else
+ cm.gainItem(4031850, -1);
+ cm.gainItem(4031847, 1);
+ cm.dispose();
+ }
+}
+
+function action(mode, type, selection){
+ if (mode == -1)
+ cm.dispose();
+ else if (mode == 0) {
+ status--;
+ start();
} else
- cm.sendNext("Your inventory is full, and there's no room for a milk bottle.");
- cm.dispose();
+ status++;
+ if (status == 0)
+ cm.sendPrev("The hungry calf isn't interested in the empty bottle.");
+ else if (status == 1)
+ cm.dispose();
}
\ No newline at end of file
diff --git a/scripts/npc/9977777.js b/scripts/npc/9977777.js
index d0f427fbd3..b35a45ff68 100644
--- a/scripts/npc/9977777.js
+++ b/scripts/npc/9977777.js
@@ -144,6 +144,7 @@ function writeFeatureTab_Serverpotentials() {
addFeature("Enhanced auto-pot system: smart pet potion handle.");
addFeature("Enhanced buff system: best buffs effects takes place.");
addFeature("Enhanced AP auto-assigner: focus on eqp demands.");
+ addFeature("Consistent experience gain system.");
addFeature("NPC crafters won't take items freely anymore.");
addFeature("Duey: pkg rcvd popup and many delivery mechanics.");
addFeature("Pet pickup gives preference to player attacks.");
@@ -169,6 +170,7 @@ function writeFeatureTab_AdminGMcommands() {
function writeFeatureTab_CustomNPCs() {
addFeature("Spiegelmann: automatized rock-refiner.");
+ addFeature("Asia: scroll & rarities shop NPC.");
addFeature("Abdula: lists droppers of needed skill/mastery books.");
addFeature("Agent E: accessory crafter.");
addFeature("Donation Box: automatized item-buyer.");
@@ -197,6 +199,7 @@ function writeFeatureTab_Project() {
addFeature("Reviewed many Java aspects that needed attention.");
addFeature("Reviewed SQL data, eliminating duplicated entries.");
addFeature("Protected many flaws with login management system.");
+ addFeature("Developed many survey tools for content management.");
addFeature("ThreadTracker: runtime tool for deadlock detection.");
addFeature("Heavily reviewed future task management, spawning much less threads and relieving task overload on the TimerManager.");
}
diff --git a/scripts/quest/20101.js b/scripts/quest/20101.js
index 895361f630..f6d2107782 100644
--- a/scripts/quest/20101.js
+++ b/scripts/quest/20101.js
@@ -22,8 +22,8 @@ function end(mode, type, selection) {
qm.sendYesNo("Have you made your decision? The decision will be final, so think carefully before deciding what to do. Are you sure you want to become a Dawn Warrior?");
} else if (status == 1) {
if(!qm.canGetFirstJob(jobType)) {
- cm.sendOk("Train a bit more and I can show you the way of the #rDawn Warrior#k.");
- cm.dispose();
+ qm.sendOk("Train a bit more until you reach #blevel 10, " + qm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rDawn Warrior#k.");
+ qm.dispose();
return;
}
diff --git a/scripts/quest/20102.js b/scripts/quest/20102.js
index a32faea5d3..f4bd37ddcb 100644
--- a/scripts/quest/20102.js
+++ b/scripts/quest/20102.js
@@ -22,8 +22,8 @@ function end(mode, type, selection) {
qm.sendYesNo("Have you made your decision? The decision will be final, so think carefully before deciding what to do. Are you sure you want to become a Blaze Wizard?");
} else if (status == 1) {
if(!qm.canGetFirstJob(jobType)) {
- cm.sendOk("Train a bit more and I can show you the way of the #rBlaze Wizard#k.");
- cm.dispose();
+ qm.sendOk("Train a bit more until you reach #blevel 10, " + qm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rBlaze Wizard#k.");
+ qm.dispose();
return;
}
diff --git a/scripts/quest/20103.js b/scripts/quest/20103.js
index 223632f608..03d54ab4e8 100644
--- a/scripts/quest/20103.js
+++ b/scripts/quest/20103.js
@@ -22,8 +22,8 @@ function end(mode, type, selection) {
qm.sendYesNo("Have you made your decision? The decision will be final, so think carefully before deciding what to do. Are you sure you want to become a Wind Archer?");
} else if (status == 1) {
if(!qm.canGetFirstJob(jobType)) {
- cm.sendOk("Train a bit more and I can show you the way of the #rWind Archer#k.");
- cm.dispose();
+ qm.sendOk("Train a bit more until you reach #blevel 10, " + qm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rWind Archer#k.");
+ qm.dispose();
return;
}
diff --git a/scripts/quest/20104.js b/scripts/quest/20104.js
index 6e1ef777e5..a0fc3311a0 100644
--- a/scripts/quest/20104.js
+++ b/scripts/quest/20104.js
@@ -22,8 +22,8 @@ function end(mode, type, selection) {
qm.sendYesNo("Have you made your decision? The decision will be final, so think carefully before deciding what to do. Are you sure you want to become a Night Walker?");
} else if (status == 1) {
if(!qm.canGetFirstJob(jobType)) {
- cm.sendOk("Train a bit more and I can show you the way of the #rNight Walker#k.");
- cm.dispose();
+ qm.sendOk("Train a bit more until you reach #blevel 10, " + qm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rNight Walker#k.");
+ qm.dispose();
return;
}
diff --git a/scripts/quest/20105.js b/scripts/quest/20105.js
index 517578e793..5de72b98f4 100644
--- a/scripts/quest/20105.js
+++ b/scripts/quest/20105.js
@@ -22,8 +22,8 @@ function end(mode, type, selection) {
qm.sendYesNo("Have you made your decision? The decision will be final, so think carefully before deciding what to do. Are you sure you want to become a Thunder Breaker?");
} else if (status == 1) {
if(!qm.canGetFirstJob(jobType)) {
- cm.sendOk("Train a bit more and I can show you the way of the #rThunder Breaker#k.");
- cm.dispose();
+ qm.sendOk("Train a bit more until you reach #blevel 10, " + qm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rThunder Breaker#k.");
+ qm.dispose();
return;
}
diff --git a/scripts/quest/4647.js b/scripts/quest/4647.js
index b317e99f17..c26f42341b 100644
--- a/scripts/quest/4647.js
+++ b/scripts/quest/4647.js
@@ -41,7 +41,7 @@ function end(mode, type, selection) {
if (status == 0) {
if(qm.haveItem(5460000)) {
qm.sendOk("You got the Pet Snack! Thanks! You can use these to feed multiple pets at once!");
- qm.teachSkill(0008, 1, 1, -1);
+ qm.teachSkill(8, 1, 1, -1);
qm.gainItem(5460000, -1, false);
qm.completeQuest();
qm.dispose();
diff --git a/scripts/reactor/8001000.js b/scripts/reactor/8001000.js
index f4f08d7db5..6be4803511 100644
--- a/scripts/reactor/8001000.js
+++ b/scripts/reactor/8001000.js
@@ -20,5 +20,5 @@
along with this program. If not, see .
*/
function act(){
- rm.spawnMonster(9400112);
+ rm.spawnMonster(9400112, 1, 420, 160);
}
\ No newline at end of file
diff --git a/sql/db_database.sql b/sql/db_database.sql
index 06f3974ba2..6439a3e495 100644
--- a/sql/db_database.sql
+++ b/sql/db_database.sql
@@ -3794,7 +3794,7 @@ INSERT IGNORE INTO `temp_data` (`id`, `dropperid`, `itemid`, `minimum_quantity`,
(3575, 4250001, 1442005, 1, 1, 0, 700),
(3576, 4250001, 4000438, 1, 1, 0, 600000),
(3577, 4250001, 4130014, 1, 1, 0, 6000),
-(3578, 5090000, 4000413, 1, 1, 0, 600000),
+(3578, 5090000, 4000413, 1, 1, 0, 200000),
(3579, 5090001, 4000412, 1, 1, 0, 600000),
(3580, 5100000, 4000048, 1, 1, 0, 600000),
(3581, 5100000, 4003005, 1, 1, 0, 200000),
@@ -7995,7 +7995,7 @@ INSERT IGNORE INTO `temp_data` (`id`, `dropperid`, `itemid`, `minimum_quantity`,
(7772, 8180000, 4004001, 1, 1, 0, 100000),
(7773, 8180000, 4004002, 1, 1, 0, 100000),
(7774, 8180000, 4004003, 1, 1, 0, 100000),
-(7775, 8180000, 4000235, 1, 1, 0, 600000),
+(7775, 8180000, 4000235, 1, 1, 0, 200000),
(7776, 8180000, 4000244, 1, 1, 0, 20000),
(7777, 8180000, 4000245, 1, 1, 0, 20000),
(7778, 8180000, 2290003, 1, 1, 0, 5000),
@@ -8052,7 +8052,7 @@ INSERT IGNORE INTO `temp_data` (`id`, `dropperid`, `itemid`, `minimum_quantity`,
(7829, 8180001, 4004001, 1, 1, 0, 100000),
(7830, 8180001, 4004002, 1, 1, 0, 100000),
(7831, 8180001, 4004003, 1, 1, 0, 100000),
-(7832, 8180001, 4000243, 1, 1, 0, 600000),
+(7832, 8180001, 4000243, 1, 1, 0, 200000),
(7833, 8180001, 4000244, 1, 1, 0, 20000),
(7834, 8180001, 4000245, 1, 1, 0, 20000),
(7835, 8180001, 2290018, 1, 1, 0, 5000),
@@ -10413,10 +10413,10 @@ INSERT IGNORE INTO `temp_data` (`id`, `dropperid`, `itemid`, `minimum_quantity`,
(10190, 9400546, 4031681, 1, 1, 4915, 1000000),
(10191, 9600005, 4000191, 1, 1, 0, 1000000),
(10192, 9600006, 4000192, 1, 1, 0, 1000000),
-(10193, 8500002, 4031196, 1, 1, 0, 1000000),
-(10194, 8500002, 4031196, 1, 1, 0, 1000000),
-(10195, 8500002, 4031196, 1, 1, 0, 187500),
-(10196, 8500002, 4031196, 1, 1, 0, 125000),
+(10193, 8500002, 4031196, 1, 1, 0, 80000),
+(10194, 8500002, 4031196, 1, 1, 0, 80000),
+(10195, 8500002, 4031196, 1, 1, 0, 80000),
+(10196, 8500002, 4031196, 1, 1, 0, 80000),
(10197, 3000006, 4031209, 1, 1, 3072, 500000),
(10198, 2230109, 4031209, 1, 1, 3072, 500000),
(10199, 2230200, 4031209, 1, 1, 3072, 500000),
diff --git a/sql/db_drops.sql b/sql/db_drops.sql
index 3e39c0a3fe..a7cb0be4db 100644
--- a/sql/db_drops.sql
+++ b/sql/db_drops.sql
@@ -15794,15 +15794,15 @@ USE `heavenms`;
(6400006, 4030012, 1, 1, 0, 125000),
(6400009, 4030012, 1, 1, 0, 125000),
(9303013, 4030012, 1, 1, 0, 125000),
-(8150000, 4031906, 1, 1, 0, 7000),
-(9300210, 4031906, 1, 1, 0, 7000),
-(9500140, 4031906, 1, 1, 0, 7000),
-(9500171, 4031906, 1, 1, 0, 7000),
-(9500328, 4031906, 1, 1, 0, 7000),
-(9500358, 4031906, 1, 1, 0, 7000),
-(6400006, 4031906, 1, 1, 0, 7000),
-(6400009, 4031906, 1, 1, 0, 7000),
-(9303013, 4031906, 1, 1, 0, 7000),
+(8150000, 4031906, 1, 1, 0, 400000),
+(9300210, 4031906, 1, 1, 0, 400000),
+(9500140, 4031906, 1, 1, 0, 400000),
+(9500171, 4031906, 1, 1, 0, 400000),
+(9500328, 4031906, 1, 1, 0, 400000),
+(9500358, 4031906, 1, 1, 0, 400000),
+(6400006, 4031906, 1, 1, 0, 400000),
+(6400009, 4031906, 1, 1, 0, 400000),
+(9303013, 4031906, 1, 1, 0, 400000),
(8150000, 2043002, 1, 4, 0, 10000),
(9300210, 2043002, 1, 4, 0, 10000),
(9500140, 2043002, 1, 4, 0, 10000),
@@ -17473,18 +17473,18 @@ USE `heavenms`;
(8200010, 1072225, 1, 1, 0, 700),
(8200010, 1082158, 1, 1, 0, 700),
(8200010, 1002646, 1, 1, 0, 700),
-(8500002, 4031196, 1, 1, 0, 7000),
-(9300214, 4031196, 1, 1, 0, 7000),
-(9500180, 4031196, 1, 1, 0, 7000),
-(9500181, 4031196, 1, 1, 0, 7000),
-(9500331, 4031196, 1, 1, 0, 7000),
-(9500362, 4031196, 1, 1, 0, 7000),
-(8500002, 4031901, 1, 1, 0, 7000),
-(9300214, 4031901, 1, 1, 0, 7000),
-(9500180, 4031901, 1, 1, 0, 7000),
-(9500181, 4031901, 1, 1, 0, 7000),
-(9500331, 4031901, 1, 1, 0, 7000),
-(9500362, 4031901, 1, 1, 0, 7000),
+(8500002, 4031196, 1, 1, 0, 80000),
+(9300214, 4031196, 1, 1, 0, 80000),
+(9500180, 4031196, 1, 1, 0, 80000),
+(9500181, 4031196, 1, 1, 0, 80000),
+(9500331, 4031196, 1, 1, 0, 80000),
+(9500362, 4031196, 1, 1, 0, 80000),
+(8500002, 4031901, 1, 1, 0, 400000),
+(9300214, 4031901, 1, 1, 0, 400000),
+(9500180, 4031901, 1, 1, 0, 400000),
+(9500181, 4031901, 1, 1, 0, 400000),
+(9500331, 4031901, 1, 1, 0, 400000),
+(9500362, 4031901, 1, 1, 0, 400000),
(8500002, 4001084, 1, 1, 0, 7000),
(9300214, 4001084, 1, 1, 0, 7000),
(9500180, 4001084, 1, 1, 0, 7000),
@@ -20775,7 +20775,7 @@ USE `heavenms`;
UPDATE drop_data SET questid=28248 WHERE itemid=4001358;
UPDATE drop_data SET questid=28248 WHERE itemid=4001359;
UPDATE drop_data SET questid=28175 WHERE itemid=4001342;
- UPDATE drop_data SET questid=7777 WHERE itemid=4031906; #id 7777 for ALL "quest items" with no v83 quest.
+ UPDATE drop_data SET questid=7777 WHERE itemid=4031905; #id 7777 for ALL "quest items" with no v83 quest.
UPDATE drop_data SET chance=0 WHERE itemid=2050099;
UPDATE drop_data SET questid=6191 WHERE itemid=4031477;
UPDATE drop_data SET questid=6190 WHERE itemid=4001111;
@@ -21364,7 +21364,7 @@ USE `heavenms`;
(9300270, 2022431, 1, 1, 0, 200000),
(9300270, 2022432, 1, 1, 0, 200000),
(9300270, 2022433, 1, 1, 0, 200000),
-(6090002, 4000414, 1, 1, 0, 400000),
+(6090002, 4000414, 1, 1, 0, 200000),
(9300147, 4001132, 1, 1, 0, 400000),
(9300148, 4001133, 1, 1, 0, 100000),
(4300012, 4000537, 1, 1, 0, 200000),
diff --git a/src/client/MapleCharacter.java b/src/client/MapleCharacter.java
index ad3ecc6922..60ba02f2b1 100644
--- a/src/client/MapleCharacter.java
+++ b/src/client/MapleCharacter.java
@@ -273,10 +273,10 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
private ScheduledFuture> extraRecoveryTask = null;
private ScheduledFuture> chairRecoveryTask = null;
private ScheduledFuture> pendantOfSpirit = null; //1122017
- private Lock chrLock = new MonitoredReentrantLock(MonitoredLockType.CHR, true);
- private Lock effLock = new MonitoredReentrantLock(MonitoredLockType.EFF, true);
- private Lock petLock = new MonitoredReentrantLock(MonitoredLockType.PET, true); // for quest tasks as well
- private Lock prtLock = new MonitoredReentrantLock(MonitoredLockType.PRT);
+ private Lock chrLock = new MonitoredReentrantLock(MonitoredLockType.CHARACTER_CHR, true);
+ private Lock effLock = new MonitoredReentrantLock(MonitoredLockType.CHARACTER_EFF, true);
+ private Lock petLock = new MonitoredReentrantLock(MonitoredLockType.CHARACTER_PET, true); // for quest tasks as well
+ private Lock prtLock = new MonitoredReentrantLock(MonitoredLockType.CHARACTER_PRT);
private Map> excluded = new LinkedHashMap<>();
private Set excludedItems = new LinkedHashSet<>();
private static String[] ariantroomleader = new String[3];
diff --git a/src/client/MapleClient.java b/src/client/MapleClient.java
index a01deed9e3..9b51e6e694 100644
--- a/src/client/MapleClient.java
+++ b/src/client/MapleClient.java
@@ -107,8 +107,8 @@ public class MapleClient {
private byte gender = -1;
private boolean disconnecting = false;
private final Lock lock = new MonitoredReentrantLock(MonitoredLockType.CLIENT, true);
- private final Lock encoderLock = new MonitoredReentrantLock(MonitoredLockType.CLIENT, true);
- private static final Lock loginLock = new MonitoredReentrantLock(MonitoredLockType.CLIENT, true);
+ private final Lock encoderLock = new MonitoredReentrantLock(MonitoredLockType.CLIENT_ENCODER, true);
+ private static final Lock loginLock = new MonitoredReentrantLock(MonitoredLockType.CLIENT_LOGIN, true);
private int votePoints;
private int voteTime = -1;
private long lastNpcClick;
diff --git a/src/client/status/MonsterStatusEffect.java b/src/client/status/MonsterStatusEffect.java
index 8cc2bc3bb1..56deeef26b 100644
--- a/src/client/status/MonsterStatusEffect.java
+++ b/src/client/status/MonsterStatusEffect.java
@@ -23,7 +23,6 @@ package client.status;
import client.Skill;
import java.util.Map;
-import java.util.concurrent.ScheduledFuture;
import server.life.MobSkill;
import tools.ArrayMap;
@@ -33,9 +32,7 @@ public class MonsterStatusEffect {
private Skill skill;
private MobSkill mobskill;
private boolean monsterSkill;
- private ScheduledFuture> cancelTask;
- private ScheduledFuture> damageSchedule;
-
+
public MonsterStatusEffect(Map stati, Skill skillId, MobSkill mobskill, boolean monsterSkill) {
this.stati = new ArrayMap<>(stati);
this.skill = skillId;
@@ -59,35 +56,10 @@ public class MonsterStatusEffect {
return monsterSkill;
}
- public final void cancelTask() {
- if (cancelTask != null) {
- cancelTask.cancel(false);
- }
- cancelTask = null;
- }
-
- public ScheduledFuture> getCancelTask() {
- return cancelTask;
- }
-
- public void setCancelTask(ScheduledFuture> cancelTask) {
- this.cancelTask = cancelTask;
- }
-
public void removeActiveStatus(MonsterStatus stat) {
stati.remove(stat);
}
- public void setDamageSchedule(ScheduledFuture> damageSchedule) {
- this.damageSchedule = damageSchedule;
- }
-
- public void cancelDamageSchedule() {
- if (damageSchedule != null) {
- damageSchedule.cancel(false);
- }
- }
-
public MobSkill getMobSkill() {
return mobskill;
}
diff --git a/src/constants/ServerConstants.java b/src/constants/ServerConstants.java
index a76f8c4b2a..ad68592292 100644
--- a/src/constants/ServerConstants.java
+++ b/src/constants/ServerConstants.java
@@ -123,6 +123,10 @@ public class ServerConstants {
public static final int ITEM_LIMIT_ON_MAP = 200; //Max number of items allowed on a map.
public static final int MAP_VISITED_SIZE = 5; //Max length for last mapids visited by a player. This is used to recover and update drops on these maps accordingly with player actions.
+ //Channel Mob Disease Monitor Configuration
+ public static final int MOB_STATUS_MONITOR_PROC = 200; //Frequency in milliseconds between each proc on the mob disease monitor schedule.
+ public static final int MOB_STATUS_MONITOR_LIFE = 84; //Idle proc count the mob disease monitor is allowed to be there before closing it due to inactivity.
+
//Some Gameplay Enhancing Configurations
//Scroll Configuration
public static final boolean USE_PERFECT_GM_SCROLL = true; //Scrolls from GMs never uses up slots nor fails.
diff --git a/src/net/server/Server.java b/src/net/server/Server.java
index 8b7a724977..c7c70e273c 100644
--- a/src/net/server/Server.java
+++ b/src/net/server/Server.java
@@ -96,8 +96,8 @@ public class Server {
private final Map guilds = new HashMap<>(100);
private final Map inLoginState = new HashMap<>(100);
private final Lock srvLock = new MonitoredReentrantLock(MonitoredLockType.SERVER);
- private final Lock lgnLock = new MonitoredReentrantLock(MonitoredLockType.SERVER);
- private final Lock disLock = new MonitoredReentrantLock(MonitoredLockType.SERVER);
+ private final Lock lgnLock = new MonitoredReentrantLock(MonitoredLockType.SERVER_LOGIN);
+ private final Lock disLock = new MonitoredReentrantLock(MonitoredLockType.SERVER_DISEASES);
private final PlayerBuffStorage buffStorage = new PlayerBuffStorage();
private final Map alliances = new HashMap<>(100);
private final Map newyears = new HashMap<>();
diff --git a/src/net/server/audit/ThreadTracker.java b/src/net/server/audit/ThreadTracker.java
index 50e0d49f1a..9d3ed7dd3c 100644
--- a/src/net/server/audit/ThreadTracker.java
+++ b/src/net/server/audit/ThreadTracker.java
@@ -233,7 +233,7 @@ public class ThreadTracker {
List list = threadTracker.get(tid);
for(int i = list.size() - 1; i >= 0; i--) {
- if(lockId.getValue() == list.get(i).getValue()) {
+ if(lockId.equals(list.get(i))) {
list.remove(i);
break;
}
diff --git a/src/net/server/channel/Channel.java b/src/net/server/channel/Channel.java
index b4c3b45499..ad43f71bd6 100644
--- a/src/net/server/channel/Channel.java
+++ b/src/net/server/channel/Channel.java
@@ -21,6 +21,7 @@ along with this program. If not, see .
*/
package net.server.channel;
+import net.server.channel.worker.MobStatusScheduler;
import java.io.File;
import java.net.InetSocketAddress;
import java.util.ArrayList;
@@ -70,6 +71,7 @@ import server.maps.MapleMiniDungeon;
import tools.MaplePacketCreator;
import tools.Pair;
import client.MapleCharacter;
+import client.status.MonsterStatusEffect;
import constants.ServerConstants;
import server.maps.MapleMiniDungeonInfo;
import tools.locks.MonitoredLockType;
@@ -83,6 +85,7 @@ public final class Channel {
private String ip, serverMessage;
private MapleMapFactory mapFactory;
private EventScriptManager eventSM;
+ private MobStatusScheduler mobStatusSchedulers[] = new MobStatusScheduler[4];
private Map hiredMerchants = new HashMap<>();
private final Map storedVars = new HashMap<>();
private List expeditions = new ArrayList<>();
@@ -149,6 +152,10 @@ public final class Channel {
dojoTask[i] = null;
}
+ for(int i = 0; i < 4; i++) {
+ mobStatusSchedulers[i] = new MobStatusScheduler();
+ }
+
System.out.println(" Channel " + getId() + ": Listening on port " + port);
} catch (Exception e) {
e.printStackTrace();
@@ -817,6 +824,34 @@ public final class Channel {
}
}
+ private static int getMobStatusSchedulerIndex(int mapid) {
+ if(mapid >= 250000000) {
+ if(mapid >= 900000000) {
+ return 3;
+ } else {
+ return 2;
+ }
+ } else {
+ if(mapid >= 200000000) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+
+ public void registerMobStatus(int mapid, MonsterStatusEffect mse, Runnable cancelAction, long duration) {
+ registerMobStatus(mapid, mse, cancelAction, duration, null, -1);
+ }
+
+ public void registerMobStatus(int mapid, MonsterStatusEffect mse, Runnable cancelAction, long duration, Runnable overtimeAction, int overtimeDelay) {
+ mobStatusSchedulers[getMobStatusSchedulerIndex(mapid)].registerMobStatus(mse, cancelAction, duration, overtimeAction, overtimeDelay);
+ }
+
+ public void interruptMobStatus(int mapid, MonsterStatusEffect mse) {
+ mobStatusSchedulers[getMobStatusSchedulerIndex(mapid)].interruptMobStatus(mse);
+ }
+
public void debugMarriageStatus() {
System.out.println(" ----- WORLD DATA -----");
Server.getInstance().getWorld(world).debugMarriageStatus();
diff --git a/src/net/server/channel/handlers/AbstractDealDamageHandler.java b/src/net/server/channel/handlers/AbstractDealDamageHandler.java
index c123a49695..aec0ca92f1 100644
--- a/src/net/server/channel/handlers/AbstractDealDamageHandler.java
+++ b/src/net/server/channel/handlers/AbstractDealDamageHandler.java
@@ -326,6 +326,15 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl
} else if (attack.skill == Outlaw.HOMING_BEACON || attack.skill == Corsair.BULLSEYE) {
player.setMarkedMonster(monster.getObjectId());
player.announce(MaplePacketCreator.giveBuff(1, attack.skill, Collections.singletonList(new Pair<>(MapleBuffStat.HOMING_BEACON, monster.getObjectId()))));
+ } else if (attack.skill == Outlaw.FLAME_THROWER) {
+ if (!monster.isBoss()) {
+ Skill type = SkillFactory.getSkill(Outlaw.FLAME_THROWER);
+ if (player.getSkillLevel(type) > 0) {
+ MapleStatEffect DoT = type.getEffect(player.getSkillLevel(type));
+ MonsterStatusEffect monsterStatusEffect = new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.POISON, 1), type, null, false);
+ monster.applyStatus(player, monsterStatusEffect, true, DoT.getDuration(), false);
+ }
+ }
}
if (job == 2111 || job == 2112) {
@@ -406,15 +415,6 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl
}
}
}
- } else if (job == 521 || job == 522) { // from what I can gather this is how it should work
- if (!monster.isBoss()) {
- Skill type = SkillFactory.getSkill(Outlaw.FLAME_THROWER);
- if (player.getSkillLevel(type) > 0) {
- MapleStatEffect DoT = type.getEffect(player.getSkillLevel(type));
- MonsterStatusEffect monsterStatusEffect = new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.POISON, 1), type, null, false);
- monster.applyStatus(player, monsterStatusEffect, true, DoT.getDuration(), false);
- }
- }
} else if (job >= 311 && job <= 322) {
if (!monster.isBoss()) {
Skill mortalBlow;
diff --git a/src/net/server/channel/handlers/MoveLifeHandler.java b/src/net/server/channel/handlers/MoveLifeHandler.java
index b79c423aea..6223b19f31 100644
--- a/src/net/server/channel/handlers/MoveLifeHandler.java
+++ b/src/net/server/channel/handlers/MoveLifeHandler.java
@@ -27,6 +27,7 @@ import java.awt.Point;
import java.util.ArrayList;
import java.util.List;
import server.life.MapleMonster;
+import server.life.MapleMonsterInformationProvider;
//import server.life.MobAttackInfo;
//import server.life.MobAttackInfoFactory;
import server.life.MobSkill;
@@ -94,20 +95,27 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler {
} else if (toUse.getHP() < percHpLeft) {
toUse = null;
} else if (monster.canUseSkill(toUse)) {
- toUse.applyEffect(c.getPlayer(), monster, true, banishPlayers);
+ int animationTime = MapleMonsterInformationProvider.getInstance().getMobSkillAnimationTime(monster.getId(), Random);
+ if(animationTime > 0) {
+ toUse.applyDelayedEffect(c.getPlayer(), monster, true, banishPlayers, animationTime);
+ } else {
+ toUse.applyEffect(c.getPlayer(), monster, true, banishPlayers);
+ }
} else {
toUse = null;
}
} else {
+ toUse = null; // paliative measure for suspicious mob movement
+
+ /*
long curtime = System.currentTimeMillis();
if(curtime >= monster.getNextBasicSkillTime()) { // dont use the special attack too often, chase the player f3
- //MobAttackInfo mobAttack = MobAttackInfoFactory.getMobAttackInfo(monster, attackId);
- //monster.setNextBasicSkillTime(curtime);
-
- toUse = null; // paliative measure for suspicious mob movement
+ MobAttackInfo mobAttack = MobAttackInfoFactory.getMobAttackInfo(monster, attackId);
+ monster.setNextBasicSkillTime(curtime);
} else {
toUse = null;
}
+ */
}
}
diff --git a/src/net/server/channel/worker/MobStatusScheduler.java b/src/net/server/channel/worker/MobStatusScheduler.java
new file mode 100644
index 0000000000..083ade0827
--- /dev/null
+++ b/src/net/server/channel/worker/MobStatusScheduler.java
@@ -0,0 +1,148 @@
+/*
+ This file is part of the HeavenMS MapleStory Server
+ Copyleft 2016 - 2018 RonanLana
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation version 3 as published by
+ the Free Software Foundation. You may not use, modify or distribute
+ this program under any other version of the GNU Affero General Public
+ License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+package net.server.channel.worker;
+
+import client.status.MonsterStatusEffect;
+import constants.ServerConstants;
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.locks.Lock;
+import server.TimerManager;
+import tools.Pair;
+import tools.locks.MonitoredLockType;
+import tools.locks.MonitoredReentrantLock;
+
+/**
+ *
+ * @author Ronan
+ */
+public class MobStatusScheduler {
+ private int idleProcs = 0;
+ private Map> registeredMobStatus = new HashMap<>();
+ private Map registeredMobStatusOvertime = new HashMap<>();
+
+ private class MobStatusOvertimeEntry {
+ private int procCount;
+ private int procLimit;
+ private Runnable r;
+
+ protected MobStatusOvertimeEntry(int delay, Runnable run) {
+ procCount = 0;
+ procLimit = (int)Math.ceil((float) delay / ServerConstants.MOB_STATUS_MONITOR_PROC);
+ r = run;
+ }
+
+ protected void update() {
+ procCount++;
+ if(procCount >= procLimit) {
+ procCount = 0;
+ r.run();
+ }
+ }
+ }
+
+ private ScheduledFuture> mobStatusSchedule = null;
+ private Lock mobStatusLock = new MonitoredReentrantLock(MonitoredLockType.CHANNEL_MOBSTATUS, true);
+ private Runnable monitorTask = new Runnable() {
+ @Override
+ public void run() {
+ runMobStatusSchedule();
+ }
+ };
+
+ private void runMobStatusSchedule() {
+ mobStatusLock.lock();
+ try {
+ if(registeredMobStatus.isEmpty()) {
+ idleProcs++;
+
+ if(idleProcs >= ServerConstants.MOB_STATUS_MONITOR_LIFE) {
+ if(mobStatusSchedule != null) {
+ mobStatusSchedule.cancel(false);
+ mobStatusSchedule = null;
+ }
+ }
+
+ return;
+ }
+ idleProcs = 0;
+
+ long timeNow = System.currentTimeMillis();
+ List toRemove = new LinkedList<>();
+ for(Entry> rmd : registeredMobStatus.entrySet()) {
+ Pair r = rmd.getValue();
+
+ if(r.getRight() < timeNow) {
+ r.getLeft().run(); // runs the cancel action
+ toRemove.add(rmd.getKey());
+ }
+ }
+
+ for(MonsterStatusEffect mse : toRemove) {
+ registeredMobStatus.remove(mse);
+ registeredMobStatusOvertime.remove(mse);
+ }
+
+ // it's probably ok to use one thread for both management & overtime actions
+ List mdoeList = new ArrayList<>(registeredMobStatusOvertime.values());
+ for(MobStatusOvertimeEntry mdoe : mdoeList) {
+ mdoe.update();
+ }
+ } finally {
+ mobStatusLock.unlock();
+ }
+ }
+
+ public void registerMobStatus(MonsterStatusEffect mse, Runnable cancelStatus, long duration, Runnable overtimeStatus, int overtimeDelay) {
+ mobStatusLock.lock();
+ try {
+ idleProcs = 0;
+ if(mobStatusSchedule == null) {
+ mobStatusSchedule = TimerManager.getInstance().register(monitorTask, ServerConstants.MOB_STATUS_MONITOR_PROC, ServerConstants.MOB_STATUS_MONITOR_PROC);
+ }
+
+ registeredMobStatus.put(mse, new Pair<>(cancelStatus, System.currentTimeMillis() + duration));
+
+ if(overtimeStatus != null) {
+ MobStatusOvertimeEntry mdoe = new MobStatusOvertimeEntry(overtimeDelay, overtimeStatus);
+ registeredMobStatusOvertime.put(mse, mdoe);
+ }
+ } finally {
+ mobStatusLock.unlock();
+ }
+ }
+
+ public void interruptMobStatus(MonsterStatusEffect mse) {
+ mobStatusLock.lock();
+ try {
+ Pair rmd = registeredMobStatus.remove(mse);
+ if(rmd != null) rmd.getLeft().run();
+
+ registeredMobStatusOvertime.remove(mse);
+ } finally {
+ mobStatusLock.unlock();
+ }
+ }
+}
diff --git a/src/net/server/worker/BaseWorker.java b/src/net/server/worker/BaseWorker.java
index 283415f478..1b40005331 100644
--- a/src/net/server/worker/BaseWorker.java
+++ b/src/net/server/worker/BaseWorker.java
@@ -24,7 +24,7 @@ import net.server.world.World;
/**
* @author Ronan
*/
-public class BaseWorker implements Runnable {
+public abstract class BaseWorker implements Runnable {
protected World wserv;
@Override
diff --git a/src/net/server/world/World.java b/src/net/server/world/World.java
index c2eae5bcf9..155f2366f7 100644
--- a/src/net/server/world/World.java
+++ b/src/net/server/world/World.java
@@ -124,7 +124,7 @@ public class World {
private Map registeredTimedMapObjects = new LinkedHashMap<>();
private ScheduledFuture> timedMapObjectsSchedule;
- private Lock timedMapObjectLock = new MonitoredReentrantLock(MonitoredLockType.MAP_OBJS, true);
+ private Lock timedMapObjectLock = new MonitoredReentrantLock(MonitoredLockType.WORLD_MAPOBJS, true);
private ScheduledFuture> charactersSchedule;
private ScheduledFuture> marriagesSchedule;
diff --git a/src/scripting/AbstractPlayerInteraction.java b/src/scripting/AbstractPlayerInteraction.java
index 6db9031a94..1f5e5e8358 100644
--- a/src/scripting/AbstractPlayerInteraction.java
+++ b/src/scripting/AbstractPlayerInteraction.java
@@ -978,4 +978,23 @@ public class AbstractPlayerInteraction {
return true;
}
}
+
+ public static String getFirstJobStatRequirement(int jobType) {
+ switch(jobType) {
+ case 1:
+ return "STR " + 35;
+
+ case 2:
+ return "INT " + 20;
+
+ case 3:
+ case 4:
+ return "DEX " + 25;
+
+ case 5:
+ return "DEX " + 20;
+ }
+
+ return null;
+ }
}
diff --git a/src/scripting/AbstractScriptManager.java b/src/scripting/AbstractScriptManager.java
index 02c741ed5a..2ec8782b11 100644
--- a/src/scripting/AbstractScriptManager.java
+++ b/src/scripting/AbstractScriptManager.java
@@ -69,7 +69,7 @@ public abstract class AbstractScriptManager {
}
try (FileReader fr = new FileReader(scriptFile)) {
if (ServerConstants.JAVA_8){
- engine.eval("load('nashorn:mozilla_compat.js');");
+ engine.eval("load('nashorn:mozilla_compat.js');" + System.lineSeparator());
}
engine.eval(fr);
} catch (final ScriptException | IOException t) {
diff --git a/src/scripting/item/ItemScriptManager.java b/src/scripting/item/ItemScriptManager.java
index 33319d47b9..b5d11eade1 100644
--- a/src/scripting/item/ItemScriptManager.java
+++ b/src/scripting/item/ItemScriptManager.java
@@ -22,6 +22,7 @@ along with this program. If not, see .
package scripting.item;
import client.MapleClient;
+import constants.ServerConstants;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
@@ -29,7 +30,6 @@ import java.lang.reflect.UndeclaredThrowableException;
import java.util.HashMap;
import java.util.Map;
import javax.script.Compilable;
-import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
@@ -76,8 +76,13 @@ public class ItemScriptManager {
ScriptEngine portal = sef.getScriptEngine();
try {
fr = new FileReader(scriptFile);
- CompiledScript compiled = ((Compilable) portal).compile(fr);
- compiled.eval();
+
+ // java 8 support here thanks to Arufonsu
+ if (ServerConstants.JAVA_8){
+ portal.eval("load('nashorn:mozilla_compat.js');" + System.lineSeparator());
+ }
+
+ ((Compilable) portal).compile(fr).eval();
final Invocable script = ((Invocable) portal);
scripts.put(scriptName, script);
diff --git a/src/scripting/map/MapScriptManager.java b/src/scripting/map/MapScriptManager.java
index a8ad9d45b6..303f1ae1fc 100644
--- a/src/scripting/map/MapScriptManager.java
+++ b/src/scripting/map/MapScriptManager.java
@@ -22,6 +22,7 @@ along with this program. If not, see .
package scripting.map;
import client.MapleClient;
+import constants.ServerConstants;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
@@ -29,7 +30,6 @@ import java.lang.reflect.UndeclaredThrowableException;
import java.util.HashMap;
import java.util.Map;
import javax.script.Compilable;
-import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
@@ -80,8 +80,14 @@ public class MapScriptManager {
ScriptEngine portal = sef.getScriptEngine();
try {
fr = new FileReader(scriptFile);
- CompiledScript compiled = ((Compilable) portal).compile(fr);
- compiled.eval();
+
+ // java 8 support here thanks to Arufonsu
+ if (ServerConstants.JAVA_8){
+ portal.eval("load('nashorn:mozilla_compat.js');" + System.lineSeparator());
+ }
+
+ ((Compilable) portal).compile(fr).eval();
+
final Invocable script = ((Invocable) portal);
scripts.put(scriptName, script);
script.invokeFunction("start", new MapScriptMethods(c));
diff --git a/src/scripting/portal/PortalScriptManager.java b/src/scripting/portal/PortalScriptManager.java
index 8875f32a15..fd85fe5cc3 100644
--- a/src/scripting/portal/PortalScriptManager.java
+++ b/src/scripting/portal/PortalScriptManager.java
@@ -22,6 +22,7 @@ along with this program. If not, see .
package scripting.portal;
import client.MapleClient;
+import constants.ServerConstants;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
@@ -65,6 +66,12 @@ public class PortalScriptManager {
ScriptEngine portal = sef.getScriptEngine();
try {
fr = new FileReader(scriptFile);
+
+ // java 8 support here thanks to Arufonsu
+ if (ServerConstants.JAVA_8){
+ portal.eval("load('nashorn:mozilla_compat.js');" + System.lineSeparator());
+ }
+
((Compilable) portal).compile(fr).eval();
} catch (ScriptException | IOException | UndeclaredThrowableException e) {
FilePrinter.printError(FilePrinter.PORTAL + scriptName + ".txt", e);
diff --git a/src/server/life/MapleLifeFactory.java b/src/server/life/MapleLifeFactory.java
index b39005682f..5d5f3655d3 100644
--- a/src/server/life/MapleLifeFactory.java
+++ b/src/server/life/MapleLifeFactory.java
@@ -132,12 +132,23 @@ public class MapleLifeFactory {
stats.setRevives(revives);
}
decodeElementalString(stats, MapleDataTool.getString("elemAttr", monsterInfoData, ""));
- MapleData monsterSkillData = monsterInfoData.getChildByPath("skill");
- if (monsterSkillData != null) {
+ MapleData monsterSkillInfoData = monsterInfoData.getChildByPath("skill");
+ if (monsterSkillInfoData != null) {
int i = 0;
List> skills = new ArrayList<>();
- while (monsterSkillData.getChildByPath(Integer.toString(i)) != null) {
- skills.add(new Pair<>(Integer.valueOf(MapleDataTool.getInt(i + "/skill", monsterSkillData, 0)), Integer.valueOf(MapleDataTool.getInt(i + "/level", monsterSkillData, 0))));
+ while (monsterSkillInfoData.getChildByPath(Integer.toString(i)) != null) {
+ skills.add(new Pair<>(Integer.valueOf(MapleDataTool.getInt(i + "/skill", monsterSkillInfoData, 0)), Integer.valueOf(MapleDataTool.getInt(i + "/level", monsterSkillInfoData, 0))));
+
+ MapleData monsterSkillData = monsterData.getChildByPath("skill" + i);
+ if(monsterSkillData != null) {
+ int animationTime = 0;
+ for(MapleData effectEntry : monsterSkillData.getChildren()) {
+ animationTime += MapleDataTool.getIntConvert("delay", effectEntry, 0);
+ }
+
+ MapleMonsterInformationProvider.getInstance().setMobSkillAnimationTime(mid, i, animationTime);
+ }
+
i++;
}
stats.setSkills(skills);
diff --git a/src/server/life/MapleMonster.java b/src/server/life/MapleMonster.java
index fe9449b7f0..c7d3d30a72 100644
--- a/src/server/life/MapleMonster.java
+++ b/src/server/life/MapleMonster.java
@@ -56,6 +56,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import tools.locks.MonitoredReentrantLock;
+import net.server.channel.Channel;
import net.server.world.MapleParty;
import net.server.world.MaplePartyCharacter;
import server.TimerManager;
@@ -320,7 +321,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
return takenDamage.containsKey(chr.getId());
}
- private void distributeExperienceToParty(int pid, int exp, int killer, Set underleveled, int minThresholdLevel) {
+ private void distributeExperienceToParty(int pid, float exp, int killer, Set underleveled, int minThresholdLevel) {
List members = new LinkedList<>();
MapleCharacter pchar = getMap().getAnyCharacterFromParty(pid);
if(pchar != null) {
@@ -353,7 +354,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
if (level >= minThresholdLevel) {
boolean isKiller = killer == id;
boolean mostDamage = mostDamageCid == id;
- int xp = (int) ((0.80f * exp * level) / partyLevel);
+ float xp = ((0.80f * exp * level) / partyLevel);
if (mostDamage) {
xp += (0.20f * exp);
}
@@ -378,14 +379,14 @@ public class MapleMonster extends AbstractLoadedMapleLife {
int minThresholdLevel = calcThresholdLevel(this.getMap().getEventInstance() != null);
int exp = getExp();
long totalHealth = maxHpPlusHeal.get();
- Map expDist = new HashMap<>();
- Map partyExp = new HashMap<>();
+ Map expDist = new HashMap<>();
+ Map partyExp = new HashMap<>();
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((exp8perHp * damage.getValue().get()), Integer.MAX_VALUE)));
+ expDist.put(damage.getKey(), exp8perHp * damage.getValue().get());
}
Collection chrs = map.getCharacters();
@@ -393,15 +394,16 @@ public class MapleMonster extends AbstractLoadedMapleLife {
for (MapleCharacter mc : chrs) {
if (expDist.containsKey(mc.getId())) {
boolean isKiller = (mc.getId() == killerId);
- int xp = expDist.get(mc.getId());
+ float xp = expDist.get(mc.getId());
if (isKiller) {
- xp = (int)Math.min(exp2 + xp, Integer.MAX_VALUE);
+ xp += exp2;
}
+
MapleParty p = mc.getParty();
if (p != null) {
int pID = p.getId();
- long pXP = (long) xp + (partyExp.containsKey(pID) ? partyExp.get(pID) : 0);
- partyExp.put(pID, (int)Math.min(pXP, Integer.MAX_VALUE));
+ float pXP = xp + (partyExp.containsKey(pID) ? partyExp.get(pID) : 0);
+ partyExp.put(pID, pXP);
} else {
if(mc.getLevel() >= minThresholdLevel) {
//NO EXP WILL BE GIVEN for those who are underleveled!
@@ -413,7 +415,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
}
- for (Entry party : partyExp.entrySet()) {
+ for (Entry party : partyExp.entrySet()) {
distributeExperienceToParty(party.getKey(), party.getValue(), killerId, underleveled, minThresholdLevel);
}
@@ -422,7 +424,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
}
- public void giveExpToCharacter(MapleCharacter attacker, int exp, boolean isKiller, int numExpSharers) {
+ public void giveExpToCharacter(MapleCharacter attacker, float exp, boolean isKiller, int numExpSharers) {
if (isKiller) {
if (getMap().getEventInstance() != null) {
getMap().getEventInstance().monsterKilled(attacker, this);
@@ -434,9 +436,10 @@ public class MapleMonster extends AbstractLoadedMapleLife {
int partyExp = 0;
if (attacker.getHp() > 0) {
- int personalExp = exp * attacker.getExpRate();
+ exp *= attacker.getExpRate();
+ int personalExp = (int) exp;
- if (exp > 0) {
+ if (exp <= Integer.MAX_VALUE) { // assuming no negative xp here
if (partyModifier > 0.0f) {
partyExp = (int) (personalExp * partyModifier * ServerConstants.PARTY_BONUS_EXP_RATE);
}
@@ -444,7 +447,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
if (holySymbol != null) {
personalExp *= 1.0 + (holySymbol.doubleValue() / 100.0);
}
-
+
statiLock.lock();
try {
if (stati.containsKey(MonsterStatus.SHOWDOWN)) {
@@ -453,6 +456,8 @@ public class MapleMonster extends AbstractLoadedMapleLife {
} finally {
statiLock.unlock();
}
+ } else {
+ personalExp = Integer.MAX_VALUE;
}
attacker.gainExp(personalExp, partyExp, true, false, isKiller);
@@ -777,9 +782,9 @@ public class MapleMonster extends AbstractLoadedMapleLife {
byte[] packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), status, null);
map.broadcastMessage(packet, getPosition());
- MapleCharacter controller = getController();
- if (controller != null && !controller.isMapObjectVisible(this)) {
- controller.getClient().announce(packet);
+ MapleCharacter chrController = getController();
+ if (chrController != null && !chrController.isMapObjectVisible(this)) {
+ chrController.getClient().announce(packet);
}
return animationTime;
@@ -832,6 +837,8 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
}
+ final Channel ch = map.getChannelServer();
+ final int mapid = map.getId();
if(statis.size() > 0) {
statiLock.lock();
try {
@@ -840,8 +847,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
if (oldEffect != null) {
oldEffect.removeActiveStatus(stat);
if (oldEffect.getStati().isEmpty()) {
- oldEffect.cancelTask();
- oldEffect.cancelDamageSchedule();
+ ch.interruptMobStatus(mapid, oldEffect);
}
}
}
@@ -850,7 +856,6 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
}
- TimerManager timerManager = TimerManager.getInstance();
final Runnable cancelTask = new Runnable() {
@Override
@@ -875,17 +880,21 @@ public class MapleMonster extends AbstractLoadedMapleLife {
}
setVenomMulti(0);
- status.cancelDamageSchedule();
}
};
+ Runnable overtimeAction = null;
+ int overtimeDelay = -1;
+
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));
+
+ overtimeAction = new DamageTask(poisonDamage, from, status, 0);
+ overtimeDelay = 1000;
} else if (venom) {
if (from.getJob() == MapleJob.NIGHTLORD || from.getJob() == MapleJob.SHADOWER || from.getJob().isA(MapleJob.NIGHTWALKER3)) {
int poisonLevel, matk, jobid = from.getJob().getId();
@@ -910,7 +919,9 @@ public class MapleMonster extends AbstractLoadedMapleLife {
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));
+
+ overtimeAction = new DamageTask(poisonDamage, from, status, 0);
+ overtimeDelay = 1000;
} else {
return false;
}
@@ -919,7 +930,9 @@ public class MapleMonster extends AbstractLoadedMapleLife {
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));
+
+ overtimeAction = new DamageTask(webDamage, from, status, 1);
+ overtimeDelay = 3500;
*/
} else if (status.getSkill().getId() == 4121004 || status.getSkill().getId() == 4221004) { // Ninja Ambush
final Skill skill = SkillFactory.getSkill(status.getSkill().getId());
@@ -928,7 +941,9 @@ 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));
+
+ overtimeAction = new DamageTask(damage, from, status, 2);
+ overtimeDelay = 1000;
} else {
animationTime = broadcastStatusEffect(status);
}
@@ -943,12 +958,11 @@ public class MapleMonster extends AbstractLoadedMapleLife {
statiLock.unlock();
}
- status.setCancelTask(timerManager.schedule(cancelTask, duration + animationTime - 100));
+ ch.registerMobStatus(mapid, status, cancelTask, duration + animationTime - 100, overtimeAction, overtimeDelay);
return true;
}
public void applyMonsterBuff(final Map stats, final int x, int skillId, long duration, MobSkill skill, final List reflection) {
- TimerManager timerManager = TimerManager.getInstance();
final Runnable cancelTask = new Runnable() {
@Override
@@ -991,7 +1005,8 @@ public class MapleMonster extends AbstractLoadedMapleLife {
if (controller != null && !controller.isMapObjectVisible(this)) {
controller.getClient().announce(packet);
}
- effect.setCancelTask(timerManager.schedule(cancelTask, duration));
+
+ map.getChannelServer().registerMobStatus(map.getId(), effect, cancelTask, duration);
}
private void debuffMobStat(MonsterStatus stat) {
@@ -1191,15 +1206,13 @@ public class MapleMonster extends AbstractLoadedMapleLife {
private final int dealDamage;
private final MapleCharacter chr;
private final MonsterStatusEffect status;
- private final Runnable cancelTask;
private final int type;
private final MapleMap map;
- private DamageTask(int dealDamage, MapleCharacter chr, MonsterStatusEffect status, Runnable cancelTask, int type) {
+ private DamageTask(int dealDamage, MapleCharacter chr, MonsterStatusEffect status, int type) {
this.dealDamage = dealDamage;
this.chr = chr;
this.status = status;
- this.cancelTask = cancelTask;
this.type = type;
this.map = chr.getMap();
}
@@ -1207,20 +1220,26 @@ public class MapleMonster extends AbstractLoadedMapleLife {
@Override
public void run() {
int curHp = hp.get();
- if(curHp <= 1) return;
+ if(curHp <= 1) {
+ map.getChannelServer().interruptMobStatus(map.getId(), status);
+ return;
+ }
int damage = dealDamage;
if (damage >= curHp) {
damage = curHp - 1;
if (type == 1 || type == 2) {
- cancelTask.run();
- status.getCancelTask().cancel(false);
+ map.getChannelServer().interruptMobStatus(map.getId(), status);
}
}
if (damage > 0) {
damage(chr, damage, true);
- if (type == 1) { // ninja ambush (type 2) is already displaying DOT
+ if (type == 1) {
map.broadcastMessage(MaplePacketCreator.damageMonster(getObjectId(), damage), getPosition());
+ } else if (type == 2) {
+ if(damage < dealDamage) { // ninja ambush (type 2) is already displaying DOT to the caster
+ map.broadcastMessage(MaplePacketCreator.damageMonster(getObjectId(), damage), getPosition());
+ }
}
}
}
diff --git a/src/server/life/MapleMonsterInformationProvider.java b/src/server/life/MapleMonsterInformationProvider.java
index b24a3679f4..41eaa84bce 100644
--- a/src/server/life/MapleMonsterInformationProvider.java
+++ b/src/server/life/MapleMonsterInformationProvider.java
@@ -53,6 +53,8 @@ public class MapleMonsterInformationProvider {
private final Map> dropsChancePool = new HashMap<>(); // thanks to ronan
private final Set hasNoMultiEquipDrops = new HashSet<>();
private final Map> extraMultiEquipDrops = new HashMap<>();
+
+ private final Map, Integer> mobSkillAnimationTime = new HashMap<>();
protected MapleMonsterInformationProvider() {
retrieveGlobal();
@@ -223,9 +225,17 @@ public class MapleMonsterInformationProvider {
dropsChancePool.put(monsterId, ret);
return ret;
}
+
+ public final void setMobSkillAnimationTime(int monsterId, int skillPos, int animationTime) {
+ mobSkillAnimationTime.put(new Pair<>(monsterId, skillPos), animationTime);
+ }
+
+ public final Integer getMobSkillAnimationTime(int monsterId, int skillPos) {
+ Integer time = mobSkillAnimationTime.get(new Pair<>(monsterId, skillPos));
+ return time == null ? 0 : time;
+ }
- public static ArrayList> getMobsIDsFromName(String search)
- {
+ public static ArrayList> getMobsIDsFromName(String search) {
MapleDataProvider dataProvider = MapleDataProviderFactory.getDataProvider(new File("wz/String.wz"));
ArrayList> retMobs = new ArrayList>();
MapleData data = dataProvider.getData("Mob.img");
@@ -243,8 +253,7 @@ public class MapleMonsterInformationProvider {
return retMobs;
}
- public static String getMobNameFromId(int id)
- {
+ public static String getMobNameFromId(int id) {
try
{
return MapleLifeFactory.getMonster(id).getName();
@@ -261,8 +270,7 @@ public class MapleMonsterInformationProvider {
}
}
- public static String getMobNameFromID(int id)
- {
+ public static String getMobNameFromID(int id) {
try
{
return MapleLifeFactory.getMonster(id).getName();
diff --git a/src/server/life/MobSkill.java b/src/server/life/MobSkill.java
index 9ea11cd784..032b6ab662 100644
--- a/src/server/life/MobSkill.java
+++ b/src/server/life/MobSkill.java
@@ -32,6 +32,7 @@ import client.status.MonsterStatus;
import constants.GameConstants;
import java.util.LinkedList;
import java.util.Map;
+import server.TimerManager;
import tools.Randomizer;
import server.maps.MapleMapObject;
import server.maps.MapleMapObjectType;
@@ -103,6 +104,17 @@ public class MobSkill {
this.limit = limit;
}
+ public void applyDelayedEffect(final MapleCharacter player, final MapleMonster monster, final boolean skill, final List banishPlayers, int animationTime) {
+ TimerManager.getInstance().schedule(new Runnable() {
+ @Override
+ public void run() {
+ if(monster.isAlive()) {
+ applyEffect(player, monster, skill, banishPlayers);
+ }
+ }
+ }, animationTime);
+ }
+
public void applyEffect(MapleCharacter player, MapleMonster monster, boolean skill, List banishPlayers) {
MapleDisease disease = null;
Map stats = new ArrayMap();
@@ -282,15 +294,15 @@ public class MobSkill {
System.out.println("Unhandled Mob skill: " + skillId);
break;
}
- if (stats.size() > 0) {
- if (lt != null && rb != null && skill) {
- for (MapleMapObject mons : getObjectsInRange(monster, MapleMapObjectType.MONSTER)) {
- ((MapleMonster) mons).applyMonsterBuff(stats, getX(), getSkillId(), getDuration(), this, reflection);
- }
- } else {
- monster.applyMonsterBuff(stats, getX(), getSkillId(), getDuration(), this, reflection);
- }
- }
+ if (stats.size() > 0) {
+ if (lt != null && rb != null && skill) {
+ for (MapleMapObject mons : getObjectsInRange(monster, MapleMapObjectType.MONSTER)) {
+ ((MapleMonster) mons).applyMonsterBuff(stats, getX(), getSkillId(), getDuration(), this, reflection);
+ }
+ } else {
+ monster.applyMonsterBuff(stats, getX(), getSkillId(), getDuration(), this, reflection);
+ }
+ }
if (disease != null) {
if (lt != null && rb != null && skill) {
int i = 0;
diff --git a/src/server/maps/MapleMap.java b/src/server/maps/MapleMap.java
index 4e37f71fcd..c9fcd86f71 100644
--- a/src/server/maps/MapleMap.java
+++ b/src/server/maps/MapleMap.java
@@ -59,7 +59,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.lang.ref.WeakReference;
-import java.util.Comparator;
import net.server.Server;
import net.server.channel.Channel;
import scripting.map.MapScriptManager;
@@ -255,15 +254,43 @@ public class MapleMap {
public int getId() {
return mapid;
}
+
+ public Channel getChannelServer() {
+ return Server.getInstance().getWorld(world).getChannel(channel);
+ }
public MapleMap getReturnMap() {
if(returnMapId == 999999999) return this;
- return Server.getInstance().getWorld(world).getChannel(channel).getMapFactory().getMap(returnMapId);
+ return getChannelServer().getMapFactory().getMap(returnMapId);
}
public int getReturnMapId() {
return returnMapId;
}
+
+ public MapleMap getForcedReturnMap() {
+ return getChannelServer().getMapFactory().getMap(forcedReturnMap);
+ }
+
+ public int getForcedReturnId() {
+ return forcedReturnMap;
+ }
+
+ public void setForcedReturnMap(int map) {
+ this.forcedReturnMap = map;
+ }
+
+ public long getTimeLimit() {
+ return timeLimit;
+ }
+
+ public void setTimeLimit(int timeLimit) {
+ this.timeLimit = timeLimit;
+ }
+
+ public int getTimeLeft() {
+ return (int) ((timeLimit - System.currentTimeMillis()) / 1000);
+ }
public void setReactorState() {
chrRLock.lock();
@@ -322,30 +349,6 @@ public class MapleMap {
return true;
}
- public int getForcedReturnId() {
- return forcedReturnMap;
- }
-
- public MapleMap getForcedReturnMap() {
- return Server.getInstance().getWorld(world).getChannel(channel).getMapFactory().getMap(forcedReturnMap);
- }
-
- public void setForcedReturnMap(int map) {
- this.forcedReturnMap = map;
- }
-
- public long getTimeLimit() {
- return timeLimit;
- }
-
- public void setTimeLimit(int timeLimit) {
- this.timeLimit = timeLimit;
- }
-
- public int getTimeLeft() {
- return (int) ((timeLimit - System.currentTimeMillis()) / 1000);
- }
-
public int getCurrentPartyId() {
for (MapleCharacter chr : this.getCharacters()) {
if (chr.getPartyId() != -1) {
diff --git a/src/server/maps/MapleReactor.java b/src/server/maps/MapleReactor.java
index 56fd2cd229..5d3e398e78 100644
--- a/src/server/maps/MapleReactor.java
+++ b/src/server/maps/MapleReactor.java
@@ -55,7 +55,7 @@ public class MapleReactor extends AbstractMapleMapObject {
private boolean attackHit;
private ScheduledFuture> timeoutTask = null;
private Lock reactorLock = new MonitoredReentrantLock(MonitoredLockType.REACTOR, true);
- private Lock hitLock = new MonitoredReentrantLock(MonitoredLockType.REACTOR, true);
+ private Lock hitLock = new MonitoredReentrantLock(MonitoredLockType.REACTOR_HIT, true);
public MapleReactor(MapleReactorStats stats, int rid) {
this.evstate = (byte)0;
diff --git a/src/tools/MaplePacketCreator.java b/src/tools/MaplePacketCreator.java
index 7db90e26e8..ca12f0e55b 100644
--- a/src/tools/MaplePacketCreator.java
+++ b/src/tools/MaplePacketCreator.java
@@ -3877,12 +3877,13 @@ public class MaplePacketCreator {
}
public static byte[] applyMonsterStatus(final int oid, final MonsterStatusEffect mse, final List reflection) {
+ Map stati = mse.getStati();
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.APPLY_MONSTER_STATUS.getValue());
mplew.writeInt(oid);
mplew.writeLong(0);
- writeIntMask(mplew, mse.getStati());
- for (Map.Entry stat : mse.getStati().entrySet()) {
+ writeIntMask(mplew, stati);
+ for (Map.Entry stat : stati.entrySet()) {
mplew.writeShort(stat.getValue());
if (mse.isMonsterSkill()) {
mplew.writeShort(mse.getMobSkill().getSkillId());
@@ -3892,7 +3893,7 @@ public class MaplePacketCreator {
}
mplew.writeShort(-1); // might actually be the buffTime but it's not displayed anywhere
}
- int size = mse.getStati().size(); // size
+ int size = stati.size(); // size
if (reflection != null) {
for (Integer ref : reflection) {
mplew.writeInt(ref);
diff --git a/src/tools/locks/MonitoredLockType.java b/src/tools/locks/MonitoredLockType.java
index fee7c5ca6e..7930122041 100644
--- a/src/tools/locks/MonitoredLockType.java
+++ b/src/tools/locks/MonitoredLockType.java
@@ -1,6 +1,6 @@
/*
This file is part of the HeavenMS MapleStory Server
- Copyleft (L) 2016 - 2018 RonanLana
+ Copyleft 2016 - 2018 RonanLana
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -25,59 +25,56 @@ package tools.locks;
*/
public enum MonitoredLockType {
- UNDEFINED(-1),
- CHR(0),
- EFF(1),
- PET(2),
- PRT(3),
- CLIENT(4),
- BOOK(5),
- ITEM(6),
- INVENTORY(7),
- SRVHANDLER_IDLE(8),
- SRVHANDLER_TEMP(9),
- BUFF_STORAGE(10),
- PLAYER_STORAGE(11),
- SERVER(12),
- MERCHANT(13),
- CHANNEL(14),
- GUILD(15),
- PARTY(16),
- WORLD_PARTY(17),
- WORLD_OWL(18),
- WORLD_PETS(19),
- WORLD_MOUNTS(20),
- WORLD_PSHOPS(21),
- WORLD_MERCHS(21),
- EIM(22),
- EIM_PARTY(23),
- EIM_SCRIPT(24),
- EM_LOBBY(25),
- EM_QUEUE(26),
- CASHSHOP(27),
- VISITOR_PSHOP(28),
- STORAGE(29),
- MOB_EXT(30),
- MOB(31),
- MOB_STATI(32),
- MOBSKILL_FACTORY(33),
- PORTAL(34),
- VISITOR_MERCH(35),
- MAP_CHRS(36),
- MAP_OBJS(37),
- MAP_FACTORY(38),
- MAP_ITEM(39),
- MAP_BOUNDS(40),
- MINIDUNGEON(41),
- REACTOR(42);
-
- private final int i;
-
- private MonitoredLockType(int val) {
- this.i = val;
- }
-
- public int getValue() {
- return i;
- }
+ UNDEFINED,
+ CHARACTER_CHR,
+ CHARACTER_EFF,
+ CHARACTER_PET,
+ CHARACTER_PRT,
+ CLIENT,
+ CLIENT_ENCODER,
+ CLIENT_LOGIN,
+ BOOK,
+ ITEM,
+ INVENTORY,
+ SRVHANDLER_IDLE,
+ SRVHANDLER_TEMP,
+ BUFF_STORAGE,
+ PLAYER_STORAGE,
+ SERVER,
+ SERVER_LOGIN,
+ SERVER_DISEASES,
+ MERCHANT,
+ CHANNEL,
+ CHANNEL_MOBSTATUS,
+ GUILD,
+ PARTY,
+ WORLD_PARTY,
+ WORLD_OWL,
+ WORLD_PETS,
+ WORLD_MOUNTS,
+ WORLD_PSHOPS,
+ WORLD_MERCHS,
+ WORLD_MAPOBJS,
+ EIM,
+ EIM_PARTY,
+ EIM_SCRIPT,
+ EM_LOBBY,
+ EM_QUEUE,
+ CASHSHOP,
+ VISITOR_PSHOP,
+ STORAGE,
+ MOB_EXT,
+ MOB,
+ MOB_STATI,
+ MOBSKILL_FACTORY,
+ PORTAL,
+ VISITOR_MERCH,
+ MAP_CHRS,
+ MAP_OBJS,
+ MAP_FACTORY,
+ MAP_ITEM,
+ MAP_BOUNDS,
+ MINIDUNGEON,
+ REACTOR,
+ REACTOR_HIT;
}
diff --git a/tools/MapleQuestItemFetcher/dist/MapleQuestItemFetcher.jar b/tools/MapleQuestItemFetcher/dist/MapleQuestItemFetcher.jar
index a81e6a6878..c16e02e882 100644
Binary files a/tools/MapleQuestItemFetcher/dist/MapleQuestItemFetcher.jar and b/tools/MapleQuestItemFetcher/dist/MapleQuestItemFetcher.jar differ
diff --git a/tools/MapleQuestItemFetcher/lib/QuestReport.txt b/tools/MapleQuestItemFetcher/lib/QuestReport.txt
index 0d20e38d85..8b24fabf13 100644
--- a/tools/MapleQuestItemFetcher/lib/QuestReport.txt
+++ b/tools/MapleQuestItemFetcher/lib/QuestReport.txt
@@ -1,5 +1,5 @@
# Report File autogenerated from the MapleQuestItemFetcher feature by Ronan Lana.
- # Generated data takes into account several data info from the underlying DB and the server-side WZ.xmls.
+ # Generated data takes into account several data info from the underlying DB, server source files and the server-side WZ.xmls.
INCORRECT QUESTIDS ON DB
2022055 : -1 -> 9330 EXPIRED
diff --git a/tools/MapleQuestItemFetcher/src/maplequestitemfetcher/MapleQuestItemFetcher.java b/tools/MapleQuestItemFetcher/src/maplequestitemfetcher/MapleQuestItemFetcher.java
index 47e1c9ad84..8302ee5a39 100644
--- a/tools/MapleQuestItemFetcher/src/maplequestitemfetcher/MapleQuestItemFetcher.java
+++ b/tools/MapleQuestItemFetcher/src/maplequestitemfetcher/MapleQuestItemFetcher.java
@@ -403,36 +403,29 @@ public class MapleQuestItemFetcher {
}
}
- private static boolean foundMatchingDataOnFile(Scanner scan, String searchStr) {
- while(scan.hasNext()){
- String line = scan.nextLine().toLowerCase();
- if(line.contains(searchStr)){
- return true;
- }
- }
-
- return false;
+ private static boolean foundMatchingDataOnFile(String fileContent, String searchStr) {
+ return fileContent.contains(searchStr);
}
-
private static void fileSearchMatchingData(File file, List> itemsWithQuest) {
try {
- Scanner scanner = new Scanner(file);
-
+ String fileContent = FileUtils.readFileToString(file, "UTF-8");
+
List> copyItemsWithQuest = new ArrayList<>(itemsWithQuest);
for(Pair iq : copyItemsWithQuest) {
- scanner.reset();
-
- if(foundMatchingDataOnFile(scanner, String.valueOf(iq.getLeft()))) {
+ if(foundMatchingDataOnFile(fileContent, String.valueOf(iq.getLeft()))) {
itemsWithQuest.remove(iq);
}
}
- } catch(FileNotFoundException e) {}
+ } catch(IOException ioe) {
+ System.out.println("Failed to read file: " + file.getAbsolutePath());
+ ioe.printStackTrace();
+ }
}
private static void printReportFileHeader() {
printWriter.println(" # Report File autogenerated from the MapleQuestItemFetcher feature by Ronan Lana.");
- printWriter.println(" # Generated data takes into account several data info from the underlying DB and the server-side WZ.xmls.");
+ printWriter.println(" # Generated data takes into account several data info from the underlying DB, server source files and the server-side WZ.xmls.");
printWriter.println();
}
diff --git a/wz/Item.wz/Etc/0403.img.xml b/wz/Item.wz/Etc/0403.img.xml
index c716d243fe..124dfea55a 100644
--- a/wz/Item.wz/Etc/0403.img.xml
+++ b/wz/Item.wz/Etc/0403.img.xml
@@ -9725,10 +9725,8 @@
-
-
-
-
+
+
@@ -9805,8 +9803,8 @@
-
-
+
+
diff --git a/wz/Map.wz/Map/Map1/101000301.img.xml b/wz/Map.wz/Map/Map1/101000301.img.xml
index 84e959944c..0dedff9a41 100644
--- a/wz/Map.wz/Map/Map1/101000301.img.xml
+++ b/wz/Map.wz/Map/Map1/101000301.img.xml
@@ -10,7 +10,7 @@
-
+
diff --git a/wz/Quest.wz/Act.img.xml b/wz/Quest.wz/Act.img.xml
index 1d797c4c02..fd93b2cc11 100644
--- a/wz/Quest.wz/Act.img.xml
+++ b/wz/Quest.wz/Act.img.xml
@@ -1,4 +1,4 @@
-
+
@@ -39231,7 +39231,7 @@
-
+
@@ -39258,130 +39258,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -39390,6 +39266,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wz/Quest.wz/Check.img.xml b/wz/Quest.wz/Check.img.xml
index a16066f0da..729f3aff70 100644
--- a/wz/Quest.wz/Check.img.xml
+++ b/wz/Quest.wz/Check.img.xml
@@ -62651,7 +62651,7 @@
-
+
@@ -62678,126 +62678,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -62806,6 +62686,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wz/Quest.wz/QuestInfo.img.xml b/wz/Quest.wz/QuestInfo.img.xml
index 6a50e7bcd1..a6c91d3fef 100644
--- a/wz/Quest.wz/QuestInfo.img.xml
+++ b/wz/Quest.wz/QuestInfo.img.xml
@@ -19525,7 +19525,7 @@ Able to proceed to 'Merry-go-round in Kampung' as next quest.
-
+
diff --git a/wz/String.wz/Etc.img.xml b/wz/String.wz/Etc.img.xml
index f28b14de75..14f985fcd8 100644
--- a/wz/String.wz/Etc.img.xml
+++ b/wz/String.wz/Etc.img.xml
@@ -973,8 +973,8 @@
-
-
+
+