diff --git a/docs/mychanges_ptbr.txt b/docs/mychanges_ptbr.txt
index 641813bf12..023d4d3822 100644
--- a/docs/mychanges_ptbr.txt
+++ b/docs/mychanges_ptbr.txt
@@ -1265,4 +1265,31 @@ Corrigido setGender não modificando estado do cliente para não-logado ao cance
24 Agosto 2018,
Melhorado mecânica de logout de líder de party, passando a liderança adiante antes de efetivamente deslogar, permitindo assim instâncias a continuarem após a saída do mesmo, dadas circunstâncias favoráveis.
Outra correção no XMLDomMapleData, que ainda continua dando NullPointerExceptions.
-Corrigido stati de mobs não protegido concorrentemente em certos casos.
\ No newline at end of file
+Corrigido stati de mobs não protegido concorrentemente em certos casos.
+
+28 Agosto 2018,
+Implementado sistema de rewarp para instâncias de eventos, usado por jogadores ao se reconectar ao jogo. Ideia de Alisson.
+Adicionado contador de compras de itens do cash shop. Sugestão de mais comprados do cash shop implementado.
+Otimizado função goto, agora não mais gerando mapas de cidade/mapid a todo uso de comando.
+
+29 - 31 Agosto 2018,
+Melhorado função de disconnect do MapleClient, evitando múltiplos envios de dados do jogador à DB.
+Desenhado (todo conteúdo de imagens creditado à Nexon) e implementado novos mapas-mundi referente a regiões de M. Shrine, Showa, CBD e Metropolis/Kampung.
+
+03 Agosto 2018,
+Implementado buffs inexpiráveis.
+Corrigido comando resetStats não levando em conta AP's atuais dos jogadores.
+Corrigido novo sistema gerenciador de loots removendo a possibilidade de conseguir novos itens de quest após coleta do primeiro item.
+
+04 Agosto 2018,
+Corrigido um flicker na animação do efeito da skill Hurricane.
+Ajustado MapleSessionCoordinator, agora verificando HWID's ao invés de contar somente com o remote IP, evitando negação de serviço para usuários de VPNs.
+Corrigido dispel normal incorretamente mostrando efeitos aleatórios a outros jogadores, issue apontado por Thora.
+Corrigido change job não mostrando efeito a outros jogadores.
+Corrigido valores incorretos sendo retirado de jogadores para expansão de guild, issue apontado por Thora.
+
+05 Agosto 2018,
+Adicionado world map em Ellin Forest.
+Protegido concorrentemente sistema de fames.
+Adicionado ganho de quest points para jogadores que participam de PQs. Reformulado sistema de quest points para viabilizar a nova feature.
+Otimizado método de ganho de experiência em equipamentos, agora devidamente cacheado e sem busca em strings no processo.
\ No newline at end of file
diff --git a/nbproject/private/private.properties b/nbproject/private/private.properties
index cb253ffdf6..b7375e1e9d 100644
--- a/nbproject/private/private.properties
+++ b/nbproject/private/private.properties
@@ -7,4 +7,4 @@ file.reference.slf4j-api-1.6.6.jar=C:\\Nexon\\HeavenMS\\cores\\slf4j-api-1.6.6.j
file.reference.slf4j-jdk14-1.7.5.jar=C:\\Nexon\\HeavenMS\\cores\\slf4j-jdk14-1.7.5.jar
javac.debug=true
javadoc.preview=true
-user.properties.file=C:\\Users\\USER\\AppData\\Roaming\\NetBeans\\8.0.2\\build.properties
+user.properties.file=C:\\Users\\RonanLana\\AppData\\Roaming\\NetBeans\\8.0.2\\build.properties
diff --git a/scripts/event/HenesysPQ.js b/scripts/event/HenesysPQ.js
index 45404f767e..74ce27aa94 100644
--- a/scripts/event/HenesysPQ.js
+++ b/scripts/event/HenesysPQ.js
@@ -142,6 +142,11 @@ function scheduledTimeout(eim) {
}
}
+function bunnyDefeated(eim) {
+ eim.dropMessage(5, "Due to your failure to protect the Moon Bunny, you have been transported to the Exile Map.");
+ end(eim);
+}
+
function playerUnregistered(eim, player) {}
function playerExit(eim, player) {
@@ -232,6 +237,12 @@ function clearPQ(eim) {
function monsterKilled(mob, eim) {}
+function friendlyKilled(mob, eim) {
+ if (mob.getId() == 9300061) {
+ eim.schedule("bunnyDefeated", 5 * 1000);
+ }
+}
+
function allMonstersDead(eim) {}
function cancelSchedule() {}
diff --git a/scripts/npc/2020002.js b/scripts/npc/2020002.js
index e083d02e90..37ecd3ece9 100644
--- a/scripts/npc/2020002.js
+++ b/scripts/npc/2020002.js
@@ -185,7 +185,7 @@ function action(mode, type, selection) {
cm.gainItem(mats[i], -matQty [i]);
else
cm.gainItem(mats, -matQty );
- cm.gainMeso(-cost );
+ cm.gainMeso(-cost);
cm.gainItem(item, 1);
cm.sendOk("All done. Stay warm!");
}
diff --git a/scripts/npc/9201084.js b/scripts/npc/9201084.js
new file mode 100644
index 0000000000..0290ddf1b1
--- /dev/null
+++ b/scripts/npc/9201084.js
@@ -0,0 +1,45 @@
+/*
+ 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 .
+*/
+
+var status;
+
+function start() {
+ status = -1;
+ action(1, 0, 0);
+}
+
+function action(mode, type, selection) {
+ if (mode == -1) {
+ cm.dispose();
+ } else {
+ if (mode == 0 && type > 0) {
+ cm.dispose();
+ return;
+ }
+ if (mode == 1)
+ status++;
+ else
+ status--;
+
+ if(status == 0) {
+ cm.dispose();
+ }
+ }
+}
\ No newline at end of file
diff --git a/scripts/npc/9977777.js b/scripts/npc/9977777.js
index 9e47f10de7..9725a8388e 100644
--- a/scripts/npc/9977777.js
+++ b/scripts/npc/9977777.js
@@ -32,7 +32,7 @@ var ambientSong = "Bgm04/Shinin'Harbor";
var feature_tree = [];
var feature_cursor;
-var tabs = ["PQs", "Skills", "Quests", "Player Social Network", "Cash & Items", "Monsters, Maps & Reactors", "PQ potentials", "Player potentials", "Server potentials", "Admin/GM commands", "Custom NPCs", "Localhost edits", "Project"];
+var tabs = ["PQs", "Skills", "Quests", "Player Social Network", "Cash & Items", "Monsters, Maps & Reactors", "PQ potentials", "Player potentials", "Server potentials", "Commands", "Custom NPCs", "Localhost edits", "Project"];
function addFeature(feature) {
feature_cursor.push(feature);
@@ -43,7 +43,7 @@ function writeFeatureTab_PQs() {
addFeature("RnJPQ/HorntailPQ/TreasurePQ/ElnathPQ/HolidayPQ.");
addFeature("CWKPQ as Expedition-based instance.");
addFeature("Scarga/Horntail/Showa/Balrog/Zakum/Pinkbean.");
- addFeature("GuildPQ & queue with multi-lobby systems available.");
+ addFeature("GuildPQ & queue with multi-lobby system available.");
addFeature("Brand-new PQs: BossRushPQ, CafePQ.");
addFeature("Mu Lung Dojo.");
addFeature("Capt. Latanica with party fighting the boss.");
@@ -128,6 +128,7 @@ function writeFeatureTab_MonstersMapsReactors() {
addFeature("Reactors pick items up smartly from the field.");
addFeature("Updated scripted portals now with proper portal SFX.");
addFeature("Reviewed Masteria, W. Tour, N. Desert and Neo City.");
+ addFeature("Added world maps for M. Castle, W. Tour & Ellin areas.");
addFeature("Giant Cake boss drops s. bags and Maple items.");
}
@@ -137,13 +138,14 @@ function writeFeatureTab_PQpotentials() {
addFeature("Exped system: Many parties can join a same instance.");
addFeature("Guild queue: guild registration for the GPQ.");
addFeature("EIM Pool system: optimized instance loadouts.");
+ addFeature("Recall system: players can rejoin PQ after d/c.");
}
function writeFeatureTab_Playerpotentials() {
addFeature("Adventurer Mount quests functional.");
addFeature("All Equipment levels up.");
addFeature("Player level rates.");
- addFeature("Gain fame by quests.");
+ addFeature("Gain fame by quests and event instances.");
addFeature("Pet evolutions functional (not GMS-like).");
addFeature("Reviewed keybinding system.");
addFeature("Character slots per world/server-wide.");
@@ -160,6 +162,7 @@ function writeFeatureTab_Serverpotentials() {
addFeature("Enhanced AP auto-assigner: focus on eqp demands.");
addFeature("Enhanced inventory check: free slots smartly fetched.");
addFeature("Enhanced petloot handler: no brute-force inv. checks.");
+ addFeature("Players-appointed bestsellers for Owl and Cash Shop.");
addFeature("Tweaked pet/mount hunger to a balanced growth rate.");
addFeature("Consistent experience and meso gain system.");
addFeature("NPC crafters won't take items freely anymore.");
@@ -184,7 +187,7 @@ function writeFeatureTab_Serverpotentials() {
addFeature("Automatic account registration - thanks shavit!");
}
-function writeFeatureTab_AdminGMcommands() {
+function writeFeatureTab_Commands() {
addFeature("Spawn Zakum/Horntail/Pinkbean.");
addFeature("Several new commands.");
addFeature("Rank command highlighting users by world or overall.");
diff --git a/scripts/npc/commands.js b/scripts/npc/commands.js
index a9c8257359..b90af5eae3 100644
--- a/scripts/npc/commands.js
+++ b/scripts/npc/commands.js
@@ -47,7 +47,7 @@ function action(mode, type, selection) {
cm.sendSimple(sendStr);
} else if(status == 1) {
- var lvComm, lvDesc, lvHead = (cm.getPlayer().gmLevel() < 2) ? common_heading : staff_heading;
+ var lvComm, lvDesc, lvHead = (selection < 2) ? common_heading : staff_heading;
if(selection > 6) {
selection = 6;
diff --git a/sql/db_database.sql b/sql/db_database.sql
index a1b780a0c8..57a1499d17 100644
--- a/sql/db_database.sql
+++ b/sql/db_database.sql
@@ -12935,6 +12935,14 @@ CREATE TABLE IF NOT EXISTS `htsquads` (
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
+CREATE TABLE IF NOT EXISTS `hwidaccounts` (
+ `accountid` int(11) NOT NULL DEFAULT '0',
+ `hwid` varchar(40) NOT NULL DEFAULT '',
+ `relevance` tinyint(2) NOT NULL DEFAULT '0',
+ `expiresat` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`accountid`,`hwid`)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
+
CREATE TABLE IF NOT EXISTS `hwidbans` (
`hwidbanid` int(10) unsigned NOT NULL AUTO_INCREMENT,
`hwid` varchar(30) NOT NULL,
@@ -13005,14 +13013,6 @@ CREATE TABLE IF NOT EXISTS `ipbans` (
PRIMARY KEY (`ipbanid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
-CREATE TABLE IF NOT EXISTS `ipaccounts` (
- `accountid` int(11) NOT NULL DEFAULT '0',
- `ip` varchar(40) NOT NULL DEFAULT '',
- `relevance` tinyint(2) NOT NULL DEFAULT '0',
- `expiresat` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (`accountid`,`ip`)
-) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
-
CREATE TABLE IF NOT EXISTS `keymap` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`characterid` int(11) NOT NULL DEFAULT '0',
diff --git a/sql/db_drops.sql b/sql/db_drops.sql
index 994d42e23d..f46dbf5aee 100644
--- a/sql/db_drops.sql
+++ b/sql/db_drops.sql
@@ -1871,7 +1871,7 @@ USE `heavenms`;
(2230102, 4000020, 1, 1, 0, 200000),
(2230102, 4000021, 1, 1, 0, 200000),
(2230102, 4003004, 1, 1, 0, 7000),
-(2230102, 4001372, 1, 1, 0, 200000),
+(2230102, 4001372, 1, 1, 28282, 200000),
(2230102, 2000001, 1, 1, 0, 40000),
(2230102, 2000003, 1, 1, 0, 40000),
(2230102, 2002004, 1, 1, 0, 10000),
@@ -2107,7 +2107,7 @@ USE `heavenms`;
(2230112, 1082186, 1, 1, 0, 700),
(2230100, 4000007, 1, 1, 0, 200000),
(2230100, 4030012, 1, 1, 0, 125000),
-(2230100, 4001373, 1, 1, 0, 7000),
+(2230100, 4001373, 1, 1, 28282, 200000),
(2230100, 2000001, 1, 1, 0, 40000),
(2230100, 2000003, 1, 1, 0, 40000),
(2230100, 2002001, 1, 1, 0, 10000),
@@ -20795,8 +20795,6 @@ USE `heavenms`;
UPDATE drop_data SET chance=0 WHERE itemid=2050099;
UPDATE drop_data SET questid=6191 WHERE itemid=4031477;
UPDATE drop_data SET questid=6190 WHERE itemid=4001111;
- UPDATE drop_data SET questid=28344 WHERE itemid=4001372;
- UPDATE drop_data SET questid=28282 WHERE itemid=4001373;
# two items named "Sparta": remove the entries where lv100 Sparta is being dropped by low-level mobs.
UPDATE IGNORE drop_data SET itemid=1402011 WHERE itemid=1302056 AND dropperid < 8000000;
diff --git a/src/client/MapleCharacter.java b/src/client/MapleCharacter.java
index e29c3902ca..b0038282e8 100644
--- a/src/client/MapleCharacter.java
+++ b/src/client/MapleCharacter.java
@@ -998,7 +998,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
public FameStatus canGiveFame(MapleCharacter from) {
- if (gmLevel > 0) {
+ if (this.isGM()) {
return FameStatus.OK;
} else if (lastfametime >= System.currentTimeMillis() - 3600000 * 24) {
return FameStatus.NOT_TODAY;
@@ -1171,9 +1171,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
setMasteries(this.job.getId());
guildUpdate();
- getMap().broadcastMessage(this, MaplePacketCreator.removePlayerFromMap(this.getId()), false);
- getMap().broadcastMessage(this, MaplePacketCreator.spawnPlayerMapObject(this), false);
- getMap().broadcastMessage(this, MaplePacketCreator.showForeignEffect(getId(), 8), false);
+ getMap().broadcastMessage(this, MaplePacketCreator.showForeignEffect(this.getId(), 8), false);
if (GameConstants.hasSPTable(newJob) && newJob.getId() != 2001) {
if (getBuffedValue(MapleBuffStat.MONSTER_RIDING) != null) {
@@ -1182,8 +1180,8 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
createDragon();
}
- if(ServerConstants.USE_ANNOUNCE_CHANGEJOB) {
- if(gmLevel > 1) {
+ if (ServerConstants.USE_ANNOUNCE_CHANGEJOB) {
+ if (!this.isGM()) {
broadcastAcquaintances(6, "[" + GameConstants.ordinal(GameConstants.getJobBranch(newJob)) + " Job] " + name + " has just become a " + newJob.name() + ".");
}
}
@@ -2793,7 +2791,6 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
public enum FameStatus {
-
OK, NOT_TODAY, NOT_THIS_MONTH
}
@@ -2887,9 +2884,39 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
}
+ private synchronized int applyFame(int delta) {
+ int newFame = fame + delta;
+ if (newFame < -30000) {
+ delta = -(30000 + fame);
+ } else if (newFame > 30000) {
+ delta = 30000 - fame;
+ }
+
+ fame += delta;
+ return delta;
+ }
+
public void gainFame(int delta) {
- this.addFame(delta);
- this.updateSingleStat(MapleStat.FAME, this.fame);
+ gainFame(delta, null, 0);
+ }
+
+ public boolean gainFame(int delta, MapleCharacter fromPlayer, int mode) {
+ delta = applyFame(delta);
+ if (delta != 0) {
+ int thisFame = fame;
+ updateSingleStat(MapleStat.FAME, thisFame);
+
+ if (fromPlayer != null) {
+ fromPlayer.announce(MaplePacketCreator.giveFameResponse(mode, getName(), thisFame));
+ announce(MaplePacketCreator.receiveFame(mode, fromPlayer.getName()));
+ } else {
+ announce(MaplePacketCreator.getShowFameGain(delta));
+ }
+
+ return true;
+ } else {
+ return false;
+ }
}
public void gainMeso(int gain) {
@@ -5417,16 +5444,18 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
return getInventory(MapleInventoryType.getByType(invType)).getNextFreeSlot() > -1;
}
- public void increaseGuildCapacity() { //hopefully nothing is null
- if (getMeso() < MapleGuild.getIncreaseGuildCost(getGuild().getCapacity())) {
+ public void increaseGuildCapacity() {
+ int cost = MapleGuild.getIncreaseGuildCost(getGuild().getCapacity());
+
+ if (getMeso() < cost) {
dropMessage(1, "You don't have enough mesos.");
return;
}
if(Server.getInstance().increaseGuildCapacity(guildid)) {
- gainMeso(-MapleGuild.getIncreaseGuildCost(getGuild().getCapacity()), true, false, false);
+ gainMeso(-cost, true, false, true);
} else {
- dropMessage(1, "You guild already reached the maximum capacity of players.");
+ dropMessage(1, "Your guild already reached the maximum capacity of players.");
}
}
@@ -5581,155 +5610,160 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
public void levelUp(boolean takeexp) {
- Skill improvingMaxHP = null;
- Skill improvingMaxMP = null;
- int improvingMaxHPLevel = 0;
- int improvingMaxMPLevel = 0;
+ petLock.lock();
+ try {
+ Skill improvingMaxHP = null;
+ Skill improvingMaxMP = null;
+ int improvingMaxHPLevel = 0;
+ int improvingMaxMPLevel = 0;
- boolean isBeginner = isBeginnerJob();
- if (ServerConstants.USE_AUTOASSIGN_STARTERS_AP && isBeginner && level < 11) {
- remainingAp = 0;
- if (level < 6) {
- str += 5;
- } else {
- str += 4;
- dex += 1;
- }
- } else {
- remainingAp += 5;
- if (isCygnus() && level > 10 && level < 70) {
- remainingAp++;
- }
- }
-
- if (isBeginner) {
- maxhp += Randomizer.rand(12, 16);
- maxmp += Randomizer.rand(10, 12);
- } else if (job.isA(MapleJob.WARRIOR) || job.isA(MapleJob.DAWNWARRIOR1)) {
- improvingMaxHP = isCygnus() ? SkillFactory.getSkill(DawnWarrior.MAX_HP_INCREASE) : SkillFactory.getSkill(Swordsman.IMPROVED_MAX_HP_INCREASE);
- if (job.isA(MapleJob.CRUSADER)) {
- improvingMaxMP = SkillFactory.getSkill(1210000);
- } else if (job.isA(MapleJob.DAWNWARRIOR2)) {
- improvingMaxMP = SkillFactory.getSkill(11110000);
- }
- improvingMaxHPLevel = getSkillLevel(improvingMaxHP);
- maxhp += Randomizer.rand(24, 28);
- maxmp += Randomizer.rand(4, 6);
- } else if (job.isA(MapleJob.MAGICIAN) || job.isA(MapleJob.BLAZEWIZARD1)) {
- improvingMaxMP = isCygnus() ? SkillFactory.getSkill(BlazeWizard.INCREASING_MAX_MP) : SkillFactory.getSkill(Magician.IMPROVED_MAX_MP_INCREASE);
- improvingMaxMPLevel = getSkillLevel(improvingMaxMP);
- maxhp += Randomizer.rand(10, 14);
- maxmp += Randomizer.rand(22, 24);
- } else if (job.isA(MapleJob.BOWMAN) || job.isA(MapleJob.THIEF) || (job.getId() > 1299 && job.getId() < 1500)) {
- maxhp += Randomizer.rand(20, 24);
- maxmp += Randomizer.rand(14, 16);
- } else if (job.isA(MapleJob.GM)) {
- maxhp = 30000;
- maxmp = 30000;
- } else if (job.isA(MapleJob.PIRATE) || job.isA(MapleJob.THUNDERBREAKER1)) {
- improvingMaxHP = isCygnus() ? SkillFactory.getSkill(ThunderBreaker.IMPROVE_MAX_HP) : SkillFactory.getSkill(5100000);
- improvingMaxHPLevel = getSkillLevel(improvingMaxHP);
- maxhp += Randomizer.rand(22, 28);
- maxmp += Randomizer.rand(18, 23);
- } else if (job.isA(MapleJob.ARAN1)) {
- maxhp += Randomizer.rand(44, 48);
- int aids = Randomizer.rand(4, 8);
- maxmp += aids + Math.floor(aids * 0.1);
- }
- if (improvingMaxHPLevel > 0 && (job.isA(MapleJob.WARRIOR) || job.isA(MapleJob.PIRATE) || job.isA(MapleJob.DAWNWARRIOR1))) {
- maxhp += improvingMaxHP.getEffect(improvingMaxHPLevel).getX();
- }
- if (improvingMaxMPLevel > 0 && (job.isA(MapleJob.MAGICIAN) || job.isA(MapleJob.CRUSADER) || job.isA(MapleJob.BLAZEWIZARD1))) {
- maxmp += improvingMaxMP.getEffect(improvingMaxMPLevel).getX();
- }
-
- if (ServerConstants.USE_RANDOMIZE_HPMP_GAIN) {
- if (job.isA(MapleJob.MAGICIAN) || job.isA(MapleJob.BLAZEWIZARD1)) {
- maxmp += localint_ / 20;
- } else {
- maxmp += localint_ / 10;
- }
- }
-
- if (takeexp) {
- exp.addAndGet(-ExpTable.getExpNeededForLevel(level));
- if (exp.get() < 0) {
- exp.set(0);
- }
- }
- level++;
- if (level >= getMaxClassLevel()) {
- exp.set(0);
- level = getMaxClassLevel(); //To prevent levels past the maximum
- }
-
- maxhp = Math.min(30000, maxhp);
- maxmp = Math.min(30000, maxmp);
- if (level == 200) {
- exp.set(0);
- if(ServerConstants.PLAYERNPC_AUTODEPLOY && !this.isGM()) MaplePlayerNPC.spawnPlayerNPC(GameConstants.getHallOfFameMapid(job), this);
- }
- recalcLocalStats();
- hp = localmaxhp;
- mp = localmaxmp;
- List> statup = new ArrayList<>(10);
- statup.add(new Pair<>(MapleStat.AVAILABLEAP, remainingAp));
- statup.add(new Pair<>(MapleStat.HP, localmaxhp));
- statup.add(new Pair<>(MapleStat.MP, localmaxmp));
- statup.add(new Pair<>(MapleStat.EXP, exp.get()));
- statup.add(new Pair<>(MapleStat.LEVEL, level));
- statup.add(new Pair<>(MapleStat.MAXHP, maxhp));
- statup.add(new Pair<>(MapleStat.MAXMP, maxmp));
- statup.add(new Pair<>(MapleStat.STR, Math.min(str, Short.MAX_VALUE)));
- statup.add(new Pair<>(MapleStat.DEX, Math.min(dex, Short.MAX_VALUE)));
- if (job.getId() % 1000 > 0) {
- remainingSp[GameConstants.getSkillBook(job.getId())] += 3;
- statup.add(new Pair<>(MapleStat.AVAILABLESP, remainingSp[GameConstants.getSkillBook(job.getId())]));
- }
- client.announce(MaplePacketCreator.updatePlayerStats(statup, this));
- getMap().broadcastMessage(this, MaplePacketCreator.showForeignEffect(getId(), 0), false);
- recalcLocalStats();
- setMPC(new MaplePartyCharacter(this));
- silentPartyUpdate();
-
- if(level == 10 && party != null) {
- if(this.isPartyLeader()) party.assignNewLeader(client);
- PartyOperationHandler.leaveParty(party, mpc, client);
-
- showHint("You have reached #blevel 10#k, therefore you must leave your #rstarter party#k.");
- }
-
- if (this.guildid > 0) {
- getGuild().broadcast(MaplePacketCreator.levelUpMessage(2, level, name), this.getId());
- }
- if (ServerConstants.USE_PERFECT_PITCH && level >= 30) {
- //milestones?
- if (MapleInventoryManipulator.checkSpace(client, 4310000, (short) 1, "")) {
- MapleInventoryManipulator.addById(client, 4310000, (short) 1);
- }
- }
- if (level == 200 && !isGM()) {
- final String names = (getMedalText() + name);
- client.getWorldServer().broadcastPacket(MaplePacketCreator.serverNotice(6, String.format(LEVEL_200, names, names)));
- }
-
- if(level % 20 == 0 && ServerConstants.USE_ADD_SLOTS_BY_LEVEL == true) {
- if (!isGM()) {
- for (byte i = 1; i < 5; i++) {
- gainSlots(i, 4, true);
+ boolean isBeginner = isBeginnerJob();
+ if (ServerConstants.USE_AUTOASSIGN_STARTERS_AP && isBeginner && level < 11) {
+ remainingAp = 0;
+ if (level < 6) {
+ str += 5;
+ } else {
+ str += 4;
+ dex += 1;
}
-
- this.yellowMessage("You reached level " + level + ". Congratulations! As a token of your success, your inventory has been expanded a little bit.");
- }
+ } else {
+ remainingAp += 5;
+ if (isCygnus() && level > 10 && level < 70) {
+ remainingAp++;
+ }
+ }
+
+ if (isBeginner) {
+ maxhp += Randomizer.rand(12, 16);
+ maxmp += Randomizer.rand(10, 12);
+ } else if (job.isA(MapleJob.WARRIOR) || job.isA(MapleJob.DAWNWARRIOR1)) {
+ improvingMaxHP = isCygnus() ? SkillFactory.getSkill(DawnWarrior.MAX_HP_INCREASE) : SkillFactory.getSkill(Swordsman.IMPROVED_MAX_HP_INCREASE);
+ if (job.isA(MapleJob.CRUSADER)) {
+ improvingMaxMP = SkillFactory.getSkill(1210000);
+ } else if (job.isA(MapleJob.DAWNWARRIOR2)) {
+ improvingMaxMP = SkillFactory.getSkill(11110000);
+ }
+ improvingMaxHPLevel = getSkillLevel(improvingMaxHP);
+ maxhp += Randomizer.rand(24, 28);
+ maxmp += Randomizer.rand(4, 6);
+ } else if (job.isA(MapleJob.MAGICIAN) || job.isA(MapleJob.BLAZEWIZARD1)) {
+ improvingMaxMP = isCygnus() ? SkillFactory.getSkill(BlazeWizard.INCREASING_MAX_MP) : SkillFactory.getSkill(Magician.IMPROVED_MAX_MP_INCREASE);
+ improvingMaxMPLevel = getSkillLevel(improvingMaxMP);
+ maxhp += Randomizer.rand(10, 14);
+ maxmp += Randomizer.rand(22, 24);
+ } else if (job.isA(MapleJob.BOWMAN) || job.isA(MapleJob.THIEF) || (job.getId() > 1299 && job.getId() < 1500)) {
+ maxhp += Randomizer.rand(20, 24);
+ maxmp += Randomizer.rand(14, 16);
+ } else if (job.isA(MapleJob.GM)) {
+ maxhp = 30000;
+ maxmp = 30000;
+ } else if (job.isA(MapleJob.PIRATE) || job.isA(MapleJob.THUNDERBREAKER1)) {
+ improvingMaxHP = isCygnus() ? SkillFactory.getSkill(ThunderBreaker.IMPROVE_MAX_HP) : SkillFactory.getSkill(5100000);
+ improvingMaxHPLevel = getSkillLevel(improvingMaxHP);
+ maxhp += Randomizer.rand(22, 28);
+ maxmp += Randomizer.rand(18, 23);
+ } else if (job.isA(MapleJob.ARAN1)) {
+ maxhp += Randomizer.rand(44, 48);
+ int aids = Randomizer.rand(4, 8);
+ maxmp += aids + Math.floor(aids * 0.1);
+ }
+ if (improvingMaxHPLevel > 0 && (job.isA(MapleJob.WARRIOR) || job.isA(MapleJob.PIRATE) || job.isA(MapleJob.DAWNWARRIOR1))) {
+ maxhp += improvingMaxHP.getEffect(improvingMaxHPLevel).getX();
+ }
+ if (improvingMaxMPLevel > 0 && (job.isA(MapleJob.MAGICIAN) || job.isA(MapleJob.CRUSADER) || job.isA(MapleJob.BLAZEWIZARD1))) {
+ maxmp += improvingMaxMP.getEffect(improvingMaxMPLevel).getX();
+ }
+
+ if (ServerConstants.USE_RANDOMIZE_HPMP_GAIN) {
+ if (job.isA(MapleJob.MAGICIAN) || job.isA(MapleJob.BLAZEWIZARD1)) {
+ maxmp += localint_ / 20;
+ } else {
+ maxmp += localint_ / 10;
+ }
+ }
+
+ if (takeexp) {
+ exp.addAndGet(-ExpTable.getExpNeededForLevel(level));
+ if (exp.get() < 0) {
+ exp.set(0);
+ }
+ }
+ level++;
+ if (level >= getMaxClassLevel()) {
+ exp.set(0);
+ level = getMaxClassLevel(); //To prevent levels past the maximum
+ }
+
+ maxhp = Math.min(30000, maxhp);
+ maxmp = Math.min(30000, maxmp);
+ if (level == 200) {
+ exp.set(0);
+ if(ServerConstants.PLAYERNPC_AUTODEPLOY && !this.isGM()) MaplePlayerNPC.spawnPlayerNPC(GameConstants.getHallOfFameMapid(job), this);
+ }
+ recalcLocalStats();
+ hp = localmaxhp;
+ mp = localmaxmp;
+ List> statup = new ArrayList<>(10);
+ statup.add(new Pair<>(MapleStat.AVAILABLEAP, remainingAp));
+ statup.add(new Pair<>(MapleStat.HP, localmaxhp));
+ statup.add(new Pair<>(MapleStat.MP, localmaxmp));
+ statup.add(new Pair<>(MapleStat.EXP, exp.get()));
+ statup.add(new Pair<>(MapleStat.LEVEL, level));
+ statup.add(new Pair<>(MapleStat.MAXHP, maxhp));
+ statup.add(new Pair<>(MapleStat.MAXMP, maxmp));
+ statup.add(new Pair<>(MapleStat.STR, Math.min(str, Short.MAX_VALUE)));
+ statup.add(new Pair<>(MapleStat.DEX, Math.min(dex, Short.MAX_VALUE)));
+ if (job.getId() % 1000 > 0) {
+ remainingSp[GameConstants.getSkillBook(job.getId())] += 3;
+ statup.add(new Pair<>(MapleStat.AVAILABLESP, remainingSp[GameConstants.getSkillBook(job.getId())]));
+ }
+ client.announce(MaplePacketCreator.updatePlayerStats(statup, this));
+ getMap().broadcastMessage(this, MaplePacketCreator.showForeignEffect(getId(), 0), false);
+ recalcLocalStats();
+ setMPC(new MaplePartyCharacter(this));
+ silentPartyUpdate();
+
+ if(level == 10 && party != null) {
+ if(this.isPartyLeader()) party.assignNewLeader(client);
+ PartyOperationHandler.leaveParty(party, mpc, client);
+
+ showHint("You have reached #blevel 10#k, therefore you must leave your #rstarter party#k.");
+ }
+
+ if (this.guildid > 0) {
+ getGuild().broadcast(MaplePacketCreator.levelUpMessage(2, level, name), this.getId());
+ }
+ if (ServerConstants.USE_PERFECT_PITCH && level >= 30) {
+ //milestones?
+ if (MapleInventoryManipulator.checkSpace(client, 4310000, (short) 1, "")) {
+ MapleInventoryManipulator.addById(client, 4310000, (short) 1);
+ }
+ }
+ if (level == 200 && !isGM()) {
+ final String names = (getMedalText() + name);
+ client.getWorldServer().broadcastPacket(MaplePacketCreator.serverNotice(6, String.format(LEVEL_200, names, names)));
+ }
+
+ if(level % 20 == 0 && ServerConstants.USE_ADD_SLOTS_BY_LEVEL == true) {
+ if (!isGM()) {
+ for (byte i = 1; i < 5; i++) {
+ gainSlots(i, 4, true);
+ }
+
+ this.yellowMessage("You reached level " + level + ". Congratulations! As a token of your success, your inventory has been expanded a little bit.");
+ }
+ }
+ if (level % 20 == 0 && ServerConstants.USE_ADD_RATES_BY_LEVEL == true) { //For the rate upgrade
+ revertLastPlayerRates();
+ setPlayerRates();
+ this.yellowMessage("You managed to get level " + level + "! Getting experience and items seems a little easier now, huh?");
+ }
+
+ levelUpMessages();
+ guildUpdate();
+ } finally {
+ petLock.unlock();
}
- if (level % 20 == 0 && ServerConstants.USE_ADD_RATES_BY_LEVEL == true) { //For the rate upgrade
- revertLastPlayerRates();
- setPlayerRates();
- this.yellowMessage("You managed to get level " + level + "! Getting experience and items seems a little easier now, huh?");
- }
-
- levelUpMessages();
- guildUpdate();
}
public void gainAp(int amount) {
@@ -6939,27 +6973,25 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
visibleMapObjects.remove(mo);
}
- public void resetStats() {
+ public synchronized void resetStats() {
if(!ServerConstants.USE_AUTOASSIGN_STARTERS_AP) {
return;
}
List> statup = new ArrayList<>(5);
- int tap = 0, tsp = 1;
+ int tap = remainingAp + str + dex + int_ + luk, tsp = 1;
int tstr = 4, tdex = 4, tint = 4, tluk = 4;
- int levelap = (isCygnus() ? 6 : 5);
+
switch (job.getId()) {
case 100:
case 1100:
case 2100:
tstr = 35;
- tap = ((getLevel() - 10) * levelap) + 14;
tsp += ((getLevel() - 10) * 3);
break;
case 200:
case 1200:
tint = 20;
- tap = ((getLevel() - 8) * levelap) + 29;
tsp += ((getLevel() - 8) * 3);
break;
case 300:
@@ -6967,29 +6999,37 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
case 400:
case 1400:
tdex = 25;
- tap = ((getLevel() - 10) * levelap) + 24;
tsp += ((getLevel() - 10) * 3);
break;
case 500:
case 1500:
tdex = 20;
- tap = ((getLevel() - 10) * levelap) + 29;
tsp += ((getLevel() - 10) * 3);
break;
}
- this.remainingAp = tap;
- this.remainingSp[GameConstants.getSkillBook(job.getId())] = tsp;
- this.dex = tdex;
- this.int_ = tint;
- this.str = tstr;
- this.luk = tluk;
- statup.add(new Pair<>(MapleStat.AVAILABLEAP, tap));
- statup.add(new Pair<>(MapleStat.AVAILABLESP, tsp));
- statup.add(new Pair<>(MapleStat.STR, tstr));
- statup.add(new Pair<>(MapleStat.DEX, tdex));
- statup.add(new Pair<>(MapleStat.INT, tint));
- statup.add(new Pair<>(MapleStat.LUK, tluk));
- announce(MaplePacketCreator.updatePlayerStats(statup, this));
+
+ tap -= tstr;
+ tap -= tdex;
+ tap -= tint;
+ tap -= tluk;
+
+ if (tap >= 0) {
+ this.remainingAp = tap;
+ this.remainingSp[GameConstants.getSkillBook(job.getId())] = tsp;
+ this.str = tstr;
+ this.dex = tdex;
+ this.int_ = tint;
+ this.luk = tluk;
+ statup.add(new Pair<>(MapleStat.AVAILABLEAP, tap));
+ statup.add(new Pair<>(MapleStat.AVAILABLESP, tsp));
+ statup.add(new Pair<>(MapleStat.STR, tstr));
+ statup.add(new Pair<>(MapleStat.DEX, tdex));
+ statup.add(new Pair<>(MapleStat.INT, tint));
+ statup.add(new Pair<>(MapleStat.LUK, tluk));
+ announce(MaplePacketCreator.updatePlayerStats(statup, this));
+ } else {
+ FilePrinter.print(FilePrinter.EXCEPTION_CAUGHT, name + " tried to get their stats reseted, without having enough AP available.");
+ }
}
public void resetBattleshipHp() {
@@ -8474,18 +8514,20 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}
announce(MaplePacketCreator.updateQuestInfo((short) qs.getQuest().getId(), qs.getNpc()));
}
-
- private void fameGainByQuest() {
+
+ public void awardQuestPoint(int awardedPoints) {
+ if (ServerConstants.QUEST_POINT_REQUIREMENT < 1 || awardedPoints < 1) return;
+
+ int delta;
synchronized (quests) {
- quest_fame += 1;
+ quest_fame += awardedPoints;
- int delta = quest_fame / ServerConstants.FAME_GAIN_BY_QUEST;
- if(delta > 0) {
- gainFame(delta);
- client.announce(MaplePacketCreator.getShowFameGain(delta));
- }
-
- quest_fame %= ServerConstants.FAME_GAIN_BY_QUEST;
+ delta = quest_fame / ServerConstants.QUEST_POINT_REQUIREMENT;
+ quest_fame %= ServerConstants.QUEST_POINT_REQUIREMENT;
+ }
+
+ if(delta > 0) {
+ gainFame(delta);
}
}
@@ -8503,8 +8545,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
MapleQuest mquest = quest.getQuest();
short questid = mquest.getId();
if(!mquest.isSameDayRepeatable() && !MapleQuest.isExploitableQuest(questid)) {
- if(ServerConstants.FAME_GAIN_BY_QUEST > 0)
- fameGainByQuest();
+ awardQuestPoint(ServerConstants.QUEST_POINT_PER_QUEST_COMPLETE);
}
announce(MaplePacketCreator.completeQuest(questid, quest.getCompletionTime()));
@@ -8572,7 +8613,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
private void runQuestExpireTask() {
petLock.lock();
try {
- long timeNow = System.currentTimeMillis();
+ long timeNow = Server.getInstance().getCurrentTime();
List expireList = new LinkedList<>();
for(Entry qe : questExpirations.entrySet()) {
@@ -8607,7 +8648,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
}, 10 * 1000);
}
- questExpirations.put(quest, System.currentTimeMillis() + time);
+ questExpirations.put(quest, Server.getInstance().getCurrentTime() + time);
} finally {
petLock.unlock();
}
@@ -8880,6 +8921,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
return false;
}
+
//EVENTS
private byte team = 0;
private MapleFitness fitness;
@@ -8917,6 +8959,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
public void setLastSnowballAttack(long time) {
this.snowballattack = time;
}
+
//Monster Carnival
private int cp = 0;
private int obtainedcp = 0;
@@ -9059,7 +9102,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject {
continue;
}
- if ((nEquip.getItemLevel() < ServerConstants.USE_EQUIPMNT_LVLUP) || (itemName.contains("Reverse") && nEquip.getItemLevel() < 4) || (itemName.contains("Timeless") && nEquip.getItemLevel() < 6)) {
+ if ((nEquip.getItemLevel() < ServerConstants.USE_EQUIPMNT_LVLUP) || (nEquip.getItemLevel() < ii.getEquipLevel(nEquip.getItemId(), true))) {
nEquip.gainItemExp(client, expGain);
}
}
diff --git a/src/client/MapleClient.java b/src/client/MapleClient.java
index 0de1520973..7e762135d9 100644
--- a/src/client/MapleClient.java
+++ b/src/client/MapleClient.java
@@ -85,6 +85,8 @@ public class MapleClient {
public static final int LOGIN_SERVER_TRANSITION = 1;
public static final int LOGIN_LOGGEDIN = 2;
public static final String CLIENT_KEY = "CLIENT";
+ public static final String CLIENT_HWID = "HWID";
+ public static final String CLIENT_NIBBLEHWID = "HWID2";
private MapleAESOFB send;
private MapleAESOFB receive;
private final IoSession session;
@@ -507,7 +509,7 @@ public class MapleClient {
return false;
}
- public int login(String login, String pwd) {
+ public int login(String login, String pwd, String nibbleHwid) {
loginattempt++;
if (loginattempt > 4) {
MapleSessionCoordinator.getInstance().closeSession(session, false);
@@ -571,7 +573,7 @@ public class MapleClient {
}
if (loginok == 0 || loginok == 4) {
- AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptSessionLogin(session, accId, loginok == 4);
+ AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptLoginSession(session, nibbleHwid, accId, loginok == 4);
switch (res) {
case SUCCESS:
@@ -887,71 +889,59 @@ public class MapleClient {
final World wserv = getWorldServer(); // obviously wserv is NOT null if this player was online on it
try {
- if (channel == -1 || shutdown) {
- if(chrg != null) chrg.setCharacter(null);
-
- wserv.removePlayer(player);
- removePlayer(wserv);
-
- player.saveCooldowns();
- player.cancelAllDebuffs();
- player.saveCharToDB(true);
-
- clear();
- return;
- }
-
removePlayer(wserv);
-
- if (!cashshop) {
- if (!this.serverTransition) { // meaning not changing channels
- if (messengerid > 0) {
- wserv.leaveMessenger(messengerid, chrm);
- }
- /*
- if (fid > 0) {
- final MapleFamily family = worlda.getFamily(fid);
- family.
+
+ if (!(channel == -1 || shutdown)) {
+ if (!cashshop) {
+ if (!this.serverTransition) { // meaning not changing channels
+ if (messengerid > 0) {
+ wserv.leaveMessenger(messengerid, chrm);
+ }
+ /*
+ if (fid > 0) {
+ final MapleFamily family = worlda.getFamily(fid);
+ family.
+ }
+ */
+ for (MapleQuestStatus status : player.getStartedQuests()) { //This is for those quests that you have to stay logged in for a certain amount of time
+ MapleQuest quest = status.getQuest();
+ if (quest.getTimeLimit() > 0) {
+ MapleQuestStatus newStatus = new MapleQuestStatus(quest, MapleQuestStatus.Status.NOT_STARTED);
+ newStatus.setForfeited(player.getQuest(quest).getForfeited() + 1);
+ player.updateQuest(newStatus);
+ }
+ }
+ if (guild != null) {
+ final Server server = Server.getInstance();
+ server.setGuildMemberOnline(player, false, player.getClient().getChannel());
+ player.getClient().announce(MaplePacketCreator.showGuildInfo(player));
+ }
+ if (bl != null) {
+ wserv.loggedOff(player.getName(), player.getId(), channel, player.getBuddylist().getBuddyIds());
+ }
}
- */
- for (MapleQuestStatus status : player.getStartedQuests()) { //This is for those quests that you have to stay logged in for a certain amount of time
- MapleQuest quest = status.getQuest();
- if (quest.getTimeLimit() > 0) {
- MapleQuestStatus newStatus = new MapleQuestStatus(quest, MapleQuestStatus.Status.NOT_STARTED);
- newStatus.setForfeited(player.getQuest(quest).getForfeited() + 1);
- player.updateQuest(newStatus);
- }
- }
- if (guild != null) {
- final Server server = Server.getInstance();
- server.setGuildMemberOnline(player, false, player.getClient().getChannel());
- player.getClient().announce(MaplePacketCreator.showGuildInfo(player));
- }
- if (bl != null) {
- wserv.loggedOff(player.getName(), player.getId(), channel, player.getBuddylist().getBuddyIds());
- }
- }
- } else {
- if (!this.serverTransition) { // if dc inside of cash shop.
- if (bl != null) {
- wserv.loggedOff(player.getName(), player.getId(), channel, player.getBuddylist().getBuddyIds());
- }
- }
- }
+ } else {
+ if (!this.serverTransition) { // if dc inside of cash shop.
+ if (bl != null) {
+ wserv.loggedOff(player.getName(), player.getId(), channel, player.getBuddylist().getBuddyIds());
+ }
+ }
+ }
+ }
} catch (final Exception e) {
FilePrinter.printError(FilePrinter.ACCOUNT_STUCK, e);
} finally {
if (!this.serverTransition) {
+ if(chrg != null) chrg.setCharacter(null);
wserv.removePlayer(player);
//getChannelServer().removePlayer(player); already being done
player.saveCooldowns();
player.cancelAllDebuffs();
player.saveCharToDB(true);
- if (player != null) {//no idea, occur :(
- player.empty(false);
- }
+
player.logOff();
+ clear();
} else {
getChannelServer().removePlayer(player);
diff --git a/src/client/command/CommandsExecutor.java b/src/client/command/CommandsExecutor.java
index 438a074aae..3db0b3c38c 100644
--- a/src/client/command/CommandsExecutor.java
+++ b/src/client/command/CommandsExecutor.java
@@ -301,7 +301,7 @@ public class CommandsExecutor {
addCommand("servermessage", 4, ServerMessageCommand.class);
addCommand("proitem", 4, ProItemCommand.class);
- addCommand("seteqstat", 4, SetQStatCommand.class);
+ addCommand("seteqstat", 4, SetEqStatCommand.class);
addCommand("exprate", 4, ExpRateCommand.class);
addCommand("mesorate", 4, MesoRateCommand.class);
addCommand("droprate", 4, DropRateCommand.class);
diff --git a/src/client/command/commands/v1/GotoCommand.java b/src/client/command/commands/v1/GotoCommand.java
index 85c49a5bf0..8deb4c3ba8 100644
--- a/src/client/command/commands/v1/GotoCommand.java
+++ b/src/client/command/commands/v1/GotoCommand.java
@@ -26,6 +26,7 @@ package client.command.commands.v1;
import client.MapleCharacter;
import client.command.Command;
import client.MapleClient;
+import constants.GameConstants;
import server.MaplePortal;
import server.maps.MapleMap;
@@ -38,56 +39,7 @@ public class GotoCommand extends Command {
@Override
public void execute(MapleClient c, String[] params) {
- final HashMap gotomaps = new HashMap();
- gotomaps.put("gmmap", 180000000);
- gotomaps.put("southperry", 60000);
- gotomaps.put("amherst", 1000000);
- gotomaps.put("henesys", 100000000);
- gotomaps.put("ellinia", 101000000);
- gotomaps.put("perion", 102000000);
- gotomaps.put("kerning", 103000000);
- gotomaps.put("lith", 104000000);
- gotomaps.put("sleepywood", 105040300);
- gotomaps.put("florina", 110000000);
- gotomaps.put("nautilus", 120000000);
- gotomaps.put("ereve", 130000000);
- gotomaps.put("rien", 140000000);
- gotomaps.put("orbis", 200000000);
- gotomaps.put("happy", 209000000);
- gotomaps.put("elnath", 211000000);
- gotomaps.put("ludi", 220000000);
- gotomaps.put("aqua", 230000000);
- gotomaps.put("leafre", 240000000);
- gotomaps.put("mulung", 250000000);
- gotomaps.put("herb", 251000000);
- gotomaps.put("omega", 221000000);
- gotomaps.put("korean", 222000000);
- gotomaps.put("ellin", 300000000);
- gotomaps.put("nlc", 600000000);
- gotomaps.put("excavation", 990000000);
- gotomaps.put("pianus", 230040420);
- gotomaps.put("horntail", 240060200);
- gotomaps.put("mushmom", 100000005);
- gotomaps.put("griffey", 240020101);
- gotomaps.put("manon", 240020401);
- gotomaps.put("horseman", 682000001);
- gotomaps.put("balrog", 105090900);
- gotomaps.put("zakum", 211042300);
- gotomaps.put("papu", 220080001);
- gotomaps.put("showa", 801000000);
- gotomaps.put("guild", 200000301);
- gotomaps.put("shrine", 800000000);
- gotomaps.put("skelegon", 240040511);
- gotomaps.put("hpq", 100000200);
- gotomaps.put("ht", 240050400);
- gotomaps.put("ariant", 260000000);
- gotomaps.put("magatia", 261000000);
- gotomaps.put("singapore", 540000000);
- gotomaps.put("keep", 610020006);
- gotomaps.put("amoria", 680000000);
- gotomaps.put("temple", 270000100);
- gotomaps.put("neo", 240070000);
- gotomaps.put("fm", 910000000);
+ final HashMap gotomaps = GameConstants.GOTO_MAPS;
MapleCharacter player = c.getPlayer();
if (params.length < 1){
diff --git a/src/client/command/commands/v4/ProItemCommand.java b/src/client/command/commands/v4/ProItemCommand.java
index 8fbb431a51..c65b9a253a 100644
--- a/src/client/command/commands/v4/ProItemCommand.java
+++ b/src/client/command/commands/v4/ProItemCommand.java
@@ -42,11 +42,12 @@ public class ProItemCommand extends Command {
public void execute(MapleClient c, String[] params) {
MapleCharacter player = c.getPlayer();
if (params.length < 2) {
- player.yellowMessage("Syntax: !proitem ");
+ player.yellowMessage("Syntax: !proitem []");
return;
}
int itemid = Integer.parseInt(params[0]);
- short multiply = Short.parseShort(params[1]);
+ short stat = (short) Math.max(0, Short.parseShort(params[1]));
+ short spdjmp = params.length >= 3 ? (short) Math.max(0, Short.parseShort(params[2])) : 0;
MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance();
MapleInventoryType type = ItemConstants.getInventoryType(itemid);
@@ -54,13 +55,13 @@ public class ProItemCommand extends Command {
Item it = ii.getEquipById(itemid);
it.setOwner(player.getName());
- hardsetItemStats((Equip) it, multiply);
+ hardsetItemStats((Equip) it, stat, spdjmp);
MapleInventoryManipulator.addFromDrop(c, it);
} else {
player.dropMessage(6, "Make sure it's an equippable item.");
}
}
- private static void hardsetItemStats(Equip equip, short stat) {
+ private static void hardsetItemStats(Equip equip, short stat, short spdjmp) {
equip.setStr(stat);
equip.setDex(stat);
equip.setInt(stat);
@@ -69,8 +70,8 @@ public class ProItemCommand extends Command {
equip.setWatk(stat);
equip.setAcc(stat);
equip.setAvoid(stat);
- equip.setJump(stat);
- equip.setSpeed(stat);
+ equip.setJump(spdjmp);
+ equip.setSpeed(spdjmp);
equip.setWdef(stat);
equip.setMdef(stat);
equip.setHp(stat);
diff --git a/src/client/command/commands/v4/SetQStatCommand.java b/src/client/command/commands/v4/SetEqStatCommand.java
similarity index 70%
rename from src/client/command/commands/v4/SetQStatCommand.java
rename to src/client/command/commands/v4/SetEqStatCommand.java
index f79abe5143..f616f8601f 100644
--- a/src/client/command/commands/v4/SetQStatCommand.java
+++ b/src/client/command/commands/v4/SetEqStatCommand.java
@@ -31,7 +31,7 @@ import client.inventory.MapleInventory;
import client.inventory.MapleInventoryType;
import constants.ItemConstants;
-public class SetQStatCommand extends Command {
+public class SetEqStatCommand extends Command {
{
setDescription("");
}
@@ -40,33 +40,33 @@ public class SetQStatCommand extends Command {
public void execute(MapleClient c, String[] params) {
MapleCharacter player = c.getPlayer();
if (params.length < 1) {
- player.yellowMessage("Syntax: !seteqstat ");
+ player.yellowMessage("Syntax: !seteqstat []");
return;
}
- int newStat = Integer.parseInt(params[0]);
+ short newStat = (short) Math.max(0, Integer.parseInt(params[0]));
+ short newSpdJmp = params.length >= 2 ? (short) Integer.parseInt(params[1]) : 0;
MapleInventory equip = player.getInventory(MapleInventoryType.EQUIP);
-
+
for (byte i = 1; i <= equip.getSlotLimit(); i++) {
try {
Equip eu = (Equip) equip.getItem(i);
if (eu == null) continue;
- short incval = (short) newStat;
- eu.setWdef(incval);
- eu.setAcc(incval);
- eu.setAvoid(incval);
- eu.setJump(incval);
- eu.setMatk(incval);
- eu.setMdef(incval);
- eu.setHp(incval);
- eu.setMp(incval);
- eu.setSpeed(incval);
- eu.setWatk(incval);
- eu.setDex(incval);
- eu.setInt(incval);
- eu.setStr(incval);
- eu.setLuk(incval);
+ eu.setWdef(newStat);
+ eu.setAcc(newStat);
+ eu.setAvoid(newStat);
+ eu.setJump(newSpdJmp);
+ eu.setMatk(newStat);
+ eu.setMdef(newStat);
+ eu.setHp(newStat);
+ eu.setMp(newStat);
+ eu.setSpeed(newSpdJmp);
+ eu.setWatk(newStat);
+ eu.setDex(newStat);
+ eu.setInt(newStat);
+ eu.setStr(newStat);
+ eu.setLuk(newStat);
byte flag = eu.getFlag();
flag |= ItemConstants.UNTRADEABLE;
diff --git a/src/client/inventory/Equip.java b/src/client/inventory/Equip.java
index e8395e2819..26fd6e7dd2 100644
--- a/src/client/inventory/Equip.java
+++ b/src/client/inventory/Equip.java
@@ -66,7 +66,7 @@ public class Equip extends Item {
private float itemExp;
private int ringid = -1;
private boolean wear = false;
- private boolean isUpgradeable, isElemental = false; // timeless or reverse
+ private boolean isUpgradeable, isElemental = false; // timeless or reverse, or any equip that could levelup on GMS for all effects
public Equip(int id, short position) {
this(id, position, 0);
@@ -78,8 +78,7 @@ public class Equip extends Item {
this.itemExp = 0;
this.itemLevel = 1;
- String itemName = MapleItemInformationProvider.getInstance().getName(id);
- if(itemName != null) this.isElemental = (itemName.contains("Timeless") || itemName.contains("Reverse"));
+ this.isElemental = (MapleItemInformationProvider.getInstance().getEquipLevel(id, false) > 1);
}
@Override
@@ -533,17 +532,14 @@ public class Equip extends Item {
}
}
- private boolean reachedMaxLevel(String eqpName) {
- if(isElemental) {
- if(eqpName.contains("Timeless")) {
- if(itemLevel < 6) return false;
- } else {
- if(itemLevel < 4) return false;
+ private boolean reachedMaxLevel() {
+ if (isElemental) {
+ if (itemLevel < MapleItemInformationProvider.getInstance().getEquipLevel(getItemId(), true)) {
+ return false;
}
}
- if(itemLevel < ServerConstants.USE_EQUIPMNT_LVLUP) return false;
- return true;
+ return itemLevel >= ServerConstants.USE_EQUIPMNT_LVLUP;
}
public String showEquipFeatures(MapleClient c) {
@@ -551,7 +547,7 @@ public class Equip extends Item {
if(!ii.isUpgradeable(this.getItemId())) return "";
String eqpName = ii.getName(getItemId());
- String eqpInfo = reachedMaxLevel(eqpName) ? " #e#rMAX LEVEL#k#n" : (" EXP: #e#b" + (int)itemExp + "#k#n / " + ExpTable.getEquipExpNeededForLevel(itemLevel));
+ String eqpInfo = reachedMaxLevel() ? " #e#rMAX LEVEL#k#n" : (" EXP: #e#b" + (int)itemExp + "#k#n / " + ExpTable.getEquipExpNeededForLevel(itemLevel));
return "'" + eqpName + "' -> LV: #e#b" + itemLevel + "#k#n " + eqpInfo + "\r\n";
}
diff --git a/src/client/inventory/Item.java b/src/client/inventory/Item.java
index 85e9f1a5ff..ad0ca26ca7 100644
--- a/src/client/inventory/Item.java
+++ b/src/client/inventory/Item.java
@@ -25,10 +25,12 @@ import constants.ItemConstants;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
-import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
public class Item implements Comparable- {
+ private static AtomicInteger runningCashId = new AtomicInteger(0);
+
private int id, cashId, sn;
private short position;
private short quantity;
@@ -81,7 +83,7 @@ public class Item implements Comparable
- {
public int getCashId() {
if (cashId == 0) {
- cashId = new Random().nextInt(Integer.MAX_VALUE) + 1;
+ cashId = runningCashId.incrementAndGet();
}
return cashId;
}
diff --git a/src/constants/GameConstants.java b/src/constants/GameConstants.java
index 39741a0371..3bbb5d8b74 100644
--- a/src/constants/GameConstants.java
+++ b/src/constants/GameConstants.java
@@ -17,7 +17,8 @@ import server.quest.MapleQuest;
*/
public class GameConstants {
public static String[] WORLD_NAMES = {"Scania", "Bera", "Broa", "Windia", "Khaini", "Bellocan", "Mardia", "Kradia", "Yellonde", "Demethos", "Galicia", "El Nido", "Zenith", "Arcenia", "Kastia", "Judis", "Plana", "Kalluna", "Stius", "Croa", "Medere"};
- public static final int[] OWL_DATA = new int[]{1082002, 2070005, 2070006, 1022047, 1102041, 2044705, 2340000, 2040017, 1092030, 2040804};
+ public static final int[] OWL_DATA = new int[]{1082002, 2070005, 2070006, 1022047, 1102041, 2044705, 2340000, 2040017, 1092030, 2040804};
+ public static final int[] CASH_DATA = new int[]{50200004, 50200069, 50200117, 50100008, 50000047};
// Ronan's rates upgrade system
private static final int[] DROP_RATE_GAIN = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
@@ -40,6 +41,62 @@ public class GameConstants {
return(EXP_RATE_GAIN[slot]);
}
+ // used by the "goto" command
+ public static final HashMap GOTO_MAPS = new HashMap() {{
+ put("gmmap", 180000000);
+ put("southperry", 60000);
+ put("amherst", 1000000);
+ put("henesys", 100000000);
+ put("ellinia", 101000000);
+ put("perion", 102000000);
+ put("kerning", 103000000);
+ put("lith", 104000000);
+ put("sleepywood", 105040300);
+ put("florina", 110000000);
+ put("nautilus", 120000000);
+ put("ereve", 130000000);
+ put("rien", 140000000);
+ put("orbis", 200000000);
+ put("happy", 209000000);
+ put("elnath", 211000000);
+ put("ludi", 220000000);
+ put("aqua", 230000000);
+ put("leafre", 240000000);
+ put("mulung", 250000000);
+ put("herb", 251000000);
+ put("omega", 221000000);
+ put("korean", 222000000);
+ put("ellin", 300000000);
+ put("nlc", 600000000);
+ put("excavation", 990000000);
+ put("pianus", 230040420);
+ put("horntail", 240060200);
+ put("mushmom", 100000005);
+ put("griffey", 240020101);
+ put("manon", 240020401);
+ put("horseman", 682000001);
+ 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("ht", 240050400);
+ 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("fm", 910000000);
+ }};
+
// MapleStory default keyset
private static final int[] DEFAULT_KEY = {18, 65, 2, 23, 3, 4, 5, 6, 16, 17, 19, 25, 26, 27, 31, 34, 35, 37, 38, 40, 43, 44, 45, 46, 50, 56, 59, 60, 61, 62, 63, 64, 57, 48, 29, 7, 24, 33, 41, 39};
private static final int[] DEFAULT_TYPE = {4, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 4, 4, 5, 6, 6, 6, 6, 6, 6, 5, 4, 5, 4, 4, 4, 4, 4};
diff --git a/src/constants/ServerConstants.java b/src/constants/ServerConstants.java
index 7830aadb28..e3fb801041 100644
--- a/src/constants/ServerConstants.java
+++ b/src/constants/ServerConstants.java
@@ -33,12 +33,12 @@ public class ServerConstants {
public static final boolean AUTOMATIC_REGISTER = true; //Automatically register players when they login with a nonexistent username.
public static final boolean BCRYPT_MIGRATION = true; //Performs a migration from old SHA-1 and SHA-512 password to bcrypt.
public static final boolean COLLECTIVE_CHARSLOT = false; //Available character slots are contabilized globally rather than per world server.
- public static final boolean DETERRED_MULTICLIENT = false; //Enables multi-client and suspicious remote IP detection on the login system.
+ public static final boolean DETERRED_MULTICLIENT = false; //Enables multi-client and suspicious remote IP detection on the login system.
//Besides blocking logging in with several client sessions on the same machine, this also blocks suspicious login attempts for players that tries to login on an account using several diferent remote addresses.
//Multiclient Coordinator Configuration
- public static final int MAX_ALLOWED_ACCOUNT_IP = 4; //Allows up to N concurrent IP's for an account. IP's remains linked to an account longer the more times it's used to login.
+ public static final int MAX_ALLOWED_ACCOUNT_HWID = 4; //Allows up to N concurrent HWID's for an account. HWID's remains linked to an account longer the more times it's used to login.
public static final int MAX_ACCOUNT_LOGIN_ATTEMPT = 15; //After N tries on an account, login on that account gets disabled for a short period.
public static final int LOGIN_ATTEMPT_DURATION = 120; //Period in seconds the login attempt remains registered on the system.
@@ -77,7 +77,7 @@ public class ServerConstants {
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.
- public static final boolean USE_ENFORCE_OWL_SUGGESTIONS = false;//Forces the Owl of Minerva to always display the defined item array on GameConstants.OWL_DATA instead of those featured by the players.
+ public static final boolean USE_ENFORCE_ITEM_SUGGESTION = false;//Forces the Owl of Minerva and the Cash Shop to always display the defined item array instead of those featured by the players.
public static final boolean USE_ENFORCE_UNMERCHABLE_CASH = true;//Forces players to not sell CASH items via merchants.
public static final boolean USE_ENFORCE_UNMERCHABLE_PET = true; //Forces players to not sell pets via merchants. (since non-named pets gets dirty name and other possible DB-related issues)
public static final boolean USE_ENFORCE_MDOOR_POSITION = false; //Forces mystic door to be spawned near spawnpoints.
@@ -87,11 +87,15 @@ public class ServerConstants {
public static final boolean USE_ERASE_UNTRADEABLE_DROP = true; //Forces flagged untradeable items to disappear when dropped.
public static final boolean USE_ERASE_PET_ON_EXPIRATION = false;//Forces pets to be removed from inventory when expire time comes, rather than converting it to a doll.
public static final boolean USE_BUFF_MOST_SIGNIFICANT = true; //When applying buffs, the player will stick with the highest stat boost among the listed, rather than overwriting stats.
+ public static final boolean USE_BUFF_EVERLASTING = false; //Every applied buff on players holds expiration time so high it'd be considered permanent. Suggestion thanks to Vcoc.
public static final boolean USE_MULTIPLE_SAME_EQUIP_DROP = true;//Enables multiple drops by mobs of the same equipment, number of possible drops based on the quantities provided at the drop data.
public static final boolean USE_BANISHABLE_TOWN_SCROLL = true; //Enables town scrolls to act as if it's a "player banish", rendering the antibanish scroll effect available.
+ public static final boolean USE_ENABLE_FULL_RESPAWN = true; //At respawn task, always respawn missing mobs when they're available. Spawn count doesn't depend on how many players are currently there.
+
+ //Events/PQs Configuration
public static final boolean USE_OLD_GMS_STYLED_PQ_NPCS = true; //Enables PQ NPCs with similar behaviour to old GMS style, that skips info about the PQs and immediately tries to register the party in.
public static final boolean USE_ENABLE_SOLO_EXPEDITIONS = true; //Enables start expeditions with any number of players. This will also bypass all the Zakum prequest.
- public static final boolean USE_ENABLE_FULL_RESPAWN = true; //At respawn task, always respawn missing mobs when they're available. Spawn count doesn't depend on how many players are currently there.
+ public static final boolean USE_ENABLE_RECALL_EVENT = true; //Enables a disconnected player to reaccess the last event instance they were in before logging out. Recall only works if the event isn't cleared or disposed yet. Suggestion thanks to Alisson (Goukken).
//Announcement Configuration
public static final boolean USE_ANNOUNCE_SHOPITEMSOLD = false; //Automatic message sent to owner when an item from the Player Shop or Hired Merchant is sold.
@@ -178,12 +182,19 @@ public class ServerConstants {
//Quest Configuration
public static final boolean USE_QUEST_RATE = false; //Exp/Meso gained by quests uses fixed server exp/meso rate times quest rate as multiplier, instead of player rates.
- public static final int FAME_GAIN_MIN_HOUR_INTERVAL = 24; //Minimum time interval in hours the repeatable quest must have to be accounted for the "fame gain by quest".
- public static final int FAME_GAIN_BY_QUEST = 4; //Fame gain each N quest completes, set 0 to disable.
+
+ //Quest Points Configuration
+ public static final int QUEST_POINT_REPEATABLE_INTERVAL = 24;//Minimum interval between repeatable quest completions for quest points to be awarded.
+ public static final int QUEST_POINT_REQUIREMENT = 16; //Exchange factor between N quest points to +1 fame, set 0 to disable the entire quest point mechanism.
+ public static final int QUEST_POINT_PER_QUEST_COMPLETE = 4; //Each completed quest awards N quest points, set 0 to disable.
+ public static final int QUEST_POINT_PER_EVENT_CLEAR = 1; //Each completed event instance awards N quest points, set 0 to disable.
//Guild Configuration
public static final int CREATE_GUILD_COST = 1500000;
public static final int CHANGE_EMBLEM_COST = 5000000;
+ public static final int EXPAND_GUILD_BASE_COST = 500000;
+ public static final int EXPAND_GUILD_TIER_COST = 1000000;
+ public static final int EXPAND_GUILD_MAX_COST = 5000000;
//Equipment Configuration
public static final boolean USE_EQUIPMNT_LVLUP_SLOTS = true;//Equips can upgrade slots at level up.
diff --git a/src/net/server/Server.java b/src/net/server/Server.java
index fffd2e0ec2..0a57cbda56 100644
--- a/src/net/server/Server.java
+++ b/src/net/server/Server.java
@@ -59,6 +59,7 @@ import net.server.guild.MapleGuild;
import net.server.guild.MapleGuildCharacter;
import net.server.worker.CharacterDiseaseWorker;
import net.server.worker.CouponWorker;
+import net.server.worker.EventRecallCoordinatorWorker;
import net.server.worker.LoginCoordinatorWorker;
import net.server.worker.LoginStorageWorker;
import net.server.worker.RankingCommandWorker;
@@ -841,6 +842,7 @@ public class Server {
tMan.register(new RankingCommandWorker(), 5 * 60 * 1000, 5 * 60 * 1000);
tMan.register(new RankingLoginWorker(), ServerConstants.RANKING_INTERVAL, timeLeft);
tMan.register(new LoginCoordinatorWorker(), 60 * 60 * 1000, timeLeft);
+ tMan.register(new EventRecallCoordinatorWorker(), 60 * 60 * 1000, timeLeft);
tMan.register(new LoginStorageWorker(), 2 * 60 * 1000, 2 * 60 * 1000);
long timeToTake = System.currentTimeMillis();
diff --git a/src/net/server/audit/locks/MonitoredLockType.java b/src/net/server/audit/locks/MonitoredLockType.java
index 042d302dd6..3f338d130a 100644
--- a/src/net/server/audit/locks/MonitoredLockType.java
+++ b/src/net/server/audit/locks/MonitoredLockType.java
@@ -70,6 +70,7 @@ public enum MonitoredLockType {
WORLD_PSHOPS,
WORLD_MERCHS,
WORLD_MAPOBJS,
+ WORLD_SUGGEST,
EIM,
EIM_PARTY,
EIM_SCRIPT,
diff --git a/src/net/server/channel/handlers/CashOperationHandler.java b/src/net/server/channel/handlers/CashOperationHandler.java
index bcbe2d810d..a44097c85a 100644
--- a/src/net/server/channel/handlers/CashOperationHandler.java
+++ b/src/net/server/channel/handlers/CashOperationHandler.java
@@ -53,14 +53,14 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.enableActions());
return;
}
- final int action = slea.readByte();
+ final int action = slea.readByte();
if (action == 0x03 || action == 0x1E) {
slea.readByte();
final int useNX = slea.readInt();
final int snCS = slea.readInt();
CashItem cItem = CashItemFactory.getItem(snCS);
- if (cItem == null || !cItem.isOnSale() || cs.getCash(useNX) < cItem.getPrice()) {
+ if (!canBuy(cItem, cs.getCash(useNX))) {
FilePrinter.printError(FilePrinter.ITEM, "Denied to sell cash item with SN " + cItem.getSN());
c.announce(MaplePacketCreator.enableActions());
return;
@@ -87,7 +87,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
}
c.announce(MaplePacketCreator.showBoughtCashPackage(cashPackage, c.getAccID()));
}
- cs.gainCash(useNX, -cItem.getPrice());
+ cs.gainCash(useNX, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
} else if (action == 0x04) {//TODO check for gender
int birthday = slea.readInt();
@@ -113,7 +113,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
}
cs.gift(Integer.parseInt(recipient.get("id")), chr.getName(), message, cItem.getSN());
c.announce(MaplePacketCreator.showGiftSucceed(recipient.get("name"), cItem));
- cs.gainCash(4, -cItem.getPrice());
+ cs.gainCash(4, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
try {
chr.sendNote(recipient.get("name"), chr.getName() + " has sent you a gift! Go check out the Cash Shop.", (byte) 0); //fame or not
@@ -156,7 +156,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
}
if (chr.gainSlots(type, 8, false)) {
c.announce(MaplePacketCreator.showBoughtInventorySlots(type, chr.getSlots(type)));
- cs.gainCash(cash, -cItem.getPrice());
+ cs.gainCash(cash, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
}
}
@@ -186,7 +186,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
}
if (chr.getStorage().gainSlots(8)) {
c.announce(MaplePacketCreator.showBoughtStorageSlots(chr.getStorage().getSlots()));
- cs.gainCash(cash, -cItem.getPrice());
+ cs.gainCash(cash, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
}
}
@@ -202,7 +202,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
if (c.gainCharacterSlot()) {
c.announce(MaplePacketCreator.showBoughtCharacterSlot(c.getCharacterSlots()));
- cs.gainCash(cash, -cItem.getPrice());
+ cs.gainCash(cash, cItem, chr.getWorld());
c.announce(MaplePacketCreator.showCash(chr));
} else {
chr.dropMessage(1, "You have already used up all 12 extra character slots.");
@@ -276,7 +276,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
cs.addToInventory(eqp);
c.announce(MaplePacketCreator.showBoughtCashItem(eqp, c.getAccID()));
cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), (ringid + 1));
- cs.gainCash(toCharge, -itemRing.getPrice());
+ cs.gainCash(toCharge, itemRing, chr.getWorld());
chr.addCrushRing(MapleRing.loadFromDb(ringid));
try {
chr.sendNote(partner.getName(), text, (byte) 1);
@@ -357,7 +357,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
return c.checkBirthDate(cal);
}
- public static boolean canBuy(CashItem item, int cash) {
+ private static boolean canBuy(CashItem item, int cash) {
return item != null && item.isOnSale() && item.getPrice() <= cash;
}
}
diff --git a/src/net/server/channel/handlers/GiveFameHandler.java b/src/net/server/channel/handlers/GiveFameHandler.java
index 5792efbb24..c8c11a7943 100644
--- a/src/net/server/channel/handlers/GiveFameHandler.java
+++ b/src/net/server/channel/handlers/GiveFameHandler.java
@@ -42,24 +42,23 @@ public final class GiveFameHandler extends AbstractMaplePacketHandler {
if (target == null || target.getId() == player.getId() || player.getLevel() < 15) {
return;
} else if (famechange != 1 && famechange != -1) {
- AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit fame.");
- FilePrinter.printError(FilePrinter.EXPLOITS + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + " tried to fame hack with famechange " + famechange + "\r\n");
- c.disconnect(true, false);
- return;
+ AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit fame.");
+ FilePrinter.printError(FilePrinter.EXPLOITS + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + " tried to fame hack with famechange " + famechange + "\r\n");
+ c.disconnect(true, false);
+ return;
}
+
FameStatus status = player.canGiveFame(target);
- if (status == FameStatus.OK || player.isGM()){
- if (Math.abs(target.getFame() + famechange) < 30001) {
- target.addFame(famechange);
- target.updateSingleStat(MapleStat.FAME, target.getFame());
- }
- if (!player.isGM()) {
- player.hasGivenFame(target);
- }
- c.announce(MaplePacketCreator.giveFameResponse(mode, target.getName(), target.getFame()));
- target.getClient().announce(MaplePacketCreator.receiveFame(mode, player.getName()));
+ if (status == FameStatus.OK) {
+ if (target.gainFame(famechange, player, mode)) {
+ if (!player.isGM()) {
+ player.hasGivenFame(target);
+ }
+ } else {
+ player.message("Could not process the request, since this character currently has the minimum/maximum level of fame.");
+ }
} else {
- c.announce(MaplePacketCreator.giveFameErrorResponse(status == FameStatus.NOT_TODAY ? 3 : 4));
+ c.announce(MaplePacketCreator.giveFameErrorResponse(status == FameStatus.NOT_TODAY ? 3 : 4));
}
}
}
\ No newline at end of file
diff --git a/src/net/server/channel/handlers/NoteActionHandler.java b/src/net/server/channel/handlers/NoteActionHandler.java
index ef50564fce..b5d1d45c18 100644
--- a/src/net/server/channel/handlers/NoteActionHandler.java
+++ b/src/net/server/channel/handlers/NoteActionHandler.java
@@ -77,7 +77,6 @@ public final class NoteActionHandler extends AbstractMaplePacketHandler {
}
if (fame > 0) {
c.getPlayer().gainFame(fame);
- c.announce(MaplePacketCreator.getShowFameGain(fame));
}
}
}
diff --git a/src/net/server/channel/handlers/PlayerLoggedinHandler.java b/src/net/server/channel/handlers/PlayerLoggedinHandler.java
index f8b5ac5673..21dc38a877 100644
--- a/src/net/server/channel/handlers/PlayerLoggedinHandler.java
+++ b/src/net/server/channel/handlers/PlayerLoggedinHandler.java
@@ -62,7 +62,11 @@ import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
+import net.server.coordinator.MapleEventRecallCoordinator;
+import net.server.coordinator.MapleSessionCoordinator;
+import org.apache.mina.core.session.IoSession;
import server.life.MobSkill;
+import scripting.event.EventInstanceManager;
import tools.packets.Wedding;
public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
@@ -79,11 +83,23 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
MapleCharacter player = c.getWorldServer().getPlayerStorage().getCharacterById(cid);
boolean newcomer = false;
if (player == null) {
- if(!server.validateCharacteridInTransition((InetSocketAddress) c.getSession().getRemoteAddress(), cid)) {
+ IoSession session = c.getSession();
+
+ if (!server.validateCharacteridInTransition((InetSocketAddress) session.getRemoteAddress(), cid)) {
c.disconnect(true, false);
return;
}
+ if (ServerConstants.DETERRED_MULTICLIENT) {
+ String remoteHwid = MapleSessionCoordinator.getInstance().getGameSessionHwid(session);
+ if (remoteHwid == null) {
+ c.disconnect(true, false);
+ return;
+ }
+
+ session.setAttribute(MapleClient.CLIENT_HWID, remoteHwid);
+ }
+
try {
player = MapleCharacter.loadCharFromDB(cid, c, true);
newcomer = true;
@@ -263,7 +279,7 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
c.announce(MaplePacketCreator.requestBuddylistAdd(pendingBuddyRequest.getId(), c.getPlayer().getId(), pendingBuddyRequest.getName()));
}
- if(newcomer) {
+ if (newcomer) {
for(MaplePet pet : player.getPets()) {
if(pet != null)
world.registerPetHunger(player, player.getPetIndex(pet));
@@ -332,6 +348,13 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler {
partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(player.getId(), player.getMapId()));
}
}
+
+ if (newcomer) {
+ EventInstanceManager eim = MapleEventRecallCoordinator.getInstance().recallEventInstance(cid);
+ if (eim != null) {
+ eim.registerPlayer(player);
+ }
+ }
}
private static void showDueyNotification(MapleClient c, MapleCharacter player) {
diff --git a/src/net/server/channel/handlers/SpecialMoveHandler.java b/src/net/server/channel/handlers/SpecialMoveHandler.java
index 4cbf68167c..1369ca469b 100644
--- a/src/net/server/channel/handlers/SpecialMoveHandler.java
+++ b/src/net/server/channel/handlers/SpecialMoveHandler.java
@@ -117,8 +117,8 @@ public final class SpecialMoveHandler extends AbstractMaplePacketHandler {
int gain = lose * (ef.getY() / 100);
chr.setMp(chr.getMp() + gain);
chr.updateSingleStat(MapleStat.MP, chr.getMp());
- } else if (skillid == Priest.DISPEL || skillid == SuperGM.HEAL_PLUS_DISPEL) {
- slea.skip((skillid == Priest.DISPEL) ? 10 : 11);
+ } else if (skillid == SuperGM.HEAL_PLUS_DISPEL) {
+ slea.skip(11);
chr.getMap().broadcastMessage(chr, MaplePacketCreator.showBuffeffect(chr.getId(), skillid, chr.getSkillLevel(skillid)), false);
} else if (skillid % 10000000 == 1004) {
slea.readShort();
diff --git a/src/net/server/channel/handlers/UseCashItemHandler.java b/src/net/server/channel/handlers/UseCashItemHandler.java
index d0f4e489be..18aeed4570 100644
--- a/src/net/server/channel/handlers/UseCashItemHandler.java
+++ b/src/net/server/channel/handlers/UseCashItemHandler.java
@@ -352,7 +352,7 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler {
} else if (itemType == 523) {
int itemid = slea.readInt();
- if(!ServerConstants.USE_ENFORCE_OWL_SUGGESTIONS) c.getWorldServer().addOwlItemSearch(itemid);
+ if(!ServerConstants.USE_ENFORCE_ITEM_SUGGESTION) c.getWorldServer().addOwlItemSearch(itemid);
player.setOwlSearch(itemid);
List> hmsAvailable = c.getWorldServer().getAvailableItemBundles(itemid);
if(!hmsAvailable.isEmpty()) remove(c, itemId);
diff --git a/src/net/server/channel/handlers/UseOwlOfMinervaHandler.java b/src/net/server/channel/handlers/UseOwlOfMinervaHandler.java
index a22c7074b0..91a0b733a1 100644
--- a/src/net/server/channel/handlers/UseOwlOfMinervaHandler.java
+++ b/src/net/server/channel/handlers/UseOwlOfMinervaHandler.java
@@ -56,7 +56,7 @@ public final class UseOwlOfMinervaHandler extends AbstractMaplePacketHandler {
}
};
- PriorityQueue> queue = new PriorityQueue<>(10, comparator);
+ PriorityQueue> queue = new PriorityQueue<>(Math.max(1, owlSearched.size()), comparator);
for(Pair p : owlSearched) {
queue.add(p);
}
diff --git a/src/net/server/coordinator/LoginStorage.java b/src/net/server/coordinator/LoginStorage.java
index 314069638b..8c889a6a1b 100644
--- a/src/net/server/coordinator/LoginStorage.java
+++ b/src/net/server/coordinator/LoginStorage.java
@@ -34,7 +34,7 @@ import net.server.Server;
*/
public class LoginStorage {
- ConcurrentHashMap> loginHistory = new ConcurrentHashMap<>();
+ private ConcurrentHashMap> loginHistory = new ConcurrentHashMap<>();
public boolean registerLogin(int accountId) {
List accHist = loginHistory.putIfAbsent(accountId, new LinkedList());
diff --git a/src/net/server/coordinator/MapleEventRecallCoordinator.java b/src/net/server/coordinator/MapleEventRecallCoordinator.java
new file mode 100644
index 0000000000..e790cce155
--- /dev/null
+++ b/src/net/server/coordinator/MapleEventRecallCoordinator.java
@@ -0,0 +1,73 @@
+/*
+ This file is part of the HeavenMS MapleStory Server
+ Copyleft (L) 2016 - 2018 RonanLana
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation version 3 as published by
+ the Free Software Foundation. You may not use, modify or distribute
+ this program under any other version of the GNU Affero General Public
+ License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+package net.server.coordinator;
+
+import constants.ServerConstants;
+import scripting.event.EventInstanceManager;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ *
+ * @author Ronan
+ */
+public class MapleEventRecallCoordinator {
+
+ private final static MapleEventRecallCoordinator instance = new MapleEventRecallCoordinator();
+
+ public static MapleEventRecallCoordinator getInstance() {
+ return instance;
+ }
+
+ private ConcurrentHashMap eventHistory = new ConcurrentHashMap<>();
+
+ private static boolean isRecallableEvent(EventInstanceManager eim) {
+ return eim != null && !eim.isEventDisposed() && !eim.isEventCleared();
+ }
+
+ public EventInstanceManager recallEventInstance(int characterId) {
+ EventInstanceManager eim = eventHistory.remove(characterId);
+ return isRecallableEvent(eim) ? eim : null;
+ }
+
+ public void storeEventInstance(int characterId, EventInstanceManager eim) {
+ if (ServerConstants.USE_ENABLE_RECALL_EVENT && isRecallableEvent(eim)) {
+ eventHistory.put(characterId, eim);
+ }
+ }
+
+ public void manageEventInstances() {
+ if (!eventHistory.isEmpty()) {
+ List toRemove = new LinkedList<>();
+
+ for (Entry eh : eventHistory.entrySet()) {
+ if (!isRecallableEvent(eh.getValue())) {
+ toRemove.add(eh.getKey());
+ }
+ }
+
+ for (Integer r : toRemove) {
+ eventHistory.remove(r);
+ }
+ }
+ }
+}
diff --git a/src/net/server/coordinator/MapleSessionCoordinator.java b/src/net/server/coordinator/MapleSessionCoordinator.java
index ddf6820b35..05850085a2 100644
--- a/src/net/server/coordinator/MapleSessionCoordinator.java
+++ b/src/net/server/coordinator/MapleSessionCoordinator.java
@@ -19,6 +19,7 @@
*/
package net.server.coordinator;
+import client.MapleClient;
import constants.ServerConstants;
import net.server.Server;
@@ -33,11 +34,14 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -58,14 +62,18 @@ public class MapleSessionCoordinator {
REMOTE_LOGGEDIN,
REMOTE_REACHED_LIMIT,
REMOTE_PROCESSING,
+ REMOTE_NO_MATCH,
MANY_ACCOUNT_ATTEMPTS,
COORDINATOR_ERROR
}
private final LoginStorage loginStorage = new LoginStorage();
- private final Set onlineRemoteHosts = new HashSet<>();
+ private final Set onlineRemoteHwids = new HashSet<>();
private final Map> loginRemoteHosts = new HashMap<>();
private final Set pooledRemoteHosts = new HashSet<>();
+
+ private final ConcurrentHashMap cachedHostHwids = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap cachedHostTimeout = new ConcurrentHashMap<>();
private final List poolLock = new ArrayList<>(100);
private MapleSessionCoordinator() {
@@ -74,7 +82,7 @@ public class MapleSessionCoordinator {
}
}
- private static long ipExpirationUpdate(int relevance) {
+ private static long hwidExpirationUpdate(int relevance) {
int degree = 1, i = relevance, subdegree;
while ((subdegree = 5 * degree) <= i) {
i -= subdegree;
@@ -109,59 +117,90 @@ public class MapleSessionCoordinator {
return 3600000 * (baseTime + subdegreeTime);
}
- private static void updateAccessAccount(Connection con, String remoteHost, int accountId, int loginRelevance) throws SQLException {
- java.sql.Timestamp nextTimestamp = new java.sql.Timestamp(Server.getInstance().getCurrentTime() + ipExpirationUpdate(loginRelevance));
+ private static void updateAccessAccount(Connection con, String remoteHwid, int accountId, int loginRelevance) throws SQLException {
+ java.sql.Timestamp nextTimestamp = new java.sql.Timestamp(Server.getInstance().getCurrentTime() + hwidExpirationUpdate(loginRelevance));
if(loginRelevance < Byte.MAX_VALUE) {
loginRelevance++;
}
- try (PreparedStatement ps = con.prepareStatement("UPDATE ipaccounts SET relevance = ?, expiresat = ? WHERE accountid = ? AND ip LIKE ?")) {
+ try (PreparedStatement ps = con.prepareStatement("UPDATE hwidaccounts SET relevance = ?, expiresat = ? WHERE accountid = ? AND hwid LIKE ?")) {
ps.setInt(1, loginRelevance);
ps.setTimestamp(2, nextTimestamp);
ps.setInt(3, accountId);
- ps.setString(4, remoteHost);
+ ps.setString(4, remoteHwid);
ps.executeUpdate();
}
}
- private static void registerAccessAccount(Connection con, String remoteHost, int accountId) throws SQLException {
- try (PreparedStatement ps = con.prepareStatement("INSERT INTO ipaccounts (accountid, ip, expiresat) VALUES (?, ?, ?)")) {
+ private static void registerAccessAccount(Connection con, String remoteHwid, int accountId) throws SQLException {
+ try (PreparedStatement ps = con.prepareStatement("INSERT INTO hwidaccounts (accountid, hwid, expiresat) VALUES (?, ?, ?)")) {
ps.setInt(1, accountId);
- ps.setString(2, remoteHost);
- ps.setTimestamp(3, new java.sql.Timestamp(Server.getInstance().getCurrentTime() + ipExpirationUpdate(0)));
+ ps.setString(2, remoteHwid);
+ ps.setTimestamp(3, new java.sql.Timestamp(Server.getInstance().getCurrentTime() + hwidExpirationUpdate(0)));
ps.executeUpdate();
}
}
- private static boolean attemptAccessAccount(String remoteHost, int accountId, boolean routineCheck) {
+ private static boolean associateHwidAccountIfAbsent(String remoteHwid, int accountId) {
try {
Connection con = DatabaseConnection.getConnection();
- int ipCount = 0;
+ int hwidCount = 0;
- try (PreparedStatement ps = con.prepareStatement("SELECT SQL_CACHE * FROM ipaccounts WHERE accountid = ?")) {
+ try (PreparedStatement ps = con.prepareStatement("SELECT SQL_CACHE hwid FROM hwidaccounts WHERE accountid = ?")) {
ps.setInt(1, accountId);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
- if (remoteHost.contentEquals(rs.getString("ip"))) {
+ String rsHwid = rs.getString("hwid");
+ if (rsHwid.contentEquals(remoteHwid)) {
+ return false;
+ }
+
+ hwidCount++;
+ }
+ }
+
+ if (hwidCount < ServerConstants.MAX_ALLOWED_ACCOUNT_HWID) {
+ registerAccessAccount(con, remoteHwid, accountId);
+ return true;
+ }
+ } finally {
+ con.close();
+ }
+ } catch (SQLException ex) {
+ ex.printStackTrace();
+ }
+
+ return false;
+ }
+
+ private static boolean attemptAccessAccount(String nibbleHwid, int accountId, boolean routineCheck) {
+ try {
+ Connection con = DatabaseConnection.getConnection();
+ int hwidCount = 0;
+
+ try (PreparedStatement ps = con.prepareStatement("SELECT SQL_CACHE * FROM hwidaccounts WHERE accountid = ?")) {
+ ps.setInt(1, accountId);
+ try (ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ String rsHwid = rs.getString("hwid");
+ if (rsHwid.endsWith(nibbleHwid)) {
if (!routineCheck) {
+ // better update HWID relevance as soon as the login is authenticated
+
int loginRelevance = rs.getInt("relevance");
- updateAccessAccount(con, remoteHost, accountId, loginRelevance);
+ updateAccessAccount(con, rsHwid, accountId, loginRelevance);
}
return true;
}
- ipCount++;
+ hwidCount++;
}
}
- if (ipCount < ServerConstants.MAX_ALLOWED_ACCOUNT_IP) {
- if (!routineCheck) {
- registerAccessAccount(con, remoteHost, accountId);
- }
-
+ if (hwidCount < ServerConstants.MAX_ALLOWED_ACCOUNT_HWID) {
return true;
}
} finally {
@@ -218,7 +257,14 @@ public class MapleSessionCoordinator {
}
try {
- if (onlineRemoteHosts.contains(remoteHost) || loginRemoteHosts.containsKey(remoteHost)) {
+ String knownHwid = cachedHostHwids.get(remoteHost);
+ if (knownHwid != null) {
+ if (onlineRemoteHwids.contains(knownHwid)) {
+ return false;
+ }
+ }
+
+ if (loginRemoteHosts.containsKey(remoteHost)) {
return false;
}
@@ -244,11 +290,16 @@ public class MapleSessionCoordinator {
lrh.remove(session);
if (lrh.isEmpty()) {
loginRemoteHosts.remove(remoteIp);
+
+ String nibbleHwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID);
+ if (nibbleHwid != null) {
+ onlineRemoteHwids.remove(nibbleHwid);
+ }
}
}
}
- public AntiMulticlientResult attemptSessionLogin(IoSession session, int accountId, boolean routineCheck) {
+ public AntiMulticlientResult attemptLoginSession(IoSession session, String nibbleHwid, int accountId, boolean routineCheck) {
if (!ServerConstants.DETERRED_MULTICLIENT) return AntiMulticlientResult.SUCCESS;
String remoteHost = getRemoteIp(session);
@@ -289,17 +340,18 @@ public class MapleSessionCoordinator {
}
if (!routineCheck) {
- if (onlineRemoteHosts.contains(remoteHost)) {
+ if (onlineRemoteHwids.contains(nibbleHwid)) {
return AntiMulticlientResult.REMOTE_LOGGEDIN;
}
- if (!attemptAccessAccount(remoteHost, accountId, routineCheck)) {
+ if (!attemptAccessAccount(nibbleHwid, accountId, routineCheck)) {
return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
}
- onlineRemoteHosts.add(remoteHost);
+ session.setAttribute(MapleClient.CLIENT_NIBBLEHWID, nibbleHwid);
+ onlineRemoteHwids.add(nibbleHwid);
} else {
- if (!attemptAccessAccount(remoteHost, accountId, routineCheck)) {
+ if (!attemptAccessAccount(nibbleHwid, accountId, routineCheck)) {
return AntiMulticlientResult.REMOTE_REACHED_LIMIT;
}
}
@@ -315,15 +367,99 @@ public class MapleSessionCoordinator {
}
}
- public void closeSession(IoSession session, Boolean immediately) {
- onlineRemoteHosts.remove(getRemoteIp(session));
- if (immediately != null) session.close(immediately);
+ public AntiMulticlientResult attemptGameSession(IoSession session, int accountId, String remoteHwid) {
+ if (!ServerConstants.DETERRED_MULTICLIENT) return AntiMulticlientResult.SUCCESS;
+
+ String remoteHost = getRemoteIp(session);
+ Lock lock = getCoodinatorLock(remoteHost);
+
+ try {
+ int tries = 0;
+ while (true) {
+ if (lock.tryLock()) {
+ try {
+ if (pooledRemoteHosts.contains(remoteHost)) {
+ return AntiMulticlientResult.REMOTE_PROCESSING;
+ }
+
+ pooledRemoteHosts.add(remoteHost);
+ } finally {
+ lock.unlock();
+ }
+
+ break;
+ } else {
+ if(tries == 2) {
+ return AntiMulticlientResult.COORDINATOR_ERROR;
+ }
+ tries++;
+
+ Thread.sleep(1777);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return AntiMulticlientResult.COORDINATOR_ERROR;
+ }
+
+ try {
+ String nibbleHwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID);
+ if (nibbleHwid != null) {
+ onlineRemoteHwids.remove(nibbleHwid);
+
+ if (remoteHwid.endsWith(nibbleHwid)) {
+ if (!onlineRemoteHwids.contains(remoteHwid)) {
+ // assumption: after a SUCCESSFUL login attempt, the incoming client WILL receive a new IoSession from the game server
+
+ // updated session CLIENT_HWID attribute will be set when the player log in the game
+ onlineRemoteHwids.add(remoteHwid);
+
+ cachedHostHwids.put(remoteHost, remoteHwid);
+ cachedHostTimeout.put(remoteHost, Server.getInstance().getCurrentTime() + 604800000); // 1 week-time entry
+
+ associateHwidAccountIfAbsent(remoteHwid, accountId);
+
+ return AntiMulticlientResult.SUCCESS;
+ } else {
+ return AntiMulticlientResult.REMOTE_LOGGEDIN;
+ }
+ } else {
+ return AntiMulticlientResult.REMOTE_NO_MATCH;
+ }
+ } else {
+ return AntiMulticlientResult.REMOTE_NO_MATCH;
+ }
+ } finally {
+ lock.lock();
+ try {
+ pooledRemoteHosts.remove(remoteHost);
+ } finally {
+ lock.unlock();
+ }
+ }
}
- public void runUpdateIpHistory() {
+ public void closeSession(IoSession session, Boolean immediately) {
+ String hwid = (String) session.removeAttribute(MapleClient.CLIENT_HWID);
+ onlineRemoteHwids.remove(hwid);
+
+ hwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID); // making sure to clean up calls to this function on login phase
+ onlineRemoteHwids.remove(hwid);
+
+ if (immediately != null) {
+ session.close(immediately);
+ }
+ }
+
+ public String getGameSessionHwid(IoSession session) {
+ String remoteHost = getRemoteIp(session);
+ return cachedHostHwids.get(remoteHost);
+ }
+
+ public void runUpdateHwidHistory() {
try {
Connection con = DatabaseConnection.getConnection();
- try (PreparedStatement ps = con.prepareStatement("DELETE FROM ipaccounts WHERE expiresat < CURRENT_TIMESTAMP")) {
+ try (PreparedStatement ps = con.prepareStatement("DELETE FROM hwidaccounts WHERE expiresat < CURRENT_TIMESTAMP")) {
ps.execute();
} finally {
con.close();
@@ -331,6 +467,21 @@ public class MapleSessionCoordinator {
} catch (SQLException ex) {
ex.printStackTrace();
}
+
+ long timeNow = Server.getInstance().getCurrentTime();
+ List toRemove = new LinkedList<>();
+ for (Entry cht : cachedHostTimeout.entrySet()) {
+ if (cht.getValue() < timeNow) {
+ toRemove.add(cht.getKey());
+ }
+ }
+
+ if (!toRemove.isEmpty()) {
+ for (String s : toRemove) {
+ cachedHostHwids.remove(s);
+ cachedHostTimeout.remove(s);
+ }
+ }
}
public void runUpdateLoginHistory() {
diff --git a/src/net/server/guild/MapleGuild.java b/src/net/server/guild/MapleGuild.java
index 6bd23b913d..5ba2321f70 100644
--- a/src/net/server/guild/MapleGuild.java
+++ b/src/net/server/guild/MapleGuild.java
@@ -23,6 +23,7 @@ package net.server.guild;
import client.MapleCharacter;
import client.MapleClient;
+import constants.ServerConstants;
import java.sql.Connection;
import java.sql.PreparedStatement;
@@ -752,6 +753,12 @@ public class MapleGuild {
}
public static int getIncreaseGuildCost(int size) {
- return 500000 * (size - 6) / 6;
+ int cost = ServerConstants.EXPAND_GUILD_BASE_COST + Math.max(0, (size - 15) / 5) * ServerConstants.EXPAND_GUILD_TIER_COST;
+
+ if (size > 30) {
+ return Math.min(ServerConstants.EXPAND_GUILD_MAX_COST, Math.max(cost, 5000000));
+ } else {
+ return cost;
+ }
}
}
diff --git a/src/net/server/handlers/login/CharSelectedHandler.java b/src/net/server/handlers/login/CharSelectedHandler.java
index 0601d93336..dd24f59873 100644
--- a/src/net/server/handlers/login/CharSelectedHandler.java
+++ b/src/net/server/handlers/login/CharSelectedHandler.java
@@ -27,28 +27,64 @@ import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler;
import net.server.Server;
+import net.server.coordinator.MapleSessionCoordinator;
+import net.server.coordinator.MapleSessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
+import org.apache.mina.core.session.IoSession;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
public final class CharSelectedHandler extends AbstractMaplePacketHandler {
+
+ private static int parseAntiMulticlientError(AntiMulticlientResult res) {
+ switch (res) {
+ case REMOTE_PROCESSING:
+ return 10;
+ case REMOTE_LOGGEDIN:
+ return 7;
+
+ case REMOTE_NO_MATCH:
+ return 17;
+
+ case COORDINATOR_ERROR:
+ return 8;
+
+ default:
+ return 9;
+ }
+ }
+
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
int charId = slea.readInt();
String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
+
+ if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) {
+ c.announce(MaplePacketCreator.getAfterLoginError(17));
+ return;
+ }
+
c.updateMacs(macs);
c.updateHWID(hwid);
+
+ IoSession session = c.getSession();
+ AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid);
+ if (res != AntiMulticlientResult.SUCCESS) {
+ c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
+ return;
+ }
+
if (c.hasBannedMac() || c.hasBannedHWID()) {
- c.getSession().close(true);
+ session.close(true);
return;
}
Server server = Server.getInstance();
if(!server.haveCharacterEntry(c.getAccID(), charId)) {
- c.getSession().close(true);
+ session.close(true);
return;
}
@@ -67,7 +103,7 @@ public final class CharSelectedHandler extends AbstractMaplePacketHandler {
server.unregisterLoginState(c);
c.updateLoginState(MapleClient.LOGIN_SERVER_TRANSITION);
- server.setCharacteridInTransition((InetSocketAddress) c.getSession().getRemoteAddress(), charId);
+ server.setCharacteridInTransition((InetSocketAddress) session.getRemoteAddress(), charId);
try {
c.announce(MaplePacketCreator.getServerIP(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]), charId));
diff --git a/src/net/server/handlers/login/CharSelectedWithPicHandler.java b/src/net/server/handlers/login/CharSelectedWithPicHandler.java
index b2bdcf7cb5..3d31ff1203 100644
--- a/src/net/server/handlers/login/CharSelectedWithPicHandler.java
+++ b/src/net/server/handlers/login/CharSelectedWithPicHandler.java
@@ -6,13 +6,35 @@ import java.net.UnknownHostException;
import net.AbstractMaplePacketHandler;
import net.server.Server;
+import net.server.coordinator.MapleSessionCoordinator;
+import net.server.coordinator.MapleSessionCoordinator.AntiMulticlientResult;
import net.server.world.World;
+import org.apache.mina.core.session.IoSession;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
import client.MapleClient;
public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler {
+ private static int parseAntiMulticlientError(AntiMulticlientResult res) {
+ switch (res) {
+ case REMOTE_PROCESSING:
+ return 10;
+
+ case REMOTE_LOGGEDIN:
+ return 7;
+
+ case REMOTE_NO_MATCH:
+ return 17;
+
+ case COORDINATOR_ERROR:
+ return 8;
+
+ default:
+ return 9;
+ }
+ }
+
@Override
public void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
String pic = slea.readMapleAsciiString();
@@ -20,8 +42,22 @@ public class CharSelectedWithPicHandler extends AbstractMaplePacketHandler {
String macs = slea.readMapleAsciiString();
String hwid = slea.readMapleAsciiString();
+
+ if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) {
+ c.announce(MaplePacketCreator.getAfterLoginError(17));
+ return;
+ }
+
c.updateMacs(macs);
c.updateHWID(hwid);
+
+ IoSession session = c.getSession();
+ AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid);
+ if (res != AntiMulticlientResult.SUCCESS) {
+ c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
+ return;
+ }
+
if (c.hasBannedMac() || c.hasBannedHWID()) {
c.getSession().close(true);
return;
diff --git a/src/net/server/handlers/login/LoginPasswordHandler.java b/src/net/server/handlers/login/LoginPasswordHandler.java
index a675cd68a3..1feada78e3 100644
--- a/src/net/server/handlers/login/LoginPasswordHandler.java
+++ b/src/net/server/handlers/login/LoginPasswordHandler.java
@@ -31,6 +31,7 @@ import net.MaplePacketHandler;
import net.server.Server;
import tools.BCrypt;
import tools.DatabaseConnection;
+import tools.HexTool;
import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
import client.MapleClient;
@@ -51,8 +52,10 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
String login = slea.readMapleAsciiString();
String pwd = slea.readMapleAsciiString();
c.setAccountName(login);
-
- int loginok = c.login(login, pwd);
+
+ slea.skip(6); // localhost masked the initial part with zeroes...
+ byte[] hwidNibbles = slea.read(4);
+ int loginok = c.login(login, pwd, HexTool.toCompressedString(hwidNibbles));
Connection con = null;
PreparedStatement ps = null;
@@ -63,8 +66,8 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
ps = con.prepareStatement("INSERT INTO accounts (name, password, birthday, tempban) VALUES (?, ?, ?, ?);", Statement.RETURN_GENERATED_KEYS); //Jayd: Added birthday, tempban
ps.setString(1, login);
ps.setString(2, BCrypt.hashpw(pwd, BCrypt.gensalt(12)));
- ps.setString(3, "2018-06-20"); //Jayd: was added to solve the MySQL 5.7 strict checking (birthday)
- ps.setString(4, "2018-06-20"); //Jayd: was added to solve the MySQL 5.7 strict checking (tempban)
+ ps.setString(3, "2018-06-20"); //Jayd's idea: was added to solve the MySQL 5.7 strict checking (birthday)
+ ps.setString(4, "2018-06-20"); //Jayd's idea: was added to solve the MySQL 5.7 strict checking (tempban)
ps.executeUpdate();
ResultSet rs = ps.getGeneratedKeys();
@@ -75,7 +78,7 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
e.printStackTrace();
} finally {
disposeSql(con, ps);
- loginok = c.login(login, pwd);
+ loginok = c.login(login, pwd, HexTool.toCompressedString(hwidNibbles));
}
}
@@ -100,7 +103,7 @@ public final class LoginPasswordHandler implements MaplePacketHandler {
}
Calendar tempban = c.getTempBanCalendar();
if (tempban != null) {
- if (tempban.getTimeInMillis() > System.currentTimeMillis()) {
+ if (tempban.getTimeInMillis() > Calendar.getInstance().getTimeInMillis()) {
c.announce(MaplePacketCreator.getTempBan(tempban.getTimeInMillis(), c.getGReason()));
return;
}
diff --git a/src/net/server/handlers/login/RegisterPicHandler.java b/src/net/server/handlers/login/RegisterPicHandler.java
index 3b42cf3b8b..016dd89527 100644
--- a/src/net/server/handlers/login/RegisterPicHandler.java
+++ b/src/net/server/handlers/login/RegisterPicHandler.java
@@ -10,18 +10,54 @@ import tools.MaplePacketCreator;
import tools.data.input.SeekableLittleEndianAccessor;
import client.MapleClient;
import java.net.InetSocketAddress;
+import net.server.coordinator.MapleSessionCoordinator;
+import net.server.coordinator.MapleSessionCoordinator.AntiMulticlientResult;
+import org.apache.mina.core.session.IoSession;
public final class RegisterPicHandler extends AbstractMaplePacketHandler {
+ private static int parseAntiMulticlientError(AntiMulticlientResult res) {
+ switch (res) {
+ case REMOTE_PROCESSING:
+ return 10;
+
+ case REMOTE_LOGGEDIN:
+ return 7;
+
+ case REMOTE_NO_MATCH:
+ return 17;
+
+ case COORDINATOR_ERROR:
+ return 8;
+
+ default:
+ return 9;
+ }
+ }
+
@Override
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
slea.readByte();
int charId = slea.readInt();
String macs = slea.readMapleAsciiString();
- String hwid = slea.readMapleAsciiString();
+ String hwid = slea.readMapleAsciiString();
+
+ if (!hwid.matches("[0-9A-F]{12}_[0-9A-F]{8}")) {
+ c.announce(MaplePacketCreator.getAfterLoginError(17));
+ return;
+ }
+
c.updateMacs(macs);
c.updateHWID(hwid);
+
+ IoSession session = c.getSession();
+ AntiMulticlientResult res = MapleSessionCoordinator.getInstance().attemptGameSession(session, c.getAccID(), hwid);
+ if (res != AntiMulticlientResult.SUCCESS) {
+ c.announce(MaplePacketCreator.getAfterLoginError(parseAntiMulticlientError(res)));
+ return;
+ }
+
if (c.hasBannedMac() || c.hasBannedHWID()) {
c.getSession().close(true);
return;
diff --git a/src/net/server/worker/EventRecallCoordinatorWorker.java b/src/net/server/worker/EventRecallCoordinatorWorker.java
new file mode 100644
index 0000000000..35a6517fc2
--- /dev/null
+++ b/src/net/server/worker/EventRecallCoordinatorWorker.java
@@ -0,0 +1,34 @@
+/*
+ This file is part of the HeavenMS MapleStory Server
+ Copyleft (L) 2016 - 2018 RonanLana
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation version 3 as published by
+ the Free Software Foundation. You may not use, modify or distribute
+ this program under any other version of the GNU Affero General Public
+ License.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+*/
+package net.server.worker;
+
+import net.server.coordinator.MapleEventRecallCoordinator;
+
+/**
+ *
+ * @author Ronan
+ */
+public class EventRecallCoordinatorWorker implements Runnable {
+
+ @Override
+ public void run() {
+ MapleEventRecallCoordinator.getInstance().manageEventInstances();
+ }
+}
diff --git a/src/net/server/worker/LoginCoordinatorWorker.java b/src/net/server/worker/LoginCoordinatorWorker.java
index 91bc2d9a17..0bb2345547 100644
--- a/src/net/server/worker/LoginCoordinatorWorker.java
+++ b/src/net/server/worker/LoginCoordinatorWorker.java
@@ -29,6 +29,6 @@ public class LoginCoordinatorWorker implements Runnable {
@Override
public void run() {
- MapleSessionCoordinator.getInstance().runUpdateIpHistory();
+ MapleSessionCoordinator.getInstance().runUpdateHwidHistory();
}
}
diff --git a/src/net/server/world/World.java b/src/net/server/world/World.java
index 69babeb30e..3baee5125d 100644
--- a/src/net/server/world/World.java
+++ b/src/net/server/world/World.java
@@ -27,6 +27,7 @@ import client.BuddyList.BuddyOperation;
import client.BuddylistEntry;
import client.MapleCharacter;
import client.MapleFamily;
+import constants.GameConstants;
import constants.ServerConstants;
import java.sql.Connection;
@@ -53,6 +54,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
import java.util.Set;
import java.util.HashSet;
+import java.util.PriorityQueue;
import java.util.concurrent.ScheduledFuture;
import scripting.event.EventInstanceManager;
@@ -122,6 +124,11 @@ public class World {
private MonitoredReentrantLock partyLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.WORLD_PARTY, true);
private Map owlSearched = new LinkedHashMap<>();
+ private List