Mob aggro overhaul + STR-DEX autoassign patch + Guild & Pl. Store fix
Reviewed Monster Magnet skill effect on mobs, now properly showing up to other players. Reworked concurrency protection on login handler, now properly synchronizing requests for the same accountId. Reviewed an expedition issue with attempting to send packets on loggedoff players. Expeditions no longer holds a character object list of its own, rather finding players through their id on the world storage. Added stat requirement definition as first message on 1st job NPCs. Fixed an issue with area triggered NPC conversations not getting properly disposed. Reviewed "launch.bat", internally referencing Java7 engine. Assuming it has been installed in the default directory on the system, it's no longer necessary to set precedence on PATH for it. Fixed need for "reapproval from the 3rd job instructors" to attempt Zakum once more. Reviewed goto command. Non-GM's no longer has access to area maps, only towns. Implemented a new server flag for enforcing base rates (server rate: 1x) on players level 10 or lower. Fixed player guild tooltips not being properly marshalled to others on the map, at the event of several guild actions. Reviewed event system allowing creation of same-name events, potentially leading to null EIM problems. Refactored some locks on MapleCharacter, potentially solving a deadlock case within expiring/forfeiting quest methods. Implemented a major overhaul on mob aggro system, now updating player aggro in real-time based on their latest DPS. Properly refactored and encapsulated aggro mechanics. Fixed NPE on disposing events with alive mobs. Added server-side birthday check handling when opening player shops or merchants having cash items in store. Fixed player shop tooltip not finishing properly when shop owner closes store with visitors still in there. Fixed forceChangeMap method not warping players to the designated event map (rather sending them to the starting event map). Reviewed "summon" command, now using forceChangeMap, also changing channels if needed. Reworked HeavenMS autoassigner: STR-based classes now ups a bit more DEX than before, since its a vital attribute for accuracy on their actions. Added meso ceil check on player transactions. Trades now gets suspended if the max amount is reached. Implemented server-side item limit check on player shops and merchants. Fixed an exploit with merchants, on where players would be able to save the selling item on DB before taking from inventory.
This commit is contained in:
@@ -6,7 +6,7 @@ Besides myself for maintaining this repository, credits are to be given to Wizet
|
||||
|
||||
Regarding distributability and usage of the code presented here: like it was before, this MapleStory server is open-source. By that, it is meant that anyone is **free to install, use, modify and redistribute the contents**, as long as there is **no kind of commercial trading involved** and the **credits to the original creators are maintained** within the codes.
|
||||
|
||||
This is a NetBeans 8.0.2 Project, that MUST be built and run under JDK/JRE 7 (1.7.0_79+) in order to run properly. This means that it's easier to install the project via opening the server project folder inside NetBeans' IDE. Once installed, build this project on your machine and run the server using the "launch.bat" application.
|
||||
This is a NetBeans 8.0.2 Project, that MUST be built and run on Java 7 (JDK/JRE 1.7.0_79+) in order to run properly. This means that it's easier to install the project via opening the server project folder inside NetBeans' IDE. Once installed, build this project on your machine and run the server using the "launch.bat" application.
|
||||
|
||||
In this project, many gameplay-wise issues generated from either the original WZ files and the server source have been partially or completely solved. Considering the use of the provided edited WZ's and server-side wz.xml files should be of the greatest importance when dealing with this instance of server source, in order to perceive it at it's full potential. My opinion, though!
|
||||
|
||||
@@ -19,6 +19,8 @@ Server files: https://github.com/ronancpl/HeavenMS
|
||||
|
||||
Client files & general tools: https://drive.google.com/drive/folders/0BzDsHSr-0V4MYVJ0TWIxd05hYUk
|
||||
|
||||
Java7 SDK: https://www.oracle.com/technetwork/java/javase/downloads/java-archive-downloads-javase7-521261.html
|
||||
|
||||
**Important note about localhosts**: these executables are red-flagged by antivirus tools as __potentially malicious softwares__, this happens due to the reverse engineering methods that were applied onto these software artifacts. Those depicted here have been put to use for years already and posed no harm so far, so they are soundly assumed to be safe.
|
||||
|
||||
Latest localhost: https://hostr.co/m2bVtnizCtmD
|
||||
@@ -81,6 +83,7 @@ It's never enough to tell this, thanks to everyone that have been contributing s
|
||||
|
||||
Our Discord channel is still available on: https://discord.gg/Q7wKxHX
|
||||
|
||||
<hr id="donate" />
|
||||
### Donation
|
||||
|
||||
If you REALLY liked what you have seen on the project, please feel free to donate a little something as a helping hand for my contributions towards Maple development. Also remember to **support Nexon**!
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1574,3 +1574,49 @@ Protegido concorrentemente sistema de comandos, assim evitando processamento em
|
||||
16 Janeiro 2019,
|
||||
Revisado login handler, trazendo métodos de desconexão para antes de checar LoginState, e não permitindo reatribuição de objeto-cliente para jogador já logado em canal.
|
||||
Revisado disconnect no login handler, agora devidamente finalizando sessões recém-criadas que falham em conectar jogador ao mundo.
|
||||
Revisado skill Monster Magnet, agora devidamente mostrando efeitos de skill.
|
||||
Mais uma tentativa de proteção contra acesso concorrente no login handler, buscando evitar múltiplas requisições concorrente por um accountId durante processo e múltiplas requisições de mesmo objeto de cliente.
|
||||
Revisado objeto das expedições, não mais permitindo armazenar objetos de jogadores (que já podem ter sido desativados).
|
||||
Corrigido expedições enviando pacotes para jogadores que estão fora de jogo.
|
||||
Normalizado mensagens de 1o job de NPCs explorers, com afirmação de mínimo de stats antes de checar requerimentos.
|
||||
|
||||
18 Janeiro 2019,
|
||||
Corrigido dispose de interação com NPCs não devolvendo controle de certas ações ao jogador, mais notável quando a interação ocorreu automaticamente (jogador caminhou pra área de atuação).
|
||||
Removido pigs de Maple Island, já que o quiz de 3rd job sugere não há os mesmos lá. Cynical Orange Mushrooms passam a ocupar posições dos mesmos.
|
||||
Editado "launch.bat" para somente usar Java7, assim permitindo usuários a não terem que especificar preferência de uso de Java7 nas variáveis de ambiente PATH.
|
||||
|
||||
20 Janeiro 2019,
|
||||
Corrigido necessidade de repetir aprovação dos mestres para realizar expedições de Zakum.
|
||||
Revisado comando goto. Jogadores tem acesso somente a mapas de cidade, GMs tem acesso a areas também.
|
||||
Implementado EXP rate específico para novatos de nível menor que 11.
|
||||
Revisado petid sendo setado fora de área de atuação (até então não compete setar isso após criado objeto de Item).
|
||||
|
||||
21 - 24 Janeiro 2019,
|
||||
Corrigido sistema de guilds não atualizando corretamente tooltips de informação de guild de jogadores no mapa, para ampla quantidade de eventos com guilds (adicionar player na guild, trocar emblema, remover player, etc).
|
||||
Corrigido sistema de eventos permitindo entrada duplicada de eventos com mesmo nome nos registros do gerenciador de eventos, potencialmente levando a nulos em EIMs.
|
||||
Corrigido skill Hyper Body cancelando efeito do buff anterior ao reutilizar a skill, levando valores suficientemente grandes de HP corrente do jogador ao MaxHP base (MaxHP antes de utilizar a skill).
|
||||
Refatorado uso de evtLock para métodos que gerenciam expiração de quests, possívelmente resolvendo caso de deadlock no método de desistência de quests expiráveis.
|
||||
Revisado MoveLifeHandler, não mais propagando pacotes de movimentação de mobs vindas de jogadores que não são controladores.
|
||||
Refatorado completamente o sistema de aggro de mobs. Agora, métodos que atuam na atualização de aggros de mobs usam funções comuns.
|
||||
|
||||
25 - 26 Janeiro 2019,
|
||||
Corrigido problema de acesso a ponteiro nulo ao realizar dispose no sistema de mapas das instâncias de eventos. Referência ao gerenciador de eventos já era nulo ao atingir tal método.
|
||||
Implementado retorno de estado de aggro para "sem aggro" se não houveram atividades de aggro num mob por algum tempo.
|
||||
Implementado checagem de aniversário ao adicionar itens de cash às lojas de player.
|
||||
Corrigido Hired Merchants não fechando devidamente ao abrir loja com itens de cash.
|
||||
Corrigido tooltip de Hired Merchant não atualizando para "indisponível" quando a loja está em manutenção.
|
||||
Corrigido tooltip de Player Shops não fechando corretamente se ainda havia visitantes na loja.
|
||||
Mais alguns ajustes no novo sistema de aggros. Nesta implementação aparentemente somente controladores processam ataques do tipo "areaWarning" de mobs.
|
||||
Corrigido função forceChangeMap não levando devidamente jogadores para o mapa dado como parâmetro, quando tal mapa é de instância de evento.
|
||||
Revisado comando de summon de jogadores, agora utilizando função padrão forceChangeMap, que lida com registro de jogadores em instâncias e coloca jogador no mapa alvo.
|
||||
Adicionado, no comando de summon de jogadores, habilidade de colocar jogadores em outros canais dentro do mapa de evento.
|
||||
|
||||
27 - 29 Janeiro 2019,
|
||||
Corrigido custom AP autoassigner não utilizando devidamente os caps definidos no escopo do handler.
|
||||
Revisado custom AP autoassigner. Classes que usam DEX como atributo secundário para STR foram rebalanceados, de forma que o aumento no uso do atributo secundário permita melhores condições de ataque no jogo.
|
||||
Adicionado checagem de mesos em transações entre jogadores, lojas de jogador, ao receber itens de Duey e Fredrick.
|
||||
Adicionado checagem em quantidade de itens nas lojas e mercantes de jogadores, assim evitando bugs quando há mais itens a serem vendidos do que o esperado.
|
||||
Corrigido um exploit com mercantes, envolvendo chamada à DB antes de retirar item (que está sendo inserido na loja) do inventário do jogador.
|
||||
|
||||
30 Janeiro 2019,
|
||||
Corrigido chamadas irregulares a "qm/cm" em scripts.
|
||||
@@ -1,5 +1,6 @@
|
||||
@echo off
|
||||
@title HeavenMS
|
||||
set PATH=C:\Program Files\Java\jdk1.7.0_79\bin;%PATH%
|
||||
set CLASSPATH=.;dist\*
|
||||
java -Xmx2048m -Dwzpath=wz\ net.server.Server
|
||||
pause
|
||||
@@ -28,21 +28,21 @@ dist.jar=${dist.dir}/HeavenMS.jar
|
||||
dist.javadoc.dir=${dist.dir}/javadoc
|
||||
endorsed.classpath=
|
||||
excludes=
|
||||
file.reference.HikariCP-java7-2.4.12.jar=cores/HikariCP-java7-2.4.12.jar
|
||||
file.reference.HikariCP-java7-2.4.13.jar=cores/HikariCP-java7-2.4.13.jar
|
||||
file.reference.MapleSolaxia-src=src
|
||||
file.reference.mina-core-2.0.7.jar=cores/mina-core-2.0.7.jar
|
||||
file.reference.mysql-connector-java-bin.jar=cores/mysql-connector-java-bin.jar
|
||||
file.reference.slf4j-api-1.6.6.jar=cores/slf4j-api-1.6.6.jar
|
||||
file.reference.slf4j-api-1.7.21.jar=cores/slf4j-api-1.7.21.jar
|
||||
file.reference.slf4j-jdk14-1.7.5.jar=cores/slf4j-jdk14-1.7.5.jar
|
||||
includes=**
|
||||
jar.archive.disabled=${jnlp.enabled}
|
||||
jar.compress=true
|
||||
jar.index=${jnlp.enabled}
|
||||
javac.classpath=\
|
||||
${file.reference.HikariCP-java7-2.4.12.jar}:\
|
||||
${file.reference.mina-core-2.0.7.jar}:\
|
||||
${file.reference.slf4j-api-1.7.21.jar}:\
|
||||
${file.reference.HikariCP-java7-2.4.13.jar}:\
|
||||
${file.reference.mysql-connector-java-bin.jar}:\
|
||||
${file.reference.slf4j-api-1.6.6.jar}:\
|
||||
${file.reference.slf4j-jdk14-1.7.5.jar}
|
||||
# Space-separated list of extra javac options
|
||||
javac.compilerargs=
|
||||
|
||||
+15
-10
@@ -46,12 +46,7 @@ function start() {
|
||||
} else {
|
||||
if (cm.getJobId() == 0) {
|
||||
actionx["1stJob"] = true;
|
||||
if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType))
|
||||
cm.sendNext("So you decided to become a #rBowman#k?");
|
||||
else {
|
||||
cm.sendOk("Train a bit more until you reach #blevel 10, " + cm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rBowman#k.");
|
||||
cm.dispose();
|
||||
}
|
||||
cm.sendNext("So you decided to become a #rbowman#k? There are some standards to meet, y'know... #bYour level should be at least 10, with at least " + cm.getFirstJobStatRequirement(jobType) + "#k. Let's see."); // thanks Vcoc for noticing a need to state and check requirements on first job adv starting message
|
||||
} else if (cm.getLevel() >= 30 && cm.getJobId() == 300) {
|
||||
actionx["2ndJob"] = true;
|
||||
if (cm.haveItem(4031012))
|
||||
@@ -79,8 +74,13 @@ function start() {
|
||||
|
||||
function action(mode, type, selection) {
|
||||
status++;
|
||||
if (mode == 0 && type != 1)
|
||||
if (mode == -1 && selection == -1) {
|
||||
cm.dispose();
|
||||
return;
|
||||
} else if (mode == 0 && type != 1) {
|
||||
status -= 2;
|
||||
}
|
||||
|
||||
if (status == -1){
|
||||
start();
|
||||
return;
|
||||
@@ -116,10 +116,15 @@ function action(mode, type, selection) {
|
||||
}
|
||||
|
||||
if (actionx["1stJob"]){
|
||||
if (status == 0)
|
||||
if (status == 0) {
|
||||
if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType)) {
|
||||
cm.sendNextPrev("It is an important and final choice. You will not be able to turn back.");
|
||||
else if (status == 1){
|
||||
if (cm.canHold(1452051) && cm.canHold(2060000)){
|
||||
} else {
|
||||
cm.sendOk("Train a bit more until you reach the base requirements and I can show you the way of the #rBowman#k.");
|
||||
cm.dispose();
|
||||
}
|
||||
} else if (status == 1){
|
||||
if (cm.canHold(1452051) && cm.canHold(2070000)){
|
||||
if (cm.getJobId() == 0){
|
||||
cm.changeJobById(300);
|
||||
cm.gainItem(1452051, 1);
|
||||
|
||||
@@ -8,7 +8,7 @@ function action(mode, type, selection) {
|
||||
if (mode == 0 && type == 0) {
|
||||
status--;
|
||||
} else if (mode == -1) {
|
||||
qm.dispose();
|
||||
cm.dispose();
|
||||
return;
|
||||
} else {
|
||||
status++;
|
||||
|
||||
+14
-9
@@ -47,12 +47,7 @@ function start() {
|
||||
} else {
|
||||
if (cm.getJobId() == 0) {
|
||||
actionx["1stJob"] = true;
|
||||
if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType))
|
||||
cm.sendNext("Do you want to become a Warrior? You need to meet some criteria in order to do so.#b You should be at least in level 10, with at least 35 in STR#k. Let's see...");
|
||||
else {
|
||||
cm.sendOk("Train a bit more until you reach #blevel 10, " + cm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rWarrior#k.");
|
||||
cm.dispose();
|
||||
}
|
||||
cm.sendNext("Do you want to become a #rwarrior#k? You need to meet some criteria in order to do so.#b You should be at least in level 10, and at least " + cm.getFirstJobStatRequirement(jobType) + "#k. Let's see..."); // thanks Vcoc for noticing a need to state and check requirements on first job adv starting message
|
||||
} else if (cm.getLevel() >= 30 && cm.getJobId() == 100) {
|
||||
actionx["2ndJob"] = true;
|
||||
if (cm.haveItem(4031012))
|
||||
@@ -80,8 +75,13 @@ function start() {
|
||||
|
||||
function action(mode, type, selection) {
|
||||
status++;
|
||||
if (mode == 0 && type != 1)
|
||||
if (mode == -1 && selection == -1) {
|
||||
cm.dispose();
|
||||
return;
|
||||
} else if (mode == 0 && type != 1) {
|
||||
status -= 2;
|
||||
}
|
||||
|
||||
if (status == -1){
|
||||
start();
|
||||
return;
|
||||
@@ -117,9 +117,14 @@ function action(mode, type, selection) {
|
||||
}
|
||||
|
||||
if (actionx["1stJob"]){
|
||||
if (status == 0)
|
||||
if (status == 0) {
|
||||
if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType)) {
|
||||
cm.sendNextPrev("It is an important and final choice. You will not be able to turn back.");
|
||||
else if (status == 1){
|
||||
} else {
|
||||
cm.sendOk("Train a bit more until you reach the base requirements and I can show you the way of the #rWarrior#k.");
|
||||
cm.dispose();
|
||||
}
|
||||
} else if (status == 1){
|
||||
if (cm.canHold(1302077)){
|
||||
if (cm.getJobId() == 0){
|
||||
cm.changeJobById(100);
|
||||
|
||||
+14
-9
@@ -47,12 +47,7 @@ function start() {
|
||||
} else {
|
||||
if (cm.getJobId() == 0) {
|
||||
actionx["1stJob"] = true;
|
||||
if (cm.getLevel() >= 8 && cm.canGetFirstJob(jobType))
|
||||
cm.sendNext("Want to be a magician? There are some standards to meet. because we can't just accept EVERYONE in... #bYour level should be at least 8#k, with getting INT as your top priority. Let's see.");
|
||||
else {
|
||||
cm.sendOk("Train a bit more until you reach #blevel 8, " + cm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rMagician#k.");
|
||||
cm.dispose();
|
||||
}
|
||||
cm.sendNext("Want to be a #rmagician#k? There are some standards to meet. because we can't just accept EVERYONE in... #bYour level should be at least 8#k, with getting " + cm.getFirstJobStatRequirement(jobType) + " as your top priority. Let's see."); // thanks Vcoc for noticing a need to state and check requirements on first job adv starting message
|
||||
} else if (cm.getLevel() >= 30 && cm.getJobId() == 200) {
|
||||
actionx["2ndJob"] = true;
|
||||
if (cm.haveItem(4031012))
|
||||
@@ -80,8 +75,13 @@ function start() {
|
||||
|
||||
function action(mode, type, selection) {
|
||||
status++;
|
||||
if (mode == 0 && type == 0)
|
||||
if (mode == -1 && selection == -1) {
|
||||
cm.dispose();
|
||||
return;
|
||||
} else if (mode == 0 && type == 0) {
|
||||
status -= 2;
|
||||
}
|
||||
|
||||
if (status == -1){
|
||||
start();
|
||||
return;
|
||||
@@ -117,9 +117,14 @@ function action(mode, type, selection) {
|
||||
}
|
||||
|
||||
if (actionx["1stJob"]){
|
||||
if (status == 0)
|
||||
if (status == 0) {
|
||||
if (cm.getLevel() >= 8 && cm.canGetFirstJob(jobType)) {
|
||||
cm.sendYesNo("Oh...! You look like someone that can definitely be a part of us... all you need is a little sinister mind, and... yeah... so, what do you think? Wanna be the Magician?");
|
||||
else if (status == 1){
|
||||
} else {
|
||||
cm.sendOk("Train a bit more until you reach the base requirements and I can show you the way of the #rMagician#k.");
|
||||
cm.dispose();
|
||||
}
|
||||
} else if (status == 1){
|
||||
if (cm.canHold(1372043)){
|
||||
if (cm.getJobId() == 0){
|
||||
cm.changeJobById(200);
|
||||
|
||||
+17
-11
@@ -46,12 +46,7 @@ function start() {
|
||||
} else {
|
||||
if (cm.getJobId() == 0) {
|
||||
actionx["1stJob"] = true;
|
||||
if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType))
|
||||
cm.sendNext("Want to be a thief? There are some standards to meet. because we can't just accept EVERYONE in... #bYour level should be at least 10, with your DEX over 25#k. Let's see.");
|
||||
else {
|
||||
cm.sendOk("Train a bit more until you reach #blevel 10, " + cm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rThief#k.");
|
||||
cm.dispose();
|
||||
}
|
||||
cm.sendNext("Want to be a #rthief#k? There are some standards to meet. because we can't just accept EVERYONE in... #bYour level should be at least 10, with at least your " + cm.getFirstJobStatRequirement(jobType) + "#k. Let's see."); // thanks Vcoc for noticing a need to state and check requirements on first job adv starting message
|
||||
} else if (cm.getLevel() >= 30 && cm.getJobId() == 400) {
|
||||
actionx["2ndJob"] = true;
|
||||
if (cm.haveItem(4031012))
|
||||
@@ -81,8 +76,13 @@ function start() {
|
||||
|
||||
function action(mode, type, selection) {
|
||||
status++;
|
||||
if (mode == 0 && type != 1)
|
||||
if (mode == -1 && selection == -1) {
|
||||
cm.dispose();
|
||||
return;
|
||||
} else if (mode == 0 && type != 1) {
|
||||
status -= 2;
|
||||
}
|
||||
|
||||
if (status == -1){
|
||||
start();
|
||||
return;
|
||||
@@ -118,14 +118,20 @@ function action(mode, type, selection) {
|
||||
}
|
||||
|
||||
if (actionx["1stJob"]){
|
||||
if (status == 0)
|
||||
if (status == 0) {
|
||||
if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType))
|
||||
cm.sendYesNo("Oh...! You look like someone that can definitely be a part of us... all you need is a little sinister mind, and... yeah... so, what do you think? Wanna be the Rogue?");
|
||||
else if (status == 1){
|
||||
if (cm.canHold(2070000) && cm.canHold(1472061)){
|
||||
else {
|
||||
cm.sendOk("Train a bit more until you reach the base requirements and I can show you the way of the #rThief#k.");
|
||||
cm.dispose();
|
||||
}
|
||||
} else if (status == 1){
|
||||
if (cm.canHold(2070000) && cm.canHoldAll([1472061, 1332063])){
|
||||
if (cm.getJobId() == 0){
|
||||
cm.changeJobById(400);
|
||||
cm.gainItem(2070000, 500);
|
||||
cm.gainItem(2070015, 500);
|
||||
cm.gainItem(1472061, 1);
|
||||
cm.gainItem(1332063, 1);
|
||||
cm.resetStats();
|
||||
}
|
||||
cm.sendNext("Alright, from here out, you are a part of us! You'll be living the life of a wanderer at ..., but just be patient as soon, you'll be living the high life. Alright, it ain't much, but I'll give you some of my abilities... HAAAHHH!!!");
|
||||
|
||||
@@ -29,6 +29,7 @@ importPackage(Packages.scripting.event);
|
||||
|
||||
var status = 0;
|
||||
var expedition;
|
||||
var expedMembers;
|
||||
var player;
|
||||
var em;
|
||||
var exped = MapleExpeditionType.BALROG_NORMAL;
|
||||
@@ -68,7 +69,7 @@ function action(mode, type, selection) {
|
||||
status = 2;
|
||||
} else if (expedition.isRegistering()) { //If the expedition is registering
|
||||
if (expedition.contains(player)) { //If you're in it but it hasn't started, be patient
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin the expedition.");
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin it.");
|
||||
cm.dispose();
|
||||
} else { //If you aren't in it, you're going to get added
|
||||
cm.sendOk(expedition.addMember(cm.getPlayer()));
|
||||
@@ -120,7 +121,8 @@ function action(mode, type, selection) {
|
||||
cm.dispose();
|
||||
return;
|
||||
}
|
||||
var size = expedition.getMembers().size();
|
||||
expedMembers = expedition.getMemberList();
|
||||
var size = expedMembers.size();
|
||||
if (size == 1) {
|
||||
cm.sendOk("You are the only member of the expedition.");
|
||||
cm.dispose();
|
||||
@@ -129,13 +131,13 @@ function action(mode, type, selection) {
|
||||
var text = "The following members make up your expedition (Click on them to expel them):\r\n";
|
||||
text += "\r\n\t\t1." + expedition.getLeader().getName();
|
||||
for (var i = 1; i < size; i++) {
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedition.getMembers().get(i).getName() + "#l\n";
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedMembers.get(i).getValue() + "#l\n";
|
||||
}
|
||||
cm.sendSimple(text);
|
||||
status = 6;
|
||||
} else if (selection == 2) {
|
||||
var min = exped.getMinSize();
|
||||
var size = expedition.getMembers().size();
|
||||
var size = expedition.getMemberList().size();
|
||||
if (size < min) {
|
||||
cm.sendOk("You need at least " + min + " players registered in your expedition.");
|
||||
cm.dispose();
|
||||
@@ -170,7 +172,7 @@ function action(mode, type, selection) {
|
||||
return;
|
||||
} else if (status == 6) {
|
||||
if (selection > 0) {
|
||||
var banned = expedition.getMembers().get(selection - 1);
|
||||
var banned = expedMembers.get(selection - 1);
|
||||
expedition.ban(banned);
|
||||
cm.sendOk("You have banned " + banned.getName() + " from the expedition.");
|
||||
cm.dispose();
|
||||
|
||||
+15
-10
@@ -77,12 +77,7 @@ function start() {
|
||||
} else {
|
||||
if (cm.getJobId() == 0) {
|
||||
actionx["1stJob"] = true;
|
||||
if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType))
|
||||
cm.sendNext("Want to be a pirate? There are some standards to meet. because we can't just accept EVERYONE in... #bYour level should be at least 10#k. Let's see.");
|
||||
else {
|
||||
cm.sendOk("Train a bit more until you reach #blevel 10, " + cm.getFirstJobStatRequirement(jobType) + "#k and I can show you the way of the #rPirate#k.");
|
||||
cm.dispose();
|
||||
}
|
||||
cm.sendNext("Want to be a #rpirate#k? There are some standards to meet. because we can't just accept EVERYONE in... #bYour level should be at least 10, with " + cm.getFirstJobStatRequirement(jobType) + " minimum#k. Let's see."); // thanks Vcoc for noticing a need to state and check requirements on first job adv starting message
|
||||
} else if (cm.getLevel() >= 30 && cm.getJobId() == 500) {
|
||||
actionx["2ndJob"] = true;
|
||||
if (cm.isQuestCompleted(2191) || cm.isQuestCompleted(2192))
|
||||
@@ -107,8 +102,13 @@ function start() {
|
||||
|
||||
function action(mode, type, selection) {
|
||||
status++;
|
||||
if (mode == 0 && type != 1)
|
||||
if (mode == -1 && selection == -1) {
|
||||
cm.dispose();
|
||||
return;
|
||||
} else if (mode == 0 && type != 1) {
|
||||
status -= 2;
|
||||
}
|
||||
|
||||
if (status == -1){
|
||||
start();
|
||||
return;
|
||||
@@ -161,10 +161,15 @@ function action(mode, type, selection) {
|
||||
}
|
||||
|
||||
if (actionx["1stJob"]){
|
||||
if (status == 0)
|
||||
if (status == 0) {
|
||||
if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType)) {
|
||||
cm.sendYesNo("Oh...! You look like someone that can definitely be a part of us... all you need is a little slang, and... yeah... so, what do you think? Wanna be the Pirate?");
|
||||
else if (status == 1){
|
||||
if (cm.canHold(2070000) && cm.canHold(1472061)){
|
||||
} else {
|
||||
cm.sendOk("Train a bit more until you reach the base requirements and I can show you the way of the #rPirate#k.");
|
||||
cm.dispose();
|
||||
}
|
||||
} else if (status == 1){
|
||||
if (cm.canHold(2070000) && cm.canHoldAll([1482000, 1492000])){
|
||||
if (cm.getJobId() == 0){
|
||||
cm.changeJobById(500);
|
||||
cm.gainItem(1492000, 1);
|
||||
|
||||
@@ -141,7 +141,7 @@ function action(mode, type, selection){
|
||||
} else {
|
||||
if (cm.getPlayer().getLevel() >= 50){
|
||||
cm.sendNext("Ok, go.");
|
||||
if(!cm.isQuestStarted(100200)) cm.startQuest(100200);
|
||||
if(!(cm.isQuestStarted(100200) || cm.isQuestCompleted(100200))) cm.startQuest(100200);
|
||||
if(Packages.constants.ServerConstants.USE_ENABLE_SOLO_EXPEDITIONS && !cm.isQuestCompleted(100201)) cm.completeQuest(100201);
|
||||
}else
|
||||
cm.sendNext("You're weak.");
|
||||
|
||||
@@ -113,7 +113,7 @@ function action(mode, type, selection){
|
||||
} else {
|
||||
if (cm.getPlayer().getLevel() >= 50){
|
||||
cm.sendNext("Ok, go.");
|
||||
if(!cm.isQuestStarted(100200)) cm.startQuest(100200);
|
||||
if(!(cm.isQuestStarted(100200) || cm.isQuestCompleted(100200))) cm.startQuest(100200);
|
||||
if(Packages.constants.ServerConstants.USE_ENABLE_SOLO_EXPEDITIONS && !cm.isQuestCompleted(100201)) cm.completeQuest(100201);
|
||||
}else
|
||||
cm.sendNext("You're weak.");
|
||||
|
||||
@@ -114,7 +114,7 @@ function action(mode, type, selection){
|
||||
} else {
|
||||
if (cm.getPlayer().getLevel() >= 50){
|
||||
cm.sendNext("Ok, go.");
|
||||
if(!cm.isQuestStarted(100200)) cm.startQuest(100200);
|
||||
if(!(cm.isQuestStarted(100200) || cm.isQuestCompleted(100200))) cm.startQuest(100200);
|
||||
if(Packages.constants.ServerConstants.USE_ENABLE_SOLO_EXPEDITIONS && !cm.isQuestCompleted(100201)) cm.completeQuest(100201);
|
||||
}else
|
||||
cm.sendNext("You're weak.");
|
||||
|
||||
@@ -113,7 +113,7 @@ function action(mode, type, selection){
|
||||
} else {
|
||||
if (cm.getPlayer().getLevel() >= 50){
|
||||
cm.sendNext("Ok, go.");
|
||||
if(!cm.isQuestStarted(100200)) cm.startQuest(100200);
|
||||
if(!(cm.isQuestStarted(100200) || cm.isQuestCompleted(100200))) cm.startQuest(100200);
|
||||
if(Packages.constants.ServerConstants.USE_ENABLE_SOLO_EXPEDITIONS && !cm.isQuestCompleted(100201)) cm.completeQuest(100201);
|
||||
}else
|
||||
cm.sendNext("You're weak.");
|
||||
|
||||
@@ -112,7 +112,7 @@ function action(mode, type, selection){
|
||||
} else {
|
||||
if (cm.getPlayer().getLevel() >= 50){
|
||||
cm.sendNext("Ok, go.");
|
||||
if(!cm.isQuestStarted(100200)) cm.startQuest(100200);
|
||||
if(!(cm.isQuestStarted(100200) || cm.isQuestCompleted(100200))) cm.startQuest(100200);
|
||||
if(Packages.constants.ServerConstants.USE_ENABLE_SOLO_EXPEDITIONS && !cm.isQuestCompleted(100201)) cm.completeQuest(100201);
|
||||
}else
|
||||
cm.sendNext("You're weak.");
|
||||
|
||||
@@ -32,14 +32,14 @@ var questionTree = [
|
||||
//Questions Related to ITEMS
|
||||
["Which of following monsters got CORRECT item corresponding to the monster?", ["Royal cactus - Needle", "Wild Boar - Boar fang", "Lazy Buffy - Buffy hat", "Chipmunk - Nut", "Stirge - Stirge's wing"], 4],
|
||||
["Which of following monsters got WRONG item corresponding to the monster?", ["Greatest Oldies - Greatest oldies", "Nependeath - Nependeath's leaf", "Ghost stump - Seedling", "Sparker - Seal tooth", "Miner Zombie - Zombie's lost tooth"], 1],
|
||||
["In GM Event, how many FRUIT CAKE you can get as reward?", ["20", "200", "5", "25", "100"], 2],
|
||||
//["In GM Event, how many FRUIT CAKE you can get as reward?", ["20", "200", "5", "25", "100"], 2],
|
||||
["Which of following potions got CORRECT info.?", ["Warrior Elixir - Attack +5 for 3 minutes", "Pure Water - Recover 700 MP", "Cake - Recover 150 HP & MP", "Salad - Recover 300 MP", "Pizza - Recover 400 HP"], 4],
|
||||
["Which of following potions got WRONG info.?", ["Mana Elixir - Recover 300 MP", "Tonic - Cures state of weakness", "Apple - Recover 30 HP", "Sunrise Dew - Recover 3000 MP", "Ramen - Recover 1000 HP"], 3],
|
||||
|
||||
//Questions Related to MONSTERS
|
||||
["Green Mushroom, Tree Stump, Bubbling, Axe Stump, Octopus, which is highest level of all?", ["Tree Stump", "Bubbling", "Axe Stump", "Octopus", "Green Mushroom"], 2],
|
||||
["Which monster will be seen during the ship trip to Orbis/Ellinia?", ["Werewolf", "Slime", "Crimson Balrog", "Zakum", "Star Pixie"], 2],
|
||||
["Maple Island doesn't have which following monsters?", ["Green Mushroom", "Blue Snail", "Orange Mushroom", "Red Snail", "Pig"], 0],
|
||||
["Maple Island doesn't have which following monsters?", ["Shroom", "Blue Snail", "Slime", "Red Snail", "Pig"], 4], // to get conformant with website answers, thanks to Vcoc
|
||||
["Which monster is not at Victoria Island and Sleepywood?", ["Evil Eye", "Sentinel", "Jr. Balrog", "Ghost Stump", "Snail"], 1],
|
||||
["El Nath doesn't have which following monsters?", ["Dark Yeti", "Dark Ligator", "Yeti & Pepe", "Bain", "Coolie Zombie"], 1],
|
||||
["Which of following monsters can fly?", ["Malady", "Ligator", "Cold Eye", "Meerkat", "Alishar"], 0],
|
||||
|
||||
@@ -58,7 +58,7 @@ function action(mode, type, selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!cm.isQuestStarted(100200)) {
|
||||
if(!(cm.isQuestStarted(100200) || cm.isQuestCompleted(100200))) { // thanks Vcoc for finding out a need of reapproval from the masters for Zakum expeditions
|
||||
cm.sendOk("Beware, for the power of olde has not been forgotten... ");
|
||||
cm.dispose();
|
||||
return;
|
||||
|
||||
@@ -29,6 +29,7 @@ importPackage(Packages.scripting.event);
|
||||
|
||||
var status = 0;
|
||||
var expedition;
|
||||
var expedMembers;
|
||||
var player;
|
||||
var em;
|
||||
var exped = MapleExpeditionType.ZAKUM;
|
||||
@@ -69,7 +70,7 @@ function action(mode, type, selection) {
|
||||
status = 2;
|
||||
} else if (expedition.isRegistering()) { //If the expedition is registering
|
||||
if (expedition.contains(player)) { //If you're in it but it hasn't started, be patient
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin the expedition.");
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin it.");
|
||||
cm.dispose();
|
||||
} else { //If you aren't in it, you're going to get added
|
||||
cm.sendOk(expedition.addMember(cm.getPlayer()));
|
||||
@@ -121,7 +122,8 @@ function action(mode, type, selection) {
|
||||
cm.dispose();
|
||||
return;
|
||||
}
|
||||
var size = expedition.getMembers().size();
|
||||
expedMembers = expedition.getMemberList();
|
||||
var size = expedMembers.size();
|
||||
if (size == 1) {
|
||||
cm.sendOk("You are the only member of the expedition.");
|
||||
cm.dispose();
|
||||
@@ -130,14 +132,14 @@ function action(mode, type, selection) {
|
||||
var text = "The following members make up your expedition (Click on them to expel them):\r\n";
|
||||
text += "\r\n\t\t1." + expedition.getLeader().getName();
|
||||
for (var i = 1; i < size; i++) {
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedition.getMembers().get(i).getName() + "#l\n";
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedMembers.get(i).getValue() + "#l\n";
|
||||
}
|
||||
cm.sendSimple(text);
|
||||
status = 6;
|
||||
} else if (selection == 2) {
|
||||
var min = exped.getMinSize();
|
||||
|
||||
var size = expedition.getMembers().size();
|
||||
var size = expedition.getMemberList().size();
|
||||
if (size < min) {
|
||||
cm.sendOk("You need at least " + min + " players registered in your expedition.");
|
||||
cm.dispose();
|
||||
@@ -172,7 +174,7 @@ function action(mode, type, selection) {
|
||||
return;
|
||||
} else if (status == 6) {
|
||||
if (selection > 0) {
|
||||
var banned = expedition.getMembers().get(selection - 1);
|
||||
var banned = expedMembers.get(selection - 1);
|
||||
expedition.ban(banned);
|
||||
cm.sendOk("You have banned " + banned.getName() + " from the expedition.");
|
||||
cm.dispose();
|
||||
|
||||
@@ -29,6 +29,7 @@ importPackage(Packages.scripting.event);
|
||||
|
||||
var status = 0;
|
||||
var expedition;
|
||||
var expedMembers;
|
||||
var player;
|
||||
var em;
|
||||
var exped = MapleExpeditionType.HORNTAIL;
|
||||
@@ -67,7 +68,7 @@ function action(mode, type, selection) {
|
||||
status = 2;
|
||||
} else if (expedition.isRegistering()) { //If the expedition is registering
|
||||
if (expedition.contains(player)) { //If you're in it but it hasn't started, be patient
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin the expedition.");
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin it.");
|
||||
cm.dispose();
|
||||
} else { //If you aren't in it, you're going to get added
|
||||
cm.sendOk(expedition.addMember(cm.getPlayer()));
|
||||
@@ -113,7 +114,8 @@ function action(mode, type, selection) {
|
||||
cm.dispose();
|
||||
return;
|
||||
}
|
||||
var size = expedition.getMembers().size();
|
||||
expedMembers = expedition.getMemberList();
|
||||
var size = expedMembers.size();
|
||||
if (size == 1) {
|
||||
cm.sendOk("You are the only member of the expedition.");
|
||||
cm.dispose();
|
||||
@@ -122,14 +124,14 @@ function action(mode, type, selection) {
|
||||
var text = "The following members make up your expedition (Click on them to expel them):\r\n";
|
||||
text += "\r\n\t\t1." + expedition.getLeader().getName();
|
||||
for (var i = 1; i < size; i++) {
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedition.getMembers().get(i).getName() + "#l\n";
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedMembers.get(i).getValue() + "#l\n";
|
||||
}
|
||||
cm.sendSimple(text);
|
||||
status = 6;
|
||||
} else if (selection == 2) {
|
||||
var min = exped.getMinSize();
|
||||
|
||||
var size = expedition.getMembers().size();
|
||||
var size = expedition.getMemberList().size();
|
||||
if (size < min) {
|
||||
cm.sendOk("You need at least " + min + " players registered in your expedition.");
|
||||
cm.dispose();
|
||||
@@ -164,7 +166,7 @@ function action(mode, type, selection) {
|
||||
return;
|
||||
} else if (status == 6) {
|
||||
if (selection > 0) {
|
||||
var banned = expedition.getMembers().get(selection - 1);
|
||||
var banned = expedMembers.get(selection - 1);
|
||||
expedition.ban(banned);
|
||||
cm.sendOk("You have banned " + banned.getName() + " from the expedition.");
|
||||
cm.dispose();
|
||||
|
||||
@@ -31,6 +31,7 @@ importPackage(Packages.scripting.event);
|
||||
|
||||
var status = 0;
|
||||
var expedition;
|
||||
var expedMembers;
|
||||
var player;
|
||||
var em;
|
||||
var exped = MapleExpeditionType.PINKBEAN;
|
||||
@@ -70,7 +71,7 @@ function action(mode, type, selection) {
|
||||
status = 2;
|
||||
} else if (expedition.isRegistering()) { //If the expedition is registering
|
||||
if (expedition.contains(player)) { //If you're in it but it hasn't started, be patient
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin the expedition.");
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin it.");
|
||||
cm.dispose();
|
||||
} else { //If you aren't in it, you're going to get added
|
||||
cm.sendOk(expedition.addMember(cm.getPlayer()));
|
||||
@@ -116,7 +117,8 @@ function action(mode, type, selection) {
|
||||
cm.dispose();
|
||||
return;
|
||||
}
|
||||
var size = expedition.getMembers().size();
|
||||
expedMembers = expedition.getMemberList();
|
||||
var size = expedMembers.size();
|
||||
if (size == 1) {
|
||||
cm.sendOk("You are the only member of the expedition.");
|
||||
cm.dispose();
|
||||
@@ -125,14 +127,14 @@ function action(mode, type, selection) {
|
||||
var text = "The following members make up your expedition (Click on them to expel them):\r\n";
|
||||
text += "\r\n\t\t1." + expedition.getLeader().getName();
|
||||
for (var i = 1; i < size; i++) {
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedition.getMembers().get(i).getName() + "#l\n";
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedMembers.get(i).getValue() + "#l\n";
|
||||
}
|
||||
cm.sendSimple(text);
|
||||
status = 6;
|
||||
} else if (selection == 2) {
|
||||
var min = exped.getMinSize();
|
||||
|
||||
var size = expedition.getMembers().size();
|
||||
var size = expedition.getMemberList().size();
|
||||
if (size < min) {
|
||||
cm.sendOk("You need at least " + min + " players registered in your expedition.");
|
||||
cm.dispose();
|
||||
@@ -167,7 +169,7 @@ function action(mode, type, selection) {
|
||||
return;
|
||||
} else if (status == 6) {
|
||||
if (selection > 0) {
|
||||
var banned = expedition.getMembers().get(selection - 1);
|
||||
var banned = expedMembers.get(selection - 1);
|
||||
expedition.ban(banned);
|
||||
cm.sendOk("You have banned " + banned.getName() + " from the expedition.");
|
||||
cm.dispose();
|
||||
|
||||
@@ -28,6 +28,7 @@ importPackage(Packages.scripting.event);
|
||||
|
||||
var status = 0;
|
||||
var expedition;
|
||||
var expedMembers;
|
||||
var player;
|
||||
var em;
|
||||
var exped = MapleExpeditionType.SHOWA;
|
||||
@@ -68,7 +69,7 @@ function action(mode, type, selection) {
|
||||
status = 2;
|
||||
} else if (expedition.isRegistering()) { //If the expedition is registering
|
||||
if (expedition.contains(player)) { //If you're in it but it hasn't started, be patient
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin the expedition.");
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin it.");
|
||||
cm.dispose();
|
||||
} else { //If you aren't in it, you're going to get added
|
||||
cm.sendOk(expedition.addMember(cm.getPlayer()));
|
||||
@@ -120,7 +121,8 @@ function action(mode, type, selection) {
|
||||
cm.dispose();
|
||||
return;
|
||||
}
|
||||
var size = expedition.getMembers().size();
|
||||
expedMembers = expedition.getMemberList();
|
||||
var size = expedMembers.size();
|
||||
if (size == 1) {
|
||||
cm.sendOk("You are the only member of the expedition.");
|
||||
cm.dispose();
|
||||
@@ -129,14 +131,14 @@ function action(mode, type, selection) {
|
||||
var text = "The following members make up your expedition (Click on them to expel them):\r\n";
|
||||
text += "\r\n\t\t1." + expedition.getLeader().getName();
|
||||
for (var i = 1; i < size; i++) {
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedition.getMembers().get(i).getName() + "#l\n";
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedMembers.get(i).getValue() + "#l\n";
|
||||
}
|
||||
cm.sendSimple(text);
|
||||
status = 6;
|
||||
} else if (selection == 2) {
|
||||
var min = exped.getMinSize();
|
||||
|
||||
var size = expedition.getMembers().size();
|
||||
var size = expedition.getMemberList().size();
|
||||
if (size < min) {
|
||||
cm.sendOk("You need at least " + min + " players registered in your expedition.");
|
||||
cm.dispose();
|
||||
@@ -171,7 +173,7 @@ function action(mode, type, selection) {
|
||||
return;
|
||||
} else if (status == 6) {
|
||||
if (selection > 0) {
|
||||
var banned = expedition.getMembers().get(selection - 1);
|
||||
var banned = expedMembers.get(selection - 1);
|
||||
expedition.ban(banned);
|
||||
cm.sendOk("You have banned " + banned.getName() + " from the expedition.");
|
||||
cm.dispose();
|
||||
|
||||
@@ -61,7 +61,7 @@ function action(mode, type, selection) {
|
||||
} else if (status == 1) {
|
||||
if (selection == 0) {
|
||||
if (cm.haveItem(4031424)) {
|
||||
if (cm.isMarried()) {
|
||||
if (cm.getPlayer().isMarried()) { // thanks MedicOP for solving an issue here
|
||||
if(cm.getInventory(2).getNextFreeSlot() >= 0) {
|
||||
var rand = Math.floor(Math.random() * bgPrizes.length);
|
||||
cm.gainItem(bgPrizes[rand][0], bgPrizes[rand][1]);
|
||||
|
||||
@@ -29,6 +29,7 @@ importPackage(Packages.scripting.event);
|
||||
|
||||
var status = 0;
|
||||
var expedition;
|
||||
var expedMembers;
|
||||
var player;
|
||||
var em;
|
||||
var cwkpq = MapleExpeditionType.CWKPQ;
|
||||
@@ -64,7 +65,7 @@ function action(mode, type, selection) {
|
||||
status = 2;
|
||||
} else if (expedition.isRegistering()) { //If the expedition is registering
|
||||
if (expedition.contains(player)) { //If you're in it but it hasn't started, be patient
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin the expedition.");
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin it.");
|
||||
cm.dispose();
|
||||
} else { //If you aren't in it, you're going to get added
|
||||
cm.sendOk(expedition.addMember(cm.getPlayer()));
|
||||
@@ -104,7 +105,8 @@ function action(mode, type, selection) {
|
||||
cm.dispose();
|
||||
return;
|
||||
}
|
||||
var size = expedition.getMembers().size();
|
||||
expedMembers = expedition.getMemberList();
|
||||
var size = expedMembers.size();
|
||||
if (size == 1) {
|
||||
cm.sendOk("You are the only member of the expedition.");
|
||||
cm.dispose();
|
||||
@@ -113,13 +115,13 @@ function action(mode, type, selection) {
|
||||
var text = "The following members make up your expedition (Click on them to expel them):\r\n";
|
||||
text += "\r\n\t\t1." + expedition.getLeader().getName();
|
||||
for (var i = 1; i < size; i++) {
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedition.getMembers().get(i).getName() + "#l\n";
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedMembers.get(i).getValue() + "#l\n";
|
||||
}
|
||||
cm.sendSimple(text);
|
||||
status = 6;
|
||||
} else if (selection == 2) {
|
||||
var min = cwkpq.getMinSize();
|
||||
var size = expedition.getMembers().size();
|
||||
var size = expedition.getMemberList().size();
|
||||
if (size < min) {
|
||||
cm.sendOk("You need at least " + min + " players registered in your expedition.");
|
||||
cm.dispose();
|
||||
@@ -154,7 +156,7 @@ function action(mode, type, selection) {
|
||||
return;
|
||||
} else if (status == 6) {
|
||||
if (selection > 0) {
|
||||
var banned = expedition.getMembers().get(selection - 1);
|
||||
var banned = expedMembers.get(selection - 1);
|
||||
expedition.ban(banned);
|
||||
cm.sendOk("You have banned " + banned.getName() + " from the expedition.");
|
||||
cm.dispose();
|
||||
|
||||
@@ -29,6 +29,7 @@ importPackage(Packages.scripting.event);
|
||||
|
||||
var status = 0;
|
||||
var expedition;
|
||||
var expedMembers;
|
||||
var player;
|
||||
var em;
|
||||
var exped = MapleExpeditionType.SCARGA;
|
||||
@@ -69,7 +70,7 @@ function action(mode, type, selection) {
|
||||
status = 2;
|
||||
} else if (expedition.isRegistering()) { //If the expedition is registering
|
||||
if (expedition.contains(player)) { //If you're in it but it hasn't started, be patient
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin the expedition.");
|
||||
cm.sendOk("You have already registered for the expedition. Please wait for #r" + expedition.getLeader().getName() + "#k to begin it.");
|
||||
cm.dispose();
|
||||
} else { //If you aren't in it, you're going to get added
|
||||
cm.sendOk(expedition.addMember(cm.getPlayer()));
|
||||
@@ -121,7 +122,8 @@ function action(mode, type, selection) {
|
||||
cm.dispose();
|
||||
return;
|
||||
}
|
||||
var size = expedition.getMembers().size();
|
||||
expedMembers = expedition.getMemberList();
|
||||
var size = expedMembers.size();
|
||||
if (size == 1) {
|
||||
cm.sendOk("You are the only member of the expedition.");
|
||||
cm.dispose();
|
||||
@@ -130,13 +132,13 @@ function action(mode, type, selection) {
|
||||
var text = "The following members make up your expedition (Click on them to expel them):\r\n";
|
||||
text += "\r\n\t\t1." + expedition.getLeader().getName();
|
||||
for (var i = 1; i < size; i++) {
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedition.getMembers().get(i).getName() + "#l\n";
|
||||
text += "\r\n#b#L" + (i + 1) + "#" + (i + 1) + ". " + expedMembers.get(i).getValue() + "#l\n";
|
||||
}
|
||||
cm.sendSimple(text);
|
||||
status = 6;
|
||||
} else if (selection == 2) {
|
||||
var min = exped.getMinSize();
|
||||
var size = expedition.getMembers().size();
|
||||
var size = expedition.getMemberList().size();
|
||||
if (size < min) {
|
||||
cm.sendOk("You need at least " + min + " players registered in your expedition.");
|
||||
cm.dispose();
|
||||
@@ -171,7 +173,7 @@ function action(mode, type, selection) {
|
||||
return;
|
||||
} else if (status == 6) {
|
||||
if (selection > 0) {
|
||||
var banned = expedition.getMembers().get(selection - 1);
|
||||
var banned = expedMembers.get(selection - 1);
|
||||
expedition.ban(banned);
|
||||
cm.sendOk("You have banned " + banned.getName() + " from the expedition.");
|
||||
cm.dispose();
|
||||
|
||||
@@ -77,6 +77,7 @@ function writeFeatureTab_PlayerSocialNetwork() {
|
||||
addFeature("P. members' HPBar accounts HP gain on equips.");
|
||||
addFeature("Thoroughly reviewed P. Shops and H. Merchants.");
|
||||
addFeature("Transactions on Merchs instantly announced to owner.");
|
||||
addFeature("Proper meso space check on player transactions.");
|
||||
addFeature("Game minirooms with functional pw system.");
|
||||
addFeature("Proper item pickup cooldown on non-owned items.");
|
||||
addFeature("Improved ranking system, with daily movement.");
|
||||
@@ -121,6 +122,7 @@ function writeFeatureTab_MonstersMapsReactors() {
|
||||
addFeature("Added meso drop data for many missing mobs.");
|
||||
addFeature("Monsterbook displays updated drop data info.");
|
||||
addFeature("Every skill/mastery book is now obtainable.");
|
||||
addFeature("Enhanced aggro system: real-time DPS aggro detection.");
|
||||
addFeature("Mobs now can drop more than one of the same equip.");
|
||||
addFeature("Mobs only drop items collectable by the player/party.");
|
||||
addFeature("Mobs shouldn't fall from foothold too often now.");
|
||||
@@ -146,6 +148,7 @@ function writeFeatureTab_MonstersMapsReactors() {
|
||||
addFeature("Added world maps for M. Castle, W. Tour & Ellin areas.");
|
||||
addFeature("Added W. Tour & Masteria continents in the world map.");
|
||||
addFeature("Reviewed several issues with W. Map tooltips & links.");
|
||||
addFeature("Continent separated global drops.");
|
||||
addFeature("Giant Cake boss drops s. bags and Maple items.");
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
*/
|
||||
|
||||
function enter(pi) {
|
||||
if (!pi.isQuestStarted(100200)) {
|
||||
if (!(pi.isQuestStarted(100200) || pi.isQuestCompleted(100200))) {
|
||||
pi.getPlayer().dropMessage(5,"You need approval from the masters to battle. You may not attempt the boss right now.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,12 @@ function end(mode, type, selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(qm.canHoldAll([1302077, 1142066]))) {
|
||||
qm.sendOk("Make some room in your inventory and talk back to me.");
|
||||
qm.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
qm.sendNext("I have just molded your body to make it perfect for a Dawn Warrior. If you wish to become more powerful, use Stat Window (S) to raise the appropriate stats. If you aren't sure what to raise, just click on #bAuto#k.");
|
||||
if (qm.getPlayer().getJob().getId() != 1100) {
|
||||
qm.gainItem(1302077, 1);
|
||||
|
||||
@@ -27,6 +27,12 @@ function end(mode, type, selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(qm.canHoldAll([1372043, 1142066]))) {
|
||||
qm.sendOk("Make some room in your inventory and talk back to me.");
|
||||
qm.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
qm.sendNext("I have just molded your body to make it perfect for a Blaze Wizard. If you wish to become more powerful, use Stat Window (S) to raise the appropriate stats. If you aren't sure what to raise, just click on #bAuto#k.");
|
||||
if (qm.getPlayer().getJob().getId() != 1200) {
|
||||
qm.gainItem(1372043, 1);
|
||||
|
||||
@@ -27,6 +27,12 @@ function end(mode, type, selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(qm.canHoldAll([1452051, 1142066]) && qm.canHold(2070000))) {
|
||||
qm.sendOk("Make some room in your inventory and talk back to me.");
|
||||
qm.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
qm.sendNext("I have just molded your body to make it perfect for a Wind Archer. If you wish to become more powerful, use Stat Window (S) to raise the appropriate stats. If you aren't sure what to raise, just click on #bAuto#k.");
|
||||
if (qm.getPlayer().getJob().getId() != 1300) {
|
||||
qm.gainItem(2060000, 2000);
|
||||
|
||||
@@ -27,11 +27,16 @@ function end(mode, type, selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(qm.canHoldAll([1472061, 1142066]) && qm.canHold(2070000))) {
|
||||
qm.sendOk("Make some room in your inventory and talk back to me.");
|
||||
qm.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
qm.sendNext("I have just molded your body to make it perfect for a Night Walker. If you wish to become more powerful, use Stat Window (S) to raise the appropriate stats. If you aren't sure what to raise, just click on #bAuto#k.");
|
||||
if (qm.getPlayer().getJob().getId() != 1400) {
|
||||
qm.gainItem(1472061, 1);
|
||||
qm.gainItem(2070015, 800);
|
||||
qm.gainItem(2070015, 800);
|
||||
qm.gainItem(2070000, 800);
|
||||
qm.gainItem(1142066, 1);
|
||||
qm.changeJob(MapleJob.NIGHTWALKER1);
|
||||
qm.getPlayer().resetStats();
|
||||
|
||||
@@ -27,6 +27,12 @@ function end(mode, type, selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(qm.canHoldAll([1482014, 1142066]))) {
|
||||
qm.sendOk("Make some room in your inventory and talk back to me.");
|
||||
qm.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
qm.sendNext("I have just molded your body to make it perfect for a Thunder Breaker. If you wish to become more powerful, use Stat Window (S) to raise the appropriate stats. If you aren't sure what to raise, just click on #bAuto#k.");
|
||||
if (qm.getPlayer().getJob().getId() != 1500) {
|
||||
qm.gainItem(1482014, 1);
|
||||
|
||||
@@ -39,7 +39,7 @@ function start(mode, type, selection) {
|
||||
} else if (status == 1) {
|
||||
if (qm.getPlayer().getJob().getId() == 2000) {
|
||||
if(!qm.canHold(1142129)) {
|
||||
cm.sendOk("Wow, your #bequip#k inventory is full. You need to make at least 1 empty slot to complete this quest.");
|
||||
qm.sendOk("Wow, your #bequip#k inventory is full. You need to make at least 1 empty slot to complete this quest.");
|
||||
qm.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ function end(mode, type, selection) {
|
||||
else if (status == 8) {
|
||||
if(!qm.isQuestCompleted(21201)) {
|
||||
if(!qm.canHold(1142130)) {
|
||||
cm.sendOk("Wow, your #bequip#k inventory is full. I need you to make at least 1 empty slot to complete this quest.");
|
||||
qm.sendOk("Wow, your #bequip#k inventory is full. I need you to make at least 1 empty slot to complete this quest."); // thanks MedicOP for finding an issue here
|
||||
qm.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ function end(mode, type, selection) {
|
||||
} else if (status == 2) {
|
||||
if(!qm.isQuestCompleted(21302)) {
|
||||
if(!qm.canHold(1142131)) {
|
||||
cm.sendOk("Wow, your #bequip#k inventory is full. I need you to make at least 1 empty slot to complete this quest.");
|
||||
qm.sendOk("Wow, your #bequip#k inventory is full. I need you to make at least 1 empty slot to complete this quest.");
|
||||
qm.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
+7
-8
@@ -12776,8 +12776,7 @@ INSERT IGNORE INTO `temp_data` (`id`, `dropperid`, `itemid`, `minimum_quantity`,
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `drop_data_global` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
`continent` int(11) NOT NULL,
|
||||
`dropType` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`continent` tinyint(1) NOT NULL DEFAULT '-1',
|
||||
`itemid` int(11) NOT NULL DEFAULT '0',
|
||||
`minimum_quantity` int(11) NOT NULL DEFAULT '1',
|
||||
`maximum_quantity` int(11) NOT NULL DEFAULT '1',
|
||||
@@ -12788,12 +12787,12 @@ CREATE TABLE IF NOT EXISTS `drop_data_global` (
|
||||
KEY `mobid` (`continent`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ROW_FORMAT=DYNAMIC AUTO_INCREMENT=5 ;
|
||||
|
||||
INSERT INTO `drop_data_global` (`id`, `continent`, `dropType`, `itemid`, `minimum_quantity`, `maximum_quantity`, `questid`, `chance`, `comments`) VALUES
|
||||
(1, 0, 0, 4031865, 1, 1, 0, 35000, 'NX Card 100 PTS'),
|
||||
(2, 0, 0, 4031866, 1, 1, 0, 20000, 'NX Card 250 PTS'),
|
||||
(3, 0, 0, 4001126, 1, 2, 0, 8000, 'Maple Leaves'),
|
||||
(4, 0, 0, 2049100, 1, 1, 0, 1200, 'Chaos Scroll 60%'),
|
||||
(5, 0, 0, 4001006, 1, 1, 0, 10000, 'Flaming Feather');
|
||||
INSERT INTO `drop_data_global` (`id`, `continent`, `itemid`, `minimum_quantity`, `maximum_quantity`, `questid`, `chance`, `comments`) VALUES
|
||||
(1, -1, 4031865, 1, 1, 0, 35000, 'NX Card 100 PTS'),
|
||||
(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');
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `dueyitems` (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
|
||||
+2
-2
@@ -6719,8 +6719,8 @@ USE `heavenms`;
|
||||
(4220000, 4010002, 1, 1, 0, 7000),
|
||||
(4220001, 4010002, 1, 1, 0, 7000),
|
||||
(9303014, 4010002, 1, 1, 0, 7000),
|
||||
(4220000, 1442018, 1, 1, 0, 40000),
|
||||
(4220001, 1442018, 1, 1, 0, 40000),
|
||||
(4220000, 1442018, 1, 1, 0, 25000),
|
||||
(4220001, 1442018, 1, 1, 0, 25000),
|
||||
(9303014, 1442018, 1, 1, 0, 700),
|
||||
(4220000, 1302010, 1, 1, 0, 40000),
|
||||
(4220001, 1302010, 1, 1, 0, 40000),
|
||||
|
||||
@@ -273,7 +273,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
private ScheduledFuture<?> pendantOfSpirit = null; //1122017
|
||||
private Lock chrLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHARACTER_CHR, true);
|
||||
private Lock evtLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHARACTER_EVT, true);
|
||||
private Lock petLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHARACTER_PET, true); // for meso & quest tasks as well
|
||||
private Lock petLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHARACTER_PET, true);
|
||||
private Lock prtLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHARACTER_PRT);
|
||||
private Lock cpnLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHARACTER_CPN);
|
||||
private Map<Integer, Set<Integer>> excluded = new LinkedHashMap<>();
|
||||
@@ -852,17 +852,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
}
|
||||
List<Pair<MapleBuffStat, Integer>> ldsstat = Collections.singletonList(new Pair<MapleBuffStat, Integer>(MapleBuffStat.DARKSIGHT, 0));
|
||||
getMap().broadcastGMMessage(this, MaplePacketCreator.giveForeignBuff(id, ldsstat), false);
|
||||
for (MapleMonster mon : this.getControlledMonsters()) {
|
||||
mon.lockMonster();
|
||||
try {
|
||||
mon.setController(null);
|
||||
mon.setControllerHasAggro(false);
|
||||
mon.setControllerKnowsAboutAggro(false);
|
||||
mon.getMap().updateMonsterController(mon);
|
||||
} finally {
|
||||
mon.unlockMonster();
|
||||
}
|
||||
}
|
||||
this.releaseControlledMonsters();
|
||||
}
|
||||
announce(MaplePacketCreator.enableActions());
|
||||
}
|
||||
@@ -1356,7 +1346,8 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
}
|
||||
}
|
||||
|
||||
mapEim.registerPlayer(this);
|
||||
// thanks Thora for finding an issue with players not being actually warped into the target event map (rather sent to the event starting map)
|
||||
mapEim.registerPlayer(this, false);
|
||||
}
|
||||
|
||||
MapleMap to = target; // warps directly to the target intead of the target's map id, this allows GMs to patrol players inside instances.
|
||||
@@ -1708,29 +1699,48 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
}
|
||||
}
|
||||
|
||||
public void checkMonsterAggro(MapleMonster monster) {
|
||||
monster.lockMonster();
|
||||
public void controlMonster(MapleMonster monster) {
|
||||
if (cpnLock.tryLock()) {
|
||||
try {
|
||||
if (!monster.isControllerHasAggro()) {
|
||||
if (monster.getController() == this) {
|
||||
monster.setControllerHasAggro(true);
|
||||
} else {
|
||||
monster.switchController(this, true);
|
||||
}
|
||||
}
|
||||
controlled.add(monster);
|
||||
} finally {
|
||||
monster.unlockMonster();
|
||||
cpnLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void controlMonster(MapleMonster monster, boolean aggro) {
|
||||
monster.lockMonster();
|
||||
public void stopControllingMonster(MapleMonster monster) {
|
||||
if (cpnLock.tryLock()) {
|
||||
try {
|
||||
monster.setController(this);
|
||||
controlled.add(monster);
|
||||
client.announce(MaplePacketCreator.controlMonster(monster, false, aggro));
|
||||
controlled.remove(monster);
|
||||
} finally {
|
||||
monster.unlockMonster();
|
||||
cpnLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getNumControlledMonsters() {
|
||||
cpnLock.lock();
|
||||
try {
|
||||
return controlled.size();
|
||||
} finally {
|
||||
cpnLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseControlledMonsters() {
|
||||
Collection<MapleMonster> controlledMonsters;
|
||||
|
||||
cpnLock.lock();
|
||||
try {
|
||||
controlledMonsters = new ArrayList<>(controlled);
|
||||
controlled.clear();
|
||||
} finally {
|
||||
cpnLock.unlock();
|
||||
}
|
||||
|
||||
for (MapleMonster monster : controlledMonsters) {
|
||||
monster.aggroRedirectController();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2967,6 +2977,11 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean canHoldMeso(int gain) { // thanks lucasziron found pointing out a need to check space availability for mesos on player transactions
|
||||
long nextMeso = (long) meso.get() + gain;
|
||||
return nextMeso <= Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
public void gainMeso(int gain) {
|
||||
gainMeso(gain, true, false, true);
|
||||
}
|
||||
@@ -4125,10 +4140,6 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<MapleMonster> getControlledMonsters() {
|
||||
return Collections.unmodifiableCollection(controlled);
|
||||
}
|
||||
|
||||
public List<MapleRing> getCrushRings() {
|
||||
Collections.sort(crushRings);
|
||||
return crushRings;
|
||||
@@ -4343,7 +4354,15 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
return gachaexp.get();
|
||||
}
|
||||
|
||||
public boolean hasNoviceExpRate() {
|
||||
return ServerConstants.USE_ENFORCE_NOVICE_EXPRATE && isBeginnerJob() && level < 11;
|
||||
}
|
||||
|
||||
public int getExpRate() {
|
||||
if (hasNoviceExpRate()) { // base exp rate 1x for early levels idea thanks to Vcoc
|
||||
return 1;
|
||||
}
|
||||
|
||||
return expRate;
|
||||
}
|
||||
|
||||
@@ -4384,6 +4403,16 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
return mesoRate / (mesoCoupon * getWorldServer().getMesoRate());
|
||||
}
|
||||
|
||||
public int getQuestExpRate() {
|
||||
World w = getWorldServer();
|
||||
return w.getExpRate() * w.getQuestRate();
|
||||
}
|
||||
|
||||
public int getQuestMesoRate() {
|
||||
World w = getWorldServer();
|
||||
return w.getMesoRate() * w.getQuestRate();
|
||||
}
|
||||
|
||||
public int getFace() {
|
||||
return face;
|
||||
}
|
||||
@@ -4829,10 +4858,6 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
}
|
||||
}
|
||||
|
||||
public int getNumControlledMonsters() {
|
||||
return controlled.size();
|
||||
}
|
||||
|
||||
public MapleParty getParty() {
|
||||
prtLock.lock();
|
||||
try {
|
||||
@@ -5637,7 +5662,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
}
|
||||
|
||||
public void leaveMap() {
|
||||
controlled.clear();
|
||||
releaseControlledMonsters();
|
||||
visibleMapObjects.clear();
|
||||
setChair(0);
|
||||
if (hpDecreaseTask != null) {
|
||||
@@ -7772,17 +7797,22 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
throw new RuntimeException("Character not in database (" + id + ")");
|
||||
}
|
||||
|
||||
List<MaplePet> petList = new LinkedList<>();
|
||||
petLock.lock();
|
||||
try {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (pets[i] != null) {
|
||||
pets[i].saveToDb();
|
||||
petList.add(pets[i]);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
petLock.unlock();
|
||||
}
|
||||
|
||||
for (MaplePet pet : petList) {
|
||||
pet.saveToDb();
|
||||
}
|
||||
|
||||
for(Entry<Integer, Set<Integer>> es: getExcluded().entrySet()) { // this set is already protected
|
||||
try (PreparedStatement ps2 = con.prepareStatement("DELETE FROM petignores WHERE petid=?")) {
|
||||
ps2.setInt(1, es.getKey());
|
||||
@@ -8913,10 +8943,6 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
}, duration);
|
||||
}
|
||||
|
||||
public void stopControllingMonster(MapleMonster monster) {
|
||||
controlled.remove(monster);
|
||||
}
|
||||
|
||||
public void unequipAllPets() {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
MaplePet pet = getPet(i);
|
||||
@@ -9046,19 +9072,19 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
}
|
||||
|
||||
public void cancelQuestExpirationTask() {
|
||||
petLock.lock();
|
||||
evtLock.lock();
|
||||
try {
|
||||
if (questExpireTask != null) {
|
||||
questExpireTask.cancel(false);
|
||||
questExpireTask = null;
|
||||
}
|
||||
} finally {
|
||||
petLock.unlock();
|
||||
evtLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void forfeitExpirableQuests() {
|
||||
petLock.lock();
|
||||
evtLock.lock();
|
||||
try {
|
||||
for(MapleQuest quest : questExpirations.keySet()) {
|
||||
quest.forfeit(this);
|
||||
@@ -9066,12 +9092,12 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
|
||||
questExpirations.clear();
|
||||
} finally {
|
||||
petLock.unlock();
|
||||
evtLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void questExpirationTask() {
|
||||
petLock.lock();
|
||||
evtLock.lock();
|
||||
try {
|
||||
if(!questExpirations.isEmpty()) {
|
||||
if(questExpireTask == null) {
|
||||
@@ -9084,12 +9110,12 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
petLock.unlock();
|
||||
evtLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void runQuestExpireTask() {
|
||||
petLock.lock();
|
||||
evtLock.lock();
|
||||
try {
|
||||
long timeNow = Server.getInstance().getCurrentTime();
|
||||
List<MapleQuest> expireList = new LinkedList<>();
|
||||
@@ -9110,12 +9136,12 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
petLock.unlock();
|
||||
evtLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void registerQuestExpire(MapleQuest quest, long time) {
|
||||
petLock.lock();
|
||||
evtLock.lock();
|
||||
try {
|
||||
if(questExpireTask == null) {
|
||||
questExpireTask = TimerManager.getInstance().register(new Runnable() {
|
||||
@@ -9128,7 +9154,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
|
||||
questExpirations.put(quest, Server.getInstance().getCurrentTime() + time);
|
||||
} finally {
|
||||
petLock.unlock();
|
||||
evtLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9671,7 +9697,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
if (pendantOfSpirit != null) { pendantOfSpirit.cancel(true); }
|
||||
pendantOfSpirit = null;
|
||||
|
||||
petLock.lock();
|
||||
evtLock.lock();
|
||||
try {
|
||||
if (questExpireTask != null) {
|
||||
questExpireTask.cancel(false);
|
||||
@@ -9681,7 +9707,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
questExpirations = null;
|
||||
}
|
||||
} finally {
|
||||
petLock.unlock();
|
||||
evtLock.unlock();
|
||||
}
|
||||
|
||||
if (maplemount != null) {
|
||||
|
||||
@@ -175,22 +175,22 @@ public class MapleClient {
|
||||
return chars;
|
||||
}
|
||||
|
||||
public List<String> loadCharacterNames(int serverId) {
|
||||
public List<String> loadCharacterNames(int worldId) {
|
||||
List<String> chars = new ArrayList<>(15);
|
||||
for (CharNameAndId cni : loadCharactersInternal(serverId)) {
|
||||
for (CharNameAndId cni : loadCharactersInternal(worldId)) {
|
||||
chars.add(cni.name);
|
||||
}
|
||||
return chars;
|
||||
}
|
||||
|
||||
private List<CharNameAndId> loadCharactersInternal(int serverId) {
|
||||
private List<CharNameAndId> loadCharactersInternal(int worldId) {
|
||||
PreparedStatement ps;
|
||||
List<CharNameAndId> chars = new ArrayList<>(15);
|
||||
try {
|
||||
Connection con = DatabaseConnection.getConnection();
|
||||
ps = con.prepareStatement("SELECT id, name FROM characters WHERE accountid = ? AND world = ?");
|
||||
ps.setInt(1, this.getAccID());
|
||||
ps.setInt(2, serverId);
|
||||
ps.setInt(2, worldId);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
chars.add(new CharNameAndId(rs.getString("name"), rs.getInt("id")));
|
||||
@@ -882,7 +882,7 @@ public class MapleClient {
|
||||
}
|
||||
|
||||
public final void disconnect(final boolean shutdown, final boolean cashshop) {
|
||||
if (isDisconnecting()) {
|
||||
if (canDisconnect()) {
|
||||
ThreadManager.getInstance().newTask(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -893,12 +893,12 @@ public class MapleClient {
|
||||
}
|
||||
|
||||
public final void forceDisconnect() {
|
||||
if (isDisconnecting()) {
|
||||
if (canDisconnect()) {
|
||||
disconnectInternal(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized boolean isDisconnecting() {
|
||||
private synchronized boolean canDisconnect() {
|
||||
if (disconnecting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class RatesCommand extends Command {
|
||||
|
||||
// travel rates not applicable since it's intrinsically a server/environment rate rather than a character rate
|
||||
String showMsg_ = "#eCHARACTER RATES#n" + "\r\n\r\n";
|
||||
showMsg_ += "EXP Rate: #e#b" + player.getExpRate() + "x#k#n" + "\r\n";
|
||||
showMsg_ += "EXP Rate: #e#b" + player.getExpRate() + "x#k#n" + (player.hasNoviceExpRate() ? " - novice rate" : "") + "\r\n";
|
||||
showMsg_ += "MESO Rate: #e#b" + player.getMesoRate() + "x#k#n" + "\r\n";
|
||||
showMsg_ += "DROP Rate: #e#b" + player.getDropRate() + "x#k#n" + "\r\n";
|
||||
showMsg_ += "BOSS DROP Rate: #e#b" + player.getBossDropRate() + "x#k#n" + "\r\n";
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ShowRatesCommand extends Command {
|
||||
showMsg += "World EXP Rate: #k" + c.getWorldServer().getExpRate() + "x#k" + "\r\n";
|
||||
showMsg += "Player EXP Rate: #k" + player.getRawExpRate() + "x#k" + "\r\n";
|
||||
if(player.getCouponExpRate() != 1) showMsg += "Coupon EXP Rate: #k" + player.getCouponExpRate() + "x#k" + "\r\n";
|
||||
showMsg += "EXP Rate: #e#b" + player.getExpRate() + "x#k#n" + "\r\n";
|
||||
showMsg += "EXP Rate: #e#b" + player.getExpRate() + "x#k#n" + (player.hasNoviceExpRate() ? " - novice rate" : "") + "\r\n";
|
||||
|
||||
showMsg += "\r\n" + "#eMESO RATE#n" + "\r\n";
|
||||
showMsg += "World MESO Rate: #k" + c.getWorldServer().getMesoRate() + "x#k" + "\r\n";
|
||||
@@ -66,7 +66,7 @@ public class ShowRatesCommand extends Command {
|
||||
}
|
||||
|
||||
showMsg += "\r\n";
|
||||
showMsg += "World TRAVEL Rate: #e#b" + c.getWorldServer().getTravelRate() + "x#k#n" + "\r\nServer\r\nPlayer";
|
||||
showMsg += "World TRAVEL Rate: #e#b" + c.getWorldServer().getTravelRate() + "x#k#n" + "\r\n";
|
||||
|
||||
player.showHint(showMsg, 300);
|
||||
}
|
||||
|
||||
@@ -41,8 +41,6 @@ public class GotoCommand extends Command {
|
||||
|
||||
@Override
|
||||
public void execute(MapleClient c, String[] params) {
|
||||
final HashMap<String, Integer> gotomaps = GameConstants.GOTO_MAPS;
|
||||
|
||||
MapleCharacter player = c.getPlayer();
|
||||
if (params.length < 1){
|
||||
player.yellowMessage("Syntax: @goto <map name>");
|
||||
@@ -54,6 +52,13 @@ public class GotoCommand extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
HashMap<String, Integer> gotomaps;
|
||||
if (player.isGM()) {
|
||||
gotomaps = new HashMap<>(GameConstants.GOTO_AREAS); // distinct map registry for GM/users suggested thanks to Vcoc
|
||||
} else {
|
||||
gotomaps = new HashMap<>(GameConstants.GOTO_TOWNS);
|
||||
}
|
||||
|
||||
if (gotomaps.containsKey(params[0])) {
|
||||
MapleMap target = c.getChannelServer().getMapFactory().getMap(gotomaps.get(params[0]));
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ package client.command.commands.gm2;
|
||||
import client.command.Command;
|
||||
import client.MapleClient;
|
||||
import client.MapleCharacter;
|
||||
import server.maps.MapleMap;
|
||||
import net.server.Server;
|
||||
import net.server.channel.Channel;
|
||||
|
||||
@@ -54,33 +55,26 @@ public class SummonCommand extends Command {
|
||||
}
|
||||
}
|
||||
if (victim != null) {
|
||||
boolean changingEvent = true;
|
||||
|
||||
if (victim.getEventInstance() != null) {
|
||||
if (player.getEventInstance() != null && victim.getEventInstance().getLeaderId() == player.getEventInstance().getLeaderId()) {
|
||||
changingEvent = false;
|
||||
} else {
|
||||
victim.getEventInstance().unregisterPlayer(victim);
|
||||
}
|
||||
if (!victim.isLoggedinWorld()) {
|
||||
player.dropMessage(6, "Player currently not logged in or unreachable.");
|
||||
return;
|
||||
}
|
||||
|
||||
//Attempt to join the warpers instance.
|
||||
if (player.getEventInstance() != null && changingEvent) {
|
||||
if (player.getClient().getChannel() == victim.getClient().getChannel()) {
|
||||
player.getEventInstance().registerPlayer(victim);
|
||||
victim.saveLocationOnWarp();
|
||||
victim.changeMap(player.getEventInstance().getMapInstance(player.getMapId()), player.getMap().findClosestPortal(player.getPosition()));
|
||||
} else {
|
||||
player.dropMessage("Target isn't on your channel, not able to warp into event instance.");
|
||||
}
|
||||
} else {//If victim isn't in an event instance or is in the same event instance as the one the caller is, just warp them.
|
||||
victim.saveLocationOnWarp();
|
||||
victim.changeMap(player.getMapId(), player.getMap().findClosestPortal(player.getPosition()));
|
||||
}
|
||||
if (player.getClient().getChannel() != victim.getClient().getChannel()) {//And then change channel if needed.
|
||||
victim.dropMessage("Changing channel, please wait a moment.");
|
||||
victim.getClient().changeChannel(player.getClient().getChannel());
|
||||
}
|
||||
|
||||
try {
|
||||
for (int i = 0; i < 7; i++) { // poll for a while until the player reconnects
|
||||
if (victim.isLoggedinWorld()) break;
|
||||
Thread.sleep(1777);
|
||||
}
|
||||
} catch (InterruptedException e) {}
|
||||
|
||||
MapleMap map = player.getMap();
|
||||
victim.saveLocationOnWarp();
|
||||
victim.forceChangeMap(map, map.findClosestPortal(player.getPosition()));
|
||||
} else {
|
||||
player.dropMessage(6, "Unknown player.");
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import client.command.Command;
|
||||
import server.maps.MapleMap;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
public class WarpAreaCommand extends Command {
|
||||
@@ -54,7 +53,7 @@ public class WarpAreaCommand extends Command {
|
||||
|
||||
Point pos = player.getPosition();
|
||||
|
||||
Collection<MapleCharacter> characters = new ArrayList<>(player.getMap().getCharacters());
|
||||
Collection<MapleCharacter> characters = player.getMap().getAllPlayers();
|
||||
|
||||
for (MapleCharacter victim : characters) {
|
||||
if (victim.getPosition().distanceSq(pos) <= 50000) {
|
||||
|
||||
@@ -28,7 +28,6 @@ import client.MapleClient;
|
||||
import client.command.Command;
|
||||
import server.maps.MapleMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
public class WarpMapCommand extends Command {
|
||||
@@ -51,7 +50,7 @@ public class WarpMapCommand extends Command {
|
||||
return;
|
||||
}
|
||||
|
||||
Collection<MapleCharacter> characters = new ArrayList<>(player.getMap().getCharacters());
|
||||
Collection<MapleCharacter> characters = player.getMap().getAllPlayers();
|
||||
|
||||
for (MapleCharacter victim : characters) {
|
||||
victim.saveLocationOnWarp();
|
||||
|
||||
@@ -30,6 +30,8 @@ import net.server.Server;
|
||||
import net.server.channel.Channel;
|
||||
import server.expeditions.MapleExpedition;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class ExpedsCommand extends Command {
|
||||
{
|
||||
setDescription("");
|
||||
@@ -53,11 +55,11 @@ public class ExpedsCommand extends Command {
|
||||
player.yellowMessage(">> Size: " + exped.getMembers().size());
|
||||
player.yellowMessage(">> Leader: " + exped.getLeader().getName());
|
||||
int memId = 2;
|
||||
for (MapleCharacter member : exped.getMembers()) {
|
||||
if (exped.isLeader(member)) {
|
||||
for (Entry<Integer, String> e : exped.getMembers().entrySet()) {
|
||||
if (exped.isLeader(e.getKey())) {
|
||||
continue;
|
||||
}
|
||||
player.yellowMessage(">>> Member " + memId + ": " + member.getName());
|
||||
player.yellowMessage(">>> Member " + memId + ": " + e.getValue());
|
||||
memId++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import client.MapleClient;
|
||||
import client.MapleCharacter;
|
||||
import server.maps.MapleMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
public class ReloadMapCommand extends Command {
|
||||
@@ -42,7 +41,7 @@ public class ReloadMapCommand extends Command {
|
||||
MapleMap newMap = c.getChannelServer().getMapFactory().resetMap(player.getMapId());
|
||||
int callerid = c.getPlayer().getId();
|
||||
|
||||
Collection<MapleCharacter> characters = new ArrayList<>(player.getMap().getCharacters());
|
||||
Collection<MapleCharacter> characters = player.getMap().getAllPlayers();
|
||||
|
||||
for (MapleCharacter chr : characters) {
|
||||
chr.saveLocationOnWarp();
|
||||
|
||||
@@ -23,12 +23,11 @@
|
||||
*/
|
||||
package client.command.commands.gm3;
|
||||
|
||||
import client.command.Command;
|
||||
import client.MapleClient;
|
||||
import client.MapleCharacter;
|
||||
import client.command.Command;
|
||||
import client.MapleClient;
|
||||
import client.MapleCharacter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.List;
|
||||
|
||||
public class WarpSnowBallCommand extends Command {
|
||||
{
|
||||
@@ -38,7 +37,7 @@ public class WarpSnowBallCommand extends Command {
|
||||
@Override
|
||||
public void execute(MapleClient c, String[] params) {
|
||||
MapleCharacter player = c.getPlayer();
|
||||
List<MapleCharacter> chars = new ArrayList<>(player.getMap().getCharacters());
|
||||
List<MapleCharacter> chars = player.getMap().getAllPlayers();
|
||||
for (MapleCharacter chr : chars) {
|
||||
chr.saveLocationOnWarp();
|
||||
chr.changeMap(109060000, chr.getTeam());
|
||||
|
||||
@@ -72,7 +72,7 @@ public class DebugCommand extends Command {
|
||||
for (MapleMapObject monstermo : monsters) {
|
||||
MapleMonster monster = (MapleMonster) monstermo;
|
||||
MapleCharacter controller = monster.getController();
|
||||
player.message("Monster ID: " + monster.getId() + " Aggro target: " + ((controller != null) ? controller.getName() : "<none>"));
|
||||
player.message("Monster ID: " + monster.getId() + " Aggro target: " + ((controller != null) ? controller.getName() + " Has aggro: " + monster.isControllerHasAggro() + " Knowns aggro: " + monster.isControllerKnowsAboutAggro() : "<none>"));
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -56,8 +56,13 @@ public class Item implements Comparable<Item> {
|
||||
this.id = id;
|
||||
this.position = position;
|
||||
this.quantity = quantity;
|
||||
if (petid > -1) { // issue with null "pet" having petid > -1 found thanks to MedicOP
|
||||
this.pet = MaplePet.loadFromDb(id, position, petid);
|
||||
if (this.pet == null) {
|
||||
petid = -1;
|
||||
}
|
||||
}
|
||||
this.petid = petid;
|
||||
if (petid > -1) this.pet = MaplePet.loadFromDb(id, position, petid);
|
||||
this.flag = 0;
|
||||
this.log = new LinkedList<>();
|
||||
}
|
||||
@@ -122,10 +127,6 @@ public class Item implements Comparable<Item> {
|
||||
return petid;
|
||||
}
|
||||
|
||||
public void setPetId(int id) {
|
||||
this.petid = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Item other) {
|
||||
if (this.id < other.getItemId()) {
|
||||
|
||||
@@ -134,10 +134,10 @@ public class AssignAPProcessor {
|
||||
luk = scStat;
|
||||
str = 0; dex = 0;
|
||||
|
||||
if(luk + chr.getLuk() > CAP) {
|
||||
if(ServerConstants.USE_AUTOASSIGN_SECONDARY_CAP && luk + chr.getLuk() > CAP) {
|
||||
temp = luk + chr.getLuk() - CAP;
|
||||
luk -= temp;
|
||||
int_ += temp;
|
||||
scStat -= temp;
|
||||
prStat += temp;
|
||||
}
|
||||
|
||||
primary = MapleStat.INT;
|
||||
@@ -160,10 +160,10 @@ public class AssignAPProcessor {
|
||||
str = scStat;
|
||||
int_ = 0; luk = 0;
|
||||
|
||||
if(str + chr.getStr() > CAP) {
|
||||
if(ServerConstants.USE_AUTOASSIGN_SECONDARY_CAP && str + chr.getStr() > CAP) {
|
||||
temp = str + chr.getStr() - CAP;
|
||||
str -= temp;
|
||||
dex += temp;
|
||||
scStat -= temp;
|
||||
prStat += temp;
|
||||
}
|
||||
|
||||
primary = MapleStat.DEX;
|
||||
@@ -186,10 +186,10 @@ public class AssignAPProcessor {
|
||||
str = scStat;
|
||||
int_ = 0; luk = 0;
|
||||
|
||||
if(str + chr.getStr() > CAP) {
|
||||
if(ServerConstants.USE_AUTOASSIGN_SECONDARY_CAP && str + chr.getStr() > CAP) {
|
||||
temp = str + chr.getStr() - CAP;
|
||||
str -= temp;
|
||||
dex += temp;
|
||||
scStat -= temp;
|
||||
prStat += temp;
|
||||
}
|
||||
|
||||
primary = MapleStat.DEX;
|
||||
@@ -240,15 +240,15 @@ public class AssignAPProcessor {
|
||||
str = trStat;
|
||||
int_ = 0;
|
||||
|
||||
if(dex + chr.getDex() > CAP) {
|
||||
if(ServerConstants.USE_AUTOASSIGN_SECONDARY_CAP && dex + chr.getDex() > CAP) {
|
||||
temp = dex + chr.getDex() - CAP;
|
||||
dex -= temp;
|
||||
luk += temp;
|
||||
scStat -= temp;
|
||||
prStat += temp;
|
||||
}
|
||||
if(str + chr.getStr() > CAP) {
|
||||
if(ServerConstants.USE_AUTOASSIGN_SECONDARY_CAP && str + chr.getStr() > CAP) {
|
||||
temp = str + chr.getStr() - CAP;
|
||||
str -= temp;
|
||||
luk += temp;
|
||||
trStat -= temp;
|
||||
prStat += temp;
|
||||
}
|
||||
|
||||
primary = MapleStat.LUK;
|
||||
@@ -258,50 +258,64 @@ public class AssignAPProcessor {
|
||||
break;
|
||||
|
||||
case BRAWLER:
|
||||
CAP = 120;
|
||||
default: //warrior, beginner, ...
|
||||
CAP = 300;
|
||||
|
||||
scStat = chr.getLevel() - (chr.getDex() + dex - eqpDex);
|
||||
if(scStat < 0) scStat = 0;
|
||||
scStat = Math.min(scStat, tempAp);
|
||||
|
||||
if(tempAp > scStat) tempAp -= scStat;
|
||||
else tempAp = 0;
|
||||
|
||||
prStat = tempAp;
|
||||
str = prStat;
|
||||
dex = scStat;
|
||||
int_ = 0; luk = 0;
|
||||
|
||||
if(dex + chr.getDex() > CAP) {
|
||||
temp = dex + chr.getDex() - CAP;
|
||||
dex -= temp;
|
||||
str += temp;
|
||||
boolean highDex = false; // thanks lucasziron & Vcoc for finding out DEX autoassigning poorly for STR-based characters
|
||||
if (chr.getLevel() < 40) {
|
||||
if (chr.getDex() >= (2 * chr.getLevel()) + 2) {
|
||||
highDex = true;
|
||||
}
|
||||
} else {
|
||||
if (chr.getDex() >= chr.getLevel() + 42) {
|
||||
highDex = true;
|
||||
}
|
||||
}
|
||||
|
||||
primary = MapleStat.STR;
|
||||
secondary = MapleStat.DEX;
|
||||
|
||||
break;
|
||||
|
||||
default: //warrior, beginner, ...
|
||||
CAP = 80;
|
||||
|
||||
scStat = ((2 * chr.getLevel()) / 3) - (chr.getDex() + dex - eqpDex);
|
||||
// other classes will start favoring more DEX only if a level-based threshold is reached.
|
||||
if(!highDex) {
|
||||
scStat = 0;
|
||||
if(chr.getDex() < 80) {
|
||||
scStat = (2 * chr.getLevel()) - (chr.getDex() + dex - eqpDex);
|
||||
if(scStat < 0) scStat = 0;
|
||||
scStat = Math.min(scStat, tempAp);
|
||||
|
||||
if(tempAp > scStat) tempAp -= scStat;
|
||||
else tempAp = 0;
|
||||
scStat = Math.min(80 - chr.getDex(), scStat);
|
||||
scStat = Math.min(tempAp, scStat);
|
||||
tempAp -= scStat;
|
||||
}
|
||||
|
||||
temp = (chr.getLevel() + 40) - Math.max(80, scStat + chr.getDex() + dex - eqpDex);
|
||||
if(temp < 0) temp = 0;
|
||||
temp = Math.min(tempAp, temp);
|
||||
scStat += temp;
|
||||
tempAp -= temp;
|
||||
} else {
|
||||
scStat = 0;
|
||||
if(chr.getDex() < 96) {
|
||||
scStat = (int)(2.4 * chr.getLevel()) - (chr.getDex() + dex - eqpDex);
|
||||
if(scStat < 0) scStat = 0;
|
||||
|
||||
scStat = Math.min(96 - chr.getDex(), scStat);
|
||||
scStat = Math.min(tempAp, scStat);
|
||||
tempAp -= scStat;
|
||||
}
|
||||
|
||||
temp = 96 + (int)(1.2 * (chr.getLevel() - 40)) - Math.max(96, scStat + chr.getDex() + dex - eqpDex);
|
||||
if(temp < 0) temp = 0;
|
||||
temp = Math.min(tempAp, temp);
|
||||
scStat += temp;
|
||||
tempAp -= temp;
|
||||
}
|
||||
|
||||
prStat = tempAp;
|
||||
str = prStat;
|
||||
dex = scStat;
|
||||
int_ = 0; luk = 0;
|
||||
|
||||
if(dex + chr.getDex() > CAP) {
|
||||
if(ServerConstants.USE_AUTOASSIGN_SECONDARY_CAP && dex + chr.getDex() > CAP) {
|
||||
temp = dex + chr.getDex() - CAP;
|
||||
dex -= temp;
|
||||
str += temp;
|
||||
scStat -= temp;
|
||||
prStat += temp;
|
||||
}
|
||||
|
||||
primary = MapleStat.STR;
|
||||
|
||||
@@ -469,6 +469,11 @@ public class DueyProcessor {
|
||||
}
|
||||
|
||||
if (dp.getItem() != null) {
|
||||
if (!c.getPlayer().canHoldMeso(dp.getMesos())) {
|
||||
c.announce(MaplePacketCreator.sendDueyMSG(Actions.TOCLIENT_RECV_UNKNOWN_ERROR.getCode()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MapleInventoryManipulator.checkSpace(c, dp.getItem().getItemId(), dp.getItem().getQuantity(), dp.getItem().getOwner())) {
|
||||
int itemid = dp.getItem().getItemId();
|
||||
if(MapleItemInformationProvider.getInstance().isPickupRestricted(itemid) && c.getPlayer().getInventory(ItemConstants.getInventoryType(itemid)).findById(itemid) != null) {
|
||||
@@ -483,17 +488,7 @@ public class DueyProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
long gainmesos;
|
||||
long totalmesos = (long) dp.getMesos() + c.getPlayer().getMeso();
|
||||
|
||||
if (totalmesos < 0 || dp.getMesos() < 0) {
|
||||
gainmesos = 0;
|
||||
} else {
|
||||
totalmesos = Math.min(totalmesos, Integer.MAX_VALUE);
|
||||
gainmesos = totalmesos - c.getPlayer().getMeso();
|
||||
}
|
||||
|
||||
c.getPlayer().gainMeso((int)gainmesos, false);
|
||||
c.getPlayer().gainMeso(dp.getMesos(), false);
|
||||
|
||||
removeItemFromDB(packageid);
|
||||
c.announce(MaplePacketCreator.removeItemFromDuey(false, packageid));
|
||||
|
||||
@@ -47,7 +47,7 @@ import tools.Pair;
|
||||
*/
|
||||
public class FredrickProcessor {
|
||||
private static boolean canRetrieveFromFredrick(MapleCharacter chr, List<Pair<Item, MapleInventoryType>> items) {
|
||||
if (chr.getMeso() + chr.getMerchantMeso() < 0) {
|
||||
if (!chr.canHoldMeso(chr.getMerchantMeso())) {
|
||||
return false;
|
||||
}
|
||||
return MapleInventory.checkSpotsAndOwnership(chr, items);
|
||||
|
||||
@@ -45,9 +45,8 @@ public class GameConstants {
|
||||
return(EXP_RATE_GAIN[slot]);
|
||||
}
|
||||
|
||||
// used by the "goto" command
|
||||
public static final HashMap<String, Integer> GOTO_MAPS = new HashMap<String, Integer>() {{
|
||||
put("gmmap", 180000000);
|
||||
// used by the "goto" command for players
|
||||
public static final HashMap<String, Integer> GOTO_TOWNS = new HashMap<String, Integer>() {{
|
||||
put("southperry", 60000);
|
||||
put("amherst", 1000000);
|
||||
put("henesys", 100000000);
|
||||
@@ -72,6 +71,25 @@ public class GameConstants {
|
||||
put("korean", 222000000);
|
||||
put("ellin", 300000000);
|
||||
put("nlc", 600000000);
|
||||
put("showa", 801000000);
|
||||
put("shrine", 800000000);
|
||||
put("ariant", 260000000);
|
||||
put("magatia", 261000000);
|
||||
put("singapore", 540000000);
|
||||
put("quay", 541000000);
|
||||
put("kampung", 551000000);
|
||||
put("amoria", 680000000);
|
||||
put("temple", 270000100);
|
||||
put("square", 103040000);
|
||||
put("neo", 240070000);
|
||||
put("mushking", 106020000);
|
||||
}};
|
||||
|
||||
// used by the "goto" command for only-GMs
|
||||
public static final HashMap<String, Integer> GOTO_AREAS = new HashMap<String, Integer>() {{
|
||||
putAll(GOTO_TOWNS);
|
||||
|
||||
put("gmmap", 180000000);
|
||||
put("excavation", 990000000);
|
||||
put("mushmom", 100000005);
|
||||
put("griffey", 240020101);
|
||||
@@ -80,25 +98,13 @@ public class GameConstants {
|
||||
put("balrog", 105090900);
|
||||
put("zakum", 211042300);
|
||||
put("papu", 220080001);
|
||||
put("showa", 801000000);
|
||||
put("guild", 200000301);
|
||||
put("shrine", 800000000);
|
||||
put("skelegon", 240040511);
|
||||
put("hpq", 100000200);
|
||||
put("pianus", 230040420);
|
||||
put("horntail", 240050400);
|
||||
put("pinkbean", 270050000);
|
||||
put("ariant", 260000000);
|
||||
put("magatia", 261000000);
|
||||
put("singapore", 540000000);
|
||||
put("quay", 541000000);
|
||||
put("kampung", 551000000);
|
||||
put("keep", 610020006);
|
||||
put("amoria", 680000000);
|
||||
put("temple", 270000100);
|
||||
put("square", 103040000);
|
||||
put("neo", 240070000);
|
||||
put("mushking", 106020000);
|
||||
put("dojo", 925020001);
|
||||
put("bosspq", 970030000);
|
||||
put("fm", 910000000);
|
||||
|
||||
@@ -76,13 +76,15 @@ public class ServerConstants {
|
||||
public static final boolean USE_ITEM_SORT_BY_NAME = false; //Item sorting based on name rather than id.
|
||||
public static final boolean USE_PARTY_SEARCH = false;
|
||||
public static final boolean USE_PARTY_FOR_STARTERS = true; //Players level 10 or below can create/invite other players on the given level range.
|
||||
public static final boolean USE_AUTOASSIGN_STARTERS_AP = false; //Beginners level 10 or below have their AP autoassigned (they can't choose to levelup a stat). Set true if the localhost doesn't support AP assigning for beginners level 10 or below.
|
||||
public static final boolean USE_AUTOASSIGN_STARTERS_AP = false; //Beginners level 10 or below have their AP autoassigned (they can't choose to levelup a stat). Set true ONLY if the localhost doesn't support AP assigning for beginners level 10 or below.
|
||||
public static final boolean USE_AUTOASSIGN_SECONDARY_CAP = true;//Prevents AP autoassign from spending on secondary stats after the player class' cap (defined on the autoassign handler) has been reached.
|
||||
public static final boolean USE_AUTOBAN = false; //Commands the server to detect infractors automatically.
|
||||
public static final boolean USE_AUTOBAN_LOG = true; //Log autoban related messages. Still logs even with USE_AUTOBAN disabled.
|
||||
public static final boolean USE_AUTOSAVE = true; //Enables server autosaving feature (saves characters to DB each 1 hour).
|
||||
public static final boolean USE_SERVER_AUTOASSIGNER = true; //HeavenMS-builtin autoassigner, uses algorithm based on distributing AP accordingly with required secondary stat on equipments.
|
||||
public static final boolean USE_REFRESH_RANK_MOVE = true;
|
||||
public static final boolean USE_ENFORCE_ADMIN_ACCOUNT = false; //Forces accounts having GM characters to be treated as a "GM account" by the client (localhost). Some of the GM account perks is the ability to FLY, but unable to TRADE.
|
||||
public static final boolean USE_ENFORCE_NOVICE_EXPRATE = false; //Hardsets experience rate 1x for beginners level 10 or under. Ideal for roaming on novice areas without caring too much about losing some stats.
|
||||
public static final boolean USE_ENFORCE_HPMP_SWAP = false; //Forces players to reuse stats (via AP Resetting) located on HP/MP pool only inside the HP/MP stats.
|
||||
public static final boolean USE_ENFORCE_MOB_LEVEL_RANGE = true; //Players N levels below the killed mob will gain no experience from defeating it.
|
||||
public static final boolean USE_ENFORCE_JOB_LEVEL_RANGE = false;//Caps the player level on the minimum required to advance their current jobs.
|
||||
@@ -166,11 +168,13 @@ public class ServerConstants {
|
||||
public static final int ITEM_EXPIRE_CHECK = 10 * 1000; //Interval between item expiring tasks on maps, which checks and makes disappear expired items.
|
||||
public static final int ITEM_LIMIT_ON_MAP = 200; //Max number of items allowed on a map.
|
||||
public static final int MAP_VISITED_SIZE = 5; //Max length for last mapids visited by a player. This is used to recover and update drops on these maps accordingly with player actions.
|
||||
public static final int MAP_DAMAGE_OVERTIME_INTERVAL = 5000;//Interval in seconds between map environment damage (e.g. El Nath and Aqua Road surrondings).
|
||||
public static final int MAP_DAMAGE_OVERTIME_INTERVAL = 5000;//Interval in milliseconds between map environment damage (e.g. El Nath and Aqua Road surrondings).
|
||||
|
||||
//Channel Mob Disease Monitor Configuration
|
||||
public static final int MOB_STATUS_MONITOR_PROC = 200; //Frequency in milliseconds between each proc on the mob disease monitor schedule.
|
||||
public static final int MOB_STATUS_MONITOR_LIFE = 84; //Idle proc count the mob disease monitor is allowed to be there before closing it due to inactivity.
|
||||
public static final int MOB_STATUS_AGGRO_PERSISTENCE = 2; //Idle proc count on aggro update for a mob to keep following the current controller, given him/her is the leading damage dealer.
|
||||
public static final int MOB_STATUS_AGGRO_INTERVAL = 5000; //Interval in milliseconds between aggro logistics update.
|
||||
|
||||
//Some Gameplay Enhancing Configurations
|
||||
//Scroll Configuration
|
||||
|
||||
@@ -228,6 +228,8 @@ public enum SendOpcode {
|
||||
GIVE_FOREIGN_BUFF(0xC7),
|
||||
CANCEL_FOREIGN_BUFF(0xC8),
|
||||
UPDATE_PARTYMEMBER_HP(0xC9),
|
||||
GUILD_NAME_CHANGED(0xCA),
|
||||
GUILD_MARK_CHANGED(0xCB),
|
||||
THROW_GRENADE(0xCC),
|
||||
CANCEL_CHAIR(0xCD),
|
||||
SHOW_ITEM_GAIN_INCHAT(0xCE),
|
||||
@@ -257,8 +259,8 @@ public enum SendOpcode {
|
||||
DAMAGE_MONSTER(0xF6),
|
||||
ARIANT_THING(0xF9),
|
||||
SHOW_MONSTER_HP(0xFA),
|
||||
SHOW_DRAGGED(0xFB),//CATCH
|
||||
CATCH_MONSTER(0xFC),
|
||||
CATCH_MONSTER(0xFB),
|
||||
CATCH_MONSTER_WITH_ITEM(0xFC),
|
||||
SHOW_MAGNET(0xFD),
|
||||
SPAWN_NPC(0x101),
|
||||
REMOVE_NPC(0x102),
|
||||
|
||||
@@ -35,7 +35,6 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import constants.ServerConstants;
|
||||
import net.server.audit.locks.MonitoredLockType;
|
||||
import server.TimerManager;
|
||||
import tools.FilePrinter;
|
||||
|
||||
@@ -84,6 +84,7 @@ public enum MonitoredLockType {
|
||||
VISITOR_PSHOP,
|
||||
STORAGE,
|
||||
MOB,
|
||||
MOB_AGGRO,
|
||||
MOB_ANI,
|
||||
MOB_EXT,
|
||||
MOB_STATI,
|
||||
@@ -96,6 +97,8 @@ public enum MonitoredLockType {
|
||||
MAP_ITEM,
|
||||
MAP_LOOT,
|
||||
MAP_BOUNDS,
|
||||
MAP_AGGRO,
|
||||
MAP_AGGRO_IDLE,
|
||||
MINIDUNGEON,
|
||||
REACTOR,
|
||||
REACTOR_HIT;
|
||||
|
||||
@@ -274,7 +274,7 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl
|
||||
totDamageToOneMonster += eachd;
|
||||
}
|
||||
totDamage += totDamageToOneMonster;
|
||||
player.checkMonsterAggro(monster);
|
||||
monster.aggroMonsterDamage(player, totDamageToOneMonster);
|
||||
if (player.getBuffedValue(MapleBuffStat.PICKPOCKET) != null && (attack.skill == 0 || attack.skill == Rogue.DOUBLE_STAB || attack.skill == Bandit.SAVAGE_BLOW || attack.skill == ChiefBandit.ASSAULTER || attack.skill == ChiefBandit.BAND_OF_THIEVES || attack.skill == Shadower.ASSASSINATE || attack.skill == Shadower.TAUNT || attack.skill == Shadower.BOOMERANG_STEP)) {
|
||||
Skill pickpocket = SkillFactory.getSkill(ChiefBandit.PICKPOCKET);
|
||||
int picklv = (player.isGM()) ? pickpocket.getMaxLevel() : player.getSkillLevel(pickpocket);
|
||||
|
||||
@@ -51,11 +51,11 @@ public abstract class AbstractMovementPacketHandler extends AbstractMaplePacketH
|
||||
short ypos = lea.readShort();
|
||||
short xwobble = lea.readShort();
|
||||
short ywobble = lea.readShort();
|
||||
short unk = lea.readShort();
|
||||
short fh = lea.readShort();
|
||||
byte newstate = lea.readByte();
|
||||
short duration = lea.readShort();
|
||||
AbsoluteLifeMovement alm = new AbsoluteLifeMovement(command, new Point(xpos, ypos), duration, newstate);
|
||||
alm.setUnk(unk);
|
||||
alm.setFh(fh);
|
||||
alm.setPixelsPerSecond(new Point(xwobble, ywobble));
|
||||
res.add(alm);
|
||||
break;
|
||||
@@ -105,11 +105,11 @@ public abstract class AbstractMovementPacketHandler extends AbstractMaplePacketH
|
||||
/*case 11: { // Chair
|
||||
short xpos = lea.readShort();
|
||||
short ypos = lea.readShort();
|
||||
short unk = lea.readShort();
|
||||
short fh = lea.readShort();
|
||||
byte newstate = lea.readByte();
|
||||
short duration = lea.readShort();
|
||||
ChairMovement cm = new ChairMovement(command, new Point(xpos, ypos), duration, newstate);
|
||||
cm.setUnk(unk);
|
||||
cm.setFh(fh);
|
||||
res.add(cm);
|
||||
break;
|
||||
}*/
|
||||
@@ -118,14 +118,14 @@ public abstract class AbstractMovementPacketHandler extends AbstractMaplePacketH
|
||||
short ypos = lea.readShort();
|
||||
short xwobble = lea.readShort();
|
||||
short ywobble = lea.readShort();
|
||||
short unk = lea.readShort();
|
||||
short fh = lea.readShort();
|
||||
short ofh = lea.readShort();
|
||||
byte newstate = lea.readByte();
|
||||
short duration = lea.readShort();
|
||||
JumpDownMovement jdm = new JumpDownMovement(command, new Point(xpos, ypos), duration, newstate);
|
||||
jdm.setUnk(unk);
|
||||
jdm.setFh(fh);
|
||||
jdm.setPixelsPerSecond(new Point(xwobble, ywobble));
|
||||
jdm.setFH(fh);
|
||||
jdm.setOriginFh(ofh);
|
||||
res.add(jdm);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -32,32 +32,15 @@ public final class AutoAggroHandler extends AbstractMaplePacketHandler {
|
||||
|
||||
@Override
|
||||
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
|
||||
if (c.getPlayer().isHidden()) return; // Don't auto aggro GM's in hide...
|
||||
MapleCharacter player = c.getPlayer();
|
||||
if (player.isHidden()) return; // Don't auto aggro GM's in hide...
|
||||
|
||||
MapleMap map = c.getPlayer().getMap();
|
||||
MapleMap map = player.getMap();
|
||||
int oid = slea.readInt();
|
||||
|
||||
MapleMonster monster = map.getMonsterByOid(oid);
|
||||
if (monster != null) {
|
||||
MapleCharacter currentController = monster.getController();
|
||||
monster.lockMonster();
|
||||
try {
|
||||
if (currentController != null) {
|
||||
if (!monster.isControllerHasAggro()) {
|
||||
if (map.getCharacterById(currentController.getId()) == null) {
|
||||
monster.switchController(c.getPlayer(), true);
|
||||
} else {
|
||||
monster.switchController(currentController, true);
|
||||
}
|
||||
} else if (map.getCharacterById(currentController.getId()) == null) {
|
||||
monster.switchController(c.getPlayer(), true);
|
||||
}
|
||||
} else {
|
||||
monster.switchController(c.getPlayer(), true);
|
||||
}
|
||||
} finally {
|
||||
monster.unlockMonster();
|
||||
}
|
||||
monster.aggroAutoAggroUpdate(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,10 +47,6 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void restancePlayer(MapleCharacter mc) {
|
||||
mc.broadcastStance();
|
||||
}
|
||||
|
||||
private class Invited {
|
||||
public String name;
|
||||
public int gid;
|
||||
@@ -131,7 +127,8 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
|
||||
c.announce(MaplePacketCreator.showGuildInfo(mc));
|
||||
|
||||
c.getPlayer().dropMessage(1, "You have successfully created a Guild.");
|
||||
restancePlayer(mc);
|
||||
mc.getGuild().broadcastNameChanged();
|
||||
mc.getGuild().broadcastEmblemChanged();
|
||||
break;
|
||||
case 0x05:
|
||||
if (mc.getGuildId() <= 0 || mc.getGuildRank() > 2) {
|
||||
@@ -191,7 +188,8 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
|
||||
if(allianceId > 0) Server.getInstance().getAlliance(allianceId).updateAlliancePackets(mc);
|
||||
|
||||
mc.saveGuildStatus(); // update database
|
||||
restancePlayer(mc);
|
||||
mc.getMap().broadcastMessage(mc, MaplePacketCreator.guildNameChanged(mc.getId(), mc.getGuild().getName())); // thanks Vcoc for pointing out an issue with updating guild tooltip to players in the map
|
||||
mc.getMap().broadcastMessage(mc, MaplePacketCreator.guildMarkChanged(mc.getId(), mc.getGuild()));
|
||||
break;
|
||||
case 0x07:
|
||||
cid = slea.readInt();
|
||||
@@ -212,7 +210,7 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
|
||||
mc.getMGC().setGuildId(0);
|
||||
mc.getMGC().setGuildRank(5);
|
||||
mc.saveGuildStatus();
|
||||
restancePlayer(mc);
|
||||
mc.getMap().broadcastMessage(mc, MaplePacketCreator.guildNameChanged(mc.getId(), ""));
|
||||
break;
|
||||
case 0x08:
|
||||
allianceId = mc.getGuild().getAllianceId();
|
||||
@@ -272,7 +270,8 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler {
|
||||
}
|
||||
|
||||
mc.gainMeso(-ServerConstants.CHANGE_EMBLEM_COST, true, false, true);
|
||||
restancePlayer(mc);
|
||||
mc.getGuild().broadcastNameChanged();
|
||||
mc.getGuild().broadcastEmblemChanged();
|
||||
break;
|
||||
case 0x10:
|
||||
if (mc.getGuildId() <= 0 || mc.getGuildRank() > 2) {
|
||||
|
||||
@@ -78,8 +78,6 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler {
|
||||
boolean isAttack = inRangeInclusive(rawActivity, 24, 41);
|
||||
boolean isSkill = inRangeInclusive(rawActivity, 42, 59);
|
||||
|
||||
boolean currentController = (monster.getController() == player);
|
||||
|
||||
MobSkill toUse = null;
|
||||
int useSkillId = 0, useSkillLevel = 0;
|
||||
|
||||
@@ -112,10 +110,6 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler {
|
||||
|
||||
int atkStatus = monster.canUseAttack(castPos, isSkill);
|
||||
if (atkStatus < 1) {
|
||||
if (!currentController) {
|
||||
return;
|
||||
}
|
||||
|
||||
rawActivity = -1;
|
||||
pOption = 0;
|
||||
}
|
||||
@@ -147,24 +141,8 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler {
|
||||
Point startPos = new Point(start_x, start_y - 2);
|
||||
List<LifeMovementFragment> res = parseMovement(slea);
|
||||
|
||||
boolean aggro;
|
||||
monster.lockMonster();
|
||||
try {
|
||||
if (!currentController) {
|
||||
if (monster.isAttackedBy(player)) {
|
||||
monster.switchController(player, true);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
aggro = monster.isControllerHasAggro();
|
||||
if (aggro) {
|
||||
monster.setControllerKnowsAboutAggro(true);
|
||||
}
|
||||
} finally {
|
||||
monster.unlockMonster();
|
||||
}
|
||||
Boolean aggro = monster.aggroMoveLifeUpdate(player);
|
||||
if (aggro == null) return;
|
||||
|
||||
if (nextUse != null) {
|
||||
c.announce(MaplePacketCreator.moveMonsterResponse(objectid, moveid, mobMp, aggro, nextSkillId, nextSkillLevel));
|
||||
@@ -174,7 +152,7 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler {
|
||||
|
||||
if (res != null) {
|
||||
if (ServerConstants.USE_DEBUG_SHOW_RCVD_MVLIFE) {
|
||||
System.out.println((isSkill ? "SKILL " : (isAttack ? "ATTCK " : " ")) + "castPos: " + castPos + " rawAct: " + rawActivity + " opt: " + pOption + " skillID: " + useSkillId + " skillLV: " + useSkillLevel + " " + "allowSkill: " + nextMovementCouldBeSkill);
|
||||
System.out.println((isSkill ? "SKILL " : (isAttack ? "ATTCK " : " ")) + "castPos: " + castPos + " rawAct: " + rawActivity + " opt: " + pOption + " skillID: " + useSkillId + " skillLV: " + useSkillLevel + " " + "allowSkill: " + nextMovementCouldBeSkill + " mobMp: " + mobMp);
|
||||
}
|
||||
|
||||
map.broadcastMessage(player, MaplePacketCreator.moveMonster(objectid, nextMovementCouldBeSkill, rawActivity, useSkillId, useSkillLevel, pOption, startPos, res), monster.getPosition());
|
||||
|
||||
@@ -35,6 +35,7 @@ import net.AbstractMaplePacketHandler;
|
||||
import server.MapleItemInformationProvider;
|
||||
import server.MapleTrade;
|
||||
import constants.GameConstants;
|
||||
import java.sql.SQLException;
|
||||
import server.maps.FieldLimit;
|
||||
import server.maps.MapleHiredMerchant;
|
||||
import server.maps.MapleMapObject;
|
||||
@@ -61,8 +62,8 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
|
||||
CHAT(6),
|
||||
CHAT_THING(8),
|
||||
EXIT(0xA),
|
||||
OPEN(0xB),
|
||||
TRADE_BIRTHDAY(0x0E),
|
||||
OPEN_STORE(0xB),
|
||||
OPEN_CASH(0xE),
|
||||
SET_ITEMS(0xF),
|
||||
SET_MESO(0x10),
|
||||
CONFIRM(0x11),
|
||||
@@ -74,7 +75,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
|
||||
REMOVE_ITEM(0x1B),
|
||||
BAN_PLAYER(0x1C),
|
||||
MERCHANT_THING(0x1D),
|
||||
OPEN_STORE(0x1E),
|
||||
OPEN_THING(0x1E),
|
||||
PUT_ITEM(0x21),
|
||||
MERCHANT_BUY(0x22),
|
||||
TAKE_ITEM_BACK(0x26),
|
||||
@@ -333,14 +334,25 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
|
||||
chr.closeMiniGame();
|
||||
chr.closeHiredMerchant(true);
|
||||
}
|
||||
} else if (mode == Action.OPEN.getCode()) {
|
||||
} else if (mode == Action.OPEN_STORE.getCode() || mode == Action.OPEN_CASH.getCode()) {
|
||||
if (isTradeOpen(chr)) return;
|
||||
|
||||
if (mode == Action.OPEN_STORE.getCode()) {
|
||||
slea.readByte(); //01
|
||||
} else {
|
||||
slea.readShort();
|
||||
int birthday = slea.readInt();
|
||||
if (!CashOperationHandler.checkBirthday(c, birthday)) { // birthday check here found thanks to lucasziron
|
||||
c.announce(MaplePacketCreator.serverNotice(1, "Please check again the birthday date."));
|
||||
return;
|
||||
}
|
||||
|
||||
c.announce(MaplePacketCreator.hiredMerchantOwnerMaintenanceLeave());
|
||||
}
|
||||
|
||||
MaplePlayerShop shop = chr.getPlayerShop();
|
||||
MapleHiredMerchant merchant = chr.getHiredMerchant();
|
||||
if (shop != null && shop.isOwner(chr)) {
|
||||
slea.readByte();//01
|
||||
|
||||
if(ServerConstants.USE_ERASE_PERMIT_ON_OPENSHOP) {
|
||||
try {
|
||||
MapleInventoryManipulator.removeById(c, MapleInventoryType.CASH, shop.getItemId(), 1, true, false);
|
||||
@@ -355,7 +367,6 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
|
||||
chr.getMap().addMapObject(merchant);
|
||||
chr.setHiredMerchant(null);
|
||||
chr.getMap().broadcastMessage(MaplePacketCreator.spawnHiredMerchantBox(merchant));
|
||||
slea.readByte();
|
||||
}
|
||||
} else if (mode == Action.READY.getCode()) {
|
||||
MapleMiniGame game = chr.getMiniGame();
|
||||
@@ -543,27 +554,45 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
|
||||
MaplePlayerShop shop = chr.getPlayerShop();
|
||||
MapleHiredMerchant merchant = chr.getHiredMerchant();
|
||||
if (shop != null && shop.isOwner(chr)) {
|
||||
if (shop.isOpen()) {
|
||||
if (shop.isOpen() || !shop.addItem(shopItem)) { // thanks Vcoc for pointing an exploit with unlimited shop slots
|
||||
c.announce(MaplePacketCreator.serverNotice(1, "You can't sell it anymore."));
|
||||
return;
|
||||
}
|
||||
|
||||
shop.addItem(shopItem);
|
||||
c.announce(MaplePacketCreator.getPlayerShopItemUpdate(shop));
|
||||
} else if (merchant != null && merchant.isOwner(chr)) {
|
||||
if (merchant.isOpen()) {
|
||||
c.announce(MaplePacketCreator.serverNotice(1, "You can't sell it anymore."));
|
||||
return;
|
||||
}
|
||||
|
||||
merchant.addItem(shopItem);
|
||||
c.announce(MaplePacketCreator.updateHiredMerchant(merchant, chr));
|
||||
}
|
||||
if (ItemConstants.isRechargeable(ivItem.getItemId())) {
|
||||
MapleInventoryManipulator.removeFromSlot(c, ivType, slot, ivItem.getQuantity(), true);
|
||||
} else {
|
||||
MapleInventoryManipulator.removeFromSlot(c, ivType, slot, (short) (bundles * perBundle), true);
|
||||
}
|
||||
|
||||
c.announce(MaplePacketCreator.getPlayerShopItemUpdate(shop));
|
||||
} else if (merchant != null && merchant.isOwner(chr)) {
|
||||
if (ivType.equals(MapleInventoryType.CASH) && merchant.isPublished()) {
|
||||
c.announce(MaplePacketCreator.serverNotice(1, "Cash items are only allowed to be sold when first opening the store."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (merchant.isOpen() || !merchant.addItem(shopItem)) { // thanks Vcoc for pointing an exploit with unlimited shop slots
|
||||
c.announce(MaplePacketCreator.serverNotice(1, "You can't sell it anymore."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ItemConstants.isRechargeable(ivItem.getItemId())) {
|
||||
MapleInventoryManipulator.removeFromSlot(c, ivType, slot, ivItem.getQuantity(), true);
|
||||
} else {
|
||||
MapleInventoryManipulator.removeFromSlot(c, ivType, slot, (short) (bundles * perBundle), true);
|
||||
}
|
||||
|
||||
c.announce(MaplePacketCreator.updateHiredMerchant(merchant, chr));
|
||||
|
||||
try {
|
||||
merchant.saveItems(false); // thanks Masterrulax for realizing yet another dupe with merchants/Fredrick
|
||||
} catch (SQLException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
c.announce(MaplePacketCreator.serverNotice(1, "You can't sell without owning a shop."));
|
||||
}
|
||||
} else if (mode == Action.REMOVE_ITEM.getCode()) {
|
||||
if (isTradeOpen(chr)) return;
|
||||
|
||||
@@ -616,8 +645,9 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
|
||||
MaplePlayerShop shop = chr.getPlayerShop();
|
||||
MapleHiredMerchant merchant = chr.getHiredMerchant();
|
||||
if (shop != null && shop.isVisitor(chr)) {
|
||||
shop.buy(c, itemid, quantity);
|
||||
if (shop.buy(c, itemid, quantity)) {
|
||||
shop.broadcast(MaplePacketCreator.getPlayerShopItemUpdate(shop));
|
||||
}
|
||||
} else if (merchant != null && !merchant.isOwner(chr)) {
|
||||
merchant.buy(c, itemid, quantity);
|
||||
merchant.broadcastToVisitorsThreadsafe(MaplePacketCreator.updateHiredMerchant(merchant, chr));
|
||||
@@ -661,6 +691,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
|
||||
if (merchant.isOwner(chr)) {
|
||||
merchant.clearMessages();
|
||||
merchant.setOpen(true);
|
||||
merchant.getMap().broadcastMessage(MaplePacketCreator.updateHiredMerchantBox(merchant));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,9 @@ import constants.ServerConstants;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import net.server.coordinator.MapleEventRecallCoordinator;
|
||||
import net.server.coordinator.MapleSessionCoordinator;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
@@ -72,6 +74,25 @@ import tools.packets.Wedding;
|
||||
|
||||
public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
|
||||
|
||||
private static Set<Integer> attemptingLoginAccounts = new HashSet<>();
|
||||
|
||||
private boolean tryAcquireAccount(int accId) {
|
||||
synchronized (attemptingLoginAccounts) {
|
||||
if (attemptingLoginAccounts.contains(accId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
attemptingLoginAccounts.add(accId);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseAccount(int accId) {
|
||||
synchronized (attemptingLoginAccounts) {
|
||||
attemptingLoginAccounts.remove(accId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean validateState(MapleClient c) {
|
||||
return !c.isLoggedIn();
|
||||
@@ -82,6 +103,8 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
|
||||
final int cid = slea.readInt();
|
||||
final Server server = Server.getInstance();
|
||||
|
||||
if (c.tryacquireClient()) { // thanks MedicOP for assisting on concurrency protection here
|
||||
try {
|
||||
World wserv = server.getWorld(c.getWorld());
|
||||
if(wserv == null) {
|
||||
c.disconnect(true, false);
|
||||
@@ -152,7 +175,8 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
|
||||
}
|
||||
*/
|
||||
|
||||
c.lockClient(); // Sync this to prevent wrong login state for double channel changes
|
||||
int accId = c.getAccID();
|
||||
if (tryAcquireAccount(accId)) { // Sync this to prevent wrong login state for double loggedin handling
|
||||
try {
|
||||
int state = c.getLoginState();
|
||||
if (state != MapleClient.LOGIN_SERVER_TRANSITION || !allowLogin) {
|
||||
@@ -169,7 +193,13 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
|
||||
}
|
||||
c.updateLoginState(MapleClient.LOGIN_LOGGEDIN);
|
||||
} finally {
|
||||
c.unlockClient();
|
||||
releaseAccount(accId);
|
||||
}
|
||||
} else {
|
||||
c.setPlayer(null);
|
||||
c.setAccID(0);
|
||||
c.announce(MaplePacketCreator.getAfterLoginError(10));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!newcomer) {
|
||||
@@ -375,6 +405,12 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
|
||||
eim.registerPlayer(player);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
c.releaseClient();
|
||||
}
|
||||
} else {
|
||||
c.announce(MaplePacketCreator.getAfterLoginError(10));
|
||||
}
|
||||
}
|
||||
|
||||
private static void showDueyNotification(MapleClient c, MapleCharacter player) {
|
||||
|
||||
@@ -91,26 +91,22 @@ public final class SpecialMoveHandler extends AbstractMaplePacketHandler {
|
||||
}
|
||||
if (skillid == Hero.MONSTER_MAGNET || skillid == Paladin.MONSTER_MAGNET || skillid == DarkKnight.MONSTER_MAGNET) { // Monster Magnet
|
||||
int num = slea.readInt();
|
||||
int mobId;
|
||||
byte success;
|
||||
for (int i = 0; i < num; i++) {
|
||||
mobId = slea.readInt();
|
||||
success = slea.readByte();
|
||||
chr.getMap().broadcastMessage(chr, MaplePacketCreator.showMagnet(mobId, success), false);
|
||||
MapleMonster monster = chr.getMap().getMonsterByOid(mobId);
|
||||
int mobOid = slea.readInt();
|
||||
byte success = slea.readByte();
|
||||
chr.getMap().broadcastMessage(chr, MaplePacketCreator.catchMonster(mobOid, success), false);
|
||||
MapleMonster monster = chr.getMap().getMonsterByOid(mobOid);
|
||||
if (monster != null) {
|
||||
if (!monster.isBoss()) {
|
||||
monster.lockMonster();
|
||||
try {
|
||||
monster.switchController(chr, monster.isControllerHasAggro());
|
||||
} finally {
|
||||
monster.unlockMonster();
|
||||
monster.aggroClearDamages();
|
||||
monster.aggroMonsterDamage(chr, 1);
|
||||
|
||||
monster.aggroSwitchController(chr, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
byte direction = slea.readByte();
|
||||
chr.getMap().broadcastMessage(chr, MaplePacketCreator.showBuffeffect(chr.getId(), skillid, chr.getSkillLevel(skillid), direction), false);
|
||||
byte direction = slea.readByte(); // thanks MedicOP for pointing some 3rd-party related issues with Magnet
|
||||
chr.getMap().broadcastMessage(chr, MaplePacketCreator.showBuffeffect(chr.getId(), skillid, chr.getSkillLevel(skillid), 1, direction), false);
|
||||
c.announce(MaplePacketCreator.enableActions());
|
||||
return;
|
||||
} else if (skillid == Brawler.MP_RECOVERY) {// MP Recovery
|
||||
|
||||
@@ -191,7 +191,7 @@ public final class TakeDamageHandler extends AbstractMaplePacketHandler {
|
||||
damage -= bouncedamage;
|
||||
map.damageMonster(chr, attacker, bouncedamage);
|
||||
map.broadcastMessage(chr, MaplePacketCreator.damageMonster(oid, bouncedamage), false, true);
|
||||
chr.checkMonsterAggro(attacker);
|
||||
attacker.aggroMonsterDamage(chr, bouncedamage);
|
||||
}
|
||||
if (attacker != null && damagefrom == -1 && chr.getBuffedValue(MapleBuffStat.BODY_PRESSURE) != null) {
|
||||
Skill skill = SkillFactory.getSkill(Aran.BODY_PRESSURE);
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import constants.ServerConstants;
|
||||
import client.MapleCharacter;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import net.server.Server;
|
||||
import net.server.audit.LockCollector;
|
||||
import server.life.MapleMonster;
|
||||
import server.maps.MapleMap;
|
||||
import net.server.audit.locks.MonitoredLockType;
|
||||
import net.server.audit.locks.MonitoredReentrantLock;
|
||||
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
|
||||
import server.TimerManager;
|
||||
import tools.Pair;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Ronan
|
||||
*/
|
||||
public class MapleMonsterAggroCoordinator {
|
||||
|
||||
private MonitoredReentrantLock lock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MAP_AGGRO);
|
||||
private MonitoredReentrantLock idleLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MAP_AGGRO_IDLE, true);
|
||||
private long lastStopTime = Server.getInstance().getCurrentTime();
|
||||
|
||||
private ScheduledFuture<?> aggroMonitor = null;
|
||||
|
||||
private Map<MapleMonster, Map<Integer, PlayerAggroEntry>> mobAggroEntries = new HashMap<>();
|
||||
private Map<MapleMonster, List<PlayerAggroEntry>> mobSortedAggros = new HashMap<>();
|
||||
|
||||
private class PlayerAggroEntry {
|
||||
protected int cid;
|
||||
protected int averageDamage = 0;
|
||||
protected int currentDamageInstances = 0;
|
||||
protected long accumulatedDamage = 0;
|
||||
|
||||
protected int expireStreak = 0;
|
||||
protected int updateStreak = 0;
|
||||
protected int toNextUpdate = 0;
|
||||
protected int entryRank = -1;
|
||||
|
||||
protected PlayerAggroEntry(int cid) {
|
||||
this.cid = cid;
|
||||
}
|
||||
}
|
||||
|
||||
public void stopAggroCoordinator() {
|
||||
idleLock.lock();
|
||||
try {
|
||||
if (aggroMonitor == null) return;
|
||||
|
||||
aggroMonitor.cancel(false);
|
||||
aggroMonitor = null;
|
||||
} finally {
|
||||
idleLock.unlock();
|
||||
}
|
||||
|
||||
lastStopTime = Server.getInstance().getCurrentTime();
|
||||
}
|
||||
|
||||
public void startAggroCoordinator() {
|
||||
idleLock.lock();
|
||||
try {
|
||||
if (aggroMonitor != null) return;
|
||||
|
||||
aggroMonitor = TimerManager.getInstance().register(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
runAggroUpdate(1);
|
||||
runSortLeadingCharactersAggro();
|
||||
}
|
||||
}, ServerConstants.MOB_STATUS_AGGRO_INTERVAL, ServerConstants.MOB_STATUS_AGGRO_INTERVAL);
|
||||
} finally {
|
||||
idleLock.unlock();
|
||||
}
|
||||
|
||||
int timeDelta = (int) Math.ceil((Server.getInstance().getCurrentTime() - lastStopTime) / ServerConstants.MOB_STATUS_AGGRO_INTERVAL);
|
||||
if (timeDelta > 0) {
|
||||
runAggroUpdate(timeDelta);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateEntryExpiration(PlayerAggroEntry pae) {
|
||||
pae.toNextUpdate = (int) Math.ceil((120000L / ServerConstants.MOB_STATUS_AGGRO_INTERVAL) / Math.pow(2, pae.expireStreak + pae.currentDamageInstances));
|
||||
}
|
||||
|
||||
private static void insertEntryDamage(PlayerAggroEntry pae, int damage) {
|
||||
synchronized (pae) {
|
||||
long totalDamage = pae.averageDamage;
|
||||
totalDamage *= pae.currentDamageInstances;
|
||||
totalDamage += damage;
|
||||
|
||||
pae.expireStreak = 0;
|
||||
pae.updateStreak = 0;
|
||||
updateEntryExpiration(pae);
|
||||
|
||||
pae.currentDamageInstances += 1;
|
||||
pae.averageDamage = (int)(totalDamage / pae.currentDamageInstances);
|
||||
pae.accumulatedDamage = totalDamage;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean expiredAfterUpdateEntryDamage(PlayerAggroEntry pae, int deltaTime) {
|
||||
synchronized (pae) {
|
||||
pae.updateStreak += 1;
|
||||
pae.toNextUpdate -= deltaTime;
|
||||
|
||||
if (pae.toNextUpdate <= 0) { // reached dmg instance expire time
|
||||
pae.expireStreak += 1;
|
||||
updateEntryExpiration(pae);
|
||||
|
||||
pae.currentDamageInstances -= 1;
|
||||
if (pae.currentDamageInstances < 1) { // expired aggro for this player
|
||||
return true;
|
||||
}
|
||||
pae.accumulatedDamage = pae.averageDamage * pae.currentDamageInstances;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void addAggroDamage(MapleMonster mob, int cid, int damage) { // assumption: should not trigger after dispose()
|
||||
if (!mob.isAlive()) return;
|
||||
|
||||
List<PlayerAggroEntry> sortedAggro = mobSortedAggros.get(mob);
|
||||
Map<Integer, PlayerAggroEntry> mobAggro = mobAggroEntries.get(mob);
|
||||
if (mobAggro == null) {
|
||||
if (lock.tryLock()) { // can run unreliably, as fast as possible... try lock that is!
|
||||
try {
|
||||
mobAggro = mobAggroEntries.get(mob);
|
||||
if (mobAggro == null) {
|
||||
mobAggro = new HashMap<>();
|
||||
mobAggroEntries.put(mob, mobAggro);
|
||||
|
||||
sortedAggro = new LinkedList<>();
|
||||
mobSortedAggros.put(mob, sortedAggro);
|
||||
} else {
|
||||
sortedAggro = mobSortedAggros.get(mob);
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PlayerAggroEntry aggroEntry = mobAggro.get(cid);
|
||||
if (aggroEntry == null) {
|
||||
aggroEntry = new PlayerAggroEntry(cid);
|
||||
|
||||
synchronized (mobAggro) {
|
||||
synchronized (sortedAggro) {
|
||||
PlayerAggroEntry mappedEntry = mobAggro.get(cid);
|
||||
|
||||
if (mappedEntry == null) {
|
||||
mobAggro.put(aggroEntry.cid, aggroEntry);
|
||||
sortedAggro.add(aggroEntry);
|
||||
} else {
|
||||
aggroEntry = mappedEntry;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (damage < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
insertEntryDamage(aggroEntry, damage);
|
||||
}
|
||||
|
||||
private void runAggroUpdate(int deltaTime) {
|
||||
List<Pair<MapleMonster, Map<Integer, PlayerAggroEntry>>> aggroMobs = new LinkedList<>();
|
||||
lock.lock();
|
||||
try {
|
||||
for (Entry<MapleMonster, Map<Integer, PlayerAggroEntry>> e : mobAggroEntries.entrySet()) {
|
||||
aggroMobs.add(new Pair<>(e.getKey(), e.getValue()));
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
for (Pair<MapleMonster, Map<Integer, PlayerAggroEntry>> am : aggroMobs) {
|
||||
Map<Integer, PlayerAggroEntry> mobAggro = am.getRight();
|
||||
List<PlayerAggroEntry> sortedAggro = mobSortedAggros.get(am.getLeft());
|
||||
|
||||
if (sortedAggro != null) {
|
||||
List<Integer> toRemove = new LinkedList<>();
|
||||
List<Integer> toRemoveIdx = new ArrayList<>(mobAggro.size());
|
||||
List<Integer> toRemoveByFetch = new LinkedList<>();
|
||||
|
||||
synchronized (mobAggro) {
|
||||
synchronized (sortedAggro) {
|
||||
for (PlayerAggroEntry pae : mobAggro.values()) {
|
||||
if (expiredAfterUpdateEntryDamage(pae, deltaTime)) {
|
||||
toRemove.add(pae.cid);
|
||||
if (pae.entryRank > -1) toRemoveIdx.add(pae.entryRank);
|
||||
else toRemoveByFetch.add(pae.cid);
|
||||
}
|
||||
}
|
||||
|
||||
if (!toRemove.isEmpty()) {
|
||||
for (Integer cid : toRemove) {
|
||||
mobAggro.remove(cid);
|
||||
}
|
||||
|
||||
if (mobAggro.isEmpty()) { // all aggro on this mob expired
|
||||
am.getLeft().aggroResetAggro();
|
||||
}
|
||||
}
|
||||
|
||||
if (!toRemoveIdx.isEmpty()) {
|
||||
Collections.sort(toRemoveIdx, new Comparator<Integer>() { // last to first indexes
|
||||
@Override
|
||||
public int compare(Integer p1, Integer p2) {
|
||||
return p1 < p2 ? 1 : p1.equals(p2) ? 0 : -1;
|
||||
}
|
||||
});
|
||||
|
||||
for (int idx : toRemoveIdx) {
|
||||
sortedAggro.remove(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (!toRemoveByFetch.isEmpty()) {
|
||||
for (Integer cid : toRemoveByFetch) {
|
||||
for (int i = 0; i < sortedAggro.size(); i++) {
|
||||
if (cid.equals(sortedAggro.get(i).cid)) {
|
||||
sortedAggro.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void insertionSortAggroList(List<PlayerAggroEntry> paeList) {
|
||||
for (int i = 1; i < paeList.size(); i++) {
|
||||
PlayerAggroEntry pae = paeList.get(i);
|
||||
long curAccDmg = pae.accumulatedDamage;
|
||||
|
||||
int j = i - 1;
|
||||
while (j >= 0 && curAccDmg > paeList.get(j).accumulatedDamage) {
|
||||
j -= 1;
|
||||
}
|
||||
|
||||
j += 1;
|
||||
if (j != i) {
|
||||
paeList.remove(i);
|
||||
paeList.add(j, pae);
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (PlayerAggroEntry pae : paeList) {
|
||||
pae.entryRank = i;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isLeadingCharacterAggro(MapleMonster mob, MapleCharacter player) {
|
||||
// by assuming the quasi-sorted nature of "mobAggroList", this method
|
||||
// returns whether the player given as parameter can be elected as next aggro leader
|
||||
|
||||
List<PlayerAggroEntry> mobAggroList = mobSortedAggros.get(mob);
|
||||
if (mobAggroList != null) {
|
||||
synchronized (mobAggroList) {
|
||||
mobAggroList = new ArrayList<>(mobAggroList.subList(0, Math.min(mobAggroList.size(), 5)));
|
||||
}
|
||||
|
||||
MapleMap map = mob.getMap();
|
||||
for (PlayerAggroEntry pae : mobAggroList) {
|
||||
MapleCharacter chr = map.getCharacterById(pae.cid);
|
||||
if (chr != null) {
|
||||
if (player.getId() == pae.cid) {
|
||||
return true;
|
||||
} else if (pae.updateStreak < ServerConstants.MOB_STATUS_AGGRO_PERSISTENCE && chr.isAlive()) { // verifies currently leading players activity
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void runSortLeadingCharactersAggro() {
|
||||
List<List<PlayerAggroEntry>> aggroList;
|
||||
lock.lock();
|
||||
try {
|
||||
aggroList = new ArrayList<>(mobSortedAggros.values());
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
for (List<PlayerAggroEntry> mobAggroList : aggroList) {
|
||||
synchronized (mobAggroList) {
|
||||
insertionSortAggroList(mobAggroList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAggroEntries(MapleMonster mob) {
|
||||
lock.lock();
|
||||
try {
|
||||
mobAggroEntries.remove(mob);
|
||||
mobSortedAggros.remove(mob);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
stopAggroCoordinator();
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
mobAggroEntries.clear();
|
||||
mobSortedAggros.clear();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
disposeLocks();
|
||||
}
|
||||
|
||||
private void disposeLocks() {
|
||||
LockCollector.getInstance().registerDisposeAction(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
emptyLocks();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void emptyLocks() {
|
||||
lock = lock.dispose();
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ import java.util.Set;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
|
||||
|
||||
import net.server.PlayerStorage;
|
||||
import net.server.Server;
|
||||
import net.server.channel.Channel;
|
||||
import tools.DatabaseConnection;
|
||||
@@ -109,11 +110,11 @@ public class MapleGuild {
|
||||
con.close();
|
||||
} catch (SQLException se) {
|
||||
se.printStackTrace();
|
||||
System.out.println("Unable to read guild information from sql" + se);
|
||||
System.out.println("Unable to read guild information from sql: " + se);
|
||||
}
|
||||
}
|
||||
|
||||
public void buildNotifications() {
|
||||
private void buildNotifications() {
|
||||
if (!bDirty) {
|
||||
return;
|
||||
}
|
||||
@@ -260,10 +261,10 @@ public class MapleGuild {
|
||||
return name;
|
||||
}
|
||||
|
||||
public java.util.Collection<MapleGuildCharacter> getMembers() {
|
||||
public List<MapleGuildCharacter> getMembers() {
|
||||
membersLock.lock();
|
||||
try {
|
||||
return java.util.Collections.unmodifiableCollection(members);
|
||||
return new ArrayList<>(members);
|
||||
} finally {
|
||||
membersLock.unlock();
|
||||
}
|
||||
@@ -277,6 +278,30 @@ public class MapleGuild {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public void broadcastNameChanged() {
|
||||
PlayerStorage ps = Server.getInstance().getWorld(world).getPlayerStorage();
|
||||
|
||||
for (MapleGuildCharacter mgc : getMembers()) {
|
||||
MapleCharacter chr = ps.getCharacterById(mgc.getId());
|
||||
if (chr == null || !chr.isLoggedinWorld()) continue;
|
||||
|
||||
byte[] packet = MaplePacketCreator.guildNameChanged(chr.getId(), this.getName());
|
||||
chr.getMap().broadcastMessage(chr, packet);
|
||||
}
|
||||
}
|
||||
|
||||
public void broadcastEmblemChanged() {
|
||||
PlayerStorage ps = Server.getInstance().getWorld(world).getPlayerStorage();
|
||||
|
||||
for (MapleGuildCharacter mgc : getMembers()) {
|
||||
MapleCharacter chr = ps.getCharacterById(mgc.getId());
|
||||
if (chr == null || !chr.isLoggedinWorld()) continue;
|
||||
|
||||
byte[] packet = MaplePacketCreator.guildMarkChanged(chr.getId(), this);
|
||||
chr.getMap().broadcastMessage(chr, packet);
|
||||
}
|
||||
}
|
||||
|
||||
public void broadcast(final byte[] packet) {
|
||||
broadcast(packet, -1, BCOp.NONE);
|
||||
}
|
||||
|
||||
@@ -612,7 +612,15 @@ public class World {
|
||||
mc.saveGuildStatus();
|
||||
}
|
||||
if (bDifferentGuild) {
|
||||
mc.broadcastStance();
|
||||
if (mc.isLoggedinWorld()) {
|
||||
MapleGuild guild = Server.getInstance().getGuild(guildid);
|
||||
if (guild != null) {
|
||||
mc.getMap().broadcastMessage(mc, MaplePacketCreator.guildNameChanged(cid, guild.getName()));
|
||||
mc.getMap().broadcastMessage(mc, MaplePacketCreator.guildMarkChanged(cid, guild));
|
||||
} else {
|
||||
mc.getMap().broadcastMessage(mc, MaplePacketCreator.guildNameChanged(cid, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -237,6 +237,15 @@ public class AbstractPlayerInteraction {
|
||||
return intList;
|
||||
}
|
||||
|
||||
public boolean canHoldAll(List<Double> itemids) {
|
||||
List<Double> quantity = new LinkedList<>();
|
||||
for (int i = 0; i < itemids.size(); i++) {
|
||||
quantity.add(1.0);
|
||||
}
|
||||
|
||||
return canHoldAll(itemids, quantity);
|
||||
}
|
||||
|
||||
public boolean canHoldAll(List<Double> itemids, List<Double> quantity) {
|
||||
return canHoldAll(convertToIntegerArray(itemids), convertToIntegerArray(quantity), true);
|
||||
}
|
||||
@@ -555,8 +564,6 @@ public class AbstractPlayerInteraction {
|
||||
if(expires >= 0)
|
||||
item.setExpiration(System.currentTimeMillis() + expires);
|
||||
|
||||
item.setPetId(petId);
|
||||
|
||||
if (!MapleInventoryManipulator.checkSpace(c, id, quantity, "")) {
|
||||
c.getPlayer().dropMessage(1, "Your inventory is full. Please remove an item from your " + ItemConstants.getInventoryType(id).name() + " inventory.");
|
||||
return null;
|
||||
|
||||
@@ -230,6 +230,10 @@ public class EventInstanceManager {
|
||||
}
|
||||
|
||||
public synchronized void registerPlayer(final MapleCharacter chr) {
|
||||
registerPlayer(chr, true);
|
||||
}
|
||||
|
||||
public synchronized void registerPlayer(final MapleCharacter chr, boolean runEntryScript) {
|
||||
if (chr == null || !chr.isLoggedinWorld() || disposed) {
|
||||
return;
|
||||
}
|
||||
@@ -246,12 +250,14 @@ public class EventInstanceManager {
|
||||
wL.unlock();
|
||||
}
|
||||
|
||||
if (runEntryScript) {
|
||||
try {
|
||||
em.getIv().invokeFunction("playerEntry", EventInstanceManager.this, chr);
|
||||
} catch (ScriptException | NoSuchMethodException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void exitPlayer(final MapleCharacter chr) {
|
||||
if (chr == null || !chr.isLoggedin()){
|
||||
@@ -372,7 +378,7 @@ public class EventInstanceManager {
|
||||
private void registerExpeditionTeam(MapleExpedition exped, int recruitMap) {
|
||||
expedition = exped;
|
||||
|
||||
for (MapleCharacter chr: exped.getMembers()) {
|
||||
for (MapleCharacter chr: exped.getActiveMembers()) {
|
||||
if (chr.getMapId() == recruitMap) {
|
||||
registerPlayer(chr);
|
||||
}
|
||||
@@ -628,7 +634,6 @@ public class EventInstanceManager {
|
||||
sL.lock();
|
||||
try {
|
||||
if(!eventCleared) em.disposeInstance(name);
|
||||
em = null;
|
||||
} finally {
|
||||
sL.unlock();
|
||||
}
|
||||
@@ -636,10 +641,11 @@ public class EventInstanceManager {
|
||||
TimerManager.getInstance().schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mapFactory.dispose(); // reactors issue on dispose event maps found thanks to MedicOP
|
||||
mapFactory.dispose(); // issues from instantly disposing some event objects found thanks to MedicOP
|
||||
wL.lock();
|
||||
try {
|
||||
mapFactory = null;
|
||||
em = null;
|
||||
} finally {
|
||||
wL.unlock();
|
||||
}
|
||||
@@ -675,7 +681,6 @@ public class EventInstanceManager {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if(em == null) return;
|
||||
em.getIv().invokeFunction(methodName, EventInstanceManager.this);
|
||||
} catch (ScriptException | NoSuchMethodException ex) {
|
||||
ex.printStackTrace();
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
package scripting.event;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
@@ -116,13 +115,16 @@ public class EventManager {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
Collection<EventInstanceManager> eimList;
|
||||
synchronized(instances) {
|
||||
for(EventInstanceManager eim : instances.values()) {
|
||||
eim.dispose(true);
|
||||
}
|
||||
eimList = getInstances();
|
||||
instances.clear();
|
||||
}
|
||||
|
||||
for(EventInstanceManager eim : eimList) {
|
||||
eim.dispose(true);
|
||||
}
|
||||
|
||||
List<EventInstanceManager> readyEims;
|
||||
queueLock.lock();
|
||||
try {
|
||||
@@ -253,7 +255,9 @@ public class EventManager {
|
||||
}
|
||||
|
||||
public Collection<EventInstanceManager> getInstances() {
|
||||
return Collections.unmodifiableCollection(instances.values());
|
||||
synchronized (instances) {
|
||||
return new LinkedList<>(instances.values());
|
||||
}
|
||||
}
|
||||
|
||||
public EventInstanceManager newInstance(String name) {
|
||||
@@ -265,7 +269,13 @@ public class EventManager {
|
||||
ret.setName(name);
|
||||
}
|
||||
|
||||
synchronized (instances) {
|
||||
if (instances.containsKey(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
instances.put(name, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -274,8 +284,11 @@ public class EventManager {
|
||||
@Override
|
||||
public void run() {
|
||||
freeLobbyInstance(name);
|
||||
|
||||
synchronized (instances) {
|
||||
instances.remove(name);
|
||||
}
|
||||
}
|
||||
}, ServerConstants.EVENT_LOBBY_DELAY * 1000);
|
||||
}
|
||||
|
||||
@@ -379,14 +392,17 @@ public class EventManager {
|
||||
if(!startLobbyInstance(lobbyId)) return false;
|
||||
}
|
||||
|
||||
EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", leader.getClient().getChannel()));
|
||||
if(eim == null) {
|
||||
EventInstanceManager eim;
|
||||
try {
|
||||
eim = (EventInstanceManager) (iv.invokeFunction("setup", leader.getClient().getChannel()));
|
||||
instanceLocks.put(eim.getName(), lobbyId);
|
||||
} catch (NullPointerException npe) {
|
||||
if(lobbyId > -1) {
|
||||
setLockLobby(lobbyId, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
instanceLocks.put(eim.getName(), lobbyId);
|
||||
|
||||
eim.setLeader(leader);
|
||||
|
||||
exped.start();
|
||||
@@ -442,14 +458,16 @@ public class EventManager {
|
||||
}
|
||||
}
|
||||
|
||||
EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", difficulty, (lobbyId > -1) ? lobbyId : leader.getId()));
|
||||
if(eim == null) {
|
||||
EventInstanceManager eim;
|
||||
try {
|
||||
eim = (EventInstanceManager) (iv.invokeFunction("setup", difficulty, (lobbyId > -1) ? lobbyId : leader.getId()));
|
||||
instanceLocks.put(eim.getName(), lobbyId);
|
||||
} catch (NullPointerException npe) {
|
||||
if(lobbyId > -1) {
|
||||
setLockLobby(lobbyId, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
instanceLocks.put(eim.getName(), lobbyId);
|
||||
eim.setLeader(leader);
|
||||
|
||||
if(chr != null) eim.registerPlayer(chr);
|
||||
@@ -500,14 +518,17 @@ public class EventManager {
|
||||
if(!startLobbyInstance(lobbyId)) return false;
|
||||
}
|
||||
|
||||
EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", (Object) null));
|
||||
if(eim == null) {
|
||||
EventInstanceManager eim;
|
||||
try {
|
||||
eim = (EventInstanceManager) (iv.invokeFunction("setup", (Object) null));
|
||||
instanceLocks.put(eim.getName(), lobbyId);
|
||||
} catch (NullPointerException npe) {
|
||||
if(lobbyId > -1) {
|
||||
setLockLobby(lobbyId, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
instanceLocks.put(eim.getName(), lobbyId);
|
||||
|
||||
eim.setLeader(leader);
|
||||
|
||||
eim.registerParty(party, map);
|
||||
@@ -559,14 +580,17 @@ public class EventManager {
|
||||
if(!startLobbyInstance(lobbyId)) return false;
|
||||
}
|
||||
|
||||
EventInstanceManager eim = (EventInstanceManager) (iv.invokeFunction("setup", difficulty, (lobbyId > -1) ? lobbyId : party.getLeaderId()));
|
||||
if(eim == null) {
|
||||
EventInstanceManager eim;
|
||||
try {
|
||||
eim = (EventInstanceManager) (iv.invokeFunction("setup", difficulty, (lobbyId > -1) ? lobbyId : party.getLeaderId()));
|
||||
instanceLocks.put(eim.getName(), lobbyId);
|
||||
} catch (NullPointerException npe) {
|
||||
if(lobbyId > -1) {
|
||||
setLockLobby(lobbyId, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
instanceLocks.put(eim.getName(), lobbyId);
|
||||
|
||||
eim.setLeader(leader);
|
||||
|
||||
eim.registerParty(party, map);
|
||||
|
||||
@@ -100,6 +100,7 @@ public class NPCConversationManager extends AbstractPlayerInteraction {
|
||||
|
||||
public void dispose() {
|
||||
NPCScriptManager.getInstance().dispose(this);
|
||||
getClient().announce(MaplePacketCreator.enableActions());
|
||||
}
|
||||
|
||||
public void sendNext(String text) {
|
||||
|
||||
@@ -202,6 +202,10 @@ public class MapleTrade {
|
||||
return exchangeMeso;
|
||||
}
|
||||
|
||||
private boolean fitsMeso() {
|
||||
return chr.canHoldMeso(exchangeMeso - getFee(exchangeMeso));
|
||||
}
|
||||
|
||||
private boolean fitsInInventory() {
|
||||
List<Pair<Item, MapleInventoryType>> tradeItems = new LinkedList<>();
|
||||
for (Item item : exchangeItems) {
|
||||
@@ -218,6 +222,18 @@ public class MapleTrade {
|
||||
if (partner.isLocked()) {
|
||||
local.complete1();
|
||||
partner.complete1();
|
||||
if (!local.fitsMeso()) {
|
||||
cancelTrade(c);
|
||||
c.message("There is not enough meso inventory space to complete the trade.");
|
||||
partner.getChr().message("Partner does not have enough meso inventory space to complete the trade.");
|
||||
return;
|
||||
}
|
||||
else if (!partner.fitsMeso()) {
|
||||
cancelTrade(c);
|
||||
c.message("Partner does not have enough meso inventory space to complete the trade.");
|
||||
partner.getChr().message("There is not enough meso inventory space to complete the trade.");
|
||||
return;
|
||||
}
|
||||
if (!local.fitsInInventory()) {
|
||||
cancelTrade(c);
|
||||
c.message("There is not enough inventory space to complete the trade.");
|
||||
|
||||
@@ -23,11 +23,17 @@
|
||||
package server.expeditions;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import net.server.PlayerStorage;
|
||||
import net.server.Server;
|
||||
import server.TimerManager;
|
||||
import server.life.MapleMonster;
|
||||
@@ -78,18 +84,18 @@ public class MapleExpedition {
|
||||
private MapleExpeditionType type;
|
||||
private boolean registering;
|
||||
private MapleMap startMap;
|
||||
private ArrayList<String> bossLogs;
|
||||
private List<String> bossLogs;
|
||||
private ScheduledFuture<?> schedule;
|
||||
private List<MapleCharacter> members = new ArrayList<>();
|
||||
private List<Integer> banned = new ArrayList<>();
|
||||
private Map<Integer, String> members = new ConcurrentHashMap<>();
|
||||
private List<Integer> banned = new CopyOnWriteArrayList<>();
|
||||
private long startTime;
|
||||
|
||||
public MapleExpedition(MapleCharacter player, MapleExpeditionType met) {
|
||||
leader = player;
|
||||
members.add(leader);
|
||||
members.put(player.getId(), player.getName());
|
||||
startMap = player.getMap();
|
||||
type = met;
|
||||
bossLogs = new ArrayList<String>();
|
||||
bossLogs = new CopyOnWriteArrayList<>();
|
||||
beginRegistration();
|
||||
}
|
||||
|
||||
@@ -109,7 +115,7 @@ public class MapleExpedition {
|
||||
@Override
|
||||
public void run() {
|
||||
if (registering){
|
||||
leader.getClient().getChannelServer().getExpeditions().remove(exped);
|
||||
startMap.getChannelServer().getExpeditions().remove(exped);
|
||||
startMap.broadcastMessage(MaplePacketCreator.serverNotice(6, "[Expedition] The time limit has been reached. Expedition has been disbanded."));
|
||||
|
||||
dispose(false);
|
||||
@@ -134,7 +140,7 @@ public class MapleExpedition {
|
||||
broadcastExped(MaplePacketCreator.removeClock());
|
||||
broadcastExped(MaplePacketCreator.serverNotice(6, "[Expedition] The expedition has started! Good luck, brave heroes!"));
|
||||
startTime = System.currentTimeMillis();
|
||||
Server.getInstance().broadcastGMMessage(leader.getWorld(), MaplePacketCreator.serverNotice(6, "[Expedition] " + type.toString() + " Expedition started with leader: " + leader.getName()));
|
||||
Server.getInstance().broadcastGMMessage(startMap.getWorld(), MaplePacketCreator.serverNotice(6, "[Expedition] " + type.toString() + " Expedition started with leader: " + leader.getName()));
|
||||
}
|
||||
|
||||
public String addMember(MapleCharacter player) {
|
||||
@@ -147,22 +153,21 @@ public class MapleExpedition {
|
||||
if (members.size() >= type.getMaxSize()){ //Would be a miracle if anybody ever saw this
|
||||
return "Sorry, this expedition is full!";
|
||||
}
|
||||
if (members.add(player)){
|
||||
|
||||
members.put(player.getId(), player.getName());
|
||||
player.announce(MaplePacketCreator.getClock((int)(startTime - System.currentTimeMillis()) / 1000));
|
||||
broadcastExped(MaplePacketCreator.serverNotice(6, "[Expedition] " + player.getName() + " has joined the expedition!"));
|
||||
return "You have registered for the expedition successfully!";
|
||||
}
|
||||
return "Sorry, something went really wrong. Report this on the forum with a screenshot!";
|
||||
}
|
||||
|
||||
private void broadcastExped(byte [] data){
|
||||
for (MapleCharacter member : members){
|
||||
member.getClient().announce(data);
|
||||
private void broadcastExped(byte[] packet){
|
||||
for (MapleCharacter chr : getActiveMembers()){
|
||||
chr.announce(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean removeMember(MapleCharacter chr) {
|
||||
if(members.remove(chr)) {
|
||||
if(members.remove(chr.getId()) != null) {
|
||||
chr.announce(MaplePacketCreator.removeClock());
|
||||
broadcastExped(MaplePacketCreator.serverNotice(6, "[Expedition] " + chr.getName() + " has left the expedition."));
|
||||
chr.dropMessage(6, "[Expedition] You have left this expedition.");
|
||||
@@ -176,8 +181,41 @@ public class MapleExpedition {
|
||||
return type;
|
||||
}
|
||||
|
||||
public List<MapleCharacter> getMembers() {
|
||||
return members;
|
||||
public List<MapleCharacter> getActiveMembers() { // thanks MedicOP for figuring out an issue with broadcasting packets to offline members
|
||||
PlayerStorage ps = startMap.getWorldServer().getPlayerStorage();
|
||||
|
||||
List<MapleCharacter> activeMembers = new LinkedList<>();
|
||||
for (Integer chrid : getMembers().keySet()){
|
||||
MapleCharacter chr = ps.getCharacterById(chrid);
|
||||
if (chr != null && chr.isLoggedinWorld()) {
|
||||
activeMembers.add(chr);
|
||||
}
|
||||
}
|
||||
|
||||
return activeMembers;
|
||||
}
|
||||
|
||||
public Map<Integer, String> getMembers() {
|
||||
return new HashMap<>(members);
|
||||
}
|
||||
|
||||
public List<Entry<Integer, String>> getMemberList() {
|
||||
List<Entry<Integer, String>> memberList = new LinkedList<>();
|
||||
Entry<Integer, String> leaderEntry = null;
|
||||
|
||||
for (Entry<Integer, String> e : getMembers().entrySet()) {
|
||||
if (!isLeader(e.getKey())) {
|
||||
memberList.add(e);
|
||||
} else {
|
||||
leaderEntry = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (leaderEntry != null) {
|
||||
memberList.add(0, leaderEntry);
|
||||
}
|
||||
|
||||
return memberList;
|
||||
}
|
||||
|
||||
public MapleCharacter getLeader(){
|
||||
@@ -189,16 +227,15 @@ public class MapleExpedition {
|
||||
}
|
||||
|
||||
public boolean contains(MapleCharacter player) {
|
||||
for (MapleCharacter member : members){
|
||||
if (member.getId() == player.getId()){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return members.containsKey(player.getId());
|
||||
}
|
||||
|
||||
public boolean isLeader(MapleCharacter player) {
|
||||
return leader.equals(player);
|
||||
return isLeader(player.getId());
|
||||
}
|
||||
|
||||
public boolean isLeader(int playerid) {
|
||||
return leader.getId() == playerid;
|
||||
}
|
||||
|
||||
public boolean isRegistering(){
|
||||
@@ -209,23 +246,28 @@ public class MapleExpedition {
|
||||
return !registering;
|
||||
}
|
||||
|
||||
public void ban(MapleCharacter player) {
|
||||
if (!banned.contains(player.getId())) {
|
||||
banned.add(player.getId());
|
||||
members.remove(player);
|
||||
public void ban(Entry<Integer, String> chr) {
|
||||
int cid = chr.getKey();
|
||||
|
||||
broadcastExped(MaplePacketCreator.serverNotice(6, "[Expedition] " + player.getName() + " has been banned from the expedition."));
|
||||
if (!banned.contains(cid)) {
|
||||
banned.add(cid);
|
||||
members.remove(cid);
|
||||
|
||||
broadcastExped(MaplePacketCreator.serverNotice(6, "[Expedition] " + chr.getValue() + " has been banned from the expedition."));
|
||||
|
||||
MapleCharacter player = startMap.getWorldServer().getPlayerStorage().getCharacterById(cid);
|
||||
if (player != null && player.isLoggedinWorld()) {
|
||||
player.announce(MaplePacketCreator.removeClock());
|
||||
player.dropMessage(6, "[Expedition] You have been banned from this expedition.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long getStartTime(){
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public ArrayList<String> getBossLogs(){
|
||||
public List<String> getBossLogs(){
|
||||
return bossLogs;
|
||||
}
|
||||
|
||||
|
||||
+257
-131
@@ -55,7 +55,6 @@ import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import net.server.audit.locks.MonitoredReentrantLock;
|
||||
import net.server.Server;
|
||||
import net.server.channel.Channel;
|
||||
import net.server.world.World;
|
||||
import net.server.world.MapleParty;
|
||||
@@ -72,6 +71,7 @@ import tools.Randomizer;
|
||||
import net.server.audit.LockCollector;
|
||||
import net.server.audit.locks.MonitoredLockType;
|
||||
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
|
||||
import net.server.coordinator.MapleMonsterAggroCoordinator;
|
||||
|
||||
public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
private ChangeableStats ostats = null; //unused, v83 WZs offers no support for changeable stats.
|
||||
@@ -100,6 +100,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
private MonitoredReentrantLock monsterLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB, true);
|
||||
private MonitoredReentrantLock statiLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB_STATI);
|
||||
private MonitoredReentrantLock animationLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB_ANI);
|
||||
private MonitoredReentrantLock aggroUpdateLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.MOB_AGGRO);
|
||||
|
||||
public MapleMonster(int id, MapleMonsterStats stats) {
|
||||
super(id);
|
||||
@@ -578,31 +579,10 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<MapleCharacter, Boolean> removeController() {
|
||||
this.lockMonster();
|
||||
try {
|
||||
MapleCharacter chrController = getController();
|
||||
boolean hadAggro = isControllerHasAggro();
|
||||
|
||||
if (chrController != null) { // this can/should only happen when a hidden gm attacks the monster
|
||||
chrController.announce(MaplePacketCreator.stopControllingMonster(this.getObjectId()));
|
||||
chrController.stopControllingMonster(this);
|
||||
}
|
||||
|
||||
setController(null);
|
||||
setControllerHasAggro(false);
|
||||
setControllerKnowsAboutAggro(false);
|
||||
|
||||
return new Pair<>(chrController, hadAggro);
|
||||
} finally {
|
||||
this.unlockMonster();
|
||||
}
|
||||
}
|
||||
|
||||
public MapleCharacter killBy(final MapleCharacter killer) {
|
||||
distributeExperience(killer != null ? killer.getId() : 0);
|
||||
|
||||
final Pair<MapleCharacter, Boolean> lastController = removeController();
|
||||
final Pair<MapleCharacter, Boolean> lastController = aggroRemoveController();
|
||||
final List<Integer> toSpawn = this.getRevives();
|
||||
if (toSpawn != null) {
|
||||
final MapleMap reviveMap = map;
|
||||
@@ -675,7 +655,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
reviveMap.killMonster(reviveMap.getMonsterById(i), killer, true);
|
||||
}
|
||||
} else if (controller != null) {
|
||||
mob.switchController(controller, aggro);
|
||||
mob.aggroSwitchController(controller, aggro);
|
||||
}
|
||||
|
||||
if(eim != null) {
|
||||
@@ -725,10 +705,12 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
}
|
||||
|
||||
private synchronized void processMonsterKilled(boolean hasKiller) {
|
||||
if(!hasKiller) {
|
||||
if(!hasKiller) { // players won't gain EXP from a mob that has no killer, but a quest count they should
|
||||
dispatchUpdateQuestMobCount();
|
||||
}
|
||||
|
||||
this.aggroClearDamages();
|
||||
|
||||
MonsterListener[] listenersList;
|
||||
statiLock.lock();
|
||||
try {
|
||||
@@ -795,45 +777,6 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
return this.hp.get() > 0;
|
||||
}
|
||||
|
||||
public MapleCharacter getController() {
|
||||
monsterLock.lock();
|
||||
try {
|
||||
return controller.get();
|
||||
} finally {
|
||||
monsterLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void setController(MapleCharacter controller) {
|
||||
monsterLock.lock();
|
||||
try {
|
||||
this.controller = new WeakReference<>(controller);
|
||||
} finally {
|
||||
monsterLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void switchController(MapleCharacter newController, boolean immediateAggro) {
|
||||
this.lockMonster();
|
||||
try {
|
||||
MapleCharacter controllers = getController();
|
||||
if (controllers == newController) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeController();
|
||||
|
||||
newController.controlMonster(this, immediateAggro);
|
||||
setController(newController);
|
||||
if (immediateAggro) {
|
||||
setControllerHasAggro(true);
|
||||
}
|
||||
setControllerKnowsAboutAggro(false);
|
||||
} finally {
|
||||
this.unlockMonster();
|
||||
}
|
||||
}
|
||||
|
||||
public void addListener(MonsterListener listener) {
|
||||
statiLock.lock();
|
||||
try {
|
||||
@@ -843,46 +786,28 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isControllerHasAggro() {
|
||||
monsterLock.lock();
|
||||
try {
|
||||
return fake ? false : controllerHasAggro;
|
||||
} finally {
|
||||
monsterLock.unlock();
|
||||
}
|
||||
public MapleCharacter getController() {
|
||||
return controller.get();
|
||||
}
|
||||
|
||||
public void setControllerHasAggro(boolean controllerHasAggro) {
|
||||
monsterLock.lock();
|
||||
try {
|
||||
if (fake) {
|
||||
return;
|
||||
private void setController(MapleCharacter controller) {
|
||||
this.controller = new WeakReference<>(controller);
|
||||
}
|
||||
this.controllerHasAggro = controllerHasAggro;
|
||||
} finally {
|
||||
monsterLock.unlock();
|
||||
|
||||
public boolean isControllerHasAggro() {
|
||||
return fake ? false : controllerHasAggro;
|
||||
}
|
||||
|
||||
private void setControllerHasAggro(boolean controllerHasAggro) {
|
||||
if (!fake) this.controllerHasAggro = controllerHasAggro;
|
||||
}
|
||||
|
||||
public boolean isControllerKnowsAboutAggro() {
|
||||
monsterLock.lock();
|
||||
try {
|
||||
return fake ? false : controllerKnowsAboutAggro;
|
||||
} finally {
|
||||
monsterLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void setControllerKnowsAboutAggro(boolean controllerKnowsAboutAggro) {
|
||||
monsterLock.lock();
|
||||
try {
|
||||
if (fake) {
|
||||
return;
|
||||
}
|
||||
this.controllerKnowsAboutAggro = controllerKnowsAboutAggro;
|
||||
} finally {
|
||||
monsterLock.unlock();
|
||||
}
|
||||
private void setControllerKnowsAboutAggro(boolean controllerKnowsAboutAggro) {
|
||||
if (!fake) this.controllerKnowsAboutAggro = controllerKnowsAboutAggro;
|
||||
}
|
||||
|
||||
public byte[] makeBossHPBarPacket() {
|
||||
@@ -956,15 +881,29 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
}
|
||||
}
|
||||
|
||||
private MapleCharacter getActiveController() {
|
||||
MapleCharacter chr = getController();
|
||||
|
||||
if (chr != null && chr.isLoggedinWorld() && chr.getMap() == this.getMap()) {
|
||||
return chr;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastMonsterStatusMessage(byte[] packet) {
|
||||
map.broadcastMessage(packet, getPosition());
|
||||
|
||||
MapleCharacter chrController = getActiveController();
|
||||
if (chrController != null && !chrController.isMapObjectVisible(MapleMonster.this)) {
|
||||
chrController.announce(packet);
|
||||
}
|
||||
}
|
||||
|
||||
private int broadcastStatusEffect(final MonsterStatusEffect status) {
|
||||
int animationTime = status.getSkill().getAnimationTime();
|
||||
byte[] packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), status, null);
|
||||
map.broadcastMessage(packet, getPosition());
|
||||
|
||||
MapleCharacter chrController = getController();
|
||||
if (chrController != null && !chrController.isMapObjectVisible(this)) {
|
||||
chrController.getClient().announce(packet);
|
||||
}
|
||||
broadcastMonsterStatusMessage(packet);
|
||||
|
||||
return animationTime;
|
||||
}
|
||||
@@ -1041,12 +980,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
public void run() {
|
||||
if (isAlive()) {
|
||||
byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), status.getStati());
|
||||
map.broadcastMessage(packet, getPosition());
|
||||
|
||||
MapleCharacter controller = getController();
|
||||
if (controller != null && !controller.isMapObjectVisible(MapleMonster.this)) {
|
||||
controller.getClient().announce(packet);
|
||||
}
|
||||
broadcastMonsterStatusMessage(packet);
|
||||
}
|
||||
|
||||
statiLock.lock();
|
||||
@@ -1148,12 +1082,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
public void run() {
|
||||
if (isAlive()) {
|
||||
byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), stats);
|
||||
map.broadcastMessage(packet, getPosition());
|
||||
|
||||
MapleCharacter controller = getController();
|
||||
if (controller != null && !controller.isMapObjectVisible(MapleMonster.this)) {
|
||||
controller.getClient().announce(packet);
|
||||
}
|
||||
broadcastMonsterStatusMessage(packet);
|
||||
|
||||
statiLock.lock();
|
||||
try {
|
||||
@@ -1168,7 +1097,7 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
};
|
||||
final MonsterStatusEffect effect = new MonsterStatusEffect(stats, null, skill, true);
|
||||
byte[] packet = MaplePacketCreator.applyMonsterStatus(getObjectId(), effect, reflection);
|
||||
map.broadcastMessage(packet, getPosition());
|
||||
broadcastMonsterStatusMessage(packet);
|
||||
|
||||
statiLock.lock();
|
||||
try {
|
||||
@@ -1180,11 +1109,6 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
statiLock.unlock();
|
||||
}
|
||||
|
||||
MapleCharacter controller = getController();
|
||||
if (controller != null && !controller.isMapObjectVisible(this)) {
|
||||
controller.getClient().announce(packet);
|
||||
}
|
||||
|
||||
map.getChannelServer().registerMobStatus(map.getId(), effect, cancelTask, duration);
|
||||
}
|
||||
|
||||
@@ -1193,30 +1117,28 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
}
|
||||
|
||||
public void resetMobPosition(Point newPoint) {
|
||||
removeController();
|
||||
aggroRemoveController();
|
||||
|
||||
setPosition(newPoint);
|
||||
map.broadcastMessage(MaplePacketCreator.moveMonster(this.getObjectId(), false, -1, 0, 0, 0, this.getPosition(), this.getIdleMovement()));
|
||||
map.moveMonster(this, this.getPosition());
|
||||
map.updateMonsterController(this);
|
||||
|
||||
aggroUpdateController();
|
||||
}
|
||||
|
||||
private void debuffMobStat(MonsterStatus stat) {
|
||||
MonsterStatusEffect oldEffect;
|
||||
statiLock.lock();
|
||||
try {
|
||||
if (isBuffed(stat)) {
|
||||
final MonsterStatusEffect oldEffect = stati.get(stat);
|
||||
byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), oldEffect.getStati());
|
||||
map.broadcastMessage(packet, getPosition());
|
||||
|
||||
MapleCharacter chrController = getController();
|
||||
if (chrController != null && !chrController.isMapObjectVisible(MapleMonster.this)) {
|
||||
chrController.getClient().announce(packet);
|
||||
}
|
||||
stati.remove(stat);
|
||||
}
|
||||
oldEffect = stati.remove(stat);
|
||||
} finally {
|
||||
statiLock.unlock();
|
||||
}
|
||||
|
||||
if (oldEffect != null) {
|
||||
byte[] packet = MaplePacketCreator.cancelMonsterStatus(getObjectId(), oldEffect.getStati());
|
||||
broadcastMonsterStatusMessage(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public void debuffMob(int skillid) {
|
||||
@@ -1281,6 +1203,10 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
return map;
|
||||
}
|
||||
|
||||
public MapleMonsterAggroCoordinator getMapAggroCoordinator() {
|
||||
return map.getAggroCoordinator();
|
||||
}
|
||||
|
||||
public List<Pair<Integer, Integer>> getSkills() {
|
||||
return stats.getSkills();
|
||||
}
|
||||
@@ -1679,6 +1605,206 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
changeLevelByDifficulty(difficulty, pqMob);
|
||||
}
|
||||
|
||||
private MapleCharacter getNextControllerCandidate() {
|
||||
int mincontrolled = Integer.MAX_VALUE;
|
||||
MapleCharacter newController = null;
|
||||
|
||||
int mincontrolleddead = Integer.MAX_VALUE;
|
||||
MapleCharacter newControllerDead = null;
|
||||
|
||||
for (MapleCharacter chr : getMap().getAllPlayers()) {
|
||||
if (!chr.isHidden()) {
|
||||
int ctrlMonsSize = chr.getNumControlledMonsters();
|
||||
|
||||
if (chr.isAlive()) {
|
||||
if (ctrlMonsSize < mincontrolled) {
|
||||
mincontrolled = ctrlMonsSize;
|
||||
newController = chr;
|
||||
}
|
||||
} else {
|
||||
if (ctrlMonsSize < mincontrolleddead) {
|
||||
mincontrolleddead = ctrlMonsSize;
|
||||
newControllerDead = chr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newController != null ? newController : newControllerDead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes controllability status from the current controller of this mob.
|
||||
*
|
||||
*/
|
||||
private Pair<MapleCharacter, Boolean> aggroRemoveController() {
|
||||
MapleCharacter chrController;
|
||||
boolean hadAggro;
|
||||
|
||||
aggroUpdateLock.lock();
|
||||
try {
|
||||
chrController = getActiveController();
|
||||
hadAggro = isControllerHasAggro();
|
||||
|
||||
this.setController(null);
|
||||
this.setControllerHasAggro(false);
|
||||
this.setControllerKnowsAboutAggro(false);
|
||||
} finally {
|
||||
aggroUpdateLock.unlock();
|
||||
}
|
||||
|
||||
if (chrController != null) { // this can/should only happen when a hidden gm attacks the monster
|
||||
chrController.announce(MaplePacketCreator.stopControllingMonster(this.getObjectId()));
|
||||
chrController.stopControllingMonster(this);
|
||||
}
|
||||
|
||||
return new Pair<>(chrController, hadAggro);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass over the mob controllability and updates aggro status on the new
|
||||
* player controller.
|
||||
*
|
||||
*/
|
||||
public void aggroSwitchController(MapleCharacter newController, boolean immediateAggro) {
|
||||
if (aggroUpdateLock.tryLock()) {
|
||||
try {
|
||||
MapleCharacter prevController = getController();
|
||||
if (prevController == newController) {
|
||||
return;
|
||||
}
|
||||
|
||||
aggroRemoveController();
|
||||
if (!(newController != null && newController.isLoggedinWorld() && newController.getMap() == this.getMap())) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setController(newController);
|
||||
this.setControllerHasAggro(immediateAggro);
|
||||
this.setControllerKnowsAboutAggro(false);
|
||||
} finally {
|
||||
aggroUpdateLock.unlock();
|
||||
}
|
||||
|
||||
newController.announce(MaplePacketCreator.controlMonster(this, false, immediateAggro));
|
||||
newController.controlMonster(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automagically finds a new controller for the given monster from the chars
|
||||
* on the map it is from...
|
||||
*
|
||||
*/
|
||||
public void aggroUpdateController() {
|
||||
MapleCharacter chrController = this.getActiveController();
|
||||
if (chrController != null && chrController.isAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MapleCharacter newController = getNextControllerCandidate();
|
||||
if (newController == null) { // was a new controller found? (if not no one is on the map)
|
||||
return;
|
||||
}
|
||||
|
||||
this.aggroSwitchController(newController, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures controllability removal of the current player controller, and
|
||||
* fetches for any player on the map to start controlling in place.
|
||||
*
|
||||
*/
|
||||
public void aggroRedirectController() {
|
||||
this.aggroRemoveController(); // don't care if new controller not found, at least remove current controller
|
||||
this.aggroUpdateController();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current aggro status on the specified player, or null if the
|
||||
* specified player is currently not this mob's controller.
|
||||
*
|
||||
*/
|
||||
public Boolean aggroMoveLifeUpdate(MapleCharacter player) {
|
||||
MapleCharacter chrController = getController();
|
||||
if (chrController != null && player.getId() == chrController.getId()) {
|
||||
boolean aggro = this.isControllerHasAggro();
|
||||
if (aggro) {
|
||||
this.setControllerKnowsAboutAggro(true);
|
||||
}
|
||||
|
||||
return aggro;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes auto aggro for the player passed as parameter, does nothing if
|
||||
* there is already an active controller for this mob.
|
||||
*
|
||||
*/
|
||||
public void aggroAutoAggroUpdate(MapleCharacter player) {
|
||||
MapleCharacter chrController = this.getActiveController();
|
||||
|
||||
if (chrController == null) {
|
||||
this.aggroSwitchController(player, true);
|
||||
} else if (chrController.getId() == player.getId()) {
|
||||
this.setControllerHasAggro(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applied damage input for this mob, enough damage taken implies
|
||||
* an aggro target update for the attacker shortly.
|
||||
*
|
||||
*/
|
||||
public void aggroMonsterDamage(MapleCharacter attacker, int damage) {
|
||||
MapleMonsterAggroCoordinator mmac = this.getMapAggroCoordinator();
|
||||
mmac.addAggroDamage(this, attacker.getId(), damage);
|
||||
|
||||
MapleCharacter chrController = this.getController(); // aggro based on DPS rather than first-come-first-served, now live after suggestions thanks to MedicOP, Thora, Vcoc
|
||||
if (chrController != attacker) {
|
||||
if (this.getMapAggroCoordinator().isLeadingCharacterAggro(this, attacker)) {
|
||||
this.aggroSwitchController(attacker, true);
|
||||
}
|
||||
|
||||
/*
|
||||
For some reason, some mobs loses aggro on controllers if other players also attacks them.
|
||||
Maybe it was intended by Nexon to interchange controllers at every attack...
|
||||
|
||||
else if (chrController != null) {
|
||||
chrController.announce(MaplePacketCreator.stopControllingMonster(this.getObjectId()));
|
||||
chrController.announce(MaplePacketCreator.controlMonster(this, false, true));
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
this.setControllerHasAggro(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all applied damage input for this mob, doesn't refresh target aggro.
|
||||
*
|
||||
*/
|
||||
public void aggroClearDamages() {
|
||||
this.getMapAggroCoordinator().removeAggroEntries(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears this mob aggro on the current controller.
|
||||
*
|
||||
*/
|
||||
public void aggroResetAggro() {
|
||||
aggroUpdateLock.lock();
|
||||
try {
|
||||
this.setControllerHasAggro(false);
|
||||
this.setControllerKnowsAboutAggro(false);
|
||||
} finally {
|
||||
aggroUpdateLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public final void disposeLocks() {
|
||||
LockCollector.getInstance().registerDisposeAction(new Runnable() {
|
||||
@Override
|
||||
|
||||
@@ -54,6 +54,7 @@ public class MapleMonsterInformationProvider {
|
||||
|
||||
private final Map<Integer, List<MonsterDropEntry>> drops = new HashMap<>();
|
||||
private final List<MonsterGlobalDropEntry> globaldrops = new ArrayList<>();
|
||||
private final Map<Integer, List<MonsterGlobalDropEntry>> continentdrops = new HashMap<>();
|
||||
|
||||
private final Map<Integer, List<Integer>> dropsChancePool = new HashMap<>(); // thanks to ronan
|
||||
private final Set<Integer> hasNoMultiEquipDrops = new HashSet<>();
|
||||
@@ -71,8 +72,23 @@ public class MapleMonsterInformationProvider {
|
||||
retrieveGlobal();
|
||||
}
|
||||
|
||||
public final List<MonsterGlobalDropEntry> getGlobalDrop() {
|
||||
return globaldrops;
|
||||
public final List<MonsterGlobalDropEntry> getRelevantGlobalDrops(int mapid) {
|
||||
int continentid = mapid / 100000000;
|
||||
|
||||
List<MonsterGlobalDropEntry> contiItems = continentdrops.get(continentid);
|
||||
if (contiItems == null) { // continent separated global drops found thanks to marcuswoon
|
||||
contiItems = new ArrayList<>();
|
||||
|
||||
for (MonsterGlobalDropEntry e : globaldrops) {
|
||||
if (e.continentid < 0 || e.continentid == continentid) {
|
||||
contiItems.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
continentdrops.put(continentid, contiItems);
|
||||
}
|
||||
|
||||
return contiItems;
|
||||
}
|
||||
|
||||
private void retrieveGlobal() {
|
||||
@@ -90,8 +106,7 @@ public class MapleMonsterInformationProvider {
|
||||
new MonsterGlobalDropEntry(
|
||||
rs.getInt("itemid"),
|
||||
rs.getInt("chance"),
|
||||
rs.getInt("continent"),
|
||||
rs.getByte("dropType"),
|
||||
rs.getByte("continent"),
|
||||
rs.getInt("minimum_quantity"),
|
||||
rs.getInt("maximum_quantity"),
|
||||
rs.getShort("questid")));
|
||||
|
||||
@@ -25,15 +25,14 @@ package server.life;
|
||||
* @author LightPepsi
|
||||
*/
|
||||
public class MonsterGlobalDropEntry {
|
||||
public MonsterGlobalDropEntry(int itemId, int chance, int continent, byte dropType, int Minimum, int Maximum, short questid) {
|
||||
public MonsterGlobalDropEntry(int itemId, int chance, int continent, int Minimum, int Maximum, short questid) {
|
||||
this.itemId = itemId;
|
||||
this.chance = chance;
|
||||
this.dropType = dropType;
|
||||
this.questid = questid;
|
||||
this.continentid = continent;
|
||||
this.Minimum = Minimum;
|
||||
this.Maximum = Maximum;
|
||||
}
|
||||
public byte dropType;
|
||||
public int itemId, chance, Minimum, Maximum;
|
||||
public int itemId, chance, Minimum, Maximum, continentid;
|
||||
public short questid;
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
|
||||
private List<Pair<String, Byte>> messages = new LinkedList<>();
|
||||
private List<SoldItem> sold = new LinkedList<>();
|
||||
private AtomicBoolean open = new AtomicBoolean();
|
||||
private boolean published = false;
|
||||
private MapleMap map;
|
||||
private Lock visitorLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.VISITOR_MERCH, true);
|
||||
|
||||
@@ -100,11 +101,15 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
|
||||
visitorLock.lock();
|
||||
try {
|
||||
byte count = 0;
|
||||
if (this.isOpen()) {
|
||||
for (MapleCharacter visitor : visitors) {
|
||||
if (visitor != null) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
count = (byte) (visitors.length + 1);
|
||||
}
|
||||
|
||||
return new byte[]{count, (byte) (visitors.length + 1)};
|
||||
} finally {
|
||||
@@ -471,15 +476,12 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void addItem(MaplePlayerShopItem item) {
|
||||
public boolean addItem(MaplePlayerShopItem item) {
|
||||
synchronized (items) {
|
||||
items.add(item);
|
||||
}
|
||||
if (items.size() >= 16) return false;
|
||||
|
||||
try {
|
||||
this.saveItems(false);
|
||||
} catch (SQLException ex) {
|
||||
ex.printStackTrace();
|
||||
items.add(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,12 +524,17 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public boolean isPublished() {
|
||||
return published;
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return open.get();
|
||||
}
|
||||
|
||||
public void setOpen(boolean set) {
|
||||
open.getAndSet(set);
|
||||
published = true;
|
||||
}
|
||||
|
||||
public int getItemId() {
|
||||
|
||||
+28
-106
@@ -62,6 +62,7 @@ import net.server.audit.locks.MonitoredReentrantReadWriteLock;
|
||||
import net.server.audit.locks.factory.MonitoredReentrantLockFactory;
|
||||
import java.lang.ref.WeakReference;
|
||||
import net.server.Server;
|
||||
import net.server.coordinator.MapleMonsterAggroCoordinator;
|
||||
import net.server.channel.Channel;
|
||||
import net.server.world.World;
|
||||
import scripting.map.MapScriptManager;
|
||||
@@ -140,6 +141,7 @@ public class MapleMap {
|
||||
private int fieldType;
|
||||
private int fieldLimit = 0;
|
||||
private int mobCapacity = -1;
|
||||
private MapleMonsterAggroCoordinator aggroMonitor = null; // aggroMonitor activity in sync with itemMonitor
|
||||
private ScheduledFuture<?> mapMonitor = null;
|
||||
private ScheduledFuture<?> itemMonitor = null;
|
||||
private ScheduledFuture<?> expireItemsTask = null;
|
||||
@@ -185,6 +187,8 @@ public class MapleMap {
|
||||
final ReentrantReadWriteLock objectLock = new MonitoredReentrantReadWriteLock(MonitoredLockType.MAP_OBJS, true);
|
||||
objectRLock = objectLock.readLock();
|
||||
objectWLock = objectLock.writeLock();
|
||||
|
||||
aggroMonitor = new MapleMonsterAggroCoordinator();
|
||||
}
|
||||
|
||||
public void setEventInstance(EventInstanceManager eim) {
|
||||
@@ -709,7 +713,7 @@ public class MapleMap {
|
||||
if(useBaseRate) chRate = 1;
|
||||
|
||||
final MapleMonsterInformationProvider mi = MapleMonsterInformationProvider.getInstance();
|
||||
final List<MonsterGlobalDropEntry> globalEntry = mi.getGlobalDrop();
|
||||
final List<MonsterGlobalDropEntry> globalEntry = mi.getRelevantGlobalDrops(this.getId());
|
||||
|
||||
final List<MonsterDropEntry> dropEntry = new ArrayList<>();
|
||||
final List<MonsterDropEntry> visibleQuestEntry = new ArrayList<>();
|
||||
@@ -777,6 +781,7 @@ public class MapleMap {
|
||||
if(itemMonitorTimeout == 0) {
|
||||
if(itemMonitor != null) {
|
||||
stopItemMonitor();
|
||||
aggroMonitor.stopAggroCoordinator();
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -1144,12 +1149,10 @@ public class MapleMap {
|
||||
}
|
||||
|
||||
public List<MapleCharacter> getAllPlayers() {
|
||||
List<MapleCharacter> character = new LinkedList<>();
|
||||
List<MapleCharacter> character;
|
||||
chrRLock.lock();
|
||||
try {
|
||||
for (MapleCharacter a : characters) {
|
||||
character.add(a);
|
||||
}
|
||||
character = new ArrayList<>(characters);
|
||||
} finally {
|
||||
chrRLock.unlock();
|
||||
}
|
||||
@@ -1316,7 +1319,7 @@ public class MapleMap {
|
||||
if (mons != null) {
|
||||
if (mons.getId() == 8800000) {
|
||||
makeMonsterReal(mons);
|
||||
updateMonsterController(mons);
|
||||
mons.aggroUpdateController();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1552,88 +1555,6 @@ public class MapleMap {
|
||||
}
|
||||
}
|
||||
|
||||
private MapleCharacter getNextControllerCandidate() {
|
||||
int mincontrolled = Integer.MAX_VALUE;
|
||||
MapleCharacter newController = null;
|
||||
|
||||
int mincontrolleddead = Integer.MAX_VALUE;
|
||||
MapleCharacter newControllerDead = null;
|
||||
|
||||
chrRLock.lock();
|
||||
try {
|
||||
for (MapleCharacter chr : characters) {
|
||||
if (!chr.isHidden()) {
|
||||
int ctrlMonsSize = chr.getControlledMonsters().size();
|
||||
|
||||
if (chr.isAlive()) {
|
||||
if (ctrlMonsSize < mincontrolled) {
|
||||
mincontrolled = ctrlMonsSize;
|
||||
newController = chr;
|
||||
}
|
||||
} else {
|
||||
if (ctrlMonsSize < mincontrolleddead) {
|
||||
mincontrolleddead = ctrlMonsSize;
|
||||
newControllerDead = chr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
chrRLock.unlock();
|
||||
}
|
||||
|
||||
return newController != null ? newController : newControllerDead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automagically finds a new controller for the given monster from the chars
|
||||
* on the map...
|
||||
*
|
||||
* @param monster
|
||||
*/
|
||||
public void updateMonsterController(MapleMonster monster) {
|
||||
monster.lockMonster();
|
||||
try {
|
||||
if (!monster.isAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MapleCharacter newController;
|
||||
MapleCharacter chrController = monster.getController();
|
||||
if (chrController != null) {
|
||||
if (chrController.getMap() != this) {
|
||||
chrController.stopControllingMonster(monster);
|
||||
newController = getNextControllerCandidate();
|
||||
} else {
|
||||
if (chrController.isAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
newController = getNextControllerCandidate();
|
||||
if (newController == null || !newController.isAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
chrController.stopControllingMonster(monster);
|
||||
}
|
||||
} else {
|
||||
newController = getNextControllerCandidate();
|
||||
}
|
||||
|
||||
if (newController != null) { // was a new controller found? (if not no one is on the map)
|
||||
if (monster.isFirstAttack()) {
|
||||
newController.controlMonster(monster, true);
|
||||
monster.setControllerHasAggro(true);
|
||||
monster.setControllerKnowsAboutAggro(true);
|
||||
} else {
|
||||
newController.controlMonster(monster, false);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
monster.unlockMonster();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Integer, MapleMapObject> getCopyMapObjects() {
|
||||
objectRLock.lock();
|
||||
try {
|
||||
@@ -1862,7 +1783,7 @@ public class MapleMap {
|
||||
}
|
||||
});
|
||||
|
||||
updateMonsterController(monster);
|
||||
monster.aggroUpdateController();
|
||||
|
||||
if (monster.hasBossHPBar()) {
|
||||
broadcastBossHpMessage(monster, monster.hashCode(), monster.makeBossHPBarPacket(), monster.getPosition());
|
||||
@@ -1948,7 +1869,7 @@ public class MapleMap {
|
||||
}
|
||||
}, null);
|
||||
|
||||
updateMonsterController(monster);
|
||||
monster.aggroUpdateController();
|
||||
|
||||
if (monster.hasBossHPBar()) {
|
||||
broadcastBossHpMessage(monster, monster.hashCode(), monster.makeBossHPBarPacket(), monster.getPosition());
|
||||
@@ -1993,7 +1914,8 @@ public class MapleMap {
|
||||
c.announce(MaplePacketCreator.spawnMonster(monster, true, effect));
|
||||
}
|
||||
});
|
||||
updateMonsterController(monster);
|
||||
|
||||
monster.aggroUpdateController();
|
||||
|
||||
if (monster.hasBossHPBar()) {
|
||||
broadcastBossHpMessage(monster, monster.hashCode(), monster.makeBossHPBarPacket(), monster.getPosition());
|
||||
@@ -2019,7 +1941,8 @@ public class MapleMap {
|
||||
public void makeMonsterReal(final MapleMonster monster) {
|
||||
monster.setFake(false);
|
||||
broadcastMessage(MaplePacketCreator.makeMonsterReal(monster));
|
||||
updateMonsterController(monster);
|
||||
|
||||
monster.aggroUpdateController();
|
||||
}
|
||||
|
||||
public void spawnReactor(final MapleReactor reactor) {
|
||||
@@ -2403,7 +2326,10 @@ public class MapleMap {
|
||||
chr.setMapId(mapid);
|
||||
|
||||
if (chrSize == 1) {
|
||||
if(!hasItemMonitor()) startItemMonitor();
|
||||
if(!hasItemMonitor()) {
|
||||
startItemMonitor();
|
||||
aggroMonitor.startAggroCoordinator();
|
||||
}
|
||||
|
||||
if (onFirstUserEnter.length() != 0 && !chr.hasEntered(onFirstUserEnter, mapid) && MapScriptManager.getInstance().scriptExists(onFirstUserEnter, true)) {
|
||||
chr.enteredScript(onFirstUserEnter, mapid);
|
||||
@@ -2688,19 +2614,8 @@ public class MapleMap {
|
||||
broadcastGMMessage(MaplePacketCreator.removePlayerFromMap(chr.getId()));
|
||||
}
|
||||
|
||||
for (MapleMonster monster : chr.getControlledMonsters()) {
|
||||
monster.lockMonster();
|
||||
try {
|
||||
monster.setController(null);
|
||||
monster.setControllerHasAggro(false);
|
||||
monster.setControllerKnowsAboutAggro(false);
|
||||
updateMonsterController(monster);
|
||||
} finally {
|
||||
monster.unlockMonster();
|
||||
}
|
||||
}
|
||||
|
||||
chr.leaveMap();
|
||||
|
||||
for (MapleSummon summon : new ArrayList<>(chr.getSummonsValues())) {
|
||||
if (summon.isStationary()) {
|
||||
chr.cancelEffectFromBuffStat(MapleBuffStat.PUPPET);
|
||||
@@ -2926,7 +2841,7 @@ public class MapleMap {
|
||||
if (isNonRangedType(o.getType())) {
|
||||
o.sendSpawnData(mapleClient);
|
||||
} else if (o.getType() == MapleMapObjectType.MONSTER) {
|
||||
updateMonsterController((MapleMonster) o);
|
||||
((MapleMonster) o).aggroUpdateController();
|
||||
} else if (o.getType() == MapleMapObjectType.SUMMON) {
|
||||
MapleSummon summon = (MapleSummon) o;
|
||||
if (summon.getOwner() == chr) {
|
||||
@@ -3038,6 +2953,10 @@ public class MapleMap {
|
||||
mapArea.setBounds(vrLeft, vrTop, vrRight - vrLeft, vrBottom - vrTop);
|
||||
}
|
||||
|
||||
public MapleMonsterAggroCoordinator getAggroCoordinator() {
|
||||
return aggroMonitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* it's threadsafe, gtfo :D
|
||||
*
|
||||
@@ -4091,6 +4010,9 @@ public class MapleMap {
|
||||
|
||||
chrWLock.lock();
|
||||
try {
|
||||
aggroMonitor.dispose();
|
||||
aggroMonitor = null;
|
||||
|
||||
if(itemMonitor != null) {
|
||||
itemMonitor.cancel(false);
|
||||
itemMonitor = null;
|
||||
|
||||
@@ -103,11 +103,15 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
|
||||
visitorLock.lock();
|
||||
try {
|
||||
byte count = 0;
|
||||
//if (this.isOpen()) {
|
||||
for (MapleCharacter visitor : visitors) {
|
||||
if (visitor != null) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
//} else { shouldn't happen since there isn't a "closed" state for player shops.
|
||||
// count = (byte) (visitors.length + 1);
|
||||
//}
|
||||
|
||||
return new byte[]{count, (byte) visitors.length};
|
||||
} finally {
|
||||
@@ -198,9 +202,12 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
|
||||
}
|
||||
}
|
||||
|
||||
public void addItem(MaplePlayerShopItem item) {
|
||||
public boolean addItem(MaplePlayerShopItem item) {
|
||||
synchronized (items) {
|
||||
if (items.size() >= 16) return false;
|
||||
|
||||
items.add(item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +248,7 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
|
||||
* @param item
|
||||
* @param quantity
|
||||
*/
|
||||
public void buy(MapleClient c, int item, short quantity) {
|
||||
public boolean buy(MapleClient c, int item, short quantity) {
|
||||
synchronized (items) {
|
||||
if (isVisitor(c.getPlayer())) {
|
||||
MaplePlayerShopItem pItem = items.get(item);
|
||||
@@ -250,10 +257,10 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
|
||||
newItem.setQuantity((short) ((pItem.getItem().getQuantity() * quantity)));
|
||||
if (quantity < 1 || !pItem.isExist() || pItem.getBundles() < quantity) {
|
||||
c.announce(MaplePacketCreator.enableActions());
|
||||
return;
|
||||
return false;
|
||||
} else if (newItem.getInventoryType().equals(MapleInventoryType.EQUIP) && newItem.getQuantity() > 1) {
|
||||
c.announce(MaplePacketCreator.enableActions());
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
MapleKarmaManipulator.toggleKarmaFlagToUntradeable(newItem);
|
||||
@@ -264,6 +271,12 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
|
||||
|
||||
if (c.getPlayer().getMeso() >= price) {
|
||||
if (canBuy(c, newItem)) {
|
||||
if (!owner.canHoldMeso(price)) {
|
||||
owner.dropMessage(1, "Transaction failed since the shop owner can't hold any more mesos.");
|
||||
c.announce(MaplePacketCreator.enableActions());
|
||||
return false;
|
||||
}
|
||||
|
||||
c.getPlayer().gainMeso(-price, false);
|
||||
owner.gainMeso(price, true);
|
||||
|
||||
@@ -286,13 +299,21 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
|
||||
}
|
||||
} else {
|
||||
c.getPlayer().dropMessage(1, "Your inventory is full. Please clean a slot before buying this item.");
|
||||
c.announce(MaplePacketCreator.enableActions());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
c.getPlayer().dropMessage(1, "You don't have enough mesos to purchase this item.");
|
||||
c.announce(MaplePacketCreator.enableActions());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} finally {
|
||||
visitorLock.unlock();
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -418,9 +439,9 @@ public class MaplePlayerShop extends AbstractMapleMapObject {
|
||||
}
|
||||
|
||||
public void closeShop() {
|
||||
owner.getMap().broadcastMessage(MaplePacketCreator.removePlayerShopBox(this));
|
||||
clearChatLog();
|
||||
removeVisitors();
|
||||
owner.getMap().broadcastMessage(MaplePacketCreator.removePlayerShopBox(this));
|
||||
}
|
||||
|
||||
public void sendShop(MapleClient c) {
|
||||
|
||||
@@ -26,7 +26,7 @@ import tools.data.output.LittleEndianWriter;
|
||||
|
||||
public class AbsoluteLifeMovement extends AbstractLifeMovement {
|
||||
private Point pixelsPerSecond;
|
||||
private int unk;
|
||||
private int fh;
|
||||
|
||||
public AbsoluteLifeMovement(int type, Point position, int duration, int newstate) {
|
||||
super(type, position, duration, newstate);
|
||||
@@ -40,12 +40,12 @@ public class AbsoluteLifeMovement extends AbstractLifeMovement {
|
||||
this.pixelsPerSecond = wobble;
|
||||
}
|
||||
|
||||
public int getUnk() {
|
||||
return unk;
|
||||
public int getFh() { // unk -> fh, thanks Spoon for pointing this out
|
||||
return fh;
|
||||
}
|
||||
|
||||
public void setUnk(int unk) {
|
||||
this.unk = unk;
|
||||
public void setFh(int fh) {
|
||||
this.fh = fh;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,7 +55,7 @@ public class AbsoluteLifeMovement extends AbstractLifeMovement {
|
||||
lew.writeShort(getPosition().y);
|
||||
lew.writeShort(pixelsPerSecond.x);
|
||||
lew.writeShort(pixelsPerSecond.y);
|
||||
lew.writeShort(unk);
|
||||
lew.writeShort(fh);
|
||||
lew.write(getNewstate());
|
||||
lew.writeShort(getDuration());
|
||||
}
|
||||
|
||||
@@ -25,18 +25,18 @@ import java.awt.Point;
|
||||
import tools.data.output.LittleEndianWriter;
|
||||
|
||||
public class ChairMovement extends AbstractLifeMovement {
|
||||
private int unk;
|
||||
private int fh;
|
||||
|
||||
public ChairMovement(int type, Point position, int duration, int newstate) {
|
||||
super(type, position, duration, newstate);
|
||||
}
|
||||
|
||||
public int getUnk() {
|
||||
return unk;
|
||||
public int getFh() {
|
||||
return fh;
|
||||
}
|
||||
|
||||
public void setUnk(int unk) {
|
||||
this.unk = unk;
|
||||
public void setFh(int fh) {
|
||||
this.fh = fh;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,7 +44,7 @@ public class ChairMovement extends AbstractLifeMovement {
|
||||
lew.write(getType());
|
||||
lew.writeShort(getPosition().x);
|
||||
lew.writeShort(getPosition().y);
|
||||
lew.writeShort(unk);
|
||||
lew.writeShort(fh);
|
||||
lew.write(getNewstate());
|
||||
lew.writeShort(getDuration());
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ import tools.data.output.LittleEndianWriter;
|
||||
|
||||
public class JumpDownMovement extends AbstractLifeMovement {
|
||||
private Point pixelsPerSecond;
|
||||
private int unk;
|
||||
private int fh;
|
||||
private int originFh;
|
||||
|
||||
public JumpDownMovement(int type, Point position, int duration, int newstate) {
|
||||
super(type, position, duration, newstate);
|
||||
@@ -41,22 +41,22 @@ public class JumpDownMovement extends AbstractLifeMovement {
|
||||
this.pixelsPerSecond = wobble;
|
||||
}
|
||||
|
||||
public int getUnk() {
|
||||
return unk;
|
||||
}
|
||||
|
||||
public void setUnk(int unk) {
|
||||
this.unk = unk;
|
||||
}
|
||||
|
||||
public int getFH() {
|
||||
public int getFh() {
|
||||
return fh;
|
||||
}
|
||||
|
||||
public void setFH(int fh) {
|
||||
public void setFh(int fh) {
|
||||
this.fh = fh;
|
||||
}
|
||||
|
||||
public int getOriginFh() {
|
||||
return originFh;
|
||||
}
|
||||
|
||||
public void setOriginFh(int fh) { // fh actually originFh, thanks Spoon for pointing this out
|
||||
this.originFh = fh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(LittleEndianWriter lew) {
|
||||
lew.write(getType());
|
||||
@@ -64,8 +64,8 @@ public class JumpDownMovement extends AbstractLifeMovement {
|
||||
lew.writeShort(getPosition().y);
|
||||
lew.writeShort(pixelsPerSecond.x);
|
||||
lew.writeShort(pixelsPerSecond.y);
|
||||
lew.writeShort(unk);
|
||||
lew.writeShort(fh);
|
||||
lew.writeShort(originFh);
|
||||
lew.write(getNewstate());
|
||||
lew.writeShort(getDuration());
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ package server.quest.actions;
|
||||
|
||||
import client.MapleCharacter;
|
||||
import constants.ServerConstants;
|
||||
import net.server.world.World;
|
||||
import provider.MapleData;
|
||||
import provider.MapleDataTool;
|
||||
import server.quest.MapleQuest;
|
||||
@@ -53,15 +52,10 @@ public class ExpAction extends MapleQuestAction {
|
||||
}
|
||||
|
||||
public static void runAction(MapleCharacter chr, int gain) {
|
||||
if (chr.isBeginnerJob() && chr.getLevel() < 10) {
|
||||
chr.gainExp(gain, true, true);
|
||||
} else {
|
||||
if(!ServerConstants.USE_QUEST_RATE) {
|
||||
if (!ServerConstants.USE_QUEST_RATE) {
|
||||
chr.gainExp(gain * chr.getExpRate(), true, true);
|
||||
} else {
|
||||
World w = chr.getClient().getWorldServer();
|
||||
chr.gainExp(gain * w.getExpRate() * w.getQuestRate(), true, true);
|
||||
}
|
||||
chr.gainExp(gain * chr.getQuestExpRate(), true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ package server.quest.actions;
|
||||
|
||||
import client.MapleCharacter;
|
||||
import constants.ServerConstants;
|
||||
import net.server.world.World;
|
||||
import provider.MapleData;
|
||||
import provider.MapleDataTool;
|
||||
import server.quest.MapleQuest;
|
||||
@@ -54,14 +53,13 @@ public class MesoAction extends MapleQuestAction {
|
||||
}
|
||||
|
||||
public static void runAction(MapleCharacter chr, int gain) {
|
||||
if(gain < 0) {
|
||||
if (gain < 0) {
|
||||
chr.gainMeso(gain, true, false, true);
|
||||
} else {
|
||||
if(!ServerConstants.USE_QUEST_RATE) {
|
||||
if (!ServerConstants.USE_QUEST_RATE) {
|
||||
chr.gainMeso(gain * chr.getMesoRate(), true, false, true);
|
||||
} else {
|
||||
World w = chr.getClient().getWorldServer();
|
||||
chr.gainMeso(gain * w.getMesoRate() * w.getQuestRate(), true, false, true);
|
||||
chr.gainMeso(gain * chr.getQuestMesoRate(), true, false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ public class LogHelper {
|
||||
String log = expedition.getType().toString() + " EXPEDITION\r\n";
|
||||
log += getTimeString(expedition.getStartTime()) + "\r\n";
|
||||
|
||||
for (MapleCharacter member : expedition.getMembers()){
|
||||
log += ">>" + member.getName() + "\r\n";
|
||||
for (String memberName : expedition.getMembers().values()){
|
||||
log += ">>" + memberName + "\r\n";
|
||||
}
|
||||
log += "BOSS KILLS\r\n";
|
||||
for (String message: expedition.getBossLogs()){
|
||||
|
||||
@@ -1841,6 +1841,30 @@ public class MaplePacketCreator {
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Guild Name & Mark update packet, thanks to Arnah (Vertisy)
|
||||
*
|
||||
* @param guildName The Guild name, blank for nothing.
|
||||
*/
|
||||
public static byte[] guildNameChanged(int chrid, String guildName){
|
||||
MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.GUILD_NAME_CHANGED.getValue());
|
||||
mplew.writeInt(chrid);
|
||||
mplew.writeMapleAsciiString(guildName);
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] guildMarkChanged(int chrid, MapleGuild guild){
|
||||
MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.GUILD_MARK_CHANGED.getValue());
|
||||
mplew.writeInt(chrid);
|
||||
mplew.writeShort(guild.getLogoBG());
|
||||
mplew.write(guild.getLogoBGColor());
|
||||
mplew.writeShort(guild.getLogo());
|
||||
mplew.write(guild.getLogoColor());
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a packet spawning a player as a mapobject to other clients.
|
||||
*
|
||||
@@ -3397,6 +3421,19 @@ public class MaplePacketCreator {
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] showBuffeffect(int cid, int skillid, int skilllv, int effectid, byte direction) { // updated packet structure found thanks to Rien dev team
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.SHOW_FOREIGN_EFFECT.getValue());
|
||||
mplew.writeInt(cid);
|
||||
mplew.write(effectid);
|
||||
mplew.writeInt(skillid);
|
||||
mplew.write(0);
|
||||
mplew.write(skilllv);
|
||||
mplew.write(direction);
|
||||
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] showOwnBuffEffect(int skillid, int effectid) {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.SHOW_ITEM_GAIN_INCHAT.getValue());
|
||||
@@ -4598,12 +4635,20 @@ public class MaplePacketCreator {
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] showMagnet(int mobid, byte success) { // Monster Magnet
|
||||
public static byte[] catchMonster(int mobOid, byte success) { // updated packet structure found thanks to Rien dev team
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.SHOW_MAGNET.getValue());
|
||||
mplew.writeInt(mobid);
|
||||
mplew.writeShort(SendOpcode.CATCH_MONSTER.getValue());
|
||||
mplew.writeInt(mobOid);
|
||||
mplew.write(success);
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] catchMonster(int mobOid, int itemid, byte success) {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.CATCH_MONSTER_WITH_ITEM.getValue());
|
||||
mplew.writeInt(mobOid);
|
||||
mplew.writeInt(itemid);
|
||||
mplew.write(success);
|
||||
mplew.skip(10); //Mmmk
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
@@ -4932,15 +4977,6 @@ public class MaplePacketCreator {
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] catchMonster(int monsobid, int itemid, byte success) {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.CATCH_MONSTER.getValue());
|
||||
mplew.writeInt(monsobid);
|
||||
mplew.writeInt(itemid);
|
||||
mplew.write(success);
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] catchMessage(int message) { // not done, I guess
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.BRIDLE_MOB_CATCH_FAIL.getValue());
|
||||
@@ -5608,6 +5644,14 @@ public class MaplePacketCreator {
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] hiredMerchantOwnerMaintenanceLeave() {
|
||||
final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter();
|
||||
mplew.writeShort(SendOpcode.PLAYER_INTERACTION.getValue());
|
||||
mplew.write(PlayerInteractionHandler.Action.REAL_CLOSE_MERCHANT.getCode());
|
||||
mplew.write(5);
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
public static byte[] hiredMerchantMaintenanceMessage() {
|
||||
MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(5);
|
||||
mplew.writeShort(SendOpcode.PLAYER_INTERACTION.getValue());
|
||||
|
||||
@@ -41,7 +41,7 @@ import org.apache.commons.io.FileUtils;
|
||||
and fill empty functions for every function name not yet present in the
|
||||
script.
|
||||
|
||||
Estimated parse time:
|
||||
Estimated parse time: 10 seconds
|
||||
*/
|
||||
public class MapleEventMethodFiller {
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
<imgdir name="life">
|
||||
<imgdir name="15">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="787"/>
|
||||
<int name="y" value="-1483"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -112,7 +112,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="16">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="-866"/>
|
||||
<int name="y" value="-883"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -125,7 +125,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="19">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="325"/>
|
||||
<int name="y" value="-876"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -171,7 +171,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="17">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="-395"/>
|
||||
<int name="y" value="-841"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -184,7 +184,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="18">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="695"/>
|
||||
<int name="y" value="-1033"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -210,7 +210,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="20">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="-644"/>
|
||||
<int name="y" value="-897"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -223,7 +223,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="13">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="-83"/>
|
||||
<int name="y" value="-802"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -236,7 +236,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="12">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="-705"/>
|
||||
<int name="y" value="-1431"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -249,7 +249,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="11">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="-841"/>
|
||||
<int name="y" value="-1429"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -262,7 +262,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="10">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="-525"/>
|
||||
<int name="y" value="-1192"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -275,7 +275,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="9">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="-317"/>
|
||||
<int name="y" value="-1192"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -288,7 +288,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="8">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="803"/>
|
||||
<int name="y" value="-1025"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -301,7 +301,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="7">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="559"/>
|
||||
<int name="y" value="-1030"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -384,7 +384,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="14">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="-430"/>
|
||||
<int name="y" value="-1202"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -397,7 +397,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="24">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="1210102"/>
|
||||
<int name="x" value="75"/>
|
||||
<int name="y" value="-787"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
<imgdir name="life">
|
||||
<imgdir name="0">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="9300274"/>
|
||||
<int name="x" value="-374"/>
|
||||
<int name="y" value="-760"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -116,7 +116,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="1">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="9300274"/>
|
||||
<int name="x" value="-172"/>
|
||||
<int name="y" value="-764"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -129,7 +129,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="2">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="9300274"/>
|
||||
<int name="x" value="25"/>
|
||||
<int name="y" value="-759"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -142,7 +142,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="3">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="9300274"/>
|
||||
<int name="x" value="211"/>
|
||||
<int name="y" value="-768"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -155,7 +155,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="4">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="9300274"/>
|
||||
<int name="x" value="377"/>
|
||||
<int name="y" value="-756"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -168,7 +168,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="5">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="9300274"/>
|
||||
<int name="x" value="322"/>
|
||||
<int name="y" value="-1111"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -181,7 +181,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="6">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="9300274"/>
|
||||
<int name="x" value="458"/>
|
||||
<int name="y" value="-1119"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -194,7 +194,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="7">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="9300274"/>
|
||||
<int name="x" value="98"/>
|
||||
<int name="y" value="-1064"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -207,7 +207,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="8">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="9300274"/>
|
||||
<int name="x" value="-41"/>
|
||||
<int name="y" value="-1062"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -220,7 +220,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="9">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="9300274"/>
|
||||
<int name="x" value="-132"/>
|
||||
<int name="y" value="-1241"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -233,7 +233,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="10">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="9300274"/>
|
||||
<int name="x" value="-280"/>
|
||||
<int name="y" value="-1242"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
@@ -246,7 +246,7 @@
|
||||
</imgdir>
|
||||
<imgdir name="11">
|
||||
<string name="type" value="m"/>
|
||||
<string name="id" value="1210100"/>
|
||||
<string name="id" value="9300274"/>
|
||||
<int name="x" value="-332"/>
|
||||
<int name="y" value="-995"/>
|
||||
<int name="mobTime" value="0"/>
|
||||
|
||||
+15274
-15317
File diff suppressed because it is too large
Load Diff
@@ -8339,7 +8339,7 @@
|
||||
<int name="npc" value="12100"/>
|
||||
<imgdir name="mob">
|
||||
<imgdir name="0">
|
||||
<int name="id" value="1210100"/>
|
||||
<int name="id" value="9300274"/>
|
||||
<int name="count" value="2"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
|
||||
@@ -2541,8 +2541,8 @@
|
||||
<string name="name" value="Mai's Last Training"/>
|
||||
<string name="parent" value="Mai's Training!"/>
|
||||
<int name="order" value="4"/>
|
||||
<string name="1" value="I defeated the Slimes and brought back the Squishy Liquid to Mai. She seemed to be impressed, and instructed me this time to hunt a really powerful monster. She then told me to hunt #r2 Pigs#k.\n\nPig #r#a10441##k"/>
|
||||
<string name="2" value="I was able to defeat 2 Pigs and reported the result to Mai."/>
|
||||
<string name="1" value="I defeated the Slimes and brought back the Squishy Liquid to Mai. She seemed to be impressed, and instructed me this time to hunt a really powerful monster. She then told me to hunt #r2 Cynical Orange Mushrooms#k.\n\nCynical Orange Mushroom #r#a10441##k"/>
|
||||
<string name="2" value="I was able to defeat 2 Cynical Orange Mushrooms and reported the result to Mai."/>
|
||||
<int name="area" value="20"/>
|
||||
</imgdir>
|
||||
<imgdir name="10440">
|
||||
|
||||
@@ -4503,21 +4503,22 @@
|
||||
<imgdir name="0">
|
||||
<string name="0" value="Oh wow! You really brought me Squishy Liquid from the Slime. You're doing much better than I expected. I think... you're now ready to take on a really challenging monster."/>
|
||||
<imgdir name="yes">
|
||||
<string name="0" value="This time, you'll need to take on #r2 Pigs#k. They are very fast and powerful, but thankfully they aren't the Ribbon Pigs you'll find at Victoria Island. They are just regular Pigs, so if you focus on the combat, you should be able to handle those two."/>
|
||||
<string name="0" value="Alright, this time, you'll need to take on #r2 Cynical Orange Mushrooms#k. They are pretty tough and powerful, but thankfully they aren't the Blue Mushrooms you'll find at Victoria Island. They are basically regular Orange Mushrooms, so if you focus on the combat, you should be able to handle those two."/>
|
||||
</imgdir>
|
||||
<imgdir name="no">
|
||||
<string name="0" value="Hmmm... did I really scare you? Don't worry. If you are persistent, you'll be able to handle those two. I won't make you hunt monsters that you can't handle, you know."/>
|
||||
</imgdir>
|
||||
<string name="1" value="So, reports of late shows up a growth on masses of distempered Orange Mushrooms around. Some explorers seems to not get their job done after they started engaging combat. That has been making some Orange Mushrooms rather... uhh, cynical... towards us humans... "/>
|
||||
</imgdir>
|
||||
<imgdir name="1">
|
||||
<string name="0" value="So you did defeat 2 Pigs. That's incredible! Seriously, you're doing much better than I ever thought possible! You may be just a beginner, but I have a feeling that you'll one day become a legendary hero in Maple World."/>
|
||||
<string name="0" value="So you did defeat 2 Cynical Orange Mushrooms. That's incredible! Seriously, you're doing much better than I ever thought possible! You may be just a beginner, but I have a feeling that you'll one day become a legendary hero in Maple World."/>
|
||||
<imgdir name="yes">
|
||||
<string name="0" value="The training is now complete. You may still have some weaknesses here and there, but you'll be able to overcome that in no time. Keep training!"/>
|
||||
<string name="1" value="Oh, and I have one last #btest#k for you. Think of it as a final exam. If you aren't satisfied with your current skill level and want to push further, then meet #bBari#k, who will have a test in store for you."/>
|
||||
</imgdir>
|
||||
<imgdir name="stop">
|
||||
<imgdir name="mob">
|
||||
<string name="0" value="I don't think you've slayed #r2 Pigs#k yet. Please take care of both, then let me know of the results."/>
|
||||
<string name="0" value="I don't think you've slayed #r2 Cynical Orange Mushrooms#k yet. Please take care of both, then let me know of the results."/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user