Several PQ platform patches + Quest complete count + Fast meso drop

Implemented CPQ challenges using the matching system.
Fixed LanguageConstants statically acting for all players.
Fixed OPQ's <On the Way Up> stage sometimes leading players to unexpected platforms.
Fixed EllinPQ fountain not giving Altaire Fragment to players.
Fixed "Lab - Unit" stage on RnJPQ, now using correlated sequences between the units.
Fixed Fredrick handing out negative values of mesos to players.
Improved "goto" command info.
Implemented quest complete count.
Fixed mobs still being "controlled" by players even though it's already dead.
Concurrently protected adding items into inventory.
Concurrently protected EXP gain through Writs of Solomon.
Adjusted smoothly respawn rate of mobs in map (solo players in a map now experiences 75% of mobs spawned).
Fixed mesos not being able to drop so frequently (prior 200ms threshold between drops).
Tweaked matchchecking so that match checking doesn't outright dispose matching members on dismissal (match still sticks to the player until they answer or timeout).
Fixed a dupe case within storage's item store.
Added any-NPC scriptable to the source.
This commit is contained in:
ronancpl
2019-05-02 11:02:07 -03:00
parent b6a91c523f
commit c2fa7883fe
90 changed files with 1454 additions and 611 deletions
+4
View File
@@ -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/
+11 -7
View File
@@ -1,4 +1,4 @@
<img src="https://heavenmssurveyhome.files.wordpress.com/2018/12/heavenmslogo.png" width="370" height="auto">
<img src="https://heavenmssurveyhome.files.wordpress.com/2018/12/heavenmslogo.png" width="370" height="auto">
## 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
<hr id="donate" />
@@ -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
+29 -2
View File
@@ -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 "<On the Way Up>" da OPQ levando jogadores incorretamente para a próxima plataforma.
Corrigido portais do stage "<On the Way Up>" 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.
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.
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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);
}
}
+1 -1
View File
@@ -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;
}
}
+28
View File
@@ -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) {
+28
View File
@@ -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) {
+1 -1
View File
@@ -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) {
+6 -2
View File
@@ -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);
+6 -2
View File
@@ -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);
+1 -1
View File
@@ -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));
+1 -1
View File
@@ -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));
+17 -3
View File
@@ -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();
}
}
}
+5 -1
View File
@@ -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.");
+5 -1
View File
@@ -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.");
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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;
}
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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;
+2 -1
View File
@@ -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() {
+8 -6
View File
@@ -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();
+12 -7
View File
@@ -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();
}
}
+2 -2
View File
@@ -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 {
+2 -2
View File
@@ -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;
+2 -1
View File
@@ -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;
-11
View File
@@ -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);
+3 -4
View File
@@ -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();
}
}
-1
View File
@@ -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);
+3 -2
View File
@@ -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 ;
+26 -21
View File
@@ -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<MapleQuestStatus> getCompletedQuests() {
synchronized (quests) {
List<MapleQuestStatus> 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;
+3 -13
View File
@@ -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() {
+13 -1
View File
@@ -65,7 +65,7 @@ public class MapleQuestStatus {
private final List<Integer> medalProgress = new LinkedList<Integer>();
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;
+3 -4
View File
@@ -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<String>) new ArrayList<String>(), (List<String>) new ArrayList<String>());
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);
@@ -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<Entry<String, Integer>> towns = new ArrayList<>(GameConstants.GOTO_TOWNS.entrySet());
sortGotoEntries(towns);
for (Map.Entry<String, Integer> e : towns) {
GOTO_TOWNS_INFO += ("'" + e.getKey() + "' - #b" + (mapFactory.getMap(e.getValue()).getMapName()) + "#k\r\n");
}
List<Entry<String, Integer>> areas = new ArrayList<>(GameConstants.GOTO_AREAS.entrySet());
sortGotoEntries(areas);
for (Map.Entry<String, Integer> 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<Entry<String, Integer>> listEntries) {
Collections.sort(listEntries, new Comparator<Entry<String, Integer>>() {
@Override
public int compare(Entry<String, Integer> e1, Entry<String, Integer> 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 <map name>");
String sendStr = "Syntax: #b@goto <map name>#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);
}
}
}
@@ -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();
@@ -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 <http://www.gnu.org/licenses/>.
*/
/*
@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 <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();
}
}
}
@@ -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 <http://www.gnu.org/licenses/>.
*/
/*
@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<MapleCharacter> chars = player.getMap().getAllPlayers();
for (MapleCharacter chr : chars) {
chr.saveLocationOnWarp();
chr.changeMap(109060000, chr.getTeam());
if (params.length < 1) {
player.yellowMessage("Syntax: !bossdroprate <newrate>");
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."));
}
}
@@ -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."));
}
}
+25 -1
View File
@@ -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<Pair<StatUpgrade, Integer>> 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));
@@ -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<Item> 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<Item> 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());
+34 -22
View File
@@ -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);
+83 -60
View File
@@ -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()];
}
}
+23
View File
@@ -0,0 +1,23 @@
package constants;
/**
* @brief ScriptableNPCConstants
* @author GabrielSin <gabrielsin@playellin.net>
* @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<Pair<Integer, String>> SCRIPTABLE_NPCS = new HashSet<Pair<Integer, String>>(){{
add(new Pair<>(9200000, "Cody"));
}};
}
+3
View File
@@ -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.
+1
View File
@@ -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),
+12
View File
@@ -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();
@@ -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,
+13 -8
View File
@@ -296,17 +296,22 @@ public final class Channel {
}
private void closeAllMerchants() {
merchWlock.lock();
try {
final Iterator<MapleHiredMerchant> hmit = hiredMerchants.values().iterator();
while (hmit.hasNext()) {
hmit.next().forceClose();
hmit.remove();
List<MapleHiredMerchant> 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();
}
}
@@ -273,6 +273,17 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl
int totDamageToOneMonster = 0;
List<Integer> 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) {
@@ -77,7 +77,13 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
Set<MapleCharacter> 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
}
}
}
@@ -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);
}
}
}
@@ -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);
@@ -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 {
@@ -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;
@@ -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();
}
@@ -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) {
@@ -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());
}
}
@@ -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();
}
@@ -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());
}
}
@@ -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<Integer, MapleMatchCheckingEntry> 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<Integer> getMatchPlayers() {
return confirmingMembers.keySet();
}
private Set<Integer> getAcceptedMatchPlayers() {
Set<Integer> s = new HashSet<>();
for (Entry<Integer, MapleMatchCheckingEntry> e : confirmingMembers.entrySet()) {
if (e.getValue().getAccept()) {
s.add(e.getKey());
}
}
return s;
}
private Set<MapleCharacter> getMatchCharacters() {
Set<MapleCharacter> players = new HashSet<>();
@@ -197,6 +219,20 @@ public class MapleMatchCheckerCoordinator {
return true;
}
private void reenablePlayerMatching(Set<Integer> 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<Integer> 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<Integer> 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<Integer> matchPlayers = mmce.getMatchPlayers();
Set<Integer> 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);
@@ -305,12 +305,12 @@ public class MapleSessionCoordinator {
}
public void closeLoginSession(IoSession session) {
String remoteIp = getSessionRemoteAddress(session);
Set<IoSession> lrh = loginRemoteHosts.get(remoteIp);
String remoteHost = getSessionRemoteAddress(session);
Set<IoSession> lrh = loginRemoteHosts.get(remoteHost);
if (lrh != null) {
lrh.remove(session);
if (lrh.isEmpty()) {
loginRemoteHosts.remove(remoteIp);
loginRemoteHosts.remove(remoteHost);
}
}
@@ -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;
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<MapleCharacter> 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<MapleCharacter> 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<MaplePartyCharacter> 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<MapleCharacter> 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<MapleCharacter> matchPlayers, String message) {
MapleCharacter chr = getChallenger(leaderid, matchPlayers);
chr.dropMessage(5, LanguageConstants.getMessage(chr, LanguageConstants.CPQChallengeRoomDenied));
}
@Override
public void onMatchDismissed(int leaderid, Set<MapleCharacter> matchPlayers, String message) {}
};
}
}
@@ -166,13 +166,29 @@ public class MatchCheckerGuildCreation implements MatchCheckerListenerRecipe {
@Override
public void onMatchDismissed(int leaderid, Set<MapleCharacter> 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);
}
}
}
+4 -2
View File
@@ -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<MapleCharacter> 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));
}
@@ -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 {
+3 -2
View File
@@ -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();
@@ -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());
+35 -27
View File
@@ -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<Integer> 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<MaplePartyCharacter> 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<MaplePartyCharacter> 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));
}
}
@@ -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();
}
}
+1 -1
View File
@@ -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;
}
+2
View File
@@ -1750,6 +1750,8 @@ public class MapleMonster extends AbstractLoadedMapleLife {
changeLevelByDifficulty(difficulty, pqMob);
}
// ---------------------------------------------------------------------------------
private boolean isPuppetInVicinity(MapleSummon summon) {
return summon.getPosition().distanceSq(this.getPosition()) < 177777;
}
+14 -1
View File
@@ -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();
}
+4 -3
View File
@@ -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) {
+1 -1
View File
@@ -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);
}
}
+1 -1
View File
@@ -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);
}
}
+8 -8
View File
@@ -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;
+2
View File
@@ -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);
+14
View File
@@ -7902,4 +7902,18 @@ public class MaplePacketCreator {
mplew.writeInt(transition);
return mplew.getPacket();
}
public static byte[] setNPCScriptable(Set<Pair<Integer, String>> scriptNpcDescriptions) { // thanks to GabrielSin
MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
mplew.writeShort(SendOpcode.SET_NPC_SCRIPTABLE.getValue());
mplew.write(scriptNpcDescriptions.size());
for (Pair<Integer, String> 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();
}
}
+1 -1
View File
@@ -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.";
+73
View File
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See commented blocks below for -->
<!-- some examples of how to customize the build. -->
<!-- (If you delete it and reopen the project it will be recreated.) -->
<!-- By default, only the Clean and Build commands use this build script. -->
<!-- Commands such as Run, Debug, and Test only use this build script if -->
<!-- the Compile on Save feature is turned off for the project. -->
<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
<!-- in the project's Project Properties dialog box.-->
<project name="MapleSkillbookStackUpdate" default="default" basedir=".">
<description>Builds, tests, and runs the project MapleSkillbookStackUpdate.</description>
<import file="nbproject/build-impl.xml"/>
<!--
There exist several targets which are by default empty and which can be
used for execution of your tasks. These targets are usually executed
before and after some main targets. They are:
-pre-init: called before initialization of project properties
-post-init: called after initialization of project properties
-pre-compile: called before javac compilation
-post-compile: called after javac compilation
-pre-compile-single: called before javac compilation of single file
-post-compile-single: called after javac compilation of single file
-pre-compile-test: called before javac compilation of JUnit tests
-post-compile-test: called after javac compilation of JUnit tests
-pre-compile-test-single: called before javac compilation of single JUnit test
-post-compile-test-single: called after javac compilation of single JUunit test
-pre-jar: called before JAR building
-post-jar: called after JAR building
-post-clean: called after cleaning build products
(Targets beginning with '-' are not intended to be called on their own.)
Example of inserting an obfuscator after compilation could look like this:
<target name="-post-compile">
<obfuscate>
<fileset dir="${build.classes.dir}"/>
</obfuscate>
</target>
For list of available properties check the imported
nbproject/build-impl.xml file.
Another way to customize the build is by overriding existing main targets.
The targets of interest are:
-init-macrodef-javac: defines macro for javac compilation
-init-macrodef-junit: defines macro for junit execution
-init-macrodef-debug: defines macro for class debugging
-init-macrodef-java: defines macro for class execution
-do-jar: JAR building
run: execution of project
-javadoc-build: Javadoc generation
test-report: JUnit report generation
An example of overriding the target for project execution could look like this:
<target name="run" depends="MapleSkillbookStackUpdate-impl.jar">
<exec dir="bin" executable="launcher.exe">
<arg file="${dist.jar}"/>
</exec>
</target>
Notice that the overridden target depends on the jar target and not only on
the compile target as the regular run target does. Again, for a list of available
properties which you can use, check the target you are overriding in the
nbproject/build-impl.xml file.
-->
</project>
@@ -0,0 +1,3 @@
Manifest-Version: 1.0
X-COMMENT: Main-Class will be added automatically by build
@@ -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 <http://www.gnu.org/licenses/>.
*/
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(" <int name=\"slotMax\" value=\"100\"/>");
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!");
}
}
+26 -26
View File
@@ -8,7 +8,7 @@
<canvas name="iconRaw" width="29" height="32">
<vector name="origin" x="-2" y="32"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -27,7 +27,7 @@
<canvas name="iconRaw" width="28" height="25">
<vector name="origin" x="-2" y="28"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -46,7 +46,7 @@
<canvas name="iconRaw" width="20" height="26">
<vector name="origin" x="-5" y="29"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -77,7 +77,7 @@
<canvas name="iconRaw" width="32" height="32">
<vector name="origin" x="0" y="32"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -108,7 +108,7 @@
<canvas name="iconRaw" width="31" height="22">
<vector name="origin" x="0" y="24"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -127,7 +127,7 @@
<canvas name="iconRaw" width="32" height="32">
<vector name="origin" x="0" y="32"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -149,7 +149,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -172,7 +172,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -194,7 +194,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -215,7 +215,7 @@
<canvas name="iconRaw" width="32" height="32">
<vector name="origin" x="0" y="32"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -235,7 +235,7 @@
<canvas name="iconRaw" width="32" height="32">
<vector name="origin" x="0" y="32"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -255,7 +255,7 @@
<canvas name="iconRaw" width="32" height="32">
<vector name="origin" x="0" y="32"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -275,7 +275,7 @@
<canvas name="iconRaw" width="32" height="32">
<vector name="origin" x="0" y="32"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -296,7 +296,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -317,7 +317,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -338,7 +338,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -359,7 +359,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -380,7 +380,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -400,7 +400,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -420,7 +420,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -440,7 +440,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -460,7 +460,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -480,7 +480,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -500,7 +500,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -520,7 +520,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
@@ -540,7 +540,7 @@
<vector name="origin" x="0" y="32"/>
<int name="z" value="0"/>
</canvas>
<int name="slotMax" value="1"/>
<int name="slotMax" value="100"/>
<int name="price" value="1"/>
<int name="only" value="1"/>
<int name="tradeBlock" value="1"/>
File diff suppressed because it is too large Load Diff
-1
View File
@@ -34753,7 +34753,6 @@
<imgdir name="0">
<int name="npc" value="9201031"/>
<int name="lvmin" value="23"/>
<string name="end" value="2010010100"/>
</imgdir>
</imgdir>
<imgdir name="28333">
+22 -22
View File
@@ -130,7 +130,7 @@
</imgdir>
<imgdir name="2002009">
<string name="name" value="Dexterity Pill"/>
<string name="desc" value="A pill of concentrated dexterity potion. Avoidablity + 10 for 10 minutes"/>
<string name="desc" value="A pill of concentrated dexterity potion. Avoidability + 10 for 10 minutes"/>
</imgdir>
<imgdir name="2002010">
<string name="name" value="Speed Pill"/>
@@ -338,7 +338,7 @@
</imgdir>
<imgdir name="2020020">
<string name="name" value="Wedding Cake"/>
<string name="desc" value="A Wedding cake that coming Recovers 2000 HP and MP."/>
<string name="desc" value="A Wedding cake that coming. Recovers 2000 HP and MP."/>
</imgdir>
<imgdir name="2020021">
<string name="name" value="Nemi&apos;s Lunch Box"/>
@@ -778,7 +778,7 @@
</imgdir>
<imgdir name="2022112">
<string name="name" value="Baby Witch"/>
<string name="desc" value="Weapon Attack +20, Magic Attack +30 nfor 15 minutes."/>
<string name="desc" value="Weapon Attack +20, Magic Attack +30 for 15 minutes."/>
</imgdir>
<imgdir name="2022113">
<string name="name" value="Pumpkin Pie"/>
@@ -798,7 +798,7 @@
</imgdir>
<imgdir name="2022119">
<string name="name" value="Tree Ornament"/>
<string name="desc" value="Weapon Attack +20, Magic Attack +30 forn15 minutes."/>
<string name="desc" value="Weapon Attack +20, Magic Attack +30 for 15 minutes."/>
</imgdir>
<imgdir name="2022120">
<string name="name" value="Christmas Melon"/>
@@ -2301,7 +2301,7 @@
</imgdir>
<imgdir name="2040800">
<string name="name" value="Scroll for Gloves for DEX"/>
<string name="desc" value="Improves dexterity on gloves.\nSuccess rate:100%, accurcacy +1"/>
<string name="desc" value="Improves dexterity on gloves.\nSuccess rate:100%, accuracy +1"/>
</imgdir>
<imgdir name="2040801">
<string name="name" value="Scroll for Gloves for DEX"/>
@@ -3545,7 +3545,7 @@
</imgdir>
<imgdir name="2044905">
<string name="name" value="[4yrAnniv] Gun for Attack 40%"/>
<string name="desc" value="Improves the attact on Maple Canon Shooter. nSuccess rate:40%, weapon att.+3,naccuracy+2nIf failed, the item will be destroyed at a 30% rate."/>
<string name="desc" value="Improves the attact on Maple Canon Shooter. nSuccess rate:40%, weapon att.+3, accuracy+2nIf failed, the item will be destroyed at a 30% rate."/>
</imgdir>
<imgdir name="2048000">
<string name="name" value="Scroll for Pet Equip. for Speed"/>
@@ -5225,7 +5225,7 @@
</imgdir>
<imgdir name="2382068">
<string name="name" value="Moon Bunny Card"/>
<string name="desc" value="A card that features Moon Bnny.n#cIf hunting at the Black MountainnJump +1#"/>
<string name="desc" value="A card that features Moon Bunny.n#cIf hunting at the Black MountainnJump +1#"/>
</imgdir>
<imgdir name="2382069">
<string name="name" value="Iron Boar Card"/>
@@ -5273,7 +5273,7 @@
</imgdir>
<imgdir name="2383006">
<string name="name" value="Jar Card"/>
<string name="desc" value="A card that features Jar.n#cIf hunting at Herb Townn소비Item drop rates are increased#"/>
<string name="desc" value="A card that features Jar.n#cIf hunting at Herb Townn#cItem drop rates are increased#"/>
</imgdir>
<imgdir name="2383007">
<string name="name" value="Mithril Mutae Card"/>
@@ -5289,7 +5289,7 @@
</imgdir>
<imgdir name="2383010">
<string name="name" value="Ginseng Jar"/>
<string name="desc" value="A card that features Ginseng Jar.n#cIf hunting at Herb Townnthe &quot;Use&quot; item drop rates are increased#"/>
<string name="desc" value="A card that features Ginseng Jar.n#cIf hunting at Herb TownnThe &quot;Use&quot; item drop rates are increased#"/>
</imgdir>
<imgdir name="2383011">
<string name="name" value="Chief Grey Card"/>
@@ -5328,8 +5328,8 @@
<string name="desc" value="A card that features Croco.n#cIf hunting at the SwampnAvoidability +2#"/>
</imgdir>
<imgdir name="2383020">
<string name="name" value="Luster Pixe Card"/>
<string name="desc" value="A card that features Luster Pixe.n#cIf hunting at Cloud ParknAccuracy +2#"/>
<string name="name" value="Luster Pixie Card"/>
<string name="desc" value="A card that features Luster Pixie.n#cIf hunting at Cloud ParknAccuracy +2#"/>
</imgdir>
<imgdir name="2383021">
<string name="name" value="Cellion Card"/>
@@ -6697,7 +6697,7 @@
</imgdir>
<imgdir name="2210017">
<string name="name" value="Moon Photo"/>
<string name="desc" value="The more you look at it, the more you feel lightheaded… and you feel closer to the moon."/>
<string name="desc" value="The more you look at it, the more you feel lightheaded... and you feel closer to the moon."/>
</imgdir>
<imgdir name="2210018">
<string name="name" value="Sweet Rice Cake"/>
@@ -6737,7 +6737,7 @@
</imgdir>
<imgdir name="2388046">
<string name="name" value="Mu Gong Card"/>
<string name="desc" value="A card with a drawing of Moo Gong on it.n#cWhen hunting in Mu Lungnincrease Item/Meso drop rate.#"/>
<string name="desc" value="A card with a drawing of Moo Gong on it.n#cWhen hunting in Mu LungnIncreases Item/Meso drop rate.#"/>
</imgdir>
<imgdir name="2430004">
<string name="name" value="Richie Gold&apos;s Random Key Pot"/>
@@ -7073,7 +7073,7 @@
</imgdir>
<imgdir name="2022542">
<string name="name" value="Little Lamb&apos;s Surprise Attack"/>
<string name="desc" value="Attacks a Wolf&apos;s back, temporarily immobolizing it."/>
<string name="desc" value="Attacks a Wolf&apos;s back, temporarily immobilizing it."/>
</imgdir>
<imgdir name="2022543">
<string name="name" value="Great Confusion"/>
@@ -7141,7 +7141,7 @@
</imgdir>
<imgdir name="2040731">
<string name="name" value="Balrog&apos;s DEX Scroll 30%"/>
<string name="desc" value="Improves DEX on Balrog Leather Shoes. nSucess rate: 30%, DEX +2"/>
<string name="desc" value="Improves DEX on Balrog Leather Shoes. nSuccess rate: 30%, DEX +2"/>
</imgdir>
<imgdir name="2040732">
<string name="name" value="Balrog&apos;s HP Scroll 30%"/>
@@ -7349,7 +7349,7 @@
</imgdir>
<imgdir name="2049106">
<string name="name" value="[6th Anniversary] Dark Scroll for Gloves for ATT 30%"/>
<string name="desc" value="Improves ATT on Gloves. nSucces rate: 30%, Weapons ATT +2nIf unsuccessful, item has an 80% chance of being destroyed."/>
<string name="desc" value="Improves ATT on Gloves. nSuccess rate: 30%, Weapons ATT +2nIf unsuccessful, item has an 80% chance of being destroyed."/>
</imgdir>
<imgdir name="2049107">
<string name="name" value="[6th Anniversary] Dark Scroll for Gloves for STR 70% ["/>
@@ -7493,7 +7493,7 @@
</imgdir>
<imgdir name="2210030">
<string name="name" value="Geppetto Transformation"/>
<string name="desc" value="You&apos;ve entered a fable and transformed into Geppetto. You’re feel a bit sluggish, but do your best to leave the dungeon with your party members."/>
<string name="desc" value="You&apos;ve entered a fable and transformed into Geppetto. You feel a bit sluggish, but do your best to leave the dungeon with your party members."/>
</imgdir>
<imgdir name="2210034">
<string name="name" value="Alien Gray Transformation"/>
@@ -8153,15 +8153,15 @@
</imgdir>
<imgdir name="2388052">
<string name="name" value="Giant Centipede Card"/>
<string name="desc" value="A card that features Giant Centipede.\n#ctem drop rates are increased at Herb Town#"/>
<string name="desc" value="A card that features Giant Centipede.\n#cItem drop rates are increased at Herb Town#"/>
</imgdir>
<imgdir name="2388053">
<string name="name" value="Blue Mushmom Card"/>
<string name="desc" value="A card that features Blue Mushmom.\n#c#tem drop rates are increased at Korean Folk Town#"/>
<string name="desc" value="A card that features Blue Mushmom.\n#cItem drop rates are increased at Korean Folk Town#"/>
</imgdir>
<imgdir name="2388054">
<string name="name" value="Snack Bar Card"/>
<string name="desc" value="A card that features Snack Bar.\n#ctem drop rates are increased at Drake&apos;s area#"/>
<string name="desc" value="A card that features Snack Bar.\n#cItem drop rates are increased at Drake&apos;s area#"/>
</imgdir>
<imgdir name="2388067">
<string name="name" value="Gray Yeti and King Pepe Card"/>
@@ -8177,7 +8177,7 @@
</imgdir>
<imgdir name="2388070">
<string name="name" value="Prime Minister Mushroom Card"/>
<string name="desc" value="A card featuring the Prime Minister Mushroom.\n\cIncreases the item/meso drop rate when used inside the Mushroom Castle.#"/>
<string name="desc" value="A card featuring the Prime Minister Mushroom.\n#cIncreases the item/meso drop rate when used inside the Mushroom Castle.#"/>
</imgdir>
<imgdir name="2430013">
<string name="name" value="Peng Peng Popsicle"/>
@@ -8657,7 +8657,7 @@
</imgdir>
<imgdir name="2290020">
<string name="name" value="[Mastery Book] Heaven&apos;s Hammer"/>
<string name="desc" value="This increases the master level of #cHeaven&apos;s Hammer# up to 20 with 70% of chance.\r\nClass : Paldain\r\nCondition : Skill level above 5"/>
<string name="desc" value="This increases the master level of #cHeaven&apos;s Hammer# up to 20 with 70% of chance.\r\nClass : Paladin\r\nCondition : Skill level above 5"/>
</imgdir>
<imgdir name="2290021">
<string name="name" value="[Mastery Book] Heaven&apos;s Hammer"/>