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> cashItemBought = new ArrayList<>(9); + private final ReentrantReadWriteLock suggestLock = new MonitoredReentrantReadWriteLock(MonitoredLockType.WORLD_SUGGEST, true); + private ReadLock suggestRLock = suggestLock.readLock(); + private WriteLock suggestWLock = suggestLock.writeLock(); + private Map disabledServerMessages = new HashMap<>(); // reuse owl lock private MonitoredReentrantLock srvMessagesLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.WORLD_SRVMESSAGES); private ScheduledFuture srvMessagesSchedule; @@ -166,6 +173,10 @@ public class World { petUpdate = System.currentTimeMillis(); mountUpdate = petUpdate; + for (int i = 0; i < 9; i++) { + cashItemBought.add(new LinkedHashMap()); + } + TimerManager tman = TimerManager.getInstance(); petsSchedule = tman.register(new PetFullnessWorker(this), 60 * 1000, 60 * 1000); srvMessagesSchedule = tman.register(new ServerMessageWorker(this), 10 * 1000, 10 * 1000); @@ -1152,7 +1163,7 @@ public class World { } public void addOwlItemSearch(Integer itemid) { - srvMessagesLock.lock(); + suggestWLock.lock(); try { Integer cur = owlSearched.get(itemid); if(cur != null) { @@ -1161,16 +1172,16 @@ public class World { owlSearched.put(itemid, 1); } } finally { - srvMessagesLock.unlock(); + suggestWLock.unlock(); } } public List> getOwlSearchedItems() { - if(ServerConstants.USE_ENFORCE_OWL_SUGGESTIONS) { + if(ServerConstants.USE_ENFORCE_ITEM_SUGGESTION) { return new ArrayList<>(0); } - srvMessagesLock.lock(); + suggestRLock.lock(); try { List> searchCounts = new ArrayList<>(owlSearched.size()); @@ -1180,10 +1191,108 @@ public class World { return searchCounts; } finally { - srvMessagesLock.unlock(); + suggestRLock.unlock(); } } + public void addCashItemBought(Integer snid) { + suggestWLock.lock(); + try { + Map tabItemBought = cashItemBought.get(snid / 10000000); + + Integer cur = tabItemBought.get(snid); + if (cur != null) { + tabItemBought.put(snid, cur + 1); + } else { + tabItemBought.put(snid, 1); + } + } finally { + suggestWLock.unlock(); + } + } + + private List>> getBoughtCashItems() { + if (ServerConstants.USE_ENFORCE_ITEM_SUGGESTION) { + return new ArrayList<>(0); + } + + suggestRLock.lock(); + try { + List>> boughtCounts = new ArrayList<>(cashItemBought.size()); + + for (Map tab : cashItemBought) { + List> tabItems = new LinkedList<>(); + boughtCounts.add(tabItems); + + for (Entry e : tab.entrySet()) { + tabItems.add(new Pair<>(e.getKey(), e.getValue())); + } + } + + return boughtCounts; + } finally { + suggestRLock.unlock(); + } + } + + private List getMostSellerOnTab(List> tabSellers) { + List tabLeaderboards; + + Comparator> comparator = new Comparator>() { // descending order + @Override + public int compare(Pair p1, Pair p2) { + return p2.getRight().compareTo(p1.getRight()); + } + }; + + PriorityQueue> queue = new PriorityQueue<>(Math.max(1, tabSellers.size()), comparator); + for(Pair p : tabSellers) { + queue.add(p); + } + + tabLeaderboards = new LinkedList<>(); + for(int i = 0; i < Math.min(tabSellers.size(), 5); i++) { + tabLeaderboards.add(queue.remove().getLeft()); + } + + return tabLeaderboards; + } + + public List> getMostSellerCashItems() { + List>> mostSellers = this.getBoughtCashItems(); + List> cashLeaderboards = new ArrayList<>(9); + List tabLeaderboards; + List allLeaderboards = null; + + for(List> tabSellers : mostSellers) { + if (tabSellers.size() < 5) { + if (allLeaderboards == null) { + List> allSellers = new LinkedList<>(); + for (List> tabItems : mostSellers) { + allSellers.addAll(tabItems); + } + + allLeaderboards = getMostSellerOnTab(allSellers); + } + + tabLeaderboards = new LinkedList<>(); + if (allLeaderboards.size() < 5) { + for(int i : GameConstants.CASH_DATA) { + tabLeaderboards.add(i); + } + } else { + tabLeaderboards.addAll(allLeaderboards); + } + } else { + tabLeaderboards = getMostSellerOnTab(tabSellers); + } + + cashLeaderboards.add(tabLeaderboards); + } + + return cashLeaderboards; + } + public void registerPetHunger(MapleCharacter chr, byte petSlot) { if(chr.isGM() && ServerConstants.GM_PETS_NEVER_HUNGRY || ServerConstants.PETS_NEVER_HUNGRY) { return; diff --git a/src/scripting/AbstractPlayerInteraction.java b/src/scripting/AbstractPlayerInteraction.java index d1f70dc879..919c447d71 100644 --- a/src/scripting/AbstractPlayerInteraction.java +++ b/src/scripting/AbstractPlayerInteraction.java @@ -54,6 +54,7 @@ import tools.MaplePacketCreator; import client.MapleCharacter; import client.MapleClient; import client.MapleQuestStatus; +import client.MapleStat; import client.SkillFactory; import client.inventory.Equip; import client.inventory.Item; @@ -581,8 +582,7 @@ public class AbstractPlayerInteraction { } public void gainFame(int delta) { - c.getPlayer().addFame(delta); - c.announce(MaplePacketCreator.getShowFameGain(delta)); + getPlayer().gainFame(delta); } public void changeMusic(String songName) { diff --git a/src/scripting/event/EventInstanceManager.java b/src/scripting/event/EventInstanceManager.java index dd5e2a4462..5af9606cc0 100644 --- a/src/scripting/event/EventInstanceManager.java +++ b/src/scripting/event/EventInstanceManager.java @@ -65,6 +65,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import java.util.logging.Level; import java.util.logging.Logger; +import net.server.coordinator.MapleEventRecallCoordinator; import scripting.AbstractPlayerInteraction; import server.MapleItemInformationProvider; import server.life.MapleLifeFactory; @@ -599,6 +600,8 @@ public class EventInstanceManager { } catch (ScriptException | NoSuchMethodException ex) { ex.printStackTrace(); } + + MapleEventRecallCoordinator.getInstance().storeEventInstance(chr.getId(), this); } /** @@ -862,7 +865,7 @@ public class EventInstanceManager { } public void clearPQ() { - try { + try { sL.lock(); try { em.getIv().invokeFunction("clearPQ", this); @@ -1157,6 +1160,10 @@ public class EventInstanceManager { public final void setEventCleared() { eventCleared = true; + for (MapleCharacter chr : getPlayers()) { + chr.awardQuestPoint(ServerConstants.QUEST_POINT_PER_EVENT_CLEAR); + } + sL.lock(); try { em.disposeInstance(name); @@ -1171,6 +1178,10 @@ public class EventInstanceManager { return eventCleared; } + public final boolean isEventDisposed() { + return disposed; + } + private boolean isEventTeamLeaderOn() { for(MapleCharacter chr: getPlayers()) { if(chr.getId() == getLeaderId()) return true; diff --git a/src/scripting/npc/NPCConversationManager.java b/src/scripting/npc/NPCConversationManager.java index 69f59294e0..c9fa8f4053 100644 --- a/src/scripting/npc/NPCConversationManager.java +++ b/src/scripting/npc/NPCConversationManager.java @@ -285,6 +285,7 @@ public class NPCConversationManager extends AbstractPlayerInteraction { return getPlayer().getLevel(); } + @Override public void showEffect(String effect) { getPlayer().getMap().broadcastMessage(MaplePacketCreator.environmentChange(effect, 3)); } diff --git a/src/scripting/quest/QuestScriptManager.java b/src/scripting/quest/QuestScriptManager.java index 46450f7ea9..7b94add557 100644 --- a/src/scripting/quest/QuestScriptManager.java +++ b/src/scripting/quest/QuestScriptManager.java @@ -42,7 +42,7 @@ public class QuestScriptManager extends AbstractScriptManager { private static QuestScriptManager instance = new QuestScriptManager(); - public synchronized static QuestScriptManager getInstance() { + public static QuestScriptManager getInstance() { return instance; } diff --git a/src/scripting/reactor/ReactorScriptManager.java b/src/scripting/reactor/ReactorScriptManager.java index d3b7f36cb4..f10395768b 100644 --- a/src/scripting/reactor/ReactorScriptManager.java +++ b/src/scripting/reactor/ReactorScriptManager.java @@ -44,7 +44,7 @@ public class ReactorScriptManager extends AbstractScriptManager { private static ReactorScriptManager instance = new ReactorScriptManager(); - public synchronized static ReactorScriptManager getInstance() { + public static ReactorScriptManager getInstance() { return instance; } diff --git a/src/server/CashShop.java b/src/server/CashShop.java index 9ecb721ba6..d32991dc09 100644 --- a/src/server/CashShop.java +++ b/src/server/CashShop.java @@ -32,6 +32,8 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.locks.Lock; + +import net.server.Server; import net.server.audit.locks.factory.MonitoredReentrantLockFactory; import provider.MapleData; @@ -107,18 +109,18 @@ public class CashShop { if (ItemConstants.EXPIRING_ITEMS) { if(period == 1) { if(itemId == 5211048 || itemId == 5360042) { // 4 Hour 2X coupons, the period is 1, but we don't want them to last a day. - item.setExpiration(System.currentTimeMillis() + (1000 * 60 * 60 * 4)); + item.setExpiration(Server.getInstance().getCurrentTime() + (1000 * 60 * 60 * 4)); /* } else if(itemId == 5211047 || itemId == 5360014) { // 3 Hour 2X coupons, unused as of now - item.setExpiration(System.currentTimeMillis() + (1000 * 60 * 60 * 3)); + item.setExpiration(Server.getInstance().getCurrentTime() + (1000 * 60 * 60 * 3)); */ } else if(itemId == 5211060) { // 2 Hour 3X coupons. - item.setExpiration(System.currentTimeMillis() + (1000 * 60 * 60 * 2)); + item.setExpiration(Server.getInstance().getCurrentTime() + (1000 * 60 * 60 * 2)); } else { - item.setExpiration(System.currentTimeMillis() + (1000 * 60 * 60 * 24)); + item.setExpiration(Server.getInstance().getCurrentTime() + (1000 * 60 * 60 * 24)); } } else { - item.setExpiration(System.currentTimeMillis() + (1000 * 60 * 60 * 24 * period)); + item.setExpiration(Server.getInstance().getCurrentTime() + (1000 * 60 * 60 * 24 * period)); } } @@ -353,6 +355,11 @@ public class CashShop { break; } } + + public void gainCash(int type, CashItem buyItem, int world) { + gainCash(type, -buyItem.getPrice()); + if(!ServerConstants.USE_ENFORCE_ITEM_SUGGESTION) Server.getInstance().getWorld(world).addCashItemBought(buyItem.getSN()); + } public boolean isOpened() { return opened; diff --git a/src/server/MapleItemInformationProvider.java b/src/server/MapleItemInformationProvider.java index 56642c9d81..e79eba08b1 100644 --- a/src/server/MapleItemInformationProvider.java +++ b/src/server/MapleItemInformationProvider.java @@ -97,6 +97,8 @@ public class MapleItemInformationProvider { protected Map itemEffects = new HashMap<>(); protected Map> equipStatsCache = new HashMap<>(); protected Map equipCache = new HashMap<>(); + protected Map equipLevelInfoCache = new HashMap<>(); + protected Map equipMaxLevelCache = new HashMap<>(); protected Map wholePriceCache = new HashMap<>(); protected Map unitPriceCache = new HashMap<>(); protected Map projectileWatkCache = new HashMap<>(); @@ -1217,7 +1219,7 @@ public class MapleItemInformationProvider { return ret; } - public boolean isDropRestricted(int itemId) { + public boolean isLootRestricted(int itemId) { if (dropRestrictionCache.containsKey(itemId)) { return dropRestrictionCache.get(itemId); } @@ -1227,16 +1229,17 @@ public class MapleItemInformationProvider { MapleData data = getItemData(itemId); bRestricted = MapleDataTool.getIntConvert("info/tradeBlock", data, 0) == 1; if (!bRestricted) { - bRestricted = MapleDataTool.getIntConvert("info/accountSharable", data, 0) == 1; - } - if (!bRestricted) { - bRestricted = MapleDataTool.getIntConvert("info/quest", data, 0) == 1; + bRestricted = MapleDataTool.getIntConvert("info/accountSharable", data, 0) == 1; } } dropRestrictionCache.put(itemId, bRestricted); return bRestricted; } + + public boolean isDropRestricted(int itemId) { + return isLootRestricted(itemId) || isQuestItem(itemId); + } public boolean isPickupRestricted(int itemId) { if (pickupRestrictionCache.containsKey(itemId)) { @@ -1674,24 +1677,72 @@ public class MapleItemInformationProvider { return true; } - public ArrayList> getItemDataByName(String name) - { + public ArrayList> getItemDataByName(String name) { ArrayList> ret = new ArrayList<>(); - for (Pair itemPair : MapleItemInformationProvider.getInstance().getAllItems()) { - if (itemPair.getRight().toLowerCase().contains(name.toLowerCase())) { - ret.add(itemPair); - } - } - return ret; + for (Pair itemPair : MapleItemInformationProvider.getInstance().getAllItems()) { + if (itemPair.getRight().toLowerCase().contains(name.toLowerCase())) { + ret.add(itemPair); + } + } + return ret; } + private MapleData getEquipLevelInfo(int itemId) { + MapleData equipLevelData = equipLevelInfoCache.get(itemId); + if (equipLevelData == null) { + if (equipLevelInfoCache.containsKey(itemId)) return null; + + MapleData iData = getItemData(itemId); + if (iData != null) { + MapleData data = iData.getChildByPath("info/level"); + if (data != null) { + equipLevelData = data.getChildByPath("info"); + } + } + + equipLevelInfoCache.put(itemId, equipLevelData); + } + + return equipLevelData; + } + + public int getEquipLevel(int itemId, boolean getMaxLevel) { + Integer eqLevel = equipMaxLevelCache.get(itemId); + if (eqLevel == null) { + eqLevel = 1; // greater than 1 means that it was supposed to levelup on GMS + + MapleData data = getEquipLevelInfo(itemId); + if (data != null) { + if (getMaxLevel) { + int curLevel = 1; + + while (true) { + MapleData data2 = data.getChildByPath(Integer.toString(curLevel)); + if (data2 == null || data2.getChildren().size() <= 1) { + eqLevel = curLevel; + equipMaxLevelCache.put(itemId, eqLevel); + break; + } + + curLevel++; + } + } else { + MapleData data2 = data.getChildByPath("1"); + if (data2 != null && data2.getChildren().size() > 1) { + eqLevel = 2; + } + } + } + } + + return eqLevel; + } + public List> getItemLevelupStats(int itemId, int level) { List> list = new LinkedList<>(); - MapleData data = getItemData(itemId); - MapleData data1 = data.getChildByPath("info").getChildByPath("level"); - - if (data1 != null) { - MapleData data2 = data1.getChildByPath("info").getChildByPath(Integer.toString(level)); + MapleData data = getEquipLevelInfo(itemId); + if (data != null) { + MapleData data2 = data.getChildByPath(Integer.toString(level)); if (data2 != null) { for (MapleData da : data2.getChildren()) { if (Math.random() < 0.9) { diff --git a/src/server/MapleStatEffect.java b/src/server/MapleStatEffect.java index 5576be91f8..6f41d68726 100644 --- a/src/server/MapleStatEffect.java +++ b/src/server/MapleStatEffect.java @@ -30,7 +30,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import net.server.world.MaplePartyCharacter; +import net.server.Server; import provider.MapleData; import provider.MapleDataTool; import server.life.MapleMonster; @@ -997,11 +997,15 @@ public class MapleStatEffect { return bounds; } + public int getBuffLocalDuration() { + return !ServerConstants.USE_BUFF_EVERLASTING ? duration : Integer.MAX_VALUE; + } + public void silentApplyBuff(MapleCharacter chr, long localStartTime) { - int localDuration = duration; + int localDuration = getBuffLocalDuration(); localDuration = alchemistModifyVal(chr, localDuration, false); //CancelEffectAction cancelAction = new CancelEffectAction(chr, this, starttime); - //ScheduledFuture schedule = TimerManager.getInstance().schedule(cancelAction, ((starttime + localDuration) - System.currentTimeMillis())); + //ScheduledFuture schedule = TimerManager.getInstance().schedule(cancelAction, ((starttime + localDuration) - Server.getInstance().getCurrentTime())); chr.registerEffect(this, localStartTime, localStartTime + localDuration, true); SummonMovementType summonMovementType = getSummonMovementType(); @@ -1021,17 +1025,17 @@ public class MapleStatEffect { final List> stat = Collections.singletonList(new Pair<>(MapleBuffStat.ARAN_COMBO, combo)); applyto.announce(MaplePacketCreator.giveBuff(sourceid, 99999, stat)); - final long starttime = System.currentTimeMillis(); + final long starttime = Server.getInstance().getCurrentTime(); // final CancelEffectAction cancelAction = new CancelEffectAction(applyto, this, starttime); -// final ScheduledFuture schedule = TimerManager.getInstance().schedule(cancelAction, ((starttime + 99999) - System.currentTimeMillis())); +// final ScheduledFuture schedule = TimerManager.getInstance().schedule(cancelAction, ((starttime + 99999) - Server.getInstance().getCurrentTime())); applyto.registerEffect(this, starttime, Long.MAX_VALUE, false); } public void updateBuffEffect(MapleCharacter target, List> activeStats, long starttime) { - int localDuration = duration; + int localDuration = getBuffLocalDuration(); localDuration = alchemistModifyVal(target, localDuration, false); - long leftDuration = (starttime + localDuration) - System.currentTimeMillis(); + long leftDuration = (starttime + localDuration) - Server.getInstance().getCurrentTime(); if(leftDuration > 0) { target.announce(MaplePacketCreator.giveBuff((skill ? sourceid : -sourceid), (int)leftDuration, activeStats)); } @@ -1043,7 +1047,7 @@ public class MapleStatEffect { } List> localstatups = statups; - int localDuration = duration; + int localDuration = getBuffLocalDuration(); int localsourceid = sourceid; int seconds = localDuration / 1000; MapleMount givemount = null; @@ -1155,7 +1159,7 @@ public class MapleStatEffect { } } - long starttime = System.currentTimeMillis(); + long starttime = Server.getInstance().getCurrentTime(); //CancelEffectAction cancelAction = new CancelEffectAction(applyto, this, starttime); //ScheduledFuture schedule = TimerManager.getInstance().schedule(cancelAction, localDuration); applyto.registerEffect(this, starttime, starttime + localDuration, false); diff --git a/src/server/life/MobSkill.java b/src/server/life/MobSkill.java index 384e678e55..42ee5fc3a3 100644 --- a/src/server/life/MobSkill.java +++ b/src/server/life/MobSkill.java @@ -32,7 +32,6 @@ import client.status.MonsterStatus; import constants.GameConstants; import java.util.LinkedList; import java.util.Map; -import server.TimerManager; import tools.Randomizer; import server.maps.MapleMapObject; import server.maps.MapleMapObjectType; diff --git a/src/server/loot/MapleLootInventory.java b/src/server/loot/MapleLootInventory.java index 857e18ff70..c005342cb4 100644 --- a/src/server/loot/MapleLootInventory.java +++ b/src/server/loot/MapleLootInventory.java @@ -48,9 +48,9 @@ public class MapleLootInventory { } } - public boolean hasItem(int itemid, int quantity) { + public int hasItem(int itemid, int quantity) { Integer itemQty = items.get(itemid); - return itemQty != null && itemQty >= quantity; + return itemQty == null ? 0 : itemQty >= quantity ? 2 : itemQty > 0 ? 1 : 0; } } diff --git a/src/server/loot/MapleLootManager.java b/src/server/loot/MapleLootManager.java index 0c39bffb56..f8686dedd8 100644 --- a/src/server/loot/MapleLootManager.java +++ b/src/server/loot/MapleLootManager.java @@ -34,25 +34,30 @@ import server.quest.MapleQuest; */ public class MapleLootManager { - private static boolean needQuestItem(MonsterDropEntry dropEntry, MapleQuest quest, MapleCharacter chr, MapleLootInventory chrInv) { - if (chr.getQuestStatus(dropEntry.questid) != 1) return false; - return !chrInv.hasItem(dropEntry.itemId, quest.getItemAmountNeeded(dropEntry.itemId)); - } - private static boolean isRelevantDrop(MonsterDropEntry dropEntry, List partyMembers, List partyInv) { MapleQuest quest = MapleQuest.getInstance(dropEntry.questid); - boolean restricted = MapleItemInformationProvider.getInstance().isDropRestricted(dropEntry.itemId); + int qItemAmount = quest != null ? quest.getItemAmountNeeded(dropEntry.itemId) : 0; + boolean restricted = MapleItemInformationProvider.getInstance().isLootRestricted(dropEntry.itemId); for (int i = 0; i < partyMembers.size(); i++) { MapleLootInventory chrInv = partyInv.get(i); - if(restricted && chrInv.hasItem(dropEntry.itemId, 1)) { + if (dropEntry.questid > 0) { + if (partyMembers.get(i).getQuestStatus(dropEntry.questid) != 1) { + continue; + } + + int qItemStatus = chrInv.hasItem(dropEntry.itemId, qItemAmount); + if (qItemStatus == 2) { + continue; + } else if (restricted && qItemStatus == 1) { + continue; + } + } else if (restricted && chrInv.hasItem(dropEntry.itemId, 1) > 0) { continue; } - if(dropEntry.questid <= 0 || needQuestItem(dropEntry, quest, partyMembers.get(i), chrInv)) { - return true; - } + return true; } return false; diff --git a/src/server/maps/MapleHiredMerchant.java b/src/server/maps/MapleHiredMerchant.java index dd0819f966..1626ba0d5f 100644 --- a/src/server/maps/MapleHiredMerchant.java +++ b/src/server/maps/MapleHiredMerchant.java @@ -242,7 +242,7 @@ public class MapleHiredMerchant extends AbstractMapleMapObject { if (c.getPlayer().getMeso() >= price) { if (canBuy(c, newItem)) { c.getPlayer().gainMeso(-price, false); - if(ServerConstants.USE_ANNOUNCE_SHOPITEMSOLD) announceItemSold(newItem, price); // idea thanks to vcoc + if(ServerConstants.USE_ANNOUNCE_SHOPITEMSOLD) announceItemSold(newItem, price); // idea thanks to Vcoc synchronized (sold) { sold.add(new SoldItem(c.getPlayer().getName(), pItem.getItem().getItemId(), newItem.getQuantity(), price)); diff --git a/src/server/maps/MapleMap.java b/src/server/maps/MapleMap.java index 14f55e3580..e2225dff96 100644 --- a/src/server/maps/MapleMap.java +++ b/src/server/maps/MapleMap.java @@ -1144,11 +1144,11 @@ public class MapleMap { return character; } - public List getPlayersInRange(Rectangle box, List chrList) { + public List getPlayersInRange(Rectangle box, List targets) { List character = new LinkedList<>(); chrRLock.lock(); try { - for (MapleCharacter chr : chrList) { + for (MapleCharacter chr : targets) { if (characters.contains(chr)) { if (box.contains(chr.getPosition())) { character.add(chr); diff --git a/src/server/quest/MapleQuest.java b/src/server/quest/MapleQuest.java index 30895ffa6e..6a99916b96 100644 --- a/src/server/quest/MapleQuest.java +++ b/src/server/quest/MapleQuest.java @@ -227,7 +227,7 @@ public class MapleQuest { if(!repeatable) return false; IntervalRequirement ir = (IntervalRequirement) startReqs.get(MapleQuestRequirementType.INTERVAL); - return ir.getInterval() < ServerConstants.FAME_GAIN_MIN_HOUR_INTERVAL * 60 * 60 * 1000; + return ir.getInterval() < ServerConstants.QUEST_POINT_REPEATABLE_INTERVAL * 60 * 60 * 1000; } public boolean canStartWithoutRequirements(MapleCharacter c) { @@ -369,12 +369,11 @@ public class MapleQuest { public int getItemAmountNeeded(int itemid) { MapleQuestRequirement req = completeReqs.get(MapleQuestRequirementType.ITEM); - if(req == null) - return 0; + if(req == null) + return 0; - ItemRequirement ireq = (ItemRequirement) req; - - return ireq.getItemAmountNeeded(itemid); + ItemRequirement ireq = (ItemRequirement) req; + return ireq.getItemAmountNeeded(itemid); } public int getMobAmountNeeded(int mid) { diff --git a/src/server/quest/actions/FameAction.java b/src/server/quest/actions/FameAction.java index 6d7160a71c..aee2b04298 100644 --- a/src/server/quest/actions/FameAction.java +++ b/src/server/quest/actions/FameAction.java @@ -50,8 +50,6 @@ public class FameAction extends MapleQuestAction { @Override public void run(MapleCharacter chr, Integer extSelection) { - chr.addFame(fame); - chr.updateSingleStat(MapleStat.FAME, chr.getFame()); - chr.announce(MaplePacketCreator.getShowFameGain(fame)); + chr.gainFame(fame); } } diff --git a/src/tools/HexTool.java b/src/tools/HexTool.java index 8cc0c8aa84..428baf3115 100644 --- a/src/tools/HexTool.java +++ b/src/tools/HexTool.java @@ -40,6 +40,14 @@ public class HexTool { } return hexed.substring(0, hexed.length() - 1); } + + public static String toCompressedString(byte[] bytes) { + StringBuilder hexed = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + hexed.append(toString(bytes[i])); + } + return hexed.substring(0, hexed.length()); + } public static byte[] getByteArrayFromHexString(String hex) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); diff --git a/src/tools/MaplePacketCreator.java b/src/tools/MaplePacketCreator.java index 419b6f9919..901737f51e 100644 --- a/src/tools/MaplePacketCreator.java +++ b/src/tools/MaplePacketCreator.java @@ -758,6 +758,8 @@ public class MaplePacketCreator { * * @param serverId * @param serverName The name of the server. + * @param flag + * @param eventmsg * @param channelLoad Load of the channel - 1200 seems to be max. * @return The server info packet. */ @@ -777,8 +779,11 @@ public class MaplePacketCreator { for (Channel ch : channelLoad) { mplew.writeMapleAsciiString(serverName + "-" + ch.getId()); mplew.writeInt(ch.getChannelCapacity()); - mplew.write(1); - mplew.writeShort(ch.getId() - 1); + + // thanks GabrielSin for this channel packet structure part + mplew.write(1);// nWorldID + mplew.write(ch.getId() - 1);// nChannelID + mplew.writeBool(false);// bAdultChannel } mplew.writeShort(0); return mplew.getPacket(); @@ -1042,8 +1047,8 @@ public class MaplePacketCreator { mplew.write(spawnPoint); mplew.writeShort(chr.getHp()); mplew.writeBool(false); - mplew.writeLong(getTime(System.currentTimeMillis())); - mplew.writeShort(0); + mplew.writeLong(getTime(Server.getInstance().getCurrentTime())); + mplew.writeInt(0); return mplew.getPacket(); } @@ -1059,8 +1064,8 @@ public class MaplePacketCreator { mplew.writeBool(true); mplew.writeInt(spawnPosition.x); // spawn position placement thanks to Arnah (Vertisy) mplew.writeInt(spawnPosition.y); - mplew.writeLong(getTime(System.currentTimeMillis())); - mplew.writeShort(0); + mplew.writeLong(getTime(Server.getInstance().getCurrentTime())); + mplew.writeInt(0); return mplew.getPacket(); } @@ -2461,27 +2466,27 @@ public class MaplePacketCreator { mplew.write(mod.getInventoryType()); mplew.writeShort(mod.getMode() == 2 ? mod.getOldPosition() : mod.getPosition()); switch (mod.getMode()) { - case 0: {//add item - addItemInfo(mplew, mod.getItem(), true); - break; - } - case 1: {//update quantity - mplew.writeShort(mod.getQuantity()); - break; - } - case 2: {//move - mplew.writeShort(mod.getPosition()); - if (mod.getPosition() < 0 || mod.getOldPosition() < 0) { - addMovement = mod.getOldPosition() < 0 ? 1 : 2; + case 0: {//add item + addItemInfo(mplew, mod.getItem(), true); + break; } - break; - } - case 3: {//remove - if (mod.getPosition() < 0) { - addMovement = 2; + case 1: {//update quantity + mplew.writeShort(mod.getQuantity()); + break; + } + case 2: {//move + mplew.writeShort(mod.getPosition()); + if (mod.getPosition() < 0 || mod.getOldPosition() < 0) { + addMovement = mod.getOldPosition() < 0 ? 1 : 2; + } + break; + } + case 3: {//remove + if (mod.getPosition() < 0) { + addMovement = 2; + } + break; } - break; - } } mod.clear(); } @@ -7527,27 +7532,16 @@ public class MaplePacketCreator { } mplew.skip(121); + List> mostSellers = c.getWorldServer().getMostSellerCashItems(); for (int i = 1; i <= 8; i++) { + List mostSellersTab = mostSellers.get(i); + for (int j = 0; j < 2; j++) { - mplew.writeInt(i); - mplew.writeInt(j); - mplew.writeInt(50200004); - - mplew.writeInt(i); - mplew.writeInt(j); - mplew.writeInt(50200069); - - mplew.writeInt(i); - mplew.writeInt(j); - mplew.writeInt(50200117); - - mplew.writeInt(i); - mplew.writeInt(j); - mplew.writeInt(50100008); - - mplew.writeInt(i); - mplew.writeInt(j); - mplew.writeInt(50000047); + for (Integer snid : mostSellersTab) { + mplew.writeInt(i); + mplew.writeInt(j); + mplew.writeInt(snid); + } } } diff --git a/tools/MapleInvalidItemIdFetcher/src/maplenoitemidfetcher/MapleNoItemIdFetcher.java b/tools/MapleInvalidItemIdFetcher/src/maplenoitemidfetcher/MapleNoItemIdFetcher.java index fa43c8062c..9f802c7cc7 100644 --- a/tools/MapleInvalidItemIdFetcher/src/maplenoitemidfetcher/MapleNoItemIdFetcher.java +++ b/tools/MapleInvalidItemIdFetcher/src/maplenoitemidfetcher/MapleNoItemIdFetcher.java @@ -43,7 +43,7 @@ import java.util.Set; * * This application finds inexistent itemids within the drop data from * the Maplestory database specified by the URL below. This program - * assumes all itemids haves at most 7 digits. + * assumes all itemids uses 7 digits. * * A file is generated listing all the inexistent ids. */ diff --git a/tools/MapleInvalidItemWithNoNameFetcher/nbbuild.xml b/tools/MapleInvalidItemWithNoNameFetcher/nbbuild.xml new file mode 100644 index 0000000000..22ac1635ba --- /dev/null +++ b/tools/MapleInvalidItemWithNoNameFetcher/nbbuild.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project MapleInvalidItemWithNoNameFetcher. + + + diff --git a/tools/MapleInvalidItemWithNoNameFetcher/src/maplenoitemnamefetcher/MapleNoItemNameFetcher.java b/tools/MapleInvalidItemWithNoNameFetcher/src/maplenoitemnamefetcher/MapleNoItemNameFetcher.java index 04656d8e56..5d1bb4528e 100644 --- a/tools/MapleInvalidItemWithNoNameFetcher/src/maplenoitemnamefetcher/MapleNoItemNameFetcher.java +++ b/tools/MapleInvalidItemWithNoNameFetcher/src/maplenoitemnamefetcher/MapleNoItemNameFetcher.java @@ -36,16 +36,16 @@ import provider.MapleDataDirectoryEntry; import provider.MapleDataFileEntry; import provider.MapleDataProvider; import provider.MapleDataProviderFactory; +import provider.MapleDataTool; /** * * @author RonanLana * - * This application finds inexistent itemids within the drop data from - * the Maplestory database specified by the URL below. This program - * assumes all itemids have at most 7 digits. - * - * A file is generated listing all the inexistent ids. + * This application finds itemids with inexistent name and description from + * within the server-side XMLs, then identify them on a report file along + * with a XML excerpt to be appended on the String.wz xml nodes. This program + * assumes all equipids are depicted using 8 digits and item using 7 digits. * * Estimated parse time: 2 minutes */ @@ -65,6 +65,9 @@ public class MapleNoItemNameFetcher { static Map itemsWithNoNameProperty = new HashMap<>(); static Set equipsWithNoCashProperty = new HashSet<>(); + static Map nameContentCache = new HashMap<>(); + static Map descContentCache = new HashMap<>(); + static ItemType curType = ItemType.UNDEF; private enum ItemType { @@ -78,11 +81,20 @@ public class MapleNoItemNameFetcher { private static void processStringSubdirectoryData(MapleData subdirData, String subdirPath) { for(MapleData md : subdirData.getChildren()) { try { - if(md.getChildByPath("name") != null) { - int itemId = Integer.parseInt(md.getName()); + MapleData nameData = md.getChildByPath("name"); + MapleData descData = md.getChildByPath("desc"); + + int itemId = Integer.parseInt(md.getName()); + if (nameData != null && descData != null) { itemsWithNoNameProperty.remove(itemId); } else { - System.out.println("Found itemid on String.wz with no name property: " + subdirPath + subdirData.getName() + "/" + md.getName()); + if (nameData != null) { + nameContentCache.put(itemId, MapleDataTool.getString("name", md)); + } else if (descData != null) { + descContentCache.put(itemId, MapleDataTool.getString("desc", md)); + } + + System.out.println("Found itemid on String.wz with no full property: " + subdirPath + subdirData.getName() + "/" + md.getName()); } } catch (NumberFormatException nfe) { System.out.println("Error reading string image: " + subdirPath + subdirData.getName() + "/" + md.getName()); @@ -422,10 +434,33 @@ public class MapleNoItemNameFetcher { printWriter.println(); } + private static String getMissingEquipName(int itemid) { + String s = nameContentCache.get(itemid); + if (s == null) { + s = "MISSING NAME " + itemid; + } + + return s; + } + + private static String getMissingEquipDesc(int itemid) { + String s = descContentCache.get(itemid); + if (s == null) { + s = "MISSING INFO " + itemid; + } + + return s; + } + private static void writeMissingEquipInfo(Integer itemid) { printWriter.println(" "); - printWriter.println(" "); - printWriter.println(" "); + + String s; + s = getMissingEquipName(itemid); + printWriter.println(" "); + + s = getMissingEquipDesc(itemid); + printWriter.println(" "); printWriter.println(" "); } diff --git a/tools/MapleQuestlineFetcher/nbbuild.xml b/tools/MapleQuestlineFetcher/nbbuild.xml new file mode 100644 index 0000000000..c18acb607b --- /dev/null +++ b/tools/MapleQuestlineFetcher/nbbuild.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project MapleQuestlineFetcher. + + + diff --git a/tools/MapleReactorDropFetcher/nbbuild.xml b/tools/MapleReactorDropFetcher/nbbuild.xml new file mode 100644 index 0000000000..24bf91c390 --- /dev/null +++ b/tools/MapleReactorDropFetcher/nbbuild.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project MapleReactorDropFetcher. + + + diff --git a/wz/Map.wz/Map/Map6/682000000.img.xml b/wz/Map.wz/Map/Map6/682000000.img.xml index bc8e735158..e32002a880 100644 --- a/wz/Map.wz/Map/Map6/682000000.img.xml +++ b/wz/Map.wz/Map/Map6/682000000.img.xml @@ -2389,7 +2389,7 @@ - + @@ -2397,7 +2397,7 @@ - + @@ -2405,7 +2405,7 @@ - + diff --git a/wz/Map.wz/Map/Map8/801000100.img.xml b/wz/Map.wz/Map/Map8/801000100.img.xml index 99b664745c..68246e6b78 100644 --- a/wz/Map.wz/Map/Map8/801000100.img.xml +++ b/wz/Map.wz/Map/Map8/801000100.img.xml @@ -315,7 +315,7 @@ - + diff --git a/wz/Map.wz/Map/Map8/801000200.img.xml b/wz/Map.wz/Map/Map8/801000200.img.xml index 4b5155068d..61d99cc2c1 100644 --- a/wz/Map.wz/Map/Map8/801000200.img.xml +++ b/wz/Map.wz/Map/Map8/801000200.img.xml @@ -315,7 +315,7 @@ - + diff --git a/wz/Map.wz/Map/Map8/801010000.img.xml b/wz/Map.wz/Map/Map8/801010000.img.xml index f8ef10710d..b66441c2ae 100644 --- a/wz/Map.wz/Map/Map8/801010000.img.xml +++ b/wz/Map.wz/Map/Map8/801010000.img.xml @@ -5116,7 +5116,7 @@ - + @@ -5124,7 +5124,7 @@ - + diff --git a/wz/Map.wz/Map/Map8/801030000.img.xml b/wz/Map.wz/Map/Map8/801030000.img.xml index 1098267088..5722feabea 100644 --- a/wz/Map.wz/Map/Map8/801030000.img.xml +++ b/wz/Map.wz/Map/Map8/801030000.img.xml @@ -7555,7 +7555,7 @@ - + diff --git a/wz/Map.wz/WorldMap/WorldMap.img.xml b/wz/Map.wz/WorldMap/WorldMap.img.xml index b7d1005a37..76e492d837 100644 --- a/wz/Map.wz/WorldMap/WorldMap.img.xml +++ b/wz/Map.wz/WorldMap/WorldMap.img.xml @@ -64,6 +64,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -202,6 +232,10 @@ + + + + @@ -527,9 +561,26 @@ + + + + + + + + + + + + + + + + + - + @@ -918,6 +969,19 @@ + + + + + + + + + + + + + @@ -948,6 +1012,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1061,5 +1229,15 @@ + + + + + + + + + + diff --git a/wz/Map.wz/WorldMap/WorldMap010.img.xml b/wz/Map.wz/WorldMap/WorldMap010.img.xml index a38e459bc6..deac0a7bff 100644 --- a/wz/Map.wz/WorldMap/WorldMap010.img.xml +++ b/wz/Map.wz/WorldMap/WorldMap010.img.xml @@ -51,15 +51,6 @@ - - - - - - - - - @@ -810,6 +801,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -842,5 +948,15 @@ + + + + + + + + + + diff --git a/wz/Map.wz/WorldMap/WorldMap014.img.xml b/wz/Map.wz/WorldMap/WorldMap014.img.xml new file mode 100644 index 0000000000..b7c69b34be --- /dev/null +++ b/wz/Map.wz/WorldMap/WorldMap014.img.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wz/Map.wz/WorldMap/WorldMap030.img.xml b/wz/Map.wz/WorldMap/WorldMap030.img.xml index 565fc1d071..61c82e4991 100644 --- a/wz/Map.wz/WorldMap/WorldMap030.img.xml +++ b/wz/Map.wz/WorldMap/WorldMap030.img.xml @@ -602,6 +602,29 @@ + + + + + + + + + + + + + + + + + + + + + + + @@ -614,5 +637,15 @@ + + + + + + + + + + diff --git a/wz/Map.wz/WorldMap/WorldMap031.img.xml b/wz/Map.wz/WorldMap/WorldMap031.img.xml index 3a62f3d12f..dd6ad0834a 100644 --- a/wz/Map.wz/WorldMap/WorldMap031.img.xml +++ b/wz/Map.wz/WorldMap/WorldMap031.img.xml @@ -1,7 +1,7 @@ - + diff --git a/wz/Map.wz/WorldMap/WorldMap032.img.xml b/wz/Map.wz/WorldMap/WorldMap032.img.xml new file mode 100644 index 0000000000..fc8ec1abc4 --- /dev/null +++ b/wz/Map.wz/WorldMap/WorldMap032.img.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wz/Map.wz/WorldMap/WorldMap140.img.xml b/wz/Map.wz/WorldMap/WorldMap140.img.xml index b2cee8d561..820911b1c1 100644 --- a/wz/Map.wz/WorldMap/WorldMap140.img.xml +++ b/wz/Map.wz/WorldMap/WorldMap140.img.xml @@ -2,6 +2,9 @@ + @@ -11,56 +14,136 @@ - - + - - - - - - - - - - - + + + + + + + + - - - + + + + + + + + + + + + + + + - - - + + + + + + + - - - + + + + + + + + + + - - - + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wz/Map.wz/WorldMap/WorldMap210.img.xml b/wz/Map.wz/WorldMap/WorldMap210.img.xml new file mode 100644 index 0000000000..eab16c7e42 --- /dev/null +++ b/wz/Map.wz/WorldMap/WorldMap210.img.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wz/Map.wz/WorldMap/WorldMap211.img.xml b/wz/Map.wz/WorldMap/WorldMap211.img.xml new file mode 100644 index 0000000000..f834a278bf --- /dev/null +++ b/wz/Map.wz/WorldMap/WorldMap211.img.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wz/Map.wz/WorldMap/WorldMap220.img.xml b/wz/Map.wz/WorldMap/WorldMap220.img.xml new file mode 100644 index 0000000000..2c0347973c --- /dev/null +++ b/wz/Map.wz/WorldMap/WorldMap220.img.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wz/Map.wz/WorldMap/WorldMap230.img.xml b/wz/Map.wz/WorldMap/WorldMap230.img.xml new file mode 100644 index 0000000000..57005242f2 --- /dev/null +++ b/wz/Map.wz/WorldMap/WorldMap230.img.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wz/Quest.wz/Act.img.xml b/wz/Quest.wz/Act.img.xml index b179c0e3b5..570a733968 100644 --- a/wz/Quest.wz/Act.img.xml +++ b/wz/Quest.wz/Act.img.xml @@ -55982,8 +55982,8 @@ - - + + diff --git a/wz/Quest.wz/Check.img.xml b/wz/Quest.wz/Check.img.xml index cc9c94ab8f..96f9965669 100644 --- a/wz/Quest.wz/Check.img.xml +++ b/wz/Quest.wz/Check.img.xml @@ -76673,8 +76673,8 @@ - - + + diff --git a/wz/Quest.wz/QuestInfo.img.xml b/wz/Quest.wz/QuestInfo.img.xml index 4adc2f7bdb..8300b50c21 100644 --- a/wz/Quest.wz/QuestInfo.img.xml +++ b/wz/Quest.wz/QuestInfo.img.xml @@ -10317,7 +10317,7 @@ Able to proceed to 'For the peace of Victoria Island...' as next quest - + diff --git a/wz/Quest.wz/Say.img.xml b/wz/Quest.wz/Say.img.xml index f1684c735d..f2407b85e8 100644 --- a/wz/Quest.wz/Say.img.xml +++ b/wz/Quest.wz/Say.img.xml @@ -64772,7 +64772,7 @@ - + diff --git a/wz/Skill.wz/312.img.xml b/wz/Skill.wz/312.img.xml index 59b875ef9f..01407d2f17 100644 --- a/wz/Skill.wz/312.img.xml +++ b/wz/Skill.wz/312.img.xml @@ -1039,12 +1039,16 @@ + + + + + + - - diff --git a/wz/String.wz/Map.img.xml b/wz/String.wz/Map.img.xml index 5b25c7751f..4de2792d2a 100644 --- a/wz/String.wz/Map.img.xml +++ b/wz/String.wz/Map.img.xml @@ -1560,20 +1560,20 @@ - - + + - - + + - - + + - - + +