diff --git a/README.md b/README.md index 66bc810125..cfa63a92c8 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Although development is halted, support for fixing features that were implemente If you liked this project, please don't forget to __star__ the repo ;) . -It's never too much to tell this, even still thanks to everyone that have been contributing something for the continuous improvement of the server! Be it through bug reports, donation, code snippets and/or pull requests. +It's never enough to tell this, thanks to everyone that have been contributing something for the continuous improvement of the server! Be it through bug reports, donation, code snippets and/or pull requests. Our Discord channel is still available on: https://discord.gg/Q7wKxHX diff --git a/docs/feature_list.md b/docs/feature_list.md index fbc84a16c6..c23548b2b3 100644 --- a/docs/feature_list.md +++ b/docs/feature_list.md @@ -116,6 +116,7 @@ Monsters, Maps & Reactors: PQ potentials: +* Advanced synchronization and smart management of the PQ registration system, as expected for a core server mechanic that is largely used by the players. * Lobby system - Multiple PQ instances on same channel. * Expedition system - Multiples parties can attempt on a same instance (lobbies and expeds are mutually-exclusive). * Guild queue system - Guilds can register themselves on a queue for the GPQ. @@ -129,6 +130,7 @@ Player potentials: * Gain fame by quests. * Pet evolutions functional (not GMS-like). * Reviewed keybinding system. +* Account's Character slots: either each world has it's own count or there's a shared value between all worlds. Server potentials: @@ -147,6 +149,7 @@ Server potentials: * Pet item pickup now gives preference to player attacks rather than forcing attack disables when automatically picking up. * Channel capacity bar functional and world servers with max capacity checks. * Disease status are now visible for other players, even when changing maps. +* Players keep their current disease status saved when exiting the game, returning with them on login. * Poison damage value are now visible for other players. * Mastery book announcer displays droppers of needed books of a player, by reading underlying DB. * Custom jail system (needs provided custom wz). diff --git a/docs/mychanges_ptbr.txt b/docs/mychanges_ptbr.txt index c81cf81d06..61a8b68f89 100644 --- a/docs/mychanges_ptbr.txt +++ b/docs/mychanges_ptbr.txt @@ -1226,4 +1226,24 @@ Modificado algumas tabelas em ThreadTracker e World para usar Integer ao invés 07 Agosto 2018, Corrigido um bug na fase de login que nao construia corretamente overview de characters sem itens equipados, lancando excecoes posteriormente. -Revisado alguns casos de borda com doors levando as mesmas a nao sumirem imediatamente em certos cenarios. \ No newline at end of file +Revisado alguns casos de borda com doors levando as mesmas a nao sumirem imediatamente em certos cenarios. + +08 - 10 Agosto 2018, +Adicionado registro de diseases na DB. Agora jogadores continuam com diseases ao voltar a logar no jogo. +Corrigido exploit com jogadores podendo visitar sua propria loja via owl. +Corrigido varios problemas de acesso concorrente e deadlocks em recursos do MapleMap e MapleMonster. +Implementado character slots para cobrir limite de personagens por conta (ao inves de 3 em cada mundo). +Corrigido diversos problemas com character slots ao entrar/sair do cash shop (erro ligado a alternancia de MapleClients). +Corrigido exploit com quest de Papulatus, onde jogadores poderiam gerar cracks of dimension por desistir de quests. +Corrigido jogadores podendo ser registrados em event instances mesmo quando estas ja estao liberadas (disposed). +Corrigido comando "reach" nao funcionando corretamente para casos que envolvam event instances. +Implementado um sistema avancado e seguro de escalonamento de requisicoes delegadas com iniciar nova PQs. + +13 - 14 Agosto 2018, +Corrigido quest "Revealed Identity" erroneamente requisitando itens de quest anteriores de jogadores, impossibilitando jogadores de comecar quests em certos casos. +Melhorado handler de whisper, agora buscando referencias a DB somente em casos onde o jogador eh GM. +Retirado campo de GM da tabela "accounts". +Corrigido estatuas do stage1 da GuildPQ nao funcionando corretamente. +Alterado varias strings de NPCs e de mensagens utilizando a palavra "evento" querendo dizer instancias (julgamento subjetivo). +Adicionado comandos para start, complete e reset quests. +Adicionado scripts para meteoritos de Omega Sector. \ No newline at end of file diff --git a/nbproject/project.properties b/nbproject/project.properties index b250ad8009..cd7cebb20d 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -30,10 +30,10 @@ endorsed.classpath= excludes= file.reference.HikariCP-java7-2.4.12.jar=cores/HikariCP-java7-2.4.12.jar file.reference.MapleSolaxia-src=src -file.reference.mina-core-2.0.7.jar=C:\\Users\\Tyler\\Desktop\\MoopleDev\\dist\\mina-core-2.0.7.jar -file.reference.mysql-connector-java-bin.jar=C:\\Users\\Tyler\\Desktop\\MoopleDev\\dist\\mysql-connector-java-bin.jar -file.reference.slf4j-api-1.6.6.jar=C:\\Users\\Tyler\\Desktop\\MoopleDev\\dist\\slf4j-api-1.6.6.jar -file.reference.slf4j-jdk14-1.7.5.jar=C:\\Users\\Tyler\\Desktop\\MoopleDev\\dist\\slf4j-jdk14-1.7.5.jar +file.reference.mina-core-2.0.7.jar=cores\\mina-core-2.0.7.jar +file.reference.mysql-connector-java-bin.jar=cores\\mysql-connector-java-bin.jar +file.reference.slf4j-api-1.6.6.jar=cores\\slf4j-api-1.6.6.jar +file.reference.slf4j-jdk14-1.7.5.jar=cores\\slf4j-jdk14-1.7.5.jar includes=** jar.archive.disabled=${jnlp.enabled} jar.compress=true diff --git a/scripts/event/CWKPQ.js b/scripts/event/CWKPQ.js index 5977548e5c..f1fc7fc430 100644 --- a/scripts/event/CWKPQ.js +++ b/scripts/event/CWKPQ.js @@ -247,7 +247,7 @@ function changedMap(eim, player, mapid) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } else { @@ -303,7 +303,7 @@ function playerRevive(eim, player) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } @@ -315,7 +315,7 @@ function playerDisconnected(eim, player) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } diff --git a/scripts/event/GuildQuest.js b/scripts/event/GuildQuest.js index d64683e064..d683de323e 100644 --- a/scripts/event/GuildQuest.js +++ b/scripts/event/GuildQuest.js @@ -236,7 +236,7 @@ function changedMap(eim, player, mapid) { function afterChangedMap(eim, player, mapid) { if (mapid == 990000100) { - var texttt = "So, here is the brief. You guys should be warned that, once out on the fortress outskirts, anyone that would not be equipping the #b#t1032033##k will die instantly due to the deteriorated state of the air around there. That being said, once your team moves out to the next stage, make sure to #bhit the glowing rocks#k in that region and #bequip the dropped item#k before advancing stages. That will protect you thoroughly from the air sickness. Good luck!"; + var texttt = "So, here is the brief. You guys should be warned that, once out on the fortress outskirts, anyone that would not be equipping the #b#t1032033##k will die instantly due to the deteriorated state of the air around there. That being said, once your team moves out, make sure to #bhit the glowing rocks#k in that region and #bequip the dropped item#k before advancing stages. That will protect you thoroughly from the air sickness. Good luck!"; player.getClient().getSession().write(Packages.tools.MaplePacketCreator.getNPCTalk(9040000, /*(byte)*/ 0, texttt, "00 00", /*(byte)*/ 0)); } } diff --git a/scripts/event/HorntailBattle.js b/scripts/event/HorntailBattle.js index 732015e7bb..0be282d612 100644 --- a/scripts/event/HorntailBattle.js +++ b/scripts/event/HorntailBattle.js @@ -132,7 +132,7 @@ function changedMap(eim, player, mapid) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } @@ -149,7 +149,7 @@ function playerRevive(eim, player) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } @@ -161,7 +161,7 @@ function playerDisconnected(eim, player) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } diff --git a/scripts/event/ScargaBattle.js b/scripts/event/ScargaBattle.js index c59de35d8c..60089d57ae 100644 --- a/scripts/event/ScargaBattle.js +++ b/scripts/event/ScargaBattle.js @@ -118,7 +118,7 @@ function changedMap(eim, player, mapid) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } @@ -135,7 +135,7 @@ function playerRevive(eim, player) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } @@ -147,7 +147,7 @@ function playerDisconnected(eim, player) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } diff --git a/scripts/event/ShowaBattle.js b/scripts/event/ShowaBattle.js index 15818174c8..1a637d67a8 100644 --- a/scripts/event/ShowaBattle.js +++ b/scripts/event/ShowaBattle.js @@ -125,7 +125,7 @@ function changedMap(eim, player, mapid) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } @@ -144,7 +144,7 @@ function playerRevive(eim, player) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } @@ -156,7 +156,7 @@ function playerDisconnected(eim, player) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } diff --git a/scripts/event/ZakumBattle.js b/scripts/event/ZakumBattle.js index f1da74768a..7fc014adc3 100644 --- a/scripts/event/ZakumBattle.js +++ b/scripts/event/ZakumBattle.js @@ -120,7 +120,7 @@ function changedMap(eim, player, mapid) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } @@ -137,7 +137,7 @@ function playerRevive(eim, player) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } @@ -149,7 +149,7 @@ function playerDisconnected(eim, player) { end(eim); } else { - eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the event."); + eim.dropMessage(5, "[Expedition] " + player.getName() + " has left the instance."); eim.unregisterPlayer(player); } } diff --git a/scripts/npc/1012112.js b/scripts/npc/1012112.js index f2bd1b0070..0ebfe39966 100644 --- a/scripts/npc/1012112.js +++ b/scripts/npc/1012112.js @@ -58,7 +58,7 @@ function action(mode, type, selection) { return; } - cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nI'm Tory. Inside here is a beautiful hill where the primrose blooms. There's a tiger that lives in the hill, Growlie, and he seems to be looking for something to eat. Would you like to head over to the hill of primrose and join forces with your party members to help Growlie out?#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details.\r\n#L3#I would like to redeem an event hat."); + cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nI'm Tory. Inside here is a beautiful hill where the primrose blooms. There's a tiger that lives in the hill, Growlie, and he seems to be looking for something to eat. Would you like to head over to the hill of primrose and join forces with your party members to help Growlie out?#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details.\r\n#L3#I would like to redeem an instance hat."); } else if (status == 1) { if (selection == 0) { if (cm.getParty() == null) { @@ -87,7 +87,7 @@ function action(mode, type, selection) { cm.sendOk("#e#b#k#n\r\nCollect primrose seeds from the flowers at the bottom part of the map and drop them by the platforms above the stage. Primrose seed color must match to grow the seeds, so test until you find the correct combination. When all the seeds have been planted, that is, starting second part of the mission, scout the Moon Bunny while it prepares Rice Cakes for the hungry Growlie. Once Growlie becomes satisfied, your mission is complete."); cm.dispose(); } else { - cm.sendYesNo("So you want to exchange #b20 #b#t4001158##k for the event-designed hat?"); + cm.sendYesNo("So you want to exchange #b20 #b#t4001158##k for the instance-designed hat?"); } } else { if(cm.hasItem(4001158, 20)) { diff --git a/scripts/npc/1012118.js b/scripts/npc/1012118.js index 38386a879f..b6fad8a434 100644 --- a/scripts/npc/1012118.js +++ b/scripts/npc/1012118.js @@ -22,18 +22,16 @@ function action(mode, type, selection) { cm.sendOk("This training ground is available only for those under level 20."); cm.dispose(); return; - } - - if (cm.isQuestActive(22515) || cm.isQuestActive(22516) || cm.isQuestActive(22517) || cm.isQuestActive(22518)) { - cm.sendYesNo("Would you like to go in the special Spore Training Center?"); + } else if (cm.isQuestActive(22515) || cm.isQuestActive(22516) || cm.isQuestActive(22517) || cm.isQuestActive(22518)) { + cm.sendYesNo("Would you like to go in the special Training Center?"); status = 1; - } - - var selStr = "Would you like to go into the Training Center?"; - for (var i = 0; i < num; i++) { - selStr += "\r\n#b#L" + i + "#Training Center " + i + " (" + cm.getPlayerCount(map + i) + "/" + maxp + ")#l#k"; - } - cm.sendSimple(selStr); + } else { + var selStr = "Would you like to go into the Training Center?"; + for (var i = 0; i < num; i++) { + selStr += "\r\n#b#L" + i + "#Training Center " + i + " (" + cm.getPlayerCount(map + i) + "/" + maxp + ")#l#k"; + } + cm.sendSimple(selStr); + } } else if (status == 1) { if (selection < 0 || selection >= num) { cm.dispose(); diff --git a/scripts/npc/1012119.js b/scripts/npc/1012119.js index a030420cd6..652b65b62e 100644 --- a/scripts/npc/1012119.js +++ b/scripts/npc/1012119.js @@ -22,21 +22,16 @@ function action(mode, type, selection) { cm.sendOk("This training ground is available only for those under level 20."); cm.dispose(); return; - } - - else if (cm.isQuestActive(22515) || cm.isQuestActive(22516) || cm.isQuestActive(22517) || cm.isQuestActive(22518)) { + } else if (cm.isQuestActive(22515) || cm.isQuestActive(22516) || cm.isQuestActive(22517) || cm.isQuestActive(22518)) { cm.sendYesNo("Would you like to enter the special Training Center?"); status = 1; - } - -else { - - var selStr = "Would you like to go into the Training Center?"; - for (var i = 0; i < num; i++) { - selStr += "\r\n#b#L" + i + "#Training Center " + i + " (" + cm.getPlayerCount(map + i) + "/" + maxp + ")#l#k"; - } - cm.sendSimple(selStr); -} + } else { + var selStr = "Would you like to go into the Training Center?"; + for (var i = 0; i < num; i++) { + selStr += "\r\n#b#L" + i + "#Training Center " + i + " (" + cm.getPlayerCount(map + i) + "/" + maxp + ")#l#k"; + } + cm.sendSimple(selStr); + } } else if (status == 1) { if (selection < 0 || selection >= num) { diff --git a/scripts/npc/1032004.js b/scripts/npc/1032004.js index db7c6dd7e0..f8eb757c7e 100644 --- a/scripts/npc/1032004.js +++ b/scripts/npc/1032004.js @@ -20,7 +20,7 @@ along with this program. If not, see . */ /* Shane 1032004 - * By Moogra + * @Author Moogra */ function start() { diff --git a/scripts/npc/1052013.js b/scripts/npc/1052013.js index e3f13e6d88..c6a83ff96b 100644 --- a/scripts/npc/1052013.js +++ b/scripts/npc/1052013.js @@ -61,10 +61,10 @@ function action(mode, type, selection) { cm.dispose(); return; } else { - cm.sendYesNo("Your team must collect #r" + couponsNeeded + "#k coupons to complete this event. Talk to me when you have the right amount in hands... Or you want to #bquit now#k? Note that if you quit now #ryour team will be forced to quit#k as well."); + cm.sendYesNo("Your team must collect #r" + couponsNeeded + "#k coupons to complete this instance. Talk to me when you have the right amount in hands... Or you want to #bquit now#k? Note that if you quit now #ryour team will be forced to quit#k as well."); } } else { - cm.sendYesNo("Your team must collect #r" + couponsNeeded + "#k coupons to complete this event. Let your leader talk to me with the right amount in hands... Or you want to #bquit now#k? Note that if you quit now your team #rmay become undermanned#k to complete this event."); + cm.sendYesNo("Your team must collect #r" + couponsNeeded + "#k coupons to complete this instance. Let your leader talk to me with the right amount in hands... Or you want to #bquit now#k? Note that if you quit now your team #rmay become undermanned#k to further continue this instance."); } } else { if(!eim.giveEventReward(cm.getPlayer())) { diff --git a/scripts/npc/1061012.js b/scripts/npc/1061012.js index 4429ef3cf2..ab052815a3 100644 --- a/scripts/npc/1061012.js +++ b/scripts/npc/1061012.js @@ -25,7 +25,7 @@ function start() { var eli = em.getEligibleParty(cm.getParty()); if(eli.size() > 0) { if(!em.startInstance(cm.getParty(), cm.getPlayer().getMap(), 1)) { - cm.sendOk("A party in your name is already registered in this event."); + cm.sendOk("A party in your name is already registered in this instance."); } } else { cm.sendOk("You cannot start this party quest yet, because either your party is not in the range size, some of your party members are not eligible to attempt it or they are not in this map. If you're having trouble finding party members, try Party Search."); diff --git a/scripts/npc/1103005.js b/scripts/npc/1103005.js index 77e45c7800..980e989ba8 100644 --- a/scripts/npc/1103005.js +++ b/scripts/npc/1103005.js @@ -24,7 +24,7 @@ * @ID: 1103005 * @Map Id: 913040006 * @Function: Cygnus Creator - * @Author Jay (text) + * @Author Jay * @Author David */ diff --git a/scripts/npc/2030013.js b/scripts/npc/2030013.js index aa65cd80ee..77d0586a59 100644 --- a/scripts/npc/2030013.js +++ b/scripts/npc/2030013.js @@ -20,7 +20,7 @@ */ /*Adobis * - *@author SharpAceX (Alan) + *@author Alan (SharpAceX) *@author Ronan */ importPackage(Packages.server.expeditions); diff --git a/scripts/npc/2030013_old.js b/scripts/npc/2030013_old.js index 358c846eae..09ece3bdbf 100644 --- a/scripts/npc/2030013_old.js +++ b/scripts/npc/2030013_old.js @@ -20,7 +20,7 @@ */ /* - * @Authors Stereo, xQuasar, <> + * @Author Stereo, xQuasar, <> * * Adobis - El Nath: Entrance to Zakum Altar (211042400) * @@ -80,7 +80,7 @@ function action(mode, type, selection) { else { // start Zakum Battle var eim = em.newInstance("Zakum" + passwd); if(!em.startInstance(eim,cm.getPlayer().getName())) { - cm.sendOk("A party in your name is already registered in this event."); + cm.sendOk("A party in your name is already registered in this instance."); cm.dispose(); return; } diff --git a/scripts/npc/2040030.js b/scripts/npc/2040030.js index 244400e5ba..1b6352a8cc 100644 --- a/scripts/npc/2040030.js +++ b/scripts/npc/2040030.js @@ -1,4 +1,4 @@ -/* Author: aaroncsn(MapleSea Like)(Need to add creation of minigame) +/* Author: aaroncsn <(MapleSea Like)(Need to add creation of minigame)> NPC Name: Wisp Map(s): Ludibrium: Eos Tower Entrance(220000400) Description: Pet Master @@ -12,130 +12,131 @@ function start() { function action(mode, type, selection) { if (mode == -1) { - cm.dispose(); - } else { - if (status >= 0 && mode == 0) { cm.dispose(); - return; - } - if (mode == 1) - status++; - else - status--; - if(status == 0){ - cm.sendSimple("Hello there, I'm #bMar the Fairy#k of Victoria Island's main disciple. Mar the Fairy summoned me here to see if the pets are being taken care of here in Ludibrium. What can I do for you? \r\n#L0##bMy pet has turned back into a doll\r\nPlease help me make it move again!#k#l \r\n#L1##bTell me more about Pets.#k#l \r\n#L2##bHow do I raise Pets?#k#l \r\n#L3##bDo Pets die too?#k#l \r\n#L4##bWhat are the commands for brown and black kitty?#k#l \r\n#L5##bWhat are the commands for brown puppy?#k#l \r\n#L6##bWhat are the commands for pink and white bunny?#k#l \r\n#L7##bWhat are the commands for Mini Cargo?#k#l \r\n#L8##bWhat are the commands for Husky?#k#l \r\n#L9##bWhat are the commands for Black Pig?#k#l \r\n#L10##bWhat are the commands for Panda#k#l \r\n#L11##bWhat are the commands for Dino Boy & Girl?#k#l \r\n#L12##bWhat are the commands for Rudolph?#k#l \r\n#L13##bWhat are the commands for Monkey?#k#l \r\n#L14##bWhat are the commands for Robot?#k#l \r\n#L15##bWhat are the commands for Elephant?#k#l \r\n#L16##bWhat are the commands for Golden Pig?#k#l \r\n#L17##bWhat are the commands for Penguin?#k#l \r\n#L18##bWhat are the commands for Mini Yeti?#k#l \r\n#L19##bWhat are the commands for Jr. Balrog? \r\n#L20##bWhat are the commands for Baby Dragon?#k#l \r\n#L21##bWhat are the commands for Green/Red/Blue Dragon?#k#l \r\n#L22##bWhat are the commands for Black Dragon?#k#l \r\n#L23##bWhat are the commands for Snowman?#k#l \r\n#L24##bWhat are the commands for Sun Wu Kong?#k#l \r\n#L25##bWhat are the commands for Jr. Reaper?#k#l \r\n#L26##bWhat are the commands for Crystal Rudolph?#k#l \r\n#L27##bWhat are the commands for Kino?#k#l \r\n#L28##bWhat are the commands for White Duck?#k#l \r\n#L29##bWhat are the commands for Pink Bean?#k#l \r\n#L30##bWhat are the commands for Porcupine?#k#l"); - } - else if(status == 1){ - if(selection == 0){ - cm.sendNext("I'm Wisp, continuing on with the studies that my Master Mar the Fairy assigned me. There seems to be a lot of pets even her in Ludibrium. I need to get back to my studies, so if you'll excuse me..."); - cm.dispose(); - } else if(selection == 1){ - cm.sendNext("Hmmmm,you must have a lot of questions regarding the pets. Long ago, a person by the name #bCloy#k, sprayed Water of Life on it, and cast spell on it to create a magical animal. I know it sounds unbelievable, but it's a doll that became an actual living thing. They understand and follow people very well."); - } else if(selection == 2){ - cm.sendNext("Depending on the command you give, pets can love it, hate, and display other kinds of reactions to it. If you give the pet a command and it follows you well, your closeness goes up. Double click on the pet and you can check the closeness, level, fullness and etc..."); - } else if(selection == 3){ - cm.sendNext("Dying... well, they aren't technically ALIVE per se, so I don't know if dying is the right term to use. They are dolls with my magical power and the power of Water of Life to become a live object. Of course while it's alive, it's just like a live animal..."); - } else if(selection == 4){ - cm.sendNext("These are the commands for #rBrown Kitty and Black Kitty#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#bstupid, ihateyou, dummy#k (level 1 ~ 30)\r\n#biloveyou#k (level 1~30)\r\n#bpoop#k (level 1 ~ 30)\r\n#btalk, say, chat#k (level 10 ~ 30)\r\n#bcutie#k (level 10 ~ 30)\r\n#bup, stand, rise#k (level 20 ~ 30)"); - cm.dispose(); - } else if(selection == 5){ - cm.sendNext("These are the commands for #rBrown Puppy#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#bstupid, ihateyou, baddog, dummy#k (level 1 ~ 30)\r\n#biloveyou#k (level 1~30)\r\n#bpee#k (level 10 ~ 30)\r\n#btalk, say, chat, bark#k (level 10 ~ 30)\r\n#bdown#k (level 10 ~ 30)\r\n#bup, stand, rise#k (level 20 ~ 30)"); - cm.dispose(); - } else if(selection == 6){ - cm.sendNext("These are the commands for #rPink Bunny and White Bunny#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#bup, stand#k (level 1 ~ 30)\r\n#biloveyou#k (level 1~30)\r\n#bpoop#k (level 1 ~ 30)\r\n#btalk, say, chat#k (level 10 ~ 30)\r\n#bhug#k (level 10 ~ 30)\r\n#bsleep, sleepy, gotobed#k (level 20 ~ 30)"); - cm.dispose(); - } else if(selection == 7){ - cm.sendNext("These are the commands for #rMini Cargo#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#bup, stand#k (level 1 ~ 30)\r\n#biloveyou#k (level 1~30)\r\n#bpee#k (level 1 ~ 30)\r\n#btalk, say, chat#k (level 10 ~ 30)\r\n#bthelook, charisma#k (level 10 ~ 30)\r\n#bgoodboy, good#k (level 20 ~ 30)"); - cm.dispose(); - } else if(selection == 8){ - cm.sendNext("These are the commands for #rHusky#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#bstupid, ihateyou, baddog, dummy#k (level 1 ~ 30)\r\n#biloveyou#k (level 1 ~ 30)\r\n#bpee#k (level 1 ~ 30)\r\n#btalk, say, chat, bark#k (level 10 ~ 30)\r\n#bdown#k (level 10 ~ 30)\r\n#bup, stand, rise#k (level 20 ~ 30)"); - cm.dispose(); - } else if(selection == 9){ - cm.sendNext("These are the commands for #rBlack Pig#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#biloveyou#k (level 1~30)\r\n#bpoop#k (level 1 ~ 30)\r\n#bhand, up, stand#k (level 1 ~ 30)\r\n#btalk, say, chat, hug#k (level 10 ~ 30)\r\n#bsmile#k (level 10 ~ 30)\r\n#blaugh, smile#k (level 10 ~ 30)\r\n#bcharisma, sleep, sleepy, gotobed#k(level 20~30)"); - cm.dispose(); - } else if(selection == 10){ - cm.sendNext("These are the commands for #rPanda#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#biloveyou#k (level 1 ~ 30)\r\n#bpee#k(level 1 ~ 30)\r\n#bup, stand, hug#k (level 1 ~ 30)\r\n#btalk, chat#k (level 10 ~ 30)\r\n#bplay#k (level 20 ~ 30)\r\n#bmeh, bleh#k (level 10 ~ 30)\r\n#bsleep, sleepy, gotobed#k (level 20 ~ 30)"); - cm.dispose(); - } else if(selection == 11){ - cm.sendNext("These are the commands for #rDino Boy and Dino Girl#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no,, stupid, ihateyou, badboy, badgirl#k (evel 1 ~ 30)\r\n#biloveyou, dummy#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#btalk, chat(level 10 ~ 30)\r\n#bsmile, laugh#k (level 1 ~ 30)\r\n#bcutie#k (level 10 ~ 30)\r\n#bsleep, nap, sleepy#k (level 20 ~ 30)"); - cm.dispose(); - } else if(selection == 12){ - cm.sendNext("These are the commands for #rRudolph#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k(level 1 ~30) \r\n#bbad, no, badgirl, badboy#k(level 1~30)\r\n#bup, stand#k(level 1 ~ 30) \r\n#bstupid, ihateyou, dummy#k(level 1 ~ 30) \r\n#bmerryxmas, merrychristmas#k(level 11 ~ 30)\r\n#biloveyou#k(level 1 ~ 30)\r\n#bpoop#k(level 1 ~ 30)\r\n#btalk, say, chat#k(level 11 ~ 30)\r\n#blonely, alone, down, rednose#k(level 11~30),\r\n#bcutie#k(level 11 ~ 30)\r\n#bmush, go#k(level 21 ~ 30)"); - cm.dispose(); - } else if (selection == 13) { - cm.sendNext("These are the commands for #rMonkey#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit, rest#k (level 1 ~ 30)\r\n#bbad, no, badboy, badgirl#k (level 1 ~ 30)\r\n#bup, stand#k(level 1 ~ 30)\r\n#biloveyou, pee#k (level 1 ~ 30)\r\n#btalk, say, chat#k (level 11 ~ 30)\r\n#bplay, melong#k (level 11 ~ 30)\r\n#bsleep, sleepy, gotobed#k (level 21 ~ 30)"); - cm.dispose(); - } else if (selection == 14) { - cm.sendNext("These are the commands for #rRobot#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit, stand, rise#k (level 1 ~ 30)\r\n#battack, bad, no, badboy#k (level 1 ~ 30)\r\n#bstupid, ihateyou, dummy#k (level 1 ~ 30)\r\n#biloveyou, good#k (level 1 ~ 30)\r\n#bspeak, disguise#k (level 11 ~ 30)"); - cm.dispose(); - } else if (selection == 15) { - cm.sendNext("These are the commands for #rElephant#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit, rest#k (level 1 ~ 30)\r\n#bbad, no, badboy, badgirl#k (level 1 ~ 30)\r\n#bup, stand, rise#k(level 1 ~ 30)\r\n#biloveyou, pee#k (level 1 ~ 30)\r\n#btalk, say, chat, play#k (level 11 ~ 30)\r\n#bsleep, sleepy, gotobed#k (level 21 ~ 30)"); - cm.dispose(); - } else if (selection == 16) { - cm.sendNext("These are the commands for #rGolden Pig#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badboy, badgirl#k (level 1 ~ 30)\r\n#bpoop, iloveyou#k (level 1 ~ 30)\r\n#btalk, say, chat#k (level 11 ~ 30)\r\n#bloveme, hugme#k (level 11 ~ 30)\r\n#bsleep, sleepy, gotobed#k (level 21 ~ 30)\r\n#bimpressed, outofhere#k (level 21 ~ 30)\r\n#broll, showmethemoney#k (level 21 ~ 30)"); - cm.dispose(); - } else if (selection == 17) { - cm.sendNext("These are the commands for #rPenguin#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badboy, badgirl#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#bup, stand, rise#k (level 1 ~ 30)\r\n#biloveyou#k (level 1 ~ 30)\r\n#btalk, chat, say#k (level 10 ~ 30)\r\n#bhug, hugme#k (level 10 ~ 30)\r\n#bwing, hand#k (level 10 ~ 30)\r\n#bsleep#k (level 20 ~ 30)\r\n#bkiss, smooch, muah#k (level 20 ~ 30)\r\n#bfly#k (level 20 ~ 30)\r\n#bcute, adorable#k (level 20 ~ 30)"); - cm.dispose(); - } else if (selection == 18) { - cm.sendNext("These are the commands for #rMini Yeti#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badboy, badgirl#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#bdance, boogie, shakeit#k (level 1 ~ 30)\r\n#bcute, cutie, pretty, adorable#k (level 1 ~ 30)\r\n#biloveyou, likeyou, mylove#k (level 1 ~ 30)\r\n#btalk, chat, say#k (level 10 ~ 30)\r\n#bsleep, nap#k (level 10 ~ 30)"); - cm.dispose(); - } else if (selection == 19) { - cm.sendNext("These are the commands for #rJr. Balrog#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bliedown#k (level 1 ~ 30)\r\n#bno|bad|badgirl|badboy#k (level 1 ~ 30)\r\n#biloveyou|mylove|likeyou#k (level 1 ~ 30)\r\n#bcute|cutie|pretty|adorable#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#bsmirk|crooked|laugh#k (level 1 ~ 30)\r\n#bmelong#k (level 11 ~ 30)\r\n#bgood|thelook|charisma#k (level 11 ~ 30)\r\n#bspeak|talk|chat|say#k (level 11 ~ 30)\r\n#bsleep|nap|sleepy#k (level 11 ~ 30)\r\n#bgas#k (level 21 ~ 30)"); - cm.dispose(); - } else if (selection == 20) { - cm.sendNext("These are the commands for #rBaby Dragon#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bno|bad|badgirl|badboy#k (level 1 ~ 30)\r\n#biloveyou|loveyou#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#bstupid|ihateyou|dummy#k (level 1 ~ 30)\r\n#bcutie#k (level 11 ~ 30)\r\n#btalk|chat|say#k (level 11 ~ 30)\r\n#bsleep|sleepy|gotobed#k (level 11 ~ 30)"); - cm.dispose(); - } else if (selection == 21) { - cm.sendNext("These are the commands for #rGreen/Red/Blue Dragon#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 15 ~ 30)\r\n#bno|bad|badgirl|badboy#k (level 15 ~ 30)\r\n#biloveyou|loveyou#k (level 15 ~ 30)\r\n#bpoop#k (level 15 ~ 30)\r\n#bstupid|ihateyou|dummy#k (level 15 ~ 30)\r\n#btalk|chat|say#k (level 15 ~ 30)\r\n#bsleep|sleepy|gotobed#k (level 15 ~ 30)\r\n#bchange#k (level 21 ~ 30)"); - cm.dispose(); - } else if (selection == 22) { - cm.sendNext("These are the commands for #rBlack Dragon#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 15 ~ 30)\r\n#bno|bad|badgirl|badboy#k (level 15 ~ 30)\r\n#biloveyou|loveyou#k (level 15 ~ 30)\r\n#bpoop#k (level 15 ~ 30)\r\n#bstupid|ihateyou|dummy#k (level 15 ~ 30)\r\n#btalk|chat|say#k (level 15 ~ 30)\r\n#bsleep|sleepy|gotobed#k (level 15 ~ 30)\r\n#bcutie, change#k (level 21 ~ 30)"); - cm.dispose(); - } else if (selection == 23) { - cm.sendNext("These are the commands for #rSnowman#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bstupid, ihateyou, dummy#k (level 1 ~ 30)\r\n#bloveyou, mylove, ilikeyou#k (level 1 ~ 30)\r\n#bmerrychristmas#k (level 1 ~ 30)\r\n#bcutie, adorable, cute, pretty#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#btalk, chat, say/sleep, sleepy, gotobed#k (level 10 ~ 30)\r\n#bchang#k (level 20 ~ 30)"); - cm.dispose(); - } else if (selection == 24) { - cm.sendNext("These are the commands for #rSun Wu Kong#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k(level 1 ~ 30)\r\n#bno,bad,badgirl,badboy#k(level 1 ~ 30) \r\n#bpoope#k(level 1 ~ 30) \r\n#bcutie,adorable,cute,pretty#k(level 1 ~ 30) \r\n#biloveyou,loveyou,luvyou,ilikeyou,mylove#k(level 1 ~ 30) \r\n#btalk,chat,say/sleep,sleepy,gotobed#k(level 10 ~ 30) \r\n#btransform#k(level 20 ~ 30)"); - cm.dispose(); - } else if (selection == 25) { - cm.sendNext("These are the commands for #rJr. Reaper#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bno|bad|badgirl|badboy#k (level 1 ~ 30)\r\n#bplaydead, poop#k (level 1 ~ 30)\r\n#btalk|chat|say#k (level 1 ~ 30)\r\n#biloveyou, hug#k (level 1 ~ 30)\r\n#bsmellmyfeet, rockout, boo#k (level 1 ~ 30)\r\n#btrickortreat#k (level 1 ~ 30)\r\n#bmonstermash#k (level 1 ~ 30)"); - cm.dispose(); - } else if (selection == 26) { - cm.sendNext("These are the commands for #rCrystal Rudolph#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bno|badgirl|badboy#k (level 1 ~ 30)\r\n#bbleh|joke#k(level 1~30)\r\n#bdisguise|transform#k(level 1 ~ 30) \r\n#bawesome|feelgood|lalala#k(level 1 ~ 30) \r\n#bloveyou|heybabe#k(level 1 ~ 30) \r\n#btalk|say|chat#k(level 10 ~ 30) \r\n#bsleep|sleepy|nap|gotobed#k(level 20 ~ 30)"); - cm.dispose(); - } else if (selection == 27) { - cm.sendNext("These are the commands for #rKino#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad|no|badgirl|badboy#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#bsleep|nap|sleepy|gotobed#k(level 1 ~ 30) \r\n#btalk|say|chat#k(level 10 ~ 30) \r\n#biloveyou|mylove|likeyou#k(level 10 ~ 30) \r\n#bmeh|bleh#k(level 10 ~ 30) \r\n#bdisguise|change|transform#k(level 20 ~ 30)"); - cm.dispose(); - } else if (selection == 28) { - cm.sendNext("These are the commands for #rWhite Duck#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k(level 1 ~ 30) \r\n#bbad|no|badgirl|badboy#k(level 1 ~ 30) \r\n#bup|stand#k(level 1 ~ 30) \r\n#bpoop#k(level 1 ~ 30) \r\n#btalk|chat|say#k(level 1 ~ 30) \r\n#bhug#k(level 1 ~ 30) \r\n#bloveyou#k(level 1 ~ 30) \r\n#bcutie#k(level 1 ~ 30) \r\n#bsleep#k(level 1 ~ 30) \r\n#bsmarty(level 10 ~ 30) \r\n#bdance#k (level 20 ~ 30) \r\n#bswan#k(level 20 ~ 30)"); - cm.dispose(); - } else if (selection == 29){ - cm.sendNext("These are the commands for #rPink Bean#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k(level 1 ~ 30) \r\n#bbad|no|badgirl|badboy|poop#k(level 1 ~ 30) \r\n#blazy|dummy|ihateyoutalk|chat|say|mumbleiloveyou|hugme|loveyou|#k(level 1 ~ 30) \r\n#bshake|music|charmbleh|joke|boo#k(level 20 ~ 30) \r\n#bgotobed|sleep|sleepypoke|stinky|dummy|ihateyou#k(level 20 ~ 30)\r\n#bkongkong#k(level 30)"); - cm.dispose(); - } else if (selection == 30){ - cm.sendNext("These are the commands for #rPorcupine#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bno|bad|badgirl|badboy#k (level 1 ~ 30)\r\n#bhugcushion|sleep|knit|poop#k (level 1 ~ 30)\r\n#bcomb|beach#k (level 10 ~ 30)\r\n#btreeninja|dart#k (level 20 ~ 30)"); - cm.dispose(); - } - } else if(status == 2){ - cm.sendNextPrev("But Water of Life only comes out little at the very bottom of the World Tree, so those babies can't be alive forever... I know, it's very unfortunate... but even if it becomes a doll again they can be brought back to life so be good to it while you're with it."); - } else if(status == 3){ - cm.sendNextPrev("Oh yeah, they'll react when you give them special commands. You can scold them, love them.. it all depends on how you take care of them. They are afraid to leave their masters so be nice to them, show them love. They can get sad and lonely fast.."); - cm.dispose(); - } else if(status == 4){ - cm.sendNextPrev("Talk to the pet, pay attention to it and its closeness level will go up and eventually his overall level will go up too. As the closeness rises, the pet's overall level will rise soon after. As the overall level rises, one day the pet may even talk like a person a little bit, so try hard raising it. Of course it won't be easy doing so..."); - } else if(status == 5){ - cm.sendNextPrev("It may be a live doll but they also have life so they can feel the hunger too. #bFullness#k shows the level of hunger the pet's in. 100 is the max, and the lower it gets, it means that the pet is getting hungrier. After a while, it won't even follow your command and be on the offensive, so watch out over that."); - } else if(status == 6){ - cm.sendNextPrev("That's right! Pets can't eat the normal human food. Instead a teddy bear in Ludibrium called #bPatricia#k sells #bPet Food#k so if you need food for your pet, find #bPatricia#k It'll be a good idea to buy the food in advance and feed the pet before it gets really hungry."); - } else if(status == 7){ - cm.sendNextPrev("Oh, and if you don't feed the pet for a long period of time, it goes back home by itself. You can take it out of its home and feed it but it's not really good for the pet's health, so try feeding him on a regular basis so it doesn't go down to that level, alright? I think this will do."); - cm.dispose(); - } else if(status == 8){ - cm.sendNextPrev("After some time... that's correct, they stop moving. They just turn back to being a doll, after the effect of magic dies down and Water of Life dries out. But that doesn't mean it's stopped forever, because once you pour Water of Life over, it's going to be back alive."); - } else if(status == 9){ - cm.sendNextPrev("Even if it someday moves again, it's sad to see them stop altogether. Please be nice to them while they are alive and moving. Feed them well, too. Isn't it nice to know that there's something alive that follows and listens to only you?"); - cm.dispose(); - } - } + } else { + if (status >= 0 && mode == 0) { + cm.dispose(); + return; + } + if (mode == 1) + status++; + else + status--; + + if(status == 0){ + cm.sendSimple("Hello there, I'm #bMar the Fairy#k of Victoria Island's main disciple. Mar the Fairy summoned me here to see if the pets are being taken care of here in Ludibrium. What can I do for you? \r\n#L0##bMy pet has turned back into a doll\r\nPlease help me make it move again!#k#l \r\n#L1##bTell me more about Pets.#k#l \r\n#L2##bHow do I raise Pets?#k#l \r\n#L3##bDo Pets die too?#k#l \r\n#L4##bWhat are the commands for brown and black kitty?#k#l \r\n#L5##bWhat are the commands for brown puppy?#k#l \r\n#L6##bWhat are the commands for pink and white bunny?#k#l \r\n#L7##bWhat are the commands for Mini Cargo?#k#l \r\n#L8##bWhat are the commands for Husky?#k#l \r\n#L9##bWhat are the commands for Black Pig?#k#l \r\n#L10##bWhat are the commands for Panda#k#l \r\n#L11##bWhat are the commands for Dino Boy & Girl?#k#l \r\n#L12##bWhat are the commands for Rudolph?#k#l \r\n#L13##bWhat are the commands for Monkey?#k#l \r\n#L14##bWhat are the commands for Robot?#k#l \r\n#L15##bWhat are the commands for Elephant?#k#l \r\n#L16##bWhat are the commands for Golden Pig?#k#l \r\n#L17##bWhat are the commands for Penguin?#k#l \r\n#L18##bWhat are the commands for Mini Yeti?#k#l \r\n#L19##bWhat are the commands for Jr. Balrog? \r\n#L20##bWhat are the commands for Baby Dragon?#k#l \r\n#L21##bWhat are the commands for Green/Red/Blue Dragon?#k#l \r\n#L22##bWhat are the commands for Black Dragon?#k#l \r\n#L23##bWhat are the commands for Snowman?#k#l \r\n#L24##bWhat are the commands for Sun Wu Kong?#k#l \r\n#L25##bWhat are the commands for Jr. Reaper?#k#l \r\n#L26##bWhat are the commands for Crystal Rudolph?#k#l \r\n#L27##bWhat are the commands for Kino?#k#l \r\n#L28##bWhat are the commands for White Duck?#k#l \r\n#L29##bWhat are the commands for Pink Bean?#k#l \r\n#L30##bWhat are the commands for Porcupine?#k#l"); + } + else if(status == 1){ + if(selection == 0){ + cm.sendNext("I'm Wisp, continuing on with the studies that my Master Mar the Fairy assigned me. There seems to be a lot of pets even her in Ludibrium. I need to get back to my studies, so if you'll excuse me..."); + cm.dispose(); + } else if(selection == 1){ + cm.sendNext("Hmmmm,you must have a lot of questions regarding the pets. Long ago, a person by the name #bCloy#k, sprayed Water of Life on it, and cast spell on it to create a magical animal. I know it sounds unbelievable, but it's a doll that became an actual living thing. They understand and follow people very well."); + } else if(selection == 2){ + cm.sendNext("Depending on the command you give, pets can love it, hate, and display other kinds of reactions to it. If you give the pet a command and it follows you well, your closeness goes up. Double click on the pet and you can check the closeness, level, fullness and etc..."); + } else if(selection == 3){ + cm.sendNext("Dying... well, they aren't technically ALIVE per se, so I don't know if dying is the right term to use. They are dolls with my magical power and the power of Water of Life to become a live object. Of course while it's alive, it's just like a live animal..."); + } else if(selection == 4){ + cm.sendNext("These are the commands for #rBrown Kitty and Black Kitty#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#bstupid, ihateyou, dummy#k (level 1 ~ 30)\r\n#biloveyou#k (level 1~30)\r\n#bpoop#k (level 1 ~ 30)\r\n#btalk, say, chat#k (level 10 ~ 30)\r\n#bcutie#k (level 10 ~ 30)\r\n#bup, stand, rise#k (level 20 ~ 30)"); + cm.dispose(); + } else if(selection == 5){ + cm.sendNext("These are the commands for #rBrown Puppy#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#bstupid, ihateyou, baddog, dummy#k (level 1 ~ 30)\r\n#biloveyou#k (level 1~30)\r\n#bpee#k (level 10 ~ 30)\r\n#btalk, say, chat, bark#k (level 10 ~ 30)\r\n#bdown#k (level 10 ~ 30)\r\n#bup, stand, rise#k (level 20 ~ 30)"); + cm.dispose(); + } else if(selection == 6){ + cm.sendNext("These are the commands for #rPink Bunny and White Bunny#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#bup, stand#k (level 1 ~ 30)\r\n#biloveyou#k (level 1~30)\r\n#bpoop#k (level 1 ~ 30)\r\n#btalk, say, chat#k (level 10 ~ 30)\r\n#bhug#k (level 10 ~ 30)\r\n#bsleep, sleepy, gotobed#k (level 20 ~ 30)"); + cm.dispose(); + } else if(selection == 7){ + cm.sendNext("These are the commands for #rMini Cargo#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#bup, stand#k (level 1 ~ 30)\r\n#biloveyou#k (level 1~30)\r\n#bpee#k (level 1 ~ 30)\r\n#btalk, say, chat#k (level 10 ~ 30)\r\n#bthelook, charisma#k (level 10 ~ 30)\r\n#bgoodboy, good#k (level 20 ~ 30)"); + cm.dispose(); + } else if(selection == 8){ + cm.sendNext("These are the commands for #rHusky#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#bstupid, ihateyou, baddog, dummy#k (level 1 ~ 30)\r\n#biloveyou#k (level 1 ~ 30)\r\n#bpee#k (level 1 ~ 30)\r\n#btalk, say, chat, bark#k (level 10 ~ 30)\r\n#bdown#k (level 10 ~ 30)\r\n#bup, stand, rise#k (level 20 ~ 30)"); + cm.dispose(); + } else if(selection == 9){ + cm.sendNext("These are the commands for #rBlack Pig#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#biloveyou#k (level 1~30)\r\n#bpoop#k (level 1 ~ 30)\r\n#bhand, up, stand#k (level 1 ~ 30)\r\n#btalk, say, chat, hug#k (level 10 ~ 30)\r\n#bsmile#k (level 10 ~ 30)\r\n#blaugh, smile#k (level 10 ~ 30)\r\n#bcharisma, sleep, sleepy, gotobed#k(level 20~30)"); + cm.dispose(); + } else if(selection == 10){ + cm.sendNext("These are the commands for #rPanda#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#biloveyou#k (level 1 ~ 30)\r\n#bpee#k(level 1 ~ 30)\r\n#bup, stand, hug#k (level 1 ~ 30)\r\n#btalk, chat#k (level 10 ~ 30)\r\n#bplay#k (level 20 ~ 30)\r\n#bmeh, bleh#k (level 10 ~ 30)\r\n#bsleep, sleepy, gotobed#k (level 20 ~ 30)"); + cm.dispose(); + } else if(selection == 11){ + cm.sendNext("These are the commands for #rDino Boy and Dino Girl#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no,, stupid, ihateyou, badboy, badgirl#k (evel 1 ~ 30)\r\n#biloveyou, dummy#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#btalk, chat(level 10 ~ 30)\r\n#bsmile, laugh#k (level 1 ~ 30)\r\n#bcutie#k (level 10 ~ 30)\r\n#bsleep, nap, sleepy#k (level 20 ~ 30)"); + cm.dispose(); + } else if(selection == 12){ + cm.sendNext("These are the commands for #rRudolph#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k(level 1 ~30) \r\n#bbad, no, badgirl, badboy#k(level 1~30)\r\n#bup, stand#k(level 1 ~ 30) \r\n#bstupid, ihateyou, dummy#k(level 1 ~ 30) \r\n#bmerryxmas, merrychristmas#k(level 11 ~ 30)\r\n#biloveyou#k(level 1 ~ 30)\r\n#bpoop#k(level 1 ~ 30)\r\n#btalk, say, chat#k(level 11 ~ 30)\r\n#blonely, alone, down, rednose#k(level 11~30),\r\n#bcutie#k(level 11 ~ 30)\r\n#bmush, go#k(level 21 ~ 30)"); + cm.dispose(); + } else if (selection == 13) { + cm.sendNext("These are the commands for #rMonkey#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit, rest#k (level 1 ~ 30)\r\n#bbad, no, badboy, badgirl#k (level 1 ~ 30)\r\n#bup, stand#k(level 1 ~ 30)\r\n#biloveyou, pee#k (level 1 ~ 30)\r\n#btalk, say, chat#k (level 11 ~ 30)\r\n#bplay, melong#k (level 11 ~ 30)\r\n#bsleep, sleepy, gotobed#k (level 21 ~ 30)"); + cm.dispose(); + } else if (selection == 14) { + cm.sendNext("These are the commands for #rRobot#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit, stand, rise#k (level 1 ~ 30)\r\n#battack, bad, no, badboy#k (level 1 ~ 30)\r\n#bstupid, ihateyou, dummy#k (level 1 ~ 30)\r\n#biloveyou, good#k (level 1 ~ 30)\r\n#bspeak, disguise#k (level 11 ~ 30)"); + cm.dispose(); + } else if (selection == 15) { + cm.sendNext("These are the commands for #rElephant#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit, rest#k (level 1 ~ 30)\r\n#bbad, no, badboy, badgirl#k (level 1 ~ 30)\r\n#bup, stand, rise#k(level 1 ~ 30)\r\n#biloveyou, pee#k (level 1 ~ 30)\r\n#btalk, say, chat, play#k (level 11 ~ 30)\r\n#bsleep, sleepy, gotobed#k (level 21 ~ 30)"); + cm.dispose(); + } else if (selection == 16) { + cm.sendNext("These are the commands for #rGolden Pig#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badboy, badgirl#k (level 1 ~ 30)\r\n#bpoop, iloveyou#k (level 1 ~ 30)\r\n#btalk, say, chat#k (level 11 ~ 30)\r\n#bloveme, hugme#k (level 11 ~ 30)\r\n#bsleep, sleepy, gotobed#k (level 21 ~ 30)\r\n#bimpressed, outofhere#k (level 21 ~ 30)\r\n#broll, showmethemoney#k (level 21 ~ 30)"); + cm.dispose(); + } else if (selection == 17) { + cm.sendNext("These are the commands for #rPenguin#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badboy, badgirl#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#bup, stand, rise#k (level 1 ~ 30)\r\n#biloveyou#k (level 1 ~ 30)\r\n#btalk, chat, say#k (level 10 ~ 30)\r\n#bhug, hugme#k (level 10 ~ 30)\r\n#bwing, hand#k (level 10 ~ 30)\r\n#bsleep#k (level 20 ~ 30)\r\n#bkiss, smooch, muah#k (level 20 ~ 30)\r\n#bfly#k (level 20 ~ 30)\r\n#bcute, adorable#k (level 20 ~ 30)"); + cm.dispose(); + } else if (selection == 18) { + cm.sendNext("These are the commands for #rMini Yeti#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad, no, badboy, badgirl#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#bdance, boogie, shakeit#k (level 1 ~ 30)\r\n#bcute, cutie, pretty, adorable#k (level 1 ~ 30)\r\n#biloveyou, likeyou, mylove#k (level 1 ~ 30)\r\n#btalk, chat, say#k (level 10 ~ 30)\r\n#bsleep, nap#k (level 10 ~ 30)"); + cm.dispose(); + } else if (selection == 19) { + cm.sendNext("These are the commands for #rJr. Balrog#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bliedown#k (level 1 ~ 30)\r\n#bno|bad|badgirl|badboy#k (level 1 ~ 30)\r\n#biloveyou|mylove|likeyou#k (level 1 ~ 30)\r\n#bcute|cutie|pretty|adorable#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#bsmirk|crooked|laugh#k (level 1 ~ 30)\r\n#bmelong#k (level 11 ~ 30)\r\n#bgood|thelook|charisma#k (level 11 ~ 30)\r\n#bspeak|talk|chat|say#k (level 11 ~ 30)\r\n#bsleep|nap|sleepy#k (level 11 ~ 30)\r\n#bgas#k (level 21 ~ 30)"); + cm.dispose(); + } else if (selection == 20) { + cm.sendNext("These are the commands for #rBaby Dragon#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bno|bad|badgirl|badboy#k (level 1 ~ 30)\r\n#biloveyou|loveyou#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#bstupid|ihateyou|dummy#k (level 1 ~ 30)\r\n#bcutie#k (level 11 ~ 30)\r\n#btalk|chat|say#k (level 11 ~ 30)\r\n#bsleep|sleepy|gotobed#k (level 11 ~ 30)"); + cm.dispose(); + } else if (selection == 21) { + cm.sendNext("These are the commands for #rGreen/Red/Blue Dragon#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 15 ~ 30)\r\n#bno|bad|badgirl|badboy#k (level 15 ~ 30)\r\n#biloveyou|loveyou#k (level 15 ~ 30)\r\n#bpoop#k (level 15 ~ 30)\r\n#bstupid|ihateyou|dummy#k (level 15 ~ 30)\r\n#btalk|chat|say#k (level 15 ~ 30)\r\n#bsleep|sleepy|gotobed#k (level 15 ~ 30)\r\n#bchange#k (level 21 ~ 30)"); + cm.dispose(); + } else if (selection == 22) { + cm.sendNext("These are the commands for #rBlack Dragon#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 15 ~ 30)\r\n#bno|bad|badgirl|badboy#k (level 15 ~ 30)\r\n#biloveyou|loveyou#k (level 15 ~ 30)\r\n#bpoop#k (level 15 ~ 30)\r\n#bstupid|ihateyou|dummy#k (level 15 ~ 30)\r\n#btalk|chat|say#k (level 15 ~ 30)\r\n#bsleep|sleepy|gotobed#k (level 15 ~ 30)\r\n#bcutie, change#k (level 21 ~ 30)"); + cm.dispose(); + } else if (selection == 23) { + cm.sendNext("These are the commands for #rSnowman#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bstupid, ihateyou, dummy#k (level 1 ~ 30)\r\n#bloveyou, mylove, ilikeyou#k (level 1 ~ 30)\r\n#bmerrychristmas#k (level 1 ~ 30)\r\n#bcutie, adorable, cute, pretty#k (level 1 ~ 30)\r\n#bbad, no, badgirl, badboy#k (level 1 ~ 30)\r\n#btalk, chat, say/sleep, sleepy, gotobed#k (level 10 ~ 30)\r\n#bchang#k (level 20 ~ 30)"); + cm.dispose(); + } else if (selection == 24) { + cm.sendNext("These are the commands for #rSun Wu Kong#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k(level 1 ~ 30)\r\n#bno,bad,badgirl,badboy#k(level 1 ~ 30) \r\n#bpoope#k(level 1 ~ 30) \r\n#bcutie,adorable,cute,pretty#k(level 1 ~ 30) \r\n#biloveyou,loveyou,luvyou,ilikeyou,mylove#k(level 1 ~ 30) \r\n#btalk,chat,say/sleep,sleepy,gotobed#k(level 10 ~ 30) \r\n#btransform#k(level 20 ~ 30)"); + cm.dispose(); + } else if (selection == 25) { + cm.sendNext("These are the commands for #rJr. Reaper#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bno|bad|badgirl|badboy#k (level 1 ~ 30)\r\n#bplaydead, poop#k (level 1 ~ 30)\r\n#btalk|chat|say#k (level 1 ~ 30)\r\n#biloveyou, hug#k (level 1 ~ 30)\r\n#bsmellmyfeet, rockout, boo#k (level 1 ~ 30)\r\n#btrickortreat#k (level 1 ~ 30)\r\n#bmonstermash#k (level 1 ~ 30)"); + cm.dispose(); + } else if (selection == 26) { + cm.sendNext("These are the commands for #rCrystal Rudolph#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bno|badgirl|badboy#k (level 1 ~ 30)\r\n#bbleh|joke#k(level 1~30)\r\n#bdisguise|transform#k(level 1 ~ 30) \r\n#bawesome|feelgood|lalala#k(level 1 ~ 30) \r\n#bloveyou|heybabe#k(level 1 ~ 30) \r\n#btalk|say|chat#k(level 10 ~ 30) \r\n#bsleep|sleepy|nap|gotobed#k(level 20 ~ 30)"); + cm.dispose(); + } else if (selection == 27) { + cm.sendNext("These are the commands for #rKino#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bbad|no|badgirl|badboy#k (level 1 ~ 30)\r\n#bpoop#k (level 1 ~ 30)\r\n#bsleep|nap|sleepy|gotobed#k(level 1 ~ 30) \r\n#btalk|say|chat#k(level 10 ~ 30) \r\n#biloveyou|mylove|likeyou#k(level 10 ~ 30) \r\n#bmeh|bleh#k(level 10 ~ 30) \r\n#bdisguise|change|transform#k(level 20 ~ 30)"); + cm.dispose(); + } else if (selection == 28) { + cm.sendNext("These are the commands for #rWhite Duck#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k(level 1 ~ 30) \r\n#bbad|no|badgirl|badboy#k(level 1 ~ 30) \r\n#bup|stand#k(level 1 ~ 30) \r\n#bpoop#k(level 1 ~ 30) \r\n#btalk|chat|say#k(level 1 ~ 30) \r\n#bhug#k(level 1 ~ 30) \r\n#bloveyou#k(level 1 ~ 30) \r\n#bcutie#k(level 1 ~ 30) \r\n#bsleep#k(level 1 ~ 30) \r\n#bsmarty(level 10 ~ 30) \r\n#bdance#k (level 20 ~ 30) \r\n#bswan#k(level 20 ~ 30)"); + cm.dispose(); + } else if (selection == 29){ + cm.sendNext("These are the commands for #rPink Bean#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k(level 1 ~ 30) \r\n#bbad|no|badgirl|badboy|poop#k(level 1 ~ 30) \r\n#blazy|dummy|ihateyoutalk|chat|say|mumbleiloveyou|hugme|loveyou|#k(level 1 ~ 30) \r\n#bshake|music|charmbleh|joke|boo#k(level 20 ~ 30) \r\n#bgotobed|sleep|sleepypoke|stinky|dummy|ihateyou#k(level 20 ~ 30)\r\n#bkongkong#k(level 30)"); + cm.dispose(); + } else if (selection == 30){ + cm.sendNext("These are the commands for #rPorcupine#k. The level mentioned next to the command shows the pet level required for it to respond.\r\n#bsit#k (level 1 ~ 30)\r\n#bno|bad|badgirl|badboy#k (level 1 ~ 30)\r\n#bhugcushion|sleep|knit|poop#k (level 1 ~ 30)\r\n#bcomb|beach#k (level 10 ~ 30)\r\n#btreeninja|dart#k (level 20 ~ 30)"); + cm.dispose(); + } + } else if(status == 2){ + cm.sendNextPrev("But Water of Life only comes out little at the very bottom of the World Tree, so those babies can't be alive forever... I know, it's very unfortunate... but even if it becomes a doll again they can be brought back to life so be good to it while you're with it."); + } else if(status == 3){ + cm.sendNextPrev("Oh yeah, they'll react when you give them special commands. You can scold them, love them.. it all depends on how you take care of them. They are afraid to leave their masters so be nice to them, show them love. They can get sad and lonely fast.."); + cm.dispose(); + } else if(status == 4){ + cm.sendNextPrev("Talk to the pet, pay attention to it and its closeness level will go up and eventually his overall level will go up too. As the closeness rises, the pet's overall level will rise soon after. As the overall level rises, one day the pet may even talk like a person a little bit, so try hard raising it. Of course it won't be easy doing so..."); + } else if(status == 5){ + cm.sendNextPrev("It may be a live doll but they also have life so they can feel the hunger too. #bFullness#k shows the level of hunger the pet's in. 100 is the max, and the lower it gets, it means that the pet is getting hungrier. After a while, it won't even follow your command and be on the offensive, so watch out over that."); + } else if(status == 6){ + cm.sendNextPrev("That's right! Pets can't eat the normal human food. Instead a teddy bear in Ludibrium called #bPatricia#k sells #bPet Food#k so if you need food for your pet, find #bPatricia#k It'll be a good idea to buy the food in advance and feed the pet before it gets really hungry."); + } else if(status == 7){ + cm.sendNextPrev("Oh, and if you don't feed the pet for a long period of time, it goes back home by itself. You can take it out of its home and feed it but it's not really good for the pet's health, so try feeding him on a regular basis so it doesn't go down to that level, alright? I think this will do."); + cm.dispose(); + } else if(status == 8){ + cm.sendNextPrev("After some time... that's correct, they stop moving. They just turn back to being a doll, after the effect of magic dies down and Water of Life dries out. But that doesn't mean it's stopped forever, because once you pour Water of Life over, it's going to be back alive."); + } else if(status == 9){ + cm.sendNextPrev("Even if it someday moves again, it's sad to see them stop altogether. Please be nice to them while they are alive and moving. Feed them well, too. Isn't it nice to know that there's something alive that follows and listens to only you?"); + cm.dispose(); + } + } } \ No newline at end of file diff --git a/scripts/npc/2041023.js b/scripts/npc/2041023.js index dd45a5c0b3..3a90542a8c 100644 --- a/scripts/npc/2041023.js +++ b/scripts/npc/2041023.js @@ -19,7 +19,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -//First version by Moogra +//First version thanks to Moogra /** * @author: Ronan diff --git a/scripts/npc/2050014.js b/scripts/npc/2050014.js new file mode 100644 index 0000000000..4803367126 --- /dev/null +++ b/scripts/npc/2050014.js @@ -0,0 +1,60 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +/* Meteorite + @Author RonanLana (Ronan) + */ + +var status; + +function start() { + status = -1; + action(1, 0, 0); +} + +function action(mode, type, selection) { + if (mode == -1) { + cm.dispose(); + } else { + if (mode == 0 && type > 0) { + cm.dispose(); + return; + } + if (mode == 1) + status++; + else + status--; + + if(status == 0) { + if(cm.isQuestStarted(3421)) { + var meteoriteId = cm.getNpc() - 2050014; + + var progress = cm.getQuestProgress(3421, 0); + if((progress >> meteoriteId) % 2 == 0 || (progress == 63 && !cm.haveItem(4031117, 6))) { + progress |= (1 << meteoriteId); + + cm.gainItem(4031117, 1); + cm.setQuestProgress(3421, 0, progress); + } + } + + cm.dispose(); + } + } +} diff --git a/scripts/npc/2050015.js b/scripts/npc/2050015.js new file mode 100644 index 0000000000..4803367126 --- /dev/null +++ b/scripts/npc/2050015.js @@ -0,0 +1,60 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +/* Meteorite + @Author RonanLana (Ronan) + */ + +var status; + +function start() { + status = -1; + action(1, 0, 0); +} + +function action(mode, type, selection) { + if (mode == -1) { + cm.dispose(); + } else { + if (mode == 0 && type > 0) { + cm.dispose(); + return; + } + if (mode == 1) + status++; + else + status--; + + if(status == 0) { + if(cm.isQuestStarted(3421)) { + var meteoriteId = cm.getNpc() - 2050014; + + var progress = cm.getQuestProgress(3421, 0); + if((progress >> meteoriteId) % 2 == 0 || (progress == 63 && !cm.haveItem(4031117, 6))) { + progress |= (1 << meteoriteId); + + cm.gainItem(4031117, 1); + cm.setQuestProgress(3421, 0, progress); + } + } + + cm.dispose(); + } + } +} diff --git a/scripts/npc/2050016.js b/scripts/npc/2050016.js new file mode 100644 index 0000000000..4803367126 --- /dev/null +++ b/scripts/npc/2050016.js @@ -0,0 +1,60 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +/* Meteorite + @Author RonanLana (Ronan) + */ + +var status; + +function start() { + status = -1; + action(1, 0, 0); +} + +function action(mode, type, selection) { + if (mode == -1) { + cm.dispose(); + } else { + if (mode == 0 && type > 0) { + cm.dispose(); + return; + } + if (mode == 1) + status++; + else + status--; + + if(status == 0) { + if(cm.isQuestStarted(3421)) { + var meteoriteId = cm.getNpc() - 2050014; + + var progress = cm.getQuestProgress(3421, 0); + if((progress >> meteoriteId) % 2 == 0 || (progress == 63 && !cm.haveItem(4031117, 6))) { + progress |= (1 << meteoriteId); + + cm.gainItem(4031117, 1); + cm.setQuestProgress(3421, 0, progress); + } + } + + cm.dispose(); + } + } +} diff --git a/scripts/npc/2050017.js b/scripts/npc/2050017.js new file mode 100644 index 0000000000..4803367126 --- /dev/null +++ b/scripts/npc/2050017.js @@ -0,0 +1,60 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +/* Meteorite + @Author RonanLana (Ronan) + */ + +var status; + +function start() { + status = -1; + action(1, 0, 0); +} + +function action(mode, type, selection) { + if (mode == -1) { + cm.dispose(); + } else { + if (mode == 0 && type > 0) { + cm.dispose(); + return; + } + if (mode == 1) + status++; + else + status--; + + if(status == 0) { + if(cm.isQuestStarted(3421)) { + var meteoriteId = cm.getNpc() - 2050014; + + var progress = cm.getQuestProgress(3421, 0); + if((progress >> meteoriteId) % 2 == 0 || (progress == 63 && !cm.haveItem(4031117, 6))) { + progress |= (1 << meteoriteId); + + cm.gainItem(4031117, 1); + cm.setQuestProgress(3421, 0, progress); + } + } + + cm.dispose(); + } + } +} diff --git a/scripts/npc/2050018.js b/scripts/npc/2050018.js new file mode 100644 index 0000000000..4803367126 --- /dev/null +++ b/scripts/npc/2050018.js @@ -0,0 +1,60 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +/* Meteorite + @Author RonanLana (Ronan) + */ + +var status; + +function start() { + status = -1; + action(1, 0, 0); +} + +function action(mode, type, selection) { + if (mode == -1) { + cm.dispose(); + } else { + if (mode == 0 && type > 0) { + cm.dispose(); + return; + } + if (mode == 1) + status++; + else + status--; + + if(status == 0) { + if(cm.isQuestStarted(3421)) { + var meteoriteId = cm.getNpc() - 2050014; + + var progress = cm.getQuestProgress(3421, 0); + if((progress >> meteoriteId) % 2 == 0 || (progress == 63 && !cm.haveItem(4031117, 6))) { + progress |= (1 << meteoriteId); + + cm.gainItem(4031117, 1); + cm.setQuestProgress(3421, 0, progress); + } + } + + cm.dispose(); + } + } +} diff --git a/scripts/npc/2050019.js b/scripts/npc/2050019.js new file mode 100644 index 0000000000..4803367126 --- /dev/null +++ b/scripts/npc/2050019.js @@ -0,0 +1,60 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +/* Meteorite + @Author RonanLana (Ronan) + */ + +var status; + +function start() { + status = -1; + action(1, 0, 0); +} + +function action(mode, type, selection) { + if (mode == -1) { + cm.dispose(); + } else { + if (mode == 0 && type > 0) { + cm.dispose(); + return; + } + if (mode == 1) + status++; + else + status--; + + if(status == 0) { + if(cm.isQuestStarted(3421)) { + var meteoriteId = cm.getNpc() - 2050014; + + var progress = cm.getQuestProgress(3421, 0); + if((progress >> meteoriteId) % 2 == 0 || (progress == 63 && !cm.haveItem(4031117, 6))) { + progress |= (1 << meteoriteId); + + cm.gainItem(4031117, 1); + cm.setQuestProgress(3421, 0, progress); + } + } + + cm.dispose(); + } + } +} diff --git a/scripts/npc/2081005.js b/scripts/npc/2081005.js index f83fa8fd1f..d709f1787e 100644 --- a/scripts/npc/2081005.js +++ b/scripts/npc/2081005.js @@ -19,7 +19,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -//Fixed by Moogra, Ronan +//@Author Moogra, Ronan //Fixed grammar, javascript syntax importPackage(Packages.client); diff --git a/scripts/npc/2083001.js b/scripts/npc/2083001.js index 998382ca8a..f7d3e7e96c 100644 --- a/scripts/npc/2083001.js +++ b/scripts/npc/2083001.js @@ -96,7 +96,7 @@ function action(mode, type, selection) { } } else { if(!cm.isEventLeader()) { - cm.sendOk("Only your event leader is allowed to interact with the Schedule."); + cm.sendOk("Only your party leader is allowed to interact with the Schedule."); } else if(cm.getMapId() == 240050100) { if(cm.haveItem(4001087) && cm.haveItem(4001088) && cm.haveItem(4001089) && cm.haveItem(4001090) && cm.haveItem(4001091)) { cm.gainItem(4001087, -1); diff --git a/scripts/npc/2083004.js b/scripts/npc/2083004.js index ad98d3336c..6194b60125 100644 --- a/scripts/npc/2083004.js +++ b/scripts/npc/2083004.js @@ -20,7 +20,7 @@ */ /*Mark of the Squad * - *@author SharpAceX (Alan) + *@author Alan (SharpAceX) *@author Ronan */ importPackage(Packages.server.expeditions); diff --git a/scripts/npc/2090004.js b/scripts/npc/2090004.js index 2785443d14..e7dc6ff8e0 100644 --- a/scripts/npc/2090004.js +++ b/scripts/npc/2090004.js @@ -1,4 +1,4 @@ -/* @author aaroncsn(MapleSea Like) +/* @author aaroncsn * @author Ronan NPC Name: Mr. Do Map(s): Mu Lung: Mu Lung(2500000000) diff --git a/scripts/npc/2094002.js b/scripts/npc/2094002.js index e7889f22d3..e29288247e 100644 --- a/scripts/npc/2094002.js +++ b/scripts/npc/2094002.js @@ -29,7 +29,7 @@ function action(mode, type, selection) { var eim = cm.getEventInstance(); if (eim == null) { cm.warp(251010404,0); - cm.sendNext("How are you even here without being registered on an event?"); + cm.sendNext("How are you even here without being registered on an instance?"); cm.dispose(); return; } diff --git a/scripts/npc/2100001.js b/scripts/npc/2100001.js index 147d3e6f50..c1143ca4b3 100644 --- a/scripts/npc/2100001.js +++ b/scripts/npc/2100001.js @@ -1,4 +1,4 @@ -/* Author: aaroncsn (MapleSea Like) +/* Author: aaroncsn NPC Name: Muhammad Map(s): Ariant:The Town of Ariant(260000200) Description: Jewel Refiner diff --git a/scripts/npc/2100005.js b/scripts/npc/2100005.js index 2c0172b494..2319919aa9 100644 --- a/scripts/npc/2100005.js +++ b/scripts/npc/2100005.js @@ -1,4 +1,4 @@ -/* Author: aaroncsn (MapleSea Like) +/* Author: aaroncsn NPC Name: Shati Map(s): The Burning Road: Ariant(2600000000) Description: Assistant Hairdresser diff --git a/scripts/npc/2100006.js b/scripts/npc/2100006.js index d307601075..54e0e80a1d 100644 --- a/scripts/npc/2100006.js +++ b/scripts/npc/2100006.js @@ -1,4 +1,4 @@ -/* Author: aaroncsn (MapleSea Like) +/* Author: aaroncsn NPC Name: Mazra Map(s): The Burning Road: Ariant(2600000000) Description: Hair Salon Owner diff --git a/scripts/npc/2100007.js b/scripts/npc/2100007.js index 89e8ecfd36..baa30118f5 100644 --- a/scripts/npc/2100007.js +++ b/scripts/npc/2100007.js @@ -1,4 +1,4 @@ -/* Author: aaroncsn (MapleSea Like)(Incomplete- Needs skin id) +/* Author: aaroncsn <(MapleSea Like)(Incomplete- Needs skin id)> NPC Name: Laila Map(s): The Burning Road: Ariant(2600000000) Description: Skin Care Specialist diff --git a/scripts/npc/2100008.js b/scripts/npc/2100008.js index c9ce080a92..8f54c40b88 100644 --- a/scripts/npc/2100008.js +++ b/scripts/npc/2100008.js @@ -1,4 +1,4 @@ -/* Author: aaroncsn (MapleSea Like) +/* Author: aaroncsn NPC Name: Badr Map(s): The Burning Road: Ariant(2600000000) Description: Ariant Plastic Surgery diff --git a/scripts/npc/2100009.js b/scripts/npc/2100009.js index 1034d0a7ca..394a5e73f5 100644 --- a/scripts/npc/2100009.js +++ b/scripts/npc/2100009.js @@ -1,4 +1,4 @@ -/* Author: aaroncsn (MapleSea Like) +/* Author: aaroncsn NPC Name: Aldin Map(s): The Burning Road: Ariant(2600000000) Description: Ariant Plastic Surgery diff --git a/scripts/npc/2101013.js b/scripts/npc/2101013.js index c36d9483b4..d1e2e42606 100644 --- a/scripts/npc/2101013.js +++ b/scripts/npc/2101013.js @@ -1,4 +1,4 @@ -/* Author: aaroncsn(MapleSea Like) +/* Author: aaroncsn NPC Name: Karcasa Map(s): The Burning Sands: Tents of the Entertainers(260010600) Description: Warps to Victoria Island diff --git a/scripts/npc/2110005.js b/scripts/npc/2110005.js index 6263cedf80..5c3c3de363 100644 --- a/scripts/npc/2110005.js +++ b/scripts/npc/2110005.js @@ -23,7 +23,7 @@ -- Odin JavaScript -------------------------------------------------------------------------------- Camel Cab - Magatia (GMS Like) -- Version Info ----------------------------------------------------------------------------------- - 1.3 - Actually fixed by SharpAceX (Alan) + 1.3 - Actually fixed by Alan (SharpAceX) 1.2 - Fixed and recoded by Moogra 1.1 - Shortened by Moogra 1.0 - First Version by Maple4U, who actually can't code at all diff --git a/scripts/npc/2131000.js b/scripts/npc/2131000.js index 61ce461e4b..d66fdcd8be 100644 --- a/scripts/npc/2131000.js +++ b/scripts/npc/2131000.js @@ -1,4 +1,4 @@ -/* Author: aaroncsn(MapleSea Like)(Incomplete) +/* Author: aaroncsn <(MapleSea Like)(Incomplete)> NPC Name: Athena Pierce Map(s): Altair Camp: Conference Hall(300000010) Description: Unknown diff --git a/scripts/npc/2131002.js b/scripts/npc/2131002.js index b5b9cddcd5..297be8edcf 100644 --- a/scripts/npc/2131002.js +++ b/scripts/npc/2131002.js @@ -1,4 +1,4 @@ -/* Author: aaroncsn(MapleSea Like)(Incomplete) +/* Author: aaroncsn <(MapleSea Like)(Incomplete)> NPC Name: Euryth Map(s): Elin Forest:Altair Camp(300000000) Description: Unknown diff --git a/scripts/npc/2132000.js b/scripts/npc/2132000.js index e55f830ff5..ca0dfa95b2 100644 --- a/scripts/npc/2132000.js +++ b/scripts/npc/2132000.js @@ -1,4 +1,4 @@ -/* Author: aaroncsn(MapleSea Like)(Incomplete) +/* Author: aaroncsn <(MapleSea Like)(Incomplete)> NPC Name: Kanderun Map(s): Elin Forest:Entrance to Rocky Mountain(300010400) Description: Unknown diff --git a/scripts/npc/2141002.js b/scripts/npc/2141002.js index ac180ce887..8506642d0f 100644 --- a/scripts/npc/2141002.js +++ b/scripts/npc/2141002.js @@ -43,7 +43,7 @@ function action(mode, type, selection) { cm.warp(270050000); } else { - cm.sendOk("You cannot receive an event prize without having an empty room in your EQUIP, USE, SET-UP and ETC inventory."); + cm.sendOk("You cannot receive an instance prize without having an empty room in your EQUIP, USE, SET-UP and ETC inventory."); } cm.dispose(); diff --git a/scripts/npc/9000037.js b/scripts/npc/9000037.js index e3c805a0d9..880fef9c34 100644 --- a/scripts/npc/9000037.js +++ b/scripts/npc/9000037.js @@ -89,7 +89,7 @@ function action(mode, type, selection) { cm.sendOk("Your party completed such an astounding feat coming this far, #byou have defeated all the bosses#k, congratulations! Now I will be handing your reward as you are being transported out..."); } else { - cm.sendOk("For #bdefeating all bosses#k in this event, congratulations! You will now receive a prize that matches your performance here as I warp you out."); + cm.sendOk("For #bdefeating all bosses#k in this instance, congratulations! You will now receive a prize that matches your performance here as I warp you out."); } } else if(state == 2) { @@ -109,7 +109,7 @@ function action(mode, type, selection) { return; } } else if(state == 1) { - cm.sendYesNo("Do you wish to abandon this event?"); + cm.sendYesNo("Do you wish to abandon this instance?"); } else { em = cm.getEventManager("BossRushPQ"); @@ -174,7 +174,7 @@ function action(mode, type, selection) { cm.sendOk("Try using a Super Megaphone or asking your buddies or guild to join!"); cm.dispose(); } else { - cm.sendOk("#e#b#k#n\r\nBrave adventurers from all over the places travels here to test their skills and abilities in combat, as they face even more powerful bosses from MapleStory. Join forces with fellow adventurers or face all the burden by yourself and receive all the glory, it is up to you. REWARDS are given accordingly to how far the adventurers reach and extra prizes may are given to a random member of the party, all attributed at the end of an expedition.\r\n\r\nThis event also supports #bmultiple lobbies for matchmaking several ranges of team levels#k at once: team up with players with lower level if you want better chances to swiftly set up a boss rush for your team."); + cm.sendOk("#e#b#k#n\r\nBrave adventurers from all over the places travels here to test their skills and abilities in combat, as they face even more powerful bosses from MapleStory. Join forces with fellow adventurers or face all the burden by yourself and receive all the glory, it is up to you. REWARDS are given accordingly to how far the adventurers reach and extra prizes may are given to a random member of the party, all attributed at the end of an expedition.\r\n\r\nThis instance also supports #bmultiple lobbies for matchmaking several ranges of team levels#k at once: team up with players with lower level if you want better chances to swiftly set up a boss rush for your team."); cm.dispose(); } } diff --git a/scripts/npc/9001108.js b/scripts/npc/9001108.js index b97c317956..81c853e49c 100644 --- a/scripts/npc/9001108.js +++ b/scripts/npc/9001108.js @@ -20,7 +20,7 @@ along with this program. If not, see . */ /** -* @Author : iAkira/Kevintjuh93 +* @Author : iAkira, Kevintjuh93 **/ var status = 0; var selected = 0; diff --git a/scripts/npc/9040009.js b/scripts/npc/9040009.js index 73ea28eff0..9d5c33b8f5 100644 --- a/scripts/npc/9040009.js +++ b/scripts/npc/9040009.js @@ -66,14 +66,13 @@ function action(mode, type, selection) { } else { stage = parseInt(eim.getProperty("stage1phase")); } + if (stage == 1) { cm.sendOk("In this challenge, I shall show a pattern on the statues around me. When I give the word, repeat the pattern to me to proceed."); - } - else { + } else { cm.sendOk("I shall now present a more difficult puzzle for you. Good luck."); } - } - else if (eim.getProperty("stage1status").equals("active")) { + } else if (eim.getProperty("stage1status").equals("active")) { stage = parseInt(eim.getProperty("stage1phase")); if (eim.getProperty("stage1combo").equals(eim.getProperty("stage1guess"))) { @@ -97,8 +96,7 @@ function action(mode, type, selection) { } eim.setProperty("stage1status", "waiting"); cm.dispose(); - } - else { + } else { cm.sendOk("The statues are working on the pattern. Please wait."); cm.dispose(); } @@ -116,7 +114,7 @@ function action(mode, type, selection) { cm.dispose(); } } else { - cm.sendOk("I need the leader of this event to speak with me, nobody else."); + cm.sendOk("I need the leader of this instance to speak with me, nobody else."); cm.dispose(); } } @@ -134,7 +132,7 @@ function getReactors() { reactors.push(mo.getObjectId()); } } - + return reactors; } diff --git a/scripts/npc/9040010.js b/scripts/npc/9040010.js index 59244232be..802ea6f628 100644 --- a/scripts/npc/9040010.js +++ b/scripts/npc/9040010.js @@ -43,7 +43,7 @@ function start() { } } else { - cm.sendOk("This is your final challenge. Defeat the evil lurking within the Rubian and let your event leader return it to me. That is all."); + cm.sendOk("This is your final challenge. Defeat the evil lurking within the Rubian and let your instance leader return it to me. That is all."); } } else diff --git a/scripts/npc/9103000.js b/scripts/npc/9103000.js index 8ad59a2ea8..eadcf5837e 100644 --- a/scripts/npc/9103000.js +++ b/scripts/npc/9103000.js @@ -54,7 +54,7 @@ function action(mode, type, selection) { if (status == 0) { if(cm.isEventLeader()) { if(!cm.getEventInstance().isEventTeamTogether()) { - cm.sendOk("One or more event team members is missing, please wait for them to reach here first."); + cm.sendOk("One or more instance team members is missing, please wait for them to reach here first."); cm.dispose(); } else if(cm.hasItem(4001106, 30)) { diff --git a/scripts/npc/9201042.js b/scripts/npc/9201042.js index e2c8d7d993..d884e0b67b 100644 --- a/scripts/npc/9201042.js +++ b/scripts/npc/9201042.js @@ -74,7 +74,7 @@ function action(mode, type, selection) { advance = true; if(status == 0) { - cm.sendNext("Hi there, how is it going? Since you're passing by Amoria, have you heard about the event my brother Amos is hosting? It is the #bAmorian Challenge#k, an event for everyone above level 40.\r\n\r\nThere, you may find the #i4031543# #i4031544# #i4031545# #bWish Tickets#k that can be brought here to redeem prizes."); + cm.sendNext("Hi there, how is it going? Since you're passing by Amoria, have you heard about the instance my brother Amos is hosting? It is the #bAmorian Challenge#k, an instance for everyone above level 40.\r\n\r\nThere, you may find the #i4031543# #i4031544# #i4031545# #bWish Tickets#k that can be brought here to redeem prizes."); } else if(status == 1) { var listStr = ""; for(var i = 0; i < wishPrizes.length; i++) { diff --git a/scripts/npc/9201048.js b/scripts/npc/9201048.js index 1c1cc13470..d1b674291e 100644 --- a/scripts/npc/9201048.js +++ b/scripts/npc/9201048.js @@ -83,7 +83,7 @@ function action(mode, type, selection) { cm.sendOk("Try using a Super Megaphone or asking your buddies or guild to join!"); cm.dispose(); } else { - cm.sendOk("#e#b#k#n\r\nI am Amos, hoster of the well-round famed Amorian Challenge. The event consist of many team puzzles, where cooperation is the fundamental key for progress. Team up with other players to attempt for the bonus stage, where many goodies can be obtained at the end of the event. If an all-couple party is formed, they can get even better prizes on the extra bonus stage."); + cm.sendOk("#e#b#k#n\r\nI am Amos, hoster of the well-round famed Amorian Challenge. The instance consist of many team puzzles, where cooperation is the fundamental key for progress. Team up with other players to attempt for the bonus stage, where many goodies can be obtained at the end of the instance. If an all-couple party is formed, they can get even better prizes on the extra bonus stage."); cm.dispose(); } } diff --git a/scripts/npc/9201113.js b/scripts/npc/9201113.js index 408331b17d..d513e4e4fb 100644 --- a/scripts/npc/9201113.js +++ b/scripts/npc/9201113.js @@ -20,7 +20,7 @@ */ /*Jack * - *@author SharpAceX (Alan) + *@author Alan (SharpAceX) *@author Ronan */ importPackage(Packages.server.expeditions); diff --git a/scripts/npc/9201123.js b/scripts/npc/9201123.js index f0ac1d5578..d88c282fb8 100644 --- a/scripts/npc/9201123.js +++ b/scripts/npc/9201123.js @@ -23,11 +23,13 @@ Version |- 1.0 by Jayd + |- 1.1 by Ronan (check job requirements) */ var status; var map = 102000003; var job = "Warrior"; +var jobType = 1; var no = "Come back to me if you decided to be a #b"+job+"#k."; function start() { @@ -52,7 +54,12 @@ function action(mode, type, selection) { if(status == 0) { if (cm.getJob() == "BEGINNER") { - cm.sendYesNo("Hey #h #, I can send you to #b#m"+map+"##k if you want to be a #b"+job+"#k. Do you want to go now?"); + if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType)) { + cm.sendYesNo("Hey #h #, I can send you to #b#m"+map+"##k if you want to be a #b"+job+"#k. Do you want to go now?"); + } else { + cm.sendOk("If you want to be a #b"+job+"#k, train yourself further until you reach #blevel 10, " + cm.getFirstJobStatRequirement(jobType) + "#k."); + cm.dispose(); + } } else { cm.sendOk("You're much stronger now. Keep training!"); cm.dispose(); diff --git a/scripts/npc/9201124.js b/scripts/npc/9201124.js index 62bb600f0f..8a88f420d9 100644 --- a/scripts/npc/9201124.js +++ b/scripts/npc/9201124.js @@ -23,11 +23,14 @@ Version |- 1.0 by Jayd + |- 1.1 by Ronan (check job requirements) */ var status; var map = 100000201; var job = "Bowman"; +var jobType = 3; +var no = "Come back to me if you decided to be a #b"+job+"#k."; function start() { status = -1; @@ -36,10 +39,11 @@ function start() { function action(mode, type, selection) { if (mode == -1) { + cm.sendOk(no); cm.dispose(); } else { if (mode == 0 && type > 0) { - cm.sendOk("Come back to me if you decided to be a #b"+job+"#k."); + cm.sendOk(no); cm.dispose(); } @@ -50,7 +54,12 @@ function action(mode, type, selection) { if(status == 0) { if (cm.getJob() == "BEGINNER") { - cm.sendYesNo("Hey #h #, I can send you to #b#m"+map+"##k if you want to be a #b"+job+"#k. Do you want to go now?"); + if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType)) { + cm.sendYesNo("Hey #h #, I can send you to #b#m"+map+"##k if you want to be a #b"+job+"#k. Do you want to go now?"); + } else { + cm.sendOk("If you want to be a #b"+job+"#k, train yourself further until you reach #blevel 10, " + cm.getFirstJobStatRequirement(jobType) + "#k."); + cm.dispose(); + } } else { cm.sendOk("You're much stronger now. Keep training!"); cm.dispose(); diff --git a/scripts/npc/9201125.js b/scripts/npc/9201125.js index a85ec3f92d..043316878b 100644 --- a/scripts/npc/9201125.js +++ b/scripts/npc/9201125.js @@ -23,11 +23,13 @@ Version |- 1.0 by Jayd + |- 1.1 by Ronan (check job requirements) */ var status; var map = 101000003; var job = "Magician"; +var jobType = 2; var no = "Come back to me if you decided to be a #b"+job+"#k."; function start() { @@ -52,7 +54,12 @@ function action(mode, type, selection) { if(status == 0) { if (cm.getJob() == "BEGINNER") { - cm.sendYesNo("Hey #h #, I can send you to #b#m"+map+"##k if you want to be a #b"+job+"#k. Do you want to go now?"); + if (cm.getLevel() >= 8 && cm.canGetFirstJob(jobType)) { + cm.sendYesNo("Hey #h #, I can send you to #b#m"+map+"##k if you want to be a #b"+job+"#k. Do you want to go now?"); + } else { + cm.sendOk("If you want to be a #b"+job+"#k, train yourself further until you reach #blevel 8, " + cm.getFirstJobStatRequirement(jobType) + "#k."); + cm.dispose(); + } } else { cm.sendOk("You're much stronger now. Keep training!"); cm.dispose(); diff --git a/scripts/npc/9201126.js b/scripts/npc/9201126.js index f0832ca33b..98e4a1cd20 100644 --- a/scripts/npc/9201126.js +++ b/scripts/npc/9201126.js @@ -23,11 +23,13 @@ Version |- 1.0 by Jayd + |- 1.1 by Ronan (check job requirements) */ var status; var map = 103000003; var job = "Thief"; +var jobType = 4; var no = "Come back to me if you decided to be a #b"+job+"#k."; function start() { @@ -52,7 +54,12 @@ function action(mode, type, selection) { if(status == 0) { if (cm.getJob() == "BEGINNER") { - cm.sendYesNo("Hey #h #, I can send you to #b#m"+map+"##k if you want to be a #b"+job+"#k. Do you want to go now?"); + if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType)) { + cm.sendYesNo("Hey #h #, I can send you to #b#m"+map+"##k if you want to be a #b"+job+"#k. Do you want to go now?"); + } else { + cm.sendOk("If you want to be a #b"+job+"#k, train yourself further until you reach #blevel 10, " + cm.getFirstJobStatRequirement(jobType) + "#k."); + cm.dispose(); + } } else { cm.sendOk("You're much stronger now. Keep training!"); cm.dispose(); diff --git a/scripts/npc/9201127.js b/scripts/npc/9201127.js index f8bbb5230a..273a394437 100644 --- a/scripts/npc/9201127.js +++ b/scripts/npc/9201127.js @@ -23,11 +23,13 @@ Version |- 1.0 by Jayd + |- 1.1 by Ronan (check job requirements) */ var status; var map = 120000101; var job = "Pirate"; +var jobType = 5; var no = "Come back to me if you decided to be a #b"+job+"#k."; function start() { @@ -52,7 +54,12 @@ function action(mode, type, selection) { if(status == 0) { if (cm.getJob() == "BEGINNER") { - cm.sendYesNo("Hey #h #, I can send you to #b#m"+map+"##k if you want to be a #b"+job+"#k. Do you want to go now?"); + if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType)) { + cm.sendYesNo("Hey #h #, I can send you to #b#m"+map+"##k if you want to be a #b"+job+"#k. Do you want to go now?"); + } else { + cm.sendOk("If you want to be a #b"+job+"#k, train yourself further until you reach #blevel 10, " + cm.getFirstJobStatRequirement(jobType) + "#k."); + cm.dispose(); + } } else { cm.sendOk("You're much stronger now. Keep training!"); cm.dispose(); diff --git a/scripts/npc/9270047.js b/scripts/npc/9270047.js index be8fec82bd..a84ced3482 100644 --- a/scripts/npc/9270047.js +++ b/scripts/npc/9270047.js @@ -20,7 +20,7 @@ */ /*Aldol * - *@author SharpAceX (Alan) + *@author Alan (SharpAceX) *@author Ronan */ importPackage(Packages.server.expeditions); diff --git a/scripts/npc/9977777.js b/scripts/npc/9977777.js index 73da2a4c3d..fa84fbf511 100644 --- a/scripts/npc/9977777.js +++ b/scripts/npc/9977777.js @@ -41,7 +41,7 @@ function addFeature(feature) { function writeFeatureTab_PQs() { addFeature("HPQ/KPQ/LPQ/LMPQ/OPQ/APQ/EllinPQ/PiratePQ."); addFeature("RnJPQ/HorntailPQ/TreasurePQ/ElnathPQ/HolidayPQ."); - addFeature("CWKPQ as Expedition-based event."); + addFeature("CWKPQ as Expedition-based instance."); addFeature("Scarga/Horntail/Showa/Balrog/Zakum/Pinkbean."); addFeature("GuildPQ & queue with multi-lobby systems available."); addFeature("Brand-new PQs: BossRushPQ, CafePQ."); @@ -131,6 +131,7 @@ function writeFeatureTab_MonstersMapsReactors() { } function writeFeatureTab_PQpotentials() { + addFeature("Advanced and well-safe PQ registration system."); addFeature("Lobby system: Same channel, multiple PQ instances."); addFeature("Exped system: Many parties can join a same instance."); addFeature("Guild queue: guild registration for the GPQ."); @@ -144,6 +145,7 @@ function writeFeatureTab_Playerpotentials() { addFeature("Gain fame by quests."); addFeature("Pet evolutions functional (not GMS-like)."); addFeature("Reviewed keybinding system."); + addFeature("Character slots per world/server-wide."); } function writeFeatureTab_Serverpotentials() { @@ -162,6 +164,7 @@ function writeFeatureTab_Serverpotentials() { addFeature("Pet pickup gives preference to player attacks."); addFeature("Channel capacity bar and worlds with capacity check."); addFeature("Diseases visible for others, even after changing maps."); + addFeature("Persistent diseases. Players keep their status on login."); addFeature("Poison damage value visible for other players."); addFeature("M. book announcer displays info based on demand."); addFeature("Custom jail system."); diff --git a/scripts/npc/commands.js b/scripts/npc/commands.js index ccab6953cb..6a3c5f3c0a 100644 --- a/scripts/npc/commands.js +++ b/scripts/npc/commands.js @@ -162,6 +162,9 @@ function writeHeavenMSCommandsLv3() { //GM addCommand("npc", ""); addCommand("face", ""); addCommand("hair", ""); + addCommand("startquest", ""); + addCommand("completequest", ""); + addCommand("resetquest", ""); } function writeHeavenMSCommandsLv2() { //JrGM diff --git a/scripts/npc/credits.js b/scripts/npc/credits.js index 3dda183df6..ac94890497 100644 --- a/scripts/npc/credits.js +++ b/scripts/npc/credits.js @@ -36,6 +36,7 @@ function writeServerStaff_MapleNext() { function writeServerStaff_HeavenMS() { addPerson("Ronan", "Developer"); addPerson("Vcoc", "Freelance Developer"); + addPerson("Thora", "Contributor"); setHistory(2015, 2018); } diff --git a/scripts/portal/TD_neo_inTree.js b/scripts/portal/TD_neo_inTree.js index a46cfbcd69..382fab3263 100644 --- a/scripts/portal/TD_neo_inTree.js +++ b/scripts/portal/TD_neo_inTree.js @@ -1,7 +1,7 @@ function enter(pi) { var nex = pi.getEventManager("GuardianNex"); if(nex == null) { - pi.message("Guardian Nex event encontered an error and is unavailable."); + pi.message("Guardian Nex challenge encountered an error and is unavailable."); return false; } diff --git a/scripts/portal/raid_rest.js b/scripts/portal/raid_rest.js index 4afd73107b..024677c186 100644 --- a/scripts/portal/raid_rest.js +++ b/scripts/portal/raid_rest.js @@ -37,7 +37,7 @@ function enter(pi) { return true; } else { - pi.message("Make a room available on all EQUIP, USE, SET-UP and ETC inventory to claim an event prize."); + pi.message("Make a room available on all EQUIP, USE, SET-UP and ETC inventory to claim an instance prize."); return false; } } \ No newline at end of file diff --git a/scripts/quest/1021.js b/scripts/quest/1021.js index cf5140b563..e7a4a89242 100644 --- a/scripts/quest/1021.js +++ b/scripts/quest/1021.js @@ -19,7 +19,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/* Author: Xterminator (Modified by XxOsirisxX) +/* Author: Xterminator NPC Name: Roger Map(s): Maple Road : Lower level of the Training Camp (2) Description: Quest - Roger's Apple diff --git a/scripts/quest/7103.js b/scripts/quest/7103.js new file mode 100644 index 0000000000..a9a4a35dad --- /dev/null +++ b/scripts/quest/7103.js @@ -0,0 +1,64 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +/* Papulatus - 7103 + */ + +var status = -1; + +function start(mode, type, selection) { + if (mode == -1) { + qm.sendOk("Oh really. Do you need more time? I'm fully confident that you'll help me out before the Time Sphere is formed."); + qm.dispose(); + } else { + if(mode == 0 && type > 0) { + qm.sendOk("Oh really. Do you need more time? I'm fully confident that you'll help me out before the Time Sphere is formed."); + qm.dispose(); + return; + } + + if (mode == 1) + status++; + else + status--; + + if (status == 0) { + qm.sendYesNo("Only thing we have to do now ...is to make #o8500002# disappear forever... are you ready?"); + } else if (status == 1) { + qm.sendNext("I'll explain to you what you need to do from here on out. \r\nTo enter the power-generating room, you'll need to pass either #bForgotten Passage#k or the #bWarped Passage#k. Once you defeat whichever monster that is guarding the passage, you can obtain #b#t4031172:##k, which is needed to enter the power-generating room."); + } else if (status == 2) { + qm.sendNextPrev("Then enter the room through the door in the middle. It's going to be MUCH quieter than you imagined. The Time Sphere should be hidden in a state undetectable in our eyes... but if you seal up the crack in dimension, the #o8500002#, panicking because its exit route is sealed up, will make its appearance there."); + } else if (status == 3) { + if (!qm.haveItem(4031179, 1)) { + if (!qm.canHold(4031179, 1)) { + qm.sendOk("Please have an #rETC slot available#k to start this quest."); + qm.dispose(); + return; + } + + qm.gainItem(4031179, 1); + } + + qm.sendAcceptDecline("Drop the #b#t4031179:##k that I returned to you to seal up whatever crack you see that #o8500002# may have used to enter this dimension in the first place. Then it'll come out of the Time Sphere and show everyone its true appearance. Please, please kill it and then come back. \r\n\r\nCollect #r1 #t4031172:##k\r\nEliminate #r#o8500001##k"); + } else if (status == 4) { + qm.forceStartQuest(); + qm.dispose(); + } + } +} diff --git a/scripts/reactor/9208000.js b/scripts/reactor/9208000.js index 24ed334c6a..0293f00842 100644 --- a/scripts/reactor/9208000.js +++ b/scripts/reactor/9208000.js @@ -40,7 +40,7 @@ function act() { if(!rm.getReactor().isRecentHitFromAttack()) { var prevCombo = eim.getProperty("stage1combo"); - var n = "" + rm.getReactor().getObjectId(); + var n = "" + (rm.getReactor().getObjectId() % 1000); prevCombo += padWithZeroes(n, 3); eim.setProperty("stage1combo",prevCombo); @@ -53,7 +53,7 @@ function act() { } else { //active var prevGuess = "" + eim.getProperty("stage1guess"); if (prevGuess.length != (3 * (stage + 3))) { - var n = "" + rm.getReactor().getObjectId(); + var n = "" + (rm.getReactor().getObjectId() % 1000); prevGuess += padWithZeroes(n, 3); eim.setProperty("stage1guess",prevGuess); diff --git a/scripts/reactor/9208001.js b/scripts/reactor/9208001.js index a03ee665e9..92f8b02a65 100644 --- a/scripts/reactor/9208001.js +++ b/scripts/reactor/9208001.js @@ -42,7 +42,7 @@ function act() { if(!rm.getReactor().isRecentHitFromAttack()) { var prevCombo = eim.getProperty("stage1combo"); - var n = "" + rm.getReactor().getObjectId(); + var n = "" + (rm.getReactor().getObjectId() % 1000); prevCombo += padWithZeroes(n, 3); eim.setProperty("stage1combo",prevCombo); @@ -55,7 +55,7 @@ function act() { } else { //active var prevGuess = "" + eim.getProperty("stage1guess"); if (prevGuess.length != (3 * (stage + 3))) { - var n = "" + rm.getReactor().getObjectId(); + var n = "" + (rm.getReactor().getObjectId() % 1000); prevGuess += padWithZeroes(n, 3); eim.setProperty("stage1guess",prevGuess); diff --git a/scripts/reactor/9208002.js b/scripts/reactor/9208002.js index b015dc84eb..ee570945dd 100644 --- a/scripts/reactor/9208002.js +++ b/scripts/reactor/9208002.js @@ -39,7 +39,7 @@ function act() { if (status.equals("display")) { if(!rm.getReactor().isRecentHitFromAttack()) { var prevCombo = eim.getProperty("stage1combo"); - var n = "" + rm.getReactor().getObjectId(); + var n = "" + (rm.getReactor().getObjectId() % 1000); prevCombo += padWithZeroes(n, 3); eim.setProperty("stage1combo",prevCombo); @@ -52,7 +52,7 @@ function act() { } else { //active var prevGuess = "" + eim.getProperty("stage1guess"); if (prevGuess.length != (3 * (stage + 3))) { - var n = "" + rm.getReactor().getObjectId(); + var n = "" + (rm.getReactor().getObjectId() % 1000); prevGuess += padWithZeroes(n, 3); eim.setProperty("stage1guess",prevGuess); diff --git a/sql/db_database.sql b/sql/db_database.sql index fca11f9286..f5192881d4 100644 --- a/sql/db_database.sql +++ b/sql/db_database.sql @@ -25,7 +25,6 @@ CREATE TABLE IF NOT EXISTS `accounts` ( `birthday` date NOT NULL DEFAULT '0000-00-00', `banned` tinyint(1) NOT NULL DEFAULT '0', `banreason` text, - `gm` tinyint(1) NOT NULL DEFAULT '0', `macs` tinytext, `nxCredit` int(11) DEFAULT NULL, `maplePoint` int(11) DEFAULT NULL, @@ -16474,6 +16473,16 @@ CREATE TABLE IF NOT EXISTS `petignores` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; +CREATE TABLE IF NOT EXISTS `playerdiseases` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `charid` int(11) NOT NULL, + `disease` int(11) NOT NULL, + `mobskillid` int(11) NOT NULL, + `mobskilllv` int(11) NOT NULL, + `length` int(11) NOT NULL DEFAULT '1', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; + CREATE TABLE IF NOT EXISTS `playernpcs` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(13) NOT NULL, diff --git a/sql/db_drops.sql b/sql/db_drops.sql index 3d28f15d10..994d42e23d 100644 --- a/sql/db_drops.sql +++ b/sql/db_drops.sql @@ -19823,6 +19823,11 @@ USE `heavenms`; (1110100, 4001369, 1, 1, 28259, 40000), (1210101, 4001370, 1, 1, 28260, 40000), (1110101, 4001371, 1, 1, 28261, 40000), +(2130100, 4001368, 1, 1, 28262, 40000), +(2110200, 4001369, 1, 1, 28262, 40000), +(1110100, 4001370, 1, 1, 28262, 40000), +(1210101, 4001371, 1, 1, 28262, 40000), +(1110101, 4001367, 1, 1, 28262, 40000), (3300003, 4001317, 1, 1, 2326, 20000), (1210102, 4001364, 1, 1, 28192, 80000), (1120100, 4001365, 1, 1, 28192, 80000), diff --git a/src/client/MapleCharacter.java b/src/client/MapleCharacter.java index ae84f877d2..7c2a820088 100644 --- a/src/client/MapleCharacter.java +++ b/src/client/MapleCharacter.java @@ -155,6 +155,7 @@ import constants.skills.Swordsman; import constants.skills.ThunderBreaker; import net.server.channel.handlers.PartyOperationHandler; import scripting.item.ItemScriptManager; +import server.life.MobSkillFactory; import server.maps.MapleMapItem; import net.server.audit.locks.MonitoredLockType; import net.server.audit.locks.factory.MonitoredReentrantLockFactory; @@ -1370,6 +1371,35 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { eventAfterChangedMap(this.getMapId()); } + public void forceChangeMap(final MapleMap target, final MaplePortal pto) { + // will actually enter the map given as parameter, regardless of being an eventmap or whatnot + + canWarpCounter++; + eventChangedMap(999999999); + + EventInstanceManager mapEim = target.getEventInstance(); + if(mapEim != null) { + EventInstanceManager playerEim = this.getEventInstance(); + if(playerEim != null) { + playerEim.exitPlayer(this); + if(playerEim.getPlayerCount() == 0) { + playerEim.dispose(); + } + } + + mapEim.registerPlayer(this); + } + + MapleMap to = getWarpMap(target.getId()); + changeMapInternal(to, pto.getPosition(), MaplePacketCreator.getWarpToMap(to, pto.getId(), this)); + canWarpMap = false; + + canWarpCounter--; + if(canWarpCounter == 0) canWarpMap = true; + + eventAfterChangedMap(this.getMapId()); + } + private boolean buffMapProtection() { effLock.lock(); chrLock.lock(); @@ -2073,6 +2103,10 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { ps.setInt(1, cid); ps.executeUpdate(); } + try (PreparedStatement ps = con.prepareStatement("DELETE FROM playerdiseases WHERE charid = ?")) { + ps.setInt(1, cid); + ps.executeUpdate(); + } try (PreparedStatement ps = con.prepareStatement("DELETE FROM area_info WHERE charid = ?")) { ps.setInt(1, cid); ps.executeUpdate(); @@ -2359,31 +2393,34 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { long expTime = curTime + di.getValue().getLeft(); diseaseExpires.put(di.getKey(), expTime); - diseases.put(di.getKey(), new Pair<>(new MapleDiseaseValueHolder(curTime, expTime), di.getValue().getRight())); + diseases.put(di.getKey(), new Pair<>(new MapleDiseaseValueHolder(curTime, di.getValue().getLeft()), di.getValue().getRight())); } } finally { chrLock.unlock(); } } - public void announceDiseases(MapleClient c) { + public void announceDiseases() { + Set>> chrDiseases; + chrLock.lock(); try { // Poison damage visibility and diseases status visibility, extended through map transitions thanks to Ronan - if(!this.isLoggedinWorld()) return; - for(Entry> di : diseases.entrySet()) { - MapleDisease disease = di.getKey(); - MobSkill skill = di.getValue().getRight(); - final List> debuff = Collections.singletonList(new Pair<>(disease, Integer.valueOf(skill.getX()))); - - if(disease != MapleDisease.SLOW) c.announce(MaplePacketCreator.giveForeignDebuff(id, debuff, skill)); - else c.announce(MaplePacketCreator.giveForeignSlowDebuff(id, debuff, skill)); - } + chrDiseases = new LinkedHashSet<>(diseases.entrySet()); } finally { chrLock.unlock(); } + + for(Entry> di : chrDiseases) { + MapleDisease disease = di.getKey(); + MobSkill skill = di.getValue().getRight(); + final List> debuff = Collections.singletonList(new Pair<>(disease, Integer.valueOf(skill.getX()))); + + if(disease != MapleDisease.SLOW) map.broadcastMessage(MaplePacketCreator.giveForeignDebuff(id, debuff, skill)); + else map.broadcastMessage(MaplePacketCreator.giveForeignSlowDebuff(id, debuff, skill)); + } } public void giveDebuff(final MapleDisease disease, MobSkill skill) { @@ -6276,11 +6313,14 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } rs.close(); ps.close(); - ps = con.prepareStatement("SELECT name FROM accounts WHERE id = ?", Statement.RETURN_GENERATED_KEYS); + ps = con.prepareStatement("SELECT name, characterslots FROM accounts WHERE id = ?", Statement.RETURN_GENERATED_KEYS); ps.setInt(1, ret.accountid); rs = ps.executeQuery(); if (rs.next()) { - ret.getClient().setAccountName(rs.getString("name")); + MapleClient retClient = ret.getClient(); + + retClient.setAccountName(rs.getString("name")); + retClient.setCharacterSlots(rs.getByte("characterslots")); } rs.close(); ps.close(); @@ -6348,7 +6388,9 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { try (ResultSet rsProgress = pse.executeQuery()) { while(rsProgress.next()) { MapleQuestStatus status = loadedQuestStatus.get(rsProgress.getInt("queststatusid")); - status.setProgress(rsProgress.getInt("progressid"), rsProgress.getString("progress")); + if(status != null) { + status.setProgress(rsProgress.getInt("progressid"), rsProgress.getString("progress")); + } } } } @@ -6358,7 +6400,9 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { try (ResultSet rsMedalMaps = pse.executeQuery()) { while(rsMedalMaps.next()) { MapleQuestStatus status = loadedQuestStatus.get(rsMedalMaps.getInt("queststatusid")); - status.addMedalMap(rsMedalMaps.getInt("mapid")); + if(status != null) { + status.addMedalMap(rsMedalMaps.getInt("mapid")); + } } } } @@ -6391,6 +6435,29 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { ps.setInt(1, ret.getId()); ps.executeUpdate(); ps.close(); + Map> loadedDiseases = new LinkedHashMap<>(); + ps = con.prepareStatement("SELECT * FROM playerdiseases WHERE charid = ?"); + ps.setInt(1, ret.getId()); + rs = ps.executeQuery(); + while (rs.next()) { + final MapleDisease disease = MapleDisease.ordinal(rs.getInt("disease")); + if(disease == MapleDisease.NULL) continue; + + final int skillid = rs.getInt("mobskillid"), skilllv = rs.getInt("mobskilllv"); + final long length = rs.getInt("length"); + + MobSkill ms = MobSkillFactory.getMobSkill(skillid, skilllv); + if(ms != null) { + loadedDiseases.put(disease, new Pair<>(length, ms)); + } + } + rs.close(); + ps.close(); + ps = con.prepareStatement("DELETE FROM playerdiseases WHERE charid = ?"); + ps.setInt(1, ret.getId()); + ps.executeUpdate(); + ps.close(); + if(!loadedDiseases.isEmpty()) Server.getInstance().getPlayerBuffStorage().addDiseasesToStorage(ret.id, loadedDiseases); ps = con.prepareStatement("SELECT * FROM skillmacros WHERE characterid = ?"); ps.setInt(1, charid); rs = ps.executeQuery(); @@ -6921,8 +6988,8 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { public synchronized void saveCooldowns() { List listcd = getAllCooldowns(); - - if (listcd.size() > 0) { + + if (!listcd.isEmpty()) { try { Connection con = DatabaseConnection.getConnection(); deleteWhereCharacterId(con, "DELETE FROM cooldowns WHERE charid = ?"); @@ -6942,6 +7009,33 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { se.printStackTrace(); } } + + Map> listds = getAllDiseases(); + if (!listds.isEmpty()) { + try { + Connection con = DatabaseConnection.getConnection(); + deleteWhereCharacterId(con, "DELETE FROM playerdiseases WHERE charid = ?"); + try (PreparedStatement ps = con.prepareStatement("INSERT INTO playerdiseases (charid, disease, mobskillid, mobskilllv, length) VALUES (?, ?, ?, ?, ?)")) { + ps.setInt(1, getId()); + + for (Entry> e : listds.entrySet()) { + ps.setInt(2, e.getKey().ordinal()); + + MobSkill ms = e.getValue().getRight(); + ps.setInt(3, ms.getSkillId()); + ps.setInt(4, ms.getSkillLevel()); + ps.setInt(5, e.getValue().getLeft().intValue()); + ps.addBatch(); + } + + ps.executeBatch(); + } + + con.close(); + } catch (SQLException se) { + se.printStackTrace(); + } + } } public void saveGuildStatus() { @@ -7454,13 +7548,6 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { psf.close(); ps.close(); - ps = con.prepareStatement("UPDATE accounts SET gm = ? WHERE id = ?"); - ps.setInt(1, gmLevel > 1 ? 1 : 0); - ps.setInt(2, client.getAccID()); - ps.executeUpdate(); - ps.close(); - - con.commit(); con.setAutoCommit(true); diff --git a/src/client/MapleClient.java b/src/client/MapleClient.java index 6b72a2ba67..874f633a0f 100644 --- a/src/client/MapleClient.java +++ b/src/client/MapleClient.java @@ -516,13 +516,13 @@ public class MapleClient { ResultSet rs = null; try { con = DatabaseConnection.getConnection(); - ps = con.prepareStatement("SELECT id, password, salt, gender, banned, gm, pin, pic, characterslots, tos FROM accounts WHERE name = ?"); + ps = con.prepareStatement("SELECT id, password, salt, gender, banned, pin, pic, characterslots, tos FROM accounts WHERE name = ?"); ps.setString(1, login); rs = ps.executeQuery(); if (rs.next()) { boolean banned = (rs.getByte("banned") == 1); accId = rs.getInt("id"); - gmlevel = rs.getInt("gm"); + gmlevel = 0; pin = rs.getString("pin"); pic = rs.getString("pic"); gender = rs.getByte("gender"); @@ -796,7 +796,6 @@ public class MapleClient { player.setDisconnectedFromChannelWorld(); player.notifyMapTransferToPartner(-1); player.cancelAllBuffs(true); - player.cancelAllDebuffs(); player.closePlayerInteractions(); QuestScriptManager.getInstance().dispose(this); @@ -845,6 +844,7 @@ public class MapleClient { removePlayer(); player.saveCooldowns(); + player.cancelAllDebuffs(); player.saveCharToDB(true); clear(); @@ -927,6 +927,7 @@ public class MapleClient { //getChannelServer().removePlayer(player); already being done player.saveCooldowns(); + player.cancelAllDebuffs(); player.saveCharToDB(true); if (player != null) {//no idea, occur :( player.empty(false); @@ -936,6 +937,7 @@ public class MapleClient { getChannelServer().removePlayer(player); player.saveCooldowns(); + player.cancelAllDebuffs(); player.saveCharToDB(); } } @@ -1191,9 +1193,21 @@ public class MapleClient { } } + public short getAvailableCharacterSlots() { + return (short) Math.max(0, characterSlots - Server.getInstance().getAccountCharacterCount(accId)); + } + + public short getAvailableCharacterWorldSlots() { + return (short) Math.max(0, characterSlots - Server.getInstance().getAccountWorldCharacterCount(accId, world)); + } + public short getCharacterSlots() { return characterSlots; } + + public void setCharacterSlots(byte slots) { + characterSlots = slots; + } public synchronized boolean gainCharacterSlot() { if (characterSlots < 15) { diff --git a/src/client/MapleDisease.java b/src/client/MapleDisease.java index e6cd16a7ce..784be02d6e 100644 --- a/src/client/MapleDisease.java +++ b/src/client/MapleDisease.java @@ -21,6 +21,9 @@ */ package client; +import java.util.ArrayList; +import java.util.List; + public enum MapleDisease { NULL(0x0), SLOW(0x1), @@ -55,5 +58,12 @@ public enum MapleDisease { public boolean isFirst() { return first; } - + + public static MapleDisease ordinal(int ord) { + try { + return MapleDisease.values()[ord]; + } catch (IndexOutOfBoundsException io) { + return NULL; + } + } } diff --git a/src/client/command/Commands.java b/src/client/command/Commands.java index dec4b6f679..566d1ea2d0 100644 --- a/src/client/command/Commands.java +++ b/src/client/command/Commands.java @@ -1201,7 +1201,7 @@ public class Commands { player.dropMessage(5, "Player '" + victim.getName() + "' is at channel " + victim.getClient().getChannel() + "."); } else { MapleMap map = victim.getMap(); - player.changeMap(map, map.findClosestPortal(victim.getPosition())); + player.forceChangeMap(map, map.findClosestPortal(victim.getPosition())); } } else { player.dropMessage(6, "Unknown player."); @@ -2417,6 +2417,72 @@ public class Commands { } catch(Exception e) {} break; + case "startquest": + if (sub.length < 2){ + player.yellowMessage("Syntax: !startquest "); + break; + } + + int questid = Integer.parseInt(sub[1]); + + if (player.getQuestStatus(questid) == 0) { + MapleQuest quest = MapleQuest.getInstance(questid); + if (quest != null) { + int npcid = quest.getNpcRequirement(false); + quest.forceStart(player, npcid); + player.dropMessage(5, "QUEST " + questid + " started."); + } else { + player.dropMessage(5, "QUESTID " + questid + " is invalid."); + } + } else { + player.dropMessage(5, "QUESTID " + questid + " already started/completed."); + } + + break; + + case "completequest": + if (sub.length < 2){ + player.yellowMessage("Syntax: !completequest "); + break; + } + + int questId = Integer.parseInt(sub[1]); + + if (player.getQuestStatus(questId) == 1) { + MapleQuest quest = MapleQuest.getInstance(questId); + if (quest != null) { + int npcid = quest.getNpcRequirement(true); + quest.forceComplete(player, npcid); + player.dropMessage(5, "QUEST " + questId + " completed."); + } else { // should not occur + player.dropMessage(5, "QUESTID " + questId + " is invalid."); + } + } else { + player.dropMessage(5, "QUESTID " + questId + " not started or already completed."); + } + + break; + + case "resetquest": + if (sub.length < 2){ + player.yellowMessage("Syntax: !resetquest "); + break; + } + + int questid_ = Integer.parseInt(sub[1]); + + if (player.getQuestStatus(questid_) != 0) { + MapleQuest quest = MapleQuest.getInstance(questid_); + if (quest != null) { + quest.reset(player); + player.dropMessage(5, "QUEST " + questid_ + " reseted."); + } else { // should not occur + player.dropMessage(5, "QUESTID " + questid_ + " is invalid."); + } + } + + break; + default: return false; } diff --git a/src/client/creator/CharacterFactory.java b/src/client/creator/CharacterFactory.java index bbba6d86c2..a5ebb12579 100644 --- a/src/client/creator/CharacterFactory.java +++ b/src/client/creator/CharacterFactory.java @@ -25,6 +25,7 @@ import client.MapleSkinColor; import client.inventory.Item; import client.inventory.MapleInventory; import client.inventory.MapleInventoryType; +import constants.ServerConstants; import net.server.Server; import server.MapleItemInformationProvider; import tools.MaplePacketCreator; @@ -36,6 +37,10 @@ import tools.MaplePacketCreator; public abstract class CharacterFactory { protected synchronized static int createNewCharacter(MapleClient c, String name, int face, int hair, int skin, int gender, CharacterFactoryRecipe recipe) { + if (ServerConstants.COLLECTIVE_CHARSLOT ? c.getAvailableCharacterSlots() <= 0 : c.getAvailableCharacterWorldSlots() <= 0) { + return -3; + } + if (!MapleCharacter.canCreateChar(name)) { return -1; } diff --git a/src/client/inventory/Equip.java b/src/client/inventory/Equip.java index 0e90836fe8..e8395e2819 100644 --- a/src/client/inventory/Equip.java +++ b/src/client/inventory/Equip.java @@ -470,7 +470,7 @@ public class Equip extends Item { lvupStr += "+UPGSLOT "; } - showLevelupMessage(showStr, c); // thx to Polaris dev team ! + showLevelupMessage(showStr, c); // thanks to Polaris dev team ! c.getPlayer().dropMessage(6, lvupStr); c.announce(MaplePacketCreator.showEquipmentLevelUp()); diff --git a/src/constants/ServerConstants.java b/src/constants/ServerConstants.java index a0c0ae08b3..f0362a8401 100644 --- a/src/constants/ServerConstants.java +++ b/src/constants/ServerConstants.java @@ -32,6 +32,7 @@ public class ServerConstants { public static final boolean AUTOMATIC_REGISTER = true; //Automatically register players when they login with a nonexistent username. public static final boolean BCRYPT_MIGRATION = true; //Performs a migration from old SHA-1 and SHA-512 password to bcrypt. + public static final boolean COLLECTIVE_CHARSLOT = false; //Available character slots are contabilized globally rather than per world server. //Ip Configuration public static String HOST; diff --git a/src/net/server/Server.java b/src/net/server/Server.java index 7a573cd5d5..fcf767795b 100644 --- a/src/net/server/Server.java +++ b/src/net/server/Server.java @@ -100,6 +100,7 @@ public class Server { private final Properties subnetInfo = new Properties(); private static Server instance = null; private final Map> accountChars = new HashMap<>(); + private final Map accountCharacterCount = new HashMap<>(); private final Map worldChars = new HashMap<>(); private final Map transitioningChars = new HashMap<>(); private List> worldRecommendedList = new LinkedList<>(); @@ -601,9 +602,7 @@ public class Server { MapleClient c = processDiseaseAnnounceClients.remove(0); MapleCharacter player = c.getPlayer(); if(player != null && player.isLoggedinWorld()) { - for(MapleCharacter chr : player.getMap().getCharacters()) { - chr.announceDiseases(c); - } + player.announceDiseases(); } } @@ -1089,6 +1088,32 @@ public class Server { } } + public short getAccountCharacterCount(Integer accountid) { + lgnRLock.lock(); + try { + return accountCharacterCount.get(accountid); + } finally { + lgnRLock.unlock(); + } + } + + public short getAccountWorldCharacterCount(Integer accountid, Integer worldid) { + lgnRLock.lock(); + try { + short count = 0; + + for(Integer chr : accountChars.get(accountid)) { + if(worldChars.get(chr).equals(worldid)) { + count++; + } + } + + return count; + } finally { + lgnRLock.unlock(); + } + } + private Set getAccountCharacterEntries(Integer accountid) { lgnRLock.lock(); try { @@ -1115,6 +1140,8 @@ public class Server { lgnWLock.lock(); try { + accountCharacterCount.put(accountid, (short)(accountCharacterCount.get(accountid) + 1)); + Set accChars = accountChars.get(accountid); accChars.add(chrid); @@ -1132,6 +1159,8 @@ public class Server { public void deleteCharacterEntry(Integer accountid, Integer chrid) { lgnWLock.lock(); try { + accountCharacterCount.put(accountid, (short)(accountCharacterCount.get(accountid) - 1)); + Set accChars = accountChars.get(accountid); accChars.remove(chrid); @@ -1169,6 +1198,7 @@ public class Server { public void deleteAccountEntry(Integer accountid) { is this even a thing? lgnWLock.lock(); try { + accountCharacterCount.remove(accountid); accountChars.remove(accountid); } finally { lgnWLock.unlock(); @@ -1190,6 +1220,7 @@ public class Server { List wchars = w.getAccountCharactersView(accountId); if(wchars == null) { if(!accountChars.containsKey(accountId)) { + accountCharacterCount.put(accountId, (short) 0); accountChars.put(accountId, new HashSet()); // not advisable at all to write on the map on a read-protected environment } // yet it's known there's no problem since no other point in the source does } else if(!wchars.isEmpty()) { // this action. @@ -1206,7 +1237,8 @@ public class Server { return new Pair<>(new Pair<>(chrTotal, lastwchars), accChars); } - private static List> loadAccountCharactersViewFromDb(int accId, int wlen) { + private static Pair>> loadAccountCharactersViewFromDb(int accId, int wlen) { + short characterCount = 0; List> wchars = new ArrayList<>(wlen); for(int i = 0; i < wlen; i++) wchars.add(i, new LinkedList()); @@ -1231,8 +1263,10 @@ public class Server { ps.setInt(1, accId); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { + characterCount++; + int cworld = rs.getByte("world"); - if(cworld >= wlen) break; + if(cworld >= wlen) continue; if(cworld > curWorld) { wchars.add(curWorld, chars); @@ -1253,7 +1287,7 @@ public class Server { sqle.printStackTrace(); } - return wchars; + return new Pair<>(characterCount, wchars); } public void loadAccountCharacters(MapleClient c) { @@ -1300,10 +1334,13 @@ public class Server { private int loadAccountCharactersView(Integer accId, int gmLevel, int fromWorldid) { // returns the maximum gmLevel found List wlist = this.getWorlds(); - List> accChars = loadAccountCharactersViewFromDb(accId, wlist.size()); + Pair>> accCharacters = loadAccountCharactersViewFromDb(accId, wlist.size()); lgnWLock.lock(); try { + List> accChars = accCharacters.getRight(); + accountCharacterCount.put(accId, accCharacters.getLeft()); + Set chars = accountChars.get(accId); if(chars == null) { chars = new HashSet<>(5); diff --git a/src/net/server/audit/locks/MonitoredLockType.java b/src/net/server/audit/locks/MonitoredLockType.java index 5d4c57b9f3..a12d435f0a 100644 --- a/src/net/server/audit/locks/MonitoredLockType.java +++ b/src/net/server/audit/locks/MonitoredLockType.java @@ -75,6 +75,7 @@ public enum MonitoredLockType { EM_LOBBY, EM_QUEUE, EM_SCHDL, + EM_START, CASHSHOP, VISITOR_PSHOP, STORAGE, diff --git a/src/net/server/channel/handlers/OwlWarpHandler.java b/src/net/server/channel/handlers/OwlWarpHandler.java index 318cc9530b..f8be4608e0 100644 --- a/src/net/server/channel/handlers/OwlWarpHandler.java +++ b/src/net/server/channel/handlers/OwlWarpHandler.java @@ -37,6 +37,11 @@ public final class OwlWarpHandler extends AbstractMaplePacketHandler { int ownerid = slea.readInt(); int mapid = slea.readInt(); + if(ownerid == c.getPlayer().getId()) { + c.announce(MaplePacketCreator.serverNotice(1, "You cannot visit your own shop.")); + return; + } + MapleHiredMerchant hm = c.getWorldServer().getHiredMerchant(ownerid); // if both hired merchant and player shop is on the same map MaplePlayerShop ps; if(hm == null || hm.getMapId() != mapid || !hm.hasItem(c.getPlayer().getOwlSearch())) { diff --git a/src/net/server/channel/handlers/PlayerLoggedinHandler.java b/src/net/server/channel/handlers/PlayerLoggedinHandler.java index 88a9e8cc35..f8b5ac5673 100644 --- a/src/net/server/channel/handlers/PlayerLoggedinHandler.java +++ b/src/net/server/channel/handlers/PlayerLoggedinHandler.java @@ -27,6 +27,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.Map.Entry; import net.AbstractMaplePacketHandler; import net.server.PlayerBuffValueHolder; @@ -90,6 +91,7 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler { e.printStackTrace(); } } else { + c.setCharacterSlots((byte) player.getClient().getCharacterSlots()); player.newClient(c); } if (player == null) { //If you are still getting null here then please just uninstall the game >.>, we dont need you fucking with the logs @@ -275,16 +277,6 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler { c.announce(MaplePacketCreator.enableReport()); player.changeSkillLevel(SkillFactory.getSkill(10000000 * player.getJobType() + 12), (byte) (player.getLinkedLevel() / 10), 20, -1); player.checkBerserk(player.isHidden()); - player.buffExpireTask(); - player.diseaseExpireTask(); - player.skillCooldownTask(); - player.expirationTask(); - player.questExpirationTask(); - if (GameConstants.hasSPTable(player.getJob()) && player.getJob().getId() != 2001) { - player.createDragon(); - } - - player.commitExcludedItems(); if (newcomer){ /* @@ -295,12 +287,31 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler { if (player.isGM()){ Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.earnTitleMessage((player.gmLevel() < 6 ? "GM " : "Admin ") + player.getName() + " has logged in")); } + + if(diseases != null) { + for(Entry> e : diseases.entrySet()) { + final List> debuff = Collections.singletonList(new Pair<>(e.getKey(), Integer.valueOf(e.getValue().getRight().getX()))); + c.announce(MaplePacketCreator.giveDebuff(debuff, e.getValue().getRight())); + } + + player.announceDiseases(); + } } else { if(player.isRidingBattleship()) { player.announceBattleshipHp(); } } + player.buffExpireTask(); + player.diseaseExpireTask(); + player.skillCooldownTask(); + player.expirationTask(); + player.questExpirationTask(); + if (GameConstants.hasSPTable(player.getJob()) && player.getJob().getId() != 2001) { + player.createDragon(); + } + + player.commitExcludedItems(); showDueyNotification(c, player); if (player.getMap().getHPDec() > 0) player.resetHpDecreaseTask(); diff --git a/src/net/server/channel/handlers/ScriptedItemHandler.java b/src/net/server/channel/handlers/ScriptedItemHandler.java index 5882997569..10e4df9563 100644 --- a/src/net/server/channel/handlers/ScriptedItemHandler.java +++ b/src/net/server/channel/handlers/ScriptedItemHandler.java @@ -39,8 +39,8 @@ public final class ScriptedItemHandler extends AbstractMaplePacketHandler { @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); - slea.readInt(); // trash stamp (thx rmzero) - short itemSlot = slea.readShort(); // item sl0t (thx rmzero) + slea.readInt(); // trash stamp (thanks rmzero) + short itemSlot = slea.readShort(); // item slot (thanks rmzero) int itemId = slea.readInt(); // itemId scriptedItem info = ii.getScriptedItemInfo(itemId); if (info == null) return; diff --git a/src/net/server/channel/handlers/ScrollHandler.java b/src/net/server/channel/handlers/ScrollHandler.java index d11d92438b..ee5db198be 100644 --- a/src/net/server/channel/handlers/ScrollHandler.java +++ b/src/net/server/channel/handlers/ScrollHandler.java @@ -34,6 +34,7 @@ import client.inventory.ModifyInventory; import constants.ItemConstants; import java.util.ArrayList; import java.util.List; +import java.util.Map; import net.AbstractMaplePacketHandler; import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; @@ -72,10 +73,17 @@ public final class ScrollHandler extends AbstractMaplePacketHandler { Item scroll = useInventory.getItem(slot); Item wscroll = null; - if (((Equip) toScroll).getUpgradeSlots() < 1 && !ItemConstants.isCleanSlate(scroll.getItemId())) { + if (ItemConstants.isCleanSlate(scroll.getItemId())) { + Map eqStats = ii.getEquipStats(scroll.getItemId()); + if (eqStats == null || eqStats.get("tuc") == 0) { + c.announce(MaplePacketCreator.getInventoryFull()); + return; + } + } else if (((Equip) toScroll).getUpgradeSlots() < 1) { c.announce(MaplePacketCreator.getInventoryFull()); return; } + List scrollReqs = ii.getScrollReqs(scroll.getItemId()); if (scrollReqs.size() > 0 && !scrollReqs.contains(toScroll.getItemId())) { c.announce(MaplePacketCreator.getInventoryFull()); diff --git a/src/net/server/channel/handlers/WhisperHandler.java b/src/net/server/channel/handlers/WhisperHandler.java index 2920b7ce03..90449fab77 100644 --- a/src/net/server/channel/handlers/WhisperHandler.java +++ b/src/net/server/channel/handlers/WhisperHandler.java @@ -27,7 +27,6 @@ import java.sql.SQLException; import net.AbstractMaplePacketHandler; import net.server.world.World; -import tools.LogHelper; import tools.DatabaseConnection; import tools.FilePrinter; import tools.MaplePacketCreator; @@ -42,7 +41,8 @@ import java.sql.Connection; * @author Matze */ public final class WhisperHandler extends AbstractMaplePacketHandler { - + + @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { byte mode = slea.readByte(); if (mode == 6) { // whisper @@ -92,7 +92,7 @@ public final class WhisperHandler extends AbstractMaplePacketHandler { } else { c.announce(MaplePacketCreator.getFindReply(victim.getName(), victim.getMap().getId(), 1)); } - } else { // not found + } else if (c.getPlayer().gmLevel() > 1) { // not found try { Connection con = DatabaseConnection.getConnection(); PreparedStatement ps = con.prepareStatement("SELECT gm FROM characters WHERE name = ?"); @@ -116,6 +116,8 @@ public final class WhisperHandler extends AbstractMaplePacketHandler { } catch (SQLException e) { e.printStackTrace(); } + } else { + c.announce(MaplePacketCreator.getWhisperReply(recipient, (byte) 0)); } } else if (mode == 0x44) { //Buddy find? diff --git a/src/net/server/guild/MapleGuild.java b/src/net/server/guild/MapleGuild.java index f5039a0438..6bd23b913d 100644 --- a/src/net/server/guild/MapleGuild.java +++ b/src/net/server/guild/MapleGuild.java @@ -117,14 +117,16 @@ public class MapleGuild { return; } Set chs = Server.getInstance().getOpenChannels(world); - if (notifications.keySet().size() != chs.size()) { - notifications.clear(); - for (Integer ch : chs) { - notifications.put(ch, new LinkedList()); - } - } else { - for (List l : notifications.values()) { - l.clear(); + synchronized (notifications) { + if (notifications.keySet().size() != chs.size()) { + notifications.clear(); + for (Integer ch : chs) { + notifications.put(ch, new LinkedList()); + } + } else { + for (List l : notifications.values()) { + l.clear(); + } } } @@ -134,7 +136,11 @@ public class MapleGuild { if (!mgc.isOnline()) { continue; } - List chl = notifications.get(mgc.getChannel()); + + List chl; + synchronized (notifications) { + chl = notifications.get(mgc.getChannel()); + } if (chl != null) chl.add(mgc.getId()); //Unable to connect to Channel... error was here } @@ -279,26 +285,31 @@ public class MapleGuild { } public void broadcast(final byte[] packet, int exceptionId, BCOp bcop) { - synchronized (notifications) { - if (bDirty) { - buildNotifications(); - } - try { - for (Integer b : Server.getInstance().getOpenChannels(world)) { - if (notifications.get(b).size() > 0) { - if (bcop == BCOp.DISBAND) { - Server.getInstance().getWorld(world).setGuildAndRank(notifications.get(b), 0, 5, exceptionId); - } else if (bcop == BCOp.EMBLEMCHANGE) { - Server.getInstance().getWorld(world).changeEmblem(this.id, notifications.get(b), new MapleGuildSummary(this)); - } else { - Server.getInstance().getWorld(world).sendPacket(notifications.get(b), packet, exceptionId); + membersLock.lock(); // membersLock awareness thanks to ProjectNano dev team + try { + synchronized (notifications) { + if (bDirty) { + buildNotifications(); + } + try { + for (Integer b : Server.getInstance().getOpenChannels(world)) { + if (notifications.get(b).size() > 0) { + if (bcop == BCOp.DISBAND) { + Server.getInstance().getWorld(world).setGuildAndRank(notifications.get(b), 0, 5, exceptionId); + } else if (bcop == BCOp.EMBLEMCHANGE) { + Server.getInstance().getWorld(world).changeEmblem(this.id, notifications.get(b), new MapleGuildSummary(this)); + } else { + Server.getInstance().getWorld(world).sendPacket(notifications.get(b), packet, exceptionId); + } } } + } catch (Exception re) { + re.printStackTrace(); + System.out.println("Failed to contact channel(s) for broadcast.");//fu? } - } catch (Exception re) { - re.printStackTrace(); - System.out.println("Failed to contact channel(s) for broadcast.");//fu? } + } finally { + membersLock.unlock(); } } diff --git a/src/scripting/event/EventInstanceManager.java b/src/scripting/event/EventInstanceManager.java index 8746f822c4..dd5e2a4462 100644 --- a/src/scripting/event/EventInstanceManager.java +++ b/src/scripting/event/EventInstanceManager.java @@ -224,14 +224,18 @@ public class EventInstanceManager { } - public void registerPlayer(MapleCharacter chr) { - if (chr == null || !chr.isLoggedin()){ + public synchronized void registerPlayer(MapleCharacter chr) { + if (chr == null || !chr.isLoggedinWorld() || disposed) { return; } try { wL.lock(); try { + if(chars.containsKey(chr.getId())) { + return; + } + chars.put(chr.getId(), chr); chr.setEventInstance(this); } finally { @@ -249,7 +253,7 @@ public class EventInstanceManager { } } - public void exitPlayer(MapleCharacter chr) { //unused + public void exitPlayer(MapleCharacter chr) { if (chr == null || !chr.isLoggedin()){ return; } @@ -299,7 +303,7 @@ public class EventInstanceManager { sL.unlock(); } } catch (ScriptException | NoSuchMethodException ex) { - Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, null, ex); + Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, "Event '" + em.getName() + "' does not implement scheduledTimeout function.", ex); } } }, time); @@ -324,7 +328,7 @@ public class EventInstanceManager { sL.unlock(); } } catch (ScriptException | NoSuchMethodException ex) { - Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, null, ex); + Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, "Event '" + em.getName() + "' does not implement scheduledTimeout function.", ex); } } }, nextTime); @@ -397,7 +401,7 @@ public class EventInstanceManager { sL.unlock(); } } catch (ScriptException | NoSuchMethodException ex) { - Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, null, ex); + Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, "Event '" + em.getName() + "' does not implement playerUnregistered function.", ex); } wL.lock(); diff --git a/src/scripting/event/EventManager.java b/src/scripting/event/EventManager.java index 3ca2b74ea0..ba20128fd1 100644 --- a/src/scripting/event/EventManager.java +++ b/src/scripting/event/EventManager.java @@ -48,10 +48,14 @@ import server.life.MapleMonster; import server.life.MapleLifeFactory; import server.quest.MapleQuest; -import java.util.List; import java.util.ArrayList; +import java.util.HashSet; import java.util.LinkedList; +import java.util.List; import java.util.Queue; +import java.util.Set; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; import net.server.audit.LockCollector; import net.server.audit.locks.MonitoredLockType; import net.server.audit.locks.MonitoredReentrantLock; @@ -74,12 +78,16 @@ public class EventManager { private final Map queuedGuildLeaders = new HashMap<>(); private List openedLobbys; private List readyInstances = new LinkedList<>(); - private Integer readyId = 0; + private Integer readyId = 0, onLoadInstances = 0; private Properties props = new Properties(); private String name; private MonitoredReentrantLock lobbyLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.EM_LOBBY); private MonitoredReentrantLock queueLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.EM_QUEUE); - + private MonitoredReentrantLock startLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.EM_START); + + private Set playerPermit = new HashSet<>(); + private Semaphore startSemaphore = new Semaphore(7); + private static final int maxLobbys = 8; // an event manager holds up to this amount of concurrent lobbys public EventManager(Channel cserv, Invocable iv, String name) { @@ -114,6 +122,7 @@ public class EventManager { try { readyEims = new ArrayList<>(readyInstances); readyInstances.clear(); + onLoadInstances = Integer.MIN_VALUE / 2; } finally { queueLock.unlock(); } @@ -143,6 +152,7 @@ public class EventManager { private void emptyLocks() { lobbyLock = lobbyLock.dispose(); queueLock = queueLock.dispose(); + startLock = startLock.dispose(); } private static List convertToIntegerArray(List list) { @@ -333,33 +343,50 @@ public class EventManager { //Expedition method: starts an expedition public boolean startInstance(int lobbyId, MapleExpedition exped, MapleCharacter leader) { try { - if(lobbyId == -1) { - lobbyId = availableLobbyInstance(); - if(lobbyId == -1) return false; - } - else { - if(!startLobbyInstance(lobbyId)) return false; - } - - EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", leader.getClient().getChannel())); - if(eim == null) { - if(lobbyId > -1) { - setLockLobby(lobbyId, false); + if(!playerPermit.contains(leader.getId()) && startSemaphore.tryAcquire(7777, TimeUnit.MILLISECONDS)) { + playerPermit.add(leader.getId()); + + startLock.lock(); + try { + try { + if(lobbyId == -1) { + lobbyId = availableLobbyInstance(); + if(lobbyId == -1) return false; + } + else { + if(!startLobbyInstance(lobbyId)) return false; + } + + EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", leader.getClient().getChannel())); + if(eim == null) { + if(lobbyId > -1) { + setLockLobby(lobbyId, false); + } + return false; + } + instanceLocks.put(eim.getName(), lobbyId); + eim.setLeader(leader); + + exped.start(); + eim.registerExpedition(exped); + + eim.startEvent(); + } catch (ScriptException | NoSuchMethodException ex) { + Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, null, ex); + } + + return true; + } finally { + startLock.unlock(); + playerPermit.remove(leader.getId()); + startSemaphore.release(); } - return false; } - instanceLocks.put(eim.getName(), lobbyId); - eim.setLeader(leader); - - exped.start(); - eim.registerExpedition(exped); - - eim.startEvent(); - } catch (ScriptException | NoSuchMethodException ex) { - Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, null, ex); + } catch(InterruptedException ie) { + playerPermit.remove(leader.getId()); } - return true; + return false; } //Regular method: player @@ -373,36 +400,53 @@ public class EventManager { public boolean startInstance(int lobbyId, MapleCharacter chr, MapleCharacter leader, int difficulty) { try { - if(lobbyId == -1) { - lobbyId = availableLobbyInstance(); - if(lobbyId == -1) { - return false; + if(!playerPermit.contains(leader.getId()) && startSemaphore.tryAcquire(7777, TimeUnit.MILLISECONDS)) { + playerPermit.add(leader.getId()); + + startLock.lock(); + try { + try { + if(lobbyId == -1) { + lobbyId = availableLobbyInstance(); + if(lobbyId == -1) { + return false; + } + } + else { + if(!startLobbyInstance(lobbyId)) { + return false; + } + } + + EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", difficulty, (lobbyId > -1) ? lobbyId : leader.getId())); + if(eim == null) { + if(lobbyId > -1) { + setLockLobby(lobbyId, false); + } + return false; + } + instanceLocks.put(eim.getName(), lobbyId); + eim.setLeader(leader); + + if(chr != null) eim.registerPlayer(chr); + + eim.startEvent(); + } catch (ScriptException | NoSuchMethodException ex) { + Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, null, ex); + } + + return true; + } finally { + startLock.unlock(); + playerPermit.remove(leader.getId()); + startSemaphore.release(); } } - else { - if(!startLobbyInstance(lobbyId)) { - return false; - } - } - - EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", difficulty, (lobbyId > -1) ? lobbyId : leader.getId())); - if(eim == null) { - if(lobbyId > -1) { - setLockLobby(lobbyId, false); - } - return false; - } - instanceLocks.put(eim.getName(), lobbyId); - eim.setLeader(leader); - - if(chr != null) eim.registerPlayer(chr); - - eim.startEvent(); - } catch (ScriptException | NoSuchMethodException ex) { - Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, null, ex); + } catch(InterruptedException ie) { + playerPermit.remove(leader.getId()); } - return true; + return false; } //PQ method: starts a PQ @@ -416,33 +460,50 @@ public class EventManager { public boolean startInstance(int lobbyId, MapleParty party, MapleMap map, MapleCharacter leader) { try { - if(lobbyId == -1) { - lobbyId = availableLobbyInstance(); - if(lobbyId == -1) return false; - } - else { - if(!startLobbyInstance(lobbyId)) return false; - } - - EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", (Object) null)); - if(eim == null) { - if(lobbyId > -1) { - setLockLobby(lobbyId, false); + if(!playerPermit.contains(leader.getId()) && startSemaphore.tryAcquire(7777, TimeUnit.MILLISECONDS)) { + playerPermit.add(leader.getId()); + + startLock.lock(); + try { + try { + if(lobbyId == -1) { + lobbyId = availableLobbyInstance(); + if(lobbyId == -1) return false; + } + else { + if(!startLobbyInstance(lobbyId)) return false; + } + + EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", (Object) null)); + if(eim == null) { + if(lobbyId > -1) { + setLockLobby(lobbyId, false); + } + return false; + } + instanceLocks.put(eim.getName(), lobbyId); + eim.setLeader(leader); + + eim.registerParty(party, map); + party.setEligibleMembers(null); + + eim.startEvent(); + } catch (ScriptException | NoSuchMethodException ex) { + Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, null, ex); + } + + return true; + } finally { + startLock.unlock(); + playerPermit.remove(leader.getId()); + startSemaphore.release(); } - return false; } - instanceLocks.put(eim.getName(), lobbyId); - eim.setLeader(leader); - - eim.registerParty(party, map); - party.setEligibleMembers(null); - - eim.startEvent(); - } catch (ScriptException | NoSuchMethodException ex) { - Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, null, ex); + } catch(InterruptedException ie) { + playerPermit.remove(leader.getId()); } - return true; + return false; } //PQ method: starts a PQ with a difficulty level, requires function setup(difficulty, leaderid) instead of setup() @@ -456,33 +517,50 @@ public class EventManager { public boolean startInstance(int lobbyId, MapleParty party, MapleMap map, int difficulty, MapleCharacter leader) { try { - if(lobbyId == -1) { - lobbyId = availableLobbyInstance(); - if(lobbyId == -1) return false; - } - else { - if(!startLobbyInstance(lobbyId)) return false; - } - - EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", difficulty, (lobbyId > -1) ? lobbyId : party.getLeaderId())); - if(eim == null) { - if(lobbyId > -1) { - setLockLobby(lobbyId, false); + if(!playerPermit.contains(leader.getId()) && startSemaphore.tryAcquire(7777, TimeUnit.MILLISECONDS)) { + playerPermit.add(leader.getId()); + + startLock.lock(); + try { + try { + if(lobbyId == -1) { + lobbyId = availableLobbyInstance(); + if(lobbyId == -1) return false; + } + else { + if(!startLobbyInstance(lobbyId)) return false; + } + + EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", difficulty, (lobbyId > -1) ? lobbyId : party.getLeaderId())); + if(eim == null) { + if(lobbyId > -1) { + setLockLobby(lobbyId, false); + } + return false; + } + instanceLocks.put(eim.getName(), lobbyId); + eim.setLeader(leader); + + eim.registerParty(party, map); + party.setEligibleMembers(null); + + eim.startEvent(); + } catch (ScriptException | NoSuchMethodException ex) { + Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, null, ex); + } + + return true; + } finally { + startLock.unlock(); + playerPermit.remove(leader.getId()); + startSemaphore.release(); } - return false; } - instanceLocks.put(eim.getName(), lobbyId); - eim.setLeader(leader); - - eim.registerParty(party, map); - party.setEligibleMembers(null); - - eim.startEvent(); - } catch (ScriptException | NoSuchMethodException ex) { - Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, null, ex); + } catch(InterruptedException ie) { + playerPermit.remove(leader.getId()); } - return true; + return false; } //non-PQ method for starting instance @@ -500,32 +578,49 @@ public class EventManager { public boolean startInstance(int lobbyId, EventInstanceManager eim, String ldr, MapleCharacter leader) { try { - if(lobbyId == -1) { - lobbyId = availableLobbyInstance(); - if(lobbyId == -1) return false; - } - else { - if(!startLobbyInstance(lobbyId)) return false; - } - - if(eim == null) { - if(lobbyId > -1) { - setLockLobby(lobbyId, false); + if(!playerPermit.contains(leader.getId()) && startSemaphore.tryAcquire(7777, TimeUnit.MILLISECONDS)) { + playerPermit.add(leader.getId()); + + startLock.lock(); + try { + try { + if(lobbyId == -1) { + lobbyId = availableLobbyInstance(); + if(lobbyId == -1) return false; + } + else { + if(!startLobbyInstance(lobbyId)) return false; + } + + if(eim == null) { + if(lobbyId > -1) { + setLockLobby(lobbyId, false); + } + return false; + } + instanceLocks.put(eim.getName(), lobbyId); + eim.setLeader(leader); + + iv.invokeFunction("setup", eim); + eim.setProperty("leader", ldr); + + eim.startEvent(); + } catch (ScriptException | NoSuchMethodException ex) { + Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, null, ex); + } + + return true; + } finally { + startLock.unlock(); + playerPermit.remove(leader.getId()); + startSemaphore.release(); } - return false; } - instanceLocks.put(eim.getName(), lobbyId); - eim.setLeader(leader); - - iv.invokeFunction("setup", eim); - eim.setProperty("leader", ldr); - - eim.startEvent(); - } catch (ScriptException | NoSuchMethodException ex) { - Logger.getLogger(EventManager.class.getName()).log(Level.SEVERE, null, ex); + } catch(InterruptedException ie) { + playerPermit.remove(leader.getId()); } - return true; + return false; } public List getEligibleParty(MapleParty party) { @@ -713,16 +808,31 @@ public class EventManager { } private void instantiateQueuedInstance() { + int nextEventId; queueLock.lock(); try { - if(readyInstances.size() >= Math.ceil((double)maxLobbys / 3.0)) return; + if(onLoadInstances <= -1000 || readyInstances.size() + onLoadInstances >= Math.ceil((double)maxLobbys / 3.0)) return; - readyInstances.add(new EventInstanceManager(this, "sampleName" + readyId)); + onLoadInstances++; + nextEventId = readyId; readyId++; } finally { queueLock.unlock(); } + EventInstanceManager eim = new EventInstanceManager(this, "sampleName" + nextEventId); + queueLock.lock(); + try { + if(onLoadInstances <= -1000) { // EM already disposed + return; + } + + readyInstances.add(eim); + onLoadInstances--; + } finally { + queueLock.unlock(); + } + instantiateQueuedInstance(); // keep filling the queue until reach threshold. } diff --git a/src/scripting/npc/NPCConversationManager.java b/src/scripting/npc/NPCConversationManager.java index 721a59c0fb..69f59294e0 100644 --- a/src/scripting/npc/NPCConversationManager.java +++ b/src/scripting/npc/NPCConversationManager.java @@ -344,7 +344,9 @@ public class NPCConversationManager extends AbstractPlayerInteraction { public void gainCloseness(int closeness) { for (MaplePet pet : getPlayer().getPets()) { - if(pet != null) pet.gainClosenessFullness(getPlayer(), closeness, 0, 0); + if(pet != null) { + pet.gainClosenessFullness(getPlayer(), closeness, 0, 0); + } } } diff --git a/src/server/expeditions/MapleExpedition.java b/src/server/expeditions/MapleExpedition.java index 450b00e56a..3aefc4378a 100644 --- a/src/server/expeditions/MapleExpedition.java +++ b/src/server/expeditions/MapleExpedition.java @@ -38,7 +38,7 @@ import client.MapleCharacter; /** * - * @author SharpAceX(Alan) + * @author Alan (SharpAceX) */ public class MapleExpedition { diff --git a/src/server/expeditions/MapleExpeditionType.java b/src/server/expeditions/MapleExpeditionType.java index eaa664689d..8bc09105e9 100644 --- a/src/server/expeditions/MapleExpeditionType.java +++ b/src/server/expeditions/MapleExpeditionType.java @@ -26,7 +26,7 @@ import constants.ServerConstants; /** * -* @author SharpAceX(Alan) +* @author Alan (SharpAceX) */ public enum MapleExpeditionType { diff --git a/src/server/gachapon/Ellinia.java b/src/server/gachapon/Ellinia.java index c458bc54b1..e053d1a5f5 100644 --- a/src/server/gachapon/Ellinia.java +++ b/src/server/gachapon/Ellinia.java @@ -2,7 +2,7 @@ package server.gachapon; /** * -* @author SharpAceX(Alan) +* @author Alan (SharpAceX) */ public class Ellinia extends GachaponItems { diff --git a/src/server/gachapon/GachaponItems.java b/src/server/gachapon/GachaponItems.java index 6970c46dcb..d850c41997 100644 --- a/src/server/gachapon/GachaponItems.java +++ b/src/server/gachapon/GachaponItems.java @@ -24,7 +24,7 @@ package server.gachapon; /** * -* @author SharpAceX(Alan) +* @author Alan (SharpAceX) */ public abstract class GachaponItems { diff --git a/src/server/gachapon/Global.java b/src/server/gachapon/Global.java index a5df649935..8e28f8f0a5 100644 --- a/src/server/gachapon/Global.java +++ b/src/server/gachapon/Global.java @@ -2,7 +2,7 @@ package server.gachapon; /** * -* @author SharpAceX(Alan) +* @author Alan (SharpAceX) */ public class Global extends GachaponItems { diff --git a/src/server/gachapon/Henesys.java b/src/server/gachapon/Henesys.java index 28f794225b..2b31cccb52 100644 --- a/src/server/gachapon/Henesys.java +++ b/src/server/gachapon/Henesys.java @@ -2,7 +2,7 @@ package server.gachapon; /** * -* @author SharpAceX(Alan) +* @author Alan (SharpAceX) */ public class Henesys extends GachaponItems { diff --git a/src/server/gachapon/KerningCity.java b/src/server/gachapon/KerningCity.java index 74f0e1c8be..26f0f77c32 100644 --- a/src/server/gachapon/KerningCity.java +++ b/src/server/gachapon/KerningCity.java @@ -2,7 +2,7 @@ package server.gachapon; /** * -* @author SharpAceX(Alan) +* @author Alan (SharpAceX) */ public class KerningCity extends GachaponItems { diff --git a/src/server/gachapon/MapleGachapon.java b/src/server/gachapon/MapleGachapon.java index 598866947d..8154bb1790 100644 --- a/src/server/gachapon/MapleGachapon.java +++ b/src/server/gachapon/MapleGachapon.java @@ -25,7 +25,7 @@ import tools.Randomizer; /** * - * @author SharpAceX(Alan) + * @author Alan (SharpAceX) */ public class MapleGachapon { diff --git a/src/server/gachapon/MushroomShrine.java b/src/server/gachapon/MushroomShrine.java index aa5f1d4b72..b34c69c3dd 100644 --- a/src/server/gachapon/MushroomShrine.java +++ b/src/server/gachapon/MushroomShrine.java @@ -2,7 +2,7 @@ package server.gachapon; /** * -* @author SharpAceX(Alan) +* @author Alan (SharpAceX) */ public class MushroomShrine extends GachaponItems { diff --git a/src/server/gachapon/NautilusHarbor.java b/src/server/gachapon/NautilusHarbor.java index 138c54d1ae..9477ac325d 100644 --- a/src/server/gachapon/NautilusHarbor.java +++ b/src/server/gachapon/NautilusHarbor.java @@ -2,7 +2,7 @@ package server.gachapon; /** * -* @author SharpAceX(Alan) +* @author Alan (SharpAceX) */ public class NautilusHarbor extends GachaponItems { diff --git a/src/server/gachapon/NewLeafCity.java b/src/server/gachapon/NewLeafCity.java index 642b0d0775..10bfad789e 100644 --- a/src/server/gachapon/NewLeafCity.java +++ b/src/server/gachapon/NewLeafCity.java @@ -2,7 +2,7 @@ package server.gachapon; /** * -* @author SharpAceX(Alan) +* @author Alan (SharpAceX) */ public class NewLeafCity extends GachaponItems { diff --git a/src/server/gachapon/Perion.java b/src/server/gachapon/Perion.java index ad02155708..a1600c3a5d 100644 --- a/src/server/gachapon/Perion.java +++ b/src/server/gachapon/Perion.java @@ -2,7 +2,7 @@ package server.gachapon; /** * -* @author SharpAceX(Alan) +* @author Alan (SharpAceX) */ public class Perion extends GachaponItems { diff --git a/src/server/gachapon/ShowaSpaFemale.java b/src/server/gachapon/ShowaSpaFemale.java index ceececd25a..ba4a9fcf6d 100644 --- a/src/server/gachapon/ShowaSpaFemale.java +++ b/src/server/gachapon/ShowaSpaFemale.java @@ -2,7 +2,7 @@ package server.gachapon; /** * -* @author SharpAceX(Alan) +* @author Alan (SharpAceX) */ public class ShowaSpaFemale extends GachaponItems { diff --git a/src/server/gachapon/ShowaSpaMale.java b/src/server/gachapon/ShowaSpaMale.java index 45efb9bacd..d55f802cb9 100644 --- a/src/server/gachapon/ShowaSpaMale.java +++ b/src/server/gachapon/ShowaSpaMale.java @@ -2,7 +2,7 @@ package server.gachapon; /** * -* @author SharpAceX(Alan) +* @author Alan (SharpAceX) */ public class ShowaSpaMale extends GachaponItems { diff --git a/src/server/gachapon/Sleepywood.java b/src/server/gachapon/Sleepywood.java index 8de992a56e..0205674bf5 100644 --- a/src/server/gachapon/Sleepywood.java +++ b/src/server/gachapon/Sleepywood.java @@ -2,7 +2,7 @@ package server.gachapon; /** * -* @author SharpAceX(Alan) +* @author Alan (SharpAceX) */ public class Sleepywood extends GachaponItems { diff --git a/src/server/life/MapleMonster.java b/src/server/life/MapleMonster.java index f7bb54ec9d..987acefae9 100644 --- a/src/server/life/MapleMonster.java +++ b/src/server/life/MapleMonster.java @@ -156,7 +156,8 @@ public class MapleMonster extends AbstractLoadedMapleLife { return hp.get(); } - public void addHp(int hp) { + public synchronized void addHp(int hp) { + if(this.hp.get() <= 0) return; this.hp.addAndGet(hp); } @@ -287,6 +288,10 @@ public class MapleMonster extends AbstractLoadedMapleLife { } } + public synchronized void disposeMapObject() { // mob is no longer associated with the map it was in + hp.set(-1); + } + public void broadcastMobHpBar(MapleCharacter from) { if (hasBossHPBar()) { from.setPlayerAggro(this.hashCode()); @@ -307,13 +312,58 @@ public class MapleMonster extends AbstractLoadedMapleLife { } } + public boolean damage(MapleCharacter attacker, int damage, boolean stayAlive) { + boolean lastHit = false; + + this.lockMonster(); + try { + if (!this.isAlive()) { + return false; + } + + /* pyramid not implemented + Pair cool = this.getStats().getCool(); + if (cool != null) { + Pyramid pq = (Pyramid) chr.getPartyQuest(); + if (pq != null) { + if (damage > 0) { + if (damage >= cool.getLeft()) { + if ((Math.random() * 100) < cool.getRight()) { + pq.cool(); + } else { + pq.kill(); + } + } else { + pq.kill(); + } + } else { + pq.miss(); + } + killed = true; + } + } + */ + + if (damage > 0) { + this.applyDamage(attacker, damage, stayAlive); + if (!this.isAlive()) { // monster just died + lastHit = true; + } + } + } finally { + this.unlockMonster(); + } + + return lastHit; + } + /** * * @param from the player that dealt the damage * @param damage * @param stayAlive */ - public synchronized void damage(MapleCharacter from, int damage, boolean stayAlive) { + private void applyDamage(MapleCharacter from, int damage, boolean stayAlive) { Integer trueDamage = applyAndGetHpDamage(damage, stayAlive); if (trueDamage == null) { return; @@ -628,10 +678,8 @@ public class MapleMonster extends AbstractLoadedMapleLife { } } - public synchronized void dispatchMonsterKilled(boolean hasKiller) { - if(!hasKiller) { - dispatchUpdateQuestMobCount(); - } + public void dispatchMonsterKilled(boolean hasKiller) { + processMonsterKilled(hasKiller); EventInstanceManager eim = getMap().getEventInstance(); if (eim != null) { @@ -641,6 +689,12 @@ public class MapleMonster extends AbstractLoadedMapleLife { eim.friendlyKilled(this, hasKiller); } } + } + + private synchronized void processMonsterKilled(boolean hasKiller) { + if(!hasKiller) { + dispatchUpdateQuestMobCount(); + } MonsterListener[] listenersList; statiLock.lock(); @@ -1321,7 +1375,13 @@ public class MapleMonster extends AbstractLoadedMapleLife { } } if (damage > 0) { - damage(chr, damage, true); + lockMonster(); + try { + applyDamage(chr, damage, true); + } finally { + unlockMonster(); + } + if (type == 1) { map.broadcastMessage(MaplePacketCreator.damageMonster(getObjectId(), damage), getPosition()); } else if (type == 2) { diff --git a/src/server/life/SpawnPoint.java b/src/server/life/SpawnPoint.java index caa8b85a42..5ebe8131b0 100644 --- a/src/server/life/SpawnPoint.java +++ b/src/server/life/SpawnPoint.java @@ -24,6 +24,7 @@ package server.life; import client.MapleCharacter; import java.awt.Point; import java.util.concurrent.atomic.AtomicInteger; +import net.server.Server; public class SpawnPoint { private int monster, mobTime, team, fh, f; @@ -42,7 +43,7 @@ public class SpawnPoint { this.f = monster.getF(); this.immobile = immobile; this.mobInterval = mobInterval; - this.nextPossibleSpawn = System.currentTimeMillis(); + this.nextPossibleSpawn = Server.getInstance().getCurrentTime(); } public int getSpawned() { @@ -61,7 +62,7 @@ public class SpawnPoint { if (denySpawn || mobTime < 0 || spawnedMonsters.get() > 0) { return false; } - return nextPossibleSpawn <= System.currentTimeMillis(); + return nextPossibleSpawn <= Server.getInstance().getCurrentTime(); } public boolean shouldForceSpawn() { @@ -82,7 +83,7 @@ public class SpawnPoint { mob.addListener(new MonsterListener() { @Override public void monsterKilled(int aniTime) { - nextPossibleSpawn = System.currentTimeMillis(); + nextPossibleSpawn = Server.getInstance().getCurrentTime(); if (mobTime > 0) { nextPossibleSpawn += mobTime * 1000; } else { @@ -98,7 +99,7 @@ public class SpawnPoint { public void monsterHealed(int trueHeal) {} }); if (mobTime == 0) { - nextPossibleSpawn = System.currentTimeMillis() + mobInterval; + nextPossibleSpawn = Server.getInstance().getCurrentTime() + mobInterval; } return mob; } diff --git a/src/server/maps/MapleMap.java b/src/server/maps/MapleMap.java index bb030829a3..f59bcd3e83 100644 --- a/src/server/maps/MapleMap.java +++ b/src/server/maps/MapleMap.java @@ -295,28 +295,21 @@ public class MapleMap { public int getTimeLeft() { return (int) ((timeLimit - System.currentTimeMillis()) / 1000); } - + public void setReactorState() { - chrRLock.lock(); - objectRLock.lock(); - try { - for (MapleMapObject o : mapobjects.values()) { - if (o.getType() == MapleMapObjectType.REACTOR) { - if (((MapleReactor) o).getState() < 1) { - MapleReactor mr = (MapleReactor) o; - mr.lockReactor(); - try { - mr.resetReactorActions(1); - broadcastMessage(MaplePacketCreator.triggerReactor((MapleReactor) o, 1)); - } finally { - mr.unlockReactor(); - } + for (MapleMapObject o : getMapObjects()) { + if (o.getType() == MapleMapObjectType.REACTOR) { + if (((MapleReactor) o).getState() < 1) { + MapleReactor mr = (MapleReactor) o; + mr.lockReactor(); + try { + mr.resetReactorActions(1); + broadcastMessage(MaplePacketCreator.triggerReactor((MapleReactor) o, 1)); + } finally { + mr.unlockReactor(); } } } - } finally { - objectRLock.unlock(); - chrRLock.unlock(); } } @@ -1191,45 +1184,7 @@ public class MapleMap { } } if (monster.isAlive()) { - boolean killed = false; - monster.lockMonster(); - try { - if (!monster.isAlive()) { - return false; - } - - /* pyramid not implemented - Pair cool = monster.getStats().getCool(); - if (cool != null) { - Pyramid pq = (Pyramid) chr.getPartyQuest(); - if (pq != null) { - if (damage > 0) { - if (damage >= cool.getLeft()) { - if ((Math.random() * 100) < cool.getRight()) { - pq.cool(); - } else { - pq.kill(); - } - } else { - pq.kill(); - } - } else { - pq.miss(); - } - killed = true; - } - } - */ - - if (damage > 0) { - monster.damage(chr, damage, false); - if (!monster.isAlive()) { // monster just died - killed = true; - } - } - } finally { - monster.unlockMonster(); - } + boolean killed = monster.damage(chr, damage, false); if (monster.getStats().selfDestruction() != null && monster.getStats().selfDestruction().getHp() > -1) {// should work ;p if (monster.getHp() <= monster.getStats().selfDestruction().getHp()) { killMonster(monster, chr, true, monster.getStats().selfDestruction().getAction()); @@ -1268,6 +1223,23 @@ public class MapleMap { Server.getInstance().getWorld(world).dropMessage(6, "[VICTORY] In a swift stroke of sorts, the crew that has attempted Pink Bean at channel " + channel + " has ultimately defeated it. The Temple of Time shines radiantly once again, the day finally coming back, as the crew that managed to finally conquer it returns victoriously from the battlefield!!"); } + private boolean removeKilledMonsterObject(MapleMonster monster) { + monster.lockMonster(); + try { + if(monster.getHp() < 0) { + return false; + } + + spawnedMonstersOnMap.decrementAndGet(); + removeMapObject(monster); + monster.disposeMapObject(); + + return true; + } finally { + monster.unlockMonster(); + } + } + public void killMonster(final MapleMonster monster, final MapleCharacter chr, final boolean withDrops) { killMonster(monster, chr, withDrops, 1); } @@ -1276,93 +1248,89 @@ public class MapleMap { if(monster == null) return; if (chr == null) { - spawnedMonstersOnMap.decrementAndGet(); - monster.setHpZero(); - removeMapObject(monster); - - monster.dispatchMonsterKilled(false); - broadcastMessage(MaplePacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition()); - return; - } - if (monster.getStats().getLevel() >= chr.getLevel() + 30 && !chr.isGM()) { - AutobanFactory.GENERAL.alert(chr, " for killing a " + monster.getName() + " which is over 30 levels higher."); - } - /*if (chr.getQuest(MapleQuest.getInstance(29400)).getStatus().equals(MapleQuestStatus.Status.STARTED)) { - if (chr.getLevel() >= 120 && monster.getStats().getLevel() >= 120) { - //FIX MEDAL SHET - } else if (monster.getStats().getLevel() >= chr.getLevel()) { - } - }*/ - int buff = monster.getBuffToGive(); - if (buff > -1) { - MapleItemInformationProvider mii = MapleItemInformationProvider.getInstance(); - for (MapleMapObject mmo : this.getPlayers()) { - MapleCharacter character = (MapleCharacter) mmo; - if (character.isAlive()) { - MapleStatEffect statEffect = mii.getItemEffect(buff); - character.getClient().announce(MaplePacketCreator.showOwnBuffEffect(buff, 1)); - broadcastMessage(character, MaplePacketCreator.showBuffeffect(character.getId(), buff, 1), false); - statEffect.applyTo(character); - } + if(removeKilledMonsterObject(monster)) { + monster.dispatchMonsterKilled(false); + broadcastMessage(MaplePacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition()); } - } - - spawnedMonstersOnMap.decrementAndGet(); - monster.setHpZero(); - removeMapObject(monster); - - if (monster.getCP() > 0 && chr.getCarnival() != null) { - chr.getCarnivalParty().addCP(chr, monster.getCP()); - chr.announce(MaplePacketCreator.updateCP(chr.getCP(), chr.getObtainedCP())); - broadcastMessage(MaplePacketCreator.updatePartyCP(chr.getCarnivalParty())); - //they drop items too ): - } - if (monster.getId() >= 8800003 && monster.getId() <= 8800010) { - boolean makeZakReal = true; - Collection objects = getMapObjects(); - for (MapleMapObject object : objects) { - MapleMonster mons = getMonsterByOid(object.getObjectId()); - if (mons != null) { - if (mons.getId() >= 8800003 && mons.getId() <= 8800010) { - makeZakReal = false; - break; - } + } else { + if(removeKilledMonsterObject(monster)) { + if (monster.getStats().getLevel() >= chr.getLevel() + 30 && !chr.isGM()) { + AutobanFactory.GENERAL.alert(chr, " for killing a " + monster.getName() + " which is over 30 levels higher."); } - } - if (makeZakReal) { - MapleMap map = chr.getMap(); - - for (MapleMapObject object : objects) { - MapleMonster mons = map.getMonsterByOid(object.getObjectId()); - if (mons != null) { - if (mons.getId() == 8800000) { - makeMonsterReal(mons); - updateMonsterController(mons); - break; + /*if (chr.getQuest(MapleQuest.getInstance(29400)).getStatus().equals(MapleQuestStatus.Status.STARTED)) { + if (chr.getLevel() >= 120 && monster.getStats().getLevel() >= 120) { + //FIX MEDAL SHET + } else if (monster.getStats().getLevel() >= chr.getLevel()) { + } + }*/ + int buff = monster.getBuffToGive(); + if (buff > -1) { + MapleItemInformationProvider mii = MapleItemInformationProvider.getInstance(); + for (MapleMapObject mmo : this.getPlayers()) { + MapleCharacter character = (MapleCharacter) mmo; + if (character.isAlive()) { + MapleStatEffect statEffect = mii.getItemEffect(buff); + character.getClient().announce(MaplePacketCreator.showOwnBuffEffect(buff, 1)); + broadcastMessage(character, MaplePacketCreator.showBuffeffect(character.getId(), buff, 1), false); + statEffect.applyTo(character); } } } - } - } - - MapleCharacter dropOwner = monster.killBy(chr); - if (withDrops && !monster.dropsDisabled()) { - if (dropOwner == null) { - dropOwner = chr; - } - dropFromMonster(dropOwner, monster, false); - } - - if (monster.hasBossHPBar()) { - for(MapleCharacter mc : this.getAllPlayers()) { - if(mc.getTargetHpBarHash() == monster.hashCode()) { - mc.resetPlayerAggro(); + + if (monster.getCP() > 0 && chr.getCarnival() != null) { + chr.getCarnivalParty().addCP(chr, monster.getCP()); + chr.announce(MaplePacketCreator.updateCP(chr.getCP(), chr.getObtainedCP())); + broadcastMessage(MaplePacketCreator.updatePartyCP(chr.getCarnivalParty())); + //they drop items too ): } + if (monster.getId() >= 8800003 && monster.getId() <= 8800010) { + boolean makeZakReal = true; + Collection objects = getMapObjects(); + for (MapleMapObject object : objects) { + MapleMonster mons = getMonsterByOid(object.getObjectId()); + if (mons != null) { + if (mons.getId() >= 8800003 && mons.getId() <= 8800010) { + makeZakReal = false; + break; + } + } + } + if (makeZakReal) { + MapleMap map = chr.getMap(); + + for (MapleMapObject object : objects) { + MapleMonster mons = map.getMonsterByOid(object.getObjectId()); + if (mons != null) { + if (mons.getId() == 8800000) { + makeMonsterReal(mons); + updateMonsterController(mons); + break; + } + } + } + } + } + + MapleCharacter dropOwner = monster.killBy(chr); + if (withDrops && !monster.dropsDisabled()) { + if (dropOwner == null) { + dropOwner = chr; + } + dropFromMonster(dropOwner, monster, false); + } + + if (monster.hasBossHPBar()) { + for(MapleCharacter mc : this.getAllPlayers()) { + if(mc.getTargetHpBarHash() == monster.hashCode()) { + mc.resetPlayerAggro(); + } + } + } + + monster.dispatchMonsterKilled(true); + broadcastMessage(MaplePacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition()); } } - - monster.dispatchMonsterKilled(true); - broadcastMessage(MaplePacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition()); } public void killFriendlies(MapleMonster mob) { @@ -1407,11 +1375,9 @@ public class MapleMap { continue; } - spawnedMonstersOnMap.decrementAndGet(); - monster.setHpZero(); - removeMapObject(monster); - - monster.dispatchMonsterKilled(false); + if(removeKilledMonsterObject(monster)) { + monster.dispatchMonsterKilled(false); + } } } @@ -2228,33 +2194,31 @@ public class MapleMap { int reactItem = reactProp.getLeft(), reactQty = reactProp.getRight(); Rectangle reactArea = react.getArea(); - List list = new ArrayList<>(); + List list; objectRLock.lock(); try { - for(MapleMapItem mmi : droppedItems.keySet()) { - mmi.lockItem(); - try { - if(!mmi.isPickedUp()) { - list.add(mmi); - } - } finally { - mmi.unlockItem(); - } - } + list = new ArrayList<>(droppedItems.keySet()); } finally { objectRLock.unlock(); } for(final MapleMapItem drop : list) { - final Item item = drop.getItem(); - - if (item != null && reactItem == item.getItemId() && reactQty == item.getQuantity()) { - if (reactArea.contains(drop.getPosition())) { - MapleClient owner = drop.getOwnerClient(); - if(owner != null) { - registerMapSchedule(new ActivateItemReactor(drop, react, owner), 5000); + drop.lockItem(); + try { + if(!drop.isPickedUp()) { + final Item item = drop.getItem(); + + if (item != null && reactItem == item.getItemId() && reactQty == item.getQuantity()) { + if (reactArea.contains(drop.getPosition())) { + MapleClient owner = drop.getOwnerClient(); + if(owner != null) { + registerMapSchedule(new ActivateItemReactor(drop, react, owner), 5000); + } + } } } + } finally { + drop.unlockItem(); } } } @@ -2365,7 +2329,7 @@ public class MapleMap { try { characters.add(chr); chrSize = characters.size(); - + addPartyMemberInternal(chr); itemMonitorTimeout = 1; } finally { diff --git a/src/server/maps/MapleMiniDungeonInfo.java b/src/server/maps/MapleMiniDungeonInfo.java index 16b3bdebd1..853a038c1e 100644 --- a/src/server/maps/MapleMiniDungeonInfo.java +++ b/src/server/maps/MapleMiniDungeonInfo.java @@ -23,7 +23,7 @@ package server.maps; /** * - * @author SharpAceX(Alan) + * @author Alan (SharpAceX) */ public enum MapleMiniDungeonInfo { diff --git a/src/server/partyquest/MonsterCarnivalParty.java b/src/server/partyquest/MonsterCarnivalParty.java index 0f3b8fed2b..f066d74740 100644 --- a/src/server/partyquest/MonsterCarnivalParty.java +++ b/src/server/partyquest/MonsterCarnivalParty.java @@ -7,7 +7,7 @@ import server.maps.MapleMap; import tools.MaplePacketCreator; /** - * @author Rob //Thanks :3 - LOST MOTIVATION >=( + * @author Rob */ public class MonsterCarnivalParty { diff --git a/src/server/quest/MapleQuest.java b/src/server/quest/MapleQuest.java index 07251f9b1b..30895ffa6e 100644 --- a/src/server/quest/MapleQuest.java +++ b/src/server/quest/MapleQuest.java @@ -540,6 +540,17 @@ public class MapleQuest { return medalid != null ? medalid : -1; } + public int getNpcRequirement(boolean complete) { + Map reqs = !complete ? startReqs : completeReqs; + + MapleQuestRequirement mqr = reqs.get(MapleQuestRequirementType.NPC); + if (mqr != null) { + return ((NpcRequirement) mqr).get(); + } else { + return -1; + } + } + public static void loadAllQuest() { questInfo = questData.getData("QuestInfo.img"); questReq = questData.getData("Check.img"); diff --git a/src/server/quest/requirements/NpcRequirement.java b/src/server/quest/requirements/NpcRequirement.java index b822e20c6b..1e7317d05f 100644 --- a/src/server/quest/requirements/NpcRequirement.java +++ b/src/server/quest/requirements/NpcRequirement.java @@ -49,4 +49,8 @@ public class NpcRequirement extends MapleQuestRequirement { public boolean check(MapleCharacter chr, Integer npcid) { return npcid != null && npcid == reqNPC; } + + public int get() { + return reqNPC; + } } diff --git a/src/server/quest/requirements/PetRequirement.java b/src/server/quest/requirements/PetRequirement.java index 144b212288..e44e9c3111 100644 --- a/src/server/quest/requirements/PetRequirement.java +++ b/src/server/quest/requirements/PetRequirement.java @@ -55,6 +55,8 @@ public class PetRequirement extends MapleQuestRequirement { @Override public boolean check(MapleCharacter chr, Integer npcid) { for(MaplePet pet : chr.getPets()) { + if(pet == null) continue; // thanks Arufonsu for showing a NPE occurring here + if(petIDs.contains(pet.getItemId())) return true; } diff --git a/src/tools/MapleLogger.java b/src/tools/MapleLogger.java index d7f457ba72..389d159523 100644 --- a/src/tools/MapleLogger.java +++ b/src/tools/MapleLogger.java @@ -29,7 +29,7 @@ import client.MapleClient; /** * Logs packets to console and file. * - * @author SharpAceX (Alan) + * @author Alan (SharpAceX) */ public class MapleLogger { diff --git a/src/tools/MaplePacketCreator.java b/src/tools/MaplePacketCreator.java index 9735bd1739..88ce4697d5 100644 --- a/src/tools/MaplePacketCreator.java +++ b/src/tools/MaplePacketCreator.java @@ -863,11 +863,11 @@ public class MaplePacketCreator { List chars = c.loadCharacters(serverId); mplew.write((byte) chars.size()); for (MapleCharacter chr : chars) { - addCharEntry(mplew, chr, false); + addCharEntry(mplew, chr, false); } mplew.write(ServerConstants.ENABLE_PIC ? (c.getPic() == null ? 0 : 1) : 2); - mplew.writeInt(c.getCharacterSlots()); + mplew.writeInt(ServerConstants.COLLECTIVE_CHARSLOT ? chars.size() + c.getAvailableCharacterSlots() : c.getCharacterSlots()); return mplew.getPacket(); } @@ -2812,7 +2812,7 @@ public class MaplePacketCreator { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); mplew.writeShort(SendOpcode.GIVE_FOREIGN_BUFF.getValue()); mplew.writeInt(cid); - mplew.writeLong(MapleBuffStat.MONSTER_RIDING.getValue()); //Thanks? + mplew.writeLong(MapleBuffStat.MONSTER_RIDING.getValue()); mplew.writeLong(0); mplew.writeShort(0); mplew.writeInt(mount.getItemId()); @@ -6079,14 +6079,14 @@ public class MaplePacketCreator { return mplew.getPacket(); } - public static byte[] enableReport() { // by snow + public static byte[] enableReport() { // thanks to snow final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(3); mplew.writeShort(SendOpcode.CLAIM_STATUS_CHANGED.getValue()); mplew.write(1); return mplew.getPacket(); } - public static byte[] giveFinalAttack(int skillid, int time) {//packets found by lailainoob + public static byte[] giveFinalAttack(int skillid, int time) { // packets found thanks to lailainoob final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); mplew.writeShort(SendOpcode.GIVE_BUFF.getValue()); mplew.writeLong(0); diff --git a/wz/Quest.wz/Act.img.xml b/wz/Quest.wz/Act.img.xml index 4dc71e60bb..b179c0e3b5 100644 --- a/wz/Quest.wz/Act.img.xml +++ b/wz/Quest.wz/Act.img.xml @@ -15965,12 +15965,6 @@ - - - - - - @@ -37379,12 +37373,6 @@ - - - - - - diff --git a/wz/Quest.wz/Check.img.xml b/wz/Quest.wz/Check.img.xml index 729f3aff70..cc9c94ab8f 100644 --- a/wz/Quest.wz/Check.img.xml +++ b/wz/Quest.wz/Check.img.xml @@ -32009,6 +32009,28 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -32034,28 +32056,6 @@ - - - - - - - - - - - - - - - - - - - - - - @@ -60894,6 +60894,7 @@ + @@ -62678,30 +62679,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - @@ -62822,6 +62799,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wz/Quest.wz/Say.img.xml b/wz/Quest.wz/Say.img.xml index b2d48aec0f..f1684c735d 100644 --- a/wz/Quest.wz/Say.img.xml +++ b/wz/Quest.wz/Say.img.xml @@ -41215,15 +41215,6 @@ - - - - - - - - -