diff --git a/.gitignore b/.gitignore index 32140893ac..db23594d4c 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,10 @@ /tools/MapleReactorDropFetcher/dist/ /tools/MapleReactorDropFetcher/nbproject/ +/tools/MapleSkillbookStackUpdate/build/ +/tools/MapleSkillbookStackUpdate/dist/ +/tools/MapleSkillbookStackUpdate/nbproject/ + /tools/MapleSkillMakerFetcher/build/ /tools/MapleSkillMakerFetcher/dist/ /tools/MapleSkillMakerFetcher/nbproject/ diff --git a/README.md b/README.md index bcb558c73f..f20ff2a3da 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + ## Head developer: Ronan C. P. Lana @@ -56,7 +56,7 @@ Java7 SDK: https://www.oracle.com/technetwork/java/javase/downloads/java-archive --- ### Open-source client development - HeavenClient -Continuing from where **SYJourney**'s JourneyClient has finished contributions (as of 5 Jul 2016), an open-source delevopment of a software artifact designed to handle both gaming operations and interactions with the server is being conducted. +Continuing from where **SYJourney**'s JourneyClient has finished contributions (as of 5 Jul 2016), an open-source development of a software artifact designed to handle both gaming operations and interactions with the server is being conducted. Newer implementations are being maintained by **頼晏 (ryantpayton)** and aims to offer higher display resolution, bring recent UI contents for the pre-BB gameplay and incremental support on overall gaming perspective. @@ -84,16 +84,20 @@ By taking the v83 MapleStory as the angular stone, incrementally look forward to #### Announcements -HeavenMS development achieved an acceptable state-of-the-art and will get into a halt. A heartfelt thanks for everyone that contributed in some way for the progress of this server! +HeavenMS development as we can see right now achieved an acceptable state-of-the-art. A heartfelt thanks for everyone that contributed in some way for the progress of this server! -Although development is halted, support for fixing features that were implemented here is still up. You can still actively help us improve the server by issuing pull requests with informative details about what's changing. +As development and support for fixing features that were implemented here is still up, with **your** help we can improve it even further! Please help where you can to better the server for everyone. + +Furthermore, you can actively help improving the server by issuing pull requests with informative details about what's changing. + +Note for anyone up to contribute further pull requests: make awareness to use __english language__ in codes and messages, as usage of any other languages will render it open to faculty of whether this content will be ready to be accepted or *further changes are going to be requested* before it becomes apt to merge. + +#### Support HeavenMS If you liked this project, please don't forget to __star__ the repo ;) . 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. -Note for anyone up to contribute further pull requests: make awareness to use __english language__ in codes and messages, as usage of any other languages will render it open to faculty of whether this content will be ready to be accepted or *further changes are going to be requested* before it becomes apt to merge. - Our Discord channel is still available on: https://discord.gg/Q7wKxHX @@ -110,7 +114,7 @@ Paypal: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3K8 * HeavenMS staff has __no current intention__ to publicly open a server with this source, if that ever comes to happen this note will be lifted. __Don't be scammed!__ -* This server source is __NOT intended to be stable__ as is. Proper deadlock review and other maintenance checks are needed in order to make it suitable for production use. +* This server source is __NOT intended to be stable__ as is. Proper deadlock review and other maintenance contributions are needed in order to make it steps ahead on viability. --- ### Preparing the ambient diff --git a/docs/mychanges_ptbr.txt b/docs/mychanges_ptbr.txt index f8373a83fe..bdb3c8686e 100644 --- a/docs/mychanges_ptbr.txt +++ b/docs/mychanges_ptbr.txt @@ -1798,10 +1798,18 @@ Corrigido cooldown de skills de mob não atuando adequadamente. 08 - 09 Abril 2019, Resolvido problema de deadlock envolvendo acesso a valores de stats de jogadores e diversas operações de despacho de update de stats. +Implementado envio de desafio na CPQ utilizando o sistema de matching. +Corrigido LanguageConstants atuando estaticamente para todos os jogadores. Corrigido mob skills que não se encontram disponíveis sendo passados para o cliente para serem usados. Resultado disso era efeito visual de skill sendo mostrado ao usuário, habilidade sem ser aplicada em sequência. +Corrigido portais do stage "" da OPQ levando jogadores incorretamente para a próxima plataforma. +Corrigido portais do stage "" da OPQ retornando jogadores à plataforma inicial, ao invés de retornar a uma plataforma "checkpoint". +Corrigido fonte da EllinPQ não atribuindo corretamente "Altaire Fragment" aos jogadores. +Implementado geração de sequências com relação entre as mesmas, utilizadas no estágio "Lab - Unit" da RnJPQ. 12 Abril 2019, +Corrigido limites de mapas nos eventos de RockSpirit. Corrigido ganho visual do EXP de party ocasionalmente mostrando EXP em amarelo ao jogador em party solo. +Corrigido battleship setando HP inicial menor que o esperado pela skill para jogadores de nível menor que 120. 15 Abril 2019, Iniciado operação de introdução da AriantPQ no fonte, a partir do pull request feito pelo Dragohe4rt. @@ -1817,7 +1825,26 @@ Ajustado drops de mobs, agora sendo buscado na DB. Ajustado diversas mecânicas da AriantPQ, tais como update visual da pontuação de jogadores (ao dropar itens, ganhar itens, acessar mapa de evento), pontos de batalha persistindo na DB, etc. 21 Abril 2019, -Adicionado debug de packets descrito pelo Atoot.Adicionado debug de packets descrito pelo Atoot. +Adicionado debug de packets descrito pelo Atoot. 22 Abril 2019, -Revisado refatoração recente em acesso a valores de stats de jogadores levando a inconsistência nos valores dos mesmos ao colocá-los para rodar em uma nova thread sem proteção concorrente. \ No newline at end of file +Corrigido Fredrick retornando valores negativos de mesos ao retornar espólios de mercantes aos jogadores. +Adicionado informação aprimorada em comando "goto". +Adicionado contagem de quests completadas de mesmo id. +Corrigido problema de visibilidade latente com mobs que já foram derrotados aparecendo num flicker ao realizar certas ações (tal como mudar de mapas). +Nova ferramenta: MapleSkillbookStackUpdate. Livros de skill e melhorias agora levam quantidade de stack padrão (100 unidades). +Protegido concorrentemente adição de itens ao inventário. Checagens de posição e inserção ocorrem atomicamente. +Protegido concorrentemente e revisado módulos de ganho de EXP do Writs of Solomon (gachapon EXP). +Revisado refatoração recente em acesso a valores de stats de jogadores levando a inconsistência nos valores dos mesmos ao colocá-los para rodar em uma nova thread sem proteção concorrente. + +24 - 26 Abril 2019, +Ajustado levemente taxa de respawn de mobs quando não se está usando a flag de respawn completo. +Ajustado ganhos de stats de ataque ao passar de nível weapons, ganha-se menos do stat que não é a afinidade (watk/matk). +Corrigido drops de mesos sendo bloqueados para drops rápidos de mesos, indevidamente dificultando jogabilidade para certas classes. +Corrigido caso de XML parser em MapleSkillBookInformationParser não lidando com terminadores "/>" corretamente. + +01 - 02 Maio 2019, +Corrigido jogadores podendo explorar mecânica de checagem de match, não respondendo ao match e sendo permitido se registrar em um novo sem ter respondido ao anterior. +Corrigido caso de dupe com itens ao serem colocados no storage. +Adicionado sistema de "qualquer NPC scriptável", com apoio do GabrielSin. +Adicionado server flag para checagem de IP's ao logar jogadores. \ No newline at end of file diff --git a/scripts/event/3rdJob_mount.js b/scripts/event/3rdJob_mount.js index b3f4bc0f7a..cd366824ed 100644 --- a/scripts/event/3rdJob_mount.js +++ b/scripts/event/3rdJob_mount.js @@ -92,7 +92,7 @@ function playerEntry(eim, player) { function playerUnregistered(eim, player) {} function playerExit(eim, player) { - var api = player.getClient().getAbstractPlayerInteraction(); + var api = player.getAbstractPlayerInteraction(); api.removeAll(4031507); api.removeAll(4031508); diff --git a/scripts/event/GuildQuest.js b/scripts/event/GuildQuest.js index 8f3c755643..d4d3fa00f8 100644 --- a/scripts/event/GuildQuest.js +++ b/scripts/event/GuildQuest.js @@ -237,7 +237,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, 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().getAbstractPlayerInteraction().npcTalk(9040000, texttt); + player.getAbstractPlayerInteraction().npcTalk(9040000, texttt); } } diff --git a/scripts/event/MK_PrimeMinister.js b/scripts/event/MK_PrimeMinister.js index c61f7b7a0d..01ddf963f9 100644 --- a/scripts/event/MK_PrimeMinister.js +++ b/scripts/event/MK_PrimeMinister.js @@ -52,7 +52,7 @@ function primeMinisterCheck(eim) { var pIter = map.getAllPlayers().iterator(); while (pIter.hasNext()) { var player = pIter.next(); - if (player.getQuestStatus(2333) == 1 && player.getClient().getAbstractPlayerInteraction().getQuestProgress(2333, mobId) == 0) { + if (player.getQuestStatus(2333) == 1 && player.getAbstractPlayerInteraction().getQuestProgress(2333, mobId) == 0) { return true; } } diff --git a/scripts/event/MagatiaPQ_A.js b/scripts/event/MagatiaPQ_A.js index b26b7376ac..312980d2a8 100644 --- a/scripts/event/MagatiaPQ_A.js +++ b/scripts/event/MagatiaPQ_A.js @@ -176,6 +176,33 @@ function shuffle(array) { return array; } +function generateStg6Combo(eim) { // thanks Chloek3, seth1 for stating generated sequences are supposed to be linked + var matrix = []; + + for (var i = 0; i < 4; i++) { + matrix.push([]); + } + + for (var j = 0; j < 10; j++) { + var array = [0, 1, 2, 3]; + array = shuffle(array); + + for (var i = 0; i < 4; i++) { + matrix[i].push(array[i]); + } + } + + for (var i = 0; i < 4; i++) { + var comb = ""; + for(var j = 0; j < 10; j++) { + var r = matrix[i][j]; + comb += r.toString(); + } + + eim.setProperty("stage6_comb" + (i + 1), comb); + } +} + function afterSetup(eim) { eim.setIntProperty("escortFail", 0); // refresh friendly status @@ -390,6 +417,7 @@ function monsterKilled(mob, eim) { eim.showClearEffect(); eim.giveEventPlayersStageReward(5); + generateStg6Combo(eim); map.getReactorByName("jnr6_out").forceHitReactor(1); } } else if(mob.getId() == 9300151 || mob.getId() == 9300152) { diff --git a/scripts/event/MagatiaPQ_Z.js b/scripts/event/MagatiaPQ_Z.js index ca62cb3fcc..fe88d4af3d 100644 --- a/scripts/event/MagatiaPQ_Z.js +++ b/scripts/event/MagatiaPQ_Z.js @@ -176,6 +176,33 @@ function shuffle(array) { return array; } +function generateStg6Combo(eim) { + var matrix = []; + + for (var i = 0; i < 4; i++) { + matrix.push([]); + } + + for (var j = 0; j < 10; j++) { + var array = [0, 1, 2, 3]; + array = shuffle(array); + + for (var i = 0; i < 4; i++) { + matrix[i].push(array[i]); + } + } + + for (var i = 0; i < 4; i++) { + var comb = ""; + for(var j = 0; j < 10; j++) { + var r = matrix[i][j]; + comb += r.toString(); + } + + eim.setProperty("stage6_comb" + (i + 1), comb); + } +} + function afterSetup(eim) { eim.setIntProperty("escortFail", 0); // refresh friendly status @@ -390,6 +417,7 @@ function monsterKilled(mob, eim) { eim.showClearEffect(); eim.giveEventPlayersStageReward(5); + generateStg6Combo(eim); map.getReactorByName("rnj6_out").forceHitReactor(1); } } else if(mob.getId() == 9300139 || mob.getId() == 9300140) { diff --git a/scripts/event/OrbisPQ.js b/scripts/event/OrbisPQ.js index 2d11c2c409..397e205a5b 100644 --- a/scripts/event/OrbisPQ.js +++ b/scripts/event/OrbisPQ.js @@ -180,7 +180,7 @@ function playerEntry(eim, player) { player.changeMap(map, map.getPortal(0)); var texttt = "Hi, my name is Eak, the Chamberlain of the Goddess. Don't be alarmed; you won't be able to see me right now. Back when the Goddess turned into a block of stone, I simultaneously lost my own power. If you gather up the power of the Magic Cloud of Orbis, however, then I'll be able to recover my body and re-transform back to my original self. Please collect #b20#k Magic Clouds and bring them back to me. Right now, you'll only see me as a tiny, flickering light."; - player.getClient().getAbstractPlayerInteraction().npcTalk(2013001, texttt); + player.getAbstractPlayerInteraction().npcTalk(2013001, texttt); } function scheduledTimeout(eim) { diff --git a/scripts/event/RockSpirit.js b/scripts/event/RockSpirit.js index fe043ad251..149ced9bd1 100644 --- a/scripts/event/RockSpirit.js +++ b/scripts/event/RockSpirit.js @@ -21,9 +21,13 @@ importPackage(Packages.tools); -var exitMap; var entryMap; +var exitMap; var otherMap; + +var minMapId = 103040410; +var maxMapId = 103040460; + var minPlayers = 1; var fightTime = 60; var timer = 1000 * 60 * fightTime; @@ -80,7 +84,7 @@ function playerDisconnected(eim, player) { } function changedMap(eim, player, mapid) { - if(mapid == exitMap.getId()) { + if (mapid < minMapId || mapid > maxMapId) { if (eim.isEventTeamLackingNow(true, minPlayers, player)) { eim.unregisterPlayer(player); end(eim); diff --git a/scripts/event/RockSpiritVIP.js b/scripts/event/RockSpiritVIP.js index 4de76839c1..0d2ecba425 100644 --- a/scripts/event/RockSpiritVIP.js +++ b/scripts/event/RockSpiritVIP.js @@ -21,9 +21,13 @@ importPackage(Packages.tools); -var exitMap; var entryMap; +var exitMap; var otherMap; + +var minMapId = 103040410; +var maxMapId = 103040460; + var minPlayers = 1; var fightTime = 30; var timer = 1000 * 60 * fightTime; @@ -80,7 +84,7 @@ function playerDisconnected(eim, player) { } function changedMap(eim, player, mapid) { - if(mapid == exitMap.getId()) { + if (mapid < minMapId || mapid > maxMapId) { if (eim.isEventTeamLackingNow(true, minPlayers, player)) { eim.unregisterPlayer(player); end(eim); diff --git a/scripts/event/WeddingCathedral.js b/scripts/event/WeddingCathedral.js index 7d811fac40..2bf92e039a 100644 --- a/scripts/event/WeddingCathedral.js +++ b/scripts/event/WeddingCathedral.js @@ -110,7 +110,7 @@ function respawnStages(eim) { function playerEntry(eim, player) { eim.setProperty("giftedItemG" + player.getId(), "0"); eim.setProperty("giftedItemB" + player.getId(), "0"); - player.getClient().getAbstractPlayerInteraction().gainItem(4000313, 1); + player.getAbstractPlayerInteraction().gainItem(4000313, 1); var map = eim.getMapInstance(entryMap); player.changeMap(map, map.getPortal(0)); diff --git a/scripts/event/WeddingChapel.js b/scripts/event/WeddingChapel.js index 259e790c32..37a44bd953 100644 --- a/scripts/event/WeddingChapel.js +++ b/scripts/event/WeddingChapel.js @@ -110,7 +110,7 @@ function respawnStages(eim) { function playerEntry(eim, player) { eim.setProperty("giftedItemG" + player.getId(), "0"); eim.setProperty("giftedItemB" + player.getId(), "0"); - player.getClient().getAbstractPlayerInteraction().gainItem(4000313, 1); + player.getAbstractPlayerInteraction().gainItem(4000313, 1); var map = eim.getMapInstance(entryMap); player.changeMap(map, map.getPortal(0)); diff --git a/scripts/npc/2042004.js b/scripts/npc/2042004.js index 84801867a7..d9cf1d2b6e 100644 --- a/scripts/npc/2042004.js +++ b/scripts/npc/2042004.js @@ -8,9 +8,23 @@ function start() { function action(mode, type, selection) { - cm.warpParty(980000000); - cm.cancelCPQLobby(); - cm.dispose(); + if (mode == -1) { + cm.dispose(); + } else { + if (mode == 0 && status == 0) { + cm.dispose(); + return; + } + if (mode == 1) + status++; + else + status--; + if (status == 0) { + cm.warpParty(980000000); + cm.cancelCPQLobby(); + cm.dispose(); + } + } } diff --git a/scripts/npc/2112000.js b/scripts/npc/2112000.js index 552e9c968d..71a0f0755a 100644 --- a/scripts/npc/2112000.js +++ b/scripts/npc/2112000.js @@ -69,9 +69,13 @@ function action(mode, type, selection) { if(state == -1) { cm.sendOk("Heh, it seems you guys have company. Have fun with them, as I politely request my leave."); - } else if (playersTooClose() || eim.getIntProperty("npcShocked") == 0) { + } else if (playersTooClose()) { cm.sendOk("Oh, hello there. I have been #bmonitoring your moves#k since you guys entered this perimeter. Quite the feat reaching here, I commend all of you. Now, now, look at the time, I've got an appointment right now, I'm afraid I will need to request my leave. But worry not, my #raccessors#k will deal with all of you. Now, if you permit me, I'm leaving now."); + eim.setIntProperty("yuleteTalked", -1); + } else if (eim.getIntProperty("npcShocked") == 0) { + cm.sendOk("Ho~ Aren't you quite the sneaky one? Well, it matters not. I have been #bmonitoring your moves#k since you guys entered this perimeter. Quite the feat reaching here, I commend all of you. Now, now, look at the time, I've got an appointment right now, I'm afraid I will need to request my leave. But worry not, my #raccessors#k will deal with all of you. Now, if you permit me, I'm leaving now."); + eim.setIntProperty("yuleteTalked", -1); } else { cm.sendOk("... Hah! What, wh-- How did you get here?! I though I had sealed all paths here! No matter, this situation will be resolved soon. Guys: DEPLOY the #rmaster weapon#k!! You! Yes, you. Don't you think this ends here, look back at your companions, they need some help! I'll be retreating for now."); diff --git a/scripts/npc/2112010.js b/scripts/npc/2112010.js index ea52077fb4..3d6d4cb895 100644 --- a/scripts/npc/2112010.js +++ b/scripts/npc/2112010.js @@ -69,9 +69,13 @@ function action(mode, type, selection) { if(state == -1) { cm.sendOk("Heh, it seems you guys have company. Have fun with them, as I politely request my leave."); - } else if (playersTooClose() || eim.getIntProperty("npcShocked") == 0) { + } else if (playersTooClose()) { cm.sendOk("Oh, hello there. I have been #bmonitoring your moves#k since you guys entered this perimeter. Quite the feat reaching here, I commend all of you. Now, now, look at the time, I've got an appointment right now, I'm afraid I will need to request my leave. But worry not, my #raccessors#k will deal with all of you. Now, if you permit me, I'm leaving now."); + eim.setIntProperty("yuleteTalked", -1); + } else if (eim.getIntProperty("npcShocked") == 0) { + cm.sendOk("Ho~ Aren't you quite the sneaky one? Well, it matters not. I have been #bmonitoring your moves#k since you guys entered this perimeter. Quite the feat reaching here, I commend all of you. Now, now, look at the time, I've got an appointment right now, I'm afraid I will need to request my leave. But worry not, my #raccessors#k will deal with all of you. Now, if you permit me, I'm leaving now."); + eim.setIntProperty("yuleteTalked", -1); } else { cm.sendOk("... Hah! What, wh-- How did you get here?! I though I had sealed all paths here! No matter, this situation will be resolved soon. Guys: DEPLOY the #rmaster weapon#k!! You! Yes, you. Don't you think this ends here, look back at your companions, they need some help! I'll be retreating for now."); diff --git a/scripts/npc/9201002.js b/scripts/npc/9201002.js index aa7e970fee..8a9341aa87 100644 --- a/scripts/npc/9201002.js +++ b/scripts/npc/9201002.js @@ -344,7 +344,7 @@ function action(mode, type, selection) { cm.sendOk("You have already confirmed your vows. All that is left is for your partner to confirm now."); } else { eim.setIntProperty("weddingStage", 3); - var cmPartner = partner.getClient().getAbstractPlayerInteraction(); + var cmPartner = partner.getAbstractPlayerInteraction(); var playerItemId = detectPlayerItemid(player); var partnerItemId = (playerItemId % 2 == 1) ? playerItemId + 1 : playerItemId - 1; diff --git a/scripts/npc/9201005.js b/scripts/npc/9201005.js index 5785d6d2f4..354076b112 100644 --- a/scripts/npc/9201005.js +++ b/scripts/npc/9201005.js @@ -159,7 +159,7 @@ function action(mode, type, selection) { return; } - if(!cm.getUnclaimedMarriageGifts().isEmpty() || !partner.getClient().getAbstractPlayerInteraction().getUnclaimedMarriageGifts().isEmpty()) { + if(!cm.getUnclaimedMarriageGifts().isEmpty() || !partner.getAbstractPlayerInteraction().getUnclaimedMarriageGifts().isEmpty()) { cm.sendOk("Eerhm... I'm sorry, something doesn't seem right according to the Amoria's Wedding Gift Registry reserve. Please check in the situation with #b#p9201014##k."); cm.dispose(); return; @@ -178,7 +178,7 @@ function action(mode, type, selection) { var expirationTime = cserv.getRelativeWeddingTicketExpireTime(resStatus); cm.gainItem(weddingSendTicket,15,false,true,expirationTime); - partner.getClient().getAbstractPlayerInteraction().gainItem(weddingSendTicket,15,false,true,expirationTime); + partner.getAbstractPlayerInteraction().gainItem(weddingSendTicket,15,false,true,expirationTime); var placeTime = cserv.getWeddingReservationTimeLeft(weddingId); diff --git a/scripts/npc/9201007.js b/scripts/npc/9201007.js index 0e879780e8..077c98b2b4 100644 --- a/scripts/npc/9201007.js +++ b/scripts/npc/9201007.js @@ -111,7 +111,7 @@ function action(mode, type, selection) { } else if (status == 1) { var cmPartner; try { - cmPartner = cm.getMap().getCharacterById(cm.getPlayer().getPartnerId()).getClient().getAbstractPlayerInteraction(); + cmPartner = cm.getMap().getCharacterById(cm.getPlayer().getPartnerId()).getAbstractPlayerInteraction(); } catch(err) { cmPartner = null; } diff --git a/scripts/npc/9201008.js b/scripts/npc/9201008.js index e3dee07937..b13337fc78 100644 --- a/scripts/npc/9201008.js +++ b/scripts/npc/9201008.js @@ -159,7 +159,7 @@ function action(mode, type, selection) { return; } - if(!cm.getUnclaimedMarriageGifts().isEmpty() || !partner.getClient().getAbstractPlayerInteraction().getUnclaimedMarriageGifts().isEmpty()) { + if(!cm.getUnclaimedMarriageGifts().isEmpty() || !partner.getAbstractPlayerInteraction().getUnclaimedMarriageGifts().isEmpty()) { cm.sendOk("Eerhm... I'm sorry, something doesn't seem right according to the Amoria's Wedding Gift Registry reserve. Please check in the situation with #b#p9201014##k."); cm.dispose(); return; @@ -178,7 +178,7 @@ function action(mode, type, selection) { var expirationTime = cserv.getRelativeWeddingTicketExpireTime(resStatus); cm.gainItem(weddingSendTicket,15,false,true,expirationTime); - partner.getClient().getAbstractPlayerInteraction().gainItem(weddingSendTicket,15,false,true,expirationTime); + partner.getAbstractPlayerInteraction().gainItem(weddingSendTicket,15,false,true,expirationTime); var placeTime = cserv.getWeddingReservationTimeLeft(weddingId); diff --git a/scripts/npc/9201009.js b/scripts/npc/9201009.js index 601d7489be..815aa0ad92 100644 --- a/scripts/npc/9201009.js +++ b/scripts/npc/9201009.js @@ -111,7 +111,7 @@ function action(mode, type, selection) { } else if (status == 1) { var cmPartner; try { - cmPartner = cm.getMap().getCharacterById(cm.getPlayer().getPartnerId()).getClient().getAbstractPlayerInteraction(); + cmPartner = cm.getMap().getCharacterById(cm.getPlayer().getPartnerId()).getAbstractPlayerInteraction(); } catch(err) { cmPartner = null; } diff --git a/scripts/npc/9201011.js b/scripts/npc/9201011.js index 01fbba44d1..b81902eab3 100644 --- a/scripts/npc/9201011.js +++ b/scripts/npc/9201011.js @@ -214,7 +214,7 @@ function action(mode, type, selection) { cm.sendOk("You have already confirmed your vows. All that is left is for your partner to confirm now."); } else { eim.setIntProperty("weddingStage", 3); - var cmPartner = partner.getClient().getAbstractPlayerInteraction(); + var cmPartner = partner.getAbstractPlayerInteraction(); var playerItemId = detectPlayerItemid(player); var partnerItemId = (playerItemId % 2 == 1) ? playerItemId + 1 : playerItemId - 1; diff --git a/scripts/npc/9977777.js b/scripts/npc/9977777.js index 7692ad766e..ab64b59dad 100644 --- a/scripts/npc/9977777.js +++ b/scripts/npc/9977777.js @@ -47,7 +47,7 @@ function writeFeatureTab_PQs() { addFeature("Brand-new PQs: BossRushPQ, CafePQ."); addFeature("Mu Lung Dojo."); addFeature("Monster Carnival 1 & 2 - thanks Dragohe4rt & Jayd!"); - addFeature("AriantPQ - thanks Dragohe4rt!"); + addFeature("AriantPQ - thanks Dragohe4rt & Jayd!"); addFeature("Capt. Latanica with party fighting the boss."); addFeature("Filled up missing obligatory event script methods."); addFeature("Secured uniquety of active lobby-name instances."); @@ -222,6 +222,7 @@ function writeFeatureTab_Serverpotentials() { addFeature("SP cap past tier-level, recovered after job upgrade."); addFeature("Bypassable PIN/PIC system for authenticated users."); addFeature("Automatic account registration - thanks shavit!"); + addFeature("Any NPC scriptable - thanks GabrielSin!"); } function writeFeatureTab_Commands() { diff --git a/scripts/npc/cpqchallenge.js b/scripts/npc/cpqchallenge.js index ab5f3ff5cf..1be873d469 100644 --- a/scripts/npc/cpqchallenge.js +++ b/scripts/npc/cpqchallenge.js @@ -18,10 +18,12 @@ function start(chrs) { function action(mode, type, selection) { if (mode == -1) { + cm.answerCPQChallenge(false); cm.getChar().setChallenged(false); cm.dispose(); } else { if (mode == 0) { + cm.answerCPQChallenge(false); cm.getChar().setChallenged(false); cm.dispose(); return; @@ -43,16 +45,16 @@ function action(mode, type, selection) { snd += "#bName: " + party.get(i).getName() + " / (Level: " + party.get(i).getLevel() + ") / " + GameConstants.getJobName(party.get(i).getJobId()) + "#k\r\n\r\n"; cm.sendAcceptDecline(snd + "Would you like to fight this party at the Monster Carnival?"); } else { - return; + cm.answerCPQChallenge(false); + cm.getChar().setChallenged(false); + cm.dispose(); } } else if (status == 1) { - var ch = cm.getChrById(party.get(0).getId()); if (party.size() == cm.getParty().getMembers().size()) { - cm.startCPQ(ch, ch.getMapId() + 1); - ch.getParty().setEnemy(cm.getPlayer().getParty()); - cm.getChar().getParty().setEnemy(ch.getParty()); - cm.getChar().setChallenged(false); + cm.answerCPQChallenge(true); } else { + cm.answerCPQChallenge(false); + cm.getChar().setChallenged(false); cm.sendOk("The number of players between the teams is not the same."); } cm.dispose(); diff --git a/scripts/npc/cpqchallenge2.js b/scripts/npc/cpqchallenge2.js index d8454eb4f4..1be873d469 100644 --- a/scripts/npc/cpqchallenge2.js +++ b/scripts/npc/cpqchallenge2.js @@ -18,11 +18,12 @@ function start(chrs) { function action(mode, type, selection) { if (mode == -1) { + cm.answerCPQChallenge(false); cm.getChar().setChallenged(false); cm.dispose(); } else { if (mode == 0) { - cm.sendOk("Come back once you have thought about it some more."); + cm.answerCPQChallenge(false); cm.getChar().setChallenged(false); cm.dispose(); return; @@ -44,14 +45,18 @@ function action(mode, type, selection) { snd += "#bName: " + party.get(i).getName() + " / (Level: " + party.get(i).getLevel() + ") / " + GameConstants.getJobName(party.get(i).getJobId()) + "#k\r\n\r\n"; cm.sendAcceptDecline(snd + "Would you like to fight this party at the Monster Carnival?"); } else { - return; + cm.answerCPQChallenge(false); + cm.getChar().setChallenged(false); + cm.dispose(); } } else if (status == 1) { - var ch = cm.getChrById(party.get(0).getId()); - cm.startCPQ2(ch, ch.getMapId() + 1); - ch.getParty().setEnemy(cm.getPlayer().getParty()); - cm.getChar().getParty().setEnemy(ch.getParty()); - cm.getChar().setChallenged(false); + if (party.size() == cm.getParty().getMembers().size()) { + cm.answerCPQChallenge(true); + } else { + cm.answerCPQChallenge(false); + cm.getChar().setChallenged(false); + cm.sendOk("The number of players between the teams is not the same."); + } cm.dispose(); } } diff --git a/scripts/portal/TD_MC_enterboss2.js b/scripts/portal/TD_MC_enterboss2.js index 066ea6e26e..be984738d4 100644 --- a/scripts/portal/TD_MC_enterboss2.js +++ b/scripts/portal/TD_MC_enterboss2.js @@ -35,7 +35,7 @@ function enter(pi) { return false; } } else { - if (em.startInstance(pi.getPlayer(), pi.getMap())) { + if (em.startInstance(pi.getPlayer())) { // thanks RedHat for noticing an issue here pi.playPortalSound(); return true; } else { @@ -57,7 +57,7 @@ function enter(pi) { return false; } } else { - if (em.startInstance(pi.getPlayer(), pi.getMap())) { + if (em.startInstance(pi.getPlayer())) { pi.playPortalSound(); return true; } else { diff --git a/scripts/portal/party3_r6pt.js b/scripts/portal/party3_r6pt.js index 5498ab4b50..b9d7a2a5d4 100644 --- a/scripts/portal/party3_r6pt.js +++ b/scripts/portal/party3_r6pt.js @@ -43,11 +43,11 @@ function enter(pi) { var pRow = Math.floor(portalId / 10); var pCol = portalId % 10; - if (pCol == parseInt(comb.substring(pRow, pRow + 1), 10)) { //climb pi.playPortalSound(); pi.warp(pi.getMapId(), (pRow % 4 != 0) ? pi.getPortal().getId() + 4 : (pRow / 4)); } else { //fail - pi.playPortalSound(); pi.warp(pi.getMapId(), 5); + pRow--; + pi.playPortalSound(); pi.warp(pi.getMapId(), (pRow / 4) > 1 ? (pRow / 4) : 5); // thanks Chloek3, seth1 for noticing next plaform issues } return true; diff --git a/scripts/portal/party6_stage800.js b/scripts/portal/party6_stage800.js index 7aa55ca09b..e80f27a744 100644 --- a/scripts/portal/party6_stage800.js +++ b/scripts/portal/party6_stage800.js @@ -5,7 +5,8 @@ function enter(pi) { pi.removeAll(4001169); pi.removeAll(2270004); - if(pi.getMap().getReactorByName("") != null && pi.getMap().getReactorByName("").getState() == 1) { + var spring = pi.getMap().getReactorById(3008000); // thanks Chloek3, seth1 for noticing fragments not being awarded properly + if(spring != null && spring.getState() > 0) { if(!pi.canHold(4001198, 1)) { pi.playerMessage(5, "Check for a free space on your ETC inventory before entering this portal."); return false; diff --git a/scripts/portal/rnj5_rp.js b/scripts/portal/rnj5_rp.js index e97a5a8d45..3dbaf07121 100644 --- a/scripts/portal/rnj5_rp.js +++ b/scripts/portal/rnj5_rp.js @@ -25,17 +25,6 @@ function enter(pi) { var mapplayer = "stage6_comb" + (pi.getMapId() % 10); var eim = pi.getEventInstance(); - if(eim.getProperty(mapplayer) == null) { - var comb = ""; - - for(var i = 0; i < 10; i++) { - var r = Math.floor((Math.random() * 4)); - comb += r.toString(); - } - - eim.setProperty(mapplayer, comb); - } - var comb = eim.getProperty(mapplayer); var name = pi.getPortal().getName().substring(2, 4); diff --git a/scripts/quest/21703.js b/scripts/quest/21703.js index e597d4190c..d6116bd8c2 100644 --- a/scripts/quest/21703.js +++ b/scripts/quest/21703.js @@ -67,10 +67,9 @@ function end(mode, type, selection) { qm.teachSkill(21000000, qm.getPlayer().getSkillLevel(21000000), 10, -1); // Combo Ability Skill qm.gainExp(2800); } - qm.sendNext("(You remembered the #bCombo Ability#k skill! You were skeptical of the training at first, since the old man suffers from Alzheimer's and all, but boy, was it effective!)", 2); - qm.showInfo("Effect/BasicEff.img/AranGetSkill"); + qm.sendNext("(You remembered the #bCombo Ability#k skill! You were skeptical of the training at first, since the old man suffers from Alzheimer's and all, but boy, was it effective!)", 2); } else if (status == 4) { - qm.sendPrev("Now report back to #p1201000#. I know she'll be ecstatic when she sees the progress you've made!"); - qm.dispose(); + qm.sendPrev("Now report back to #p1201000#. I know she'll be ecstatic when she sees the progress you've made!"); + qm.dispose(); } } \ No newline at end of file diff --git a/scripts/quest/21720.js b/scripts/quest/21720.js index e8039917d6..e054020f21 100644 --- a/scripts/quest/21720.js +++ b/scripts/quest/21720.js @@ -39,7 +39,6 @@ function end(mode, type, selection) { qm.teachSkill(21001003, qm.getPlayer().getSkillLevel(21001003), 20, -1); qm.gainExp(3900); } - qm.showIntro("Effect/BasicEff.img/AranGetSkill"); qm.sendNext('#b(You remembered the Polearm Booster skill!)#k', 2); } else if (status == 8) { qm.sendNextPrev("This skill was found in an ancient incomprehensible script. I had a hunch it might be a skill you used in the past, and I think I was right. You're not as strong as you used to be, but you'll get there, in time.", 8); diff --git a/sql/db_database.sql b/sql/db_database.sql index eab384ba11..888edc4d4b 100644 --- a/sql/db_database.sql +++ b/sql/db_database.sql @@ -12794,7 +12794,8 @@ INSERT INTO `drop_data_global` (`id`, `continent`, `itemid`, `minimum_quantity`, (2, -1, 4031866, 1, 1, 0, 20000, 'NX Card 250 PTS'), (3, -1, 4001126, 1, 2, 0, 8000, 'Maple Leaves'), (4, -1, 2049100, 1, 1, 0, 1200, 'Chaos Scroll 60%'), -(5, -1, 4001006, 1, 1, 0, 10000, 'Flaming Feather'); +(5, -1, 2340000, 1, 1, 0, 1200, 'White Scroll'), +(6, -1, 4001006, 1, 1, 0, 10000, 'Flaming Feather'); CREATE TABLE IF NOT EXISTS `dueyitems` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, @@ -16461,7 +16462,6 @@ INSERT INTO `nxcoupons` (`id`, `couponid`, `rate`, `activeday`, `starthour`, `en (39,5360008,2,254,6,10), (40,5360042,2,254,0,24); - CREATE TABLE IF NOT EXISTS `pets` ( `petid` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(13) DEFAULT NULL, @@ -16583,6 +16583,7 @@ CREATE TABLE IF NOT EXISTS `queststatus` ( `time` int(11) NOT NULL DEFAULT '0', `expires` bigint(20) NOT NULL DEFAULT '0', `forfeited` int(11) NOT NULL DEFAULT '0', + `completed` int(11) NOT NULL DEFAULT '0', `info` tinyint(3) NOT NULL DEFAULT '0', PRIMARY KEY (`queststatusid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; diff --git a/src/client/MapleCharacter.java b/src/client/MapleCharacter.java index 5e0d36d712..1181238342 100644 --- a/src/client/MapleCharacter.java +++ b/src/client/MapleCharacter.java @@ -64,6 +64,7 @@ import net.server.world.MapleParty; import net.server.world.MaplePartyCharacter; import net.server.world.PartyOperation; import net.server.world.World; +import scripting.AbstractPlayerInteraction; import scripting.event.EventInstanceManager; import server.CashShop; import server.MapleItemInformationProvider; @@ -199,7 +200,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject { private int expRate = 1, mesoRate = 1, dropRate = 1, expCoupon = 1, mesoCoupon = 1, dropCoupon = 1; private int omokwins, omokties, omoklosses, matchcardwins, matchcardties, matchcardlosses; private int owlSearch; - private long lastfametime, lastUsedCashItem, lastExpression = 0, lastHealed, lastBuyback = 0, lastDeathtime, lastMesoDrop = -1, jailExpiration = -1; + private long lastfametime, lastUsedCashItem, lastExpression = 0, lastHealed, lastBuyback = 0, lastDeathtime, jailExpiration = -1; private transient int localstr, localdex, localluk, localint_, localmagic, localwatk; private transient int equipmaxhp, equipmaxmp, equipstr, equipdex, equipluk, equipint_, equipmagic, equipwatk, localchairhp, localchairmp; private int localchairrate; @@ -2954,22 +2955,24 @@ public class MapleCharacter extends AbstractMapleCharacterObject { public void gainGachaExp() { int expgain = 0; - int currentgexp = gachaexp.get(); + long currentgexp = gachaexp.get(); if ((currentgexp + exp.get()) >= ExpTable.getExpNeededForLevel(level)) { expgain += ExpTable.getExpNeededForLevel(level) - exp.get(); + int nextneed = ExpTable.getExpNeededForLevel(level + 1); - if ((currentgexp - expgain) >= nextneed) { + if (currentgexp - expgain >= nextneed) { expgain += nextneed; } - this.gachaexp.set(currentgexp - expgain); + + this.gachaexp.set((int) (currentgexp - expgain)); } else { expgain = this.gachaexp.getAndSet(0); } - gainExp(expgain, false, false); + gainExp(expgain, false, true); updateSingleStat(MapleStat.GACHAEXP, this.gachaexp.get()); } - public void gainGachaExp(int gain) { + public void addGachaExp(int gain) { updateSingleStat(MapleStat.GACHAEXP, gachaexp.addAndGet(gain)); } @@ -4296,6 +4299,10 @@ public class MapleCharacter extends AbstractMapleCharacterObject { return client; } + public AbstractPlayerInteraction getAbstractPlayerInteraction() { + return client.getAbstractPlayerInteraction(); + } + public final List getCompletedQuests() { synchronized (quests) { List ret = new LinkedList<>(); @@ -4930,8 +4937,9 @@ public class MapleCharacter extends AbstractMapleCharacterObject { if (elapsedDays > 100) elapsedDays = 100; - int netMeso = (merchantmeso * (100 - elapsedDays)) / 100; - return netMeso; + long netMeso = (long) merchantmeso; // negative mesos issues found thanks to Flash, Vcoc + netMeso = (netMeso * (100 - elapsedDays)) / 100; + return (int) netMeso; } public int getMesosTraded() { @@ -6906,6 +6914,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject { } status.setForfeited(rs.getInt("forfeited")); + status.setCompleted(rs.getInt("completed")); ret.quests.put(q.getId(), status); loadedQuestStatus.put(rs.getInt("queststatusid"), status); } @@ -7665,7 +7674,8 @@ public class MapleCharacter extends AbstractMapleCharacterObject { } public void resetBattleshipHp() { - this.battleshipHp = 400 * getSkillLevel(SkillFactory.getSkill(Corsair.BATTLE_SHIP)) + ((getLevel() - 120) * 200); + int bshipLevel = Math.max(getLevel() - 120, 0); // thanks alex12 for noticing battleship HP issues for low-level players + this.battleshipHp = 400 * getSkillLevel(SkillFactory.getSkill(Corsair.BATTLE_SHIP)) + (bshipLevel * 200); } public void resetEnteredScript() { @@ -8254,7 +8264,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject { deleteQuestProgressWhereCharacterId(con, id); - ps = con.prepareStatement("INSERT INTO queststatus (`queststatusid`, `characterid`, `quest`, `status`, `time`, `expires`, `forfeited`) VALUES (DEFAULT, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); + ps = con.prepareStatement("INSERT INTO queststatus (`queststatusid`, `characterid`, `quest`, `status`, `time`, `expires`, `forfeited`, `completed`) VALUES (DEFAULT, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); PreparedStatement psf; try (PreparedStatement pse = con.prepareStatement("INSERT INTO questprogress VALUES (DEFAULT, ?, ?, ?, ?)")) { psf = con.prepareStatement("INSERT INTO medalmaps VALUES (DEFAULT, ?, ?, ?)"); @@ -8267,6 +8277,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject { ps.setInt(4, (int) (q.getCompletionTime() / 1000)); ps.setLong(5, q.getExpirationTime()); ps.setInt(6, q.getForfeited()); + ps.setInt(7, q.getCompleted()); ps.executeUpdate(); try (ResultSet rs = ps.getGeneratedKeys()) { rs.next(); @@ -8549,8 +8560,10 @@ public class MapleCharacter extends AbstractMapleCharacterObject { public synchronized void withdrawMerchantMesos() { int merchantMeso = this.getMerchantNetMeso(); + int playerMeso = this.getMeso(); + if (merchantMeso > 0) { - int possible = Integer.MAX_VALUE - merchantMeso; + int possible = Integer.MAX_VALUE - playerMeso; if (possible > 0) { if (possible < merchantMeso) { @@ -8562,7 +8575,6 @@ public class MapleCharacter extends AbstractMapleCharacterObject { } } } else { - int playerMeso = this.getMeso(); int nextMeso = playerMeso + merchantMeso; if (nextMeso < 0) { @@ -9401,9 +9413,10 @@ public class MapleCharacter extends AbstractMapleCharacterObject { } else if (quest.getStatus().equals(MapleQuestStatus.Status.COMPLETED)) { MapleQuest mquest = quest.getQuest(); short questid = mquest.getId(); - if(!mquest.isSameDayRepeatable() && !MapleQuest.isExploitableQuest(questid)) { + if (!mquest.isSameDayRepeatable() && !MapleQuest.isExploitableQuest(questid)) { awardQuestPoint(ServerConstants.QUEST_POINT_PER_QUEST_COMPLETE); } + quest.setCompleted(quest.getCompleted() + 1); // count quest completed Jayd's idea announce(MaplePacketCreator.completeQuest(questid, quest.getCompletionTime())); } else if (quest.getStatus().equals(MapleQuestStatus.Status.NOT_STARTED)) { @@ -10040,14 +10053,6 @@ public class MapleCharacter extends AbstractMapleCharacterObject { whiteChat = !whiteChat; } - public boolean canDropMeso() { - if (System.currentTimeMillis() - lastMesoDrop >= 200 || lastMesoDrop == -1) { //About 200 meso drops a minute - lastMesoDrop = System.currentTimeMillis(); - return true; - } - return false; - } - // These need to be renamed, but I am too lazy right now to go through the scripts and rename them... public String getPartyQuestItems() { return dataString; diff --git a/src/client/MapleClient.java b/src/client/MapleClient.java index 66d36d4956..4af9f01cb3 100644 --- a/src/client/MapleClient.java +++ b/src/client/MapleClient.java @@ -888,7 +888,6 @@ public class MapleClient { player.cancelAllBuffs(true); player.closePlayerInteractions(); - QuestScriptManager.getInstance().dispose(this); if (!serverTransition) { // thanks MedicOP for detecting an issue with party leader change on changing channels removePartyPlayer(wserv); @@ -1463,18 +1462,8 @@ public class MapleClient { return; } - if (player.getTrade() != null) { - MapleTrade.cancelTrade(getPlayer(), MapleTrade.TradeResult.PARTNER_CANCEL); - } - - MapleHiredMerchant merchant = player.getHiredMerchant(); - if (merchant != null) { - if (merchant.isOwner(getPlayer())) { - merchant.setOpen(true); - } else { - merchant.removeVisitor(getPlayer()); - } - } + player.closePlayerInteractions(); + player.unregisterChairBuff(); server.getPlayerBuffStorage().addBuffsToStorage(player.getId(), player.getAllBuffs()); server.getPlayerBuffStorage().addDiseasesToStorage(player.getId(), player.getAllDiseases()); @@ -1540,6 +1529,7 @@ public class MapleClient { public void closePlayerScriptInteractions() { this.removeClickedNPC(); NPCScriptManager.getInstance().dispose(this); + QuestScriptManager.getInstance().dispose(this); } public boolean attemptCsCoupon() { diff --git a/src/client/MapleQuestStatus.java b/src/client/MapleQuestStatus.java index 4eb31ce1fc..9e0ccc9dd1 100644 --- a/src/client/MapleQuestStatus.java +++ b/src/client/MapleQuestStatus.java @@ -65,7 +65,7 @@ public class MapleQuestStatus { private final List medalProgress = new LinkedList(); private int npc; private long completionTime, expirationTime; - private int forfeited = 0; + private int forfeited = 0, completed = 0; private String customData; public MapleQuestStatus(MapleQuest quest, Status status) { @@ -214,6 +214,10 @@ public class MapleQuestStatus { return forfeited; } + public int getCompleted() { + return completed; + } + public String getInfo() { if(!progress.containsKey(0) && !getMedalMaps().isEmpty()) { return Integer.toString(getMedalProgress()); @@ -233,6 +237,14 @@ public class MapleQuestStatus { throw new IllegalArgumentException("Can't set forfeits to something lower than before."); } } + + public void setCompleted(int completed) { + if (completed >= this.completed) { + this.completed = completed; + } else { + throw new IllegalArgumentException("Can't set completes to something lower than before."); + } + } public final void setCustomData(final String customData) { this.customData = customData; diff --git a/src/client/command/CommandsExecutor.java b/src/client/command/CommandsExecutor.java index daf53a4f4d..98be594909 100644 --- a/src/client/command/CommandsExecutor.java +++ b/src/client/command/CommandsExecutor.java @@ -204,6 +204,8 @@ public class CommandsExecutor { addCommand("enableauth", EnableAuthCommand.class); addCommand("toggleexp", ToggleExpCommand.class); addCommand("mylawn", MapOwnerClaimCommand.class); + addCommand("bosshp", BossHpCommand.class); + addCommand("mobhp", MobHpCommand.class); commandsNameDesc.add(levelCommandsCursor); } @@ -212,8 +214,6 @@ public class CommandsExecutor { private void registerLv1Commands() { levelCommandsCursor = new Pair<>((List) new ArrayList(), (List) new ArrayList()); - addCommand("bosshp", 1, BossHpCommand.class); - addCommand("mobhp", 1, MobHpCommand.class); addCommand("whatdropsfrom", 1, WhatDropsFromCommand.class); addCommand("whodrops", 1, WhoDropsCommand.class); addCommand("buffme", 1, BuffMeCommand.class); @@ -291,7 +291,6 @@ public class CommandsExecutor { addCommand("givevp", 3, GiveVpCommand.class); addCommand("givems", 3, GiveMesosCommand.class); addCommand("giverp", 3, GiveRpCommand.class); - addCommand("id", 3, IdCommand.class); addCommand("expeds", 3, ExpedsCommand.class); addCommand("kill", 3, KillCommand.class); addCommand("seed", 3, SeedCommand.class); @@ -307,7 +306,6 @@ public class CommandsExecutor { addCommand("startmapevent", 3, StartMapEventCommand.class); addCommand("stopmapevent", 3, StopMapEventCommand.class); addCommand("online2", 3, OnlineTwoCommand.class); - addCommand("warpsnowball", 3, WarpSnowBallCommand.class); addCommand("ban", 3, BanCommand.class); addCommand("unban", 3, UnBanCommand.class); addCommand("healmap", 3, HealMapCommand.class); @@ -339,6 +337,7 @@ public class CommandsExecutor { addCommand("exprate", 4, ExpRateCommand.class); addCommand("mesorate", 4, MesoRateCommand.class); addCommand("droprate", 4, DropRateCommand.class); + addCommand("bossdroprate", 4, BossDropRateCommand.class); addCommand("questrate", 4, QuestRateCommand.class); addCommand("travelrate", 4, TravelRateCommand.class); addCommand("fishrate", 4, FishingRateCommand.class); diff --git a/src/client/command/commands/gm1/GotoCommand.java b/src/client/command/commands/gm1/GotoCommand.java index 72445d122b..2824929435 100644 --- a/src/client/command/commands/gm1/GotoCommand.java +++ b/src/client/command/commands/gm1/GotoCommand.java @@ -27,23 +27,64 @@ import client.MapleCharacter; import client.command.Command; import client.MapleClient; import constants.GameConstants; +import java.util.ArrayList; +import java.util.Collections; +import net.server.Server; import server.MaplePortal; import server.maps.FieldLimit; import server.maps.MapleMap; +import server.maps.MapleMapFactory; import server.maps.MapleMiniDungeonInfo; +import java.util.Comparator; import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; public class GotoCommand extends Command { + { setDescription(""); + + MapleMapFactory mapFactory = Server.getInstance().getWorlds().get(0).getChannels().get(0).getMapFactory(); + + List> towns = new ArrayList<>(GameConstants.GOTO_TOWNS.entrySet()); + sortGotoEntries(towns); + for (Map.Entry e : towns) { + GOTO_TOWNS_INFO += ("'" + e.getKey() + "' - #b" + (mapFactory.getMap(e.getValue()).getMapName()) + "#k\r\n"); + } + + List> areas = new ArrayList<>(GameConstants.GOTO_AREAS.entrySet()); + sortGotoEntries(areas); + for (Map.Entry e : areas) { + GOTO_AREAS_INFO += ("'" + e.getKey() + "' - #b" + (mapFactory.getMap(e.getValue()).getMapName()) + "#k\r\n"); + } + } + + public static String GOTO_TOWNS_INFO = ""; + public static String GOTO_AREAS_INFO = ""; + + private static void sortGotoEntries(List> listEntries) { + Collections.sort(listEntries, new Comparator>() { + @Override + public int compare(Entry e1, Entry e2) + { + return e1.getValue().compareTo(e2.getValue()); + } + }); } @Override public void execute(MapleClient c, String[] params) { MapleCharacter player = c.getPlayer(); if (params.length < 1){ - player.yellowMessage("Syntax: @goto "); + String sendStr = "Syntax: #b@goto #k. Available areas:\r\n\r\n#rTowns:#k\r\n" + GOTO_TOWNS_INFO; + if (player.isGM()) { + sendStr += ("\r\n#rAreas:#k\r\n" + GOTO_AREAS_INFO); + } + + player.getAbstractPlayerInteraction().npcTalk(9000020, sendStr); return; } @@ -74,7 +115,13 @@ public class GotoCommand extends Command { player.saveLocationOnWarp(); player.changeMap(target, targetPortal); } else { - player.dropMessage(5, "Area '" + params[0] + "' is not registered."); + // detailed info on goto available areas suggested thanks to Vcoc + String sendStr = "Area '#r" + params[0] + "#k' is not available. Available areas:\r\n\r\n#rTowns:#k" + GOTO_TOWNS_INFO; + if (player.isGM()) { + sendStr += ("\r\n#rAreas:#k\r\n" + GOTO_AREAS_INFO); + } + + player.getAbstractPlayerInteraction().npcTalk(9000020, sendStr); } } } diff --git a/src/client/command/commands/gm1/WhatDropsFromCommand.java b/src/client/command/commands/gm1/WhatDropsFromCommand.java index e13c9c6339..1ef0f045be 100644 --- a/src/client/command/commands/gm1/WhatDropsFromCommand.java +++ b/src/client/command/commands/gm1/WhatDropsFromCommand.java @@ -62,7 +62,7 @@ public class WhatDropsFromCommand extends Command { if (name == null || name.equals("null") || drop.chance == 0){ continue; } - float chance = 1000000 / drop.chance / (!MapleMonsterInformationProvider.getInstance().isBoss(mobId) ? player.getDropRate() : player.getBossDropRate()); + float chance = Math.max(1000000 / drop.chance / (!MapleMonsterInformationProvider.getInstance().isBoss(mobId) ? player.getDropRate() : player.getBossDropRate()), 1); output += "- " + name + " (1/" + (int) chance + ")\r\n"; } catch (Exception ex){ ex.printStackTrace(); diff --git a/src/client/command/commands/gm3/IdCommand.java b/src/client/command/commands/gm3/IdCommand.java deleted file mode 100644 index c19bee6714..0000000000 --- a/src/client/command/commands/gm3/IdCommand.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - This file is part of the HeavenMS MapleStory Server, commands OdinMS-based - 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 . -*/ - -/* - @Author: Arthur L - Refactored command content into modules -*/ -package client.command.commands.gm3; - -import client.command.Command; -import client.MapleClient; -import client.MapleCharacter; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.net.URL; - -public class IdCommand extends Command { - { - setDescription(""); - } - - @Override - public void execute(MapleClient c, String[] params) { - MapleCharacter player = c.getPlayer(); - if (params.length < 1) { - player.yellowMessage("Syntax: !id "); - return; - } - try { - try (BufferedReader dis = new BufferedReader(new InputStreamReader(new URL("http://www.mapletip.com/search_java.php?search_value=" + params[0] + "&check=true").openConnection().getInputStream()))) { - String s; - while ((s = dis.readLine()) != null) { - player.dropMessage(s); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } -} diff --git a/src/client/command/commands/gm3/WarpSnowBallCommand.java b/src/client/command/commands/gm4/BossDropRateCommand.java similarity index 64% rename from src/client/command/commands/gm3/WarpSnowBallCommand.java rename to src/client/command/commands/gm4/BossDropRateCommand.java index afa0945df1..afd12a2e67 100644 --- a/src/client/command/commands/gm3/WarpSnowBallCommand.java +++ b/src/client/command/commands/gm4/BossDropRateCommand.java @@ -1,5 +1,5 @@ /* - This file is part of the HeavenMS MapleStory Server, commands OdinMS-based + 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 @@ -17,19 +17,18 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ - -/* - @Author: Arthur L - Refactored command content into modules -*/ -package client.command.commands.gm3; +package client.command.commands.gm4; import client.command.Command; import client.MapleClient; import client.MapleCharacter; +import tools.MaplePacketCreator; -import java.util.List; - -public class WarpSnowBallCommand extends Command { +/** + * + * @author Ronan + */ +public class BossDropRateCommand extends Command { { setDescription(""); } @@ -37,10 +36,13 @@ public class WarpSnowBallCommand extends Command { @Override public void execute(MapleClient c, String[] params) { MapleCharacter player = c.getPlayer(); - List chars = player.getMap().getAllPlayers(); - for (MapleCharacter chr : chars) { - chr.saveLocationOnWarp(); - chr.changeMap(109060000, chr.getTeam()); + if (params.length < 1) { + player.yellowMessage("Syntax: !bossdroprate "); + return; } + + int bossdroprate = Math.max(Integer.parseInt(params[0]), 1); + c.getWorldServer().setBossDropRate(bossdroprate); + c.getWorldServer().broadcastPacket(MaplePacketCreator.serverNotice(6, "[Rate] Boss Drop Rate has been changed to " + bossdroprate + "x.")); } } diff --git a/src/client/command/commands/gm4/MesoRateCommand.java b/src/client/command/commands/gm4/MesoRateCommand.java index 602650cc1b..3561b23d5c 100644 --- a/src/client/command/commands/gm4/MesoRateCommand.java +++ b/src/client/command/commands/gm4/MesoRateCommand.java @@ -44,6 +44,5 @@ public class MesoRateCommand extends Command { int mesorate = Math.max(Integer.parseInt(params[0]), 1); c.getWorldServer().setMesoRate(mesorate); c.getWorldServer().broadcastPacket(MaplePacketCreator.serverNotice(6, "[Rate] Meso Rate has been changed to " + mesorate + "x.")); - } } diff --git a/src/client/inventory/Equip.java b/src/client/inventory/Equip.java index 6b6824852b..ec5c6941ae 100644 --- a/src/client/inventory/Equip.java +++ b/src/client/inventory/Equip.java @@ -24,6 +24,7 @@ package client.inventory; import client.MapleClient; import constants.ServerConstants; import constants.ExpTable; +import constants.ItemConstants; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -302,10 +303,33 @@ public class Equip extends Item { return stat; } + private static boolean isPhysicalWeapon(int itemid) { + Equip eqp = (Equip) MapleItemInformationProvider.getInstance().getEquipById(itemid); + return eqp.getWatk() >= eqp.getMatk(); + } + + private boolean isNotWeaponAffinity(StatUpgrade name) { + // WATK/MATK expected gains lessens outside of weapon affinity (physical/magic): Vcoc's idea + + if (ItemConstants.isWeapon(this.getItemId())) { + if (name.equals(StatUpgrade.incPAD)) { + if (!isPhysicalWeapon(this.getItemId())) { + return true; + } + } else if (name.equals(StatUpgrade.incMAD)) { + if (isPhysicalWeapon(this.getItemId())) { + return true; + } + } + } + + return false; + } + private void getUnitStatUpgrade(List> stats, StatUpgrade name, int curStat, boolean isAttribute) { isUpgradeable = true; - int maxUpgrade = randomizeStatUpgrade((int)(1 + (curStat / getStatModifier(isAttribute)))); + int maxUpgrade = randomizeStatUpgrade((int)(1 + (curStat / (getStatModifier(isAttribute) * (isNotWeaponAffinity(name) ? 2.7 : 1))))); if(maxUpgrade == 0) return; stats.add(new Pair<>(name, maxUpgrade)); diff --git a/src/client/inventory/manipulator/MapleInventoryManipulator.java b/src/client/inventory/manipulator/MapleInventoryManipulator.java index a3fe13d19b..af689f2a57 100644 --- a/src/client/inventory/manipulator/MapleInventoryManipulator.java +++ b/src/client/inventory/manipulator/MapleInventoryManipulator.java @@ -69,10 +69,29 @@ public class MapleInventoryManipulator { } public static boolean addById(MapleClient c, int itemId, short quantity, String owner, int petid, byte flag, long expiration) { - MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); - MapleInventoryType type = ItemConstants.getInventoryType(itemId); MapleCharacter chr = c.getPlayer(); - MapleInventory inv = chr.getInventory(type); + MapleInventoryType type = ItemConstants.getInventoryType(itemId); + + if (c.tryacquireClient()) { + try { + MapleInventory inv = chr.getInventory(type); + inv.lockInventory(); + try { + return addByIdInternal(c, chr, type, inv, itemId, quantity, owner, petid, flag, expiration); + } finally { + inv.unlockInventory(); + } + } finally { + c.releaseClient(); + } + } else { + c.announce(MaplePacketCreator.enableActions()); + return false; + } + } + + private static boolean addByIdInternal(MapleClient c, MapleCharacter chr, MapleInventoryType type, MapleInventory inv, int itemId, short quantity, String owner, int petid, byte flag, long expiration) { + MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); if (!type.equals(MapleInventoryType.EQUIP)) { short slotMax = ii.getSlotMax(c, itemId); List existing = inv.listById(itemId); @@ -166,15 +185,36 @@ public class MapleInventoryManipulator { public static boolean addFromDrop(MapleClient c, Item item, boolean show, int petId) { MapleCharacter chr = c.getPlayer(); - MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); MapleInventoryType type = item.getInventoryType(); + + if (c.tryacquireClient()) { + try { + MapleInventory inv = chr.getInventory(type); + inv.lockInventory(); + try { + return addFromDropInternal(c, chr, type, inv, item, show, petId); + } finally { + inv.unlockInventory(); + } + } finally { + c.releaseClient(); + } + } else { + c.announce(MaplePacketCreator.enableActions()); + return false; + } + } + + private static boolean addFromDropInternal(MapleClient c, MapleCharacter chr, MapleInventoryType type, MapleInventory inv, Item item, boolean show, int petId) { + MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); + if (ii.isPickupRestricted(item.getItemId()) && chr.haveItemWithId(item.getItemId(), true)) { c.announce(MaplePacketCreator.getInventoryFull()); c.announce(MaplePacketCreator.showItemUnavailable()); return false; } short quantity = item.getQuantity(); - MapleInventory inv = chr.getInventory(type); + if (!type.equals(MapleInventoryType.EQUIP)) { short slotMax = ii.getSlotMax(c, item.getItemId()); List existing = inv.listById(item.getItemId()); @@ -214,13 +254,13 @@ public class MapleInventoryManipulator { nItem.setPosition(newSlot); item.setPosition(newSlot); c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(0, nItem)))); - if(MapleInventoryManipulator.isSandboxItem(nItem)) chr.setHasSandboxItem(); + if (MapleInventoryManipulator.isSandboxItem(nItem)) chr.setHasSandboxItem(); } } else { Item nItem = new Item(item.getItemId(), (short) 0, quantity, petId); nItem.setExpiration(item.getExpiration()); nItem.setFlag(item.getFlag()); - + short newSlot = inv.addItem(nItem); if (newSlot == -1) { c.announce(MaplePacketCreator.getInventoryFull()); @@ -230,7 +270,7 @@ public class MapleInventoryManipulator { nItem.setPosition(newSlot); item.setPosition(newSlot); c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(0, nItem)))); - if(MapleInventoryManipulator.isSandboxItem(nItem)) chr.setHasSandboxItem(); + if (MapleInventoryManipulator.isSandboxItem(nItem)) chr.setHasSandboxItem(); c.announce(MaplePacketCreator.enableActions()); } } else if (quantity == 1) { @@ -242,7 +282,7 @@ public class MapleInventoryManipulator { } item.setPosition(newSlot); c.announce(MaplePacketCreator.modifyInventory(true, Collections.singletonList(new ModifyInventory(0, item)))); - if(MapleInventoryManipulator.isSandboxItem(item)) chr.setHasSandboxItem(); + if (MapleInventoryManipulator.isSandboxItem(item)) chr.setHasSandboxItem(); } else { FilePrinter.printError(FilePrinter.ITEM, "Tried to pickup Equip id " + item.getItemId() + " containing more than 1 quantity --> " + quantity); c.announce(MaplePacketCreator.getInventoryFull()); diff --git a/src/client/processor/StorageProcessor.java b/src/client/processor/StorageProcessor.java index 77eeafd740..59672063b9 100644 --- a/src/client/processor/StorageProcessor.java +++ b/src/client/processor/StorageProcessor.java @@ -40,6 +40,7 @@ import tools.data.input.SeekableLittleEndianAccessor; /** * * @author Matze + * @author Ronan - inventory concurrency protection on storing items */ public class StorageProcessor { @@ -97,15 +98,15 @@ public class StorageProcessor { short slot = slea.readShort(); int itemId = slea.readInt(); short quantity = slea.readShort(); - MapleInventoryType slotType = ItemConstants.getInventoryType(itemId); - MapleInventory Inv = chr.getInventory(slotType); - if (slot < 1 || slot > Inv.getSlotLimit()) { //player inv starts at one + MapleInventoryType invType = ItemConstants.getInventoryType(itemId); + MapleInventory inv = chr.getInventory(invType); + if (slot < 1 || slot > inv.getSlotLimit()) { //player inv starts at one AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit with storage."); FilePrinter.print(FilePrinter.EXPLOITS + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + " tried to store item at slot " + slot); c.disconnect(true, false); return; } - if (quantity < 1 || chr.getItemQuantity(itemId, false) < quantity) { + if (quantity < 1) { c.announce(MaplePacketCreator.enableActions()); return; } @@ -118,28 +119,39 @@ public class StorageProcessor { if (chr.getMeso() < storeFee) { c.announce(MaplePacketCreator.getStorageError((byte) 0x0B)); } else { - MapleInventoryType invType = ItemConstants.getInventoryType(itemId); - Item item = chr.getInventory(invType).getItem(slot).copy(); - if (item != null && item.getItemId() == itemId && (item.getQuantity() >= quantity || ItemConstants.isRechargeable(itemId))) { - if (ItemConstants.isWeddingRing(itemId) || ItemConstants.isWeddingToken(itemId)) { + Item item; + + inv.lockInventory(); // thanks imbee for pointing a dupe within storage + try { + item = inv.getItem(slot); + if (item != null && item.getItemId() == itemId && (item.getQuantity() >= quantity || ItemConstants.isRechargeable(itemId))) { + if (ItemConstants.isWeddingRing(itemId) || ItemConstants.isWeddingToken(itemId)) { + c.announce(MaplePacketCreator.enableActions()); + return; + } + + if (ItemConstants.isRechargeable(itemId)) { + quantity = item.getQuantity(); + } + + MapleInventoryManipulator.removeFromSlot(c, invType, slot, quantity, false); + } else { c.announce(MaplePacketCreator.enableActions()); return; } - - if (ItemConstants.isRechargeable(itemId)) { - quantity = item.getQuantity(); - } - - chr.gainMeso(-storeFee, false, true, false); - MapleKarmaManipulator.toggleKarmaFlagToUntradeable(item); - MapleInventoryManipulator.removeFromSlot(c, invType, slot, quantity, false); - item.setQuantity(quantity); - storage.store(item); - storage.sendStored(c, ItemConstants.getInventoryType(itemId)); - String itemName = MapleItemInformationProvider.getInstance().getName(item.getItemId()); - FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " stored " + item.getQuantity() + " " + itemName + " (" + item.getItemId() + ")"); - chr.setUsedStorage(); + } finally { + inv.unlockInventory(); } + + chr.gainMeso(-storeFee, false, true, false); + + MapleKarmaManipulator.toggleKarmaFlagToUntradeable(item); + item.setQuantity(quantity); + storage.store(item); + storage.sendStored(c, ItemConstants.getInventoryType(itemId)); + String itemName = MapleItemInformationProvider.getInstance().getName(item.getItemId()); + FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " stored " + item.getQuantity() + " " + itemName + " (" + item.getItemId() + ")"); + chr.setUsedStorage(); } } else if (mode == 6) { // arrange items if(ServerConstants.USE_STORAGE_ITEM_SORT) storage.arrangeItems(c); diff --git a/src/constants/LanguageConstants.java b/src/constants/LanguageConstants.java index cde341f421..45ab0fe9a2 100644 --- a/src/constants/LanguageConstants.java +++ b/src/constants/LanguageConstants.java @@ -8,66 +8,89 @@ import client.MapleCharacter; */ public class LanguageConstants { - public static String CPQBlue; - public static String CPQError; - public static String CPQEntry; - public static String CPQFindError; - public static String CPQRed; - public static String CPQPlayerExit; - public static String CPQEntryLobby; - public static String CPQPickRoom; - public static String CPQExtendTime; - public static String CPQLeaderNotFound; - public static String CPQChallengeRoomAnswer; - public static String CPQChallengeRoomSent; - - public static LanguageConstants Languages(MapleCharacter chr) { - if (chr.getLanguage() == 0) { - LanguageConstants.CPQBlue = "Maple Azul"; - LanguageConstants.CPQRed = "Maple Vermelho"; - LanguageConstants.CPQExtendTime = "O tempo foi estendido."; - LanguageConstants.CPQPlayerExit = " deixou o Carnaval de Monstros."; - LanguageConstants.CPQError = "Ocorreu um problema. Favor recriar a sala."; - LanguageConstants.CPQLeaderNotFound = "Nao foi possivel encontrar o Lider."; - LanguageConstants.CPQPickRoom = "Inscreva-se no Festival de Monstros!\r\n"; - LanguageConstants.CPQChallengeRoomAnswer = "O grupo esta respondendo um desafio no momento."; - LanguageConstants.CPQChallengeRoomSent = "Um desafio foi enviado para o grupo na sala. Aguarde um momento."; - LanguageConstants.CPQFindError = "Nao foi possivel encontrar um grupo nesta sala.\r\nProvavelmente o grupo foi desfeito dentro da sala!"; - LanguageConstants.CPQEntryLobby = "Agora voce ira receber desafios de outros grupos. Se voce nao aceitar um desafio em 3 minutos, voce sera levado para fora."; - LanguageConstants.CPQEntry = "Voce pode selecionar \"Invocar Monstros\", \"Habilidade\", ou \"Protetor\" como sua tatica durante o Carnaval dos Monstros. Use Tab a F1~F12 para acesso rapido!"; - - - - } else if (chr.getLanguage() == 1) { - LanguageConstants.CPQBlue = "Maple Azul"; - LanguageConstants.CPQRed = "Maple Rojo"; - LanguageConstants.CPQExtendTime = "El tiempo se ha ampliado."; - LanguageConstants.CPQPlayerExit = " ha dejado el Carnaval de Monstruos."; - LanguageConstants.CPQLeaderNotFound = "No se pudo encontrar el Lider."; - LanguageConstants.CPQPickRoom = "!Inscribete en el Festival de Monstruos!\r\n"; - LanguageConstants.CPQError = "Se ha producido un problema. Por favor, volver a crear una sala."; - LanguageConstants.CPQChallengeRoomAnswer = "El grupo esta respondiendo un desafio en el momento."; - LanguageConstants.CPQChallengeRoomSent = "Un desafio fue enviado al grupo en la sala. Espera un momento."; - LanguageConstants.CPQFindError = "No se pudo encontrar un grupo en esta sala.\r\nProbablemente el grupo fue deshecho dentro de la sala!"; - LanguageConstants.CPQEntryLobby = "Ahora usted recibira los retos de otros grupos. Si usted no acepta un desafio en 3 minutos, usted sera llevado hacia fuera."; - LanguageConstants.CPQEntry = "Usted puede seleccionar \"Invocar Monstruos \", \"Habilidad \", o \"Protector \" como su tactica durante el Carnaval de los Monstruos. Utilice Tab y F1 ~ F12 para acceso rapido!"; - - - } else if (chr.getLanguage() == 2) { - LanguageConstants.CPQBlue = "Maple Blue"; - LanguageConstants.CPQRed = "Maple Red"; - LanguageConstants.CPQPlayerExit = " left the Carnival of Monsters."; - LanguageConstants.CPQExtendTime = "The time has been extended."; - LanguageConstants.CPQLeaderNotFound = "Could not find the Leader."; - LanguageConstants.CPQError = "There was a problem. Please re-create a room."; - LanguageConstants.CPQPickRoom = "Sign up for the Monster Festival!\r\n"; - LanguageConstants.CPQChallengeRoomAnswer = "The group is currently facing a challenge."; - LanguageConstants.CPQChallengeRoomSent = "A challenge has been sent to the group in the room. Please wait a while."; - LanguageConstants.CPQFindError = "We could not find a group in this room.\r\nProbably the group was scrapped inside the room!"; - LanguageConstants.CPQEntryLobby = "You will now receive challenges from other groups. If you do not accept a challenge within 3 minutes, you will be taken out."; - LanguageConstants.CPQEntry = "You can select \"Summon Monsters \", \"Ability \", or \"Protector \" as your tactic during the Monster Carnival. Use Tab and F1 ~ F12 for quick access!"; - + enum Language { + LANG_PRT(0), + LANG_ESP(1), + LANG_ENG(2); + + int lang; + + private Language(int lang) { + this.lang = lang; } - return null; + + private int getValue() { + return this.lang; + } + + } + + public static String CPQBlue[] = new String[3]; + public static String CPQError[] = new String[3]; + public static String CPQEntry[] = new String[3]; + public static String CPQFindError[] = new String[3]; + public static String CPQRed[] = new String[3]; + public static String CPQPlayerExit[] = new String[3]; + public static String CPQEntryLobby[] = new String[3]; + public static String CPQPickRoom[] = new String[3]; + public static String CPQExtendTime[] = new String[3]; + public static String CPQLeaderNotFound[] = new String[3]; + public static String CPQChallengeRoomAnswer[] = new String[3]; + public static String CPQChallengeRoomSent[] = new String[3]; + public static String CPQChallengeRoomDenied[] = new String[3]; + + static { + int lang; + + lang = Language.LANG_PRT.getValue(); + LanguageConstants.CPQBlue[lang] = "Maple Azul"; + LanguageConstants.CPQRed[lang] = "Maple Vermelho"; + LanguageConstants.CPQExtendTime[lang] = "O tempo foi estendido."; + LanguageConstants.CPQPlayerExit[lang] = " deixou o Carnaval de Monstros."; + LanguageConstants.CPQError[lang] = "Ocorreu um problema. Favor recriar a sala."; + LanguageConstants.CPQLeaderNotFound[lang] = "Nao foi possivel encontrar o Lider."; + LanguageConstants.CPQPickRoom[lang] = "Inscreva-se no Festival de Monstros!\r\n"; + LanguageConstants.CPQChallengeRoomAnswer[lang] = "O grupo esta respondendo um desafio no momento."; + LanguageConstants.CPQChallengeRoomSent[lang] = "Um desafio foi enviado para o grupo na sala. Aguarde um momento."; + LanguageConstants.CPQChallengeRoomDenied[lang] = "O grupo na sala cancelou seu desafio."; + LanguageConstants.CPQFindError[lang] = "Nao foi possivel encontrar um grupo nesta sala.\r\nProvavelmente o grupo foi desfeito dentro da sala!"; + LanguageConstants.CPQEntryLobby[lang] = "Agora voce ira receber desafios de outros grupos. Se voce nao aceitar um desafio em 3 minutos, voce sera levado para fora."; + LanguageConstants.CPQEntry[lang] = "Voce pode selecionar \"Invocar Monstros\", \"Habilidade\", ou \"Protetor\" como sua tatica durante o Carnaval dos Monstros. Use Tab a F1~F12 para acesso rapido!"; + + lang = Language.LANG_ESP.getValue(); + LanguageConstants.CPQBlue[lang] = "Maple Azul"; + LanguageConstants.CPQRed[lang] = "Maple Rojo"; + LanguageConstants.CPQExtendTime[lang] = "El tiempo se ha ampliado."; + LanguageConstants.CPQPlayerExit[lang] = " ha dejado el Carnaval de Monstruos."; + LanguageConstants.CPQLeaderNotFound[lang] = "No se pudo encontrar el Lider."; + LanguageConstants.CPQPickRoom[lang] = "!Inscribete en el Festival de Monstruos!\r\n"; + LanguageConstants.CPQError[lang] = "Se ha producido un problema. Por favor, volver a crear una sala."; + LanguageConstants.CPQChallengeRoomAnswer[lang] = "El grupo esta respondiendo un desafio en el momento."; + LanguageConstants.CPQChallengeRoomSent[lang] = "Un desafio fue enviado al grupo en la sala. Espera un momento."; + LanguageConstants.CPQChallengeRoomDenied[lang] = "El grupo en la sala cancelo su desafio."; + LanguageConstants.CPQFindError[lang] = "No se pudo encontrar un grupo en esta sala.\r\nProbablemente el grupo fue deshecho dentro de la sala!"; + LanguageConstants.CPQEntryLobby[lang] = "Ahora usted recibira los retos de otros grupos. Si usted no acepta un desafio en 3 minutos, usted sera llevado hacia fuera."; + LanguageConstants.CPQEntry[lang] = "Usted puede seleccionar \"Invocar Monstruos\", \"Habilidad\", o \"Protector\" como su tactica durante el Carnaval de los Monstruos. Utilice Tab y F1 ~ F12 para acceso rapido!"; + + lang = Language.LANG_ENG.getValue(); + LanguageConstants.CPQBlue[lang] = "Maple Blue"; + LanguageConstants.CPQRed[lang] = "Maple Red"; + LanguageConstants.CPQPlayerExit[lang] = " left the Carnival of Monsters."; + LanguageConstants.CPQExtendTime[lang] = "The time has been extended."; + LanguageConstants.CPQLeaderNotFound[lang] = "Could not find the Leader."; + LanguageConstants.CPQError[lang] = "There was a problem. Please re-create a room."; + LanguageConstants.CPQPickRoom[lang] = "Sign up for the Monster Festival!\r\n"; + LanguageConstants.CPQChallengeRoomAnswer[lang] = "The group is currently facing a challenge."; + LanguageConstants.CPQChallengeRoomSent[lang] = "A challenge has been sent to the group in the room. Please wait a while."; + LanguageConstants.CPQChallengeRoomDenied[lang] = "The group in the room canceled your challenge."; + LanguageConstants.CPQFindError[lang] = "We could not find a group in this room.\r\nProbably the group was scrapped inside the room!"; + LanguageConstants.CPQEntryLobby[lang] = "You will now receive challenges from other groups. If you do not accept a challenge within 3 minutes, you will be taken out."; + LanguageConstants.CPQEntry[lang] = "You can select \"Summon Monsters\", \"Ability\", or \"Protector\" as your tactic during the Monster Carnival. Use Tab and F1 ~ F12 for quick access!"; + + + } + + public static String getMessage(MapleCharacter chr, String[] message) { + return message[chr.getLanguage()]; } } diff --git a/src/constants/ScriptableNPCConstants.java b/src/constants/ScriptableNPCConstants.java new file mode 100644 index 0000000000..d5eb2e4eca --- /dev/null +++ b/src/constants/ScriptableNPCConstants.java @@ -0,0 +1,23 @@ +package constants; + +/** + * @brief ScriptableNPCConstants + * @author GabrielSin + * @date 16/09/2018 + * + * Adaptations to use Pair and Set, in order to suit a one-packet marshall, + * by Ronan + */ + +import java.util.HashSet; +import java.util.Set; +import tools.Pair; + +public class ScriptableNPCConstants { + + public static final Set> SCRIPTABLE_NPCS = new HashSet>(){{ + add(new Pair<>(9200000, "Cody")); + }}; + +} + \ No newline at end of file diff --git a/src/constants/ServerConstants.java b/src/constants/ServerConstants.java index d509bedd42..317f45679f 100644 --- a/src/constants/ServerConstants.java +++ b/src/constants/ServerConstants.java @@ -64,6 +64,7 @@ public class ServerConstants { public static boolean USE_DEBUG_SHOW_RCVD_MVLIFE = false; //Prints on the cmd all received move life content. public static final boolean USE_DEBUG_SHOW_PACKET = false; public static boolean USE_SUPPLY_RATE_COUPONS = true; //Allows rate coupons to be sold through the Cash Shop. + public static final boolean USE_IP_VALIDATION = true; //Enables IP checking when logging in. public static final boolean USE_MAXRANGE = true; //Will send and receive packets from all events on a map, rather than those of only view range. public static final boolean USE_MAXRANGE_ECHO_OF_HERO = true; @@ -112,6 +113,7 @@ public class ServerConstants { public static final boolean USE_REBIRTH_SYSTEM = false; //Flag to enable/disable rebirth system public static final boolean USE_MAP_OWNERSHIP_SYSTEM = true; //Flag to enable/disable map ownership system public static final boolean USE_FISHING_SYSTEM = true; //Flag to enable/disable fishing system + public static final boolean USE_NPCS_SCRIPTABLE = true; //Flag to enable/disable serverside predefined script NPCs. //Events/PQs Configuration public static final boolean USE_OLD_GMS_STYLED_PQ_NPCS = true; //Enables PQ NPCs with similar behaviour to old GMS style, that skips info about the PQs and immediately tries to register the party in. @@ -164,6 +166,7 @@ public class ServerConstants { public static final long PET_LOOT_UPON_ATTACK = (long)(0.7 * 1000); //Time the pet must wait before trying to pick items up. public static final int TOT_MOB_QUEST_REQUIREMENT = 77; //Overwrites old 999-mobs requirement for the ToT questline with new requirement value, set 0 for default. public static final int MOB_REACTOR_REFRESH_TIME = 30 * 1000; //Overwrites refresh time for those reactors oriented to inflict damage to bosses (Ice Queen, Riche), set 0 for default. + public static final int PARTY_SEARCH_REENTRY_LIMIT = 10; //Max amount of times a party leader is allowed to persist on the Party Search before entry expiration (thus needing to manually restart the Party Search to be able to search for members). //Dangling Items/Locks Configuration public static final int ITEM_EXPIRE_TIME = 3 * 60 * 1000; //Time before items start disappearing. Recommended to be set up to 3 minutes. diff --git a/src/net/opcodes/SendOpcode.java b/src/net/opcodes/SendOpcode.java index 2e374bffe4..42e14c9b8f 100644 --- a/src/net/opcodes/SendOpcode.java +++ b/src/net/opcodes/SendOpcode.java @@ -266,6 +266,7 @@ public enum SendOpcode { REMOVE_NPC(0x102), SPAWN_NPC_REQUEST_CONTROLLER(0x103), NPC_ACTION(0x104), + SET_NPC_SCRIPTABLE(0x107), SPAWN_HIRED_MERCHANT(0x109), DESTROY_HIRED_MERCHANT(0x10A), UPDATE_HIRED_MERCHANT(0x10B), diff --git a/src/net/server/Server.java b/src/net/server/Server.java index 25ab73af6d..8aa00f8810 100644 --- a/src/net/server/Server.java +++ b/src/net/server/Server.java @@ -1648,6 +1648,10 @@ public class Server { } public boolean validateCharacteridInTransition(IoSession session, int charId) { + if (!ServerConstants.USE_IP_VALIDATION) { + return true; + } + String remoteIp = getRemoteIp(session); lgnWLock.lock(); @@ -1660,6 +1664,10 @@ public class Server { } public Integer freeCharacteridInTransition(IoSession session) { + if (!ServerConstants.USE_IP_VALIDATION) { + return null; + } + String remoteIp = getRemoteIp(session); lgnWLock.lock(); @@ -1671,6 +1679,10 @@ public class Server { } public boolean hasCharacteridInTransition(IoSession session) { + if (!ServerConstants.USE_IP_VALIDATION) { + return true; + } + String remoteIp = getRemoteIp(session); lgnRLock.lock(); diff --git a/src/net/server/audit/locks/MonitoredLockType.java b/src/net/server/audit/locks/MonitoredLockType.java index a477e4ab74..50d86fcbfc 100644 --- a/src/net/server/audit/locks/MonitoredLockType.java +++ b/src/net/server/audit/locks/MonitoredLockType.java @@ -64,6 +64,8 @@ public enum MonitoredLockType { GUILD, PARTY, WORLD_PARTY, + WORLD_PARTY_SEARCH_ECHELON, + WORLD_PARTY_SEARCH_STORAGE, WORLD_SRVMESSAGES, WORLD_PETS, WORLD_CHARS, diff --git a/src/net/server/channel/Channel.java b/src/net/server/channel/Channel.java index 76fdf66f45..71ab69ea0e 100644 --- a/src/net/server/channel/Channel.java +++ b/src/net/server/channel/Channel.java @@ -296,17 +296,22 @@ public final class Channel { } private void closeAllMerchants() { - merchWlock.lock(); try { - final Iterator hmit = hiredMerchants.values().iterator(); - while (hmit.hasNext()) { - hmit.next().forceClose(); - hmit.remove(); + List merchs; + + merchWlock.lock(); + try { + merchs = new ArrayList<>(hiredMerchants.values()); + hiredMerchants.clear(); + } finally { + merchWlock.unlock(); + } + + for (MapleHiredMerchant merch : merchs) { + merch.forceClose(); } } catch (Exception e) { - e.printStackTrace(); - } finally { - merchWlock.unlock(); + e.printStackTrace(); } } diff --git a/src/net/server/channel/handlers/AbstractDealDamageHandler.java b/src/net/server/channel/handlers/AbstractDealDamageHandler.java index 854b6f0979..38ab0d1218 100644 --- a/src/net/server/channel/handlers/AbstractDealDamageHandler.java +++ b/src/net/server/channel/handlers/AbstractDealDamageHandler.java @@ -273,6 +273,17 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl int totDamageToOneMonster = 0; List onedList = attack.allDamage.get(oned); + + if (attack.magic) { // thanks BHB, Alex (CanIGetaPR) for noticing no immunity status check here + if (monster.isBuffed(MonsterStatus.MAGIC_IMMUNITY)) { + Collections.fill(onedList, 1); + } + } else { + if (monster.isBuffed(MonsterStatus.WEAPON_IMMUNITY)) { + Collections.fill(onedList, 1); + } + } + for (Integer eachd : onedList) { if(eachd < 0) eachd += Integer.MAX_VALUE; totDamageToOneMonster += eachd; @@ -453,7 +464,7 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl int skillLv = player.getSkillLevel(threeSnailsId); if(skillLv > 0) { - AbstractPlayerInteraction api = player.getClient().getAbstractPlayerInteraction(); + AbstractPlayerInteraction api = player.getAbstractPlayerInteraction(); int shellId; switch(skillLv) { diff --git a/src/net/server/channel/handlers/GuildOperationHandler.java b/src/net/server/channel/handlers/GuildOperationHandler.java index 1e71f990ad..7d36678730 100644 --- a/src/net/server/channel/handlers/GuildOperationHandler.java +++ b/src/net/server/channel/handlers/GuildOperationHandler.java @@ -77,7 +77,13 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler { Set eligibleMembers = new HashSet<>(MapleGuild.getEligiblePlayersForGuild(mc)); if (eligibleMembers.size() < ServerConstants.CREATE_GUILD_MIN_PARTNERS) { - mc.dropMessage(1, "The Guild you are trying to create don't meet the minimum criteria of number of founders."); + if (mc.getMap().getAllPlayers().size() < ServerConstants.CREATE_GUILD_MIN_PARTNERS) { + mc.dropMessage(1, "The Guild you are trying to create don't meet the minimum criteria of number of founders."); + } else { + // players may be unaware of not belonging on a party in order to become eligible, thanks Hair (Legalize) for pointing this out + mc.dropMessage(1, "Please make sure everyone you are trying to invite is neither on a guild nor on a party."); + } + return; } @@ -91,7 +97,7 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler { eligibleCids.add(chr.getId()); } - c.getWorldServer().getMatchCheckerCoordinator().createMatchConfirmation(MatchCheckerType.GUILD_CREATION, c.getWorld(), mc.getId(), eligibleCids, guildName); + c.getWorldServer().getMatchCheckerCoordinator().createMatchConfirmation(MatchCheckerType.GUILD_CREATION, c.getWorld(), mc.getId(), eligibleCids, guildName); break; case 0x05: if (mc.getGuildId() <= 0 || mc.getGuildRank() > 2) { @@ -246,12 +252,12 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler { int leaderid = wserv.getMatchCheckerCoordinator().getMatchConfirmationLeaderid(mc.getId()); if (leaderid != -1) { boolean result = slea.readByte() != 0; - if (result) { + if (result && wserv.getMatchCheckerCoordinator().isMatchConfirmationActive(mc.getId())) { MapleCharacter leader = wserv.getPlayerStorage().getCharacterById(leaderid); if (leader != null) { int partyid = leader.getPartyId(); if (partyid != -1) { - MapleParty.joinParty(mc, partyid, true); + MapleParty.joinParty(mc, partyid, true); // GMS gimmick "party to form guild" recalled thanks to Vcoc } } } diff --git a/src/net/server/channel/handlers/MesoDropHandler.java b/src/net/server/channel/handlers/MesoDropHandler.java index bed3e23e7b..a7c5bfed43 100644 --- a/src/net/server/channel/handlers/MesoDropHandler.java +++ b/src/net/server/channel/handlers/MesoDropHandler.java @@ -30,29 +30,39 @@ import client.MapleClient; /** * * @author Matze + * @author Ronan - concurrency protection */ public final class MesoDropHandler extends AbstractMaplePacketHandler { @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - MapleCharacter player = c.getPlayer(); - if (!player.isAlive()) { + MapleCharacter player = c.getPlayer(); + if (!player.isAlive()) { + c.announce(MaplePacketCreator.enableActions()); + return; + } + slea.skip(4); + int meso = slea.readInt(); + + if (c.tryacquireClient()) { // thanks imbee for noticing players not being able to throw mesos too fast, dampening gameplay of some classes + try { + if (meso <= player.getMeso() && meso > 9 && meso < 50001) { + player.gainMeso(-meso, false, true, false); + } else { c.announce(MaplePacketCreator.enableActions()); return; + } + } finally { + c.releaseClient(); } - if (!player.canDropMeso()){ - player.announce(MaplePacketCreator.serverNotice(5, "Fast meso drop has been patched, cut that out. ;)")); - return; - } - slea.skip(4); - int meso = slea.readInt(); - if (meso <= player.getMeso() && meso > 9 && meso < 50001) { - player.gainMeso(-meso, false, true, false); - - if (player.attemptCatchFish(meso)) { - player.getMap().disappearingMesoDrop(meso, player, player, player.getPosition()); - } else { - player.getMap().spawnMesoDrop(meso, player.getPosition(), player, player, true, (byte) 2); - } - } + } else { + c.announce(MaplePacketCreator.enableActions()); + return; + } + + if (player.attemptCatchFish(meso)) { + player.getMap().disappearingMesoDrop(meso, player, player, player.getPosition()); + } else { + player.getMap().spawnMesoDrop(meso, player.getPosition(), player, player, true, (byte) 2); + } } } \ No newline at end of file diff --git a/src/net/server/channel/handlers/NewYearCardHandler.java b/src/net/server/channel/handlers/NewYearCardHandler.java index f780cbb396..126deb16f5 100644 --- a/src/net/server/channel/handlers/NewYearCardHandler.java +++ b/src/net/server/channel/handlers/NewYearCardHandler.java @@ -67,8 +67,8 @@ public final class NewYearCardHandler extends AbstractMaplePacketHandler { NewYearCardRecord.saveNewYearCard(newyear); player.addNewYearRecord(newyear); - player.getClient().getAbstractPlayerInteraction().gainItem(2160101, (short)-1); - player.getClient().getAbstractPlayerInteraction().gainItem(4300000, (short) 1); + player.getAbstractPlayerInteraction().gainItem(2160101, (short)-1); + player.getAbstractPlayerInteraction().gainItem(4300000, (short) 1); Server.getInstance().setNewYearCard(newyear); newyear.startNewYearCardTask(); @@ -99,7 +99,7 @@ public final class NewYearCardHandler extends AbstractMaplePacketHandler { newyear.stopNewYearCardTask(); NewYearCardRecord.updateNewYearCard(newyear); - player.getClient().getAbstractPlayerInteraction().gainItem(4301000, (short)1); + player.getAbstractPlayerInteraction().gainItem(4301000, (short)1); if(!newyear.getMessage().isEmpty()) player.dropMessage(6, "[New Year] " + newyear.getSenderName() + ": " + newyear.getMessage()); player.addNewYearRecord(newyear); diff --git a/src/net/server/channel/handlers/PartyOperationHandler.java b/src/net/server/channel/handlers/PartyOperationHandler.java index b80c4cc0d5..0583e56408 100644 --- a/src/net/server/channel/handlers/PartyOperationHandler.java +++ b/src/net/server/channel/handlers/PartyOperationHandler.java @@ -34,14 +34,9 @@ import constants.ServerConstants; import net.server.coordinator.MapleInviteCoordinator; import net.server.coordinator.MapleInviteCoordinator.InviteResult; import net.server.coordinator.MapleInviteCoordinator.InviteType; -import net.server.coordinator.MapleMatchCheckerCoordinator; -import net.server.coordinator.matchchecker.MatchCheckerListenerFactory.MatchCheckerType; -import scripting.event.EventInstanceManager; -import server.maps.MapleMap; import tools.Pair; import java.util.List; -import server.partyquest.MonsterCarnival; public final class PartyOperationHandler extends AbstractMaplePacketHandler { diff --git a/src/net/server/channel/handlers/PlayerInteractionHandler.java b/src/net/server/channel/handlers/PlayerInteractionHandler.java index 68b5a59236..9281dd3f66 100644 --- a/src/net/server/channel/handlers/PlayerInteractionHandler.java +++ b/src/net/server/channel/handlers/PlayerInteractionHandler.java @@ -572,11 +572,11 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler { } short perBundle = slea.readShort(); - + if (ItemConstants.isRechargeable(ivItem.getItemId())) { perBundle = 1; bundles = 1; - } else if (chr.getItemQuantity(ivItem.getItemId(), false) < perBundle * bundles) { + } else if (ivItem.getQuantity() < (bundles * perBundle)) { // thanks GabrielSin for finding a dupe here c.announce(MaplePacketCreator.serverNotice(1, "Could not perform shop operation with that item.")); c.announce(MaplePacketCreator.enableActions()); return; diff --git a/src/net/server/channel/handlers/PlayerLoggedinHandler.java b/src/net/server/channel/handlers/PlayerLoggedinHandler.java index 0523d9c586..223149d23e 100644 --- a/src/net/server/channel/handlers/PlayerLoggedinHandler.java +++ b/src/net/server/channel/handlers/PlayerLoggedinHandler.java @@ -58,6 +58,7 @@ import client.inventory.MapleInventory; import client.inventory.MapleInventoryType; import client.inventory.MaplePet; import constants.GameConstants; +import constants.ScriptableNPCConstants; import constants.ServerConstants; import java.util.Collections; import java.util.Comparator; @@ -404,6 +405,10 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler { eim.registerPlayer(player); } } + + if (ServerConstants.USE_NPCS_SCRIPTABLE) { + c.announce(MaplePacketCreator.setNPCScriptable(ScriptableNPCConstants.SCRIPTABLE_NPCS)); + } } finally { c.releaseClient(); } diff --git a/src/net/server/channel/handlers/TakeDamageHandler.java b/src/net/server/channel/handlers/TakeDamageHandler.java index 4dc7677ed0..17d3b65ce2 100644 --- a/src/net/server/channel/handlers/TakeDamageHandler.java +++ b/src/net/server/channel/handlers/TakeDamageHandler.java @@ -212,21 +212,30 @@ public final class TakeDamageHandler extends AbstractMaplePacketHandler { chr.getAutobanManager().resetMisses(); } if (damage > 0 && !chr.isHidden()) { - if (attacker != null && damagefrom == -1 && chr.getBuffedValue(MapleBuffStat.POWERGUARD) != null) { // PG works on bosses, but only at half of the rate. - int bouncedamage = (int) (damage * (chr.getBuffedValue(MapleBuffStat.POWERGUARD).doubleValue() / (attacker.isBoss() ? 200 : 100))); - bouncedamage = Math.min(bouncedamage, attacker.getMaxHp() / 10); - damage -= bouncedamage; - map.damageMonster(chr, attacker, bouncedamage); - map.broadcastMessage(chr, MaplePacketCreator.damageMonster(oid, bouncedamage), false, true); - attacker.aggroMonsterDamage(chr, bouncedamage); - } - if (attacker != null && damagefrom == -1 && chr.getBuffedValue(MapleBuffStat.BODY_PRESSURE) != null) { - Skill skill = SkillFactory.getSkill(Aran.BODY_PRESSURE); - final MapleStatEffect eff = skill.getEffect(chr.getSkillLevel(skill)); - if (!attacker.alreadyBuffedStats().contains(MonsterStatus.NEUTRALISE)) { - if (!attacker.isBoss() && eff.makeChanceResult()) { - attacker.applyStatus(chr, new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.NEUTRALISE, 1), skill, null, false), false, (eff.getDuration()/10) * 2, false); + if (attacker != null) { + if (damagefrom == -1) { + if (chr.getBuffedValue(MapleBuffStat.POWERGUARD) != null) { // PG works on bosses, but only at half of the rate. + int bouncedamage = (int) (damage * (chr.getBuffedValue(MapleBuffStat.POWERGUARD).doubleValue() / (attacker.isBoss() ? 200 : 100))); + bouncedamage = Math.min(bouncedamage, attacker.getMaxHp() / 10); + damage -= bouncedamage; + map.damageMonster(chr, attacker, bouncedamage); + map.broadcastMessage(chr, MaplePacketCreator.damageMonster(oid, bouncedamage), false, true); + attacker.aggroMonsterDamage(chr, bouncedamage); } + MapleStatEffect bPressure = chr.getBuffEffect(MapleBuffStat.COMBO_BARRIER); + if (bPressure != null) { + Skill skill = SkillFactory.getSkill(Aran.BODY_PRESSURE); + if (!attacker.alreadyBuffedStats().contains(MonsterStatus.NEUTRALISE)) { + if (!attacker.isBoss() && bPressure.makeChanceResult()) { + attacker.applyStatus(chr, new MonsterStatusEffect(Collections.singletonMap(MonsterStatus.NEUTRALISE, 1), skill, null, false), false, (bPressure.getDuration() / 10) * 2, false); + } + } + } + } + + MapleStatEffect cBarrier = chr.getBuffEffect(MapleBuffStat.COMBO_BARRIER); // thanks BHB for noticing Combo Barrier buff not working + if (cBarrier != null) { + damage *= (cBarrier.getX() / 1000.0); } } if (damagefrom != -3 && damagefrom != -4) { diff --git a/src/net/server/channel/handlers/UseGachaExpHandler.java b/src/net/server/channel/handlers/UseGachaExpHandler.java index 862ceefd19..514539d67e 100644 --- a/src/net/server/channel/handlers/UseGachaExpHandler.java +++ b/src/net/server/channel/handlers/UseGachaExpHandler.java @@ -30,14 +30,24 @@ import tools.data.input.SeekableLittleEndianAccessor; /** * - * @author kevintjuh93 + * @author kevintjuh93; modified by Ronan */ public class UseGachaExpHandler extends AbstractMaplePacketHandler { + + @Override public void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - if (c.getPlayer().getGachaExp() == 0) { - AutobanFactory.GACHA_EXP.autoban(c.getPlayer(), "Player tried to redeem GachaEXP, but had none to redeem."); + + if (c.tryacquireClient()) { + try { + if (c.getPlayer().getGachaExp() <= 0) { + AutobanFactory.GACHA_EXP.autoban(c.getPlayer(), "Player tried to redeem GachaEXP, but had none to redeem."); + } + c.getPlayer().gainGachaExp(); + } finally { + c.releaseClient(); + } } - c.getPlayer().gainGachaExp(); + c.announce(MaplePacketCreator.enableActions()); } } diff --git a/src/net/server/channel/handlers/UseMountFoodHandler.java b/src/net/server/channel/handlers/UseMountFoodHandler.java index c586c8defb..a626d5ec1c 100644 --- a/src/net/server/channel/handlers/UseMountFoodHandler.java +++ b/src/net/server/channel/handlers/UseMountFoodHandler.java @@ -31,6 +31,7 @@ import constants.ExpTable; import net.AbstractMaplePacketHandler; import client.inventory.manipulator.MapleInventoryManipulator; import tools.MaplePacketCreator; +import tools.Pair; import tools.data.input.SeekableLittleEndianAccessor; /** @@ -50,6 +51,8 @@ public final class UseMountFoodHandler extends AbstractMaplePacketHandler { if (c.tryacquireClient()) { try { + Boolean mountLevelup = null; + useInv.lockInventory(); try { Item item = useInv.getItem(pos); @@ -67,7 +70,8 @@ public final class UseMountFoodHandler extends AbstractMaplePacketHandler { if (levelup) { mount.setLevel(level + 1); } - chr.getMap().broadcastMessage(MaplePacketCreator.updateMount(chr.getId(), mount, levelup)); + + mountLevelup = levelup; } MapleInventoryManipulator.removeById(c, MapleInventoryType.USE, itemid, 1, true, false); @@ -75,6 +79,10 @@ public final class UseMountFoodHandler extends AbstractMaplePacketHandler { } finally { useInv.unlockInventory(); } + + if (mountLevelup != null) { + chr.getMap().broadcastMessage(MaplePacketCreator.updateMount(chr.getId(), mount, mountLevelup)); + } } finally { c.releaseClient(); } diff --git a/src/net/server/channel/handlers/UseSolomonHandler.java b/src/net/server/channel/handlers/UseSolomonHandler.java index 78db56df87..68a89f3d11 100644 --- a/src/net/server/channel/handlers/UseSolomonHandler.java +++ b/src/net/server/channel/handlers/UseSolomonHandler.java @@ -21,7 +21,9 @@ */ package net.server.channel.handlers; +import client.MapleCharacter; import client.MapleClient; +import client.inventory.MapleInventory; import client.inventory.Item; import client.inventory.MapleInventoryType; import net.AbstractMaplePacketHandler; @@ -32,7 +34,7 @@ import tools.data.input.SeekableLittleEndianAccessor; /** * - * @author XoticStory; modified by kevintjuh93 + * @author XoticStory; modified by kevintjuh93, Ronan */ public final class UseSolomonHandler extends AbstractMaplePacketHandler { @@ -42,16 +44,35 @@ public final class UseSolomonHandler extends AbstractMaplePacketHandler { short slot = slea.readShort(); int itemId = slea.readInt(); MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); - Item slotItem = c.getPlayer().getInventory(MapleInventoryType.USE).getItem(slot); - int gachaexp = ii.getExpById(itemId); - if (c.getPlayer().getInventory(MapleInventoryType.USE).countById(itemId) <= 0 || slotItem.getItemId() != itemId || c.getPlayer().getLevel() > ii.getMaxLevelById(itemId)) { - return; + + if (c.tryacquireClient()) { + try { + MapleCharacter chr = c.getPlayer(); + MapleInventory inv = chr.getInventory(MapleInventoryType.USE); + inv.lockInventory(); + try { + Item slotItem = inv.getItem(slot); + if (slotItem == null) { + return; + } + + long gachaexp = ii.getExpById(itemId); + if (slotItem.getItemId() != itemId || slotItem.getQuantity() <= 0 || chr.getLevel() > ii.getMaxLevelById(itemId)) { + return; + } + if (gachaexp + chr.getGachaExp() > Integer.MAX_VALUE) { + return; + } + chr.addGachaExp((int) gachaexp); + MapleInventoryManipulator.removeFromSlot(c, MapleInventoryType.USE, slot, (short) 1, false); + } finally { + inv.unlockInventory(); + } + } finally { + c.releaseClient(); + } } - if ((c.getPlayer().getGachaExp() + gachaexp) > Integer.MAX_VALUE) { - return; - } - c.getPlayer().gainGachaExp(gachaexp); - MapleInventoryManipulator.removeFromSlot(c, MapleInventoryType.USE, slot, (short) 1, false); + c.announce(MaplePacketCreator.enableActions()); } } diff --git a/src/net/server/coordinator/MapleMatchCheckerCoordinator.java b/src/net/server/coordinator/MapleMatchCheckerCoordinator.java index 3172cab6ed..1caa9747bf 100644 --- a/src/net/server/coordinator/MapleMatchCheckerCoordinator.java +++ b/src/net/server/coordinator/MapleMatchCheckerCoordinator.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.Semaphore; @@ -76,6 +77,7 @@ public class MapleMatchCheckerCoordinator { private Map confirmingMembers = new HashMap<>(); private int confirmCount; + private boolean active = true; private String message; @@ -108,10 +110,30 @@ public class MapleMatchCheckerCoordinator { return false; } + private boolean isMatchActive() { + return active; + } + + private void setMatchActive(boolean a) { + active = a; + } + private Set getMatchPlayers() { return confirmingMembers.keySet(); } + private Set getAcceptedMatchPlayers() { + Set s = new HashSet<>(); + + for (Entry e : confirmingMembers.entrySet()) { + if (e.getValue().getAccept()) { + s.add(e.getKey()); + } + } + + return s; + } + private Set getMatchCharacters() { Set players = new HashSet<>(); @@ -197,6 +219,20 @@ public class MapleMatchCheckerCoordinator { return true; } + private void reenablePlayerMatching(Set matchPlayers) { + for (Integer cid : matchPlayers) { + MapleMatchCheckingElement mmce = matchEntries.get(cid); + + if (mmce != null) { + synchronized (mmce) { + if (!mmce.isMatchActive()) { + matchEntries.remove(cid); + } + } + } + } + } + public int getMatchConfirmationLeaderid(int cid) { MapleMatchCheckingElement mmce = matchEntries.get(cid); if (mmce != null) { @@ -215,6 +251,15 @@ public class MapleMatchCheckerCoordinator { } } + public boolean isMatchConfirmationActive(int cid) { + MapleMatchCheckingElement mmce = matchEntries.get(cid); + if (mmce != null) { + return mmce.active; + } else { + return false; + } + } + private void createMatchConfirmationInternal(MatchCheckerType matchType, int world, int leaderCid, AbstractMatchCheckerListener leaderListener, Set players, String message) { MapleMatchCheckingElement mmce = new MapleMatchCheckingElement(matchType, leaderCid, world, leaderListener, players, message); @@ -224,9 +269,7 @@ public class MapleMatchCheckerCoordinator { mmce.dispatchMatchCreated(); - if (mmce.acceptEntry(leaderCid)) { - acceptMatchElement(mmce, leaderCid); - } + acceptMatchElement(mmce, leaderCid); } public boolean createMatchConfirmation(MatchCheckerType matchType, int world, int leaderCid, Set players, String message) { @@ -239,6 +282,8 @@ public class MapleMatchCheckerCoordinator { AbstractMatchCheckerListener leaderListener = matchType.getListener(); createMatchConfirmationInternal(matchType, world, leaderCid, leaderListener, players, message); return true; + } else { + reenablePlayerMatching(players); } } finally { unpoolMatchPlayers(players); @@ -255,7 +300,7 @@ public class MapleMatchCheckerCoordinator { } private void disposeMatchElement(MapleMatchCheckingElement mmce) { - Set matchPlayers = mmce.getMatchPlayers(); + Set matchPlayers = mmce.getAcceptedMatchPlayers(); while (!poolMatchPlayers(matchPlayers)) { try { Thread.sleep(1000); @@ -288,6 +333,8 @@ public class MapleMatchCheckerCoordinator { } private void dismissMatchElement(MapleMatchCheckingElement mmce, int cid) { + mmce.setMatchActive(false); + unpoolMatchPlayer(cid); disposeMatchElement(mmce); @@ -302,14 +349,22 @@ public class MapleMatchCheckerCoordinator { if (poolMatchPlayer(cid)) { try { MapleMatchCheckingElement mmce = matchEntries.get(cid); - + if (mmce != null) { - if (accept) { - acceptMatchElement(mmce, cid); - } else { - denyMatchElement(mmce, cid); + synchronized (mmce) { + if (!mmce.isMatchActive()) { // thanks Alex (CanIGetaPR) for noticing that exploiters could stall on match checking + matchEntries.remove(cid); + return false; + } + + if (accept) { + acceptMatchElement(mmce, cid); + } else { + denyMatchElement(mmce, cid); + matchEntries.remove(cid); + } } - + return true; } } finally { @@ -337,8 +392,14 @@ public class MapleMatchCheckerCoordinator { MapleMatchCheckingElement mmce = matchEntries.get(cid); if (mmce != null) { - dismissMatchElement(mmce, cid); - return true; + synchronized (mmce) { + if (!mmce.isMatchActive()) { + return false; + } + + dismissMatchElement(mmce, cid); + return true; + } } } finally { unpoolMatchPlayer(cid); diff --git a/src/net/server/coordinator/MapleSessionCoordinator.java b/src/net/server/coordinator/MapleSessionCoordinator.java index f3d351d3f5..1b3ad471ae 100644 --- a/src/net/server/coordinator/MapleSessionCoordinator.java +++ b/src/net/server/coordinator/MapleSessionCoordinator.java @@ -305,12 +305,12 @@ public class MapleSessionCoordinator { } public void closeLoginSession(IoSession session) { - String remoteIp = getSessionRemoteAddress(session); - Set lrh = loginRemoteHosts.get(remoteIp); + String remoteHost = getSessionRemoteAddress(session); + Set lrh = loginRemoteHosts.get(remoteHost); if (lrh != null) { lrh.remove(session); if (lrh.isEmpty()) { - loginRemoteHosts.remove(remoteIp); + loginRemoteHosts.remove(remoteHost); } } diff --git a/src/net/server/coordinator/matchchecker/MatchCheckerListenerFactory.java b/src/net/server/coordinator/matchchecker/MatchCheckerListenerFactory.java index 648ab081b7..941fcd4186 100644 --- a/src/net/server/coordinator/matchchecker/MatchCheckerListenerFactory.java +++ b/src/net/server/coordinator/matchchecker/MatchCheckerListenerFactory.java @@ -20,6 +20,7 @@ package net.server.coordinator.matchchecker; import net.server.coordinator.matchchecker.listener.MatchCheckerGuildCreation; +import net.server.coordinator.matchchecker.listener.MatchCheckerCPQChallenge; /** * @@ -29,7 +30,8 @@ public class MatchCheckerListenerFactory { public enum MatchCheckerType { - GUILD_CREATION(MatchCheckerGuildCreation.loadListener()); + GUILD_CREATION(MatchCheckerGuildCreation.loadListener()), + CPQ_CHALLENGE(MatchCheckerCPQChallenge.loadListener()); private final AbstractMatchCheckerListener listener; diff --git a/src/net/server/coordinator/matchchecker/listener/MatchCheckerCPQChallenge.java b/src/net/server/coordinator/matchchecker/listener/MatchCheckerCPQChallenge.java new file mode 100644 index 0000000000..b0c9cf0738 --- /dev/null +++ b/src/net/server/coordinator/matchchecker/listener/MatchCheckerCPQChallenge.java @@ -0,0 +1,119 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +package net.server.coordinator.matchchecker.listener; + +import client.MapleCharacter; +import constants.LanguageConstants; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import net.server.coordinator.matchchecker.AbstractMatchCheckerListener; +import net.server.coordinator.matchchecker.MatchCheckerListenerRecipe; +import net.server.world.MaplePartyCharacter; +import scripting.npc.NPCConversationManager; +import scripting.npc.NPCScriptManager; + +/** + * + * @author Ronan + */ +public class MatchCheckerCPQChallenge implements MatchCheckerListenerRecipe { + + public static AbstractMatchCheckerListener loadListener() { + return (new MatchCheckerCPQChallenge()).getListener(); + } + + private static MapleCharacter getChallenger(int leaderid, Set matchPlayers) { + MapleCharacter leader = null; + for (MapleCharacter chr : matchPlayers) { + if (chr.getId() == leaderid && chr.getClient() != null) { + leader = chr; + break; + } + } + + return leader; + } + + @Override + public AbstractMatchCheckerListener getListener() { + return new AbstractMatchCheckerListener() { + + @Override + public void onMatchCreated(MapleCharacter leader, Set nonLeaderMatchPlayers, String message) { + NPCConversationManager cm = leader.getClient().getCM(); + int npcid = cm.getNpc(); + + MapleCharacter ldr = null; + for (MapleCharacter chr : nonLeaderMatchPlayers) { + ldr = chr; + break; + } + + MapleCharacter chr = leader; + + List chrMembers = new LinkedList<>(); + for (MaplePartyCharacter mpc : chr.getParty().getMembers()) { + chrMembers.add(mpc); + } + + if (message.contentEquals("cpq1")) { + NPCScriptManager.getInstance().start("cpqchallenge", ldr.getClient(), npcid, chrMembers); + } else { + NPCScriptManager.getInstance().start("cpqchallenge2", ldr.getClient(), npcid, chrMembers); + } + + cm.sendOk(LanguageConstants.getMessage(chr, LanguageConstants.CPQChallengeRoomSent)); + } + + @Override + public void onMatchAccepted(int leaderid, Set matchPlayers, String message) { + MapleCharacter chr = getChallenger(leaderid, matchPlayers); + + MapleCharacter ldr = null; + for (MapleCharacter ch : matchPlayers) { + if (ch != chr) { + ldr = ch; + break; + } + } + + if (message.contentEquals("cpq1")) { + ldr.getClient().getCM().startCPQ(chr, ldr.getMapId() + 1); + } else { + ldr.getClient().getCM().startCPQ2(chr, ldr.getMapId() + 1); + } + + ldr.getParty().setEnemy(chr.getParty()); + chr.getParty().setEnemy(ldr.getParty()); + chr.setChallenged(false); + } + + @Override + public void onMatchDeclined(int leaderid, Set matchPlayers, String message) { + MapleCharacter chr = getChallenger(leaderid, matchPlayers); + chr.dropMessage(5, LanguageConstants.getMessage(chr, LanguageConstants.CPQChallengeRoomDenied)); + } + + @Override + public void onMatchDismissed(int leaderid, Set matchPlayers, String message) {} + }; + } +} diff --git a/src/net/server/coordinator/matchchecker/listener/MatchCheckerGuildCreation.java b/src/net/server/coordinator/matchchecker/listener/MatchCheckerGuildCreation.java index 49f52a4b3b..ed235a00d9 100644 --- a/src/net/server/coordinator/matchchecker/listener/MatchCheckerGuildCreation.java +++ b/src/net/server/coordinator/matchchecker/listener/MatchCheckerGuildCreation.java @@ -166,13 +166,29 @@ public class MatchCheckerGuildCreation implements MatchCheckerListenerRecipe { @Override public void onMatchDismissed(int leaderid, Set matchPlayers, String message) { + + MapleCharacter leader = null; + for (MapleCharacter chr : matchPlayers) { + if (chr.getId() == leaderid) { + leader = chr; + break; + } + } + + String msg; + if (leader != null && leader.getParty() == null) { + msg = "The Guild creation has been dismissed since the leader left the founding party."; + } else { + msg = "The Guild creation has been dismissed since a member was already in a party when they answered."; + } + for (MapleCharacter chr : matchPlayers) { if (chr.getId() == leaderid && chr.getClient() != null) { MapleParty.leaveParty(chr.getParty(), chr.getClient()); } if (chr.isLoggedinWorld()) { - chr.message("All cofounders must not be in a party when performing the Guild creation."); + chr.message(msg); } } } diff --git a/src/net/server/guild/MapleGuild.java b/src/net/server/guild/MapleGuild.java index 3c614d74bd..f544f6e55c 100644 --- a/src/net/server/guild/MapleGuild.java +++ b/src/net/server/guild/MapleGuild.java @@ -50,6 +50,7 @@ import net.server.audit.locks.MonitoredLockType; import net.server.coordinator.MapleInviteCoordinator; import net.server.coordinator.MapleInviteCoordinator.InviteType; import net.server.coordinator.MapleInviteCoordinator.InviteResult; +import net.server.coordinator.MapleMatchCheckerCoordinator; public class MapleGuild { @@ -752,8 +753,9 @@ public class MapleGuild { Set guildMembers = new HashSet<>(); guildMembers.add(guildLeader); + MapleMatchCheckerCoordinator mmce = guildLeader.getWorldServer().getMatchCheckerCoordinator(); for (MapleCharacter chr : guildLeader.getMap().getAllPlayers()) { - if (chr.getParty() == null && chr.getGuild() == null) { + if (chr.getParty() == null && chr.getGuild() == null && mmce.getMatchConfirmationLeaderid(chr.getId()) == -1) { guildMembers.add(chr); } } @@ -765,7 +767,7 @@ public class MapleGuild { try { ResultSet rs; Connection con = DatabaseConnection.getConnection(); - try (PreparedStatement ps = con.prepareStatement("SELECT `name`, `GP`, `logoBG`, `logoBGColor`, `logo`, `logoColor` FROM guilds WHERE NOT `guildid` = '1' ORDER BY `GP` DESC LIMIT 50")) { + try (PreparedStatement ps = con.prepareStatement("SELECT `name`, `GP`, `logoBG`, `logoBGColor`, `logo`, `logoColor` FROM guilds ORDER BY `GP` DESC LIMIT 50")) { rs = ps.executeQuery(); c.announce(MaplePacketCreator.showGuildRanks(npcid, rs)); } diff --git a/src/net/server/handlers/login/LoginPasswordHandler.java b/src/net/server/handlers/login/LoginPasswordHandler.java index 818139142b..3a2041d510 100644 --- a/src/net/server/handlers/login/LoginPasswordHandler.java +++ b/src/net/server/handlers/login/LoginPasswordHandler.java @@ -64,15 +64,17 @@ public final class LoginPasswordHandler implements MaplePacketHandler { public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { String remoteHost = getRemoteIp(c.getSession()); if (!remoteHost.contentEquals("null")) { - if (remoteHost.startsWith("127.")) { - if (!ServerConstants.LOCALSERVER) { // thanks Mills for noting HOST can also have a field named "localhost" - c.announce(MaplePacketCreator.getLoginFailed(13)); // cannot login as localhost if it's not a local server - return; - } - } else { - if (ServerConstants.LOCALSERVER) { - c.announce(MaplePacketCreator.getLoginFailed(13)); // cannot login as non-localhost if it's a local server - return; + if (ServerConstants.USE_IP_VALIDATION) { // thanks Alex (CanIGetaPR) for suggesting IP validation as a server flag + if (remoteHost.startsWith("127.")) { + if (!ServerConstants.LOCALSERVER) { // thanks Mills for noting HOST can also have a field named "localhost" + c.announce(MaplePacketCreator.getLoginFailed(13)); // cannot login as localhost if it's not a local server + return; + } + } else { + if (ServerConstants.LOCALSERVER) { + c.announce(MaplePacketCreator.getLoginFailed(13)); // cannot login as non-localhost if it's a local server + return; + } } } } else { diff --git a/src/net/server/world/MapleParty.java b/src/net/server/world/MapleParty.java index 714710596c..2dec908d1c 100644 --- a/src/net/server/world/MapleParty.java +++ b/src/net/server/world/MapleParty.java @@ -334,7 +334,7 @@ public class MapleParty { player.dropMessage(5, "You cannot request a party creation while participating the Ariant Battle Arena."); return false; } - + MaplePartyCharacter partyplayer = new MaplePartyCharacter(player); party = player.getWorldServer().createParty(partyplayer); player.setParty(party); @@ -343,6 +343,7 @@ public class MapleParty { player.silentPartyUpdate(); player.partyOperationUpdate(party, null); + player.announce(MaplePacketCreator.partyCreated(party, partyplayer.getId())); return true; @@ -365,7 +366,7 @@ public class MapleParty { if (party.getMembers().size() < 6) { MaplePartyCharacter partyplayer = new MaplePartyCharacter(player); player.getMap().addPartyMember(player); - + world.updateParty(party.getId(), PartyOperation.JOIN, partyplayer); player.receivePartyMemberHP(); player.updatePartyMemberHP(); diff --git a/src/scripting/event/EventInstanceManager.java b/src/scripting/event/EventInstanceManager.java index 9c327273a9..695e4d4962 100644 --- a/src/scripting/event/EventInstanceManager.java +++ b/src/scripting/event/EventInstanceManager.java @@ -943,7 +943,7 @@ public class EventInstanceManager { } private void dropExclusiveItems(MapleCharacter chr) { - AbstractPlayerInteraction api = chr.getClient().getAbstractPlayerInteraction(); + AbstractPlayerInteraction api = chr.getAbstractPlayerInteraction(); for(Integer item: exclusiveItems) { api.removeAll(item); @@ -1051,7 +1051,7 @@ public class EventInstanceManager { if(!hasRewardSlot(player, eventLevel)) return false; - AbstractPlayerInteraction api = player.getClient().getAbstractPlayerInteraction(); + AbstractPlayerInteraction api = player.getAbstractPlayerInteraction(); int rnd = (int)Math.floor(Math.random() * rewardsSet.size()); api.gainItem(rewardsSet.get(rnd), rewardsQty.get(rnd).shortValue()); diff --git a/src/scripting/npc/NPCConversationManager.java b/src/scripting/npc/NPCConversationManager.java index fa110996f1..b8c1dedeef 100644 --- a/src/scripting/npc/NPCConversationManager.java +++ b/src/scripting/npc/NPCConversationManager.java @@ -62,6 +62,7 @@ import constants.ItemConstants; import constants.LanguageConstants; import net.server.PlayerStorage; import net.server.channel.Channel; +import net.server.coordinator.matchchecker.MatchCheckerListenerFactory.MatchCheckerType; import server.MapleSkillbookInformationProvider; import server.MapleSkillbookInformationProvider.SkillBookEntry; import server.TimerManager; @@ -76,8 +77,9 @@ import tools.packets.Wedding; import java.awt.Point; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import server.MapleMarriage; /** @@ -633,7 +635,7 @@ public class NPCConversationManager extends AbstractPlayerInteraction { } public boolean sendCPQMapLists() { - String msg = LanguageConstants.Languages(getPlayer()).CPQPickRoom; + String msg = LanguageConstants.getMessage(getPlayer(), LanguageConstants.CPQPickRoom); int msgLen = msg.length(); for (int i = 0; i < 6; i++) { if (fieldTaken(i)) { @@ -696,7 +698,7 @@ public class NPCConversationManager extends AbstractPlayerInteraction { mc = ps.getCharacterById(mpc.getId()); if (mc != null) { mc.changeMap(map, map.getPortal(0)); - mc.announce(MaplePacketCreator.serverNotice(6, LanguageConstants.Languages(mc).CPQEntryLobby)); + mc.announce(MaplePacketCreator.serverNotice(6, LanguageConstants.getMessage(mc, LanguageConstants.CPQEntryLobby))); TimerManager tMan = TimerManager.getInstance(); tMan.schedule(new Runnable() { @Override @@ -850,7 +852,7 @@ public class NPCConversationManager extends AbstractPlayerInteraction { } public boolean sendCPQMapLists2() { - String msg = LanguageConstants.Languages(getPlayer()).CPQPickRoom; + String msg = LanguageConstants.getMessage(getPlayer(), LanguageConstants.CPQPickRoom); int msgLen = msg.length(); for (int i = 0; i < 3; i++) { if (fieldTaken2(i)) { @@ -913,7 +915,7 @@ public class NPCConversationManager extends AbstractPlayerInteraction { mc = ps.getCharacterById(mpc.getId()); if (mc != null) { mc.changeMap(map, map.getPortal(0)); - mc.announce(MaplePacketCreator.serverNotice(6, LanguageConstants.Languages(mc).CPQEntryLobby)); + mc.announce(MaplePacketCreator.serverNotice(6, LanguageConstants.getMessage(mc, LanguageConstants.CPQEntryLobby))); TimerManager tMan = TimerManager.getInstance(); tMan.schedule(new Runnable() { @Override @@ -934,14 +936,30 @@ public class NPCConversationManager extends AbstractPlayerInteraction { ex.printStackTrace(); } } - + + public void mapClock(int time) { + getPlayer().getMap().broadcastMessage(MaplePacketCreator.getClock(time)); + } + + private boolean sendCPQChallenge(String cpqType, int leaderid) { + Set cpqLeaders = new HashSet<>(); + cpqLeaders.add(leaderid); + cpqLeaders.add(getPlayer().getId()); + + return c.getWorldServer().getMatchCheckerCoordinator().createMatchConfirmation(MatchCheckerType.CPQ_CHALLENGE, c.getWorld(), getPlayer().getId(), cpqLeaders, cpqType); + } + + public void answerCPQChallenge(boolean accept) { + c.getWorldServer().getMatchCheckerCoordinator().answerMatchConfirmation(getPlayer().getId(), accept); + } + public void challengeParty2(int field) { MapleCharacter leader = null; MapleMap map = c.getChannelServer().getMapFactory().getMap(980031000 + 1000 * field); for (MapleMapObject mmo : map.getAllPlayer()) { MapleCharacter mc = (MapleCharacter) mmo; if (mc.getParty() == null) { - sendOk(LanguageConstants.Languages(mc).CPQFindError); + sendOk(LanguageConstants.getMessage(mc, LanguageConstants.CPQFindError)); return; } if (mc.getParty().getLeader().getId() == mc.getId()) { @@ -951,23 +969,17 @@ public class NPCConversationManager extends AbstractPlayerInteraction { } if (leader != null) { if (!leader.isChallenged()) { - List members = new LinkedList<>(); - for (MaplePartyCharacter fucker : c.getPlayer().getParty().getMembers()) { - members.add(fucker); + if (!sendCPQChallenge("cpq2", leader.getId())) { + sendOk(LanguageConstants.getMessage(leader, LanguageConstants.CPQChallengeRoomAnswer)); } - NPCScriptManager.getInstance().start("cpqchallenge2", leader.getClient(), npc, members); } else { - sendOk(LanguageConstants.Languages(leader).CPQChallengeRoomAnswer); + sendOk(LanguageConstants.getMessage(leader, LanguageConstants.CPQChallengeRoomAnswer)); } } else { - sendOk(LanguageConstants.Languages(leader).CPQLeaderNotFound); + sendOk(LanguageConstants.getMessage(leader, LanguageConstants.CPQLeaderNotFound)); } } - - public void mapClock(int time) { - getPlayer().getMap().broadcastMessage(MaplePacketCreator.getClock(time)); - } - + public void challengeParty(int field) { MapleCharacter leader = null; MapleMap map = c.getChannelServer().getMapFactory().getMap(980000100 + 100 * field); @@ -978,7 +990,7 @@ public class NPCConversationManager extends AbstractPlayerInteraction { for (MapleMapObject mmo : map.getAllPlayer()) { MapleCharacter mc = (MapleCharacter) mmo; if (mc.getParty() == null) { - sendOk(LanguageConstants.Languages(mc).CPQFindError); + sendOk(LanguageConstants.getMessage(mc, LanguageConstants.CPQFindError)); return; } if (mc.getParty().getLeader().getId() == mc.getId()) { @@ -988,18 +1000,14 @@ public class NPCConversationManager extends AbstractPlayerInteraction { } if (leader != null) { if (!leader.isChallenged()) { - List members = new LinkedList<>(); - for (MaplePartyCharacter mpc : c.getPlayer().getParty().getMembers()) { - members.add(mpc); + if (!sendCPQChallenge("cpq1", leader.getId())) { + sendOk(LanguageConstants.getMessage(leader, LanguageConstants.CPQChallengeRoomAnswer)); } - - NPCScriptManager.getInstance().start("cpqchallenge", leader.getClient(), npc, members); - sendOk(LanguageConstants.Languages(leader).CPQChallengeRoomSent); } else { - sendOk(LanguageConstants.Languages(leader).CPQChallengeRoomAnswer); + sendOk(LanguageConstants.getMessage(leader, LanguageConstants.CPQChallengeRoomAnswer)); } } else { - sendOk(LanguageConstants.Languages(leader).CPQLeaderNotFound); + sendOk(LanguageConstants.getMessage(leader, LanguageConstants.CPQLeaderNotFound)); } } diff --git a/src/server/MapleSkillbookInformationProvider.java b/src/server/MapleSkillbookInformationProvider.java index 90d37eb3c3..5aa0184832 100644 --- a/src/server/MapleSkillbookInformationProvider.java +++ b/src/server/MapleSkillbookInformationProvider.java @@ -141,7 +141,7 @@ public class MapleSkillbookInformationProvider { if(token.contains("/imgdir")) { status -= 1; } - else if(token.contains("imgdir")) { + else if(token.contains("imgdir") && !token.endsWith("/>")) { // '\>' XML node description not being accounted, issue found thanks to Robin Schulz, CanIGetaPR status += 1; } } @@ -182,7 +182,7 @@ public class MapleSkillbookInformationProvider { currentItemid = 0; currentCount = 0; } - else if(token.contains("imgdir")) { + else if(token.contains("imgdir") && !token.endsWith("/>")) { status += 1; } else { @@ -203,7 +203,7 @@ public class MapleSkillbookInformationProvider { if(token.contains("/imgdir")) { status -= 1; } - else if(token.contains("imgdir")) { + else if(token.contains("imgdir") && !token.endsWith("/>")) { if(status == 1) { //getting QuestId d = getName(token); questId = Integer.parseInt(d); @@ -228,19 +228,22 @@ public class MapleSkillbookInformationProvider { } private static void fetchSkillbooksFromQuests() { - String line; + String line = ""; + int lineNumber = 0; // add line number, thanks to Alex (CanIGetaPR) + try { fileReader = new InputStreamReader(new FileInputStream(wzPath + "/Quest.wz/Act.img.xml"), "UTF-8"); bufferedReader = new BufferedReader(fileReader); while((line = bufferedReader.readLine()) != null) { + lineNumber++; translateActToken(line); } bufferedReader.close(); fileReader.close(); } catch(IOException ioe) { - System.out.println("Failed to read Quest.wz file."); + System.out.println("Failed to read Quest.wz file. Line " + lineNumber + ": " + line); ioe.printStackTrace(); } } diff --git a/src/server/MapleStatEffect.java b/src/server/MapleStatEffect.java index 2c08b7354f..62fcb526e4 100644 --- a/src/server/MapleStatEffect.java +++ b/src/server/MapleStatEffect.java @@ -805,7 +805,7 @@ public class MapleStatEffect { int mpchange = calcMPChange(applyfrom, primary); if (primary) { if (itemConNo != 0) { - if (!applyto.getClient().getAbstractPlayerInteraction().hasItem(itemCon, itemConNo)) { + if (!applyto.getAbstractPlayerInteraction().hasItem(itemCon, itemConNo)) { applyto.announce(MaplePacketCreator.enableActions()); return false; } diff --git a/src/server/life/MapleMonster.java b/src/server/life/MapleMonster.java index ed7a46af65..31a6f28b54 100644 --- a/src/server/life/MapleMonster.java +++ b/src/server/life/MapleMonster.java @@ -1750,6 +1750,8 @@ public class MapleMonster extends AbstractLoadedMapleLife { changeLevelByDifficulty(difficulty, pqMob); } + // --------------------------------------------------------------------------------- + private boolean isPuppetInVicinity(MapleSummon summon) { return summon.getPosition().distanceSq(this.getPosition()) < 177777; } diff --git a/src/server/maps/MapleHiredMerchant.java b/src/server/maps/MapleHiredMerchant.java index 06853f45e8..c7c909cad1 100644 --- a/src/server/maps/MapleHiredMerchant.java +++ b/src/server/maps/MapleHiredMerchant.java @@ -34,6 +34,7 @@ import com.mysql.jdbc.Statement; import constants.ServerConstants; import java.sql.Connection; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; @@ -297,8 +298,20 @@ public class MapleHiredMerchant extends AbstractMapleMapObject { try { Connection con = DatabaseConnection.getConnection(); - try (PreparedStatement ps = con.prepareStatement("UPDATE characters SET MerchantMesos = MerchantMesos + " + price + " WHERE id = ?", Statement.RETURN_GENERATED_KEYS)) { + long merchantMesos = 0; + try (PreparedStatement ps = con.prepareStatement("SELECT MerchantMesos FROM characters WHERE id = ?")) { ps.setInt(1, ownerId); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + merchantMesos = rs.getInt(1); + } + } + } + merchantMesos += price; + + try (PreparedStatement ps = con.prepareStatement("UPDATE characters SET MerchantMesos = ? WHERE id = ?", Statement.RETURN_GENERATED_KEYS)) { + ps.setInt(1, (int) Math.min(merchantMesos, Integer.MAX_VALUE)); + ps.setInt(2, ownerId); ps.executeUpdate(); } diff --git a/src/server/maps/MapleMap.java b/src/server/maps/MapleMap.java index 131c94ffed..990e306413 100644 --- a/src/server/maps/MapleMap.java +++ b/src/server/maps/MapleMap.java @@ -1380,12 +1380,13 @@ public class MapleMap { } if (chr == null) { - if(removeKilledMonsterObject(monster)) { + if (removeKilledMonsterObject(monster)) { monster.dispatchMonsterKilled(false); broadcastMessage(MaplePacketCreator.killMonster(monster.getObjectId(), animation), monster.getPosition()); + monster.aggroSwitchController(null, false); } } else { - if(removeKilledMonsterObject(monster)) { + 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."); } @@ -3676,7 +3677,7 @@ public class MapleMap { } private static double getCurrentSpawnRate(int numPlayers) { - return 0.550 + (0.075 * Math.min(6, numPlayers)); + return 0.70 + (0.05 * Math.min(6, numPlayers)); } private int getNumShouldSpawn(int numPlayers) { diff --git a/src/server/maps/MapleMapFactory.java b/src/server/maps/MapleMapFactory.java index 8da376a8f4..af9c2a8f87 100644 --- a/src/server/maps/MapleMapFactory.java +++ b/src/server/maps/MapleMapFactory.java @@ -110,7 +110,7 @@ public class MapleMapFactory { int y = MapleDataTool.getInt(life.getChildByPath("y")); int hide = MapleDataTool.getInt("hide", life, 0); int mobTime = MapleDataTool.getInt("mobTime", life, 0); - + loadLifeRaw(map, Integer.parseInt(id), type, cy, f, fh, rx0, rx1, x, y, hide, mobTime, team); } } diff --git a/src/server/partyquest/AriantColiseum.java b/src/server/partyquest/AriantColiseum.java index ea96536789..02d2d71e27 100644 --- a/src/server/partyquest/AriantColiseum.java +++ b/src/server/partyquest/AriantColiseum.java @@ -193,7 +193,7 @@ public class AriantColiseum { chr.setAriantColiseum(null); int shards = chr.countItem(4031868); - chr.getClient().getAbstractPlayerInteraction().removeAll(4031868); + chr.getAbstractPlayerInteraction().removeAll(4031868); chr.updateAriantScore(shards); } } diff --git a/src/server/partyquest/MonsterCarnival.java b/src/server/partyquest/MonsterCarnival.java index 45d234dbf9..aed6f96219 100644 --- a/src/server/partyquest/MonsterCarnival.java +++ b/src/server/partyquest/MonsterCarnival.java @@ -59,7 +59,7 @@ public class MonsterCarnival { mc.setTeam(0); mc.setFestivalPoints(0); mc.forceChangeMap(map, map.getPortal(redPortal)); - mc.dropMessage(6, LanguageConstants.Languages(mc).CPQEntry); + mc.dropMessage(6, LanguageConstants.getMessage(mc, LanguageConstants.CPQEntry)); if (p1.getLeader().getId() == mc.getId()) { leader1 = mc; } @@ -73,7 +73,7 @@ public class MonsterCarnival { mc.setTeam(1); mc.setFestivalPoints(0); mc.forceChangeMap(map, map.getPortal(bluePortal)); - mc.dropMessage(6, LanguageConstants.Languages(mc).CPQEntry); + mc.dropMessage(6, LanguageConstants.getMessage(mc, LanguageConstants.CPQEntry)); if (p2.getLeader().getId() == mc.getId()) { leader2 = mc; } @@ -82,10 +82,10 @@ public class MonsterCarnival { } if (Grupo1 == null || Grupo2 == null) { for (MaplePartyCharacter mpc : p2.getMembers()) { - mpc.getPlayer().dropMessage(5, LanguageConstants.Languages(mpc.getPlayer()).CPQError); + mpc.getPlayer().dropMessage(5, LanguageConstants.getMessage(mpc.getPlayer(), LanguageConstants.CPQError)); } for (MaplePartyCharacter mpc : p2.getMembers()) { - mpc.getPlayer().dropMessage(5, LanguageConstants.Languages(mpc.getPlayer()).CPQError); + mpc.getPlayer().dropMessage(5, LanguageConstants.getMessage(mpc.getPlayer(), LanguageConstants.CPQError)); } return; } @@ -140,13 +140,13 @@ public class MonsterCarnival { String teamS = ""; switch (team) { case 0: - teamS = LanguageConstants.Languages(chrMap).CPQRed; + teamS = LanguageConstants.getMessage(chrMap, LanguageConstants.CPQRed); break; case 1: - teamS = LanguageConstants.Languages(chrMap).CPQBlue; + teamS = LanguageConstants.getMessage(chrMap, LanguageConstants.CPQBlue); break; } - chrMap.dropMessage(5, teamS + LanguageConstants.Languages(chrMap).CPQPlayerExit); + chrMap.dropMessage(5, teamS + LanguageConstants.getMessage(chrMap, LanguageConstants.CPQPlayerExit)); } earlyFinish(); } @@ -353,7 +353,7 @@ public class MonsterCarnival { private void extendTime() { for (MapleCharacter chrMap : map.getAllPlayers()) { - chrMap.dropMessage(5, LanguageConstants.Languages(chrMap).CPQExtendTime); + chrMap.dropMessage(5, LanguageConstants.getMessage(chrMap, LanguageConstants.CPQExtendTime)); } startTime = System.currentTimeMillis() + 3 * 60 * 1000; diff --git a/src/server/quest/MapleQuest.java b/src/server/quest/MapleQuest.java index dc214327ef..48b1131c4a 100644 --- a/src/server/quest/MapleQuest.java +++ b/src/server/quest/MapleQuest.java @@ -321,6 +321,7 @@ public class MapleQuest { public boolean forceStart(MapleCharacter c, int npc) { MapleQuestStatus newStatus = new MapleQuestStatus(this, MapleQuestStatus.Status.STARTED, npc); newStatus.setForfeited(c.getQuest(this).getForfeited()); + newStatus.setCompleted(c.getQuest(this).getCompleted()); if (timeLimit > 0) { newStatus.setExpirationTime(System.currentTimeMillis() + (timeLimit * 1000)); @@ -355,6 +356,7 @@ public class MapleQuest { MapleQuestStatus newStatus = new MapleQuestStatus(this, MapleQuestStatus.Status.COMPLETED, npc); newStatus.setForfeited(c.getQuest(this).getForfeited()); + newStatus.setCompleted(c.getQuest(this).getCompleted()); newStatus.setCompletionTime(System.currentTimeMillis()); c.updateQuest(newStatus); diff --git a/src/tools/MaplePacketCreator.java b/src/tools/MaplePacketCreator.java index 83cffa4be5..a445e713ff 100644 --- a/src/tools/MaplePacketCreator.java +++ b/src/tools/MaplePacketCreator.java @@ -7902,4 +7902,18 @@ public class MaplePacketCreator { mplew.writeInt(transition); return mplew.getPacket(); } + + public static byte[] setNPCScriptable(Set> scriptNpcDescriptions) { // thanks to GabrielSin + MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SendOpcode.SET_NPC_SCRIPTABLE.getValue()); + mplew.write(scriptNpcDescriptions.size()); + for (Pair p : scriptNpcDescriptions) { + mplew.writeInt(p.getLeft()); + mplew.writeMapleAsciiString(p.getRight()); + mplew.writeInt(0); // start time + mplew.writeInt(Integer.MAX_VALUE); // end time + } + return mplew.getPacket(); + } + } \ No newline at end of file diff --git a/src/tools/packets/Fishing.java b/src/tools/packets/Fishing.java index 672215c05e..e4569ad261 100644 --- a/src/tools/packets/Fishing.java +++ b/src/tools/packets/Fishing.java @@ -108,7 +108,7 @@ public class Fishing { rewardStr = "a(n) " + MapleItemInformationProvider.getInstance().getName(itemid) + "."; if (chr.canHold(itemid)) { - chr.getClient().getAbstractPlayerInteraction().gainItem(itemid, true); + chr.getAbstractPlayerInteraction().gainItem(itemid, true); } else { chr.showHint("Couldn't catch a(n) #r" + MapleItemInformationProvider.getInstance().getName(itemid) + "#k due to #e#b" + ItemConstants.getInventoryType(itemid) + "#k#n inventory limit."); rewardStr += ".. but has goofed up due to full inventory."; diff --git a/tools/MapleSkillbookStackUpdate/build.xml b/tools/MapleSkillbookStackUpdate/build.xml new file mode 100644 index 0000000000..95b8f765ae --- /dev/null +++ b/tools/MapleSkillbookStackUpdate/build.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project MapleSkillbookStackUpdate. + + + diff --git a/tools/MapleSkillbookStackUpdate/manifest.mf b/tools/MapleSkillbookStackUpdate/manifest.mf new file mode 100644 index 0000000000..328e8e5bc3 --- /dev/null +++ b/tools/MapleSkillbookStackUpdate/manifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +X-COMMENT: Main-Class will be added automatically by build + diff --git a/tools/MapleSkillbookStackUpdate/src/mapleskillbookstackupdate/MapleSkillbookStackUpdate.java b/tools/MapleSkillbookStackUpdate/src/mapleskillbookstackupdate/MapleSkillbookStackUpdate.java new file mode 100644 index 0000000000..f6004cdcae --- /dev/null +++ b/tools/MapleSkillbookStackUpdate/src/mapleskillbookstackupdate/MapleSkillbookStackUpdate.java @@ -0,0 +1,187 @@ +/* + 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 . +*/ +package mapleskillbookstackupdate; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.sql.Connection; + +/** + * + * @author RonanLana + * + * This application parses skillbook XMLs, filling up stack amount of those + * items to 100 (eliminating limitations on held skillbooks, now using + * default stack quantity expected by USE items). + * + * Estimated parse time: 10 seconds + */ +public class MapleSkillbookStackUpdate { + + static String wzPath = "../../wz"; + static String outputWzPath = "lib"; + + + static Connection con = null; + static PrintWriter printWriter = null; + static InputStreamReader fileReader = null; + static BufferedReader bufferedReader = null; + + static int initialLength = 200; + static int initialStringLength = 50; + static boolean displayExtraInfo = true; // display items with zero quantity over the quest act WZ + + static int status = 0; + + private static boolean isSkillMasteryBook(int itemid) { + return itemid >= 2280000 && itemid < 2300000; + } + + private static String getName(String token) { + int i, j; + char[] dest; + String d; + + i = token.lastIndexOf("name"); + i = token.indexOf("\"", i) + 1; //lower bound of the string + j = token.indexOf("\"", i); //upper bound + + if(j < i) return "0"; //node value containing 'name' in it's scope, cheap fix since we don't deal with strings anyway + + dest = new char[initialStringLength]; + token.getChars(i, j, dest, 0); + + d = new String(dest); + return(d.trim()); + } + + private static String getValue(String token) { + int i, j; + char[] dest; + String d; + + i = token.lastIndexOf("value"); + i = token.indexOf("\"", i) + 1; //lower bound of the string + j = token.indexOf("\"", i); //upper bound + + dest = new char[initialStringLength]; + token.getChars(i, j, dest, 0); + + d = new String(dest); + return(d.trim()); + } + + private static void forwardCursor(int st) { + String line = null; + + try { + while(status >= st && (line = bufferedReader.readLine()) != null) { + simpleToken(line); + } + } + catch(Exception e) { + e.printStackTrace(); + } + } + + private static void simpleToken(String token) { + if(token.contains("/imgdir")) { + status -= 1; + } + else if(token.contains("imgdir")) { + status += 1; + } + printWriter.println(token); + } + + private static void translateItemToken(String token) { + if(token.contains("/imgdir")) { + status -= 1; + } + else if(token.contains("imgdir")) { + status += 1; + + if(status == 2) { //itemid + int itemid = Integer.valueOf(getName(token)); + + if (!isSkillMasteryBook(itemid)) { + printWriter.println(token); + forwardCursor(status); + return; + } + } + } else { + if(status == 3) { + if (getName(token).contentEquals("slotMax")) { + printWriter.println(" "); + return; + } + } + } + + printWriter.println(token); + } + + private static void parseItemFile(File file, String outputName) { + // This will reference one line at a time + String line = null; + + try { + printWriter = new PrintWriter(outputName); + fileReader = new InputStreamReader(new FileInputStream(file), "UTF-8"); + bufferedReader = new BufferedReader(fileReader); + + while((line = bufferedReader.readLine()) != null) { + translateItemToken(line); + } + + bufferedReader.close(); + fileReader.close(); + printWriter.close(); + } + catch(IOException ex) { + System.out.println("Error reading file '" + file.getName() + "'"); + } + + catch(Exception e) { + e.printStackTrace(); + } + } + + private static void parseItemDirectory(String wzPath, String outputPath) { + File wzDir = new File(wzPath); + + for (File f : wzDir.listFiles()) { + parseItemFile(f, outputPath + f.getName()); + } + + } + + public static void main(String[] args) { + System.out.println("Reading item files..."); + parseItemDirectory(wzPath + "/Item.wz/Consume/", outputWzPath + "/"); + System.out.println("Done!"); + } + +} diff --git a/wz/Item.wz/Consume/0228.img.xml b/wz/Item.wz/Consume/0228.img.xml index 5c7fd518c4..c605159bae 100644 --- a/wz/Item.wz/Consume/0228.img.xml +++ b/wz/Item.wz/Consume/0228.img.xml @@ -8,7 +8,7 @@ - + @@ -27,7 +27,7 @@ - + @@ -46,7 +46,7 @@ - + @@ -77,7 +77,7 @@ - + @@ -108,7 +108,7 @@ - + @@ -127,7 +127,7 @@ - + @@ -149,7 +149,7 @@ - + @@ -172,7 +172,7 @@ - + @@ -194,7 +194,7 @@ - + @@ -215,7 +215,7 @@ - + @@ -235,7 +235,7 @@ - + @@ -255,7 +255,7 @@ - + @@ -275,7 +275,7 @@ - + @@ -296,7 +296,7 @@ - + @@ -317,7 +317,7 @@ - + @@ -338,7 +338,7 @@ - + @@ -359,7 +359,7 @@ - + @@ -380,7 +380,7 @@ - + @@ -400,7 +400,7 @@ - + @@ -420,7 +420,7 @@ - + @@ -440,7 +440,7 @@ - + @@ -460,7 +460,7 @@ - + @@ -480,7 +480,7 @@ - + @@ -500,7 +500,7 @@ - + @@ -520,7 +520,7 @@ - + @@ -540,7 +540,7 @@ - + diff --git a/wz/Item.wz/Consume/0229.img.xml b/wz/Item.wz/Consume/0229.img.xml index df5a971b87..722a3dd5e3 100644 --- a/wz/Item.wz/Consume/0229.img.xml +++ b/wz/Item.wz/Consume/0229.img.xml @@ -8,7 +8,7 @@ - + @@ -28,7 +28,7 @@ - + @@ -48,7 +48,7 @@ - + @@ -68,7 +68,7 @@ - + @@ -88,7 +88,7 @@ - + @@ -108,7 +108,7 @@ - + @@ -128,7 +128,7 @@ - + @@ -148,7 +148,7 @@ - + @@ -168,7 +168,7 @@ - + @@ -186,7 +186,7 @@ - + @@ -204,7 +204,7 @@ - + @@ -222,7 +222,7 @@ - + @@ -240,7 +240,7 @@ - + @@ -258,7 +258,7 @@ - + @@ -276,7 +276,7 @@ - + @@ -295,7 +295,7 @@ - + @@ -314,7 +314,7 @@ - + @@ -332,7 +332,7 @@ - + @@ -350,7 +350,7 @@ - + @@ -368,7 +368,7 @@ - + @@ -386,7 +386,7 @@ - + @@ -404,7 +404,7 @@ - + @@ -422,7 +422,7 @@ - + @@ -440,7 +440,7 @@ - + @@ -458,7 +458,7 @@ - + @@ -478,7 +478,7 @@ - + @@ -498,7 +498,7 @@ - + @@ -518,7 +518,7 @@ - + @@ -538,7 +538,7 @@ - + @@ -558,7 +558,7 @@ - + @@ -578,7 +578,7 @@ - + @@ -596,7 +596,7 @@ - + @@ -614,7 +614,7 @@ - + @@ -632,7 +632,7 @@ - + @@ -650,7 +650,7 @@ - + @@ -668,7 +668,7 @@ - + @@ -686,7 +686,7 @@ - + @@ -704,7 +704,7 @@ - + @@ -722,7 +722,7 @@ - + @@ -740,7 +740,7 @@ - + @@ -758,7 +758,7 @@ - + @@ -776,7 +776,7 @@ - + @@ -794,7 +794,7 @@ - + @@ -812,7 +812,7 @@ - + @@ -830,7 +830,7 @@ - + @@ -848,7 +848,7 @@ - + @@ -866,7 +866,7 @@ - + @@ -884,7 +884,7 @@ - + @@ -902,7 +902,7 @@ - + @@ -920,7 +920,7 @@ - + @@ -938,7 +938,7 @@ - + @@ -957,7 +957,7 @@ - + @@ -976,7 +976,7 @@ - + @@ -995,7 +995,7 @@ - + @@ -1014,7 +1014,7 @@ - + @@ -1032,7 +1032,7 @@ - + @@ -1050,7 +1050,7 @@ - + @@ -1068,7 +1068,7 @@ - + @@ -1086,7 +1086,7 @@ - + @@ -1104,7 +1104,7 @@ - + @@ -1122,7 +1122,7 @@ - + @@ -1140,7 +1140,7 @@ - + @@ -1158,7 +1158,7 @@ - + @@ -1176,7 +1176,7 @@ - + @@ -1194,7 +1194,7 @@ - + @@ -1212,7 +1212,7 @@ - + @@ -1230,7 +1230,7 @@ - + @@ -1248,7 +1248,7 @@ - + @@ -1266,7 +1266,7 @@ - + @@ -1284,7 +1284,7 @@ - + @@ -1302,7 +1302,7 @@ - + @@ -1320,7 +1320,7 @@ - + @@ -1338,7 +1338,7 @@ - + @@ -1356,7 +1356,7 @@ - + @@ -1374,7 +1374,7 @@ - + @@ -1393,7 +1393,7 @@ - + @@ -1412,7 +1412,7 @@ - + @@ -1431,7 +1431,7 @@ - + @@ -1450,7 +1450,7 @@ - + @@ -1469,7 +1469,7 @@ - + @@ -1488,7 +1488,7 @@ - + @@ -1507,7 +1507,7 @@ - + @@ -1526,7 +1526,7 @@ - + @@ -1544,7 +1544,7 @@ - + @@ -1562,7 +1562,7 @@ - + @@ -1580,7 +1580,7 @@ - + @@ -1598,7 +1598,7 @@ - + @@ -1616,7 +1616,7 @@ - + @@ -1634,7 +1634,7 @@ - + @@ -1652,7 +1652,7 @@ - + @@ -1670,7 +1670,7 @@ - + @@ -1688,7 +1688,7 @@ - + @@ -1706,7 +1706,7 @@ - + @@ -1724,7 +1724,7 @@ - + @@ -1742,7 +1742,7 @@ - + @@ -1772,7 +1772,7 @@ - + @@ -1790,7 +1790,7 @@ - + @@ -1808,7 +1808,7 @@ - + @@ -1826,7 +1826,7 @@ - + @@ -1844,7 +1844,7 @@ - + @@ -1862,7 +1862,7 @@ - + @@ -1880,7 +1880,7 @@ - + @@ -1898,7 +1898,7 @@ - + @@ -1916,7 +1916,7 @@ - + @@ -1934,7 +1934,7 @@ - + @@ -1952,7 +1952,7 @@ - + @@ -1970,7 +1970,7 @@ - + @@ -1988,7 +1988,7 @@ - + @@ -2006,7 +2006,7 @@ - + @@ -2024,7 +2024,7 @@ - + @@ -2042,7 +2042,7 @@ - + @@ -2060,7 +2060,7 @@ - + @@ -2078,7 +2078,7 @@ - + @@ -2096,7 +2096,7 @@ - + @@ -2114,7 +2114,7 @@ - + @@ -2132,7 +2132,7 @@ - + @@ -2150,7 +2150,7 @@ - + @@ -2168,7 +2168,7 @@ - + @@ -2186,7 +2186,7 @@ - + @@ -2204,7 +2204,7 @@ - + @@ -2222,7 +2222,7 @@ - + @@ -2240,7 +2240,7 @@ - + @@ -2258,7 +2258,7 @@ - + @@ -2276,7 +2276,7 @@ - + @@ -2294,7 +2294,7 @@ - + @@ -2324,7 +2324,7 @@ - + @@ -2342,7 +2342,7 @@ - + @@ -2360,7 +2360,7 @@ - + @@ -2378,7 +2378,7 @@ - + @@ -2396,7 +2396,7 @@ - + @@ -2414,7 +2414,7 @@ - + @@ -2432,7 +2432,7 @@ - + @@ -2450,7 +2450,7 @@ - + @@ -2468,7 +2468,7 @@ - + @@ -2486,7 +2486,7 @@ - + @@ -2504,7 +2504,7 @@ - + @@ -2522,7 +2522,7 @@ - + @@ -2540,7 +2540,7 @@ - + @@ -2558,7 +2558,7 @@ - + diff --git a/wz/Quest.wz/Check.img.xml b/wz/Quest.wz/Check.img.xml index c654dabc56..8868940095 100644 --- a/wz/Quest.wz/Check.img.xml +++ b/wz/Quest.wz/Check.img.xml @@ -34753,7 +34753,6 @@ - diff --git a/wz/String.wz/Consume.img.xml b/wz/String.wz/Consume.img.xml index 3bdd97de95..7a2dae9b20 100644 --- a/wz/String.wz/Consume.img.xml +++ b/wz/String.wz/Consume.img.xml @@ -130,7 +130,7 @@ - + @@ -338,7 +338,7 @@ - + @@ -778,7 +778,7 @@ - + @@ -798,7 +798,7 @@ - + @@ -2301,7 +2301,7 @@ - + @@ -3545,7 +3545,7 @@ - + @@ -5225,7 +5225,7 @@ - + @@ -5273,7 +5273,7 @@ - + @@ -5289,7 +5289,7 @@ - + @@ -5328,8 +5328,8 @@ - - + + @@ -6697,7 +6697,7 @@ - + @@ -6737,7 +6737,7 @@ - + @@ -7073,7 +7073,7 @@ - + @@ -7141,7 +7141,7 @@ - + @@ -7349,7 +7349,7 @@ - + @@ -7493,7 +7493,7 @@ - + @@ -8153,15 +8153,15 @@ - + - + - + @@ -8177,7 +8177,7 @@ - + @@ -8657,7 +8657,7 @@ - +