diff --git a/README.md b/README.md index a5cb2bd5fd..d42d808d58 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,11 @@ Credits are to be given too to Nexon(Duh!), the original MapleSolaxia staff and other colaborators, as just some changes/patches on the game were applied by myself, in which some of them diverged from the original v83 patch contents. -Regarding distributability and usage of the code presented here: like it was before, this MapleStory server is open-source. By that, it is meant that anyone is free to install, use, modify and redistribute the contents, as long as there is no kind of commercial trading involved and the credits to the original creators are maintained within the codes. +Regarding distributability and usage of the code presented here: like it was before, this MapleStory server is open-source. By that, it is meant that anyone is **free to install, use, modify and redistribute the contents**, as long as there is **no kind of commercial trading involved** and the **credits to the original creators are maintained** within the codes. This is a NetBeans 8.0.2 Project, that MUST be built and run under JDK/JRE 7 in order to run properly. This means that it's easier to install the project via opening the server project folder inside NetBeans' IDE. Once installed, build this project on your machine and run the server using the "launch.bat" application. -In this project, many gameplay-wise issues generated from either the original WZ files and the server sources have been partially or completely solved. Considering the use of the provided edited WZ's and server-side wz.xml files should be of the greatest importance when dealing with this instance of private server, in order to perceive it at it's full potential. My opinion, though! Refer to "README_wzchanges.txt" for more information on what has been changed from Nexon's v83 WZ files. +In this project, many gameplay-wise issues generated from either the original WZ files and the server source have been partially or completely solved. Considering the use of the provided edited WZ's and server-side wz.xml files should be of the greatest importance when dealing with this instance of private server, in order to perceive it at it's full potential. My opinion, though! Refer to "README_wzchanges.txt" for more information on what has been changed from Nexon's v83 WZ files. The main objective of this project is to try as best as possible to recreate what once was the original MapleStory v83, while adding up some flavors that spices up the gameplay. In other words, aim to get the best of the MapleStory of that era. @@ -22,30 +22,44 @@ Client files & general tools: https://drive.google.com/drive/folders/0BzDsHSr-0V **Important note about localhosts**: these executables are red-flagged by antivirus tools as __potentially malicious softwares__, this happens due to the reverse engineering methods that were applied onto these software artifacts. Those depicted here have been put to use for years already and posed no harm so far, so they are soundly assumed to be safe. -Recommended localhost: https://hostr.co/MluJQNtzAnV9 +Recommended localhost: https://hostr.co/m2bVtnizCtmD **Change log:** + * Removed block on applying attack-based strengthening gems on non-weapon equipments. + + * Set a higher cap for SPEED. + + * Removed the AP assigning block for beginners below level 10. https://hostr.co/AHAHzneCti9B + + * Removed block on party for beginners level 10 or below. https://hostr.co/JZq53mMtToCz + + * Removed block on MTS entering in some maps, rendering the buyback option available. + * Removed "AP excess" popup and limited actions on Admin/MWLB, credits to kevintjuh93. * Removed "You've gained a level!" popup, credits to PrinceReborn. - * Removed caps for WATK, WDEF, MDEF, ACC, AVOID + * Removed caps for WATK, WDEF, MDEF, ACC, AVOID. - * 'n' problem fixed + * 'n' problem fixed. * Fraysa's https://hostr.co/gJbLZITRVHmv - * MapleSilver's starting on window-mode + * MapleSilver's starting on window-mode. --- -### Support us +### Development status -Feel free to __root for us__ on our endeavour at our Discord channel, or even actively **help us improve** the server by issuing pull requests with informative details about what's changing. +Status: __Released__. -Also, if you liked this project, please don't forget to __star__ the repo ;) . +HeavenMS development achieved an acceptable state-of-art and will get into a halt. A heartfelt thanks for everyone that contributed in some way for the progress of this server! -Discord: https://discord.gg/Q7wKxHX +Although development is halted, support for fixing features that were implemented here is still up. You can still actively help us improve the server by issuing pull requests with informative details about what's changing. + +If you liked this project, please don't forget to __star__ the repo ;) . + +Our Discord channel is still available on: https://discord.gg/Q7wKxHX ### Donation @@ -53,6 +67,10 @@ If you REALLY liked what you have seen on the project, please feel free to donat Paypal: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=3K8KVTWRLFBQ4 +### Note about public server + +HeavenMS staff has __no current intention__ to publicly open a server with this source, if that ever comes to happen this note will be lifted. __Don't be scammed!__ + --- ### Preparing the ambient diff --git a/docs/area_bosses/BossEvent.js b/docs/area_bosses/BossEvent.js new file mode 100644 index 0000000000..5cd1eb0047 --- /dev/null +++ b/docs/area_bosses/BossEvent.js @@ -0,0 +1,33 @@ +// @Author: Resinate + +var towns = new Array(800020120, 251010102, 260010201, 107000300, 200010300, 100040105, 100040106, 261030000, 110040000, 250010504, 240040401, 104000400, 222010310, 230040420, 230040420, 230020100, 105090310, 101030404, 250010304, 220050100, 220050000, 220050200, 221040301); +var spawns = new Array(6090002, 5220004, 3220001, 6220000, 8220000, 5220002, 5220002, 8220002, 5220001, 7220002, 8220003, 2220000, 7220001, 8510000, 8520000, 4220001, 8220008, 3220000, 7220000, 5220003, 5220003, 5220003, 6220001); +var x = new Array(560, 560, 645, 90, 208, 456, 474, -300, 200, 400, 0, 400, 0, 527, 138, 0, -626, 800, -300, -300, 0, 0, -4224); +var y = new Array(50, 50, 275, 119, 83, 278, 278, 180, 140, 540, 1125, 455, 33, -437, 138, 520, -604, 1280, 390, 1030, 1030, 1030, 776); +var mapObj; +var mobObj; + +function init() { + scheduleNew(); +} + +function scheduleNew() { + setupTask = em.schedule("start", 0); +} + +function cancelSchedule() { + if (setupTask != null) + setupTask.cancel(true); +} + +function start() { + var time = (Math.floor(Math.random() * 10) + 10) * (60 * 1000); + for(var i = 0; i < towns.length; i++) { + mapObj = em.getChannelServer().getMapFactory().getMap(towns[i]); + mobObj = Packages.server.life.MapleLifeFactory.getMonster(spawns[i]); + if(mapObj.getMonsterById(spawns[i]) == null) { + mapObj.spawnMonsterOnGroundBelow(mobObj, new Packages.java.awt.Point(x[i],y[i])); + } + } + em.schedule("start", time); +} \ No newline at end of file diff --git a/docs/feature_list.md b/docs/feature_list.md index ed391a4be6..88cb307184 100644 --- a/docs/feature_list.md +++ b/docs/feature_list.md @@ -22,7 +22,7 @@ Feature list: PQs: -* HPQ/KPQ/LPQ/LMPQ/OPQ/EllinPQ/PiratePQ/MagatiaPQ/HorntailPQ/AmoriaPQ/TreasurePQ. +* HPQ/KPQ/LPQ/LMPQ/OPQ/EllinPQ/PiratePQ/MagatiaPQ/HorntailPQ/AmoriaPQ/TreasurePQ/ElnathPQ. * CWKPQ as Expedition-based event. * Expeditions: Scarga/Horntail/Showa/Balrog/Zakum/Pinkbean. * GuildPQ + Guild queue with multi-lobby systems available. @@ -32,7 +32,7 @@ PQs: Skills: -* Some skills behaving oddly have been patched, such as Venomous Star/Stab and Mystic Doors. +* Some skills behaving oddly have been patched, such as Steal, Venomous Star/Stab and Mystic Doors. * Maker skill features properly developed. * Server is using heuristics to calculate fee costs for the Maker (errors sums up to 8k mesos, reagent errors stacks up comformant with it's level). * New skill: Chair Mastery (max lv 1) - Players having this passive skill can gain a significant boost of HP/MP recovery when sitting on a field/map chair. @@ -44,18 +44,23 @@ Quests: * Quest rewards according to jobs works properly. * Reward selection and randomed reward works properly. * Loads of quests have been patched. +* Meso requirement for starting/completing quests now must be met by the player. * Lots of job questlines (rewarding skills) have been patched/implemented. -* Enchanced rewarding system: checks for stacking opportunities on the inventory before checking for new slots. +* Enhanced rewarding system: checks for stacking opportunities on the inventory before checking for new slots. +* Improved the quest expiration system, one of the tweaks making the clock UI disappear when completing/expiring quests. +* Reviewed Aran questline. +* Reviewed 4th job skill questlines as a whole. * Complete overhaul on the 3rd job quiz (explorers), with all 40-question pool now made available. Player Social Network: * Guild and Alliance system fully functional. +* Implemented Marriage system from the ground-up (excluding character packet encoding parts that were already present, proper credits given throughout the source files). * Beginners can create and join a "beginner-only" party (characters up to level 10). * Enhanced synchronization on Player Shops and Hired Merchants. Transactions made are instantly informed to the owner. * Game minirooms such as match cards and omok now has semi-functional password system. * Item pickup cooldown on non-owned/non-partyowned items functional. -* Further improved the server's ranking system, to now display properly daily player ranking movement. +* Further improved the server's ranking system, now displaying properly daily player ranking movement. * Automated support for Player NPCs and Hall of Fame. Cash & Items: @@ -66,12 +71,14 @@ Cash & Items: * New scroll: antibanish. For use only in cases where bosses send a player back to town. * Inventory system properly checks for item slot free space and ownership. * Storage with "Arrange Items" feature functional. +* Spikes on shoes. * Vega's spell. * Owl of Minerva. * Pet item ignore. * New Year's card (New Year effect sometimes d/c's a player). * Kite. * Cash Shop Surprise. +* Maple Life. Monsters, Maps & Reactors: @@ -80,12 +87,18 @@ Monsters, Maps & Reactors: * Monsterbook displays drop data info conformant with the underlying DB (needs custom wz). See more on the MobBookUpdate feature. * Every skill/mastery book is now droppable by mobs. * Mobs now can drop more than one of the same equipment (number of possible drops defined at droptime, uses the minimum/maximum quantity fields on DB). +* Improved map bounding checks for item drop points, assuring most of the items dropped will be available to pickup inside the accessible map area. +* Limited item count on maps, smartly expiring oldest registered items, preventing potential item flooding. +* Implemented Zombify disease status. * Added Boss HP Bar for dozens of bosses (needs provided custom wz). * If multiple bosses are on the same area, client will prioritize Boss HP bar of the target of the player. * Boats, elevator and other travelling mechanics fully working. +* HP decreasing overtime on maps and mechanics to prevent them (consumables, equips) fully functional. * Crimson Balrog boat approaching visual effect made functional. * PQs, Taxis and other event-driven situations warps players at random spawnpoints, GMS-like. -* Some reactors (PQ bonus boxes) now sprays items on the map, instead of dropping everything at once. +* Some reactors (PQ bonus boxes) spraying items on the map, instead of dropping everything at once. +* Reactors pick items up smartly, checking for an option to pick up on many-items-nearby scenario. +* Updated many scripted portals not implementing SFX properly. * Updated Crimsonwood, World Tour, Nihal Desert and Neo City, enabling quest completion and game progression in these areas. * Giant Cake (anniversary-themed boss) drops Maple equipments, Maple scrolls, summoning bags and many more interesting items. @@ -110,16 +123,20 @@ Server potentials: * Enhanced auto-pot system: pet uses as many potions as necessary to reach the desired threshold. * Enhanced buff system: smartly checks for the best available buff effects to be active on the player. * Enhanced AP auto-assigner: exactly matches AP with the needed for the player's current level, surplus assigned to the primary attribute. +* Tweaked pet/mount hunger: calculations for fullness/tiredness takes active time of the subject into account. +* NPC crafters (equips, plates/jewels, etc) now won't take items freely if the requirement conditions are not properly met. * Pet item pickup now gives preference to player attacks rather than forcing attack disables when automatically picking up. * Channel capacity bar functional and world servers with max capacity checks. * Disease status are now visible for other players, even when changing maps. * Poison damage value are now visible for other players. * Mastery book announcer displays droppers of needed books of a player, by reading underlying DB. * Custom jail system (needs provided custom wz). +* Custom buyback system. * Delete Character (requires ENABLE_PIC activated). * Autosaver (periodically saves on DB current state of every player in-game). * Both fixed and randomized versions of HP/MP growth rate available, regarding player job (enable one at ServerConstants). Placeholder for HP/MP washing feature. * Reallocated mapobjectids utilization throughout the source, preventing issues such as "NPC disappearing mysteriously after some server time" from happening. +* Implemented old GMS statup mechanic for novices level 10 or below. Usage of the edited localhost is mandatory on this. * Accounts can be created automatically when trying to login on an inexistent account -- credits to shavit. * Usage of Bcrypt (up-to-date) as the main password hashing algorithm, replacing old SHA's -- credits to shavit. @@ -157,6 +174,7 @@ Project: * Fixed/added some missing packets for MoveEnvironment, summons and others. * Uncovered many Send/Recv opcodes throughout the source. * Reviewed many Java object aspects that needed concurrency protection. +* Usage of HikariCP to improve the DB connection management. * Protected many flaws with login management system. * Heavily reviewed future task management inside the project. Way less trivial schedules are spawned now, relieving task overload on the TimerManager. * ThreadTracker: embedded auditing tool for run-time deadlock scanning throughout the server source (relies heavily on memory usage, designed only for debugging purposes). @@ -174,5 +192,17 @@ Localhost: * Removed caps for MATK, WDEF, MDEF, ACC and AVOID. * Removed "AP excess" popup and "Admin/MWLB" action block, original credits to kevintjuh93. * Removed "You've gained a level!" popup, original credits to PrinceReborn. +* Removed "Cannot enter MTS from this map." popup on maps that blocks transitions (such change channel, CS/MTS), rendering the buyback option now available for all maps. +* Removed a check for players wishing to create/join a party being novices under level 10. +* Set a new high cap for SPEED. +* Removed the AP assign block for novices. +* Removed a block that would show up when trying to apply an attack gem on equipments that aren't weapons. + +Custom NPCs: + +* Agent E: Accessory crafter. +* Donation Box: Instant-sell NPC. +* Ace of Hearts & Coco: C. scroll crafter. +* Spiegelmann: Instant-ore refiner NPC. --------------------------- \ No newline at end of file diff --git a/docs/issues.txt b/docs/issues.txt index 0cac0338fb..312804d4f2 100644 --- a/docs/issues.txt +++ b/docs/issues.txt @@ -10,18 +10,22 @@ Known issues: - Passwords on minirooms are not encoded for players entering/logging into the map. - Some criticals (e.g. from Aran skills) will not show up as crit for other players. - Deadlocks may start appearing if the server stays online long enough with many players logged in. +- If there are multiple bosses that shows HPBar on the map, if a player hits more than one the HPBar may start flickering on the screen. --------------------------- --------------------------- Missing features list: - Miniroom tooltips (such as number of players in store/host awaiting game) not showing up properly. +- Change name/World transfer. +- Some pirate skills doesn't work for 3rd parties. +- Medal quests. --------------------------- --------------------------- ** Others ** -- Marriage - Family system +- MTS --------------------------- diff --git a/docs/mychanges_ptbr.txt b/docs/mychanges_ptbr.txt index 740d847814..c0ed894120 100644 --- a/docs/mychanges_ptbr.txt +++ b/docs/mychanges_ptbr.txt @@ -1,4 +1,5 @@ CUSTOM NPC SCRIPTS: + Spiegelmann -> 2042000 Coco -> 9000017 Agent E -> 9000036 @@ -842,7 +843,7 @@ Corrigido autoassigner para piratas somente preenchendo requisitos de gunslinger Mobs agora podem dropar mais de um equip de mesmo tipo, sistema utiliza quantidades min/max na tabela de drops. Nova ferramenta: MapleQuestItemCountFetcher. Localiza no Quest.wz possíveis locais onde um item deveria estar sendo tirado ao completar determinada quest, mas não o é (caso comum onde campo "count" não existe). -28 - 29 Março 2018, +28 - 30 Março 2018, Adicionado itens de pirata nas lojas de NPC em Singapore. Adicionado drop data para Flaming Racoon e Big Cloud Fox. Adicionado ganho de EXP em várias quests da região de Zipangu. @@ -850,6 +851,17 @@ Adicionado sistema de bonus para expedição de Showa, atingível se nenhum joga Nova ferramenta: MapleBossHpBarFetcher. Localiza no Mob.wz ids de mobs que possuem um boss hp bar mas não são labelados como "boss". Incrementado quiz de 3rd job, agora utilizando um pool de 40 perguntas com escolha arbitrária. +31 Março 2018, +Resolvido bug com diseases ao trocar de canais/entrar Cash Shop. + +05 Março 2018, +Corrigido Holy Symbol atuando descomunalmente para um jogador. +Corrigido sistema tirando mesos do jogador que tenta expandir guild com capacidade máxima alcançada. + +06 - 10 Março 2018, +Implementado todo o sistema de marriages (rings, relacionamentos, spouse chat, etc). + + 05 - 10 Abril 2018, Corrigido diseases não sendo removidas corretamente no cliente quando tentando trocar de canal/entrar Cash Shop, permitindo-as ficar no jogador infinitamente. Corrigido Holy Symbol atuando de forma inesperada no cenário com somente um jogador. @@ -956,4 +968,46 @@ Adicionado flag que define novo requerimento de mobs para questline do Temple of Corrigido pets permanentes expirando com o tempo. Adicionado suporte para Cash Shop Surprise. Revisado sistema de handlers que gerencia login no server. -Resolvido um problema com flag ultra three snails. \ No newline at end of file +Resolvido um problema com flag ultra three snails. + +21 Maio 2018, +Novo flag TRAVEL_RATE, que diminui linearmente valores de tempo de duração em scripts de viagens. +Ajustado refresh rate de alguns reatores de bosses. +Modificado stance para "pulo-direita" a ser mostrada pra outros jogadores quando jogador entra num mapa. +Corrigido mobs utilizando demais skills, quebrando a fluidez de movimento do mesmo. +Implementado sistema que permite PQ NPCs atuarem como no estilo GMS antigo, tentando registrar party na PQ imediatamente. + +22 - 23 Maio 2018, +Implementado sistema de podio para PlayerNPCs. +Corrigido problemas de overflow em diversas seções do código-fonte. +Refatorado vários usos de NumberFormat com baixa expectativa de vida sendo criados no código-fonte. +Otimizado busca por dados de quests na DB ao carregar jogadores. +Otimizado método de salvar monster card do jogador na DB e quests. +Corrigido birthday não sendo lido corretamente do DB. +Corrigido mecânica de Scissors of Karma. +Corrigido Duey não importando as flag de itens na entrega. + +24 - 25 Maio 2018, +Adicionado sistema custom de buyback. +Refatorado sistema de criação de personagens, passando a dar suporte para o cash item Maple Life. +Corrigido sistema de automatização de pnpcs com algumas avarias. + +28 - 31 Maio 2018, +Edição de localhost: removido bloqueio de acesso ao MTS em mapas que bloqueiam transição (change channel, cash shop, town scrolls), permitindo opção de buyback. +Edição de localhost: removido bloqueio de criação/entrada em party para jogadores novatos com level menor que 10. +Edição de localhost: removido cap para Speed. +Corrigido marriage ring sendo destruído indevidamente ao usar certos pergaminhos. Somente pode ser retirado do inventário divorciando. +Edição de localhost: removido bloqueio de uso de AP para jogadores novatos. +Adicionado server flag que permite mecânica de statup para jogadores novatos com level menor que 11. Necessário uso do localhost editado. + +01 - 04 Maio 2018, +Corrigido NPC de recrutamento para CafePQ não atuando corretamente com a flag que permite estilo do old-GMS PQ NPCs. +Corrigido mensagem de anúncio de Strategy Time para todas as guilds que estiverem se registrando quando a fila está vazia (mesmo que outra guild já esteja nessa etapa). +Edição de localhost: removido bloqueio de uso de gemas para WATK/MATK em equipamentos que não são weapons. +Retirado possível exploit com Maker skill, não utilizando proteção a acesso concorrente em suas operações. +Modificado sistema de EXP de mounts de forma a favorecer usar Revitalizer quando o mount estiver com 30+ de tiredness, adicionado proteção a acesso concorrente. +Retirado aspecto aleatório de ganho de closeness em pets ao usar o Pet Food, adicionado proteção a acesso concorrente. +Corrigido script da Arwen não retirando itens ao gerar certos itens. +Corrigido script de viagem para Florina levando jogadores a Lith Harbor mesmo quando entrando por outras regiões. +Corrigido Stance, Berserk, Ninja Storm, Concentrate, Mage skills and other 4th job skills questlines. +Novo release: Light. \ No newline at end of file diff --git a/nbproject/private/private.xml b/nbproject/private/private.xml index be7ea35507..3287222d26 100644 --- a/nbproject/private/private.xml +++ b/nbproject/private/private.xml @@ -2,18 +2,6 @@ - - file:/C:/Nexon/MapleSolaxia/HeavenMS/scripts/reactor/2119006.js - file:/C:/Nexon/MapleSolaxia/HeavenMS/scripts/reactor/2119002.js - file:/C:/Nexon/MapleSolaxia/HeavenMS/scripts/reactor/2119003.js - file:/C:/Nexon/MapleSolaxia/HeavenMS/scripts/reactor/2229009.js - file:/C:/Nexon/MapleSolaxia/HeavenMS/src/server/maps/MapleMap.java - file:/C:/Nexon/MapleSolaxia/HeavenMS/scripts/reactor/2119001.js - file:/C:/Nexon/MapleSolaxia/HeavenMS/scripts/reactor/2119004.js - file:/C:/Nexon/MapleSolaxia/HeavenMS/scripts/reactor/2119000.js - file:/C:/Nexon/MapleSolaxia/HeavenMS/scripts/reactor/2119005.js - file:/C:/Nexon/MapleSolaxia/HeavenMS/src/scripting/reactor/ReactorScriptManager.java - file:/C:/Nexon/MapleSolaxia/HeavenMS/src/scripting/reactor/ReactorActionManager.java - + diff --git a/scripts/event/AirPlane.js b/scripts/event/AirPlane.js index 3ffa95d899..3a3fffbf0d 100644 --- a/scripts/event/AirPlane.js +++ b/scripts/event/AirPlane.js @@ -13,6 +13,10 @@ var beginTime = 5 * 60 * 1000; //The time to begin the ride var rideTime = 1 * 60 * 1000; //The time that require move to destination function init() { + closeTime = em.getTransportationTime(closeTime); + beginTime = em.getTransportationTime(beginTime); + rideTime = em.getTransportationTime(rideTime); + KC_bfd = em.getChannelServer().getMapFactory().getMap(540010100); CBD_bfd = em.getChannelServer().getMapFactory().getMap(540010001); Plane_to_CBD = em.getChannelServer().getMapFactory().getMap(540010101); diff --git a/scripts/event/AmoriaPQ.js b/scripts/event/AmoriaPQ.js index 369f54d1bf..87c07429f6 100644 --- a/scripts/event/AmoriaPQ.js +++ b/scripts/event/AmoriaPQ.js @@ -24,6 +24,7 @@ */ var isPq = true; +var onlyMarriedPlayers = true; var minPlayers = 6, maxPlayers = 6; var minLevel = 40, maxLevel = 255; var entryMap = 670010200; @@ -58,6 +59,7 @@ function setEventRequirements() { else reqStr += minLevel; reqStr += "\r\n At least 1 of both genders"; + if(onlyMarriedPlayers) reqStr += "\r\n All married"; reqStr += "\r\n Time limit: "; reqStr += eventTime + " minutes"; @@ -84,7 +86,7 @@ function setEventRewards(eim) { function getEligibleParty(party) { //selects, from the given party, the team that is allowed to attempt this event var eligible = []; - var hasLeader = false; + var hasLeader = false, hasNotMarried = false; var mask = 0; if(party.size() > 0) { @@ -95,6 +97,7 @@ function getEligibleParty(party) { //selects, from the given party, the tea if(ch.getMapId() == recruitMap && ch.getLevel() >= minLevel && ch.getLevel() <= maxLevel) { if(ch.isLeader()) hasLeader = true; + if(!ch.getPlayer().isMarried()) hasNotMarried = true; eligible.push(ch); mask |= (1 << ch.getPlayer().getGender()); @@ -103,6 +106,7 @@ function getEligibleParty(party) { //selects, from the given party, the tea } if(!(hasLeader && eligible.length >= minPlayers && eligible.length <= maxPlayers && mask == 3)) eligible = []; + if(onlyMarriedPlayers && hasNotMarried) eligible = []; return eligible; } @@ -155,8 +159,19 @@ function setup(level, lobbyid) { return eim; } -function isTeamAllCouple(eim) { // all players married each other, not implemented - return false; +function isTeamAllCouple(eim) { // everyone partner of someone on the team + var eventPlayers = eim.getPlayers(); + + for (var iterator = eventPlayers.iterator(); iterator.hasNext();) { + var chr = iterator.next(); + + var pid = chr.getPartnerId(); + if(pid <= 0 || eim.getPlayerById(pid) == null) { + return false; + } + } + + return true; } function afterSetup(eim) { diff --git a/scripts/event/Boats.js b/scripts/event/Boats.js index 74d3b1cce2..25cc2ff35b 100644 --- a/scripts/event/Boats.js +++ b/scripts/event/Boats.js @@ -19,6 +19,12 @@ var invasionDelayTime = 1 * 60 * 1000; //The time to balrog ship approach var invasionDelay = 5 * 1000; //The time that spawn balrog function init() { + closeTime = em.getTransportationTime(closeTime); + beginTime = em.getTransportationTime(beginTime); + rideTime = em.getTransportationTime(rideTime); + invasionStartTime = em.getTransportationTime(invasionStartTime); + invasionDelayTime = em.getTransportationTime(invasionDelayTime); + Orbis_btf = em.getChannelServer().getMapFactory().getMap(200000112); Ellinia_btf = em.getChannelServer().getMapFactory().getMap(101000301); Boat_to_Orbis = em.getChannelServer().getMapFactory().getMap(200090010); diff --git a/scripts/event/Cabin.js b/scripts/event/Cabin.js index eeea304ce5..017394ecd5 100644 --- a/scripts/event/Cabin.js +++ b/scripts/event/Cabin.js @@ -54,6 +54,10 @@ var beginTime = 5 * 60 * 1000; //The time to begin the ride var rideTime = 5 * 60 * 1000; //The time that require move to destination function init() { + closeTime = em.getTransportationTime(closeTime); + beginTime = em.getTransportationTime(beginTime); + rideTime = em.getTransportationTime(rideTime); + Orbis_btf = em.getChannelServer().getMapFactory().getMap(200000132); Leafre_btf = em.getChannelServer().getMapFactory().getMap(240000111); Cabin_to_Orbis = em.getChannelServer().getMapFactory().getMap(200090210); diff --git a/scripts/event/CathedralWedding.js b/scripts/event/CathedralWedding.js deleted file mode 100644 index cf61b0b07c..0000000000 --- a/scripts/event/CathedralWedding.js +++ /dev/null @@ -1,194 +0,0 @@ -/* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation version 3 as published by - the Free Software Foundation. You may not use, modify or distribute - this program under any other version of the GNU Affero General Public - License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ -/* - * @Author Jvlaple - * - * Wedding for odinMS - */ -importPackage(java.lang); - -importPackage(Packages.world); -importPackage(Packages.client); -importPackage(Packages.server.maps); - -var exitMap; -var altarMap; -var cakeMap; -var instanceId; -var minPlayers = 1; - -function init() { - exitMap = em.getChannelServer().getMapFactory().getMap(680000500); //Teh exit map :) <---------t - altarMap = em.getChannelServer().getMapFactory().getMap(680000210); //Teh altar map - cakeMap = em.getChannelServer().getMapFactory().getMap(680000300); //Teh cake - instanceId = 1; -} - -function monsterValue(eim, mobId) { - return 1; -} - - - -function setup(eim) { - var instanceName = "CathedralWedding" + instanceId; - var eim = em.newInstance(instanceName); - instanceId++; - - var eim = em.newInstance(instanceName); - - var mf = eim.getMapFactory(); - - - var map = mf.getMap(680000200);//wutt - //Lets make the clock continue through all maps xD - em.schedule("playerAltar", 3 * 60000); - eim.setProperty("hclicked", 0); - eim.setProperty("wclicked", 0); - eim.setProperty("entryTimestamp",System.currentTimeMillis() + (3 * 60000)); - - return eim; -} - -function afterSetup(eim) {} - -function playerEntry(eim, player) { - var map = eim.getMapInstance(680000200); - player.changeMap(map, map.getPortal(0)); - - //1st - 20 min 2nd - 5 min 3rd 5 min xD - //player.getClient().getSession().write(net.sf.odinms.tools.MaplePacketCreator.getClock(1200)); - //player.getClient().getSession().write(net.sf.odinms.tools.MaplePacketCreator.getClock(180)); - player.getClient().getSession().write(net.sf.odinms.tools.MaplePacketCreator.getClock((Long.parseLong(eim.getProperty("entryTimestamp")) - System.currentTimeMillis()) / 1000)); -} - -//lets forget this bullshit... -function playerDead(eim, player) { -} - -function playerRevive(eim, player) { -//how the fuck can this happen? o.O -} - -function playerDisconnected(eim, player) { - playerExit(eim, player);//kick him/her -} - -function leftParty(eim, player) { //this doesnt fucking matter... -} - -function disbandParty(eim) { -} - -function playerUnregistered(eim, player) {} - -function playerExit(eim, player) { - eim.unregisterPlayer(player); - player.changeMap(exitMap, exitMap.getPortal(0)); -} - -function playerWarpAltar(eim, player) { - if ((player.getName() != eim.getProperty("husband")) && (player.getName() != eim.getProperty("wife"))){ - player.changeMap(altarMap, altarMap.getPortal(0)); - player.getClient().getSession().write(net.sf.odinms.tools.MaplePacketCreator.getClock(300)); - }else{ - player.changeMap(altarMap, altarMap.getPortal(2)); - player.getClient().getSession().write(net.sf.odinms.tools.MaplePacketCreator.getClock(300)); - player.getClient().getSession().write(net.sf.odinms.tools.MaplePacketCreator.serverNotice(6, "Please talk to High Priest John now!")); - } -} - -function playerWarpCake(eim, player) { - player.changeMap(cakeMap, cakeMap.getPortal(0)); - player.getClient().getSession().write(net.sf.odinms.tools.MaplePacketCreator.getClock(300)); -} - -function playerAltar(eim, player) { - var iter = em.getInstances().iterator(); - while (iter.hasNext()) { - var eim = iter.next(); - if (eim.getPlayerCount() > 0) { - var pIter = eim.getPlayers().iterator(); - while (pIter.hasNext()) { - playerWarpAltar(eim, pIter.next()); - } - } - em.schedule("playerCake", 5 * 60000); - //eim.dispose(); - } -} - -function playerCake(eim, player) { - var iter = em.getInstances().iterator(); - while (iter.hasNext()) { - var eim = iter.next(); - if (eim.getPlayerCount() > 0) { - var pIter = eim.getPlayers().iterator(); - while (pIter.hasNext()) { - playerWarpCake(eim, pIter.next()); - } - } - em.schedule("timeOut", 5 * 60000); - //eim.dispose(); - } -} - -//Those offline cuntts -function removePlayer(eim, player) { - eim.unregisterPlayer(player); - player.getMap().removePlayer(player); - player.setMap(exitMap); -} - -function clearPQ(eim) { - //Wedding? IDK about gifts o.O - var party = eim.getPlayers(); - for (var i = 0; i < party.size(); i++) { - playerExit(eim, party.get(i)); - } - eim.dispose(); -} - -function monsterKilled(mob, eim) {} - -function allMonstersDead(eim) {} - -function cancelSchedule() {} - -function timeOut() { - var iter = em.getInstances().iterator(); - while (iter.hasNext()) { - var eim = iter.next(); - if (eim.getPlayerCount() > 0) { - var pIter = eim.getPlayers().iterator(); - while (pIter.hasNext()) { - playerExit(eim, pIter.next()); - } - } - eim.dispose(); - } -} - - -function dispose() { - -} \ No newline at end of file diff --git a/scripts/event/ElnathPQ.js b/scripts/event/ElnathPQ.js new file mode 100644 index 0000000000..35ea949375 --- /dev/null +++ b/scripts/event/ElnathPQ.js @@ -0,0 +1,199 @@ +/* + 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 . +*/ + +/** + * @author: Ronan + * @event: ElNath PQ +*/ + +var isPq = true; +var minPlayers = 1, maxPlayers = 4; +var minLevel = 80, maxLevel = 255; +var entryMap = 921100300; +var exitMap = 211040100; +var recruitMap = 211000001; + +var minMapId = 921100300; +var maxMapId = 921100300; + +var eventTime = 10; // 10 minutes + +var lobbyRange = [0, 0]; + +function init() { + setEventRequirements(); +} + +function setLobbyRange() { + return lobbyRange; +} + +function setEventRequirements() { + var reqStr = ""; + + reqStr += "\r\n Number of players: "; + if(maxPlayers - minPlayers >= 1) reqStr += minPlayers + " ~ " + maxPlayers; + else reqStr += minPlayers; + + reqStr += "\r\n Level range: "; + if(maxLevel - minLevel >= 1) reqStr += minLevel + " ~ " + maxLevel; + else reqStr += minLevel; + + reqStr += "\r\n Time limit: "; + reqStr += eventTime + " minutes"; + + em.setProperty("party", reqStr); +} + +function getEligibleParty(party) { //selects, from the given party, the team that is allowed to attempt this event + var eligible = []; + var hasLeader = false; + + if(party.size() > 0) { + var partyList = party.toArray(); + + for(var i = 0; i < party.size(); i++) { + var ch = partyList[i]; + + if(ch.getMapId() == recruitMap && ch.getLevel() >= minLevel && ch.getLevel() <= maxLevel) { + if(ch.isLeader()) hasLeader = true; + eligible.push(ch); + } + } + } + + if(!(hasLeader && eligible.length >= minPlayers && eligible.length <= maxPlayers)) eligible = []; + return eligible; +} + +function setup(level, lobbyid) { + var eim = em.newInstance("Tylus" + lobbyid); + eim.setProperty("level", level); + + respawnStages(eim); + eim.startEventTimer(eventTime * 60000); + return eim; +} + +function afterSetup(eim) {} + +function respawnStages(eim) { + eim.getMapInstance(entryMap).instanceMapRespawn(); + eim.schedule("respawnStages", 15 * 1000); +} + +function playerEntry(eim, player) { + var map = eim.getMapInstance(entryMap); + player.changeMap(map, map.getPortal(0)); +} + +function scheduledTimeout(eim) { + end(eim); +} + +function playerUnregistered(eim, player) {} + +function playerExit(eim, player) { + eim.unregisterPlayer(player); + player.changeMap(exitMap, 0); +} + +function playerLeft(eim, player) { + if(!eim.isEventCleared()) { + playerExit(eim, player); + } +} + +function changedMap(eim, player, mapid) { + if (mapid < minMapId || mapid > maxMapId) { + if (eim.isEventTeamLackingNow(true, minPlayers, player)) { + eim.unregisterPlayer(player); + end(eim); + } + else + eim.unregisterPlayer(player); + } +} + +function changedLeader(eim, leader) { + var mapid = leader.getMapId(); + if (!eim.isEventCleared() && (mapid < minMapId || mapid > maxMapId)) { + end(eim); + } +} + +function playerDead(eim, player) {} + +function playerRevive(eim, player) { // player presses ok on the death pop up. + if (eim.isEventTeamLackingNow(true, minPlayers, player)) { + eim.unregisterPlayer(player); + end(eim); + } + else + eim.unregisterPlayer(player); +} + +function playerDisconnected(eim, player) { + if (eim.isEventTeamLackingNow(true, minPlayers, player)) { + eim.unregisterPlayer(player); + end(eim); + } + else + eim.unregisterPlayer(player); +} + +function leftParty(eim, player) { + if (eim.isEventTeamLackingNow(false, minPlayers, player)) { + end(eim); + } + else + playerLeft(eim, player); +} + +function disbandParty(eim) { + if (!eim.isEventCleared()) { + end(eim); + } +} + +function monsterValue(eim, mobId) { + return 1; +} + +function end(eim) { + var party = eim.getPlayers(); + for (var i = 0; i < party.size(); i++) { + playerExit(eim, party.get(i)); + } + eim.dispose(); +} + +function clearPQ(eim) { + eim.stopEventTimer(); + eim.setEventCleared(); +} + +function monsterKilled(mob, eim) {} + +function allMonstersDead(eim) {} + +function cancelSchedule() {} + +function dispose(eim) {} diff --git a/scripts/event/Genie.js b/scripts/event/Genie.js index e6940c6176..515d968680 100644 --- a/scripts/event/Genie.js +++ b/scripts/event/Genie.js @@ -34,6 +34,10 @@ var beginTime = 5 * 60 * 1000; //The time to begin the ride var rideTime = 5 * 60 * 1000; //The time that require move to destination function init() { + closeTime = em.getTransportationTime(closeTime); + beginTime = em.getTransportationTime(beginTime); + rideTime = em.getTransportationTime(rideTime); + Orbis_btf = em.getChannelServer().getMapFactory().getMap(200000152); Ariant_btf = em.getChannelServer().getMapFactory().getMap(260000110); Genie_to_Orbis = em.getChannelServer().getMapFactory().getMap(200090410); diff --git a/scripts/event/Hak.js b/scripts/event/Hak.js index 04fa391948..f952751c0c 100644 --- a/scripts/event/Hak.js +++ b/scripts/event/Hak.js @@ -12,7 +12,9 @@ var onRide; //Time Setting is in millisecond var rideTime = 60 * 1000; -function init() {} +function init() { + rideTime = em.getTransportationTime(rideTime); +} function setup() { var eim = em.newInstance("Hak_" + + em.getProperty("player")); diff --git a/scripts/event/HenesysPQ.js b/scripts/event/HenesysPQ.js index cbbab892a0..45404f767e 100644 --- a/scripts/event/HenesysPQ.js +++ b/scripts/event/HenesysPQ.js @@ -156,7 +156,7 @@ function playerLeft(eim, player) { } function changedMap(eim, player, mapid) { - if (mapid < minMapId || mapid > maxMapId) { + if (mapid < minMapId || mapid > maxMapId || mapid == 910010300) { if (eim.isEventTeamLackingNow(true, minPlayers, player)) { eim.unregisterPlayer(player); end(eim); diff --git a/scripts/event/KerningTrain.js b/scripts/event/KerningTrain.js index cde2367610..916f812675 100644 --- a/scripts/event/KerningTrain.js +++ b/scripts/event/KerningTrain.js @@ -12,7 +12,9 @@ var onRide; //Time Setting is in millisecond var rideTime = 10 * 1000; -function init() {} +function init() { + rideTime = em.getTransportationTime(rideTime); +} function setup() { var eim = em.newInstance("KerningTrain_" + em.getProperty("player")); diff --git a/scripts/event/Subway.js b/scripts/event/Subway.js index b3b81e73fc..d8e8a69f0b 100644 --- a/scripts/event/Subway.js +++ b/scripts/event/Subway.js @@ -13,6 +13,10 @@ var beginTime = 1 * 60 * 1000; //The time to begin the ride var rideTime = 4 * 60 * 1000; //The time that require move to destination function init() { + closeTime = em.getTransportationTime(closeTime); + beginTime = em.getTransportationTime(beginTime); + rideTime = em.getTransportationTime(rideTime); + KC_Waiting = em.getChannelServer().getMapFactory().getMap(600010004); NLC_Waiting = em.getChannelServer().getMapFactory().getMap(600010002); Subway_to_KC = em.getChannelServer().getMapFactory().getMap(600010003); @@ -34,6 +38,7 @@ function stopEntry() { } function takeoff() { + //sound src: https://www.soundjay.com/transportation/metro-door-close-01.mp3 KC_docked.broadcastMessage(MaplePacketCreator.playSound("subway/whistle")); NLC_docked.broadcastMessage(MaplePacketCreator.playSound("subway/whistle")); diff --git a/scripts/event/Trains.js b/scripts/event/Trains.js index 95f2c74889..8853b6421d 100644 --- a/scripts/event/Trains.js +++ b/scripts/event/Trains.js @@ -15,6 +15,10 @@ var beginTime = 5 * 60 * 1000; //The time to begin the ride var rideTime = 5 * 60 * 1000; //The time that require move to destination function init() { + closeTime = em.getTransportationTime(closeTime); + beginTime = em.getTransportationTime(beginTime); + rideTime = em.getTransportationTime(rideTime); + Orbis_btf = em.getChannelServer().getMapFactory().getMap(200000122); Ludibrium_btf = em.getChannelServer().getMapFactory().getMap(220000111); Train_to_Orbis = em.getChannelServer().getMapFactory().getMap(200090110); diff --git a/scripts/event/WeddingCathedral.js b/scripts/event/WeddingCathedral.js new file mode 100644 index 0000000000..b641984add --- /dev/null +++ b/scripts/event/WeddingCathedral.js @@ -0,0 +1,253 @@ +/** + * @author: Ronan + * @event: Cathedral Wedding +*/ + +var entryMap = 680000200; +var exitMap = 680000500; +var recruitMap = 680000000; +var clearMap = 680000500; + +var minMapId = 680000100; +var maxMapId = 680000401; + +var startMsgTime = 4; +var blessMsgTime = 5; + +var eventTime = 10; // 10 minutes gathering +var ceremonyTime = 20; // 20 minutes ceremony +var blessingsTime = 15;// blessings are held until the 15th minute from the ceremony start +var partyTime = 45; // 45 minutes party + +var forceHideMsgTime = 10; // unfortunately, EIM weddings don't send wedding talk packets to the server... this will need to suffice + +var eventBoss = true; // spawns a Cake boss at the hunting ground +var isCathedral = true; + +var lobbyRange = [0, 0]; + +function init() {} + +function setLobbyRange() { + return lobbyRange; +} + +function setEventExclusives(eim) { + var itemSet = [4031217, 4000313]; // golden key, golden maple leaf + eim.setExclusiveItems(itemSet); +} + +function setEventRewards(eim) { + var itemSet, itemQty, evLevel, expStages; + + evLevel = 1; //Rewards at clear PQ + itemSet = []; + itemQty = []; + eim.setEventRewards(evLevel, itemSet, itemQty); + + expStages = []; //bonus exp given on CLEAR stage signal + eim.setEventClearStageExp(expStages); +} + +function spawnCakeBoss(eim) { + var mapObj = eim.getMapInstance(680000400); + var mobObj = Packages.server.life.MapleLifeFactory.getMonster(9400606); + + mapObj.spawnMonsterOnGroundBelow(mobObj, new Packages.java.awt.Point(777, -177)); +} + +function setup(level, lobbyid) { + var eim = em.newInstance("Wedding" + lobbyid); + eim.setProperty("weddingId", "0"); + eim.setProperty("weddingStage", "0"); // 0: gathering time, 1: wedding time, 2: ready to fulfill the wedding, 3: just married + eim.setProperty("guestBlessings", "0"); + eim.setProperty("isPremium", "1"); + eim.setProperty("canJoin", "1"); + eim.setProperty("groomId", "0"); + eim.setProperty("brideId", "0"); + eim.setProperty("confirmedVows", "-1"); + + eim.getInstanceMap(680000400).resetPQ(level); + if(eventBoss) spawnCakeBoss(eim); + + respawnStages(eim); + eim.startEventTimer(eventTime * 60000); + setEventRewards(eim); + setEventExclusives(eim); + return eim; +} + +function afterSetup(eim) {} + +function respawnStages(eim) { + eim.getMapInstance(680000400).instanceMapRespawn(); + eim.schedule("respawnStages", 15 * 1000); +} + +function playerEntry(eim, player) { + var map = eim.getMapInstance(entryMap); + + player.getClient().getAbstractPlayerInteraction().gainItem(4000313, 1); + player.changeMap(map, map.getPortal(0)); +} + +function stopBlessings(eim) { + var mapobj = eim.getMapInstance(entryMap + 10); + mapobj.dropMessage(6, "Wedding Assistant: Alright people, our couple are preparing their vows to each other right now."); + + eim.setIntProperty("weddingStage", 2); +} + +function sendWeddingAction(eim, type) { + var chr = eim.getLeader(); + if(chr.getGender() == 0) { + chr.getMap().broadcastMessage(Packages.tools.packets.Wedding.OnWeddingProgress(type == 2, eim.getIntProperty("groomId"), eim.getIntProperty("brideId"), type + 1)); + } else { + chr.getMap().broadcastMessage(Packages.tools.packets.Wedding.OnWeddingProgress(type == 2, eim.getIntProperty("brideId"), eim.getIntProperty("groomId"), type + 1)); + } +} + +function hidePriestMsg(eim) { + sendWeddingAction(eim, 2); +} + +function showStartMsg(eim) { + eim.getMapInstance(entryMap + 10).broadcastMessage(Packages.tools.packets.Wedding.OnWeddingProgress(false, 0, 0, 0)); + eim.schedule("hidePriestMsg", forceHideMsgTime * 1000); +} + +function showBlessMsg(eim) { + eim.getMapInstance(entryMap + 10).broadcastMessage(Packages.tools.packets.Wedding.OnWeddingProgress(false, 0, 0, 1)); + eim.setIntProperty("guestBlessings", 1); + eim.schedule("hidePriestMsg", forceHideMsgTime * 1000); +} + +function showMarriedMsg(eim) { + sendWeddingAction(eim, 3); + eim.schedule("hidePriestMsg", 10 * 1000); + + eim.restartEventTimer(partyTime * 60000); +} + +function scheduledTimeout(eim) { + if(eim.getIntProperty("canJoin") == 1) { + em.getChannelServer().closeOngoingWedding(isCathedral); + eim.setIntProperty("canJoin", 0); + + var mapobj = eim.getMapInstance(entryMap); + var chr = mapobj.getCharacterById(eim.getIntProperty("groomId")); + if(chr != null) { + chr.changeMap(entryMap + 10, "we00"); + } + + chr = mapobj.getCharacterById(eim.getIntProperty("brideId")); + if(chr != null) { + chr.changeMap(entryMap + 10, "we00"); + } + + mapobj.dropMessage(6, "Wedding Assistant: The couple are heading to the altar, hurry hurry talk to me to arrange your seat."); + + eim.setIntProperty("weddingStage", 1); + eim.schedule("showStartMsg", startMsgTime * 60 * 1000); + eim.schedule("showBlessMsg", blessMsgTime * 60 * 1000); + eim.schedule("stopBlessings", blessingsTime * 60 * 1000); + eim.startEventTimer(ceremonyTime * 60000); + } else { + end(eim); + } +} + +function playerUnregistered(eim, player) {} + +function playerExit(eim, player) { + eim.unregisterPlayer(player); + player.changeMap(exitMap, 0); +} + +function playerLeft(eim, player) { + if(!eim.isEventCleared()) { + playerExit(eim, player); + } +} + +function isMarrying(eim, player) { + var playerid = player.getId(); + return playerid == eim.getIntProperty("groomId") || playerid == eim.getIntProperty("brideId"); +} + +function changedMap(eim, player, mapid) { + if (mapid < minMapId || mapid > maxMapId) { + if (isMarrying(eim, player)) { + eim.unregisterPlayer(player); + end(eim); + } + else + eim.unregisterPlayer(player); + } +} + +function changedLeader(eim, leader) {} + +function playerDead(eim, player) {} + +function playerRevive(eim, player) { // player presses ok on the death pop up. + if (isMarrying(eim, player)) { + eim.unregisterPlayer(player); + end(eim); + } + else + eim.unregisterPlayer(player); +} + +function playerDisconnected(eim, player) { + if (isMarrying(eim, player)) { + eim.unregisterPlayer(player); + end(eim); + } + else + eim.unregisterPlayer(player); +} + +function leftParty(eim, player) {} + +function disbandParty(eim) {} + +function monsterValue(eim, mobId) { + return 1; +} + +function end(eim) { + var party = eim.getPlayers(); + + for (var i = 0; i < party.size(); i++) { + playerExit(eim, party.get(i)); + } + eim.dispose(); +} + +function giveRandomEventReward(eim, player) { + eim.giveEventReward(player); +} + +function clearPQ(eim) { + eim.stopEventTimer(); + eim.setEventCleared(); +} + +function isCakeBoss(mob) { + return mob.getId() == 9400606; +} + +function monsterKilled(mob, eim) { + if(isCakeBoss(mob)) { + eim.showClearEffect(); + eim.clearPQ(); + } +} + +function allMonstersDead(eim) {} + +function cancelSchedule() {} + +function dispose(eim) {} + diff --git a/scripts/event/WeddingChapel.js b/scripts/event/WeddingChapel.js new file mode 100644 index 0000000000..e5f41a056d --- /dev/null +++ b/scripts/event/WeddingChapel.js @@ -0,0 +1,253 @@ +/** + * @author: Ronan + * @event: Chapel Wedding +*/ + +var entryMap = 680000100; +var exitMap = 680000500; +var recruitMap = 680000000; +var clearMap = 680000500; + +var minMapId = 680000100; +var maxMapId = 680000401; + +var startMsgTime = 4; +var blessMsgTime = 5; + +var eventTime = 10; // 10 minutes gathering +var ceremonyTime = 20; // 20 minutes ceremony +var blessingsTime = 15;// blessings are held until the 15th minute from the ceremony start +var partyTime = 45; // 45 minutes party + +var forceHideMsgTime = 10; // unfortunately, EIM weddings don't send wedding talk packets to the server... this will need to suffice + +var eventBoss = true; // spawns a Cake boss at the hunting ground +var isCathedral = false; + +var lobbyRange = [0, 0]; + +function init() {} + +function setLobbyRange() { + return lobbyRange; +} + +function setEventExclusives(eim) { + var itemSet = [4031217, 4000313]; // golden key, golden maple leaf + eim.setExclusiveItems(itemSet); +} + +function setEventRewards(eim) { + var itemSet, itemQty, evLevel, expStages; + + evLevel = 1; //Rewards at clear PQ + itemSet = []; + itemQty = []; + eim.setEventRewards(evLevel, itemSet, itemQty); + + expStages = []; //bonus exp given on CLEAR stage signal + eim.setEventClearStageExp(expStages); +} + +function spawnCakeBoss(eim) { + var mapObj = eim.getMapInstance(680000400); + var mobObj = Packages.server.life.MapleLifeFactory.getMonster(9400606); + + mapObj.spawnMonsterOnGroundBelow(mobObj, new Packages.java.awt.Point(777, -177)); +} + +function setup(level, lobbyid) { + var eim = em.newInstance("Wedding" + lobbyid); + eim.setProperty("weddingId", "0"); + eim.setProperty("weddingStage", "0"); // 0: gathering time, 1: wedding time, 2: ready to fulfill the wedding, 3: just married + eim.setProperty("guestBlessings", "0"); + eim.setProperty("isPremium", "1"); + eim.setProperty("canJoin", "1"); + eim.setProperty("groomId", "0"); + eim.setProperty("brideId", "0"); + eim.setProperty("confirmedVows", "-1"); + + eim.getInstanceMap(680000400).resetPQ(level); + if(eventBoss) spawnCakeBoss(eim); + + respawnStages(eim); + eim.startEventTimer(eventTime * 60000); + setEventRewards(eim); + setEventExclusives(eim); + return eim; +} + +function afterSetup(eim) {} + +function respawnStages(eim) { + eim.getMapInstance(680000400).instanceMapRespawn(); + eim.schedule("respawnStages", 15 * 1000); +} + +function playerEntry(eim, player) { + var map = eim.getMapInstance(entryMap); + + player.getClient().getAbstractPlayerInteraction().gainItem(4000313, 1); + player.changeMap(map, map.getPortal(0)); +} + +function stopBlessings(eim) { + var mapobj = eim.getMapInstance(entryMap + 10); + mapobj.dropMessage(6, "Wedding Assistant: Alright people, our couple are preparing their vows to each other right now."); + + eim.setIntProperty("weddingStage", 2); +} + +function sendWeddingAction(eim, type) { + var chr = eim.getLeader(); + if(chr.getGender() == 0) { + chr.getMap().broadcastMessage(Packages.tools.packets.Wedding.OnWeddingProgress(type == 2, eim.getIntProperty("groomId"), eim.getIntProperty("brideId"), type + 1)); + } else { + chr.getMap().broadcastMessage(Packages.tools.packets.Wedding.OnWeddingProgress(type == 2, eim.getIntProperty("brideId"), eim.getIntProperty("groomId"), type + 1)); + } +} + +function hidePriestMsg(eim) { + sendWeddingAction(eim, 2); +} + +function showStartMsg(eim) { + eim.getMapInstance(entryMap + 10).broadcastMessage(Packages.tools.packets.Wedding.OnWeddingProgress(false, 0, 0, 0)); + eim.schedule("hidePriestMsg", forceHideMsgTime * 1000); +} + +function showBlessMsg(eim) { + eim.getMapInstance(entryMap + 10).broadcastMessage(Packages.tools.packets.Wedding.OnWeddingProgress(false, 0, 0, 1)); + eim.setIntProperty("guestBlessings", 1); + eim.schedule("hidePriestMsg", forceHideMsgTime * 1000); +} + +function showMarriedMsg(eim) { + sendWeddingAction(eim, 1); + eim.schedule("hidePriestMsg", 10 * 1000); + + eim.restartEventTimer(partyTime * 60000); +} + +function scheduledTimeout(eim) { + if(eim.getIntProperty("canJoin") == 1) { + em.getChannelServer().closeOngoingWedding(isCathedral); + eim.setIntProperty("canJoin", 0); + + var mapobj = eim.getMapInstance(entryMap); + var chr = mapobj.getCharacterById(eim.getIntProperty("groomId")); + if(chr != null) { + chr.changeMap(entryMap + 10, "we00"); + } + + chr = mapobj.getCharacterById(eim.getIntProperty("brideId")); + if(chr != null) { + chr.changeMap(entryMap + 10, "we00"); + } + + mapobj.dropMessage(6, "Wedding Assistant: The couple are heading to the altar, hurry hurry talk to me to arrange your seat."); + + eim.setIntProperty("weddingStage", 1); + eim.schedule("showStartMsg", startMsgTime * 60 * 1000); + eim.schedule("showBlessMsg", blessMsgTime * 60 * 1000); + eim.schedule("stopBlessings", blessingsTime * 60 * 1000); + eim.startEventTimer(ceremonyTime * 60000); + } else { + end(eim); + } +} + +function playerUnregistered(eim, player) {} + +function playerExit(eim, player) { + eim.unregisterPlayer(player); + player.changeMap(exitMap, 0); +} + +function playerLeft(eim, player) { + if(!eim.isEventCleared()) { + playerExit(eim, player); + } +} + +function isMarrying(eim, player) { + var playerid = player.getId(); + return playerid == eim.getIntProperty("groomId") || playerid == eim.getIntProperty("brideId"); +} + +function changedMap(eim, player, mapid) { + if (mapid < minMapId || mapid > maxMapId) { + if (isMarrying(eim, player)) { + eim.unregisterPlayer(player); + end(eim); + } + else + eim.unregisterPlayer(player); + } +} + +function changedLeader(eim, leader) {} + +function playerDead(eim, player) {} + +function playerRevive(eim, player) { // player presses ok on the death pop up. + if (isMarrying(eim, player)) { + eim.unregisterPlayer(player); + end(eim); + } + else + eim.unregisterPlayer(player); +} + +function playerDisconnected(eim, player) { + if (isMarrying(eim, player)) { + eim.unregisterPlayer(player); + end(eim); + } + else + eim.unregisterPlayer(player); +} + +function leftParty(eim, player) {} + +function disbandParty(eim) {} + +function monsterValue(eim, mobId) { + return 1; +} + +function end(eim) { + var party = eim.getPlayers(); + + for (var i = 0; i < party.size(); i++) { + playerExit(eim, party.get(i)); + } + eim.dispose(); +} + +function giveRandomEventReward(eim, player) { + eim.giveEventReward(player); +} + +function clearPQ(eim) { + eim.stopEventTimer(); + eim.setEventCleared(); +} + +function isCakeBoss(mob) { + return mob.getId() == 9400606; +} + +function monsterKilled(mob, eim) { + if(isCakeBoss(mob)) { + eim.showClearEffect(); + eim.clearPQ(); + } +} + +function allMonstersDead(eim) {} + +function cancelSchedule() {} + +function dispose(eim) {} + diff --git a/scripts/event/elevator.js b/scripts/event/elevator.js index 62d66449a1..5ef9bca5ba 100644 --- a/scripts/event/elevator.js +++ b/scripts/event/elevator.js @@ -25,6 +25,9 @@ var beginTime = 60 * 1000; //The time to begin the ride var rideTime = 60 * 1000; //The time that require move to destination function init() { + beginTime = em.getTransportationTime(beginTime); + rideTime = em.getTransportationTime(rideTime); + em.getChannelServer().getMapFactory().getMap(222020100).resetReactors(); em.getChannelServer().getMapFactory().getMap(222020200).resetReactors(); diff --git a/scripts/event/s4aWorld.js b/scripts/event/s4aWorld.js index 67693dcc23..7e17b8c833 100644 --- a/scripts/event/s4aWorld.js +++ b/scripts/event/s4aWorld.js @@ -12,10 +12,33 @@ function monsterValue(eim, mobId) { return 1; } +function getEligibleParty(party) { //selects, from the given party, the team that is allowed to attempt this event + var eligible = []; + var hasLeader = false; + + if(party.size() > 0) { + var partyList = party.toArray(); + + for(var i = 0; i < party.size(); i++) { + var ch = partyList[i]; + + if(ch.getMapId() == 105090200 && ch.getLevel() >= 120) { + if(ch.isLeader()) hasLeader = true; + eligible.push(ch); + } + } + } + + if(!(hasLeader && eligible.length >= minPlayers)) eligible = []; + return eligible; +} + function setup() { var eim = em.newInstance("s4aWorld"); - eim.getInstanceMap(910500000).resetFully(); + eim.getInstanceMap(910500000).resetPQ(1); + respawnStages(eim); + eim.getMapInstance(910500000).instanceMapForceRespawn(); eim.startEventTimer(1200000); em.setProperty("started", "true"); @@ -25,6 +48,11 @@ function setup() { function afterSetup(eim) {} +function respawnStages(eim) { + eim.getMapInstance(910500000).instanceMapRespawn(); + eim.schedule("respawnStages", 15 * 1000); +} + function playerEntry(eim, player) { var map = eim.getMapFactory().getMap(910500000); player.changeMap(map, map.getPortal(0)); @@ -86,4 +114,6 @@ function monsterKilled(mob, eim) {} function allMonstersDead(eim) {} -function cancelSchedule() {} \ No newline at end of file +function cancelSchedule() {} + +function dispose() {} \ No newline at end of file diff --git a/scripts/npc/1012100.js b/scripts/npc/1012100.js index ae6aa9b749..8063e7a86d 100644 --- a/scripts/npc/1012100.js +++ b/scripts/npc/1012100.js @@ -33,24 +33,20 @@ spawnPnpc = false; spawnPnpcFee = 7000000; jobType = 3; -function numberWithCommas(x) { - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); -} - function start() { if (parseInt(cm.getJobId() / 100) == jobType && cm.canSpawnPlayerNpc(Packages.constants.GameConstants.getHallOfFameMapid(cm.getJob()))) { spawnPnpc = true; - var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, didn't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; + var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, haven't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; if(spawnPnpcFee > 0) { - sendStr += " I can do it for you, for the fee of #b " + numberWithCommas(spawnPnpcFee) + " mesos.#k"; + sendStr += " I can do it for you, for the fee of #b " + cm.numberWithCommas(spawnPnpcFee) + " mesos.#k"; } cm.sendYesNo(sendStr); } else { if (cm.getJobId() == 0) { actionx["1stJob"] = true; - if (cm.getLevel() >= 10) + if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType)) cm.sendNext("So you decided to become a #rBowman#k?"); else { cm.sendOk("Train a bit more and I can show you the way of the #rBowman#k."); diff --git a/scripts/npc/1012112.js b/scripts/npc/1012112.js index 7eebfda51a..f2bd1b0070 100644 --- a/scripts/npc/1012112.js +++ b/scripts/npc/1012112.js @@ -53,6 +53,9 @@ function action(mode, type, selection) { cm.sendOk("The Henesys PQ has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nI'm Tory. Inside here is a beautiful hill where the primrose blooms. There's a tiger that lives in the hill, Growlie, and he seems to be looking for something to eat. Would you like to head over to the hill of primrose and join forces with your party members to help Growlie out?#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details.\r\n#L3#I would like to redeem an event hat."); diff --git a/scripts/npc/1022000.js b/scripts/npc/1022000.js index 090985a05e..231c841844 100644 --- a/scripts/npc/1022000.js +++ b/scripts/npc/1022000.js @@ -34,24 +34,20 @@ spawnPnpc = false; spawnPnpcFee = 7000000; jobType = 1; -function numberWithCommas(x) { - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); -} - function start() { if (parseInt(cm.getJobId() / 100) == jobType && cm.canSpawnPlayerNpc(Packages.constants.GameConstants.getHallOfFameMapid(cm.getJob()))) { spawnPnpc = true; - var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, didn't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; + var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, haven't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; if(spawnPnpcFee > 0) { - sendStr += " I can do it for you, for the fee of #b " + numberWithCommas(spawnPnpcFee) + " mesos.#k"; + sendStr += " I can do it for you, for the fee of #b " + cm.numberWithCommas(spawnPnpcFee) + " mesos.#k"; } cm.sendYesNo(sendStr); } else { if (cm.getJobId() == 0) { actionx["1stJob"] = true; - if (cm.getLevel() >= 10) + if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType)) cm.sendNext("Do you want to become a Warrior? You need to meet some criteria in order to do so.#b You should be at least in level 10, with at least 35 in STR#k. Let's see..."); else { cm.sendOk("Train a bit more and I can show you the way of the #rWarrior#k."); diff --git a/scripts/npc/1032001.js b/scripts/npc/1032001.js index 63c3d58bd8..527b31602a 100644 --- a/scripts/npc/1032001.js +++ b/scripts/npc/1032001.js @@ -34,24 +34,20 @@ spawnPnpc = false; spawnPnpcFee = 7000000; jobType = 2; -function numberWithCommas(x) { - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); -} - function start() { if (parseInt(cm.getJobId() / 100) == jobType && cm.canSpawnPlayerNpc(Packages.constants.GameConstants.getHallOfFameMapid(cm.getJob()))) { spawnPnpc = true; - var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, didn't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; + var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, haven't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; if(spawnPnpcFee > 0) { - sendStr += " I can do it for you, for the fee of #b " + numberWithCommas(spawnPnpcFee) + " mesos.#k"; + sendStr += " I can do it for you, for the fee of #b " + cm.numberWithCommas(spawnPnpcFee) + " mesos.#k"; } cm.sendYesNo(sendStr); } else { if (cm.getJobId() == 0) { actionx["1stJob"] = true; - if (cm.getLevel() >= 8) + if (cm.getLevel() >= 8 && cm.canGetFirstJob(jobType)) cm.sendNext("Want to be a magician? There are some standards to meet. because we can't just accept EVERYONE in... #bYour level should be at least 8#k, with getting INT as your top priority. Let's see."); else { cm.sendOk("Train a bit more and I can show you the way of the #rMagician#k."); diff --git a/scripts/npc/1032100.js b/scripts/npc/1032100.js index 341fc88975..f1c52360c5 100644 --- a/scripts/npc/1032100.js +++ b/scripts/npc/1032100.js @@ -77,7 +77,7 @@ function action(mode, type, selection) { } } else if (status == 3) { if (selected == 0) { - if (cm.haveItem(4011000) && cm.haveItem(4011001) && cm.haveItem(4011002) && cm.haveItem(4011003) && cm.haveItem(4011004) && cm.haveItem(4011005) && cm.haveItem(4011006) && cm.getMeso() > 10000) { + if (cm.haveItem(4011000) && cm.haveItem(4011001) && cm.haveItem(4011002) && cm.haveItem(4011003) && cm.haveItem(4011004) && cm.haveItem(4011005) && cm.haveItem(4011006) && cm.getMeso() >= 10000) { cm.gainMeso(-10000); for(var i = 4011000; i<4011007; i++) { cm.gainItem(i,-1); @@ -88,9 +88,9 @@ function action(mode, type, selection) { cm.sendNext("Are you sure you have enough mesos? Please check and see if you have the refined #bBronze Plate#k, #bSteel Plate#k,\r\n#bMithril Plate#k, #bAdamantium Plate#k, #bSilver Plate#k, #bOrihalcon Plate#k and #bGold Plate#k, one of each."); } } else if (selected == 1) { - if (cm.haveItem(4021000) && cm.haveItem(4021001) && cm.haveItem(4021002) && cm.haveItem(4021003) && cm.haveItem(4021004) && cm.haveItem(4021005) && cm.haveItem(4021006) && cm.haveItem(4021007) && cm.haveItem(4021008) && cm.getMeso() > 15000) { + if (cm.haveItem(4021000) && cm.haveItem(4021001) && cm.haveItem(4021002) && cm.haveItem(4021003) && cm.haveItem(4021004) && cm.haveItem(4021005) && cm.haveItem(4021006) && cm.haveItem(4021007) && cm.haveItem(4021008) && cm.getMeso() >= 15000) { cm.gainMeso(-15000); - for(var j = 4021000; j<4011009; j++) { + for(var j = 4021000; j<4021009; j++) { cm.gainItem(j,-1); } cm.gainItem(4021009, 1); @@ -99,9 +99,9 @@ function action(mode, type, selection) { cm.sendNext("Are you sure you have enough mesos? Please check and see if you have the refined #bGarnet#k, #bAmethyst#k, #bAquaMarine#k, #bEmerald#k, #bOpal#k, #bSapphire#k, #bTopaz#k, #bDiamond#k and #bBlack Crystal#k, one of each."); } } else if (selected == 2) { - if (cm.haveItem(4001006) && cm.haveItem(4011007) && cm.haveItem(4021008) && cm.getMeso() > 30000) { + if (cm.haveItem(4001006) && cm.haveItem(4011007) && cm.haveItem(4021008) && cm.getMeso() >= 30000) { cm.gainMeso(-30000); - for(var k = 4021000; k<4021009; k+=1001) { + for(var k = 4001006; k<4021009; k+=10001) { cm.gainItem(k,-1); } cm.gainItem(4031042, 1); diff --git a/scripts/npc/1032102.js b/scripts/npc/1032102.js index c08fe6fa6f..3b742188ee 100644 --- a/scripts/npc/1032102.js +++ b/scripts/npc/1032102.js @@ -9,7 +9,8 @@ Dragon Evolver */ -importPackage(Packages.client); +importPackage(Packages.client.inventory); +importPackage(Packages.client.inventory.manipulator); importPackage(Packages.server); var status; diff --git a/scripts/npc/1052001.js b/scripts/npc/1052001.js index 1ed8462a20..7ef886ac0a 100644 --- a/scripts/npc/1052001.js +++ b/scripts/npc/1052001.js @@ -33,24 +33,20 @@ spawnPnpc = false; spawnPnpcFee = 7000000; jobType = 4; -function numberWithCommas(x) { - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); -} - function start() { if (parseInt(cm.getJobId() / 100) == jobType && cm.canSpawnPlayerNpc(Packages.constants.GameConstants.getHallOfFameMapid(cm.getJob()))) { spawnPnpc = true; - var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, didn't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; + var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, haven't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; if(spawnPnpcFee > 0) { - sendStr += " I can do it for you, for the fee of #b " + numberWithCommas(spawnPnpcFee) + " mesos.#k"; + sendStr += " I can do it for you, for the fee of #b " + cm.numberWithCommas(spawnPnpcFee) + " mesos.#k"; } cm.sendYesNo(sendStr); } else { if (cm.getJobId() == 0) { actionx["1stJob"] = true; - if (cm.getLevel() >= 10) + if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType)) cm.sendNext("Want to be a thief? There are some standards to meet. because we can't just accept EVERYONE in... #bYour level should be at least 10, with your DEX over 25#k. Let's see."); else { cm.sendOk("Train a bit more and I can show you the way of the #rThief#k."); diff --git a/scripts/npc/1052013.js b/scripts/npc/1052013.js index 4800732a10..e3f13e6d88 100644 --- a/scripts/npc/1052013.js +++ b/scripts/npc/1052013.js @@ -96,6 +96,10 @@ function action(mode, type, selection) { cm.sendOk("The CafePQ_" + pqArea + "has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + status = 1; + action(1, 0, 0); + return; } cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nThe #p1052014# operates differently than the common ones. They do not use mesos or gachapon tickets, rather #rERASERS#k, that can be obtained by completing the missions held on the Premium Road. To go there, you must find partners and attend to a Party Quest. When teamed up and ready, have your #bparty leader#k talk to me.#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details."); diff --git a/scripts/npc/1061012.js b/scripts/npc/1061012.js index 85749950f0..4429ef3cf2 100644 --- a/scripts/npc/1061012.js +++ b/scripts/npc/1061012.js @@ -5,7 +5,7 @@ */ function start() { - if (cm.getQuestStatus(6108) == 1) { + if (cm.getQuestStatus(6107) == 1 || cm.getQuestStatus(6108) == 1) { var ret = checkJob(); if (ret == -1) { cm.sendOk("Please form a party and talk to me again."); @@ -22,13 +22,17 @@ function start() { } else if (em.getProperty("started").equals("true")) { cm.sendOk("Someone else is already attempting to defeat the Jr.Balrog in another world." ); } else { - if(!em.startInstance(cm.getParty(), cm.getMap())) { - cm.sendOk("A party in your name is already registered in this event."); + var eli = em.getEligibleParty(cm.getParty()); + if(eli.size() > 0) { + if(!em.startInstance(cm.getParty(), cm.getPlayer().getMap(), 1)) { + cm.sendOk("A party in your name is already registered in this event."); + } + } else { + cm.sendOk("You cannot start this party quest yet, because either your party is not in the range size, some of your party members are not eligible to attempt it or they are not in this map. If you're having trouble finding party members, try Party Search."); } } } - } - else { + } else { cm.sendOk("You're not allowed to enter the other world with unknown reason."); } diff --git a/scripts/npc/1081001.js b/scripts/npc/1081001.js index 440188a79c..24cd18b410 100644 --- a/scripts/npc/1081001.js +++ b/scripts/npc/1081001.js @@ -25,6 +25,7 @@ -- By --------------------------------------------------------------------------------------------- Information & Xterminator -- Version Info ----------------------------------------------------------------------------------- + 1.3 - Fixed saved location [Ronan] 1.2 - Fixed and cleanup [Shootsource] 1.1 - Add null map check [Xterminator] 1.0 - First Version @@ -34,7 +35,7 @@ var status = 0; var returnmap; function start() { - returnmap = cm.getPlayer().getSavedLocation("FLORINA"); + returnmap = cm.getPlayer().peekSavedLocation("FLORINA"); if (returnmap == -1) returnmap = 104000000; cm.sendNext("So you want to leave #b#m110000000##k? If you want, I can take you back to #b#m"+returnmap+"##k."); @@ -53,6 +54,7 @@ function action(mode, type, selection) { if (status == 1) cm.sendYesNo("Are you sure you want to return to #b#m"+returnmap+"##k? Alright, we'll have to get going fast. Do you want to head back to #m"+returnmap+"# now?") else { + cm.getPlayer().getSavedLocation("FLORINA"); cm.warp(returnmap); cm.dispose(); } diff --git a/scripts/npc/1090000.js b/scripts/npc/1090000.js index 8b38574a77..c0163e07f4 100644 --- a/scripts/npc/1090000.js +++ b/scripts/npc/1090000.js @@ -33,24 +33,20 @@ spawnPnpc = false; spawnPnpcFee = 7000000; jobType = 5; -function numberWithCommas(x) { - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); -} - function start() { if (parseInt(cm.getJobId() / 100) == jobType && cm.canSpawnPlayerNpc(Packages.constants.GameConstants.getHallOfFameMapid(cm.getJob()))) { spawnPnpc = true; - var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, didn't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; + var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, haven't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; if(spawnPnpcFee > 0) { - sendStr += " I can do it for you, for the fee of #b " + numberWithCommas(spawnPnpcFee) + " mesos.#k"; + sendStr += " I can do it for you, for the fee of #b " + cm.numberWithCommas(spawnPnpcFee) + " mesos.#k"; } cm.sendYesNo(sendStr); } else { if (cm.getJobId() == 0) { actionx["1stJob"] = true; - if (cm.getLevel() >= 10) + if (cm.getLevel() >= 10 && cm.canGetFirstJob(jobType)) cm.sendNext("Want to be a pirate? There are some standards to meet. because we can't just accept EVERYONE in... #bYour level should be at least 10#k. Let's see."); else { cm.sendOk("Train a bit more and I can show you the way of the #rPirate#k."); diff --git a/scripts/npc/1102003.js b/scripts/npc/1102003.js index 2cfeb72b32..0433223b51 100644 --- a/scripts/npc/1102003.js +++ b/scripts/npc/1102003.js @@ -29,18 +29,14 @@ var spawnPnpcFee = 7000000; var minJobType = 11; var maxJobType = 15; -function numberWithCommas(x) { - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); -} - function start() { var jobType = parseInt(cm.getJobId() / 100); if (jobType >= minJobType && jobType <= maxJobType && cm.canSpawnPlayerNpc(Packages.constants.GameConstants.getHallOfFameMapid(cm.getJob()))) { spawnPnpc = true; - var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, didn't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; + var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, haven't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; if(spawnPnpcFee > 0) { - sendStr += " I can do it for you, for the fee of #b " + numberWithCommas(spawnPnpcFee) + " mesos.#k"; + sendStr += " I can do it for you, for the fee of #b " + cm.numberWithCommas(spawnPnpcFee) + " mesos.#k"; } cm.sendYesNo(sendStr); diff --git a/scripts/npc/1202010.js b/scripts/npc/1202010.js index 369e7f3b7b..da06e68e88 100644 --- a/scripts/npc/1202010.js +++ b/scripts/npc/1202010.js @@ -4,17 +4,13 @@ var spawnPnpc = false; var spawnPnpcFee = 7000000; var jobType = 21; -function numberWithCommas(x) { - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); -} - function start() { if (parseInt(cm.getJobId() / 100) == jobType && cm.canSpawnPlayerNpc(Packages.constants.GameConstants.getHallOfFameMapid(cm.getJob()))) { spawnPnpc = true; - var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, didn't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; + var sendStr = "You have walked a long way to reach the power, wisdom and courage you hold today, haven't you? What do you say about having right now #ra NPC on the Hall of Fame holding the current image of your character#k? Do you like it?"; if(spawnPnpcFee > 0) { - sendStr += " I can do it for you, for the fee of #b " + numberWithCommas(spawnPnpcFee) + " mesos.#k"; + sendStr += " I can do it for you, for the fee of #b " + cm.numberWithCommas(spawnPnpcFee) + " mesos.#k"; } cm.sendYesNo(sendStr); diff --git a/scripts/npc/2002000.js b/scripts/npc/2002000.js index cd659d5f05..cd3cd3913e 100644 --- a/scripts/npc/2002000.js +++ b/scripts/npc/2002000.js @@ -34,7 +34,7 @@ function action(mode, type, selection) { else { var map = cm.getPlayer().getSavedLocation("HAPPYVILLE"); if (map == -1) - map = 101000000; + map = 101000000; cm.warp(map, 0); } diff --git a/scripts/npc/2012023.js b/scripts/npc/2012023.js new file mode 100644 index 0000000000..f782abfaf0 --- /dev/null +++ b/scripts/npc/2012023.js @@ -0,0 +1,56 @@ +/* + 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 . +*/ +/* Maple Marble + 4th job skill - Night Lord + */ + +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) { + if(cm.haveItem(4031476)) { + cm.gainItem(4031476, -1); + + if(cm.canHold(4031456, 1)) { + cm.gainItem(4031456, 1); + } + } + + cm.dispose(); + } + } +} diff --git a/scripts/npc/2013000.js b/scripts/npc/2013000.js index f909e9dbea..1d58bd6777 100644 --- a/scripts/npc/2013000.js +++ b/scripts/npc/2013000.js @@ -53,6 +53,9 @@ function action(mode, type, selection) { cm.sendOk("The Orbis PQ has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nWould you like to assemble or join a team to solve the puzzles of the #bTower of Goddess#k? Have your #bparty leader#k talk to me or make yourself a party.#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details.\r\n#L3#I would like to reclaim a prize."); diff --git a/scripts/npc/2020008.js b/scripts/npc/2020008.js index e1981a97ab..41d3e34039 100644 --- a/scripts/npc/2020008.js +++ b/scripts/npc/2020008.js @@ -27,11 +27,27 @@ actionx = {"Mental" : false, "Physical" : false}; function start() { if(cm.isQuestStarted(6192)) { - if(cm.getWarpMap(921100300).getCharacters().size() > 0) - cm.sendOk("There is someone currently in this map, come back later."); + if(cm.getParty() == null) { + cm.sendOk("Form a party to start this instance."); + cm.dispose(); + return; + } + + var em = cm.getEventManager("ElnathPQ"); + if(em == null) { + cm.sendOk("The El Nath PQ has encountered an error."); + cm.dispose(); + return; + } + + var eli = em.getEligibleParty(cm.getParty()); + if(eli.size() > 0) { + if(!em.startInstance(cm.getParty(), cm.getPlayer().getMap(), 1)) { + cm.sendOk("Another party is already challenging this instance. Please try another channel, or wait for the current party to finish."); + } + } else { - cm.resetMapObjects(921100300); - cm.warp(921100300, 0); + cm.sendOk("You cannot start this instance yet, because either your party is not in the range size, some of your party members are not eligible to attempt it or they are not in this map. If you're having trouble finding party members, try Party Search."); } cm.dispose(); diff --git a/scripts/npc/2030000.js b/scripts/npc/2030000.js index 63259681c1..77b591c37b 100644 --- a/scripts/npc/2030000.js +++ b/scripts/npc/2030000.js @@ -32,6 +32,12 @@ var status = 0; function start() { + if(cm.haveItem(4031450, 1)) { + cm.warp(921100100); + cm.dispose(); + return; + } + cm.sendNext("Hey, you look like you want to go farther and deeper past this place. Over there, though, you'll find yourself surrounded by aggressive, dangerous monsters, so even if you feel that you're ready to go, please be careful. Long ago, a few brave men from our town went in wanting to eliminate anyone threatening the town, but never came back out..."); } diff --git a/scripts/npc/2030006.js b/scripts/npc/2030006.js index dc9a8714ed..3cc2d17415 100644 --- a/scripts/npc/2030006.js +++ b/scripts/npc/2030006.js @@ -1,6 +1,6 @@ /* This file is part of the HeavenMS MapleStory Server - Copyleft (L) 2018 RonanLana + 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 diff --git a/scripts/npc/2030008.js b/scripts/npc/2030008.js index 21d68b88ef..927124bcc8 100644 --- a/scripts/npc/2030008.js +++ b/scripts/npc/2030008.js @@ -52,6 +52,12 @@ function action(mode, type, selection) { else status--; + if(cm.haveItem(4001109, 1)) { + cm.warp(921100000, "out00"); + cm.dispose(); + return; + } + if(!cm.isQuestStarted(100200)) { cm.sendOk("Beware, for the power of olde has not been forgotten... "); cm.dispose(); diff --git a/scripts/npc/2030014.js b/scripts/npc/2030014.js new file mode 100644 index 0000000000..0501e9e25c --- /dev/null +++ b/scripts/npc/2030014.js @@ -0,0 +1,54 @@ +/* + 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 . +*/ +/* Ancient Icy Stone + 4th job I/L skill + */ + +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) { + if(cm.haveItem(4031450, 1)) { + if(cm.canHold(2280011, 1)) { + cm.gainItem(2280011, 1); + cm.gainItem(4031450, -1); + } + } + cm.dispose(); + } + } +} diff --git a/scripts/npc/2040034.js b/scripts/npc/2040034.js index 3ac5efa29a..09e8a01d06 100644 --- a/scripts/npc/2040034.js +++ b/scripts/npc/2040034.js @@ -33,6 +33,9 @@ function action(mode, type, selection) { cm.sendOk("The Ludibrium PQ has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nYou can't go any higher because of the extremely dangerous creatures above. Would you like to collaborate with party members to complete the quest? If so, please have your #bparty leader#k talk to me.#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details."); diff --git a/scripts/npc/2041023.js b/scripts/npc/2041023.js index 9edc5e5b15..dd45a5c0b3 100644 --- a/scripts/npc/2041023.js +++ b/scripts/npc/2041023.js @@ -61,6 +61,9 @@ function action(mode, type, selection) { cm.sendOk("The Elemental Battle has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nYou are looking for Elemental Thanatos, right? If you team up with another mage, with the opposite elemental affinity as yours, you guys will be able to overcome them. As a leader, talk to me when you feel ready to go.#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details."); diff --git a/scripts/npc/2042000.js b/scripts/npc/2042000.js index d8ebe345d2..7488954c21 100644 --- a/scripts/npc/2042000.js +++ b/scripts/npc/2042000.js @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + 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 @@ -19,39 +17,210 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -var status = 0; +/* Spiegelmann + Refining NPC: + * Auto ore refiner + * + * @author RonanLana +*/ +var status; +var refineRocks = true; // enables moon rock, star rock +var refineCrystals = true; // enables common crystals +var refineSpecials = true; // enables lithium, special crystals +var feeMultiplier = 7.0; + function start() { - cm.sendOk("The Monster Carnival is currently unavailable."); - cm.dispose(); - - /* - if (cm.getPlayer().getParty() != null) - cm.sendCPQMapLists(); - else { - cm.sendOk("You must be in a party!"); - cm.dispose(); - } + status = -1; + action(1, 0, 0); } function action(mode, type, selection) { - if (mode < 1) - cm.dispose(); - else { - status++; - if (status == 1) { - if (cm.fieldTaken(selection)) { - if (cm.fieldLobbied(selection)) { - cm.challengeParty(selection); - cm.dispose(); - } else { - cm.sendOk("The room is taken."); - cm.dispose(); - } - } else { - cm.cpqLobby(selection); + if (mode == -1) { cm.dispose(); + } else { + if (mode == 0 && type > 0) { + cm.dispose(); + return; + } + if (mode == 1) + status++; + else + status--; + + if(status == 0) { + var selStr = "The Monster Carnival is currently unavailable, but instead I offer a steadfast #bore refining#k service for you, taxing #r" + ((feeMultiplier * 100) | 0) + "%#k over the usual fee to synthetize them. What will you do?#b"; + + var options = new Array("Refine mineral ores","Refine jewel ores"); + if(refineCrystals) { + options.push("Refine crystal ores"); + } + if(refineRocks) { + options.push("Refine plates/jewels"); + } + + for (var i = 0; i < options.length; i++){ + selStr += "\r\n#L" + i + "# " + options[i] + "#l"; + } + + cm.sendSimple(selStr); + } else if(status == 1) { + var allDone; + + if (selection == 0) { + allDone = refineItems(0); // minerals + } else if (selection == 1) { + allDone = refineItems(1); // jewels + } else if (selection == 2 && refineCrystals) { + allDone = refineItems(2); // crystals + } else if (selection == 2 && !refineCrystals || selection == 3) { + allDone = refineRockItems(); // moon/star rock + } + + if(allDone) { + cm.sendOk("Done. Thanks for showing up~."); + } else { + cm.sendOk("Done. Be aware some of the items could not be synthetized because either you have a lack of space on your ETC inventory or there's not enough mesos to cover the fee."); + } + cm.dispose(); + } + } +} + +function getRefineFee(fee) { + return ((feeMultiplier * fee) | 0); +} + +function isRefineTarget(refineType, refineItemid) { + if(refineType == 0) { //mineral refine + return refineItemid >= 4010000 && refineItemid <= 4010007 && !(refineItemid == 4010007 && !refineSpecials); + } else if(refineType == 1) { //jewel refine + return refineItemid >= 4020000 && refineItemid <= 4020008 && !(refineItemid == 4020008 && !refineSpecials); + } else if(refineType == 2) { //crystal refine + return refineItemid >= 4004000 && refineItemid <= 4004004 && !(refineItemid == 4004004 && !refineSpecials); + } + + return false; +} + +function getRockRefineTarget(refineItemid) { + if(refineItemid >= 4011000 && refineItemid <= 4011006) { + return 0; + } else if(refineItemid >= 4021000 && refineItemid <= 4021008) { + return 1; + } + + return -1; +} + +function refineItems(refineType) { + var allDone = true; + + var refineFees = [[300,300,300,500,500,500,800,270],[500,500,500,500,500,500,500,1000,3000],[5000,5000,5000,5000,1000000]]; + var itemCount = {}; + + var iter = cm.getPlayer().getInventory(Packages.client.inventory.MapleInventoryType.ETC).iterator(); + while (iter.hasNext()) { + var it = iter.next(); + var itemid = it.getItemId(); + + if(isRefineTarget(refineType, itemid)) { + var ic = itemCount[itemid]; + + if(ic != undefined) { + itemCount[itemid] += it.getQuantity(); + } else { + itemCount[itemid] = it.getQuantity(); } } - }*/ -} \ No newline at end of file + } + + for(var key in itemCount) { + var itemqty = itemCount[key]; + var itemid = parseInt(key); + + var refineQty = ((itemqty / 10) | 0); + if(refineQty <= 0) continue; + + while(true) { + itemqty = refineQty * 10; + + var fee = getRefineFee(refineFees[refineType][(itemid % 100) | 0] * refineQty); + if(cm.canHold(itemid + 1000, refineQty) && cm.getMeso() >= fee) { + cm.gainMeso(-fee); + cm.gainItem(itemid, -itemqty); + cm.gainItem(itemid + (itemid != 4010007 ? 1000 : 1001), refineQty); + + break; + } else if(refineQty <= 1) { + allDone = false; + break; + } else { + refineQty--; + } + } + } + + return allDone; +} + +function refineRockItems() { + var allDone = true; + var minItems = [[0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0]]; + var minRocks = [2147483647, 2147483647]; + + var rockItems = [4011007, 4021009]; + var rockFees = [10000, 15000]; + + var iter = cm.getPlayer().getInventory(Packages.client.inventory.MapleInventoryType.ETC).iterator(); + while (iter.hasNext()) { + var it = iter.next(); + var itemid = it.getItemId(); + var rockRefine = getRockRefineTarget(itemid); + if(rockRefine >= 0) { + var rockItem = ((itemid % 100) | 0); + var itemqty = it.getQuantity(); + + minItems[rockRefine][rockItem] += itemqty; + } + } + + for(var i = 0; i < minRocks.length; i++) { + for(var j = 0; j < minItems[i].length; j++) { + if(minRocks[i] > minItems[i][j]) { + minRocks[i] = minItems[i][j]; + } + } + if(minRocks[i] <= 0 || minRocks[i] == 2147483647) continue; + + var refineQty = minRocks[i]; + while(true) { + var fee = getRefineFee(rockFees[i] * refineQty); + if(cm.canHold(rockItems[i], refineQty) && cm.getMeso() >= fee) { + cm.gainMeso(-fee); + + var j; + if(i == 0) { + for(j = 4011000; j < 4011007; j++) { + cm.gainItem(j, -refineQty); + } + cm.gainItem(j, refineQty); + } else { + for(j = 4021000; j < 4021009; j++) { + cm.gainItem(j, -refineQty); + } + cm.gainItem(j, refineQty); + } + + break; + } else if(refineQty <= 1) { + allDone = false; + break; + } else { + refineQty--; + } + } + } + + return allDone; +} diff --git a/scripts/npc/2042000_old.js b/scripts/npc/2042000_old.js new file mode 100644 index 0000000000..e93563a233 --- /dev/null +++ b/scripts/npc/2042000_old.js @@ -0,0 +1,53 @@ +/* + This file is part of the OdinMS Maple Story Server + Copyright (C) 2008 Patrick Huy + Matthias Butz + Jan Christian Meyer + + 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 = 0; + +function start() { + if (cm.getPlayer().getParty() != null) + cm.sendCPQMapLists(); + else { + cm.sendOk("You must be in a party!"); + cm.dispose(); + } +} + +function action(mode, type, selection) { + if (mode < 1) + cm.dispose(); + else { + status++; + if (status == 1) { + if (cm.fieldTaken(selection)) { + if (cm.fieldLobbied(selection)) { + cm.challengeParty(selection); + cm.dispose(); + } else { + cm.sendOk("The room is taken."); + cm.dispose(); + } + } else { + cm.cpqLobby(selection); + cm.dispose(); + } + } + } +} \ No newline at end of file diff --git a/scripts/npc/2080000.js b/scripts/npc/2080000.js index acf57e6cbc..c8e4a92eec 100644 --- a/scripts/npc/2080000.js +++ b/scripts/npc/2080000.js @@ -261,9 +261,9 @@ function getStimID(equipID){ return 4130012; case 146: //xbow return 4130013; - case 148: // Knuckle + case 148: //knuckle return 4130016; - case 149: + case 149: //pistol return 4130017; case 133: //dagger return 4130014; diff --git a/scripts/npc/2083001.js b/scripts/npc/2083001.js index 9aa191d460..998382ca8a 100644 --- a/scripts/npc/2083001.js +++ b/scripts/npc/2083001.js @@ -59,9 +59,12 @@ function action(mode, type, selection) { cm.sendOk("The Horntail PQ has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } - cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nThis is the path to Horntail's lair. If you want to face him, you and your team shall be tested on the trial grounds ahead.#b\r\n#L0#Let us pass to the trial grounds.\r\n#L1#I would like to hear more details."); + cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nThis is the path to Horntail's lair. If you want to face him, you and your team shall be tested on the trial grounds ahead.#b\r\n#L0#Let us pass to the trial grounds.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details."); } else if (status == 1) { if (selection == 0) { if (cm.getParty() == null) { @@ -83,6 +86,9 @@ function action(mode, type, selection) { cm.dispose(); } + } else if(selection == 1) { + cm.sendOk("Try using a Super Megaphone or asking your buddies or guild to join!"); + cm.dispose(); } else { cm.sendOk("#e#b#k#n\r\nAs the gatekeeper of Horntail's lair, I will grant access #bjust to those worthy#k of his presence. Even for those people, the path inside is that of a maze, full of branches and trials. However, those #radept at fighting squad bosses#k have a better chance to stand to our leader, although those #rof our kind#k have a shabby chance as well."); cm.dispose(); diff --git a/scripts/npc/2094000.js b/scripts/npc/2094000.js index 478a611f7e..7dcd4ffaba 100644 --- a/scripts/npc/2094000.js +++ b/scripts/npc/2094000.js @@ -52,6 +52,9 @@ function action(mode, type, selection) { cm.sendOk("The Pirate PQ has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nHelp! My son has been kidnapped and is bound on the hands of the fearful #rLord Pirate#k. I need your help... Would you please assemble or join a team to save him? Have your #bparty leader#k talk to me or make yourself a party.#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details."); diff --git a/scripts/npc/2112003.js b/scripts/npc/2112003.js index 419372d9b3..6c2872fc07 100644 --- a/scripts/npc/2112003.js +++ b/scripts/npc/2112003.js @@ -60,6 +60,9 @@ function action(mode, type, selection) { cm.sendOk("The Magatia PQ (Alcadno) has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nMy beloved Romeo has been kidnapped! Although he is Zenumist's, I can't stand by and just see him suffer just because of this foolish clash. I need you and your colleagues help to save him! Please, help us!! Please have your #bparty leader#k talk to me.#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details."); diff --git a/scripts/npc/2112004.js b/scripts/npc/2112004.js index d889eca094..ef66127ef4 100644 --- a/scripts/npc/2112004.js +++ b/scripts/npc/2112004.js @@ -60,6 +60,9 @@ function action(mode, type, selection) { cm.sendOk("The Magatia PQ (Zenumist) has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nMy beloved Juliet has been kidnapped! Although she is Alcadno's, I can't stand by and just see her suffer just because of this foolish clash. I need you and your colleagues help to save her! Please, help us!! Please have your #bparty leader#k talk to me.#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details."); diff --git a/scripts/npc/2133000.js b/scripts/npc/2133000.js index 51644eac1f..918d61cd07 100644 --- a/scripts/npc/2133000.js +++ b/scripts/npc/2133000.js @@ -52,6 +52,9 @@ function action(mode, type, selection) { cm.sendOk("The Ellin PQ has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nWould you like to assemble or join a team to solve the puzzles of the #bForest of Poison Haze#k? Have your #bparty leader#k talk to me or make yourself a party.#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details.\r\n#L3#I would like to reclaim a prize."); diff --git a/scripts/npc/9000020.js b/scripts/npc/9000020.js index 6e218669a5..d9fda2f36b 100644 --- a/scripts/npc/9000020.js +++ b/scripts/npc/9000020.js @@ -37,7 +37,7 @@ function action(mode, type, selection) { } if (cm.getPlayer().getMapId() == 800000000) { if (status == 0) - cm.sendSimple("How's the traveling? Are you enjoying it?#b\r\n#L0#Yes, I'm done with traveling. Can I go back to #m" + cm.getPlayer().getSavedLocation("WORLDTOUR") + "#?\r\n#L1#No, I'd like to continue exploring this place."); + cm.sendSimple("How's the traveling? Are you enjoying it?#b\r\n#L0#Yes, I'm done with traveling. Can I go back to #m" + cm.getPlayer().peekSavedLocation("WORLDTOUR") + "#?\r\n#L1#No, I'd like to continue exploring this place."); else if (status == 1) { if (selection == 0) { cm.sendNext("Alright. I'll take you back to where you were before the visit to Japan. If you ever feel like traveling again down the road, please let me know!"); @@ -47,7 +47,7 @@ function action(mode, type, selection) { } } else if (status == 2) { var map = cm.getPlayer().getSavedLocation("WORLDTOUR"); - if (map == undefined) map = 104000000; + if (map == -1) map = 104000000; cm.warp(map); cm.dispose(); diff --git a/scripts/npc/9000037.js b/scripts/npc/9000037.js index 3a23e3e129..8822be13df 100644 --- a/scripts/npc/9000037.js +++ b/scripts/npc/9000037.js @@ -117,8 +117,11 @@ function action(mode, type, selection) { cm.sendOk("The Boss Rush PQ has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } - + cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nWould you like to collaborate with party members to complete the expedition, or are you brave enough to take it on all by yourself? Have your #bparty leader#k talk to me or make yourself a party.#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details."); } } else if (status == 1) { diff --git a/scripts/npc/9000040.js b/scripts/npc/9000040.js new file mode 100644 index 0000000000..a748d23afa --- /dev/null +++ b/scripts/npc/9000040.js @@ -0,0 +1,49 @@ +/* + 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 . +*/ +/* Dalair + Medal NPC. + */ + +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.sendOk("The medal ranking system is currently unavailable."); + cm.dispose(); + } + } +} diff --git a/scripts/npc/9000041.js b/scripts/npc/9000041.js index c515697958..24429a1cd5 100644 --- a/scripts/npc/9000041.js +++ b/scripts/npc/9000041.js @@ -29,10 +29,6 @@ var name; var status; var selectedType = 0; -function numberWithCommas(x) { // I ain't interessed in finding a way to parse java int to something js will accept through toLocaleString, so be it! - return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); -} - function start() { status = -1; action(1, 0, 0); @@ -61,7 +57,7 @@ function action(mode, type, selection) { name = cm.getText(); var res = cm.getPlayer().sellAllItemsFromName(selectedType + 1, name); - if(res > -1) cm.sendOk("Transaction complete! You received #r" + numberWithCommas(res) + " mesos#k from this action."); + if(res > -1) cm.sendOk("Transaction complete! You received #r" + cm.numberWithCommas(res) + " mesos#k from this action."); else cm.sendOk("There is no #b'" + name + "'#k in your #b" + options[selectedType] + "#k inventory!"); cm.dispose(); diff --git a/scripts/npc/9020000.js b/scripts/npc/9020000.js index a162a371c5..d29c90da81 100644 --- a/scripts/npc/9020000.js +++ b/scripts/npc/9020000.js @@ -57,6 +57,9 @@ function action(mode, type, selection) { if(em == null) { cm.sendOk("The Kerning PQ has encountered an error."); cm.dispose(); + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nHow about you and your party members collectively beating a quest? Here you'll find obstacles and problems where you won't be able to beat it without great teamwork. If you want to try it, please tell the #bleader of your party#k to talk to me.#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details."); diff --git a/scripts/npc/9103001.js b/scripts/npc/9103001.js index 70ae2af23c..f89c260448 100644 --- a/scripts/npc/9103001.js +++ b/scripts/npc/9103001.js @@ -56,6 +56,9 @@ function action(mode, type, selection) { cm.sendOk("The Ludibrium Maze PQ has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nThis is the entrance to the Ludibrium Maze. Enjoy!\r\n#b#L0#Enter the Lubidrium Maze#l\r\n#L1#I want to find party members.\r\n#L2#What is the Ludibrium Maze?"); diff --git a/scripts/npc/9201000.js b/scripts/npc/9201000.js index 9f5827aff7..d7c5c871ce 100644 --- a/scripts/npc/9201000.js +++ b/scripts/npc/9201000.js @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 @@ -19,123 +17,202 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/** - *Moony - 9201000.js - *@author Jvlaple - *For HurricaneMS v.59 +/* Moony + Amoria (680000000) + Engagement ring NPC. */ -//var numberOfLoves = 0; -//var ringSelection = -1; - -function start() { - // status = -1; - // action(1, 0, 0); - //} - //function action(mode, type, selection) { - // if (mode == -1) { - // cm.dispose(); - // } else { - // if (mode == 0) { - // cm.dispose(); - // return; - // } - // if (mode == 1) { - // status++; - // } else { - // status--; - // } - // if (status == 0) { - // if (cm.getPlayer().getMarriageQuestLevel() == 0 && cm.getPlayer().getLevel() >= 10) { - // cm.sendNext("Hey, I'm Moony, and I make engagement rings for marriage."); - // } else if (cm.getPlayer().getMarriageQuestLevel() == 1) { - // for (var i = 4031367; i < 4031373; i++) - // numberOfLoves += cm.getPlayer().countItem(i); - // if (numberOfLoves >= 4) { - // cm.sendNext("Wow, you're back pretty early. Got the #bProof of Loves#k? Lets see..."); - // } else { - // cm.sendOk("Please come back when you got 4 different #bProof of Loves#k."); - // cm.dispose(); - // } - // } else if (cm.getPlayer().getMarriageQuestLevel() == 2) { - // cm.sendSimple("Hey, your'e back! Ready to choose your ring?\r\n#b#L0#Moonstone Ring#l\r\n#L1#Star Gem Ring#l\r\n#L2#Golden Heart Ring#l\r\n#L3#Silver Swan Ring#l#k"); - // } else { - cm.sendOk("I hate making rings..."); - cm.dispose(); -// } -// } else if (status == 1) { -// if (cm.getPlayer().getMarriageQuestLevel() == 0 && cm.getPlayer().getLevel() >= 10) { -// cm.sendYesNo("Hey, you look like you might want to be married! Want to make an engagement ring?"); -// } else if (cm.getPlayer().getMarriageQuestLevel() == 1) { -// cm.sendNext("Great work getting the #bProof of Loves#k! Now we can make the #bEngagement Ring#k."); -// } else if (cm.getPlayer().getMarriageQuestLevel() == 2) { -// ringSelection = selection; -// if (ringSelection == 0) { -// if (cm.haveItem(4011007, 1) && cm.haveItem(4021007, 1) && cm.getPlayer().getMeso() >= 3000000) { -// cm.gainItem(4011007, -1); -// cm.gainItem(4021007, -1); -// cm.gainMeso(-3000000); -// cm.gainItem(2240000, 1); -// cm.sendOk("Here's the ring as promised! Have fun!"); -// cm.getPlayer().setMarriageQuestLevel(50); -// cm.dispose(); -// } else { -// cm.sendNext("You did not get all the right materials. To make an engagement ring, I need one of the following:\r\n\r\n#e#dMoonstone Ring:#k\r\n#v4011007#Moon Rock 1,#v4021007#Diamond 1, 3,000,000 Meso\r\n#dStar Gem Ring:#k\r\n#v4021009#Star Rock 1,#v4021007#Diamond 1, 2,000,000 Meso\r\n#dGolden Heart Ring:#k\r\n#v4011006#Gold Plate 1,#v4021007#Diamond 1, 1,000,000 Meso\r\n#dSilver Swan Ring:#k\r\n#v4011004#Silver Plate 1,#v4021007#Diamond 1, 500,000 Meso\r\n"); -// cm.dispose(); -// } -// } else if (ringSelection == 1) { -// if (cm.haveItem(4021009, 1) && cm.haveItem(4021007, 1) && cm.getPlayer().getMeso() >= 2000000) { -// cm.gainItem(4021009, -1); -// cm.gainItem(4021007, -1); -// cm.gainMeso(-2000000); -// cm.gainItem(2240001, 1); -// cm.sendOk("Here's the ring as promised! Have fun!"); -// cm.getPlayer().setMarriageQuestLevel(50); -// cm.dispose(); -// } else { -// cm.sendNext("You did not get all the right materials. To make an engagement ring, I need one of the following:\r\n\r\n#e#dMoonstone Ring:#k\r\n#v4011007#Moon Rock 1,#v4021007#Diamond 1, 3,000,000 Meso\r\n#dStar Gem Ring:#k\r\n#v4021009#Star Rock 1,#v4021007#Diamond 1, 2,000,000 Meso\r\n#dGolden Heart Ring:#k\r\n#v4011006#Gold Plate 1,#v4021007#Diamond 1, 1,000,000 Meso\r\n#dSilver Swan Ring:#k\r\n#v4011004#Silver Plate 1,#v4021007#Diamond 1, 500,000 Meso\r\n"); -// cm.dispose(); -// } -// } else if (ringSelection == 2) { -// if (cm.haveItem(4011006, 1) && cm.haveItem(4021007, 1) && cm.getPlayer().getMeso() >= 1000000) { -// cm.gainItem(4011006, -1); -// cm.gainItem(4021007, -1); -// cm.gainMeso(-1000000); -// cm.gainItem(2240002, 1); -// cm.sendOk("Here's the ring as promised! Have fun!"); -// cm.getPlayer().setMarriageQuestLevel(50); -// cm.dispose(); -// } else { -// cm.sendNext("You did not get all the right materials. To make an engagement ring, I need one of the following:\r\n\r\n#e#dMoonstone Ring:#k\r\n#v4011007#Moon Rock 1,#v4021007#Diamond 1, 3,000,000 Meso\r\n#dStar Gem Ring:#k\r\n#v4021009#Star Rock 1,#v4021007#Diamond 1, 2,000,000 Meso\r\n#dGolden Heart Ring:#k\r\n#v4011006#Gold Plate 1,#v4021007#Diamond 1, 1,000,000 Meso\r\n#dSilver Swan Ring:#k\r\n#v4011004#Silver Plate 1,#v4021007#Diamond 1, 500,000 Meso\r\n"); -// cm.dispose(); -// } -// } else if (ringSelection == 3) { -// if (cm.haveItem(4011004, 1) && cm.haveItem(4021007, 1) && cm.getPlayer().getMeso() >= 500000) { -// cm.gainItem(4011004, -1); -// cm.gainItem(4021007, -1); -// cm.gainMeso(-500000); -// cm.gainItem(2240003, 1); -// cm.sendOk("Here's the ring as promised! Have fun!"); -// cm.getPlayer().setMarriageQuestLevel(50); -// cm.dispose(); -// } else { -// cm.sendNext("You did not get all the right materials. To make an engagement ring, I need one of the following:\r\n\r\n#e#dMoonstone Ring:#k\r\n#v4011007#Moon Rock 1,#v4021007#Diamond 1, 3,000,000 Meso\r\n#dStar Gem Ring:#k\r\n#v4021009#Star Rock 1,#v4021007#Diamond 1, 2,000,000 Meso\r\n#dGolden Heart Ring:#k\r\n#v4011006#Gold Plate 1,#v4021007#Diamond 1, 1,000,000 Meso\r\n#dSilver Swan Ring:#k\r\n#v4011004#Silver Plate 1,#v4021007#Diamond 1, 500,000 Meso\r\n"); -// cm.dispose(); -// } -// } -// } -// } else if (status == 2) { -// if (cm.getPlayer().getMarriageQuestLevel() == 0 && cm.getPlayer().getLevel() >= 10) { -// cm.getPlayer().addMarriageQuestLevel(); -// cm.sendOk("Okay, first bring me back any four colored #bProof of Loves#k. You can get them from talking to #bNana the Love Fairy#k in any town. Also, only one of you, either the Groom or Bride will do this quest."); -// cm.dispose(); -// } else if (cm.getPlayer().getMarriageQuestLevel() == 1) { -// for (var j = 4031367; j < 4031373; j++) -// cm.removeAll(j); -// cm.getPlayer().addMarriageQuestLevel(); -// cm.sendNextPrev("You need the following raw materials to make an\r\n#bEngagement Ring#k.\r\n\r\n#e#dMoonstone Ring:#k\r\n#v4011007#Moon Rock 1,#v4021007#Diamond 1, 3,000,000 Meso\r\n#dStar Gem Ring:#k\r\n#v4021009#Star Rock 1,#v4021007#Diamond 1, 2,000,000 Meso\r\n#dGolden Heart Ring:#k\r\n#v4011006#Gold Plate 1,#v4021007#Diamond 1, 1,000,000 Meso\r\n#dSilver Swan Ring:#k\r\n#v4011004#Silver Plate 1,#v4021007#Diamond 1, 500,000 Meso\r\n"); -// cm.dispose(); -// } -// } -// } +var status; +var state; + +var item; +var mats; +var matQty; +var cost; + +var options; + +function hasProofOfLoves(player) { + var count = 0; + + for(var i = 4031367; i <= 4031372; i++) { + if(player.haveItem(i)) { + count++; + } + } + + return count >= 4; +} + +function hasEngagementBox(player) { + for(var i = 2240000; i <= 2240003; i++) { + if(player.haveItem(i)) { + return true; + } + } + + return false; +} + +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) { + options = ["I want to make a ring.", "I want to discard the ring box I have."]; + cm.sendSimple("I'm #p9201000#, the #bengagement ring maker#k. How can I help you?\r\n\r\n#b" + generateSelectionMenu(options)); + } else if(status == 1) { + if(selection == 0) { + if(!cm.isQuestCompleted(100400)) { + if(!cm.isQuestStarted(100400)) { + state = 0; + cm.sendAcceptDecline("So you want to make a engagement ring, huh? Very well, I can provide one for you if you pass my test. Certainly you must have already seen #rNanas, the fairies of Love#k, around the Maple world. From 4 of them, collect #b4 #t4031367#'s#k and bring them here. Only then I'll accept you as a proper ring holder. Are you up to it?"); + } else { + if(!hasProofOfLoves(cm.getPlayer())) { + cm.sendOk("Please bring here #b4 #t4031367#'s#k. That's a must for me to accept you as a proper holder for the wedding ring."); + } else { + cm.completeQuest(100400); + cm.gainExp(20000 * cm.getPlayer().getExpRate()); + + for(var i = 4031367; i <= 4031372; i++) { + cm.removeAll(i); + } + + cm.sendOk("You brought the #t4031367#'s, good. From now on you are eligible for holding the rings I make. Talk to me again to start forging the kind of ring you want."); + } + + cm.dispose(); + } + } else { + if(hasEngagementBox(cm.getPlayer())) { + cm.sendOk("Sorry, you already have an engagement box. I cannot provide you more than one box per time."); + cm.dispose(); + return; + } + if(cm.getPlayer().getGender() != 0) { + cm.sendOk("Sorry, but the ring box is currently available only for males."); + cm.dispose(); + return; + } + + state = 1; + options = ["Moonstone","Star Gem","Golden Heart", "Silver Swan"]; + var selStr = "So, what kind of engagement ring you want me to craft?\r\n\r\n#b" + generateSelectionMenu(options); + cm.sendSimple(selStr); + } + } else { + if(hasEngagementBox(cm.getPlayer())) { + for(var i = 2240000; i <= 2240003; i++) { + cm.removeAll(i); + } + + cm.sendOk("Your ring box has been discarded."); + } else { + cm.sendOk("You have no ring box to discard."); + } + + cm.dispose(); + } + } else if(status == 2) { + if(state == 0) { + cm.startQuest(100400); + cm.sendOk("Very well, then go after these #t4031367#'s. I will be waiting here."); + + cm.dispose(); + } else { + var itemSet = new Array(2240000,2240001,2240002,2240003); + var matSet = new Array(new Array(4011007,4021007),new Array(4021009,4021007),new Array(4011006,4021007),new Array(4011004,4021007)); + var matQtySet = new Array(new Array(1,1),new Array(1,1),new Array(1,1),new Array(1,1)); + var costSet = new Array (30000,20000,10000,5000); + + item = itemSet[selection]; + mats = matSet[selection]; + matQty = matQtySet[selection]; + cost = costSet[selection]; + + var prompt = "Then I'm going to craft you a #b#t" + item + "##k, is that right?"; + prompt += " In that case, I'm going to need specific items from you in order to make it. Make sure you have room in your inventory, though!#b"; + + if (mats instanceof Array){ + for(var i = 0; i < mats.length; i++){ + prompt += "\r\n#i"+mats[i]+"# " + matQty[i] + " #t" + mats[i] + "#"; + } + } + else { + prompt += "\r\n#i"+mats+"# " + matQty + " #t" + mats + "#"; + } + + if (cost > 0) + prompt += "\r\n#i4031138# " + cost + " meso"; + + cm.sendYesNo(prompt); + } + } else if(status == 3) { + var complete = true; + var recvItem = item, recvQty = 1, qty = 1; + + if(!cm.canHold(recvItem, recvQty)) { + cm.sendOk("Check your inventory for a free slot first."); + cm.dispose(); + return; + } + else if (cm.getMeso() < cost * qty) + { + cm.sendOk("I'm sorry but there's a fee for my services. Please bring me the right amount of mesos here before trying to forge a ring."); + cm.dispose(); + return; + } + else + { + if (mats instanceof Array) { + for(var i = 0; complete && i < mats.length; i++) + if (!cm.haveItem(mats[i], matQty[i] * qty)) + complete = false; + } + else if (!cm.haveItem(mats, matQty * qty)) + complete = false; + } + + if (!complete) + cm.sendOk("Hm, it seems you're lacking some ingredients for the engagement ring. Please provide them first, will you?"); + else { + if (mats instanceof Array) { + for (var i = 0; i < mats.length; i++){ + cm.gainItem(mats[i], -matQty[i] * qty); + } + } + else + cm.gainItem(mats, -matQty * qty); + + if (cost > 0) + cm.gainMeso(-cost * qty); + + cm.gainItem(recvItem, recvQty); + cm.sendOk("All done, the engagement ring came out just right. I wish you a happy engagement."); + } + cm.dispose(); + } + } +} + +function generateSelectionMenu(array) { + var menu = ""; + for (var i = 0; i < array.length; i++) { + menu += "#L" + i + "#" + array[i] + "#l\r\n"; + } + return menu; } \ No newline at end of file diff --git a/scripts/npc/9201001.js b/scripts/npc/9201001.js index 6c3a4ca1cb..e03ad10988 100644 --- a/scripts/npc/9201001.js +++ b/scripts/npc/9201001.js @@ -1,17 +1,83 @@ /* -Credits go to Travis of DeanMS ( xKillsAlotx on RaGEZONE) -Item Exchanger for scrolls + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 RonanLana -Modified by SharpAceX (Alan) for MapleSolaxia + 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 . */ +/* Nana, the Love fairy + Amoria (680000000) + Engagement ring NPC. + */ -importPackage(Packages.tools); +var status; +var state; -var status = 0; -var leaf = 4001126; -var chairs = new Array(3010000, 3010001, 3010002, 3010003, 3010004, 3010005, 3010006, 3010007, 3010008, 3010009, 3010010, 3010011, 3010012, 3010013, 3010015, 3010016, 3010017, 3010018, 3010019, 3010022, 3010023, 3010024, 3010025, 3010026, 3010028, 3010040, 3010041, 3010043, 3010045, 3010046, 3010047,3010057,3010058,3010060,3010061,3010062,3010063, 3010064,3010065,3010066,3010067,3010069,3010071,3010072,3010073,3010080,3010081,3010082,3010083, 3010084,3010085,3010097,3010098,3010099,3010101,3010106,3010116,3011000,3012005,3012010,3012011); -var scrolls = new Array(2040603,2044503,2041024,2041025,2044703,2044603,2043303,2040807,2040806,2040006,2040007,2043103,2043203,2043003,2040506,2044403,2040903,2040709,2040710,2040711,2044303,2043803,2040403,2044103,2044203,2044003,2043703); -var weapons = new Array(1302020, 1302030, 1302033, 1302058, 1302064, 1302080, 1312032, 1322054, 1332025, 1332055, 1332056, 1372034, 1382009, 1382012, 1382039, 1402039, 1412011, 1412027, 1422014, 1422029, 1432012, 1432040, 1432046, 1442024, 1442030, 1442051, 1452016, 1452022, 1452045, 1462014, 1462019, 1462040, 1472030, 1472032, 1472055, 1482020, 1482021, 1482022, 1492020, 1492021, 1492022, 1092030, 1092045, 1092046, 1092047); +var item; +var mats; +var matQty; +var cost; + +var options; + +function hasProofOfLoves(player) { + var count = 0; + + for(var i = 4031367; i <= 4031372; i++) { + if(player.haveItem(i)) { + count++; + } + } + + return count >= 4; +} + +function getNanaLocation(player) { + var mapid = player.getMap().getId(); + + for(var i = 0; i < mapids.length; i++) { + if(mapid == mapids[i]) { + return i; + } + } + + return -1; +} + +var nanaLoc; +var mapids = [100000000, 103000000, 102000000, 101000000, 200000000, 220000000]; +var questItems = [4000001, 4000037, 4000215, 4000026, 4000070, 4000128]; +var questExp = [2000, 5000, 10000, 17000, 22000, 30000]; + +function processNanaQuest() { + if(cm.haveItem(questItems[nanaLoc], 50)) { + if(cm.canHold(4031367 + nanaLoc, 1)) { + cm.gainItem(questItems[nanaLoc], -50); + cm.gainItem(4031367 + nanaLoc, 1); + + cm.sendOk("Kyaaaa~ Thank you a lot, here get the #b#t4031367##k."); + return true; + } else { + cm.sendOk("Please have a free ETC slot available to hold the token of love."); + } + } else { + cm.sendOk("Please gather to me #b50 #t" + questItems[nanaLoc] + "##k."); + } + + return false; +} function start() { status = -1; @@ -19,102 +85,61 @@ function start() { } function action(mode, type, selection) { - if (mode == -1) + if (mode == -1) { cm.dispose(); - else { - if (mode == 0 && status == 0) + } else { + if (mode == 0 && type > 0) { cm.dispose(); + return; + } if (mode == 1) status++; else status--; - if (status == 0) { - cm.sendSimple("Hello#b #h ##k, you currently have #b#c4001126# maple leaves.#k \r\nWhat would you like to do?\r\n#k#L1# Trade 1 leaf for 5,000 NX#l\r\n\#L2# Trade 1 leaf for 1 random chair #l\r\n\#L3# Trade 1 leaf for 3 random Maple Weapons #l\r\n\#L4# Trade 1 leaf for 3 Swiss Cheese and Onyx Apples#l\r\n#L5#Trade 1 leaf for a 10 day Hired Merchant#l"); - } else if (status == 1) { - if (selection == 1) { - if(cm.haveItem(leaf, 1)) { - cm.getPlayer().getCashShop().gainCash(1, 5000); - cm.getPlayer().announce(MaplePacketCreator.earnTitleMessage("You have earned 5,000 NX")); - cm.gainItem(leaf, -1); - cm.sendOk("Here is your 5,000 NX!"); - cm.logLeaf("5k NX"); + + if(status == 0) { + if(!cm.isQuestStarted(100400)) { + cm.sendOk("Hello #b#h0##k, I'm #p9201001# the fairy of Love."); + cm.dispose(); + return; + } + + nanaLoc = getNanaLocation(cm.getPlayer()); + if(nanaLoc == -1) { + cm.sendOk("Hello #b#h0##k, I'm #p9201001# the fairy of Love."); + cm.dispose(); + return; + } + + if(!cm.haveItem(4031367 + nanaLoc, 1)) { + if(cm.isQuestCompleted(100401 + nanaLoc)) { + state = 1; + cm.sendAcceptDecline("Did you lost the #k#t4031367##k I gave to you? Well, I can share another one with you, but you will need to redo the favor I asked last time, is that ok? I need you to bring me #r50 #t" + questItems[nanaLoc] + "#'s.#k"); + } else if(cm.isQuestStarted(100401 + nanaLoc)) { + if(processNanaQuest()) { + cm.gainExp(questExp[nanaLoc] * cm.getPlayer().getExpRate()); + cm.completeQuest(100401 + nanaLoc); + } + + cm.dispose(); } else { - cm.sendOk("Sorry, you don't have a maple leaf!"); - } + state = 0; + cm.sendAcceptDecline("Are you searching for #k#t4031367#'s#k? I can share one with you, but you must do a favor for me, is that ok?"); + } + } else { + cm.sendOk("Hey there. Did you get the #t4031367# from the other Nana's already?"); cm.dispose(); - } else if (selection == 2) { - if(cm.haveItem(leaf, 1)) { - var chair1 = chairs[Math.floor(Math.random()*chairs.length)]; - if(cm.canHold(chair1)){ - cm.gainItem(chair1); - cm.gainItem(leaf, -1); - cm.sendOk("Here is your random chair!"); - cm.logLeaf("Chair ID: " + chair1); - } else { - cm.sendOk("Please make sure you have enough space to hold this chair!"); - } - } else { - cm.sendOk("Sorry, you don't have a maple leaf!"); - } + } + } else if(status == 1) { + if(state == 0) { + cm.startQuest(100401 + nanaLoc); + + cm.sendOk("I need you to collect #r50 #t" + questItems[nanaLoc] + "##k."); cm.dispose(); - } else if (selection == 3) { - if(cm.haveItem(leaf, 1)) { - var weapon1 = weapons[Math.floor(Math.random()*weapons.length)]; - var weapon2 = weapons[Math.floor(Math.random()*weapons.length)]; - var weapon3 = weapons[Math.floor(Math.random()*weapons.length)]; - if(!cm.getPlayer().getInventory(Packages.client.inventory.MapleInventoryType.EQUIP).isFull(3)) { - cm.gainItem(weapon1, 1, true, true); - cm.gainItem(weapon2, 1, true, true); - cm.gainItem(weapon3, 1, true, true); - cm.gainItem(leaf, -1); - cm.sendOk("Here are your 3 random weapons!"); - cm.logLeaf("Maple Weapons IDs: " + weapon1 + "," + weapon2 + "," + weapon3); - } else { - cm.sendOk("Please make sure you have enough space to hold these weapons!"); - } - } else { - cm.sendOk("Sorry, you don't have a maple leaf!"); - } + } else { + processNanaQuest(); cm.dispose(); - } else if (selection == 4) { - if(cm.haveItem(leaf, 1)) { - var cheese = 2022273; - var apple = 2022179; - if(!cm.getPlayer().getInventory(Packages.client.inventory.MapleInventoryType.EQUIP).isFull(2)){ - cm.gainItem(apple, 3); - cm.gainItem(cheese, 3); - cm.gainItem(leaf, -1); - cm.sendOk("Here are your 3 cheeses and apples!"); - cm.logLeaf("3 cheeses and apples"); - } else { - cm.sendOk("Please make sure you have enough space to hold these items!"); - } - } else { - cm.sendOk("Sorry, you don't have a maple leaf!"); - } - cm.dispose(); - } else if(selection == 5) { - if(cm.haveItem(leaf, 1)) { - if(!cm.haveItem(5030000, 1)) { - if(!cm.getPlayer().getInventory(Packages.client.inventory.MapleInventoryType.CASH).isFull(1)){ - cm.gainItem(5030000, 1, false, true, 1000 * 60 * 60 * 24 * 10); - cm.gainItem(leaf, -1); - cm.sendOk("Here is your Hired Merchant!"); - cm.logLeaf("10 day hired merchant"); - } else { - cm.sendOk("Please make sure you have enough space to hold these items!"); - } - } else { - cm.sendOk("I can't give you a merchant if you already have one!"); - } - } else { - cm.sendOk("Sorry, you don't have a maple leaf!"); - } - cm.dispose(); - } else { - cm.sendOk("Come back later!"); - cm.dispose(); - } + } } } } \ No newline at end of file diff --git a/scripts/npc/9201002.js b/scripts/npc/9201002.js index 2ebeae654a..307746521e 100644 --- a/scripts/npc/9201002.js +++ b/scripts/npc/9201002.js @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 @@ -19,34 +17,398 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/* NPC Base - Map Name (Map ID) - Extra NPC info. +/* High Priest John + Marriage NPC */ +importPackage(Packages.constants); +importPackage(Packages.net.server.channel.handlers); +importPackage(Packages.tools); +importPackage(Packages.tools.packets); + var status; - -function start() { - status = -1; - action(1, 0, 0); +var state; +var eim; +var weddingEventName = "WeddingCathedral"; +var cathedralWedding = true; +var weddingIndoors; +var weddingBlessingExp = ServerConstants.WEDDING_BLESS_EXP; + +function isWeddingIndoors(mapid) { + return mapid >= 680000100 && mapid <= 680000500; +} + +function getMarriageInstance(player) { + var em = cm.getEventManager(weddingEventName); + + for (var iterator = em.getInstances().iterator(); iterator.hasNext();) { + var eim = iterator.next(); + if(eim.isEventLeader(player)) { + return eim; + } + } + + return null; +} + +function detectPlayerItemid(player) { + for (var x = 4031357; x <= 4031364; x++) { + if (player.haveItem(x)) { + return x; + } + } + + return -1; +} + +function getRingId(boxItemId) { + return boxItemId == 4031357 ? 1112803 : (boxItemId == 4031359 ? 1112806 : (boxItemId == 4031361 ? 1112807 : (boxItemId == 4031363 ? 1112809 : -1))); +} + +function isSuitedForWedding(player, equipped) { + var baseid = (player.getGender() == 0) ? 1050131 : 1051150; + + if(equipped) { + for(var i = 0; i < 4; i++) { + if(player.haveItemEquipped(baseid + i)) { + return true; + } + } + } else { + for(var i = 0; i < 4; i++) { + if(player.haveItemWithId(baseid + i, true)) { + return true; + } + } + } + + return false; +} + +function getWeddingPreparationStatus(player, partner) { + if(!player.haveItem(4000313)) return -3; + if(!partner.haveItem(4000313)) return 3; + + if(!isSuitedForWedding(player, true)) return -4; + if(!isSuitedForWedding(partner, true)) return 4; + + var hasEngagement = false; + for (var x = 4031357; x <= 4031364; x++) { + if (player.haveItem(x)) { + hasEngagement = true; + break; + } + } + if(!hasEngagement) return -1; + + hasEngagement = false; + for (var x = 4031357; x <= 4031364; x++) { + if (partner.haveItem(x)) { + hasEngagement = true; + break; + } + } + if(!hasEngagement) return -2; + + if(!player.canHold(1112803)) return 1; + if(!partner.canHold(1112803)) return 2; + + return 0; +} + +function giveCoupleBlessings(eim, player, partner) { + var blessCount = eim.gridSize(); + + player.gainExp(blessCount * weddingBlessingExp); + partner.gainExp(blessCount * weddingBlessingExp); +} + +function start() { + weddingIndoors = isWeddingIndoors(cm.getMapId()); + if(weddingIndoors) eim = cm.getEventInstance(); + + 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.sendOk("Wedding is currently closed."); - cm.dispose(); - } + if (mode == -1) { + cm.dispose(); + } else { + if (mode == 0 && type > 0) { + cm.dispose(); + return; } + if (mode == 1) + status++; + else + status--; + + if(!weddingIndoors) { + if(status == 0) { + var hasEngagement = false; + for (var x = 4031357; x <= 4031364; x++) { + if (cm.haveItem(x, 1)) { + hasEngagement = true; + break; + } + } + + if(hasEngagement) { + var text = "Hi there. How can I help you?"; + var choice = new Array("We're ready to get married."); + for (x = 0; x < choice.length; x++) { + text += "\r\n#L" + x + "##b" + choice[x] + "#l"; + } + cm.sendSimple(text); + } else { + cm.sendOk("Hmm, today two fluttering hearts are about to be joined together by the blessings of love!"); + cm.dispose(); + } + } else if(status == 1) { + var wid = cm.getClient().getWorldServer().getRelationshipId(cm.getPlayer().getId()); + var cserv = cm.getClient().getChannelServer(); + + if(cserv.isWeddingReserved(wid)) { + if(wid == cserv.getOngoingWedding(cathedralWedding)) { + var partner = cserv.getPlayerStorage().getCharacterById(cm.getPlayer().getPartnerId()); + if(!(partner == null || !cm.getMap().equals(partner.getMap()))) { + if(!cm.canHold(4000313)) { + cm.sendOk("Please have a free ETC slot available to get the #b#t4000313##k."); + cm.dispose(); + return; + } else if(!partner.canHold(4000313)) { + cm.sendOk("Please let your partner know they must have a free ETC slot available to get the #b#t4000313##k."); + cm.dispose(); + return; + } else if(!isSuitedForWedding(cm.getPlayer(), false)) { + cm.sendOk("Please purchase a #rwedding garment#k for the ceremony, quickly! Without it I am not able to marry you."); + cm.dispose(); + return; + } else if(!isSuitedForWedding(partner, false)) { + cm.sendOk("Please let your partner know they must have a #rwedding garment#k ready for the ceremony."); + cm.dispose(); + return; + } + + cm.sendOk("Very well, the preparatives here are finished too. This indeed is a beautiful day, you two are truly blessed to marry on such a day. Let us begin the marriage!!"); + } else { + cm.sendOk("Hmm, it seems your partner is elsewhere... Please let them come here before starting the ceremony."); + cm.dispose(); + } + } else { + var placeTime = cserv.getWeddingReservationTimeLeft(wid); + + cm.sendOk("Have patience. Your wedding is set to happen at the #r" + placeTime + "#k."); + cm.dispose(); + } + } else { + cm.sendOk("Hmm, I'm sorry but there are no reservations made for you at this channel for the time being."); + cm.dispose(); + } + } else if(status == 2) { + var cserv = cm.getClient().getChannelServer(); + var wtype = cserv.getOngoingWeddingType(cathedralWedding); + + var partner = cserv.getPlayerStorage().getCharacterById(cm.getPlayer().getPartnerId()); + if(!(partner == null || !cm.getMap().equals(partner.getMap()))) { + if(cserv.acceptOngoingWedding(cathedralWedding)) { + var wid = cm.getClient().getWorldServer().getRelationshipId(cm.getPlayer().getId()); + if(wid > 0) { + var em = cm.getEventManager(weddingEventName); + if(em.startInstance(cm.getPlayer())) { + eim = getMarriageInstance(cm.getPlayer()); + if(eim != null) { + eim.setIntProperty("weddingId", wid); + eim.setIntProperty("groomId", cm.getPlayer().getId()); + eim.setIntProperty("brideId", cm.getPlayer().getPartnerId()); + eim.setIntProperty("isPremium", wtype ? 1 : 0); + + eim.registerPlayer(partner); + } else { + cm.sendOk("An unexpected error happened when locating the wedding event. Please try again later."); + } + + cm.dispose(); + } else { + cm.sendOk("An unexpected error happened before the wedding preparations. Please try again later."); + cm.dispose(); + } + } else { + cm.sendOk("An unexpected error happened before the wedding preparations. Please try again later."); + cm.dispose(); + } + } else { // partner already decided to start + cm.dispose(); + } + } else { + cm.sendOk("Hmm, it seems your partner is elsewhere... Please let them come here before starting the ceremony."); + cm.dispose(); + } + } + } else { + if (status == 0) { + if(eim == null) { + cm.warp(680000000,0); + cm.dispose(); + return; + } + + var playerId = cm.getPlayer().getId(); + if(playerId == eim.getIntProperty("groomId") || playerId == eim.getIntProperty("brideId")) { + var wstg = eim.getIntProperty("weddingStage"); + + if(wstg == 2) { + cm.sendYesNo("Very well, the guests has bestowed all their blessings to you now. The time has come, #rshould I make you Husband and Wife#k?"); + state = 1; + } else if(wstg == 1) { + cm.sendOk("While you two are making your wedding vows to each other, your guests are currently giving their blessings to you. This is a time of happiness for both of you, please rejoice the ceremony."); + cm.dispose(); + } else { + cm.sendOk("Congratulations on your wedding! Our ceremony is complete, you can head to #b#p9201007##k now, she will lead you and your guests to the afterparty. Cheers for your love!"); + cm.dispose(); + } + } else { + var wstg = eim.getIntProperty("weddingStage"); + if(wstg == 1) { + if(eim.gridCheck(cm.getPlayer()) != -1) { + cm.sendOk("Everyone give your blessings to this lovely couple!"); + cm.dispose(); + } else { + if(eim.getIntProperty("guestBlessings") == 1) { + cm.sendYesNo("Do you want to bless this couple?"); + state = 0; + } else { + cm.sendOk("Today we are gathered here to reunite this lively couple in matrimony!"); + cm.dispose(); + } + } + } else if(wstg == 3) { + cm.sendOk("The two loving birds are now married. What a lively day! Please #rget ready for the afterparty#k, it should start soon. Follow the married couple's lead."); + cm.dispose(); + } else { + cm.sendOk("The guest's blessing time has ended. Hang on, the couple will renew their vows very soon now. What a sight to see!"); + cm.dispose(); + } + } + } else if (status == 1) { + if(state == 0) { // give player blessings + eim.gridInsert(cm.getPlayer(), 1); + + if(ServerConstants.WEDDING_BLESSER_SHOWFX) { + var target = cm.getPlayer(); + target.announce(MaplePacketCreator.showSpecialEffect(9)); + target.getMap().broadcastMessage(target, MaplePacketCreator.showForeignEffect(target.getId(), 9), false); + } else { + var target = eim.getPlayerById(eim.getIntProperty("groomId")); + target.announce(MaplePacketCreator.showSpecialEffect(9)); + target.getMap().broadcastMessage(target, MaplePacketCreator.showForeignEffect(target.getId(), 9), false); + + target = eim.getPlayerById(eim.getIntProperty("brideId")); + target.announce(MaplePacketCreator.showSpecialEffect(9)); + target.getMap().broadcastMessage(target, MaplePacketCreator.showForeignEffect(target.getId(), 9), false); + } + + cm.sendOk("Your blessings have been added to their love. What a noble act for a lovely couple!"); + cm.dispose(); + } else { // couple wants to complete the wedding + var wstg = eim.getIntProperty("weddingStage"); + + if(wstg == 2) { + var pid = cm.getPlayer().getPartnerId(); + if(pid <= 0) { + cm.sendOk("It seems you are no longer engaged to your partner, just before the altar... Where did all that happiness you two had sported a while ago went?"); + cm.dispose(); + return; + } + + var player = cm.getPlayer(); + var partner = cm.getMap().getCharacterById(cm.getPlayer().getPartnerId()); + if(partner != null) { + state = getWeddingPreparationStatus(player, partner); + + switch(state) { + case 0: + var pid = eim.getIntProperty("confirmedVows"); + if(pid != -1) { + if(pid == player.getId()) { + cm.sendOk("You have already confirmed your vows. All that is left is for your partner to confirm now."); + } else { + eim.setIntProperty("weddingStage", 3); + var cmPartner = partner.getClient().getAbstractPlayerInteraction(); + + var playerItemId = detectPlayerItemid(player); + var partnerItemId = (playerItemId % 2 == 1) ? playerItemId + 1 : playerItemId - 1; + + var marriageRingId = getRingId((playerItemId % 2 == 1) ? playerItemId : partnerItemId); + + cm.gainItem(playerItemId, -1); + cmPartner.gainItem(partnerItemId, -1); + + RingActionHandler.giveMarriageRings(player, partner, marriageRingId); + player.setMarriageItemId(marriageRingId); + partner.setMarriageItemId(marriageRingId); + + //var marriageId = eim.getIntProperty("weddingId"); + //player.announce(Wedding.OnMarriageResult(marriageId, player, true)); + //partner.announce(Wedding.OnMarriageResult(marriageId, player, true)); + + giveCoupleBlessings(eim, player, partner); + + cm.getMap().dropMessage(6, "High Priest John: By the power vested in me through the mighty Maple tree, I now pronounce you Husband and Wife. You may kiss the bride!"); + eim.schedule("showMarriedMsg", 2 * 1000); + } + } else { + eim.setIntProperty("confirmedVows", player.getId()); + cm.getMap().dropMessage(6, "Wedding Assistant: " + player.getName() + " has confirmed vows! Alright, one step away to make it official. Tighten your seatbelts!"); + } + + break; + + case -1: + cm.sendOk("It seems you no longer have the ring/ring box you and your partner shared at the engagement time. Sorry, but that was needed for the wedding..."); + break; + + case -2: + cm.sendOk("It seems your partner no longer has the ring/ring box you two shared at the engagement time. Sorry, but that was needed for the wedding..."); + break; + + case -3: + cm.sendOk("It seems you don't have the #r#t4000313##k given at the entrance... Please find it, I can't marry you without that item in hands."); + break; + + case -4: + cm.sendOk("Pardon my rudiness, but the garments are a essential part of the ceremony. Please #rsuit yourself properly#k for a wedding."); + break; + + case 1: + cm.sendOk("Please make an EQUIP slot available to get the marriage ring, will you?"); + break; + + case 2: + cm.sendOk("Please let your partner know to make an EQUIP slot available to get the marriage ring, will you?"); + break; + + case 3: + cm.sendOk("It seems your partner don't have the #r#t4000313##k given at the entrance... Please find it, I can't marry you without that item in hands."); + break; + + case 4: + cm.sendOk("It seems your partner is not properly dressed for the wedding... Pardon my rudiness, but the garments are a essential part of the ceremony."); + break; + } + + cm.dispose(); + } else { + cm.sendOk("Hmm, it seems your partner is not here, before the altar... It is a pity, but I can't fulfill the wedding if your partner is not here."); + cm.dispose(); + } + } else { + cm.sendOk("You are now #bhusband and wife#k. Congratulations!"); + cm.dispose(); + } + } + } + } + } } \ No newline at end of file diff --git a/scripts/npc/9201004.js b/scripts/npc/9201004.js index cb21e30d7f..ccc11ca078 100644 --- a/scripts/npc/9201004.js +++ b/scripts/npc/9201004.js @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 @@ -19,11 +17,127 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/*9201091 - Ames - *@author Moogra -*/ +/* Amos the Wise + Amoria (680000000) + Wedding info. + */ + +importPackage(Packages.net.server.channel.handlers); + +var status; + +var rings = [1112806, 1112803, 1112807, 1112809]; +var divorceFee = 500000; +var ringObj; + +function getWeddingRingItemId(player) { + for (var i = 0; i < rings.length; i++) { + if (player.haveItemWithId(rings[i], false)) { + return rings[i]; + } + } + + return null; +} + +function hasEquippedWeddingRing(player) { + for (var i = 0; i < rings.length; i++) { + if (player.haveItemEquipped(rings[i])) { + return true; + } + } + + return false; +} function start() { - cm.warp(680000000, 0); - cm.dispose(); + 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) { + var questionStr = ["How can I engage someone?", "How can I marry?", "How can I divorce?"] + + if(!(!cm.getPlayer().isMarried() && getWeddingRingItemId(cm.getPlayer()))) questionStr.push("I want a divorce..."); + else questionStr.push("I wanna remove my old wedding ring..."); + + cm.sendSimple("Hello, welcome to #bAmoria#k, a beautiful land where maplers can find love and, if inspired enough, even marry. Do you have any questions about Amoria? Talk it to me.#b\r\n\r\n" + generateSelectionMenu(questionStr)); + } else if(status == 1) { + switch(selection) { + case 0: + cm.sendOk("The #bengagement process#k is as straightforward as it can be. Firstly one must start a prequest from the #bring maker, #p9201000##k. They must gather #b#t4031367#'s#k thoughout the Maple world.\r\nFrom the completion of the quest, the player will gain an engagement ring. With that in hand, declare yourself to someone you become fond of. Then, hope the person accepts your proposal."); + cm.dispose(); + break; + + case 1: + cm.sendOk("For the #bmarriage process#k you must be already engaged. The loving couple must choose a venue they want to hold their marriage. Amoria offers two: the #rCathedral#k and the #rChapel#k.\r\nThen, one of the partners must buy a #bWedding Ticket#k, available through the Cash Shop, and book their ceremony with the Wedding Assistant. Each partner will receive #rguest tickets#k to be distributed to their acquaintances."); + cm.dispose(); + break; + + case 2: + cm.sendOk("Unfortunately the love of long may fizzle someday. Well, I hope that's not the case for any loving couple that once married, is marrying today or is going to do so tomorrow. But, if that ever happens, I myself will be at service to make a safe divorce, by the fee of #r" + divorceFee + "#k mesos."); + cm.dispose(); + break; + + case 3: + ringObj = cm.getPlayer().getMarriageRing(); + if(ringObj == null) { + var itemid = getWeddingRingItemId(cm.getPlayer()); + + if(itemid != null) { + cm.sendOk("There you go, I've removed your old wedding ring."); + cm.gainItem(itemid, -1); + } else if(hasEquippedWeddingRing(cm.getPlayer())) { + cm.sendOk("If you want your old wedding ring removed, please unequip it before talking to me."); + } else { + cm.sendOk("You're not married to require a divorce from it."); + } + + cm.dispose(); + return; + } + + cm.sendYesNo("So, you want to divorce from your partner? Be sure, this process #bcannot be rollbacked#k by any means, it's supposed to be an ultimatum from which your ring will be destroyed as consequence. That said, do you #rreally want to divorce#k?"); + break; + } + } else if(status == 2) { + if(cm.getMeso() < divorceFee) { + cm.sendOk("You don't have the required amount of #r" + divorceFee + " mesos#k for the divorce fee."); + cm.dispose(); + return; + } else if(ringObj.equipped()) { + cm.sendOk("Please unequip your ring before trying to divorce."); + cm.dispose(); + return; + } + + cm.gainMeso(-divorceFee); + RingActionHandler.breakMarriageRing(cm.getPlayer(), ringObj.getItemId()); + cm.gainItem(ringObj.getItemId(), -1); + + cm.sendOk("You have divorced from your partner."); + cm.dispose(); + } + } +} + +function generateSelectionMenu(array) { + var menu = ""; + for (var i = 0; i < array.length; i++) { + menu += "#L" + i + "#" + array[i] + "#l\r\n"; + } + return menu; } \ No newline at end of file diff --git a/scripts/npc/9201005.js b/scripts/npc/9201005.js index eceec8ce7f..bce6ebecf9 100644 --- a/scripts/npc/9201005.js +++ b/scripts/npc/9201005.js @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 @@ -19,85 +17,290 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/** - Nicole --- By --------------------------------------------------------------------------------------------- - Angel (get31720 ragezone) --- Extra Info ------------------------------------------------------------------------------------- - Fixed by [happydud3] & [XotiCraze] ---------------------------------------------------------------------------------------------------- -**/ +/* Assistant Nicole + Marriage NPC + */ var status; -var x; -var hasEngageRing = false; +var wid; +var isMarrying; + +var cathedralWedding = true; +var weddingEventName = "WeddingCathedral"; +var weddingEntryTicketCommon = 5251000; +var weddingEntryTicketPremium = 5251003; +var weddingSendTicket = 4031395; +var weddingGuestTicket = 4031407; +var weddingAltarMapid = 680000210; +var weddingIndoors; + +function isWeddingIndoors(mapid) { + return mapid >= 680000100 && mapid <= 680000500; +} + +function hasSuitForWedding(player) { + var baseid = (player.getGender() == 0) ? 1050131 : 1051150; + + for(var i = 0; i < 4; i++) { + if(player.haveItemWithId(baseid + i, true)) { + return true; + } + } + + return false; +} + +function getMarriageInstance(weddingId) { + var em = cm.getEventManager(weddingEventName); + + for (var iterator = em.getInstances().iterator(); iterator.hasNext();) { + var eim = iterator.next(); + + if(eim.getIntProperty("weddingId") == weddingId) { + return eim; + } + } + + return null; +} + +function hasWeddingRing(player) { + var rings = [1112806, 1112803, 1112807, 1112809]; + for (var i = 0; i < rings.length; i++) { + if (player.haveItemWithId(rings[i], true)) { + return true; + } + } + + return false; +} function start() { - status = -1; + weddingIndoors = isWeddingIndoors(cm.getMapId()); + status = -1; + action(1, 0, 0); } function action(mode, type, selection) { - if (mode == -1 || mode == 0) { - cm.sendOk("Goodbye then"); + if (mode == -1) { + cm.dispose(); + } else { + if (mode == 0 && type > 0) { cm.dispose(); - return; - } else if (mode == 1) { - status++; - } else { - status--; + return; } - var item = new Array(4031360, 4031358, 4031362, 4031364); - for (x = 0; x < item.length && !hasEngageRing; x++) { - if (cm.haveItem(item[x], 1)) - hasEngageRing = true; - } - if (status == 0) { - var text = "I'm here to assist you on weddings !"; - var choice = new Array("How do I prepare a wedding?", "I have an engagement ring and need invites for my guests", "I am the bride/groom and I'd like to start the wedding", "I am the guest and I'd like to go into the wedding"); - for (x = 0; x < choice.length; x++) { - text += "\r\n#L" + x + "##b" + choice[x] + "#l"; - } - cm.sendSimple(text); - } else if (status == 1) { - switch(selection) { - case 0: - cm.sendOk("Moony makes the engagement ring. The engagement ring is required throughout the wedding so never lose it. To invite your guests into the wedding you need to show me your engagement ring and then I'll give you 15 Gold Maple Leaves. They need 1 each to enter the wedding. Enjoy!"); - cm.dispose(); - break; - case 1: - if (cm.haveItem(4000313)) { - cm.sendOk("You already have a Gold Maple Leaf. Go give them to your guests before you go into the wedding."); - cm.dispose(); - } else if (hasEngageRing) { - cm.sendOk("You have received 15 Gold Maple Leaves."); - cm.gainItem(4000313,15); - cm.dispose(); - } else { - cm.sendOk("You do not have an engagement ring."); - cm.dispose(); - } - break; - case 2: - if (hasEngageRing) { - cm.warp(680000210, 2); - cm.sendOk("Talk to High Priest John when you're ready to be married."); - cm.dispose(); - } else { - cm.sendOk("You do not have an engagement ring."); - cm.dispose(); - } - break; - case 3: - if (cm.haveItem(4000313)) { - cm.warp(680000210, 0); - cm.sendOk("Enjoy the wedding. Don't drop your Gold Maple Leaf or you won't be able to finish the whole wedding."); - cm.dispose(); - } else { - cm.sendOk("You do not have a Gold Maple Leaf."); - cm.dispose(); - } - break; - } - } + if (mode == 1) + status++; + else + status--; + + if(!weddingIndoors) { + var hasEngagement = false; + for (var x = 4031357; x <= 4031364; x++) { + if (cm.haveItem(x, 1)) { + hasEngagement = true; + break; + } + } + + if (status == 0) { + var text = "Welcome to the #bCathedral#k! How can I help you?"; + var choice = ["How do I prepare a wedding?", "I have an engagement and want to arrange the wedding", "I am the guest and I'd like to go into the wedding"]; + for (x = 0; x < choice.length; x++) { + text += "\r\n#L" + x + "##b" + choice[x] + "#l"; + } + + if (cm.haveItem(5251100)) { + text += "\r\n#L" + x + "##bMake additional invitation cards#l"; + } + + cm.sendSimple(text); + } else if (status == 1) { + switch(selection) { + case 0: + cm.sendOk("Firstly you need to be #bengaged#k to someone. #p9201000# makes the engagement ring. Once attained the engagement status, purchase a #b#t" + weddingEntryTicketCommon + "##k.\r\nShow me your engagement ring and a wedding ticket, and I will book a reservation for you along with #r15 Wedding Tickets#k. Use them to invite your guests into the wedding. They need 1 each to enter."); + cm.dispose(); + break; + + case 1: + if (hasEngagement) { + var wserv = cm.getClient().getWorldServer(); + var cserv = cm.getClient().getChannelServer(); + var weddingId = wserv.getRelationshipId(cm.getPlayer().getId()); + + if(weddingId > 0) { + if(cserv.isWeddingReserved(weddingId)) { // registration check + var placeTime = cserv.getWeddingReservationTimeLeft(weddingId); + cm.sendOk("Your wedding is set to start at the #r" + placeTime + "#k. Don't be late!"); + } else { + var partner = wserv.getPlayerStorage().getCharacterById(cm.getPlayer().getPartnerId()); + if(partner == null) { + cm.sendOk("Your partner seems to be offline right now... Make sure to get both gathered here when the time comes!"); + cm.dispose(); + return; + } + + if(hasWeddingRing(cm.getPlayer()) || hasWeddingRing(partner)) { + cm.sendOk("Either you or your partner already has a marriage ring."); + cm.dispose(); + return; + } + + if(!cm.getMap().equals(partner.getMap())) { + cm.sendOk("Please let your partner come here as well to register the reservation."); + cm.dispose(); + return; + } + + if(!cm.canHold(weddingSendTicket, 15) || !partner.canHold(weddingSendTicket, 15)) { + cm.sendOk("Either you or your partner doesn't have a free ETC slot for the Wedding tickets! Please make some room before trying to register a reservation."); + cm.dispose(); + return; + } + + var hasCommon = cm.haveItem(weddingEntryTicketCommon); + var hasPremium = cm.haveItem(weddingEntryTicketPremium); + + if(hasCommon || hasPremium) { + var weddingType = (hasPremium ? true : false); + + var player = cm.getPlayer(); + var resStatus = cserv.pushWeddingReservation(weddingId, cathedralWedding, weddingType, player.getId(), player.getPartnerId()); + if(resStatus > 0) { + cm.gainItem((weddingType) ? weddingEntryTicketPremium : weddingEntryTicketCommon, -1); + + var expirationTime = cserv.getRelativeWeddingTicketExpireTime(resStatus); + cm.gainItem(weddingSendTicket,15,false,true,expirationTime); + partner.getClient().getAbstractPlayerInteraction().gainItem(weddingSendTicket,15,false,true,expirationTime); + + var placeTime = cserv.getWeddingReservationTimeLeft(weddingId); + + var wedType = weddingType ? "Premium" : "Regular"; + cm.sendOk("You both have received 15 Wedding Tickets, to be given to your guests. #bDouble-click the ticket#k to send it to someone. Invitations can only be sent #rbefore the wedding start time#k. Your #b" + wedType + " wedding#k is set to start at the #r" + placeTime + "#k. Don't be late!"); + + player.dropMessage(6, "Wedding Assistant: You both have received 15 Wedding Tickets. Invitations can only be sent before the wedding start time. Your " + wedType + " wedding is set to start at the " + placeTime + ". Don't be late!"); + partner.dropMessage(6, "Wedding Assistant: You both have received 15 Wedding Tickets. Invitations can only be sent before the wedding start time. Your " + wedType + " wedding is set to start at the " + placeTime + ". Don't be late!"); + + if(!hasSuitForWedding(player)) { + player.dropMessage(5, "Wedding Assistant: Please purchase a wedding garment before showing up for the ceremony. One can be bought at the Wedding Shop left-most Amoria."); + } + + if(!hasSuitForWedding(partner)) { + partner.dropMessage(5, "Wedding Assistant: Please purchase a wedding garment before showing up for the ceremony. One can be bought at the Wedding Shop left-most Amoria."); + } + } else { + cm.sendOk("Your wedding reservation must have been processed recently. Please try again later."); + } + } else { + cm.sendOk("Please have a #b#t" + weddingEntryTicketCommon + "##k available on your CASH inventory before trying to register a reservation."); + } + } + } else { + cm.sendOk("Wedding reservation encountered an error, try again later."); + } + + cm.dispose(); + } else { + cm.sendOk("You do not have an engagement ring."); + cm.dispose(); + } + break; + + case 2: + if (cm.haveItem(weddingGuestTicket)) { + var cserv = cm.getClient().getChannelServer(); + + wid = cserv.getOngoingWedding(cathedralWedding); + if(wid > 0) { + if(cserv.isOngoingWeddingGuest(cathedralWedding, cm.getPlayer().getId())) { + var eim = getMarriageInstance(wid); + if(eim != null) { + cm.sendOk("Enjoy the wedding. Don't drop your Gold Maple Leaf or you won't be able to finish the whole wedding."); + } else { + cm.sendOk("Please wait a moment while the couple get ready to enter the Cathedral."); + cm.dispose(); + } + } else { + cm.sendOk("Sorry, but you have not been invited for this wedding."); + cm.dispose(); + } + } else { + cm.sendOk("There is no wedding booked right now."); + cm.dispose(); + } + } else { + cm.sendOk("You do not have a #b#t" + weddingGuestTicket + "##k."); + cm.dispose(); + } + break; + + default: + var wserv = cm.getClient().getWorldServer(); + var cserv = cm.getClient().getChannelServer(); + var weddingId = wserv.getRelationshipId(cm.getPlayer().getId()); + + var resStatus = cserv.getWeddingReservationStatus(weddingId, cathedralWedding); + if(resStatus > 0) { + if(cm.canHold(weddingSendTicket, 3)) { + cm.gainItem(5251100, -1); + + var expirationTime = cserv.getRelativeWeddingTicketExpireTime(resStatus); + cm.gainItem(weddingSendTicket,3,false,true,expirationTime); + } else { + cm.sendOk("Please have a free ETC slot available to get more invitations."); + } + } else { + cm.sendOk("You're not currently booked on the Cathedral to make additional invitations."); + } + + cm.dispose(); + } + } else if (status == 2) { // registering guest + var eim = getMarriageInstance(wid); + + if(eim != null) { + if(!cm.canHold(4000313)) { + cm.sendOk("Please have a free ETC slot available to get the #b#t4000313##k."); + cm.dispose(); + return; + } + + cm.gainItem(weddingGuestTicket, -1); + eim.registerPlayer(cm.getPlayer()); //cm.warp(680000210, 0); + } else { + cm.sendOk("The marriage event could not be found."); + } + + cm.dispose(); + } + } else { + if (status == 0) { + var eim = cm.getEventInstance(); + if(eim == null) { + cm.warp(680000000,0); + cm.dispose(); + return; + } + + isMarrying = (cm.getPlayer().getId() == eim.getIntProperty("groomId") || cm.getPlayer().getId() == eim.getIntProperty("brideId")); + + if(eim.getIntProperty("weddingStage") == 0) { + if(!isMarrying) { + cm.sendOk("Welcome to the #b#m" + cm.getMapId() + "##k. Please hang around with the groom and bride while the other guests are gathering here.\r\n\r\nWhen the timer reach it's end the couple will head to the altar, at that time you will be allowed to root over them from the #bguests area#k."); + } else { + cm.sendOk("Welcome to the #b#m" + cm.getMapId() + "##k. Please greet the guests that are already here while the others are coming. When the timer reach it's end the couple will head to the altar."); + } + + cm.dispose(); + } else { + cm.sendYesNo("The #bbride and groom#k are already on their way to the altar. Would you like to join them now?"); + } + } else if (status == 1) { + cm.warp(weddingAltarMapid,"sp"); + cm.dispose(); + } + } + + } } \ No newline at end of file diff --git a/scripts/npc/9201006.js b/scripts/npc/9201006.js index 2abb66c0e2..811054e617 100644 --- a/scripts/npc/9201006.js +++ b/scripts/npc/9201006.js @@ -37,7 +37,7 @@ function start() { function action(mode, type, selection) { if (mode == -1 || mode == 0) { - cm.sendOk("Goodbye then"); + cm.sendOk("Goodbye then."); cm.dispose(); return; } else if (mode == 1) { @@ -45,28 +45,53 @@ function action(mode, type, selection) { } else { status--; } + + var eim = cm.getEventInstance(); + if(eim == null) { + cm.warp(680000000,0); + cm.dispose(); + return; + } + + var isMarrying = (cm.getPlayer().getId() == eim.getIntProperty("groomId") || cm.getPlayer().getId() == eim.getIntProperty("brideId")); switch (status) { case 0: - cm.sendNext("I only warp out people who are here by accident."); - break; - case 1: - var engagementRings = new Array(4031360, 4031358, 4031362, 4031364); var hasEngagement = false; - for (var x = 0; x < engagementRings.length && !hasEngagement; x++) { - if (cm.haveItem(engagementRings[x], 1)) + for (var x = 4031357; x <= 4031364; x++) { + if (cm.haveItem(x, 1)) { hasEngagement = true; + break; + } } - if (cm.haveItem(4000313) && hasEngagement) { - cm.sendOk("Please continue with the wedding."); - cm.dispose(); + + if (cm.haveItem(4000313) && isMarrying) { + if(eim.getIntProperty("weddingStage") == 3) { + cm.sendOk("Congratulations on your wedding. Please talk to #b#p9201007##k to start the afterparty."); + cm.dispose(); + } else if(hasEngagement) { + cm.sendOk("Please continue with the wedding."); + cm.dispose(); + } else { + cm.sendOk("You do not have the required item to continue through this wedding. Unfortunately, it's over..."); + } } else { - cm.warp(680000000,0); - cm.dispose(); + if(eim.getIntProperty("weddingStage") == 3) { + if(!isMarrying) { + cm.sendYesNo("The couple #rhas just married#k, and soon #bthey will start the afterparty#k. You should wait here for them. Are you really ready to #rquit this wedding#k and return to #bAmoria#k?"); + } else { + cm.sendOk("Congratulations on your wedding. Please talk to #b#p9201007##k to start the afterparty."); + cm.dispose(); + } + } else { + cm.sendYesNo("Are you sure you want to #rquit this wedding#k and return to #bAmoria#k?"); + } } break; - case 2: - cm.sendOk("You do not have the required item to continue through this wedding."); + + case 1: + cm.warp(680000000,0); + cm.dispose(); break; } } diff --git a/scripts/npc/9201007.js b/scripts/npc/9201007.js index 6ee9895aa2..811897fdbe 100644 --- a/scripts/npc/9201007.js +++ b/scripts/npc/9201007.js @@ -25,20 +25,64 @@ Angel (get31720 ragezone) -- Extra Info ------------------------------------------------------------------------------------- Fixed by [happydud3] & [XotiCraze] +-- Content Improved by ---------------------------------------------------------------------------- + RonanLana (HeavenMS) --------------------------------------------------------------------------------------------------- **/ var status; -var i; +var eim; +var hasEngage; +var hasRing; function start() { - status = -1; - action(1, 0, 0); + eim = cm.getEventInstance(); + if(eim == null) { + cm.warp(680000000,0); + cm.dispose(); + return; + } + + if(cm.getMapId() == 680000200) { + if(eim.getIntProperty("weddingStage") == 0) { + cm.sendNext("The guests are gathering here right now. Please wait awhile, the ceremony will start soon enough."); + } else { + cm.warp(680000210, "sp"); + cm.sendNext("Pick your seat over here and good show!"); + } + + cm.dispose(); + } else { + if(cm.getPlayer().getId() != eim.getIntProperty("groomId") && cm.getPlayer().getId() != eim.getIntProperty("brideId")) { + cm.sendNext("Sorry, only the marrying couple should be talking to me right now."); + cm.dispose(); + return; + } + + hasEngage = false; + for(var i = 4031357; i <= 4031364; i++) { + if(cm.haveItem(i)) { + hasEngage = true; + break; + } + } + + var rings = [1112806, 1112803, 1112807, 1112809]; + hasRing = false; + for (i = 0; i < rings.length; i++) { + if (cm.getPlayer().haveItemWithId(rings[i], true)) { + hasRing = true; + } + } + + status = -1; + action(1, 0, 0); + } } function action(mode, type, selection) { if (mode == -1 || mode == 0) { - cm.sendOk("Goodbye then"); + cm.sendOk("Goodbye then."); cm.dispose(); return; } else if (mode == 1) { @@ -46,28 +90,16 @@ function action(mode, type, selection) { } else { status--; } - - var engagementRings = Array(4031360, 4031358, 4031362, 4031364); - var hasEngage = false; - for (i = 0; i < engagementRings.length && !hasEngage; i++) { - if (cm.haveItem(engagementRings[i])) - hasEngage = true; - } - var Rings = Array(1112806, 1112803, 1112807, 1112809); - var hasRing = false; - for (i = 0; i < Rings.length; i++) { - if (cm.haveItem(Rings[i])) { - hasRing = true; - } - } - + if (status == 0) { - if (cm.haveItem(4000313) && hasEngage) { + var hasGoldenLeaf = cm.haveItem(4000313); + + if (hasGoldenLeaf && hasEngage) { cm.sendOk("You can't leave yet! You need to click High Priest John and get married before I can let you leave."); cm.dispose(); - } else if (cm.haveItem(4000313) && hasRing) { - var choice = Array("Go to the Cherished Visage Photos", "What should I be doing"); - var msg = "What can I help you with?"; + } else if (hasGoldenLeaf && hasRing) { + var choice = Array("Go to the Afterparty", "What should I be doing"); + var msg = "What can I help you with?#b"; for (i = 0; i < choice.length; i++) { msg += "\r\n#L" + i + "#" + choice[i] + "#l"; } @@ -79,14 +111,22 @@ function action(mode, type, selection) { } else if (status == 1) { switch(selection) { case 0: - cm.warp(680000300, 0); - cm.sendOk("Enjoy! Cherish your Photos Forever!"); + if(eim.getIntProperty("isPremium") == 1) { + eim.warpEventTeam(680000300); + cm.sendOk("Enjoy! Cherish your Photos Forever!"); + } else { // skip the party-time (premium only) + eim.warpEventTeam(680000500); + cm.sendOk("Congratulations for the newly-wed! I will escort you to the exit."); + } + cm.dispose(); break; + case 1: - cm.sendOk("The Bride and Groom must click High Priest John to be wed. When you are ready you can click me to go to the Cherished Visage Photos"); + cm.sendOk("The Bride and Groom must receive the blessings of High Priest John to be wed. When you are ready you can click me to go to the Afterparty."); cm.dispose(); break; + default: cm.warp(680000000,0); cm.dispose(); diff --git a/scripts/npc/9201008.js b/scripts/npc/9201008.js index 2ebeae654a..4d2963c038 100644 --- a/scripts/npc/9201008.js +++ b/scripts/npc/9201008.js @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 @@ -19,34 +17,289 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/* NPC Base - Map Name (Map ID) - Extra NPC info. +/* Assistant Bonnie + Marriage NPC */ var status; - -function start() { - status = -1; - action(1, 0, 0); +var wid; +var isMarrying; + +var cathedralWedding = false; +var weddingEventName = "WeddingChapel"; +var weddingEntryTicketCommon = 5251001; +var weddingEntryTicketPremium = 5251002; +var weddingSendTicket = 4031377; +var weddingGuestTicket = 4031406; +var weddingAltarMapid = 680000110; +var weddingIndoors; + +function isWeddingIndoors(mapid) { + return mapid >= 680000100 && mapid <= 680000500; } -function action(mode, type, selection) { - if (mode == -1) { - cm.dispose(); - } else { - if (mode == 0 && type > 0) { +function hasSuitForWedding(player) { + var baseid = (player.getGender() == 0) ? 1050131 : 1051150; + + for(var i = 0; i < 4; i++) { + if(player.haveItemWithId(baseid + i, true)) { + return true; + } + } + + return false; +} + +function getMarriageInstance(weddingId) { + var em = cm.getEventManager(weddingEventName); + + for (var iterator = em.getInstances().iterator(); iterator.hasNext();) { + var eim = iterator.next(); + + if(eim.getIntProperty("weddingId") == weddingId) { + return eim; + } + } + + return null; +} + +function hasWeddingRing(player) { + var rings = [1112806, 1112803, 1112807, 1112809]; + for (var i = 0; i < rings.length; i++) { + if (player.haveItemWithId(rings[i], true)) { + return true; + } + } + + return false; +} + +function start() { + weddingIndoors = isWeddingIndoors(cm.getMapId()); + 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(!weddingIndoors) { + var hasEngagement = false; + for (var x = 4031357; x <= 4031364; x++) { + if (cm.haveItem(x, 1)) { + hasEngagement = true; + break; + } + } + + if (status == 0) { + var text = "Welcome to the #bChapel#k! How can I help you?"; + var choice = ["How do I prepare a wedding?", "I have an engagement and want to arrange the wedding", "I am the guest and I'd like to go into the wedding"]; + for (x = 0; x < choice.length; x++) { + text += "\r\n#L" + x + "##b" + choice[x] + "#l"; + } + + if (cm.haveItem(5251100)) { + text += "\r\n#L" + x + "##bMake additional invitation cards#l"; + } + + cm.sendSimple(text); + } else if (status == 1) { + switch(selection) { + case 0: + cm.sendOk("Firstly you need to be #bengaged#k to someone. #p9201000# makes the engagement ring. Once attained the engagement status, purchase a #b#t" + weddingEntryTicketCommon + "##k.\r\nShow me your engagement ring and a wedding ticket, and I will book a reservation for you along with #r15 Wedding Tickets#k. Use them to invite your guests into the wedding. They need 1 each to enter."); + cm.dispose(); + break; + + case 1: + if (hasEngagement) { + var wserv = cm.getClient().getWorldServer(); + var cserv = cm.getClient().getChannelServer(); + var weddingId = wserv.getRelationshipId(cm.getPlayer().getId()); + + if(weddingId > 0) { + if(cserv.isWeddingReserved(weddingId)) { // registration check + var placeTime = cserv.getWeddingReservationTimeLeft(weddingId); + cm.sendOk("Your wedding is set to start at the #r" + placeTime + "#k. Don't be late!"); + } else { + var partner = wserv.getPlayerStorage().getCharacterById(cm.getPlayer().getPartnerId()); + if(partner == null) { + cm.sendOk("Your partner seems to be offline right now... Make sure to get both gathered here when the time comes!"); + cm.dispose(); + return; + } + + if(hasWeddingRing(cm.getPlayer()) || hasWeddingRing(partner)) { + cm.sendOk("Either you or your partner already has a marriage ring."); + cm.dispose(); + return; + } + + if(!cm.getMap().equals(partner.getMap())) { + cm.sendOk("Please let your partner come here as well to register the reservation."); + cm.dispose(); + return; + } + + if(!cm.canHold(weddingSendTicket, 15) || !partner.canHold(weddingSendTicket, 15)) { + cm.sendOk("Either you or your partner doesn't have a free ETC slot for the Wedding tickets! Please make some room before trying to register a reservation."); + cm.dispose(); + return; + } + + var hasCommon = cm.haveItem(weddingEntryTicketCommon); + var hasPremium = cm.haveItem(weddingEntryTicketPremium); + + if(hasCommon || hasPremium) { + var weddingType = (hasPremium ? true : false); + + var player = cm.getPlayer(); + var resStatus = cserv.pushWeddingReservation(weddingId, cathedralWedding, weddingType, player.getId(), player.getPartnerId()); + if(resStatus > 0) { + cm.gainItem((weddingType) ? weddingEntryTicketPremium : weddingEntryTicketCommon, -1); + + var expirationTime = cserv.getRelativeWeddingTicketExpireTime(resStatus); + cm.gainItem(weddingSendTicket,15,false,true,expirationTime); + partner.getClient().getAbstractPlayerInteraction().gainItem(weddingSendTicket,15,false,true,expirationTime); + + var placeTime = cserv.getWeddingReservationTimeLeft(weddingId); + + var wedType = weddingType ? "Premium" : "Regular"; + cm.sendOk("You both have received 15 Wedding Tickets, to be given to your guests. #bDouble-click the ticket#k to send it to someone. Invitations can only be sent #rbefore the wedding start time#k. Your #b" + wedType + " wedding#k is set to start at the #r" + placeTime + "#k. Don't be late!"); + + player.dropMessage(6, "Wedding Assistant: You both have received 15 Wedding Tickets. Invitations can only be sent before the wedding start time. Your " + wedType + " wedding is set to start at the " + placeTime + ". Don't be late!"); + partner.dropMessage(6, "Wedding Assistant: You both have received 15 Wedding Tickets. Invitations can only be sent before the wedding start time. Your " + wedType + " wedding is set to start at the " + placeTime + ". Don't be late!"); + + if(!hasSuitForWedding(player)) { + player.dropMessage(5, "Wedding Assistant: Please purchase a wedding garment before showing up for the ceremony. One can be bought at the Wedding Shop left-most Amoria."); + } + + if(!hasSuitForWedding(partner)) { + partner.dropMessage(5, "Wedding Assistant: Please purchase a wedding garment before showing up for the ceremony. One can be bought at the Wedding Shop left-most Amoria."); + } + } else { + cm.sendOk("Your wedding reservation must have been processed recently. Please try again later."); + } + } else { + cm.sendOk("Please have a #b#t" + weddingEntryTicketCommon + "##k available on your CASH inventory before trying to register a reservation."); + } + } + } else { + cm.sendOk("Wedding reservation encountered an error, try again later."); + } + + cm.dispose(); + } else { + cm.sendOk("You do not have an engagement ring."); + cm.dispose(); + } + break; + + case 2: + if (cm.haveItem(weddingGuestTicket)) { + var cserv = cm.getClient().getChannelServer(); + + wid = cserv.getOngoingWedding(cathedralWedding); + if(wid > 0) { + if(cserv.isOngoingWeddingGuest(cathedralWedding, cm.getPlayer().getId())) { + var eim = getMarriageInstance(wid); + if(eim != null) { + cm.sendOk("Enjoy the wedding. Don't drop your Gold Maple Leaf or you won't be able to finish the whole wedding."); + } else { + cm.sendOk("Please wait a moment while the couple get ready to enter the Chapel."); + cm.dispose(); + } + } else { + cm.sendOk("Sorry, but you have not been invited for this wedding."); + cm.dispose(); + } + } else { + cm.sendOk("There is no wedding booked right now."); + cm.dispose(); + } + } else { + cm.sendOk("You do not have a #b#t" + weddingGuestTicket + "##k."); + cm.dispose(); + } + break; + + default: + var wserv = cm.getClient().getWorldServer(); + var cserv = cm.getClient().getChannelServer(); + var weddingId = wserv.getRelationshipId(cm.getPlayer().getId()); + + var resStatus = cserv.getWeddingReservationStatus(weddingId, cathedralWedding); + if(resStatus > 0) { + if(cm.canHold(weddingSendTicket, 3)) { + cm.gainItem(5251100, -1); + + var expirationTime = cserv.getRelativeWeddingTicketExpireTime(resStatus); + cm.gainItem(weddingSendTicket,3,false,true,expirationTime); + } else { + cm.sendOk("Please have a free ETC slot available to get more invitations."); + } + } else { + cm.sendOk("You're not currently booked on the Chapel to make additional invitations."); + } + + cm.dispose(); + } + } else if (status == 2) { // registering guest + var eim = getMarriageInstance(wid); + + if(eim != null) { + if(!cm.canHold(4000313)) { + cm.sendOk("Please have a free ETC slot available to get the #b#t4000313##k."); cm.dispose(); return; + } + + cm.gainItem(weddingGuestTicket, -1); + eim.registerPlayer(cm.getPlayer()); //cm.warp(680000210, 0); + } else { + cm.sendOk("The marriage event could not be found."); } - if (mode == 1) - status++; - else - status--; - - if(status == 0) { - cm.sendOk("Wedding is currently closed."); - cm.dispose(); + + cm.dispose(); + } + } else { + if (status == 0) { + var eim = cm.getEventInstance(); + if(eim == null) { + cm.warp(680000000,0); + cm.dispose(); + return; } + + isMarrying = (cm.getPlayer().getId() == eim.getIntProperty("groomId") || cm.getPlayer().getId() == eim.getIntProperty("brideId")); + + if(eim.getIntProperty("weddingStage") == 0) { + if(!isMarrying) { + cm.sendOk("Welcome to the #b#m" + cm.getMapId() + "##k. Please hang around with the groom and bride while the other guests are gathering here.\r\n\r\nWhen the timer reach it's end the couple will head to the altar, at that time you will be allowed to root over them from the #bguests area#k."); + } else { + cm.sendOk("Welcome to the #b#m" + cm.getMapId() + "##k. Please greet the guests that are already here while the others are coming. When the timer reach it's end the couple will head to the altar."); + } + + cm.dispose(); + } else { + cm.sendYesNo("The #bbride and groom#k are already on their way to the altar. Would you like to join them now?"); + } + } else if (status == 1) { + cm.warp(weddingAltarMapid,"sp"); + cm.dispose(); + } } + } } \ No newline at end of file diff --git a/scripts/npc/9201009.js b/scripts/npc/9201009.js new file mode 100644 index 0000000000..5899b2bdf5 --- /dev/null +++ b/scripts/npc/9201009.js @@ -0,0 +1,136 @@ +/* + This file is part of the OdinMS Maple Story Server + Copyright (C) 2008 Patrick Huy + Matthias Butz + Jan Christian Meyer + + 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 . +*/ +/** + Assistant Nancy +-- By --------------------------------------------------------------------------------------------- + Angel (get31720 ragezone) +-- Extra Info ------------------------------------------------------------------------------------- + Fixed by [happydud3] & [XotiCraze] +-- Content Improved by ---------------------------------------------------------------------------- + RonanLana (HeavenMS) +--------------------------------------------------------------------------------------------------- +**/ + +var status; +var eim; +var hasEngage; +var hasRing; + +function start() { + eim = cm.getEventInstance(); + if(eim == null) { + cm.warp(680000000,0); + cm.dispose(); + return; + } + + if(cm.getMapId() == 680000200) { + if(eim.getIntProperty("weddingStage") == 0) { + cm.sendNext("The guests are gathering here right now. Please wait awhile, the ceremony will start soon enough."); + } else { + cm.warp(680000210, "sp"); + cm.sendNext("Pick your seat over here and good show!"); + } + + cm.dispose(); + } else { + if(cm.getPlayer().getId() != eim.getIntProperty("groomId") && cm.getPlayer().getId() != eim.getIntProperty("brideId")) { + cm.sendNext("Sorry, only the marrying couple should be talking to me right now."); + cm.dispose(); + return; + } + + hasEngage = false; + for(var i = 4031357; i <= 4031364; i++) { + if(cm.haveItem(i)) { + hasEngage = true; + break; + } + } + + var rings = [1112806, 1112803, 1112807, 1112809]; + hasRing = false; + for (i = 0; i < rings.length; i++) { + if (cm.getPlayer().haveItemWithId(rings[i], true)) { + hasRing = true; + } + } + + status = -1; + action(1, 0, 0); + } +} + +function action(mode, type, selection) { + if (mode == -1 || mode == 0) { + cm.sendOk("Goodbye then."); + cm.dispose(); + return; + } else if (mode == 1) { + status++; + } else { + status--; + } + + if (status == 0) { + var hasGoldenLeaf = cm.haveItem(4000313); + + if (hasGoldenLeaf && hasEngage) { + cm.sendOk("You can't leave yet! You need to click Pelvis Bebop and get his word before I can let you leave."); + cm.dispose(); + } else if (hasGoldenLeaf && hasRing) { + var choice = Array("Go to the Afterparty", "What should I be doing"); + var msg = "What can I help you with?#b"; + for (i = 0; i < choice.length; i++) { + msg += "\r\n#L" + i + "#" + choice[i] + "#l"; + } + cm.sendSimple(msg); + } else { + cm.sendNext("You don't seem to have a Gold Maple Leaf, engagement ring, or wedding ring. You must not belong here, so I will take you to Amoria."); + selection = 20; // Random. + } + } else if (status == 1) { + switch(selection) { + case 0: + if(eim.getIntProperty("isPremium") == 1) { + eim.warpEventTeam(680000300); + cm.sendOk("Enjoy! Cherish your Photos Forever!"); + } else { // skip the party-time (premium only) + eim.warpEventTeam(680000500); + cm.sendOk("Congratulations for the newly-wed! I will escort you to the exit."); + } + + cm.dispose(); + break; + + case 1: + cm.sendOk("The superstars must receive the word of Pelvis Bebop to be united. When you are ready you can click me to go to the Afterparty."); + cm.dispose(); + break; + + default: + cm.warp(680000000,0); + cm.dispose(); + break; + } + } +} diff --git a/scripts/npc/9201010.js b/scripts/npc/9201010.js index 38c122af57..00ce690d64 100644 --- a/scripts/npc/9201010.js +++ b/scripts/npc/9201010.js @@ -29,17 +29,74 @@ **/ var status; - + function start() { - if (cm.haveItem(4000313)) { - cm.sendOk("You are a guest. Please continue with the wedding. I only warp out people who are here by accident."); - cm.dispose(); - } else - cm.sendNext("I warp people out. If you are the newly wed don't click next or you will not be able to collect your prize at the end."); + status = -1; + action(1, 0, 0); } -function action(mode, type, selection) { - if (mode > 1) - cm.warp(680000000); - cm.dispose(); -} \ No newline at end of file +function action(mode, type, selection) { + if (mode == -1 || mode == 0) { + cm.dispose(); + return; + } else if (mode == 1) { + status++; + } else { + status--; + } + + var eim = cm.getEventInstance(); + if(eim == null) { + cm.warp(680000000,0); + cm.dispose(); + return; + } + + var isMarrying = (cm.getPlayer().getId() == eim.getIntProperty("groomId") || cm.getPlayer().getId() == eim.getIntProperty("brideId")); + + switch (status) { + case 0: + if(cm.getMapId() == 680000300) { + cm.sendYesNo("Are you sure you want to #rquit the stage#k and head back to #bAmoria#k? You will be #rskipping the bonus stages#k that way."); + } else { + var hasEngagement = false; + for (var x = 4031357; x <= 4031364; x++) { + if (cm.haveItem(x, 1)) { + hasEngagement = true; + break; + } + } + + if (cm.haveItem(4000313) && isMarrying) { + if(eim.getIntProperty("weddingStage") == 3) { + cm.sendOk("You guys totally rocked the stage!!! Go go, talk to #b#p9201007##k to start the afterparty."); + cm.dispose(); + } else if(hasEngagement) { + cm.sendOk("Please continue rocking on the stage, you are our superstars today!"); + cm.dispose(); + } else { + cm.sendOk("Oh, hey, where are the credentials for the this so-lauded party? Oh man, we can't continue at this rate now... Sorry, the party is over."); + } + } else { + if(eim.getIntProperty("weddingStage") == 3) { + if(!isMarrying) { + cm.sendYesNo("You guys didn't miss them right? Our superstars #rworked so good together#k, and soon #bthey will start the afterparty#k. Are you really going to #rdrop out of the show#k and return to #bAmoria#k?"); + } else { + cm.sendOk("You guys totally rocked the stage!!! Go go, talk to #b#p9201007##k to start the afterparty."); + cm.dispose(); + } + } else { + cm.sendYesNo("Are you sure you want to #rquit the stage#k and head to #bAmoria#k? You will be #rskipping the bonus stages#k, fam."); + } + } + } + + + break; + + case 1: + cm.warp(680000000,0); + cm.dispose(); + break; + } +} diff --git a/scripts/npc/9201011.js b/scripts/npc/9201011.js new file mode 100644 index 0000000000..aaff283231 --- /dev/null +++ b/scripts/npc/9201011.js @@ -0,0 +1,292 @@ +/* + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 . +*/ +/* Pelvis Bebop + Marriage NPC + */ + +importPackage(Packages.constants); +importPackage(Packages.net.server.channel.handlers); +importPackage(Packages.tools); +importPackage(Packages.tools.packets); + +var status; +var state; +var eim; +var weddingEventName = "WeddingChapel"; +var cathedralWedding = false; +var weddingIndoors; +var weddingBlessingExp = ServerConstants.WEDDING_BLESS_EXP; + +function detectPlayerItemid(player) { + for (var x = 4031357; x <= 4031364; x++) { + if (player.haveItem(x)) { + return x; + } + } + + return -1; +} + +function getRingId(boxItemId) { + return boxItemId == 4031357 ? 1112803 : (boxItemId == 4031359 ? 1112806 : (boxItemId == 4031361 ? 1112807 : (boxItemId == 4031363 ? 1112809 : -1))); +} + +function isSuitedForWedding(player, equipped) { + var baseid = (player.getGender() == 0) ? 1050131 : 1051150; + + if(equipped) { + for(var i = 0; i < 4; i++) { + if(player.haveItemEquipped(baseid + i)) { + return true; + } + } + } else { + for(var i = 0; i < 4; i++) { + if(player.haveItemWithId(baseid + i, true)) { + return true; + } + } + } + + return false; +} + +function getWeddingPreparationStatus(player, partner) { + if(!player.haveItem(4000313)) return -3; + if(!partner.haveItem(4000313)) return 3; + + if(!isSuitedForWedding(player, true)) return -4; + if(!isSuitedForWedding(partner, true)) return 4; + + var hasEngagement = false; + for (var x = 4031357; x <= 4031364; x++) { + if (player.haveItem(x)) { + hasEngagement = true; + break; + } + } + if(!hasEngagement) return -1; + + hasEngagement = false; + for (var x = 4031357; x <= 4031364; x++) { + if (partner.haveItem(x)) { + hasEngagement = true; + break; + } + } + if(!hasEngagement) return -2; + + if(!player.canHold(1112803)) return 1; + if(!partner.canHold(1112803)) return 2; + + return 0; +} + +function giveCoupleBlessings(eim, player, partner) { + var blessCount = eim.gridSize(); + + player.gainExp(blessCount * weddingBlessingExp); + partner.gainExp(blessCount * weddingBlessingExp); +} + +function start() { + eim = cm.getEventInstance(); + + 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) { + if(eim == null) { + cm.warp(680000000,0); + cm.dispose(); + return; + } + + var playerId = cm.getPlayer().getId(); + if(playerId == eim.getIntProperty("groomId") || playerId == eim.getIntProperty("brideId")) { + var wstg = eim.getIntProperty("weddingStage"); + + if(wstg == 2) { + cm.sendYesNo("Awhoooooooooosh~, the guests have proclaimed their love to y'all. The time has come baby~, #rshould I make you Husband and Wife#k?"); + state = 1; + } else if(wstg == 1) { + cm.sendOk("W-whoah wait a bit alright? Your guests are currently giving their love to y'all. Let's shake this place up, baby~~."); + cm.dispose(); + } else { + cm.sendOk("Wheeeeeeeeeeeeeew! Our festival here is now complete, give a sweet talk to #b#p9201009##k, she will lead you and your folks to the afterparty. Cheers for your love!"); + cm.dispose(); + } + } else { + var wstg = eim.getIntProperty("weddingStage"); + if(wstg == 1) { + if(eim.gridCheck(cm.getPlayer()) != -1) { + cm.sendOk("Everyone let's shake this place up! Let's rock 'n' roll!!"); + cm.dispose(); + } else { + if(eim.getIntProperty("guestBlessings") == 1) { + cm.sendYesNo("Will you manifest your love to the superstars here present?"); + state = 0; + } else { + cm.sendOk("Our superstars are gathered down here. Everyone, let's give them some nice, nicey party~!"); + cm.dispose(); + } + } + } else if(wstg == 3) { + cm.sendOk("Whooooooo-hoo! The couple's love now are like one super big shiny heart right now! And it shall go on ever after this festival. Please #rget ready for the afterparty#k, baby~. Follow the married couple's lead!"); + cm.dispose(); + } else { + cm.sendOk("It's now guys... Stay with your eyes and ears keened up! They are about to smooch it all over the place!!!"); + cm.dispose(); + } + } + } else if (status == 1) { + if(state == 0) { // give player blessings + eim.gridInsert(cm.getPlayer(), 1); + + if(ServerConstants.WEDDING_BLESSER_SHOWFX) { + var target = cm.getPlayer(); + target.announce(MaplePacketCreator.showSpecialEffect(9)); + target.getMap().broadcastMessage(target, MaplePacketCreator.showForeignEffect(target.getId(), 9), false); + } else { + var target = eim.getPlayerById(eim.getIntProperty("groomId")); + target.announce(MaplePacketCreator.showSpecialEffect(9)); + target.getMap().broadcastMessage(target, MaplePacketCreator.showForeignEffect(target.getId(), 9), false); + + target = eim.getPlayerById(eim.getIntProperty("brideId")); + target.announce(MaplePacketCreator.showSpecialEffect(9)); + target.getMap().broadcastMessage(target, MaplePacketCreator.showForeignEffect(target.getId(), 9), false); + } + + cm.sendOk("Way to go, my friend! Your LOVE has been added to theirs, now in one bigger heart-shaped sentiment that will remain lively in our hearts forever! Who-hoo~!"); + cm.dispose(); + } else { // couple wants to complete the wedding + var wstg = eim.getIntProperty("weddingStage"); + + if(wstg == 2) { + var pid = cm.getPlayer().getPartnerId(); + if(pid <= 0) { + cm.sendOk("Huh~.... Wait wait, did you just break that thing you had right now?? Oh my, what happened?"); + cm.dispose(); + return; + } + + var player = cm.getPlayer(); + var partner = cm.getMap().getCharacterById(cm.getPlayer().getPartnerId()); + if(partner != null) { + state = getWeddingPreparationStatus(player, partner); + + switch(state) { + case 0: + var pid = eim.getIntProperty("confirmedVows"); + if(pid != -1) { + if(pid == player.getId()) { + cm.sendOk("You have already confirmed your vows. All that is left is for your partner to confirm now."); + } else { + eim.setIntProperty("weddingStage", 3); + var cmPartner = partner.getClient().getAbstractPlayerInteraction(); + + var playerItemId = detectPlayerItemid(player); + var partnerItemId = (playerItemId % 2 == 1) ? playerItemId + 1 : playerItemId - 1; + + var marriageRingId = getRingId((playerItemId % 2 == 1) ? playerItemId : partnerItemId); + + cm.gainItem(playerItemId, -1); + cmPartner.gainItem(partnerItemId, -1); + + RingActionHandler.giveMarriageRings(player, partner, marriageRingId); + player.setMarriageItemId(marriageRingId); + partner.setMarriageItemId(marriageRingId); + + //var marriageId = eim.getIntProperty("weddingId"); + //player.announce(Wedding.OnMarriageResult(marriageId, player, true)); + //partner.announce(Wedding.OnMarriageResult(marriageId, player, true)); + + giveCoupleBlessings(eim, player, partner); + + cm.getMap().dropMessage(6, "Wayne: I'll call it out right now, and it shall go on: you guys are the key of the other's lock, a lace of a pendant. That's it, snog yourselves!"); + eim.schedule("showMarriedMsg", 2 * 1000); + } + } else { + eim.setIntProperty("confirmedVows", player.getId()); + cm.getMap().dropMessage(6, "Wedding Assistant: " + player.getName() + " has confirmed vows! Alright, one step away to make it official. Tighten your seatbelts!"); + } + + break; + + case -1: + cm.sendOk("Well, it seems you no longer have the ring/ring box you guys exchanged at the engagement. Awww man~"); + break; + + case -2: + cm.sendOk("Well, it seems your partner no longer has the ring/ring box you guys exchanged at the engagement. Awww man~"); + break; + + case -3: + cm.sendOk("Well, it seems you don't have the #r#t4000313##k given at the entrance... Please find it, baby~"); + break; + + case -4: + cm.sendOk("Aww I know that shucks, but the fashionable wedding clothes does a essential part here. Please wear it before talking to me."); + break; + + case 1: + cm.sendOk("Please make an EQUIP slot available to get the marriage ring, will you?"); + break; + + case 2: + cm.sendOk("Please let your partner know to make an EQUIP slot available to get the marriage ring, will you?"); + break; + + case 3: + cm.sendOk("Well, it seems your partner don't have the #r#t4000313##k given at the entrance... Please find it, I can't call the finally without it."); + break; + + case 4: + cm.sendOk("Aww I know that shucks, but it seems your partner is not using the fashionable wedding clothes. Please tell them to wear it before talking to me."); + break; + } + + cm.dispose(); + } else { + cm.sendOk("Oof, is that it that your partner is not here, right now? ... Oh noes, I'm afraid I can't call the finally if your partner is not here."); + cm.dispose(); + } + } else { + cm.sendOk("Wheeeeeeeeeeeeew~ You are now #bofficially one couple#k, and a brilliant one. Your moves fitted in outstandingly, congratulations!"); + cm.dispose(); + } + } + } + } +} \ No newline at end of file diff --git a/scripts/npc/9201012.js b/scripts/npc/9201012.js index 2ebeae654a..6447b09832 100644 --- a/scripts/npc/9201012.js +++ b/scripts/npc/9201012.js @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 @@ -19,34 +17,168 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/* NPC Base - Map Name (Map ID) - Extra NPC info. +/* Wayne + Marriage NPC */ var status; - -function start() { - status = -1; - action(1, 0, 0); +var state; +var eim; +var weddingEventName = "WeddingChapel"; +var cathedralWedding = false; + + +function isSuitedForWedding(player, equipped) { + var baseid = (player.getGender() == 0) ? 1050131 : 1051150; + + if(equipped) { + for(var i = 0; i < 4; i++) { + if(player.haveItemEquipped(baseid + i)) { + return true; + } + } + } else { + for(var i = 0; i < 4; i++) { + if(player.haveItemWithId(baseid + i, true)) { + return true; + } + } + } + + return false; +} + +function getMarriageInstance(player) { + var em = cm.getEventManager(weddingEventName); + + for (var iterator = em.getInstances().iterator(); iterator.hasNext();) { + var eim = iterator.next(); + if(eim.isEventLeader(player)) { + return eim; + } + } + + return null; +} + +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.sendOk("Wedding is currently closed."); - cm.dispose(); - } + if (mode == -1) { + cm.dispose(); + } else { + if (mode == 0 && type > 0) { + cm.dispose(); + return; } + if (mode == 1) + status++; + else + status--; + + if(status == 0) { + var hasEngagement = false; + for (var x = 4031357; x <= 4031364; x++) { + if (cm.haveItem(x, 1)) { + hasEngagement = true; + break; + } + } + + if(hasEngagement) { + var text = "Hi there. How about skyrocket the day with your fiancee baby~?"; + var choice = new Array("We're ready to get married."); + for (x = 0; x < choice.length; x++) { + text += "\r\n#L" + x + "##b" + choice[x] + "#l"; + } + cm.sendSimple(text); + } else { + cm.sendOk("Hi there, folks. Even thought of having a wedding held on Amoria? When the talk is about wedding, everyone firstly thinks about Amoria, there is no miss to it. Our chapel here is renowned around the Maple world for offering the best wedding services for maplers!"); + cm.dispose(); + } + } else if(status == 1) { + var wid = cm.getClient().getWorldServer().getRelationshipId(cm.getPlayer().getId()); + var cserv = cm.getClient().getChannelServer(); + + if(cserv.isWeddingReserved(wid)) { + if(wid == cserv.getOngoingWedding(cathedralWedding)) { + var partner = cserv.getPlayerStorage().getCharacterById(cm.getPlayer().getPartnerId()); + if(!(partner == null || !cm.getMap().equals(partner.getMap()))) { + if(!cm.canHold(4000313)) { + cm.sendOk("Please have a free ETC slot available to get the #b#t4000313##k."); + cm.dispose(); + return; + } else if(!partner.canHold(4000313)) { + cm.sendOk("Please let your partner know they must have a free ETC slot available to get the #b#t4000313##k."); + cm.dispose(); + return; + } else if(!isSuitedForWedding(cm.getPlayer(), false)) { + cm.sendOk("Please purchase fashionable #rwedding clothes#k for the wedding, quickly! It's time to shine, baby~!"); + cm.dispose(); + return; + } else if(!isSuitedForWedding(partner, false)) { + cm.sendOk("Your partner must know they must have fashionable #rwedding clothes#k for the wedding. It's time to shine, baby~!"); + cm.dispose(); + return; + } + + cm.sendOk("Alright! The couple appeared here stylish as ever. Let's go folks, let's rock 'n' roll!!!"); + } else { + cm.sendOk("Aww, your partner is elsewhere... Both must be here for the wedding, else it's going to be sooooo lame."); + cm.dispose(); + } + } else { + var placeTime = cserv.getWeddingReservationTimeLeft(wid); + + cm.sendOk("Yo. Your wedding is set to happen at the #r" + placeTime + "#k, don't be late will you?"); + cm.dispose(); + } + } else { + cm.sendOk("Aawww, I'm sorry but there are no reservations made for you at this channel for the time being."); + cm.dispose(); + } + } else if(status == 2) { + var cserv = cm.getClient().getChannelServer(); + var wtype = cserv.getOngoingWeddingType(cathedralWedding); + + var partner = cserv.getPlayerStorage().getCharacterById(cm.getPlayer().getPartnerId()); + if(!(partner == null || !cm.getMap().equals(partner.getMap()))) { + if(cserv.acceptOngoingWedding(cathedralWedding)) { + var wid = cm.getClient().getWorldServer().getRelationshipId(cm.getPlayer().getId()); + if(wid > 0) { + var em = cm.getEventManager(weddingEventName); + if(em.startInstance(cm.getPlayer())) { + eim = getMarriageInstance(cm.getPlayer()); + if(eim != null) { + eim.setIntProperty("weddingId", wid); + eim.setIntProperty("groomId", cm.getPlayer().getId()); + eim.setIntProperty("brideId", cm.getPlayer().getPartnerId()); + eim.setIntProperty("isPremium", wtype ? 1 : 0); + + eim.registerPlayer(partner); + } else { + cm.sendOk("An unexpected error happened when locating the wedding event. Please try again later."); + } + + cm.dispose(); + } else { + cm.sendOk("An unexpected error happened before the wedding preparations. Please try again later."); + cm.dispose(); + } + } else { + cm.sendOk("An unexpected error happened before the wedding preparations. Please try again later."); + cm.dispose(); + } + } else { // partner already decided to start + cm.dispose(); + } + } else { + cm.sendOk("Aww, it seems your partner is elsewhere... Both must be here for the wedding, else it's going to be sooooo lame."); + cm.dispose(); + } + } + } } \ No newline at end of file diff --git a/scripts/npc/9201013.js b/scripts/npc/9201013.js index ba041d3b3c..f5ddbebf4e 100644 --- a/scripts/npc/9201013.js +++ b/scripts/npc/9201013.js @@ -19,51 +19,11 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/* victora -by Angel (get31720 ragezone) +/* Victoria +by Ronan */ -var wui = 0; - function start() { - cm.sendSimple ("Welcome to the Cathedral. What would you like to do? \r\n#L0##bI need invitations for my guests#k #l\r\n#L1##bI'd like to prepare a wedding#k #l\r\n#L2##bCan you explain how I should prepare a wedding?#k #l\r\n#L3##bI am either the groom or bride and I'd like to go in#k #l\r\n#L4##bI am a guest and I'd like to go in#k #l"); -} - -function action(mode, type, selection) { + cm.sendOk("Welcome to the Cathedral.\r\n\r\n#bCouples#k wanting to marry on the Cathedral should first #barrange a reservation#k with #r#p9201005##k. When the arranged time comes #bboth#k must show up here, on the same channel from the reservation, and start the ceremony (there is a 10-minutes fault policy to this) by talking to #r#p9201002##k. Once arranged, both can #bdistribute tickets#k to friends or acquainteds to become the guests for the marriage.\r\n\r\nThe ceremony will start accepting #bguests#k after the groom and the bride has entered the building. Show the #b#t4000313##k to #r#p9201005##k to access the inner rooms. No one without a ticket is allowed to enter the stage!"); cm.dispose(); - if (selection == 0) { - if (cm.haveItem(4214002)) { - cm.sendNext("Alright here are you invitations make sure your guest have them or they can't come in!"); - cm.gainItem(4031395,10); - - } else { - cm.sendOk("Sorry but please make sure you have your premium wedding receipt or you won't be able to have your wedding"); - status = 9; - } - } else if (selection == 1) { - if (cm.haveItem(5251003)) { - cm.sendNext("Alright, I'll give you your premium wedding receipt and make sure you don't lose it! If you lose your receipt you won't be able to get invitations or enter the cathedral!"); - cm.gainItem(4214002,1); - } else if (selection == 2) { - cm.sendNext("Have both the bride and groom buy a premium cathedral wedding ticket from the cash shop. Then ask me to prepare your wedding and i'll give you a wedding receipt. Talk to me if you want invitations so other guests can join. When you're ready just have everyone come to me and i'll let you or the guests in. Inside Debbie will warp you out to Amoria if you chose to leave. Nicole will warp you to the next map."); - } else if (selection == 3) { - if (cm.haveItem(4214002)) { - cm.sendNext("Okay go on in. Once you're ready click the Priest and he'll get you married."); - cm.warp(680000210, 2); - } else { - cm.sendOk("Sorry but you don't have a wedding receipt."); - status = 9; - } - } else if (selection == 4) { - if (cm.haveItem(4031395)) { - cm.sendNext("Okay go on in. Once the bride and groom is ready click Nicole on the bottom to warp to the next map. Or use Debbie to leave to Amoria."); - cm.warp(680000210,0); - - } else { - cm.sendOk("Sorry but you don't have a premium wedding invitation."); - status = 9; - } - cm.dispose(); - } - } -} \ No newline at end of file +} diff --git a/scripts/npc/9201014.js b/scripts/npc/9201014.js index 5b1f37854f..24d16259b8 100644 --- a/scripts/npc/9201014.js +++ b/scripts/npc/9201014.js @@ -25,9 +25,13 @@ Angel (get31720 ragezone) -- Extra Info ------------------------------------------------------------------------------------- Fixed by [happydud3] & [XotiCraze] + Improved by [RonanLana] --------------------------------------------------------------------------------------------------- **/ +var bgPrizes = [[2022179,10], [2022282,10], [2210005,5], [2210003,5]]; +var cmPrizes = [[2022011,10], [2000005,50], [2022273,10], [2022179,3]]; + var status; function start() { @@ -37,7 +41,7 @@ function start() { function action(mode, type, selection) { if (mode == -1 || mode == 0) { - cm.sendOk("Goodbye then"); + cm.sendOk("Goodbye then."); cm.dispose(); return; } else if (mode == 1) { @@ -47,7 +51,7 @@ function action(mode, type, selection) { } if (status == 0) { - var msg = "Hello I exchange Onyx Chest for Bride and Groom and the Onyx Chest for prizes!"; + var msg = "Hello I exchange Onyx Chest for Bride and Groom and the Onyx Chest for prizes!#b"; var choice1 = new Array("I have an Onyx Chest for Bride and Groom", "I have an Onyx Chest"); for (var i = 0; i < choice1.length; i++) { msg += "\r\n#L" + i + "#" + choice1[i] + "#l"; @@ -56,38 +60,41 @@ function action(mode, type, selection) { } else if (status == 1) { if (selection == 0) { if (cm.haveItem(4031424)) { - var rand = Math.floor(Math.random() * 4); - if (rand == 0) - cm.gainItem(2022179,10); - else if (rand == 1) - cm.gainItem(2022282,10); - else if (rand == 2) - cm.gainItem(2210005,5); - else if (rand == 3) - cm.gainItem(2210003,5); - cm.gainItem(4031424,-1); + if (cm.isMarried()) { + if(cm.getInventory(2).getNextFreeSlot() >= 0) { + var rand = Math.floor(Math.random() * bgPrizes.length); + cm.gainItem(bgPrizes[rand][0], bgPrizes[rand][1]); + + cm.gainItem(4031424,-1); + cm.dispose(); + } else { + cm.sendOk("You don't have a free USE slot right now."); + cm.dispose(); + } + } else { + cm.sendOk("You must be married to claim the prize for this box."); + cm.dispose(); + } } else { cm.sendOk("You don't have an Onyx Chest for Bride and Groom."); cm.dispose(); } } else if (selection == 1) { if (cm.haveItem(4031423)) { - cm.sendSimple("You may choose your prize.\r\n#L0#Triangular Sushi#l\r\n#L1#50 power elixers#l\r\n#L2#10 Swiss Cheese#l\r\n#L3#3 Onyx Apples#l"); + if(cm.getInventory(2).getNextFreeSlot() >= 0) { + var rand = Math.floor(Math.random() * cmPrizes.length); + cm.gainItem(cmPrizes[rand][0], cmPrizes[rand][1]); + + cm.gainItem(4031423,-1); + cm.dispose(); + } else { + cm.sendOk("You don't have a free USE slot right now."); + cm.dispose(); + } } else { - cm.sendOk("You don't have an Onyx Chest"); + cm.sendOk("You don't have an Onyx Chest."); cm.dispose(); } } - } else if (status == 2) { - if (selection == 0) - cm.gainItem(2022011,10); - else if (selection == 1) - cm.gainItem(2000005,50); - else if (selection == 2) - cm.gainItem(2022273,10); - else if (selection == 3) - cm.gainItem(2022179,3); - cm.gainItem(4031423,-1); - cm.dispose(); } } diff --git a/scripts/npc/9201021.js b/scripts/npc/9201021.js index dd6e86a3bf..2198fcd7ab 100644 --- a/scripts/npc/9201021.js +++ b/scripts/npc/9201021.js @@ -22,7 +22,8 @@ var status = 0; function start() { - cm.sendSimple("Hello, where would you like to go?\r\n#L0#Untamed Hearts Hunting Ground#l\r\n#L1#I have 7 keys. Bring me to smash boxes#l\r\n#L2#Please warp me out.#l"); + if(cm.getMapId() != 680000401) cm.sendSimple("Hello, where would you like to go?\r\n#b" + ((cm.getMapId() != 680000400) ? "#L0#Untamed Hearts Hunting Ground#l\r\n" : "") + ((cm.getMapId() == 680000400) ? "#L1#I have 7 keys. Bring me to smash boxes#l\r\n" : "") + "#L2#Please warp me out.#l#k"); + else cm.sendSimple("Hello, do you want to go back now? Returning here again will cost you #rother 7 keys#k.\r\n#b#L2#Please warp me back to the training grounds.#l#k"); } function action(mode, type, selection) { @@ -36,17 +37,30 @@ function action(mode, type, selection) { else status--; if (status == 1) { - if (selection < 1) + if (selection < 1) { + if(!cm.haveItem(4000313, 1)) { + cm.sendOk("It seems like you lost your #b#t4000313##k. I'm sorry, but I can't let you proceed to the hunting grounds without that."); + cm.dispose(); + return; + } + cm.warp(680000400, 0); - else if (selection < 2) { - if (cm.haveItem(4031217,7)) + } else if (selection < 2) { + if (cm.haveItem(4031217,7)) { cm.gainItem(4031217, -7); - else - cm.sendOk("It seems like you don't have 7 Keys. Kill the cakes and candles in the Untamed Heart Hunting Ground to get keys. "); + cm.warp(680000401, 0); + } else { + cm.sendOk("It seems like you don't have 7 Keys. Kill the cakes and candles in the Untamed Heart Hunting Ground to get keys."); + } } else if (selection > 1) { - cm.warp(680000500, 0); - cm.sendOk("Goodbye. I hope you enjoyed the wedding!"); + if(cm.getMapId() != 680000401) { + cm.warp(680000500, 0); + cm.sendOk("Goodbye. I hope you enjoyed the wedding!"); + } else { + cm.warp(680000400, 0); + } } + cm.dispose(); } } \ No newline at end of file diff --git a/scripts/npc/9201023.js b/scripts/npc/9201023.js index 640434c8ac..e03ad10988 100644 --- a/scripts/npc/9201023.js +++ b/scripts/npc/9201023.js @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 @@ -19,47 +17,129 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/** - *9201023 - Nana(K) - *@author Jvlaple +/* Nana, the Love fairy + Amoria (680000000) + Engagement ring NPC. */ - -function start() { - cm.sendOk("Hi, I'm Nana the love fairy... Hows it going?"); - cm.dispose(); + +var status; +var state; + +var item; +var mats; +var matQty; +var cost; + +var options; + +function hasProofOfLoves(player) { + var count = 0; + + for(var i = 4031367; i <= 4031372; i++) { + if(player.haveItem(i)) { + count++; + } + } + + return count >= 4; } -// -//function action(mode, type, selection) { -// if (mode == -1) { -// cm.dispose(); -// } else { -// if (mode == 0 && status == 0) { -// cm.dispose(); -// return; -// } -// if (mode == 1) -// status++; -// else -// status--; -// if (cm.getPlayer().getMarriageQuestLevel() == 1 || cm.getPlayer().getMarriageQuestLevel() == 52) { -// if (!cm.haveItem(4000083, 20)) { -// if (status == 0) { -// cm.sendNext("Hey, you look like you need proofs of love? I can get them for you."); -// } else if (status == 1) { -// cm.sendNext("All you have to do is bring me 20 #bJr. Sentinel Pieces.#k."); -// cm.dispose(); -// } -// } else { -// if (status == 0) { -// cm.sendNext("Wow, you were quick! Heres the proof of love..."); -// cm.gainItem(4000083, -20) -// cm.gainItem(4031369, 1); -// cm.dispose(); -// } -// } -// } else { -// cm.sendOk("Hi, I'm Nana the love fairy... Hows it going?"); -// cm.dispose(); -// } -// } -//} \ No newline at end of file + +function getNanaLocation(player) { + var mapid = player.getMap().getId(); + + for(var i = 0; i < mapids.length; i++) { + if(mapid == mapids[i]) { + return i; + } + } + + return -1; +} + +var nanaLoc; +var mapids = [100000000, 103000000, 102000000, 101000000, 200000000, 220000000]; +var questItems = [4000001, 4000037, 4000215, 4000026, 4000070, 4000128]; +var questExp = [2000, 5000, 10000, 17000, 22000, 30000]; + +function processNanaQuest() { + if(cm.haveItem(questItems[nanaLoc], 50)) { + if(cm.canHold(4031367 + nanaLoc, 1)) { + cm.gainItem(questItems[nanaLoc], -50); + cm.gainItem(4031367 + nanaLoc, 1); + + cm.sendOk("Kyaaaa~ Thank you a lot, here get the #b#t4031367##k."); + return true; + } else { + cm.sendOk("Please have a free ETC slot available to hold the token of love."); + } + } else { + cm.sendOk("Please gather to me #b50 #t" + questItems[nanaLoc] + "##k."); + } + + return false; +} + +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) { + if(!cm.isQuestStarted(100400)) { + cm.sendOk("Hello #b#h0##k, I'm #p9201001# the fairy of Love."); + cm.dispose(); + return; + } + + nanaLoc = getNanaLocation(cm.getPlayer()); + if(nanaLoc == -1) { + cm.sendOk("Hello #b#h0##k, I'm #p9201001# the fairy of Love."); + cm.dispose(); + return; + } + + if(!cm.haveItem(4031367 + nanaLoc, 1)) { + if(cm.isQuestCompleted(100401 + nanaLoc)) { + state = 1; + cm.sendAcceptDecline("Did you lost the #k#t4031367##k I gave to you? Well, I can share another one with you, but you will need to redo the favor I asked last time, is that ok? I need you to bring me #r50 #t" + questItems[nanaLoc] + "#'s.#k"); + } else if(cm.isQuestStarted(100401 + nanaLoc)) { + if(processNanaQuest()) { + cm.gainExp(questExp[nanaLoc] * cm.getPlayer().getExpRate()); + cm.completeQuest(100401 + nanaLoc); + } + + cm.dispose(); + } else { + state = 0; + cm.sendAcceptDecline("Are you searching for #k#t4031367#'s#k? I can share one with you, but you must do a favor for me, is that ok?"); + } + } else { + cm.sendOk("Hey there. Did you get the #t4031367# from the other Nana's already?"); + cm.dispose(); + } + } else if(status == 1) { + if(state == 0) { + cm.startQuest(100401 + nanaLoc); + + cm.sendOk("I need you to collect #r50 #t" + questItems[nanaLoc] + "##k."); + cm.dispose(); + } else { + processNanaQuest(); + cm.dispose(); + } + } + } +} \ No newline at end of file diff --git a/scripts/npc/9201024.js b/scripts/npc/9201024.js index c70634b9da..e03ad10988 100644 --- a/scripts/npc/9201024.js +++ b/scripts/npc/9201024.js @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 @@ -19,47 +17,129 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/** - *9201024 - Nana(E) - *@author Jvlaple +/* Nana, the Love fairy + Amoria (680000000) + Engagement ring NPC. */ - -function start() { - cm.sendOk("Hi, I'm Nana the love fairy... How's it going?"); - cm.dispose(); + +var status; +var state; + +var item; +var mats; +var matQty; +var cost; + +var options; + +function hasProofOfLoves(player) { + var count = 0; + + for(var i = 4031367; i <= 4031372; i++) { + if(player.haveItem(i)) { + count++; + } + } + + return count >= 4; } -// -//function action(mode, type, selection) { -// if (mode == -1) { -// cm.dispose(); -// } else { -// if (mode == 0 && status == 0) { -// cm.dispose(); -// return; -// } -// if (mode == 1) -// status++; -// else -// status--; -// if (cm.getPlayer().getMarriageQuestLevel() == 1 || cm.getPlayer().getMarriageQuestLevel() == 52) { -// if (!cm.haveItem(4003005, 20)) { -// if (status == 0) { -// cm.sendNext("Hey, you look like you need proofs of love? I can get them for you."); -// } else if (status == 1) { -// cm.sendNext("All you have to do is bring me 20 #bSoft Feathers#k."); -// cm.dispose(); -// } -// } else { -// if (status == 0) { -// cm.sendNext("Wow, you were quick! Heres the proof of love..."); -// cm.gainItem(4003005, -20) -// cm.gainItem(4031368, 1); -// cm.dispose(); -// } -// } -// } else { -// cm.sendOk("Hi, I'm Nana the love fairy... Hows it going?"); -// cm.dispose(); -// } -// } -//} \ No newline at end of file + +function getNanaLocation(player) { + var mapid = player.getMap().getId(); + + for(var i = 0; i < mapids.length; i++) { + if(mapid == mapids[i]) { + return i; + } + } + + return -1; +} + +var nanaLoc; +var mapids = [100000000, 103000000, 102000000, 101000000, 200000000, 220000000]; +var questItems = [4000001, 4000037, 4000215, 4000026, 4000070, 4000128]; +var questExp = [2000, 5000, 10000, 17000, 22000, 30000]; + +function processNanaQuest() { + if(cm.haveItem(questItems[nanaLoc], 50)) { + if(cm.canHold(4031367 + nanaLoc, 1)) { + cm.gainItem(questItems[nanaLoc], -50); + cm.gainItem(4031367 + nanaLoc, 1); + + cm.sendOk("Kyaaaa~ Thank you a lot, here get the #b#t4031367##k."); + return true; + } else { + cm.sendOk("Please have a free ETC slot available to hold the token of love."); + } + } else { + cm.sendOk("Please gather to me #b50 #t" + questItems[nanaLoc] + "##k."); + } + + return false; +} + +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) { + if(!cm.isQuestStarted(100400)) { + cm.sendOk("Hello #b#h0##k, I'm #p9201001# the fairy of Love."); + cm.dispose(); + return; + } + + nanaLoc = getNanaLocation(cm.getPlayer()); + if(nanaLoc == -1) { + cm.sendOk("Hello #b#h0##k, I'm #p9201001# the fairy of Love."); + cm.dispose(); + return; + } + + if(!cm.haveItem(4031367 + nanaLoc, 1)) { + if(cm.isQuestCompleted(100401 + nanaLoc)) { + state = 1; + cm.sendAcceptDecline("Did you lost the #k#t4031367##k I gave to you? Well, I can share another one with you, but you will need to redo the favor I asked last time, is that ok? I need you to bring me #r50 #t" + questItems[nanaLoc] + "#'s.#k"); + } else if(cm.isQuestStarted(100401 + nanaLoc)) { + if(processNanaQuest()) { + cm.gainExp(questExp[nanaLoc] * cm.getPlayer().getExpRate()); + cm.completeQuest(100401 + nanaLoc); + } + + cm.dispose(); + } else { + state = 0; + cm.sendAcceptDecline("Are you searching for #k#t4031367#'s#k? I can share one with you, but you must do a favor for me, is that ok?"); + } + } else { + cm.sendOk("Hey there. Did you get the #t4031367# from the other Nana's already?"); + cm.dispose(); + } + } else if(status == 1) { + if(state == 0) { + cm.startQuest(100401 + nanaLoc); + + cm.sendOk("I need you to collect #r50 #t" + questItems[nanaLoc] + "##k."); + cm.dispose(); + } else { + processNanaQuest(); + cm.dispose(); + } + } + } +} \ No newline at end of file diff --git a/scripts/npc/9201025.js b/scripts/npc/9201025.js index 26b0e72564..e03ad10988 100644 --- a/scripts/npc/9201025.js +++ b/scripts/npc/9201025.js @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 @@ -19,47 +17,129 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/** - *9201025 - Nana(O) - *@author Jvlaple +/* Nana, the Love fairy + Amoria (680000000) + Engagement ring NPC. */ - -function start() { - cm.sendOk("Hi, I'm Nana the love fairy... Hows it going?"); - cm.dispose(); + +var status; +var state; + +var item; +var mats; +var matQty; +var cost; + +var options; + +function hasProofOfLoves(player) { + var count = 0; + + for(var i = 4031367; i <= 4031372; i++) { + if(player.haveItem(i)) { + count++; + } + } + + return count >= 4; } -// -//function action(mode, type, selection) { -// if (mode == -1) { -// cm.dispose(); -// } else { -// if (mode == 0 && status == 0) { -// cm.dispose(); -// return; -// } -// if (mode == 1) -// status++; -// else -// status--; -// if (cm.getPlayer().getMarriageQuestLevel() == 1 || cm.getPlayer().getMarriageQuestLevel() == 52) { -// if (!cm.haveItem(4000083, 20)) { -// if (status == 0) { -// cm.sendNext("Hey, you look like you need proofs of love? I can get them for you."); -// } else if (status == 1) { -// cm.sendNext("All you have to do is bring me 20 #bJr. Sentinel Pieces.#k."); -// cm.dispose(); -// } -// } else { -// if (status == 0) { -// cm.sendNext("Wow, you were quick! Heres the proof of love..."); -// cm.gainItem(4000083, -20) -// cm.gainItem(4031369, 1); -// cm.dispose(); -// } -// } -// } else { -// cm.sendOk("Hi, I'm Nana the love fairy... Hows it going?"); -// cm.dispose(); -// } -// } -//} \ No newline at end of file + +function getNanaLocation(player) { + var mapid = player.getMap().getId(); + + for(var i = 0; i < mapids.length; i++) { + if(mapid == mapids[i]) { + return i; + } + } + + return -1; +} + +var nanaLoc; +var mapids = [100000000, 103000000, 102000000, 101000000, 200000000, 220000000]; +var questItems = [4000001, 4000037, 4000215, 4000026, 4000070, 4000128]; +var questExp = [2000, 5000, 10000, 17000, 22000, 30000]; + +function processNanaQuest() { + if(cm.haveItem(questItems[nanaLoc], 50)) { + if(cm.canHold(4031367 + nanaLoc, 1)) { + cm.gainItem(questItems[nanaLoc], -50); + cm.gainItem(4031367 + nanaLoc, 1); + + cm.sendOk("Kyaaaa~ Thank you a lot, here get the #b#t4031367##k."); + return true; + } else { + cm.sendOk("Please have a free ETC slot available to hold the token of love."); + } + } else { + cm.sendOk("Please gather to me #b50 #t" + questItems[nanaLoc] + "##k."); + } + + return false; +} + +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) { + if(!cm.isQuestStarted(100400)) { + cm.sendOk("Hello #b#h0##k, I'm #p9201001# the fairy of Love."); + cm.dispose(); + return; + } + + nanaLoc = getNanaLocation(cm.getPlayer()); + if(nanaLoc == -1) { + cm.sendOk("Hello #b#h0##k, I'm #p9201001# the fairy of Love."); + cm.dispose(); + return; + } + + if(!cm.haveItem(4031367 + nanaLoc, 1)) { + if(cm.isQuestCompleted(100401 + nanaLoc)) { + state = 1; + cm.sendAcceptDecline("Did you lost the #k#t4031367##k I gave to you? Well, I can share another one with you, but you will need to redo the favor I asked last time, is that ok? I need you to bring me #r50 #t" + questItems[nanaLoc] + "#'s.#k"); + } else if(cm.isQuestStarted(100401 + nanaLoc)) { + if(processNanaQuest()) { + cm.gainExp(questExp[nanaLoc] * cm.getPlayer().getExpRate()); + cm.completeQuest(100401 + nanaLoc); + } + + cm.dispose(); + } else { + state = 0; + cm.sendAcceptDecline("Are you searching for #k#t4031367#'s#k? I can share one with you, but you must do a favor for me, is that ok?"); + } + } else { + cm.sendOk("Hey there. Did you get the #t4031367# from the other Nana's already?"); + cm.dispose(); + } + } else if(status == 1) { + if(state == 0) { + cm.startQuest(100401 + nanaLoc); + + cm.sendOk("I need you to collect #r50 #t" + questItems[nanaLoc] + "##k."); + cm.dispose(); + } else { + processNanaQuest(); + cm.dispose(); + } + } + } +} \ No newline at end of file diff --git a/scripts/npc/9201026.js b/scripts/npc/9201026.js index 7e5a8b7610..e03ad10988 100644 --- a/scripts/npc/9201026.js +++ b/scripts/npc/9201026.js @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 @@ -19,47 +17,129 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/** - *9201026 - Nana(L) - *@author Jvlaple +/* Nana, the Love fairy + Amoria (680000000) + Engagement ring NPC. */ - -function start() { - cm.sendOk("Hi, I'm Nana the love fairy... How's it going?"); - cm.dispose(); + +var status; +var state; + +var item; +var mats; +var matQty; +var cost; + +var options; + +function hasProofOfLoves(player) { + var count = 0; + + for(var i = 4031367; i <= 4031372; i++) { + if(player.haveItem(i)) { + count++; + } + } + + return count >= 4; } -// -//function action(mode, type, selection) { -// if (mode == -1) { -// cm.dispose(); -// } else { -// if (mode == 0 && status == 0) { -// cm.dispose(); -// return; -// } -// if (mode == 1) -// status++; -// else -// status--; -// if (cm.getPlayer().getMarriageQuestLevel() == 1 || cm.getPlayer().getMarriageQuestLevel() == 52) { -// if (!cm.haveItem(4000106, 30)) { -// if (status == 0) { -// cm.sendNext("Hey, you look like you need proofs of love? I can get them for you."); -// } else if (status == 1) { -// cm.sendNext("All you have to do is bring me 30 #bTeddy Cotton#k."); -// cm.dispose(); -// } -// } else { -// if (status == 0) { -// cm.sendNext("Wow, you were quick! Heres the proof of love..."); -// cm.gainItem(4000106, -30) -// cm.gainItem(4031370, 1); -// cm.dispose(); -// } -// } -// } else { -// cm.sendOk("Hi, I'm Nana the love fairy... Hows it going?"); -// cm.dispose(); -// } -// } -//} \ No newline at end of file + +function getNanaLocation(player) { + var mapid = player.getMap().getId(); + + for(var i = 0; i < mapids.length; i++) { + if(mapid == mapids[i]) { + return i; + } + } + + return -1; +} + +var nanaLoc; +var mapids = [100000000, 103000000, 102000000, 101000000, 200000000, 220000000]; +var questItems = [4000001, 4000037, 4000215, 4000026, 4000070, 4000128]; +var questExp = [2000, 5000, 10000, 17000, 22000, 30000]; + +function processNanaQuest() { + if(cm.haveItem(questItems[nanaLoc], 50)) { + if(cm.canHold(4031367 + nanaLoc, 1)) { + cm.gainItem(questItems[nanaLoc], -50); + cm.gainItem(4031367 + nanaLoc, 1); + + cm.sendOk("Kyaaaa~ Thank you a lot, here get the #b#t4031367##k."); + return true; + } else { + cm.sendOk("Please have a free ETC slot available to hold the token of love."); + } + } else { + cm.sendOk("Please gather to me #b50 #t" + questItems[nanaLoc] + "##k."); + } + + return false; +} + +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) { + if(!cm.isQuestStarted(100400)) { + cm.sendOk("Hello #b#h0##k, I'm #p9201001# the fairy of Love."); + cm.dispose(); + return; + } + + nanaLoc = getNanaLocation(cm.getPlayer()); + if(nanaLoc == -1) { + cm.sendOk("Hello #b#h0##k, I'm #p9201001# the fairy of Love."); + cm.dispose(); + return; + } + + if(!cm.haveItem(4031367 + nanaLoc, 1)) { + if(cm.isQuestCompleted(100401 + nanaLoc)) { + state = 1; + cm.sendAcceptDecline("Did you lost the #k#t4031367##k I gave to you? Well, I can share another one with you, but you will need to redo the favor I asked last time, is that ok? I need you to bring me #r50 #t" + questItems[nanaLoc] + "#'s.#k"); + } else if(cm.isQuestStarted(100401 + nanaLoc)) { + if(processNanaQuest()) { + cm.gainExp(questExp[nanaLoc] * cm.getPlayer().getExpRate()); + cm.completeQuest(100401 + nanaLoc); + } + + cm.dispose(); + } else { + state = 0; + cm.sendAcceptDecline("Are you searching for #k#t4031367#'s#k? I can share one with you, but you must do a favor for me, is that ok?"); + } + } else { + cm.sendOk("Hey there. Did you get the #t4031367# from the other Nana's already?"); + cm.dispose(); + } + } else if(status == 1) { + if(state == 0) { + cm.startQuest(100401 + nanaLoc); + + cm.sendOk("I need you to collect #r50 #t" + questItems[nanaLoc] + "##k."); + cm.dispose(); + } else { + processNanaQuest(); + cm.dispose(); + } + } + } +} \ No newline at end of file diff --git a/scripts/npc/9201027.js b/scripts/npc/9201027.js index dba1fc6694..e03ad10988 100644 --- a/scripts/npc/9201027.js +++ b/scripts/npc/9201027.js @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 @@ -19,48 +17,129 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/** - *9201027 - Nana(P) - *@author Jvlaple +/* Nana, the Love fairy + Amoria (680000000) + Engagement ring NPC. */ - -function start() { - cm.sendOk("Hi, I'm Nana the love fairy... How's it going?"); - cm.dispose(); + +var status; +var state; + +var item; +var mats; +var matQty; +var cost; + +var options; + +function hasProofOfLoves(player) { + var count = 0; + + for(var i = 4031367; i <= 4031372; i++) { + if(player.haveItem(i)) { + count++; + } + } + + return count >= 4; } -//function action(mode, type, selection) { -// if (mode == -1) { -// cm.dispose(); -// } else { -// if (mode == 0 && status == 0) { -// cm.dispose(); -// return; -// } -// if (mode == 1) -// status++; -// else -// status--; -// if (cm.getPlayer().getMarriageQuestLevel() == 1 || cm.getPlayer().getMarriageQuestLevel() == 52) { -// if (!cm.haveItem(4000018, 40)) { -// if (status == 0) { -// cm.sendNext("Hey, you look like you need proofs of love? I can get them for you."); -// } else if (status == 1) { -// cm.sendOk("All you have to do is bring me 40 #bFirewood#k."); -// cm.dispose(); -// } -// } else { -// if (status == 0) { -// cm.sendNext("Wow, you were quick! Heres the proof of love..."); -// cm.gainItem(4000018, -40) -// cm.gainItem(4031371, 1); -// cm.dispose(); -// } -// } -// } -// else { -// cm.sendOk("Hi, I'm Nana the love fairy... Hows it going?"); -// cm.dispose(); -// } -// } -//} \ No newline at end of file +function getNanaLocation(player) { + var mapid = player.getMap().getId(); + + for(var i = 0; i < mapids.length; i++) { + if(mapid == mapids[i]) { + return i; + } + } + + return -1; +} + +var nanaLoc; +var mapids = [100000000, 103000000, 102000000, 101000000, 200000000, 220000000]; +var questItems = [4000001, 4000037, 4000215, 4000026, 4000070, 4000128]; +var questExp = [2000, 5000, 10000, 17000, 22000, 30000]; + +function processNanaQuest() { + if(cm.haveItem(questItems[nanaLoc], 50)) { + if(cm.canHold(4031367 + nanaLoc, 1)) { + cm.gainItem(questItems[nanaLoc], -50); + cm.gainItem(4031367 + nanaLoc, 1); + + cm.sendOk("Kyaaaa~ Thank you a lot, here get the #b#t4031367##k."); + return true; + } else { + cm.sendOk("Please have a free ETC slot available to hold the token of love."); + } + } else { + cm.sendOk("Please gather to me #b50 #t" + questItems[nanaLoc] + "##k."); + } + + return false; +} + +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) { + if(!cm.isQuestStarted(100400)) { + cm.sendOk("Hello #b#h0##k, I'm #p9201001# the fairy of Love."); + cm.dispose(); + return; + } + + nanaLoc = getNanaLocation(cm.getPlayer()); + if(nanaLoc == -1) { + cm.sendOk("Hello #b#h0##k, I'm #p9201001# the fairy of Love."); + cm.dispose(); + return; + } + + if(!cm.haveItem(4031367 + nanaLoc, 1)) { + if(cm.isQuestCompleted(100401 + nanaLoc)) { + state = 1; + cm.sendAcceptDecline("Did you lost the #k#t4031367##k I gave to you? Well, I can share another one with you, but you will need to redo the favor I asked last time, is that ok? I need you to bring me #r50 #t" + questItems[nanaLoc] + "#'s.#k"); + } else if(cm.isQuestStarted(100401 + nanaLoc)) { + if(processNanaQuest()) { + cm.gainExp(questExp[nanaLoc] * cm.getPlayer().getExpRate()); + cm.completeQuest(100401 + nanaLoc); + } + + cm.dispose(); + } else { + state = 0; + cm.sendAcceptDecline("Are you searching for #k#t4031367#'s#k? I can share one with you, but you must do a favor for me, is that ok?"); + } + } else { + cm.sendOk("Hey there. Did you get the #t4031367# from the other Nana's already?"); + cm.dispose(); + } + } else if(status == 1) { + if(state == 0) { + cm.startQuest(100401 + nanaLoc); + + cm.sendOk("I need you to collect #r50 #t" + questItems[nanaLoc] + "##k."); + cm.dispose(); + } else { + processNanaQuest(); + cm.dispose(); + } + } + } +} \ No newline at end of file diff --git a/scripts/npc/9201048.js b/scripts/npc/9201048.js index afbb84c1a8..1c1cc13470 100644 --- a/scripts/npc/9201048.js +++ b/scripts/npc/9201048.js @@ -52,6 +52,9 @@ function action(mode, type, selection) { cm.sendOk("The Amoria PQ has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nIf you're brave enough to attempt the Amorian Challenge, join with others like you and let your #bparty leader#k talk to me. If a party filled with whole married couples register to the challenge, better prizes awaits there.#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details."); diff --git a/scripts/npc/9201049.js b/scripts/npc/9201049.js index 42f8ed44dc..f054f065ec 100644 --- a/scripts/npc/9201049.js +++ b/scripts/npc/9201049.js @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 @@ -19,54 +17,52 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -/** - Ames the Wise --- By --------------------------------------------------------------------------------------------- - Xelkin --- Edited by -------------------------------------------------------------------------------------- - Angel (get31720 ragezone --- Extra Info ------------------------------------------------------------------------------------- - Fixed by [happydud3 (BENG)] & [XotiCraze] --- Fixed Dispose ---------------------------------------------------------------------------------- - Fixed by Moogra ---------------------------------------------------------------------------------------------------- -**/ -var status = -1; +/* Ames the Wise + Wedding exit map + Gives Onyx Chest to anyone completing the wedding event. + */ +var status; + function start() { - var rings = new Array(1112806, 1112803, 1112807, 1112809); - var hasRing = false; - for (var x = 0; x < rings.length && !hasRing; x++) - if (cm.haveItem(rings[x])) { - hasRing = true; - break; - } - if (hasRing) - cm.sendNext("You've reached the end of the wedding. You will receive an Onyx Chest for Bride and Groom and an Onyx Chest. Exchange them at Pila, she is at the top of Amoria."); - else if (cm.haveItem(4000313)) { - cm.sendNext("Wow the end of the wedding already ? Good bye then.!"); - status = 20; - } else { - cm.sendNext("You do not have the Gold Maple Leaf and you do not have a wedding ring so I will take you to Amoria."); - status = 21; - } + status = -1; + action(1, 0, 0); } function action(mode, type, selection) { - if (mode < 1) { - cm.sendOk("Goodbye then."); - cm.dispose(); - } else { - status++; - if (status == 1) { - cm.gainItem(4031424,1); - cm.gainItem(4031423,1); + if (mode == -1) { + cm.dispose(); + } else { + if (mode == 0 && type > 0) { + cm.dispose(); + return; + } + if (mode == 1) + status++; + else + status--; + + if(status == 0) { + cm.sendOk("Hey there, did you enjoy the wedding? I will head you back to #bAmoria#k now."); + } else if(status == 1) { + var eim = cm.getEventInstance(); + if(eim != null) { + var boxId = (cm.getPlayer().getId() == eim.getIntProperty("groomId") || cm.getPlayer().getId() == eim.getIntProperty("brideId")) ? 4031424 : 4031423; + + if(cm.canHold(boxId, 1)) { + cm.gainItem(boxId, 1); + cm.warp(680000000); + cm.sendOk("You just received an Onyx Chest. Search for #b#p9201014##k, she is at the top of Amoria, she knows how to open these."); + } else { + cm.sendOk("Please make room on your ETC inventory to receive the Onyx Chest."); + cm.dispose(); + return; + } + } else { + cm.warp(680000000); + } + cm.dispose(); - } else if (status == 21) { - cm.gainItem(4000313,-1); - cm.gainItem(4031423,1); } - cm.warp(680000000); - cm.dispose(); } -} +} \ No newline at end of file diff --git a/scripts/npc/9201050.js b/scripts/npc/9201050.js index 0ba84f3e84..946012cf4b 100644 --- a/scripts/npc/9201050.js +++ b/scripts/npc/9201050.js @@ -35,6 +35,11 @@ function action(mode, type, selection) { if (mode == -1) cm.dispose(); else { + if (mode == 0 && type > 0) { + cm.dispose(); + return; + } + if (mode == 1) status++; else diff --git a/scripts/npc/9209001.js b/scripts/npc/9209001.js index bc641f0449..b501028217 100644 --- a/scripts/npc/9209001.js +++ b/scripts/npc/9209001.js @@ -24,6 +24,10 @@ status = -1; var sel, sel2; function start() { + cm.sendOk("Hello, the Maple 7th Day Market is currently unavailable."); + cm.dispose(); + return; + cm.sendSimple("Hello, the Maple 7th Day Market opens today.#b\r\n#L0#Move to Maple 7th Day Market map\r\n#L1#Listen for an explanation about the Maple 7th Day Market"); } diff --git a/scripts/npc/9220018.js b/scripts/npc/9220018.js index b569645330..b9fa2c2d3b 100644 --- a/scripts/npc/9220018.js +++ b/scripts/npc/9220018.js @@ -51,8 +51,11 @@ function action(mode, type, selection) { cm.sendOk("The Treasure PQ has encountered an error."); cm.dispose(); return; + } else if(cm.isUsingOldPqNpcStyle()) { + action(1, 0, 0); + return; } - + cm.sendSimple("#e#b\r\n#k#n" + em.getProperty("party") + "\r\n\r\nYou can't go any further because of the extremely dangerous creatures lying ahead. Would you like to collaborate with party members to complete the quest? If so, please have your #bparty leader#k talk to me.#b\r\n#L0#I want to participate in the party quest.\r\n#L1#I want to find party members.\r\n#L2#I would like to hear more details."); } else if (status == 1) { if (selection == 0) { diff --git a/scripts/npc/9977777.js b/scripts/npc/9977777.js index 1ccfd57841..5accc4c84e 100644 --- a/scripts/npc/9977777.js +++ b/scripts/npc/9977777.js @@ -32,15 +32,15 @@ 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", "Project"]; +var tabs = ["PQs", "Skills", "Quests", "Player Social Network", "Cash & Items", "Monsters, Maps & Reactors", "PQ potentials", "Player potentials", "Server potentials", "Admin/GM commands", "Localhost edits", "Project"]; function addFeature(feature) { feature_cursor.push(feature); } function writeFeatureTab_PQs() { - addFeature("HPQ/KPQ/LPQ/LMPQ/OPQ/EllinPQ/PiratePQ."); - addFeature("MagatiaPQ/HorntailPQ/AmoriaPQ/TreasurePQ."); + addFeature("HPQ/KPQ/LPQ/LMPQ/OPQ/EllinPQ/PiratePQ/AmoriaPQ."); + addFeature("MagatiaPQ/HorntailPQ/TreasurePQ/ElnathPQ."); addFeature("CWKPQ as Expedition-based event."); addFeature("Scarga/Horntail/Showa/Balrog/Zakum/Pinkbean."); addFeature("GuildPQ & queue with multi-lobby systems available."); @@ -50,7 +50,7 @@ function writeFeatureTab_PQs() { } function writeFeatureTab_Skills() { - addFeature("Reviewed many skills, such as V. Star and M. Door."); + addFeature("Reviewed many skills, such as Steal and M. Door."); addFeature("Maker skill features properly developed."); addFeature("Chair Mastery - map chair boosts HP/MP rec."); } @@ -60,14 +60,15 @@ function writeFeatureTab_Quests() { addFeature("Quests are now rewarding items properly."); addFeature("Selection of rewards works properly."); addFeature("Loads of quests have been patched."); - addFeature("Lots of job questlines have been reviewed."); + addFeature("Aran questline has been reviewed."); + addFeature("Reviewed several 4th job skill questlines."); addFeature("Rewarding system now looks up for item stacking."); addFeature("3rd job quiz with all 40-question pool available."); } function writeFeatureTab_PlayerSocialNetwork() { addFeature("Guild and Alliance system fully functional."); - addFeature("Party for novices-only (may still be disabled locally)."); + addFeature("Party for novices-only."); addFeature("Thoroughly reviewed P. Shops and H. Merchants."); addFeature("Transactions on Merchs instantly announced to owner."); addFeature("Game minirooms with semi-functional pw system."); @@ -83,12 +84,16 @@ function writeFeatureTab_CashItems() { addFeature("New town scroll: antibanish. Counters boss banishes."); addFeature("Inventory system checks for free slot & stack space."); addFeature("Storage with 'Arrange Items' feature functional."); + addFeature("Further improved Karma scissors."); + addFeature("Scroll for Spikes on Shoes."); + addFeature("Scroll for Cold Protection."); addFeature("Vega's spell."); addFeature("Owl of Minerva."); addFeature("Pet item ignore."); addFeature("New Year's card."); addFeature("Kite."); addFeature("Cash Shop surprise."); + addFeature("Maple Life."); } function writeFeatureTab_MonstersMapsReactors() { @@ -97,12 +102,15 @@ function writeFeatureTab_MonstersMapsReactors() { addFeature("Monsterbook displays updated drop data info."); addFeature("Every skill/mastery book is now obtainable."); addFeature("Mobs now can drop more than one of the same equip."); + addFeature("Implemented Zombify disease status."); addFeature("Added Boss HP Bar for dozens of bosses."); addFeature("Game will favor showing the targeted boss HPbar."); + addFeature("Dmg overtime on maps and neutralizers functional."); addFeature("Boats, elevator and other travel mechanics functional."); addFeature("C. Balrog's boat approaching visual effect functional."); addFeature("PQs, Taxis and events warps players to random SPs."); addFeature("PQ boxes sprays items when opened, GMS-like."); + addFeature("Reactors pick items up smartly from the field."); addFeature("Reviewed Masteria, W. Tour, N. Desert and Neo City."); addFeature("Giant Cake boss drops s. bags and Maple items."); } @@ -127,16 +135,20 @@ function writeFeatureTab_Serverpotentials() { addFeature("Enhanced auto-pot system: smart pet potion handle."); addFeature("Enhanced buff system: best buffs effects takes place."); addFeature("Enhanced AP auto-assigner: focus on eqp demands."); + addFeature("NPC crafters won't take items freely anymore."); + addFeature("Duey: pkg rcvd popup and many delivery mechanics."); addFeature("Pet pickup gives preference to player attacks."); addFeature("Channel capacity bar and worlds with capacity check."); addFeature("Diseases visible for others, even after changing maps."); addFeature("Poison damage value visible for other players."); addFeature("M. book announcer displays info based on demand."); addFeature("Custom jail system."); + addFeature("Custom buyback system, uses mesos / NX, via MTS."); addFeature("Delete Character."); addFeature("Autosaver (periodically saves player's data on DB)."); addFeature("Fixed and randomized HP/MP growth rate available."); addFeature("Prevented 'NPC gone after some uptime' issue."); + addFeature("AP assigning available for novices level 10 or below."); addFeature("Automatic account registration - thanks shavit!"); } @@ -146,6 +158,19 @@ function writeFeatureTab_AdminGMcommands() { addFeature("Several new commands."); } +function writeFeatureTab_Localhostedits() { + addFeature("Removed the 'n' NPC dialog issue."); + addFeature("Removed caps for MATK, WMDEF, ACC and AVOID."); + addFeature("Removed MTS block, buyback available anywhere."); + addFeature("Removed party blocks for novices under level 10."); + addFeature("Set a much more higher cap for SPEED."); + addFeature("Removed AP usage block for novices."); + addFeature("Removed attack gem block on defensiver gear w/ Maker."); + addFeature("Removed AP excess popup - thanks kevintjuh93!"); + addFeature("Removed 'GMs can't attack' - thanks kevintjuh93!"); + addFeature("Removed 'Gained a level!' - thanks PrinceReborn!"); +} + function writeFeatureTab_Project() { addFeature("Organized project code."); addFeature("Highly updated drop data."); diff --git a/scripts/npc/commands.js b/scripts/npc/commands.js index b64df4f4bb..84cccd1567 100644 --- a/scripts/npc/commands.js +++ b/scripts/npc/commands.js @@ -55,6 +55,8 @@ function writeHeavenMSCommandsLv6() { //Admin addCommand("shutdownnow", ""); addCommand("clearquestcache", ""); addCommand("clearquest", ""); + addCommand("spawnallpnpcs", ""); + addCommand("eraseallpnpcs", ""); } function writeHeavenMSCommandsLv5() { //Developer @@ -74,6 +76,7 @@ function writeHeavenMSCommandsLv5() { //Developer addCommand("debugcoupons", ""); addCommand("debugplayercoupons", ""); addCommand("debugtimer", ""); + addCommand("debugmarriage", ""); addCommand("set", ""); } @@ -96,7 +99,8 @@ function writeHeavenMSCommandsLv4() { //SuperGM addCommand("pap", ""); addCommand("pianus", ""); addCommand("cake", ""); - //addCommand("playernpc", ""); + addCommand("pnpcremove", ""); + addCommand("playernpc", ""); } function writeHeavenMSCommandsLv3() { //GM diff --git a/scripts/portal/mapleMarket7_out.js b/scripts/portal/mapleMarket7_out.js index 18634e50ad..053182a1d1 100644 --- a/scripts/portal/mapleMarket7_out.js +++ b/scripts/portal/mapleMarket7_out.js @@ -20,6 +20,6 @@ along with this program. If not, see . */ function enter(pi) { - pi.playPortalSound(); pi.warp(pi.getPlayer().getSavedLocation("EVENT")); + pi.playPortalSound(); pi.warp(pi.getPlayer().getSavedLocation("EVENT")); return true; } \ No newline at end of file diff --git a/scripts/portal/met_out.js b/scripts/portal/met_out.js index cc5e7ebc92..e20c518cfc 100644 --- a/scripts/portal/met_out.js +++ b/scripts/portal/met_out.js @@ -25,10 +25,10 @@ Author: kevintjuh93 function enter(pi) { var mapId = pi.getPlayer().getSavedLocation("MIRROR"); + pi.playPortalSound(); if(mapId == -1) pi.warp(102040000, 12); else pi.warp(mapId); - //pi.playPortalSound(); pi.warp(102040000, 12); - + //pi.warp(102040000, 12); return true; } \ No newline at end of file diff --git a/scripts/portal/rankDeveloperRoom.js b/scripts/portal/rankDeveloperRoom.js index 8cd02265d2..4b6e3c6987 100644 --- a/scripts/portal/rankDeveloperRoom.js +++ b/scripts/portal/rankDeveloperRoom.js @@ -1,6 +1,6 @@ function enter(pi) { if (pi.getPlayer().getMapId() != 777777777) { - if(!Packages.constants.GameConstants.canEnterDeveloperRoom()) { + if(!Packages.net.server.Server.getInstance().canEnterDeveloperRoom()) { pi.message("The next room is currently unavailable."); return false; } diff --git a/scripts/portal/rankRoom.js b/scripts/portal/rankRoom.js index bf69c8675b..812d6ae5b8 100644 --- a/scripts/portal/rankRoom.js +++ b/scripts/portal/rankRoom.js @@ -18,7 +18,7 @@ function enter(pi) { pi.warp(103000008, 1); //or 103000009 break; case 100000201: - pi.warp(100000204, 1); //or 100000205 + pi.warp(100000204, 2); //or 100000205 break; default: pi.warp(pi.getMapId() + 1, 1); //or + 2 diff --git a/scripts/portal/s4berserk.js b/scripts/portal/s4berserk.js index dd0613a16d..e0a4e1e052 100644 --- a/scripts/portal/s4berserk.js +++ b/scripts/portal/s4berserk.js @@ -24,10 +24,12 @@ * @purpose Warps to the Junior Balrog map for the Rush Skill. */ function enter(pi) { - if(pi.isQuestStarted(6153)) { - if(pi.getWarpMap(910500200).countPlayers() == 0) { + if(pi.isQuestStarted(6153) && pi.haveItem(4031475)) { + var mapobj = pi.getWarpMap(910500200); + if(mapobj.countPlayers() == 0) { pi.resetMapObjects(910500200); - pi.playPortalSound(); pi.warp(910500200, 0); + mapobj.shuffleReactors(); + pi.playPortalSound(); pi.warp(910500200, "out01"); return true; } else { diff --git a/scripts/portal/s4berserk_move.js b/scripts/portal/s4berserk_move.js index cf3ee03e60..4c4830eab0 100644 --- a/scripts/portal/s4berserk_move.js +++ b/scripts/portal/s4berserk_move.js @@ -21,7 +21,7 @@ */ function enter(pi) { if (pi.getPlayer().getMap().countMonsters() == 0) { - pi.playPortalSound(); pi.warp(910500200, "out01"); + pi.playPortalSound(); pi.warp(910500200, "out00"); return true; } pi.getPlayer().dropMessage(5, "You must defeat all the monsters first."); diff --git a/scripts/portal/s4common1_exit.js b/scripts/portal/s4common1_exit.js index cfa7768d7b..4c2fc29580 100644 --- a/scripts/portal/s4common1_exit.js +++ b/scripts/portal/s4common1_exit.js @@ -1,9 +1,11 @@ //Author: Ronan function enter(pi) { - if(pi.hasItem(4031495)) pi.warp(921100301); - else pi.playPortalSound(); pi.warp(211040100); + if(pi.hasItem(4031495)) { + pi.playPortalSound(); pi.warp(921100301); + } else { + pi.playPortalSound(); pi.warp(211040100); + } - pi.getWarpMap(921100300).clearMapObjects(); return true; } \ No newline at end of file diff --git a/scripts/portal/s4tornado_enter.js b/scripts/portal/s4tornado_enter.js index 3f089ba6d6..5767ec5cf5 100644 --- a/scripts/portal/s4tornado_enter.js +++ b/scripts/portal/s4tornado_enter.js @@ -24,7 +24,7 @@ * @purpose Warps to the Junior Balrog map for the Rush Skill. */ function enter(pi) { - if(pi.isQuestStarted(6230) || pi.haveItem(4001110)) { + if(pi.isQuestStarted(6230) || pi.isQuestStarted(6231) || pi.haveItem(4001110)) { if(pi.getWarpMap(922020200).countPlayers() == 0) { pi.resetMapObjects(922020200); pi.playPortalSound(); pi.warp(922020200, 0); @@ -36,6 +36,5 @@ function enter(pi) { } } - pi.getPlayer().message("A mysterious force won't let you in."); return false; } \ No newline at end of file diff --git a/scripts/quest/20101.js b/scripts/quest/20101.js index a5e933e90f..895361f630 100644 --- a/scripts/quest/20101.js +++ b/scripts/quest/20101.js @@ -5,6 +5,7 @@ importPackage(Packages.client); var status = -1; +var jobType = 1; function end(mode, type, selection) { if (mode == 0) { @@ -20,6 +21,12 @@ function end(mode, type, selection) { if (status == 0) { qm.sendYesNo("Have you made your decision? The decision will be final, so think carefully before deciding what to do. Are you sure you want to become a Dawn Warrior?"); } else if (status == 1) { + if(!qm.canGetFirstJob(jobType)) { + cm.sendOk("Train a bit more and I can show you the way of the #rDawn Warrior#k."); + cm.dispose(); + return; + } + qm.sendNext("I have just molded your body to make it perfect for a Dawn Warrior. If you wish to become more powerful, use Stat Window (S) to raise the appropriate stats. If you aren't sure what to raise, just click on #bAuto#k."); if (qm.getPlayer().getJob().getId() != 1100) { qm.gainItem(1302077, 1); diff --git a/scripts/quest/20102.js b/scripts/quest/20102.js index b744f39d81..a32faea5d3 100644 --- a/scripts/quest/20102.js +++ b/scripts/quest/20102.js @@ -5,6 +5,7 @@ importPackage(Packages.client); var status = -1; +var jobType = 2; function end(mode, type, selection) { if (mode == 0) { @@ -20,6 +21,12 @@ function end(mode, type, selection) { if (status == 0) { qm.sendYesNo("Have you made your decision? The decision will be final, so think carefully before deciding what to do. Are you sure you want to become a Blaze Wizard?"); } else if (status == 1) { + if(!qm.canGetFirstJob(jobType)) { + cm.sendOk("Train a bit more and I can show you the way of the #rBlaze Wizard#k."); + cm.dispose(); + return; + } + qm.sendNext("I have just molded your body to make it perfect for a Blaze Wizard. If you wish to become more powerful, use Stat Window (S) to raise the appropriate stats. If you aren't sure what to raise, just click on #bAuto#k."); if (qm.getPlayer().getJob().getId() != 1200) { qm.gainItem(1372043, 1); diff --git a/scripts/quest/20103.js b/scripts/quest/20103.js index c50653c82e..223632f608 100644 --- a/scripts/quest/20103.js +++ b/scripts/quest/20103.js @@ -1,9 +1,11 @@ /* * Cygnus 1st Job advancement - Wind Archer */ + importPackage(Packages.client); var status = -1; +var jobType = 3; function end(mode, type, selection) { if (mode == 0) { @@ -19,6 +21,12 @@ function end(mode, type, selection) { if (status == 0) { qm.sendYesNo("Have you made your decision? The decision will be final, so think carefully before deciding what to do. Are you sure you want to become a Wind Archer?"); } else if (status == 1) { + if(!qm.canGetFirstJob(jobType)) { + cm.sendOk("Train a bit more and I can show you the way of the #rWind Archer#k."); + cm.dispose(); + return; + } + qm.sendNext("I have just molded your body to make it perfect for a Wind Archer. If you wish to become more powerful, use Stat Window (S) to raise the appropriate stats. If you aren't sure what to raise, just click on #bAuto#k."); if (qm.getPlayer().getJob().getId() != 1300) { qm.gainItem(2060000, 2000); diff --git a/scripts/quest/20104.js b/scripts/quest/20104.js index 004f4e4d6b..6e1ef777e5 100644 --- a/scripts/quest/20104.js +++ b/scripts/quest/20104.js @@ -1,9 +1,11 @@ /* * Cygnus 1st Job advancement - Night Walker */ + importPackage(Packages.client); var status = -1; +var jobType = 4; function end(mode, type, selection) { if (mode == 0) { @@ -19,6 +21,12 @@ function end(mode, type, selection) { if (status == 0) { qm.sendYesNo("Have you made your decision? The decision will be final, so think carefully before deciding what to do. Are you sure you want to become a Night Walker?"); } else if (status == 1) { + if(!qm.canGetFirstJob(jobType)) { + cm.sendOk("Train a bit more and I can show you the way of the #rNight Walker#k."); + cm.dispose(); + return; + } + qm.sendNext("I have just molded your body to make it perfect for a Night Walker. If you wish to become more powerful, use Stat Window (S) to raise the appropriate stats. If you aren't sure what to raise, just click on #bAuto#k."); if (qm.getPlayer().getJob().getId() != 1400) { qm.gainItem(1472061, 1); diff --git a/scripts/quest/20105.js b/scripts/quest/20105.js index b102c52d9d..517578e793 100644 --- a/scripts/quest/20105.js +++ b/scripts/quest/20105.js @@ -1,10 +1,11 @@ /* * Cygnus 1st Job advancement - Thunder Breaker */ + importPackage(Packages.client); - var status = -1; +var jobType = 5; function end(mode, type, selection) { if (mode == 0) { @@ -20,6 +21,12 @@ function end(mode, type, selection) { if (status == 0) { qm.sendYesNo("Have you made your decision? The decision will be final, so think carefully before deciding what to do. Are you sure you want to become a Thunder Breaker?"); } else if (status == 1) { + if(!qm.canGetFirstJob(jobType)) { + cm.sendOk("Train a bit more and I can show you the way of the #rThunder Breaker#k."); + cm.dispose(); + return; + } + qm.sendNext("I have just molded your body to make it perfect for a Thunder Breaker. If you wish to become more powerful, use Stat Window (S) to raise the appropriate stats. If you aren't sure what to raise, just click on #bAuto#k."); if (qm.getPlayer().getJob().getId() != 1500) { qm.gainItem(1482014, 1); diff --git a/scripts/quest/29002.js b/scripts/quest/29002.js deleted file mode 100644 index 4f5339f9c2..0000000000 --- a/scripts/quest/29002.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation version 3 as published by - the Free Software Foundation. You may not use, modify or distribute - this program under any other version of the GNU Affero General Public - License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ -/* - Author : Generic (http://cronusemu.net) - NPC Name: Dalair - Map(s): Every town - Description: Quest - Title Challenge - Celebrity! - Quest ID : 29002 -*/ -var status = -1; - -function start(mode, type, selection) { - if (mode == -1) { - qm.sendNext("Come back when you're ready."); - qm.dispose(); - } else { - if (mode > 0) - status++; - else - status--; - if (status == 0) {// Picture of Celebrity Medal(+blue text "Celebrity Medal" - qm.sendAcceptDecline("#v1142003# #e#b#t1142003##k \r\n- Time Limit 30 Days \r\n- Popularity 1000Increase \r\n#nDo you want to test your skills to see if you're worthy of this title?"); - } else if (status == 1) { - qm.sendNext("I'll give you 30 days to reach your goal. Once you're finished, come back and see me. Remember that you have to come back and see me within the time limit in order for it to be approved. Also, unless you complete this challenge or quit first, you can't try out for another title."); - qm.forceStartQuest(); - } - - } - -} \ No newline at end of file diff --git a/scripts/quest/29400.js b/scripts/quest/29400.js deleted file mode 100644 index cffc6f23d5..0000000000 --- a/scripts/quest/29400.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation version 3 as published by - the Free Software Foundation. You may not use, modify or distribute - this program under any other version of the GNU Affero General Public - License. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ -/* - Author : kevintjuh93 - Description: Quest - Veteran Hunter - Quest ID : 29400 -*/ - -var status = -1; - -function start(mode, type, selection) { - status++; - if (mode != 1) { - qm.sendNext("Come back when you're ready."); - - qm.dispose(); - return; - } - if (status == 0) - qm.sendAcceptDecline("#v1142004# #e#b#t1142004##k\r\n\r - Time Limit 30 Days\r - Hunt 100,000 Monsters\r #n *Only monsters that are at your level or higher are approved.\r\nDo you want to test your skills to see if you're worthy of this title?"); - else if (status == 1) { - qm.sendNext("Current Ranking \r\n1. #bMoople#k : #r538,673#k monsters\r\n2. #bZeroQuanta#k : #r111,421#k monsters\r\nDon't forget that the record resets at the beginning of each month.");//TODO - } else if (status == 2) { - qm.sendNextPrev("I'll give you 30 days to reach your hunting goal. Once you are finished, come back and see me. Remember, you have to come back and see me within the time limit in order to be approved. Also, you are prohibited from trying out for another title unless you first complete or forfeit this challenge."); - } else if (status == 3) { - qm.forceStartQuest(); - qm.dispose(); - } -} - - -function end(mode, type, selection) { - status++; - if (mode != 1) { - if(type == 1 && mode == 0) return; - else{ - qm.dispose(); - return; - } - if (status == 0) { - qm.sendOk("Not coded yet."); - qm.dispose(); - } - } -} \ No newline at end of file diff --git a/scripts/quest/29927.js b/scripts/quest/29927.js new file mode 100644 index 0000000000..e65f2df439 --- /dev/null +++ b/scripts/quest/29927.js @@ -0,0 +1,20 @@ + +var status = -1; + +function start(mode, type, selection) { + if (qm.canHold(1142132) && !qm.haveItem(1142132,1) && qm.getPlayer().getLevel() >= 120 && ((qm.getPlayer().getJob() / 100) | 0) == 21) { + qm.gainItem(1142132,1); + qm.forceStartQuest(); + qm.forceCompleteQuest(); + } + qm.dispose(); +} + +function end(mode, type, selection) { + if (qm.canHold(1142132) && !qm.haveItem(1142132,1) && qm.getPlayer().getLevel() >= 120 && ((qm.getPlayer().getJob() / 100) | 0) == 21) { + qm.gainItem(1142132,1); + qm.forceStartQuest(); + qm.forceCompleteQuest(); + } + qm.dispose(); +} \ No newline at end of file diff --git a/scripts/quest/4659.js b/scripts/quest/4659.js index be734ed60a..3a9f87d9d3 100644 --- a/scripts/quest/4659.js +++ b/scripts/quest/4659.js @@ -24,7 +24,7 @@ Map(s): New Leaf City Description: Quest - Pet Evolution */ -importPackage(Packages.client); +importPackage(Packages.client.inventory.manipulator); importPackage(Packages.server); var status = -1; diff --git a/scripts/quest/6033.js b/scripts/quest/6033.js index 090401b065..b054b7cd06 100644 --- a/scripts/quest/6033.js +++ b/scripts/quest/6033.js @@ -43,18 +43,17 @@ function end(mode, type, selection) { } else if (status == 1) { if(qm.getQuestProgress(6033) == 1 && qm.haveItem(4260003, 1)) { qm.sendNext("You indeed have crafted a fine piece of Monster Crystal, I see. You passed! Now, I shall teach you the next steps of the Maker skill. Keep the monster crystal with you as well, it's your work."); - - var skillid = Math.floor(qm.getPlayer().getJob().getId() / 1000) * 10000000 + 1007; - qm.teachSkill(skillid, 2, 3, -1); - - qm.gainExp(230000 * qm.getPlayer().getExpRate()); - qm.forceCompleteQuest(); - - } else { qm.sendNext("Hey, what's wrong? I did tell you to make a monster crystal to pass my test, didn't I? Buying one or crafting before the start of the test is NOT part of the deal. Go craft me an #b#t4260003##k."); + qm.dispose(); + return; } - + } else { + var skillid = Math.floor(qm.getPlayer().getJob().getId() / 1000) * 10000000 + 1007; + qm.teachSkill(skillid, 2, 3, -1); + + qm.gainExp(230000 * qm.getPlayer().getExpRate()); + qm.forceCompleteQuest(); qm.dispose(); } } diff --git a/scripts/quest/6036.js b/scripts/quest/6036.js index b1bab49ef2..12d485ffc7 100644 --- a/scripts/quest/6036.js +++ b/scripts/quest/6036.js @@ -43,16 +43,19 @@ function end(mode, type, selection) { } else if (status == 1) { if(qm.haveItem(4031980, 1)) { qm.sendNext("You crafted a #b#t4031980##k?! How comes, how did you do it?? ... Well, that can't be helped, I guess. The student surpassed the teacher! Youth sure do wonders to one's perception capabilities.\r\n\r\nYou are now ready to take the last step on mastering the Maker skill, contemplate it at it's finest form!"); - qm.gainItem(4031980, -1); - - var skillid = Math.floor(qm.getPlayer().getJob().getId() / 1000) * 10000000 + 1007; - qm.teachSkill(skillid, 3, 3, -1); - - qm.gainExp(300000 * qm.getPlayer().getExpRate()); - qm.forceCompleteQuest(); } else { qm.sendNext("... Please step aside, I can't finish this work if I'm being distracted at every moment."); + qm.dispose(); + return; } + } else { + qm.gainItem(4031980, -1); + + var skillid = Math.floor(qm.getPlayer().getJob().getId() / 1000) * 10000000 + 1007; + qm.teachSkill(skillid, 3, 3, -1); + + qm.gainExp(300000 * qm.getPlayer().getExpRate()); + qm.forceCompleteQuest(); qm.dispose(); } diff --git a/scripts/quest/medalQuest.js b/scripts/quest/medalQuest.js index 9c696935ee..140736e720 100644 --- a/scripts/quest/medalQuest.js +++ b/scripts/quest/medalQuest.js @@ -8,16 +8,16 @@ function start(mode, type, selection) { qm.forceCompleteQuest(); var medalname = qm.getMedalName(); - qm.message("<" + medalname + "> has been awarded."); + qm.message("<" + medalname + "> is not coded."); qm.earnTitle("<" + medalname + "> has been awarded."); qm.dispose(); } -function complete(mode, type, selection) { +function end(mode, type, selection) { qm.forceCompleteQuest(); var medalname = qm.getMedalName(); - qm.message("<" + medalname + "> has been awarded."); + qm.message("<" + medalname + "> is not coded."); qm.earnTitle("<" + medalname + "> has been awarded."); qm.dispose(); } \ No newline at end of file diff --git a/scripts/reactor/6802000.js b/scripts/reactor/6802000.js index bbe8dce9bb..2497b8d049 100644 --- a/scripts/reactor/6802000.js +++ b/scripts/reactor/6802000.js @@ -20,9 +20,5 @@ along with this program. If not, see . */ function act(){ - for (var i = 0; i < 3; i++) { - rm.spawnMonster(9400506); - rm.spawnMonster(9400507); - } - rm.spawnMonster(9400507); + rm.sprayItems(true, 1, 100, 400, 15); } \ No newline at end of file diff --git a/scripts/reactor/6802001.js b/scripts/reactor/6802001.js index 0424252522..2497b8d049 100644 --- a/scripts/reactor/6802001.js +++ b/scripts/reactor/6802001.js @@ -20,8 +20,5 @@ along with this program. If not, see . */ function act(){ - for (var i = 0; i < 5; i++) - rm.spawnMonster(9400506); - for (var j = 0; j < 7; j++) - rm.spawnMonster(9400507); + rm.sprayItems(true, 1, 100, 400, 15); } \ No newline at end of file diff --git a/sql/db_database.sql b/sql/db_database.sql index a20f960dab..b2205f75a2 100644 --- a/sql/db_database.sql +++ b/sql/db_database.sql @@ -177,6 +177,7 @@ CREATE TABLE IF NOT EXISTS `characters` ( `vanquisherKills` int(11) unsigned NOT NULL DEFAULT '0', `summonValue` int(11) unsigned NOT NULL DEFAULT '0', `partnerId` int(11) NOT NULL DEFAULT '0', + `marriageItemId` int(11) NOT NULL DEFAULT '0', `reborns` int(5) NOT NULL DEFAULT '0', `PQPoints` int(11) NOT NULL DEFAULT '0', `dataString` varchar(64) NOT NULL DEFAULT '', @@ -11628,7 +11629,6 @@ INSERT IGNORE INTO `temp_data` (`id`, `dropperid`, `itemid`, `minimum_quantity`, (11627, 5120500, 1472055, 1, 1, 0, 750), (11626, 5120000, 1472055, 1, 1, 0, 750), (11625, 9001006, 4031856, 1, 1, 2191, 400000), -(11624, 8190003, 4031461, 1, 1, 6169, 1000), (11623, 9400218, 4001106, 25, 50, 0, 999999), (11622, 9400217, 4001106, 1, 3, 0, 999999), (11613, 2110200, 4032390, 1, 1, 2248, 100000), @@ -12819,6 +12819,7 @@ CREATE TABLE IF NOT EXISTS `dueyitems` ( `hands` int(11) DEFAULT '0', `speed` int(11) DEFAULT '0', `jump` int(11) DEFAULT '0', + `flag` int(11) DEFAULT '0', `owner` varchar(13) DEFAULT NULL, PRIMARY KEY (`id`), KEY `PackageId` (`PackageId`) @@ -12826,7 +12827,7 @@ CREATE TABLE IF NOT EXISTS `dueyitems` ( CREATE TABLE IF NOT EXISTS `dueypackages` ( `PackageId` int(10) unsigned NOT NULL AUTO_INCREMENT, - `RecieverId` int(10) unsigned NOT NULL, + `ReceiverId` int(10) unsigned NOT NULL, `SenderName` varchar(13) NOT NULL, `Mesos` int(10) unsigned DEFAULT '0', `TimeStamp` varchar(10) NOT NULL, @@ -16508,6 +16509,7 @@ CREATE TABLE IF NOT EXISTS `playernpcs_field` ( `world` int(11) NOT NULL, `map` int(11) NOT NULL, `step` tinyint(1) NOT NULL DEFAULT '0', + `podium` smallint(8) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; diff --git a/sql/db_drops.sql b/sql/db_drops.sql index ad4f8bcc27..76915e3ac7 100644 --- a/sql/db_drops.sql +++ b/sql/db_drops.sql @@ -20281,7 +20281,13 @@ USE `heavenms`; (4300007, 4032521, 1, 1, 2291, 120000), (4300008, 4032521, 1, 1, 2291, 120000), (4300009, 4032521, 1, 1, 2291, 120000), -(4300010, 4032521, 1, 1, 2291, 120000); +(4300010, 4032521, 1, 1, 2291, 120000), +(9400506, 2020020, 1, 1, 0, 100000), +(9400506, 4031217, 1, 1, 0, 40000), +(9400507, 2020020, 1, 1, 0, 100000), +(9400507, 4031217, 1, 1, 0, 40000), +(9300085, 4031466, 1, 1, 6107, 40000), +(8170000, 4031448, 1, 1, 6134, 40000); # (dropperid, itemid, minqty, maxqty, questid, chance) @@ -21548,6 +21554,81 @@ USE `heavenms`; UPDATE reactordrops SET questid=1008 WHERE itemid=4031161; UPDATE reactordrops SET questid=1008 WHERE itemid=4031162; + # add Amoria Wedding reward boxes + INSERT INTO `reactordrops` (`reactorid`, `itemid`, `chance`, `questid`) VALUES +(6802000, 4031423, 5, -1), +(6802000, 4031424, 10, -1), +(6802000, 1442047, 10, -1), +(6802000, 1442049, 17, -1), +(6802000, 1302071, 20, -1), +(6802000, 1312034, 20, -1), +(6802000, 1322056, 20, -1), +(6802000, 1332059, 20, -1), +(6802000, 1382042, 20, -1), +(6802000, 1402041, 20, -1), +(6802000, 1412029, 20, -1), +(6802000, 1422033, 20, -1), +(6802000, 2022180, 3, -1), +(6802000, 2022181, 3, -1), +(6802000, 2000005, 3, -1), +(6802000, 2022181, 3, -1), +(6802000, 2020013, 3, -1), +(6802000, 2022015, 3, -1), +(6802000, 2020010, 3, -1), +(6802000, 2022130, 8, -1), +(6802000, 2022196, 3, -1), +(6802000, 2022197, 5, -1), +(6802000, 2022200, 8, -1), +(6802000, 2020020, 5, -1), +(6802000, 2022190, 5, -1), +(6802000, 2001001, 4, -1), +(6802000, 2001002, 4, -1), +(6802000, 4010004, 4, -1), +(6802000, 4010005, 4, -1), +(6802000, 4010006, 6, -1), +(6802000, 4020004, 4, -1), +(6802000, 4020005, 4, -1), +(6802000, 4020007, 6, -1), +(6802000, 2040804, 10, -1), +(6802000, 2040815, 10, -1), +(6802000, 2040908, 10, -1), +(6802000, 2040931, 10, -1), +(6802001, 1442048, 14, -1), +(6802001, 1442050, 20, -1), +(6802001, 1432042, 20, -1), +(6802001, 1442053, 20, -1), +(6802001, 1452048, 20, -1), +(6802001, 1462043, 20, -1), +(6802001, 1472058, 20, -1), +(6802001, 1482025, 20, -1), +(6802001, 1492026, 20, -1), +(6802001, 2022180, 3, -1), +(6802001, 2022181, 3, -1), +(6802001, 2000005, 3, -1), +(6802001, 2022181, 3, -1), +(6802001, 2020013, 3, -1), +(6802001, 2022015, 3, -1), +(6802001, 2020010, 3, -1), +(6802001, 2022130, 8, -1), +(6802001, 2022196, 3, -1), +(6802001, 2022197, 5, -1), +(6802001, 2022200, 8, -1), +(6802001, 2020020, 5, -1), +(6802001, 2022190, 5, -1), +(6802001, 2001001, 4, -1), +(6802001, 2001002, 4, -1), +(6802001, 4010004, 4, -1), +(6802001, 4010005, 4, -1), +(6802001, 4010006, 6, -1), +(6802001, 4020004, 4, -1), +(6802001, 4020005, 4, -1), +(6802001, 4020007, 6, -1), +(6802001, 2041010, 10, -1), +(6802001, 2041028, 10, -1), +(6802001, 2041023, 10, -1), +(6802001, 2048012, 10, -1), +(6802001, 2040502, 10, -1); + # update Amoria PQ reward boxes DELETE FROM `reactordrops` WHERE `reactorid` >= 6702003 AND `reactorid` <= 6702012; INSERT INTO `reactordrops` (`reactorid`, `itemid`, `chance`, `questid`) VALUES diff --git a/src/client/MapleCharacter.java b/src/client/MapleCharacter.java index b072dcd8fc..3479c2e195 100644 --- a/src/client/MapleCharacter.java +++ b/src/client/MapleCharacter.java @@ -29,8 +29,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; -import java.text.DecimalFormat; -import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -55,7 +53,6 @@ import java.util.regex.Pattern; import net.server.PlayerBuffValueHolder; import net.server.PlayerCoolDownValueHolder; import net.server.Server; -import net.server.channel.Channel; import net.server.guild.MapleAlliance; import net.server.guild.MapleGuild; import net.server.guild.MapleGuildCharacter; @@ -67,7 +64,7 @@ import net.server.world.PartyOperation; import net.server.world.World; import scripting.event.EventInstanceManager; import server.CashShop; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; import server.MaplePortal; import server.MapleShop; @@ -107,7 +104,9 @@ import tools.MaplePacketCreator; import tools.Pair; import tools.Randomizer; import tools.locks.MonitoredReentrantLock; +import tools.packets.Wedding; import client.autoban.AutobanManager; +import client.creator.CharacterFactoryRecipe; import client.inventory.Equip; import client.inventory.Item; import client.inventory.ItemFactory; @@ -159,7 +158,6 @@ import server.maps.MapleMapItem; import tools.locks.MonitoredLockType; public class MapleCharacter extends AbstractAnimatedMapleMapObject { - private static NumberFormat nf = new DecimalFormat("#,###,###,###"); private static MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); private static final String LEVEL_200 = "[Congrats] %s has reached Level 200! Congratulate %s on such an amazing achievement!"; private static final String[] BLOCKED_NAMES = {"admin", "owner", "moderator", "intern", "donor", "administrator", "help", "helper", "alert", "notice", "maplestory", "fuck", "wizet", "fucking", "negro", "fuk", "fuc", "penis", "pussy", "asshole", "gay", @@ -200,12 +198,13 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { private int expRate = 1, mesoRate = 1, dropRate = 1, expCoupon = 1, mesoCoupon = 1, dropCoupon = 1; private int omokwins, omokties, omoklosses, matchcardwins, matchcardties, matchcardlosses; private int owlSearch; - private long lastfametime, lastUsedCashItem, lastHealed, lastMesoDrop = -1, jailExpiration = -1; + private long lastfametime, lastUsedCashItem, lastHealed, lastBuyback, lastDeathtime, lastMesoDrop = -1, jailExpiration = -1; private transient int localmaxhp, localmaxmp, localstr, localdex, localluk, localint_, magic, watk; private boolean hidden, canDoor = true, berserk, hasMerchant, whiteChat = false; private int linkedLevel = 0; private String linkedName = null; private boolean finishedDojoTutorial; + private boolean usedStorage = false; private String name; private String chalktext; private String dataString; @@ -237,7 +236,6 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { private MapleStorage storage = null; private MapleTrade trade = null; private MonsterBook monsterbook; - private MapleRing marriageRing; private CashShop cashshop; private Set newyears = new LinkedHashSet<>(); private SavedLocation savedLocations[]; @@ -280,8 +278,6 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { private Lock prtLock = new MonitoredReentrantLock(MonitoredLockType.PRT); private Map> excluded = new LinkedHashMap<>(); private Set excludedItems = new LinkedHashSet<>(); - private List crushRings = new ArrayList<>(); - private List friendshipRings = new ArrayList<>(); private static String[] ariantroomleader = new String[3]; private static int[] ariantroomslot = new int[3]; private long portaldelay = 0, lastcombo = 0; @@ -296,8 +292,13 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { private List viptrockmaps = new ArrayList<>(); private Map events = new LinkedHashMap<>(); private PartyQuest partyQuest = null; - private boolean loggedIn = false; private MapleDragon dragon = null; + private MapleRing marriageRing; + private int marriageItemid = -1; + private int partnerId = -1; + private List crushRings = new ArrayList<>(); + private List friendshipRings = new ArrayList<>(); + private boolean loggedIn = false; private boolean useCS; //chaos scroll upon crafting item. private long npcCd; private long petLootCd; @@ -503,12 +504,54 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { return ring; } } - if (getMarriageRing().getRingId() == id) { - return getMarriageRing(); + + if(marriageRing != null) { + if (marriageRing.getRingId() == id) { + return marriageRing; + } } return null; } + + public int getMarriageItemId() { + return marriageItemid; + } + + public void setMarriageItemId(int itemid) { + marriageItemid = itemid; + } + + public int getPartnerId() { + return partnerId; + } + + public void setPartnerId(int partnerid) { + partnerId = partnerid; + } + + public int getRelationshipId() { + return client.getWorldServer().getRelationshipId(id); + } + + public boolean isMarried() { + return marriageRing != null && partnerId > 0; + } + + public boolean hasJustMarried() { + EventInstanceManager eim = getEventInstance(); + if(eim != null) { + String prop = eim.getProperty("groomId"); + + if(prop != null) { + if((Integer.parseInt(prop) == id || eim.getIntProperty("brideId") == id) && (mapid == 680000110 || mapid == 680000210)) { + return true; + } + } + } + + return false; + } public int addDojoPointsByMap(int mapid) { int pts = 0; @@ -547,6 +590,10 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { public void addFriendshipRing(MapleRing r) { friendshipRings.add(r); } + + public void addMarriageRing(MapleRing r) { + marriageRing = r; + } public void addHP(int delta) { setHp(hp + delta); @@ -877,7 +924,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } public void toggleHide(boolean login) { - Hide(!isHidden()); + Hide(!hidden); } public void cancelMagicDoor() { @@ -1012,6 +1059,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { if (newJob == null) { return;//the fuck you doing idiot! } + this.job = newJob; remainingSp[GameConstants.getSkillBook(newJob.getId())] += 1; if (GameConstants.hasSPTable(newJob)) { @@ -1388,7 +1436,16 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { petLock.unlock(); } } - + + public void notifyMapTransferToPartner(int mapid) { + if(partnerId > 0) { + final MapleCharacter partner = client.getWorldServer().getPlayerStorage().getCharacterById(partnerId); + if(partner != null && !partner.isAwayFromWorld()) { + partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(id, mapid)); + } + } + } + private void changeMapInternal(final MapleMap to, final Point pos, final byte[] warpPacket) { if(!canWarpMap) return; @@ -1425,6 +1482,8 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { FilePrinter.printError(FilePrinter.MAPLE_MAP, "Character " + this.getName() + " got stuck when moving to map " + map.getId() + "."); } + notifyMapTransferToPartner(map.getId()); + //alas, new map has been specified when a warping was being processed... if(newWarpMap != -1) { canWarpMap = true; @@ -1532,10 +1591,6 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } } - public void clearSavedLocation(SavedLocationType type) { - savedLocations[type.ordinal()] = null; - } - public void controlMonster(MapleMonster monster, boolean aggro) { monster.setController(this); controlled.add(monster); @@ -2305,10 +2360,6 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { client.announce(MaplePacketCreator.serverNotice(type, message)); } - public String emblemCost() { - return nf.format(MapleGuild.CHANGE_EMBLEM_COST); - } - private void enforceMaxHpMp() { List> stats = new ArrayList<>(2); if (getMp() > getCurrentMaxMp()) { @@ -2565,7 +2616,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { public void gainExp(int gain, boolean show, boolean inChat, boolean white) { gainExp(gain, 0, show, inChat, white); } - + public void gainExp(int gain, int party, boolean show, boolean inChat, boolean white) { if (hasDisease(MapleDisease.CURSE)) { gain *= 0.5; @@ -2574,9 +2625,9 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { if(gain < 0) gain = Integer.MAX_VALUE; // integer overflow, heh. if(party < 0) party = Integer.MAX_VALUE; // integer overflow, heh. - int equip = (int)Math.min((long)((gain / 10) * pendantExp), Integer.MAX_VALUE); + int equip = (int) Math.min((long)(gain / 10) * pendantExp, Integer.MAX_VALUE); - long total = (long)gain + equip + party; + long total = (long) gain + equip + party; gainExpInternal(total, equip, party, show, inChat, white); } @@ -2595,11 +2646,11 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { long leftover = 0; long nextExp = exp.get() + total; - if (nextExp > (long)Integer.MAX_VALUE) { + if (nextExp > (long) Integer.MAX_VALUE) { total = Integer.MAX_VALUE - exp.get(); leftover = nextExp - Integer.MAX_VALUE; } - updateSingleStat(MapleStat.EXP, exp.addAndGet((int)total)); + updateSingleStat(MapleStat.EXP, exp.addAndGet((int) total)); if (show && gain != 0) { client.announce(MaplePacketCreator.getShowExpGain((int)Math.min(gain, Integer.MAX_VALUE), equip, party, inChat, white)); } @@ -3860,7 +3911,11 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { public boolean getFinishedDojoTutorial() { return finishedDojoTutorial; } - + + public void setUsedStorage() { + usedStorage = true; + } + public List getFriendshipRings() { Collections.sort(friendshipRings); return friendshipRings; @@ -4100,7 +4155,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } public MapleRing getMarriageRing() { - return marriageRing; + return partnerId > 0 ? marriageRing : null; } public int getMasterLevel(Skill skill) { @@ -4611,15 +4666,22 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { return remainingSp; } - public int getSavedLocation(String type) { + private void clearSavedLocation(SavedLocationType type) { + savedLocations[type.ordinal()] = null; + } + + public int peekSavedLocation(String type) { SavedLocation sl = savedLocations[SavedLocationType.fromString(type).ordinal()]; if (sl == null) { return -1; } - int m = sl.getMapId(); - if (!SavedLocationType.fromString(type).equals(SavedLocationType.WORLDTOUR)) { - clearSavedLocation(SavedLocationType.fromString(type)); - } + return sl.getMapId(); + } + + public int getSavedLocation(String type) { + int m = peekSavedLocation(type); + clearSavedLocation(SavedLocationType.fromString(type)); + return m; } @@ -4801,10 +4863,6 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { return gmLevel; } - public String guildCost() { - return nf.format(MapleGuild.CREATE_GUILD_COST); - } - private void guildUpdate() { mgc.setLevel(level); mgc.setJobId(job.getId()); @@ -4959,6 +5017,50 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { public boolean isAlive() { return hp > 0; } + + private boolean canBuyback(int fee, boolean usingMesos) { + return (usingMesos ? this.getMeso() : cashshop.getCash(1)) >= fee; + } + + private void applyBuybackFee(int fee, boolean usingMesos) { + if(usingMesos) { + this.gainMeso(-fee); + } else { + cashshop.gainCash(1, -fee); + } + } + + public boolean couldBuyback() { // Ronan's buyback system + long timeNow = System.currentTimeMillis(); + + if(timeNow - lastDeathtime > ServerConstants.BUYBACK_RETURN_MINUTES * 60 * 1000) { + this.dropMessage(5, "You are unable to buyback now since the time available to decide has expired."); + return false; + } + + long nextBuybacktime = lastBuyback + ServerConstants.BUYBACK_COOLDOWN_MINUTES * 60 * 1000; + if(timeNow < nextBuybacktime) { + long timeLeft = nextBuybacktime - timeNow; + int seconds = (int) Math.floor(timeLeft / 1000) % 60; + int minutes = (int) Math.floor(timeLeft / (1000*60)) % 60; + + this.dropMessage(5, "Next buyback available in " + (minutes > 0 ? (String.format("%02d", minutes) + " minutes, ") : "") + String.format("%02d", seconds) + " seconds."); + return false; + } + + boolean usingMesos = ServerConstants.USE_BUYBACK_WITH_MESOS; + int fee = ServerConstants.BUYBACK_FEE; + if(usingMesos) fee *= ServerConstants.BUYBACK_MESO_MULTIPLIER; + + if(!canBuyback(fee, usingMesos)) { + this.dropMessage(5, "You don't have " + fee + " " + (usingMesos ? "mesos" : "NX") + " to buyback."); + return false; + } + + lastBuyback = timeNow; + applyBuybackFee(fee, usingMesos); + return true; + } public boolean isBuffFrom(MapleBuffStat stat, Skill skill) { effLock.lock(); @@ -5032,9 +5134,10 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { int improvingMaxHPLevel = 0; int improvingMaxMPLevel = 0; - if (isBeginnerJob() && getLevel() < 11) { + boolean isBeginner = isBeginnerJob(); + if (ServerConstants.USE_AUTOASSIGN_STARTERS_AP && isBeginner && level < 11) { remainingAp = 0; - if (getLevel() < 6) { + if (level < 6) { str += 5; } else { str += 4; @@ -5042,11 +5145,12 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } } else { remainingAp += 5; - if (isCygnus() && level < 70) { + if (isCygnus() && level > 10 && level < 70) { remainingAp++; } } - if (job == MapleJob.BEGINNER || job == MapleJob.NOBLESSE || job == MapleJob.LEGEND) { + + if (isBeginner) { maxhp += Randomizer.rand(12, 16); maxmp += Randomizer.rand(10, 12); } else if (job.isA(MapleJob.WARRIOR) || job.isA(MapleJob.DAWNWARRIOR1)) { @@ -5464,6 +5568,17 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } } + public void addPlayerRing(MapleRing ring) { + int ringItemId = ring.getItemId(); + if (ItemConstants.isWeddingRing(ringItemId)) { + this.addMarriageRing(ring); + } else if (ring.getItemId() > 1112012) { + this.addFriendshipRing(ring); + } else { + this.addCrushRing(ring); + } + } + public static MapleCharacter loadCharFromDB(int charid, MapleClient client, boolean channelserver) throws SQLException { try { MapleCharacter ret = new MapleCharacter(); @@ -5565,15 +5680,23 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { if (item.getRight().equals(MapleInventoryType.EQUIPPED)) { ring.equip(); } - if (ring.getItemId() > 1112012) { - ret.addFriendshipRing(ring); - } else { - ret.addCrushRing(ring); - } + + ret.addPlayerRing(ring); } } } + World wserv = Server.getInstance().getWorld(ret.world); + + ret.partnerId = rs.getInt("partnerId"); + ret.marriageItemid = rs.getInt("marriageItemId"); + if(ret.marriageItemid > 0 && ret.partnerId <= 0) { + ret.marriageItemid = -1; + } else if(ret.partnerId > 0 && wserv.getRelationshipId(ret.id) <= 0) { + ret.marriageItemid = -1; + ret.partnerId = -1; + } + NewYearCardRecord.loadPlayerNewYearCards(ret); PreparedStatement ps2, ps3; @@ -5617,7 +5740,6 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } ret.setPosition(portal.getPosition()); int partyid = rs.getInt("party"); - World wserv = Server.getInstance().getWorld(ret.world); MapleParty party = wserv.getParty(partyid); if (party != null) { ret.mpc = party.getMemberById(ret.id); @@ -5693,7 +5815,6 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { ps.close(); ret.cashshop = new CashShop(ret.accountid, ret.id, ret.getJobType()); ret.autoban = new AutobanManager(ret); - ret.marriageRing = null; //for now ps = con.prepareStatement("SELECT name, level FROM characters WHERE accountid = ? AND id != ? ORDER BY level DESC limit 1"); ps.setInt(1, ret.accountid); ps.setInt(2, charid); @@ -5708,41 +5829,56 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { ps = con.prepareStatement("SELECT * FROM queststatus WHERE characterid = ?"); ps.setInt(1, charid); rs = ps.executeQuery(); - PreparedStatement psf; - try (PreparedStatement pse = con.prepareStatement("SELECT * FROM questprogress WHERE queststatusid = ?")) { - psf = con.prepareStatement("SELECT mapid FROM medalmaps WHERE queststatusid = ?"); - while (rs.next()) { - MapleQuest q = MapleQuest.getInstance(rs.getShort("quest")); - MapleQuestStatus status = new MapleQuestStatus(q, MapleQuestStatus.Status.getById(rs.getInt("status"))); - long cTime = rs.getLong("time"); - if (cTime > -1) { - status.setCompletionTime(cTime * 1000); - } - - long eTime = rs.getLong("expires"); - if (eTime > 0) { - status.setExpirationTime(eTime); - } - - status.setForfeited(rs.getInt("forfeited")); - ret.quests.put(q.getId(), status); - pse.setInt(1, rs.getInt("queststatusid")); - try (ResultSet rsProgress = pse.executeQuery()) { - while (rsProgress.next()) { - status.setProgress(rsProgress.getInt("progressid"), rsProgress.getString("progress")); - } - } - psf.setInt(1, rs.getInt("queststatusid")); - try (ResultSet medalmaps = psf.executeQuery()) { - while (medalmaps.next()) { - status.addMedalMap(medalmaps.getInt("mapid")); - } + + Map loadedQuestStatus = new LinkedHashMap<>(); + + while (rs.next()) { + MapleQuest q = MapleQuest.getInstance(rs.getShort("quest")); + MapleQuestStatus status = new MapleQuestStatus(q, MapleQuestStatus.Status.getById(rs.getInt("status"))); + long cTime = rs.getLong("time"); + if (cTime > -1) { + status.setCompletionTime(cTime * 1000); + } + + long eTime = rs.getLong("expires"); + if (eTime > 0) { + status.setExpirationTime(eTime); + } + + status.setForfeited(rs.getInt("forfeited")); + ret.quests.put(q.getId(), status); + loadedQuestStatus.put(rs.getInt("queststatusid"), status); + } + rs.close(); + ps.close(); + + // opportunity for improvement on questprogress/medalmaps calls to DB + try (PreparedStatement pse = con.prepareStatement("SELECT * FROM questprogress WHERE characterid = ?")) { + pse.setInt(1, charid); + try (ResultSet rsProgress = pse.executeQuery()) { + while(rsProgress.next()) { + MapleQuestStatus status = loadedQuestStatus.get(rsProgress.getInt("queststatusid")); + status.setProgress(rsProgress.getInt("progressid"), rsProgress.getString("progress")); } } - rs.close(); - ps.close(); } - psf.close(); + + try (PreparedStatement pse = con.prepareStatement("SELECT * FROM medalmaps WHERE characterid = ?")) { + pse.setInt(1, charid); + try (ResultSet rsMedalMaps = pse.executeQuery()) { + while(rsMedalMaps.next()) { + MapleQuestStatus status = loadedQuestStatus.get(rsMedalMaps.getInt("queststatusid")); + status.addMedalMap(rsMedalMaps.getInt("mapid")); + } + } + } + + for(MapleQuestStatus mqs : loadedQuestStatus.values()) { + mqs.resetUpdated(); + } + + loadedQuestStatus.clear(); + ps = con.prepareStatement("SELECT skillid,skilllevel,masterlevel,expiration FROM skills WHERE characterid = ?"); ps.setInt(1, charid); rs = ps.executeQuery(); @@ -5927,6 +6063,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { private void playerDead() { cancelAllBuffs(false); dispelDebuffs(); + lastDeathtime = System.currentTimeMillis(); EventInstanceManager eim = getEventInstance(); if (eim != null) { @@ -6218,6 +6355,10 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } public void resetStats() { + if(!ServerConstants.USE_AUTOASSIGN_STARTERS_AP) { + return; + } + List> statup = new ArrayList<>(5); int tap = 0, tsp = 1; int tstr = 4, tdex = 4, tint = 4, tluk = 4; @@ -6336,8 +6477,33 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { MaplePortal closest = map.findClosestPortal(getPosition()); savedLocations[SavedLocationType.fromString(type).ordinal()] = new SavedLocation(getMapId(), closest != null ? closest.getId() : 0); } + + public final boolean insertNewChar(CharacterFactoryRecipe recipe) { + str = recipe.getStr(); + dex = recipe.getDex(); + int_ = recipe.getInt(); + luk = recipe.getLuk(); + maxhp = recipe.getMaxHp(); + maxmp = recipe.getMaxMp(); + hp = maxhp; + mp = maxmp; + level = recipe.getLevel(); + remainingAp = recipe.getRemainingAp(); + remainingSp[GameConstants.getSkillBook(job.getId())] = recipe.getRemainingSp(); + mapid = recipe.getMap(); + meso.set(recipe.getMeso()); - public final boolean insertNewChar() { + List> startingSkills = recipe.getStartingSkillLevel(); + for(Pair skEntry : startingSkills) { + Skill skill = skEntry.getLeft(); + this.changeSkillLevel(skill, skEntry.getRight().byteValue(), skill.getMaxLevel(), -1); + } + + List> itemsWithType = recipe.getStartingItems(); + for(Pair itEntry : itemsWithType) { + this.getInventory(itEntry.getRight()).addItem(itEntry.getLeft()); + } + Connection con = null; PreparedStatement ps = null; @@ -6346,11 +6512,11 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); con.setAutoCommit(false); - ps = con.prepareStatement("INSERT INTO characters (str, dex, luk, `int`, gm, skincolor, gender, job, hair, face, map, meso, spawnpoint, accountid, name, world) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); - ps.setInt(1, 12); - ps.setInt(2, 5); - ps.setInt(3, 4); - ps.setInt(4, 4); + ps = con.prepareStatement("INSERT INTO characters (str, dex, luk, `int`, gm, skincolor, gender, job, hair, face, map, meso, spawnpoint, accountid, name, world, hp, mp, maxhp, maxmp, level, ap, sp) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); + ps.setInt(1, str); + ps.setInt(2, dex); + ps.setInt(3, luk); + ps.setInt(4, int_); ps.setInt(5, gmLevel); ps.setInt(6, skinColor.getId()); ps.setInt(7, gender); @@ -6363,6 +6529,20 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { ps.setInt(14, accountid); ps.setString(15, name); ps.setInt(16, world); + ps.setInt(17, hp); + ps.setInt(18, mp); + ps.setInt(19, maxhp); + ps.setInt(20, maxmp); + ps.setInt(21, level); + ps.setInt(22, remainingAp); + + StringBuilder sps = new StringBuilder(); + for (int i = 0; i < remainingSp.length; i++) { + sps.append(remainingSp[i]); + sps.append(","); + } + String sp = sps.toString(); + ps.setString(23, sp.substring(0, sp.length() - 1)); int updateRows = ps.executeUpdate(); if (updateRows < 1) { @@ -6408,8 +6588,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } ps.close(); - final List> itemsWithType = new ArrayList<>(); - + itemsWithType = new ArrayList<>(); for (MapleInventory iv : inventory) { for (Item item : iv.list()) { itemsWithType.add(new Pair<>(item, iv.getType())); @@ -6417,6 +6596,21 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } ItemFactory.INVENTORY.saveItems(itemsWithType, id, con); + + if(!skills.isEmpty()) { + ps = con.prepareStatement("INSERT INTO skills (characterid, skillid, skilllevel, masterlevel, expiration) VALUES (?, ?, ?, ?, ?)"); + ps.setInt(1, id); + for (Entry skill : skills.entrySet()) { + ps.setInt(2, skill.getKey().getId()); + ps.setInt(3, skill.getValue().skillevel); + ps.setInt(4, skill.getValue().masterlevel); + ps.setLong(5, skill.getValue().expiration); + ps.addBatch(); + } + ps.executeBatch(); + ps.close(); + } + con.commit(); return true; } catch (Throwable t) { @@ -6457,6 +6651,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } } + //ItemFactory saveItems and monsterbook.saveCards are the most time consuming here. public synchronized void saveToDB(boolean notAutosave) { if(!loggedIn) return; @@ -6471,7 +6666,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { con.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); con.setAutoCommit(false); PreparedStatement ps; - ps = con.prepareStatement("UPDATE characters SET level = ?, fame = ?, str = ?, dex = ?, luk = ?, `int` = ?, exp = ?, gachaexp = ?, hp = ?, mp = ?, maxhp = ?, maxmp = ?, sp = ?, ap = ?, gm = ?, skincolor = ?, gender = ?, job = ?, hair = ?, face = ?, map = ?, meso = ?, hpMpUsed = ?, spawnpoint = ?, party = ?, buddyCapacity = ?, messengerid = ?, messengerposition = ?, mountlevel = ?, mountexp = ?, mounttiredness= ?, equipslots = ?, useslots = ?, setupslots = ?, etcslots = ?, monsterbookcover = ?, vanquisherStage = ?, dojoPoints = ?, lastDojoStage = ?, finishedDojoTutorial = ?, vanquisherKills = ?, matchcardwins = ?, matchcardlosses = ?, matchcardties = ?, omokwins = ?, omoklosses = ?, omokties = ?, dataString = ?, fquest = ?, jailexpire = ? WHERE id = ?", Statement.RETURN_GENERATED_KEYS); + ps = con.prepareStatement("UPDATE characters SET level = ?, fame = ?, str = ?, dex = ?, luk = ?, `int` = ?, exp = ?, gachaexp = ?, hp = ?, mp = ?, maxhp = ?, maxmp = ?, sp = ?, ap = ?, gm = ?, skincolor = ?, gender = ?, job = ?, hair = ?, face = ?, map = ?, meso = ?, hpMpUsed = ?, spawnpoint = ?, party = ?, buddyCapacity = ?, messengerid = ?, messengerposition = ?, mountlevel = ?, mountexp = ?, mounttiredness= ?, equipslots = ?, useslots = ?, setupslots = ?, etcslots = ?, monsterbookcover = ?, vanquisherStage = ?, dojoPoints = ?, lastDojoStage = ?, finishedDojoTutorial = ?, vanquisherKills = ?, matchcardwins = ?, matchcardlosses = ?, matchcardties = ?, omokwins = ?, omoklosses = ?, omokties = ?, dataString = ?, fquest = ?, jailexpire = ?, partnerId = ?, marriageItemId = ? WHERE id = ?", Statement.RETURN_GENERATED_KEYS); if (gmLevel < 1 && level > 199) { ps.setInt(1, isCygnus() ? 120 : 200); } else { @@ -6555,9 +6750,9 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { for (int i = 1; i < 5; i++) { ps.setInt(i + 31, getSlots(i)); } - + monsterbook.saveCards(getId()); - + ps.setInt(36, bookCover); ps.setInt(37, vanquisherStage); ps.setInt(38, dojoPoints); @@ -6573,9 +6768,13 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { ps.setString(48, dataString); ps.setInt(49, quest_fame); ps.setLong(50, jailExpiration); - ps.setInt(51, id); + ps.setInt(51, partnerId); + ps.setInt(52, marriageItemid); + ps.setInt(53, id); int updateRows = ps.executeUpdate(); + ps.close(); + if (updateRows < 1) { throw new RuntimeException("Character not in database (" + id + ")"); } @@ -6619,6 +6818,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { ps.addBatch(); } ps.executeBatch(); + ps.close(); deleteWhereCharacterId(con, "DELETE FROM skillmacros WHERE characterid = ?"); ps = con.prepareStatement("INSERT INTO skillmacros (characterid, skill1, skill2, skill3, name, shout, position) VALUES (?, ?, ?, ?, ?, ?, ?)"); @@ -6636,6 +6836,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } } ps.executeBatch(); + ps.close(); List> itemsWithType = new ArrayList<>(); for (MapleInventory iv : inventory) { @@ -6643,8 +6844,9 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { itemsWithType.add(new Pair<>(item, iv.getType())); } } + ItemFactory.INVENTORY.saveItems(itemsWithType, id, con); - + deleteWhereCharacterId(con, "DELETE FROM skills WHERE characterid = ?"); ps = con.prepareStatement("INSERT INTO skills (characterid, skillid, skilllevel, masterlevel, expiration) VALUES (?, ?, ?, ?, ?)"); ps.setInt(1, id); @@ -6656,6 +6858,8 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { ps.addBatch(); } ps.executeBatch(); + ps.close(); + deleteWhereCharacterId(con, "DELETE FROM savedlocations WHERE characterid = ?"); ps = con.prepareStatement("INSERT INTO savedlocations (characterid, `locationtype`, `map`, `portal`) VALUES (?, ?, ?, ?)"); ps.setInt(1, id); @@ -6668,6 +6872,8 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } } ps.executeBatch(); + ps.close(); + deleteWhereCharacterId(con, "DELETE FROM trocklocations WHERE characterid = ?"); ps = con.prepareStatement("INSERT INTO trocklocations(characterid, mapid, vip) VALUES (?, ?, 0)"); for (int i = 0; i < getTrockSize(); i++) { @@ -6678,6 +6884,8 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } } ps.executeBatch(); + ps.close(); + ps = con.prepareStatement("INSERT INTO trocklocations(characterid, mapid, vip) VALUES (?, ?, 1)"); for (int i = 0; i < getVipTrockSize(); i++) { if (viptrockmaps.get(i) != 999999999) { @@ -6687,6 +6895,8 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } } ps.executeBatch(); + ps.close(); + deleteWhereCharacterId(con, "DELETE FROM buddies WHERE characterid = ? AND pending = 0"); ps = con.prepareStatement("INSERT INTO buddies (characterid, `buddyid`, `pending`, `group`) VALUES (?, ?, 0, ?)"); ps.setInt(1, id); @@ -6698,6 +6908,8 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } } ps.executeBatch(); + ps.close(); + deleteWhereCharacterId(con, "DELETE FROM area_info WHERE charid = ?"); ps = con.prepareStatement("INSERT INTO area_info (id, charid, area, info) VALUES (DEFAULT, ?, ?, ?)"); ps.setInt(1, id); @@ -6707,9 +6919,12 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { ps.addBatch(); } ps.executeBatch(); + ps.close(); + deleteWhereCharacterId(con, "DELETE FROM eventstats WHERE characterid = ?"); deleteQuestProgressWhereCharacterId(con, id); + ps = con.prepareStatement("INSERT INTO queststatus (`queststatusid`, `characterid`, `quest`, `status`, `time`, `expires`, `forfeited`) VALUES (DEFAULT, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); PreparedStatement psf; try (PreparedStatement pse = con.prepareStatement("INSERT INTO questprogress VALUES (DEFAULT, ?, ?, ?, ?)")) { @@ -6718,6 +6933,8 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { synchronized (quests) { for (MapleQuestStatus q : quests.values()) { + if(!q.wasUpdated()) continue; + ps.setInt(2, q.getQuest().getId()); ps.setInt(3, q.getStatus().getId()); ps.setInt(4, (int) (q.getCompletionTime() / 1000)); @@ -6744,23 +6961,31 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { } } } + } psf.close(); + ps.close(); + ps = con.prepareStatement("UPDATE accounts SET gm = ? WHERE id = ?"); ps.setInt(1, gmLevel > 1 ? 1 : 0); ps.setInt(2, client.getAccID()); ps.executeUpdate(); ps.close(); - + + con.commit(); con.setAutoCommit(true); - + + if (cashshop != null) { cashshop.save(con); } - if (storage != null) { + + if (storage != null && usedStorage) { storage.saveToDB(con); + usedStorage = false; } + } catch (SQLException | RuntimeException t) { FilePrinter.printError(FilePrinter.SAVE_CHAR, t, "Error saving " + name + " Level: " + level + " Job: " + job.getId()); try { @@ -6973,7 +7198,7 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { int newAmount; try { - newAmount = (int)Math.min((long)merchantmeso + add, Integer.MAX_VALUE); + newAmount = (int) Math.min((long) merchantmeso + add, Integer.MAX_VALUE); Connection con = DatabaseConnection.getConnection(); try (PreparedStatement ps = con.prepareStatement("UPDATE characters SET MerchantMesos = ? WHERE id = ?", Statement.RETURN_GENERATED_KEYS)) { @@ -7303,9 +7528,14 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { if (item == null){ //Basic check return(0); } - if (ItemConstants.isRechargeable(item.getItemId())) { + + int itemid = item.getItemId(); + if (ItemConstants.isRechargeable(itemid)) { quantity = item.getQuantity(); + } else if (ItemConstants.isWeddingToken(itemid) || ItemConstants.isWeddingRing(itemid)) { + return(0); } + if (quantity < 0) { return(0); } @@ -7316,7 +7546,6 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { if (quantity <= iQuant && iQuant > 0) { MapleInventoryManipulator.removeFromSlot(c, type, (byte) slot, quantity, false); - int itemid = item.getItemId(); int recvMesos = ii.getPrice(itemid, quantity); if (recvMesos > 0) { gainMeso(recvMesos, false); @@ -8168,6 +8397,18 @@ public class MapleCharacter extends AbstractAnimatedMapleMapObject { this.showHint("#ePLAYER EQUIPMENTS:#n\r\n\r\n" + showMsg, 400); } } + + public void broadcastMarriageMessage() { + MapleGuild guild = this.getGuild(); + if(guild != null) { + guild.broadcast(MaplePacketCreator.marriageMessage(0, name)); + } + + MapleFamily family = this.getFamily(); + if(family != null) { + family.broadcast(MaplePacketCreator.marriageMessage(1, name)); + } + } public Map getEvents() { return events; diff --git a/src/client/MapleClient.java b/src/client/MapleClient.java index f5950a16bb..2dd2324dd4 100644 --- a/src/client/MapleClient.java +++ b/src/client/MapleClient.java @@ -736,7 +736,7 @@ public class MapleClient { public int getLoginState() { // 0 = LOGIN_NOTLOGGEDIN, 1= LOGIN_SERVER_TRANSITION, 2 = LOGIN_LOGGEDIN try { Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT loggedin, lastlogin, UNIX_TIMESTAMP(birthday) as birthday FROM accounts WHERE id = ?"); + PreparedStatement ps = con.prepareStatement("SELECT loggedin, lastlogin, birthday FROM accounts WHERE id = ?"); ps.setInt(1, getAccID()); ResultSet rs = ps.executeQuery(); if (!rs.next()) { @@ -744,11 +744,12 @@ public class MapleClient { ps.close(); throw new RuntimeException("getLoginState - MapleClient"); } + birthday = Calendar.getInstance(); - long blubb = rs.getLong("birthday"); - if (blubb > 0) { - birthday.setTimeInMillis(blubb * 1000); - } + try { + birthday.setTime(rs.getDate("birthday")); + } catch(SQLException e) {} + int state = rs.getInt("loggedin"); if (state == LOGIN_SERVER_TRANSITION) { // Arnah's note: lastlogin is a date-type, in case of login and game servers being of different timezones this becomes broken @@ -783,12 +784,13 @@ public class MapleClient { } public boolean checkBirthDate(Calendar date) { - return date.get(Calendar.YEAR) == birthday.get(Calendar.YEAR) && date.get(Calendar.MONTH) == birthday.get(Calendar.MONTH) && date.get(Calendar.DAY_OF_MONTH) == birthday.get(Calendar.DAY_OF_MONTH); + return date.get(Calendar.YEAR) == birthday.get(Calendar.YEAR) && date.get(Calendar.MONTH) == birthday.get(Calendar.MONTH) && date.get(Calendar.DAY_OF_MONTH) == birthday.get(Calendar.DAY_OF_MONTH); } private void removePlayer() { try { player.setAwayFromWorld(true); + player.notifyMapTransferToPartner(-1); player.cancelAllBuffs(true); player.cancelAllDebuffs(); @@ -1167,7 +1169,7 @@ public class MapleClient { return characterSlots; } - public boolean gainCharacterSlot() { + public synchronized boolean gainCharacterSlot() { if (characterSlots < 15) { Connection con = null; try { @@ -1301,6 +1303,7 @@ public class MapleClient { server.getPlayerBuffStorage().addBuffsToStorage(player.getId(), player.getAllBuffs()); server.getPlayerBuffStorage().addDiseasesToStorage(player.getId(), player.getAllDiseases()); player.setAwayFromWorld(true); + player.notifyMapTransferToPartner(-1); player.cancelAllBuffs(true); player.cancelAllDebuffs(); player.cancelBuffExpireTask(); diff --git a/src/client/MapleFamily.java b/src/client/MapleFamily.java index 781cbcb87a..903c06b77e 100644 --- a/src/client/MapleFamily.java +++ b/src/client/MapleFamily.java @@ -92,4 +92,8 @@ public class MapleFamily { public Map getMembers() { return members; } + + public void broadcast(byte[] packet) { + // family currently not developed + } } diff --git a/src/client/MapleQuestStatus.java b/src/client/MapleQuestStatus.java index a7732c4cf3..f3cbb0cbbb 100644 --- a/src/client/MapleQuestStatus.java +++ b/src/client/MapleQuestStatus.java @@ -60,8 +60,9 @@ public class MapleQuestStatus { } private short questID; private Status status; - private Map progress = new LinkedHashMap(); - private List medalProgress = new LinkedList(); + private boolean updated; + private final Map progress = new LinkedHashMap(); + private final List medalProgress = new LinkedList(); private int npc; private long completionTime, expirationTime; private int forfeited = 0; @@ -72,6 +73,7 @@ public class MapleQuestStatus { this.setStatus(status); this.completionTime = System.currentTimeMillis(); this.expirationTime = 0; + this.updated = true; if (status == Status.STARTED) registerMobs(); } @@ -82,6 +84,7 @@ public class MapleQuestStatus { this.setNpc(npc); this.completionTime = System.currentTimeMillis(); this.expirationTime = 0; + this.updated = true; if (status == Status.STARTED) { registerMobs(); } @@ -102,6 +105,18 @@ public class MapleQuestStatus { public final void setStatus(Status status) { this.status = status; } + + public boolean wasUpdated() { + return updated; + } + + public void setUpdated() { + this.updated = true; + } + + public void resetUpdated() { + this.updated = false; + } public int getNpc() { return npc; @@ -115,11 +130,13 @@ public class MapleQuestStatus { for (int i : MapleQuest.getInstance(questID).getRelevantMobs()) { progress.put(i, "000"); } + this.setUpdated(); } public boolean addMedalMap(int mapid) { if (medalProgress.contains(mapid)) return false; medalProgress.add(mapid); + this.setUpdated(); return true; } @@ -136,6 +153,7 @@ public class MapleQuestStatus { int current = Integer.parseInt(progress.get(id)); String str = StringUtil.getLeftPaddedStr(Integer.toString(current + 1), '0', 3); progress.put(id, str); + this.setUpdated(); return true; } return false; @@ -143,6 +161,7 @@ public class MapleQuestStatus { public void setProgress(int id, String pr) { progress.put(id, pr); + this.setUpdated(); } public boolean madeProgress() { @@ -202,6 +221,7 @@ public class MapleQuestStatus { public void setInfo(String newInfo) { progress.put(0, newInfo); + this.setUpdated(); } public void setForfeited(int forfeited) { diff --git a/src/client/MapleRing.java b/src/client/MapleRing.java index 12c4fbde2e..e05200fd6b 100644 --- a/src/client/MapleRing.java +++ b/src/client/MapleRing.java @@ -68,6 +68,40 @@ public class MapleRing implements Comparable { } } + public static void removeRing(final MapleRing ring) { + try { + if (ring == null) { + return; + } + + Connection con = DatabaseConnection.getConnection(); + + PreparedStatement ps = con.prepareStatement("DELETE FROM rings WHERE id=?"); + ps.setInt(1, ring.getRingId()); + ps.addBatch(); + + ps.setInt(1, ring.getPartnerRingId()); + ps.addBatch(); + + ps.executeBatch(); + ps.close(); + + ps = con.prepareStatement("UPDATE inventoryequipment SET ringid=-1 WHERE ringid=?"); + ps.setInt(1, ring.getRingId()); + ps.addBatch(); + + ps.setInt(1, ring.getPartnerRingId()); + ps.addBatch(); + + ps.executeBatch(); + ps.close(); + + con.close(); + } catch (SQLException ex) { + ex.printStackTrace(); + } + } + public static int createRing(int itemid, final MapleCharacter partner1, final MapleCharacter partner2) { try { if (partner1 == null) { diff --git a/src/client/MonsterBook.java b/src/client/MonsterBook.java index 98bebe0401..8a1e3c7cfe 100644 --- a/src/client/MonsterBook.java +++ b/src/client/MonsterBook.java @@ -31,12 +31,15 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.locks.Lock; +import java.util.concurrent.Semaphore; import tools.locks.MonitoredReentrantLock; import tools.DatabaseConnection; import tools.MaplePacketCreator; import tools.locks.MonitoredLockType; public final class MonsterBook { + private static final Semaphore semaphore = new Semaphore(10); + private int specialCard = 0; private int normalCard = 0; private int bookLevel = 1; @@ -171,6 +174,43 @@ public final class MonsterBook { calculateLevel(); } + private static int saveStringConcat(char[] data, int pos, Integer i) { + return saveStringConcat(data, pos, i.toString()); + } + + private static int saveStringConcat(char[] data, int pos, String s) { + int len = s.length(); + for(int j = 0; j < len; j++) { + data[pos + j] = s.charAt(j); + } + + return pos + len; + } + + private static String getSaveString(Integer charid, Set> cardSet) { + semaphore.acquireUninterruptibly(); + try { + char[] save = new char[400000]; // 500 * 10 * 10 * 8 + int i = 0; + + i = saveStringConcat(save, i, "INSERT INTO monsterbook VALUES "); + + for (Entry all : cardSet) { // assuming maxsize 500 unique cards + i = saveStringConcat(save, i, "("); + i = saveStringConcat(save, i, charid); //10 chars + i = saveStringConcat(save, i, ", "); + i = saveStringConcat(save, i, all.getKey()); //10 chars + i = saveStringConcat(save, i, ", "); + i = saveStringConcat(save, i, all.getValue()); //1 char due to being 0 ~ 5 + i = saveStringConcat(save, i, "),"); + } + + return new String(save, 0, i - 1); + } finally { + semaphore.release(); + } + } + public void saveCards(final int charid) { Set> cardSet = getCardSet(); @@ -183,23 +223,8 @@ public final class MonsterBook { ps.setInt(1, charid); ps.execute(); ps.close(); - boolean first = true; - StringBuilder query = new StringBuilder(); - for (Entry all : cardSet) { - if (first) { - query.append("INSERT INTO monsterbook VALUES ("); - first = false; - } else { - query.append(",("); - } - query.append(charid); - query.append(", "); - query.append(all.getKey()); - query.append(", "); - query.append(all.getValue()); - query.append(")"); - } - ps = con.prepareStatement(query.toString()); + + ps = con.prepareStatement(getSaveString(charid, cardSet)); ps.execute(); ps.close(); con.close(); diff --git a/src/client/command/Commands.java b/src/client/command/Commands.java index 69ef6466dd..4aa592528c 100644 --- a/src/client/command/Commands.java +++ b/src/client/command/Commands.java @@ -45,6 +45,7 @@ import java.util.List; import java.util.Properties; import java.util.TimeZone; +import client.inventory.manipulator.MapleInventoryManipulator; import net.MaplePacketHandler; import net.PacketProcessor; import net.server.Server; @@ -56,7 +57,6 @@ import provider.MapleDataProviderFactory; import provider.MapleDataTool; import scripting.npc.NPCScriptManager; import scripting.portal.PortalScriptManager; -import server.MapleInventoryManipulator; import server.MapleItemInformationProvider; import server.MaplePortal; import server.MapleShopFactory; @@ -102,6 +102,7 @@ import client.inventory.Equip; import client.inventory.MapleInventory; import client.inventory.MapleInventoryType; import client.inventory.MaplePet; +import client.processor.BuybackProcessor; import constants.ItemConstants; import constants.ServerConstants; import java.util.ArrayList; @@ -351,22 +352,6 @@ public class Commands { MapleCharacter player = c.getPlayer(); switch(sub[0]) { - case "wow": - MaplePlayerNPC.multicastSpawnPlayerNPC(player.getMapId(), player.getWorld()); - break; - - case "out": - MaplePlayerNPC.removeAllPlayerNPC(); - break; - - case "er": - MaplePlayerNPC.spawnPlayerNPC(player.getMapId(), player); - break; - - case "re": - MaplePlayerNPC.removePlayerNPC(player); - break; - case "help": case "commands": case "playercommands": @@ -383,6 +368,10 @@ public class Commands { break; + case "buyback": + BuybackProcessor.processBuyback(c); + break; + case "time": DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); dateFormat.setTimeZone(TimeZone.getTimeZone(ServerConstants.TIMEZONE)); @@ -1222,7 +1211,7 @@ public class Commands { case "item": case "drop": - if (sub.length < 2){ + if (sub.length < 2) { player.yellowMessage("Syntax: !item "); break; } @@ -2613,13 +2602,24 @@ public class Commands { player.getMap().spawnMonsterOnGroundBelow(monster, player.getPosition()); break; + case "pnpcremove": + if (sub.length < 2) { + player.yellowMessage("Syntax: !pnpcremove "); + break; + } + + MaplePlayerNPC.removePlayerNPC(c.getChannelServer().getPlayerStorage().getCharacterByName(sub[1])); + break; + case "playernpc": - if (sub.length < 3){ + if (sub.length < 2) { player.yellowMessage("Syntax: !playernpc "); break; } - MaplePlayerNPC.spawnPlayerNPC(player.getMapId(), player.getPosition(), c.getChannelServer().getPlayerStorage().getCharacterByName(sub[1])); + if(!MaplePlayerNPC.spawnPlayerNPC(player.getMapId(), player.getPosition(), c.getChannelServer().getPlayerStorage().getCharacterByName(sub[1]))) { + player.dropMessage(5, "Could not deploy PlayerNPC. Either there's no room available here or depleted out scriptids to use."); + } break; default: @@ -2634,53 +2634,48 @@ public class Commands { MapleMonster monster; switch(sub[0]) { - case "debugmonster": + case "debugmonster": List monsters = player.getMap().getMapObjectsInRange(player.getPosition(), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.MONSTER)); for (MapleMapObject monstermo : monsters) { monster = (MapleMonster) monstermo; player.message("Monster ID: " + monster.getId() + " Aggro target: " + ((monster.getController() != null) ? monster.getController().getName() : "")); } - break; + break; - - - case "debugpacket": + case "debugpacket": player.getMap().broadcastMessage(MaplePacketCreator.customPacket(joinStringFrom(sub, 1))); break; - case "debugportal": + case "debugportal": MaplePortal portal = player.getMap().findClosestPortal(player.getPosition()); if(portal != null) player.dropMessage(6, "Closest portal: " + portal.getId() + " '" + portal.getName() + "' Type: " + portal.getType() + " --> toMap: " + portal.getTargetMapId() + " scriptname: '" + portal.getScriptName() + "' state: " + portal.getPortalState() + "."); else player.dropMessage(6, "There is no portal on this map."); - break; - case "debugspawnpoint": + case "debugspawnpoint": SpawnPoint sp = player.getMap().findClosestSpawnpoint(player.getPosition()); if(sp != null) player.dropMessage(6, "Closest mob spawn point: " + " Position: x " + sp.getPosition().getX() + " y " + sp.getPosition().getY() + " Spawns mobid: '" + sp.getMonsterId() + "' --> canSpawn: " + !sp.getDenySpawn() + " canSpawnRightNow: " + sp.shouldSpawn() + "."); else player.dropMessage(6, "There is no mob spawn point on this map."); - break; - case "debugpos": + case "debugpos": player.dropMessage(6, "Current map position: (" + player.getPosition().getX() + ", " + player.getPosition().getY() + ")."); break; - case "debugmap": + case "debugmap": player.dropMessage(6, "Current map id " + player.getMap().getId() + ", event: '" + ((player.getMap().getEventInstance() != null) ? player.getMap().getEventInstance().getName() : "null") + "'; Players: " + player.getMap().getAllPlayers().size() + ", Mobs: " + player.getMap().countMonsters() + ", Reactors: " + player.getMap().countReactors() + ", Items: " + player.getMap().countItems() + ", Objects: " + player.getMap().getMapObjects().size() + "."); break; - case "debugmobsp": + case "debugmobsp": player.getMap().reportMonsterSpawnPoints(player); break; - case "debugevent": + case "debugevent": if(player.getEventInstance() == null) player.dropMessage(6, "Player currently not in an event."); else player.dropMessage(6, "Current event name: " + player.getEventInstance().getName() + "."); - break; - case "debugareas": + case "debugareas": player.dropMessage(6, "Configured areas on map " + player.getMapId() + ":"); byte index = 0; @@ -2688,54 +2683,52 @@ public class Commands { player.dropMessage(6, "Id: " + index + " -> posX: " + rect.getX() + " posY: '" + rect.getY() + "' dX: " + rect.getWidth() + " dY: " + rect.getHeight() + "."); index++; } - break; - case "debugreactors": + case "debugreactors": player.dropMessage(6, "Current reactor states on map " + player.getMapId() + ":"); for(MapleMapObject mmo: player.getMap().getReactors()) { MapleReactor mr = (MapleReactor) mmo; player.dropMessage(6, "Id: " + mr.getId() + " Oid: " + mr.getObjectId() + " name: '" + mr.getName() + "' -> Type: " + mr.getReactorType() + " State: " + mr.getState() + " Event State: " + mr.getEventState() + " Position: x " + mr.getPosition().getX() + " y " + mr.getPosition().getY() + "."); } - break; - case "debugservercoupons": - case "debugcoupons": + case "debugservercoupons": + case "debugcoupons": String s = "Currently active SERVER coupons: "; for(Integer i : Server.getInstance().getActiveCoupons()) { s += (i + " "); } player.dropMessage(6, s); - break; - case "debugplayercoupons": + case "debugplayercoupons": String st = "Currently active PLAYER coupons: "; for(Integer i : player.getActiveCoupons()) { st += (i + " "); } player.dropMessage(6, st); - break; - case "debugtimer": + case "debugtimer": TimerManager tMan = TimerManager.getInstance(); player.dropMessage(6, "Total Task: " + tMan.getTaskCount() + " Current Task: " + tMan.getQueuedTasks() + " Active Task: " + tMan.getActiveCount() + " Completed Task: " + tMan.getCompletedTaskCount()); - break; + break; - case "set": + case "debugmarriage": + c.getChannelServer().debugMarriageStatus(); + break; + + case "set": for(int i = 0; i < sub.length - 1; i++) { ServerConstants.DEBUG_VALUES[i] = Integer.parseInt(sub[i + 1]); } - - break; - - default: + + default: return false; } @@ -2747,7 +2740,7 @@ public class Commands { MapleCharacter victim; switch(sub[0]) { - case "setgmlevel": + case "setgmlevel": if (sub.length < 3){ player.yellowMessage("Syntax: !setgmlevel "); break; @@ -2766,7 +2759,7 @@ public class Commands { } break; - case "warpworld": + case "warpworld": if (sub.length < 2){ player.yellowMessage("Syntax: !warpworld "); break; @@ -2793,7 +2786,7 @@ public class Commands { } break; - case "saveall": + case "saveall": for (World world : Server.getInstance().getWorlds()) { for (MapleCharacter chr : world.getPlayerStorage().getAllCharacters()) { chr.saveToDB(); @@ -2804,7 +2797,7 @@ public class Commands { player.message("All players saved successfully."); break; - case "dcall": + case "dcall": for (World world : Server.getInstance().getWorlds()) { for (MapleCharacter chr : world.getPlayerStorage().getAllCharacters()) { if (!chr.isGM()) { @@ -2815,7 +2808,7 @@ public class Commands { player.message("All players successfully disconnected."); break; - case "mapplayers": + case "mapplayers": String names = ""; int map = player.getMapId(); for (World world : Server.getInstance().getWorlds()) { @@ -2832,7 +2825,7 @@ public class Commands { player.message("These b lurkin: " + names); break; - case "getacc": + case "getacc": if (sub.length < 2){ player.yellowMessage("Syntax: !getacc "); break; @@ -2845,8 +2838,8 @@ public class Commands { } break; - case "shutdown": - case "shutdownnow": + case "shutdown": + case "shutdownnow": int time = 60000; if (sub[0].equals("shutdownnow")) { time = 1; @@ -2877,12 +2870,12 @@ public class Commands { TimerManager.getInstance().schedule(Server.getInstance().shutdown(false), time); break; - case "clearquestcache": + case "clearquestcache": MapleQuest.clearCache(); player.dropMessage(5, "Quest Cache Cleared."); break; - case "clearquest": + case "clearquest": if(sub.length < 1) { player.dropMessage(5, "Please include a quest ID."); break; @@ -2891,11 +2884,19 @@ public class Commands { player.dropMessage(5, "Quest Cache for quest " + sub[1] + " cleared."); break; - case "fred": + case "fred": c.announce(MaplePacketCreator.fredrickMessage(Byte.valueOf(sub[1]))); break; - default: + case "spawnallpnpcs": + MaplePlayerNPC.multicastSpawnPlayerNPC(player.getMapId(), player.getWorld()); + break; + + case "eraseallpnpcs": + MaplePlayerNPC.removeAllPlayerNPC(); + break; + + default: return false; } @@ -2942,7 +2943,7 @@ public class Commands { else return true; } - public static boolean executeHeavenMSPlayerCommand(MapleClient c, String[] sub, char heading) { + public static boolean executeHeavenMsPlayerCommand(MapleClient c, String[] sub, char heading) { Channel cserv = c.getChannelServer(); Server srv = Server.getInstance(); diff --git a/src/client/creator/CharacterFactory.java b/src/client/creator/CharacterFactory.java new file mode 100644 index 0000000000..d0e4287b04 --- /dev/null +++ b/src/client/creator/CharacterFactory.java @@ -0,0 +1,94 @@ +/* + 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 client.creator; + +import client.MapleClient; +import client.MapleCharacter; +import client.MapleSkinColor; +import client.inventory.Item; +import client.inventory.MapleInventory; +import client.inventory.MapleInventoryType; +import net.server.Server; +import server.MapleItemInformationProvider; +import tools.MaplePacketCreator; + +/** + * + * @author RonanLana + */ +public abstract class CharacterFactory { + + protected synchronized static int createNewCharacter(MapleClient c, String name, int face, int hair, int skin, int gender, CharacterFactoryRecipe recipe) { + if (!MapleCharacter.canCreateChar(name)) { + return -1; + } + + MapleCharacter newchar = MapleCharacter.getDefault(c); + newchar.setWorld(c.getWorld()); + newchar.setSkinColor(MapleSkinColor.getById(skin)); + newchar.setGender(gender); + newchar.setName(name); + newchar.setHair(hair); + newchar.setFace(face); + + newchar.setLevel(recipe.getLevel()); + newchar.setJob(recipe.getJob()); + newchar.setMapId(recipe.getMap()); + + MapleInventory equipped = newchar.getInventory(MapleInventoryType.EQUIPPED); + MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); + + int top = recipe.getTop(), bottom = recipe.getBottom(), shoes = recipe.getShoes(), weapon = recipe.getWeapon(); + + if(top > 0) { + Item eq_top = ii.getEquipById(top); + eq_top.setPosition((byte) -5); + equipped.addFromDB(eq_top); + } + + if(bottom > 0) { + Item eq_bottom = ii.getEquipById(bottom); + eq_bottom.setPosition((byte) -6); + equipped.addFromDB(eq_bottom); + } + + if(shoes > 0) { + Item eq_shoes = ii.getEquipById(shoes); + eq_shoes.setPosition((byte) -7); + equipped.addFromDB(eq_shoes); + } + + if(weapon > 0) { + Item eq_weapon = ii.getEquipById(weapon); + eq_weapon.setPosition((byte) -11); + equipped.addFromDB(eq_weapon.copy()); + } + + if (!newchar.insertNewChar(recipe)) { + return -2; + } + c.announce(MaplePacketCreator.addNewCharEntry(newchar)); + + Server.getInstance().createCharacterid(newchar.getAccountID(), newchar.getId(), newchar.getWorld()); + Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.sendYellowTip("[NEW CHAR]: " + c.getAccountName() + " has created a new character with IGN " + name)); + + return 0; + } +} diff --git a/src/client/creator/CharacterFactoryRecipe.java b/src/client/creator/CharacterFactoryRecipe.java new file mode 100644 index 0000000000..28333e086f --- /dev/null +++ b/src/client/creator/CharacterFactoryRecipe.java @@ -0,0 +1,184 @@ +/* + 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 client.creator; + +import client.MapleJob; +import client.Skill; +import client.inventory.Item; +import client.inventory.MapleInventoryType; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import tools.Pair; + +/** + * + * @author RonanLana + */ +public class CharacterFactoryRecipe { + private MapleJob job; + private int level, map, top, bottom, shoes, weapon; + private int str = 4, dex = 4, int_ = 4, luk = 4; + private int maxHp = 50, maxMp = 5; + private int ap = 0, sp = 0; + private int meso = 0; + private List> skills = new LinkedList<>(); + + private List> itemsWithType = new LinkedList<>(); + private Map runningTypePosition = new LinkedHashMap<>(); + + public CharacterFactoryRecipe(MapleJob job, int level, int map, int top, int bottom, int shoes, int weapon) { + this.job = job; + this.level = level; + this.map = map; + this.top = top; + this.bottom = bottom; + this.shoes = shoes; + this.weapon = weapon; + } + + public void setStr(int v) { + str = v; + } + + public void setDex(int v) { + dex = v; + } + + public void setInt(int v) { + int_ = v; + } + + public void setLuk(int v) { + luk = v; + } + + public void setMaxHp(int v) { + maxHp = v; + } + + public void setMaxMp(int v) { + maxMp = v; + } + + public void setRemainingAp(int v) { + ap = v; + } + + public void setRemainingSp(int v) { + sp = v; + } + + public void setMeso(int v) { + meso = v; + } + + public void addStartingSkillLevel(Skill skill, int level) { + skills.add(new Pair<>(skill, level)); + } + + public void addStartingEquipment(Item eqpItem) { + itemsWithType.add(new Pair<>(eqpItem, MapleInventoryType.EQUIP)); + } + + public void addStartingItem(int itemid, int quantity, MapleInventoryType itemType) { + AtomicInteger p = runningTypePosition.get(itemType); + if(p == null) { + p = new AtomicInteger(0); + runningTypePosition.put(itemType, p); + } + + itemsWithType.add(new Pair<>(new Item(itemid, (short) p.getAndIncrement(), (short) quantity), itemType)); + } + + public MapleJob getJob() { + return job; + } + + public int getLevel() { + return level; + } + + public int getMap() { + return map; + } + + public int getTop() { + return top; + } + + public int getBottom() { + return bottom; + } + + public int getShoes() { + return shoes; + } + + public int getWeapon() { + return weapon; + } + + public int getStr() { + return str; + } + + public int getDex() { + return dex; + } + + public int getInt() { + return int_; + } + + public int getLuk() { + return luk; + } + + public int getMaxHp() { + return maxHp; + } + + public int getMaxMp() { + return maxMp; + } + + public int getRemainingAp() { + return ap; + } + + public int getRemainingSp() { + return sp; + } + + public int getMeso() { + return meso; + } + + public List> getStartingSkillLevel() { + return skills; + } + + public List> getStartingItems() { + return itemsWithType; + } +} diff --git a/src/client/creator/novice/BeginnerCreator.java b/src/client/creator/novice/BeginnerCreator.java new file mode 100644 index 0000000000..d27252e2ea --- /dev/null +++ b/src/client/creator/novice/BeginnerCreator.java @@ -0,0 +1,48 @@ +/* + 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 client.creator.novice; + +import client.MapleClient; +import client.MapleJob; +import client.creator.CharacterFactory; +import client.creator.CharacterFactoryRecipe; +import client.inventory.MapleInventoryType; + +/** + * + * @author RonanLana + */ +public class BeginnerCreator extends CharacterFactory { + + private static CharacterFactoryRecipe createRecipe(MapleJob job, int level, int map, int top, int bottom, int shoes, int weapon) { + CharacterFactoryRecipe recipe = new CharacterFactoryRecipe(job, level, map, top, bottom, shoes, weapon); + giveItem(recipe, 4161001, 1, MapleInventoryType.ETC); + return recipe; + } + + private static void giveItem(CharacterFactoryRecipe recipe, int itemid, int quantity, MapleInventoryType itemType) { + recipe.addStartingItem(itemid, quantity, itemType); + } + + public static int createCharacter(MapleClient c, String name, int face, int hair, int skin, int top, int bottom, int shoes, int weapon, int gender) { + int status = createNewCharacter(c, name, face, hair, skin, gender, createRecipe(MapleJob.BEGINNER, 1, 10000, top, bottom, shoes, weapon)); + return status; + } +} diff --git a/src/client/creator/novice/LegendCreator.java b/src/client/creator/novice/LegendCreator.java new file mode 100644 index 0000000000..6c431f26e2 --- /dev/null +++ b/src/client/creator/novice/LegendCreator.java @@ -0,0 +1,48 @@ +/* + 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 client.creator.novice; + +import client.MapleClient; +import client.MapleJob; +import client.creator.CharacterFactory; +import client.creator.CharacterFactoryRecipe; +import client.inventory.MapleInventoryType; + +/** + * + * @author RonanLana + */ +public class LegendCreator extends CharacterFactory { + + private static CharacterFactoryRecipe createRecipe(MapleJob job, int level, int map, int top, int bottom, int shoes, int weapon) { + CharacterFactoryRecipe recipe = new CharacterFactoryRecipe(job, level, map, top, bottom, shoes, weapon); + giveItem(recipe, 4161048, 1, MapleInventoryType.ETC); + return recipe; + } + + private static void giveItem(CharacterFactoryRecipe recipe, int itemid, int quantity, MapleInventoryType itemType) { + recipe.addStartingItem(itemid, quantity, itemType); + } + + public static int createCharacter(MapleClient c, String name, int face, int hair, int skin, int top, int bottom, int shoes, int weapon, int gender) { + int status = createNewCharacter(c, name, face, hair, skin, gender, createRecipe(MapleJob.LEGEND, 1, 914000000, top, bottom, shoes, weapon)); + return status; + } +} diff --git a/src/client/creator/novice/NoblesseCreator.java b/src/client/creator/novice/NoblesseCreator.java new file mode 100644 index 0000000000..f3a6b02d9d --- /dev/null +++ b/src/client/creator/novice/NoblesseCreator.java @@ -0,0 +1,48 @@ +/* + 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 client.creator.novice; + +import client.MapleClient; +import client.MapleJob; +import client.creator.CharacterFactory; +import client.creator.CharacterFactoryRecipe; +import client.inventory.MapleInventoryType; + +/** + * + * @author RonanLana + */ +public class NoblesseCreator extends CharacterFactory { + + private static CharacterFactoryRecipe createRecipe(MapleJob job, int level, int map, int top, int bottom, int shoes, int weapon) { + CharacterFactoryRecipe recipe = new CharacterFactoryRecipe(job, level, map, top, bottom, shoes, weapon); + giveItem(recipe, 4161047, 1, MapleInventoryType.ETC); + return recipe; + } + + private static void giveItem(CharacterFactoryRecipe recipe, int itemid, int quantity, MapleInventoryType itemType) { + recipe.addStartingItem(itemid, quantity, itemType); + } + + public static int createCharacter(MapleClient c, String name, int face, int hair, int skin, int top, int bottom, int shoes, int weapon, int gender) { + int status = createNewCharacter(c, name, face, hair, skin, gender, createRecipe(MapleJob.NOBLESSE, 1, 130030000, top, bottom, shoes, weapon)); + return status; + } +} diff --git a/src/client/creator/veteran/BowmanCreator.java b/src/client/creator/veteran/BowmanCreator.java new file mode 100644 index 0000000000..8d0a7a6908 --- /dev/null +++ b/src/client/creator/veteran/BowmanCreator.java @@ -0,0 +1,75 @@ +/* + 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 client.creator.veteran; + +import client.MapleClient; +import client.MapleJob; +import client.creator.CharacterFactory; +import client.creator.CharacterFactoryRecipe; +import client.inventory.Item; +import client.inventory.MapleInventoryType; +import server.MapleItemInformationProvider; + +/** + * + * @author RonanLana + */ +public class BowmanCreator extends CharacterFactory { + private static int[] equips = {1040067, 1041054, 1060056, 1061050, 1072081}; + private static int[] weapons = {1452005, 1462000}; + private static int[] startingHpMp = {797, 404}; + + private static CharacterFactoryRecipe createRecipe(MapleJob job, int level, int map, int top, int bottom, int shoes, int weapon) { + CharacterFactoryRecipe recipe = new CharacterFactoryRecipe(job, level, map, top, bottom, shoes, weapon); + MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); + + recipe.setDex(25); + recipe.setRemainingAp(133); + recipe.setRemainingSp(61); + + recipe.setMaxHp(startingHpMp[0]); + recipe.setMaxMp(startingHpMp[1]); + + recipe.setMeso(100000); + + for(int i = 1; i < weapons.length; i++) { + giveEquipment(recipe, ii, weapons[i]); + } + + giveItem(recipe, 2000002, 100, MapleInventoryType.USE); + giveItem(recipe, 2000003, 100, MapleInventoryType.USE); + giveItem(recipe, 3010000, 1, MapleInventoryType.SETUP); + + return recipe; + } + + private static void giveEquipment(CharacterFactoryRecipe recipe, MapleItemInformationProvider ii, int equipid) { + Item nEquip = ii.getEquipById(equipid); + recipe.addStartingEquipment(nEquip); + } + + private static void giveItem(CharacterFactoryRecipe recipe, int itemid, int quantity, MapleInventoryType itemType) { + recipe.addStartingItem(itemid, quantity, itemType); + } + + public static int createCharacter(MapleClient c, String name, int face, int hair, int skin, int gender, int improveSp) { + return createNewCharacter(c, name, face, hair, skin, gender, createRecipe(MapleJob.BOWMAN, 30, 100000000, equips[gender], equips[2 + gender], equips[4], weapons[0])); + } +} diff --git a/src/client/creator/veteran/MagicianCreator.java b/src/client/creator/veteran/MagicianCreator.java new file mode 100644 index 0000000000..36aa3ac4f8 --- /dev/null +++ b/src/client/creator/veteran/MagicianCreator.java @@ -0,0 +1,98 @@ +/* + 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 client.creator.veteran; + +import client.MapleClient; +import client.MapleJob; +import client.Skill; +import client.SkillFactory; +import client.creator.CharacterFactory; +import client.creator.CharacterFactoryRecipe; +import client.inventory.Item; +import client.inventory.MapleInventoryType; +import constants.skills.Magician; +import server.MapleItemInformationProvider; + +/** + * + * @author RonanLana + */ +public class MagicianCreator extends CharacterFactory { + private static int[] equips = {0, 1041041, 0, 1061034, 1072075}; + private static int[] weapons = {1372003, 1382017}; + private static int[] startingHpMp = {405, 729}; + private static int[] mpGain = {0, 40, 80, 118, 156, 194, 230, 266, 302, 336, 370}; + + private static CharacterFactoryRecipe createRecipe(MapleJob job, int level, int map, int top, int bottom, int shoes, int weapon, int gender, int improveSp) { + CharacterFactoryRecipe recipe = new CharacterFactoryRecipe(job, level, map, top, bottom, shoes, weapon); + MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); + + recipe.setInt(20); + recipe.setRemainingAp(138); + recipe.setRemainingSp(67); + + recipe.setMaxHp(startingHpMp[0]); + recipe.setMaxMp(startingHpMp[1] + mpGain[improveSp]); + + recipe.setMeso(100000); + + if(gender == 0) { + giveEquipment(recipe, ii, 1050003); + } + + for(int i = 1; i < weapons.length; i++) { + giveEquipment(recipe, ii, weapons[i]); + } + + giveItem(recipe, 2000001, 100, MapleInventoryType.USE); + giveItem(recipe, 2000006, 100, MapleInventoryType.USE); + giveItem(recipe, 3010000, 1, MapleInventoryType.SETUP); + + if(improveSp > 0) { + improveSp += 5; + recipe.setRemainingSp(recipe.getRemainingSp() - improveSp); + + int toUseSp = 5; + Skill improveMpRec = SkillFactory.getSkill(Magician.IMPROVED_MP_RECOVERY); + recipe.addStartingSkillLevel(improveMpRec, toUseSp); + improveSp -= toUseSp; + + if(improveSp > 0) { + Skill improveMaxMp = SkillFactory.getSkill(Magician.IMPROVED_MAX_MP_INCREASE); + recipe.addStartingSkillLevel(improveMaxMp, improveSp); + } + } + + return recipe; + } + + private static void giveEquipment(CharacterFactoryRecipe recipe, MapleItemInformationProvider ii, int equipid) { + Item nEquip = ii.getEquipById(equipid); + recipe.addStartingEquipment(nEquip); + } + + private static void giveItem(CharacterFactoryRecipe recipe, int itemid, int quantity, MapleInventoryType itemType) { + recipe.addStartingItem(itemid, quantity, itemType); + } + + public static int createCharacter(MapleClient c, String name, int face, int hair, int skin, int gender, int improveSp) { + return createNewCharacter(c, name, face, hair, skin, gender, createRecipe(MapleJob.MAGICIAN, 30, 101000000, equips[gender], equips[2 + gender], equips[4], weapons[0], gender, improveSp)); + } +} diff --git a/src/client/creator/veteran/PirateCreator.java b/src/client/creator/veteran/PirateCreator.java new file mode 100644 index 0000000000..96cfc4ee94 --- /dev/null +++ b/src/client/creator/veteran/PirateCreator.java @@ -0,0 +1,79 @@ +/* + 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 client.creator.veteran; + +import client.MapleClient; +import client.MapleJob; +import client.creator.CharacterFactory; +import client.creator.CharacterFactoryRecipe; +import client.inventory.Item; +import client.inventory.MapleInventoryType; +import server.MapleItemInformationProvider; + +/** + * + * @author RonanLana + */ +public class PirateCreator extends CharacterFactory { + private static int[] equips = {0, 0, 0, 0, 1072294}; + private static int[] weapons = {1482004, 1492004}; + private static int[] startingHpMp = {846, 503}; + + private static CharacterFactoryRecipe createRecipe(MapleJob job, int level, int map, int top, int bottom, int shoes, int weapon) { + CharacterFactoryRecipe recipe = new CharacterFactoryRecipe(job, level, map, top, bottom, shoes, weapon); + MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); + + recipe.setDex(20); + recipe.setRemainingAp(138); + recipe.setRemainingSp(61); + + recipe.setMaxHp(startingHpMp[0]); + recipe.setMaxMp(startingHpMp[1]); + + recipe.setMeso(100000); + + giveEquipment(recipe, ii, 1052107); + + for(int i = 1; i < weapons.length; i++) { + giveEquipment(recipe, ii, weapons[i]); + } + + giveItem(recipe, 2330000, 800, MapleInventoryType.USE); + + giveItem(recipe, 2000002, 100, MapleInventoryType.USE); + giveItem(recipe, 2000003, 100, MapleInventoryType.USE); + giveItem(recipe, 3010000, 1, MapleInventoryType.SETUP); + + return recipe; + } + + private static void giveEquipment(CharacterFactoryRecipe recipe, MapleItemInformationProvider ii, int equipid) { + Item nEquip = ii.getEquipById(equipid); + recipe.addStartingEquipment(nEquip); + } + + private static void giveItem(CharacterFactoryRecipe recipe, int itemid, int quantity, MapleInventoryType itemType) { + recipe.addStartingItem(itemid, quantity, itemType); + } + + public static int createCharacter(MapleClient c, String name, int face, int hair, int skin, int gender, int improveSp) { + return createNewCharacter(c, name, face, hair, skin, gender, createRecipe(MapleJob.PIRATE, 30, 120000000, equips[gender], equips[2 + gender], equips[4], weapons[0])); + } +} diff --git a/src/client/creator/veteran/ThiefCreator.java b/src/client/creator/veteran/ThiefCreator.java new file mode 100644 index 0000000000..d6dc679d8c --- /dev/null +++ b/src/client/creator/veteran/ThiefCreator.java @@ -0,0 +1,77 @@ +/* + 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 client.creator.veteran; + +import client.MapleClient; +import client.MapleJob; +import client.creator.CharacterFactory; +import client.creator.CharacterFactoryRecipe; +import client.inventory.Item; +import client.inventory.MapleInventoryType; +import server.MapleItemInformationProvider; + +/** + * + * @author RonanLana + */ +public class ThiefCreator extends CharacterFactory { + private static int[] equips = {1040057, 1041047, 1060043, 1061043, 1072032}; + private static int[] weapons = {1472008, 1332012}; + private static int[] startingHpMp = {794, 407}; + + private static CharacterFactoryRecipe createRecipe(MapleJob job, int level, int map, int top, int bottom, int shoes, int weapon) { + CharacterFactoryRecipe recipe = new CharacterFactoryRecipe(job, level, map, top, bottom, shoes, weapon); + MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); + + recipe.setDex(25); + recipe.setRemainingAp(133); + recipe.setRemainingSp(61); + + recipe.setMaxHp(startingHpMp[0]); + recipe.setMaxMp(startingHpMp[1]); + + recipe.setMeso(100000); + + for(int i = 1; i < weapons.length; i++) { + giveEquipment(recipe, ii, weapons[i]); + } + + giveItem(recipe, 2070000, 500, MapleInventoryType.USE); + + giveItem(recipe, 2000002, 100, MapleInventoryType.USE); + giveItem(recipe, 2000003, 100, MapleInventoryType.USE); + giveItem(recipe, 3010000, 1, MapleInventoryType.SETUP); + + return recipe; + } + + private static void giveEquipment(CharacterFactoryRecipe recipe, MapleItemInformationProvider ii, int equipid) { + Item nEquip = ii.getEquipById(equipid); + recipe.addStartingEquipment(nEquip); + } + + private static void giveItem(CharacterFactoryRecipe recipe, int itemid, int quantity, MapleInventoryType itemType) { + recipe.addStartingItem(itemid, quantity, itemType); + } + + public static int createCharacter(MapleClient c, String name, int face, int hair, int skin, int gender, int improveSp) { + return createNewCharacter(c, name, face, hair, skin, gender, createRecipe(MapleJob.THIEF, 30, 103000000, equips[gender], equips[2 + gender], equips[4], weapons[0])); + } +} diff --git a/src/client/creator/veteran/WarriorCreator.java b/src/client/creator/veteran/WarriorCreator.java new file mode 100644 index 0000000000..41335a192a --- /dev/null +++ b/src/client/creator/veteran/WarriorCreator.java @@ -0,0 +1,98 @@ +/* + 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 client.creator.veteran; + +import client.MapleClient; +import client.MapleJob; +import client.Skill; +import client.SkillFactory; +import client.creator.CharacterFactory; +import client.creator.CharacterFactoryRecipe; +import client.inventory.Item; +import client.inventory.MapleInventoryType; +import constants.skills.Warrior; +import server.MapleItemInformationProvider; + +/** + * + * @author RonanLana + */ +public class WarriorCreator extends CharacterFactory { + private static int[] equips = {1040021, 0, 1060016, 0, 1072039}; + private static int[] weapons = {1302008, 1442001, 1422001, 1312005}; + private static int[] startingHpMp = {905, 208}; + private static int[] hpGain = {0, 72, 144, 212, 280, 348, 412, 476, 540, 600, 660}; + + private static CharacterFactoryRecipe createRecipe(MapleJob job, int level, int map, int top, int bottom, int shoes, int weapon, int gender, int improveSp) { + CharacterFactoryRecipe recipe = new CharacterFactoryRecipe(job, level, map, top, bottom, shoes, weapon); + MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); + + recipe.setStr(35); + recipe.setRemainingAp(123); + recipe.setRemainingSp(61); + + recipe.setMaxHp(startingHpMp[0] + hpGain[improveSp]); + recipe.setMaxMp(startingHpMp[1]); + + recipe.setMeso(100000); + + if(gender == 1) { + giveEquipment(recipe, ii, 1051010); + } + + for(int i = 1; i < weapons.length; i++) { + giveEquipment(recipe, ii, weapons[i]); + } + + if(improveSp > 0) { + improveSp += 5; + recipe.setRemainingSp(recipe.getRemainingSp() - improveSp); + + int toUseSp = 5; + Skill improveHpRec = SkillFactory.getSkill(Warrior.IMPROVED_HPREC); + recipe.addStartingSkillLevel(improveHpRec, toUseSp); + improveSp -= toUseSp; + + if(improveSp > 0) { + Skill improveMaxHp = SkillFactory.getSkill(Warrior.IMPROVED_MAXHP); + recipe.addStartingSkillLevel(improveMaxHp, improveSp); + } + } + + giveItem(recipe, 2000002, 100, MapleInventoryType.USE); + giveItem(recipe, 2000003, 100, MapleInventoryType.USE); + giveItem(recipe, 3010000, 1, MapleInventoryType.SETUP); + + return recipe; + } + + private static void giveEquipment(CharacterFactoryRecipe recipe, MapleItemInformationProvider ii, int equipid) { + Item nEquip = ii.getEquipById(equipid); + recipe.addStartingEquipment(nEquip); + } + + private static void giveItem(CharacterFactoryRecipe recipe, int itemid, int quantity, MapleInventoryType itemType) { + recipe.addStartingItem(itemid, quantity, itemType); + } + + public static int createCharacter(MapleClient c, String name, int face, int hair, int skin, int gender, int improveSp) { + return createNewCharacter(c, name, face, hair, skin, gender, createRecipe(MapleJob.WARRIOR, 30, 102000000, equips[gender], equips[2 + gender], equips[4], weapons[0], gender, improveSp)); + } +} diff --git a/src/client/inventory/Item.java b/src/client/inventory/Item.java index f830bf7d65..85e9f1a5ff 100644 --- a/src/client/inventory/Item.java +++ b/src/client/inventory/Item.java @@ -26,7 +26,6 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Random; -import server.MapleItemInformationProvider; public class Item implements Comparable { diff --git a/src/client/inventory/MapleInventory.java b/src/client/inventory/MapleInventory.java index 25463bacd7..38da7c02a9 100644 --- a/src/client/inventory/MapleInventory.java +++ b/src/client/inventory/MapleInventory.java @@ -39,7 +39,7 @@ import client.MapleCharacter; import client.MapleClient; import constants.ItemConstants; import server.MapleItemInformationProvider; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import tools.FilePrinter; import tools.locks.MonitoredLockType; diff --git a/src/client/inventory/MaplePet.java b/src/client/inventory/MaplePet.java index 66f2c2ffda..f4ee10fe67 100644 --- a/src/client/inventory/MaplePet.java +++ b/src/client/inventory/MaplePet.java @@ -209,14 +209,12 @@ public class MaplePet extends Item { enjoyed = true; } else { - if (incCloseness > 0) { - int newCloseness = closeness - 1; - if (newCloseness < 0) newCloseness = 0; - - closeness = newCloseness; - if (level > 1 && newCloseness < ExpTable.getClosenessNeededForLevel(level - 1)) { - level -= 1; - } + int newCloseness = closeness - 1; + if (newCloseness < 0) newCloseness = 0; + + closeness = newCloseness; + if (level > 1 && newCloseness < ExpTable.getClosenessNeededForLevel(level - 1)) { + level -= 1; } enjoyed = false; diff --git a/src/server/MapleInventoryManipulator.java b/src/client/inventory/manipulator/MapleInventoryManipulator.java similarity index 99% rename from src/server/MapleInventoryManipulator.java rename to src/client/inventory/manipulator/MapleInventoryManipulator.java index 14c52a6a8e..e3b3429e8d 100644 --- a/src/server/MapleInventoryManipulator.java +++ b/src/client/inventory/manipulator/MapleInventoryManipulator.java @@ -19,10 +19,9 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package server; +package client.inventory.manipulator; import client.MapleBuffStat; -import client.MapleCharacter; import client.MapleClient; import client.inventory.Equip; import client.inventory.Item; @@ -38,6 +37,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import server.MapleItemInformationProvider; import tools.FilePrinter; import tools.MaplePacketCreator; @@ -580,10 +580,7 @@ public class MapleInventoryManipulator { NewYearCardRecord.removeAllNewYearCard(false, c.getPlayer()); c.getAbstractPlayerInteraction().removeAll(4301000); } - } - - boolean weddingRing = source.getItemId() == 1112803 || source.getItemId() == 1112806 || source.getItemId() == 1112807 || source.getItemId() == 1112809; - if (weddingRing) { + } else if (ItemConstants.isWeddingRing(source.getItemId())) { c.getPlayer().getMap().disappearingItemDrop(c.getPlayer(), c.getPlayer(), target, dropPos); } else if (c.getPlayer().getMap().getEverlast()) { if (ii.isDropRestricted(target.getItemId()) || ii.isCash(target.getItemId()) || isDroppedItemRestricted(target)) { diff --git a/src/client/inventory/manipulator/MapleKarmaManipulator.java b/src/client/inventory/manipulator/MapleKarmaManipulator.java new file mode 100644 index 0000000000..dea98e1010 --- /dev/null +++ b/src/client/inventory/manipulator/MapleKarmaManipulator.java @@ -0,0 +1,64 @@ +/* + 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 client.inventory.manipulator; + +import constants.ItemConstants; +import client.inventory.Item; + +/** + * + * @author RonanLana + */ +public class MapleKarmaManipulator { + private static int getKarmaFlag(Item item) { + return item.getItemType() == 1 ? ItemConstants.KARMA_EQP : ItemConstants.KARMA_USE; + } + + public static boolean hasKarmaFlag(Item item) { + int karmaFlag = getKarmaFlag(item); + return (item.getFlag() & karmaFlag) == karmaFlag; + } + + public static boolean hasUsedKarmaFlag(Item item) { + int karmaFlag = ItemConstants.KARMA_UNTRADEABLE; + return (item.getFlag() & karmaFlag) == karmaFlag; + } + + public static void toggleKarmaFlagToUntradeable(Item item) { + int karmaFlag = getKarmaFlag(item); + int flag = item.getFlag(); + + if ((flag & karmaFlag) == karmaFlag) { + flag ^= karmaFlag; + flag |= ItemConstants.UNTRADEABLE; + flag |= ItemConstants.KARMA_UNTRADEABLE; + + item.setFlag((byte) flag); + } + } + + public static void setKarmaFlag(Item item) { + int karmaFlag = getKarmaFlag(item); + int flag = item.getFlag(); + + flag |= karmaFlag; + item.setFlag((byte) flag); + } +} diff --git a/src/client/processor/BuybackProcessor.java b/src/client/processor/BuybackProcessor.java new file mode 100644 index 0000000000..6ee8819189 --- /dev/null +++ b/src/client/processor/BuybackProcessor.java @@ -0,0 +1,104 @@ +/* + 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 client.processor; + +import client.MapleClient; +import client.MapleCharacter; +import client.MapleStat; +import java.awt.Point; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import server.maps.MapleMap; +import server.movement.AbsoluteLifeMovement; +import server.movement.LifeMovementFragment; +import tools.MaplePacketCreator; +import tools.Pair; + +/** + * + * @author RonanLana + */ +public class BuybackProcessor { + + public static void processBuyback(MapleClient c) { + MapleCharacter chr = c.getPlayer(); + boolean buyback; + + c.lockClient(); + try { + buyback = !chr.isAlive() && chr.couldBuyback(); + } finally { + c.unlockClient(); + } + + if(buyback) { + String jobString; + switch(chr.getJobStyle()) { + case WARRIOR: + jobString = "warrior"; + break; + + case MAGICIAN: + jobString = "magician"; + break; + + case BOWMAN: + jobString = "bowman"; + break; + + case THIEF: + jobString = "thief"; + break; + + case BRAWLER: + case GUNSLINGER: + jobString = "pirate"; + break; + + default: + jobString = "beginner"; + } + + chr.setStance(0); + + chr.setHp(chr.getMaxHp()); + chr.setMp(chr.getMaxMp()); + + List> hpmpupdate = new ArrayList<>(2); + hpmpupdate.add(new Pair<>(MapleStat.HP, Integer.valueOf(chr.getHp()))); + hpmpupdate.add(new Pair<>(MapleStat.MP, Integer.valueOf(chr.getMp()))); + c.announce(MaplePacketCreator.updatePlayerStats(hpmpupdate, true, chr)); + + AbsoluteLifeMovement alm = new AbsoluteLifeMovement(0, chr.getPosition(), 0, 0); + alm.setPixelsPerSecond(new Point(0, 0)); + List moveUpdate = Collections.singletonList((LifeMovementFragment) alm); + + MapleMap map = chr.getMap(); + map.broadcastMessage(chr, MaplePacketCreator.movePlayer(c.getPlayer().getId(), moveUpdate), false); + + map.broadcastMessage(MaplePacketCreator.playSound("Buyback/" + jobString)); + map.broadcastMessage(MaplePacketCreator.earnTitleMessage(chr.getName() + " just bought back into the game!")); + + chr.announce(MaplePacketCreator.showBuybackEffect()); + map.broadcastMessage(chr, MaplePacketCreator.showForeignBuybackEffect(chr.getId()), false); + } + } +} diff --git a/src/client/processor/DueyProcessor.java b/src/client/processor/DueyProcessor.java index 3babdac6ef..6db886ffd8 100644 --- a/src/client/processor/DueyProcessor.java +++ b/src/client/processor/DueyProcessor.java @@ -29,6 +29,8 @@ import client.autoban.AutobanFactory; import client.inventory.Equip; import client.inventory.Item; import client.inventory.MapleInventoryType; +import client.inventory.manipulator.MapleInventoryManipulator; +import client.inventory.manipulator.MapleKarmaManipulator; import constants.ItemConstants; import java.sql.Connection; import java.sql.PreparedStatement; @@ -39,7 +41,6 @@ import java.util.LinkedList; import java.util.List; import net.server.channel.Channel; import server.DueyPackages; -import server.MapleInventoryManipulator; import server.MapleItemInformationProvider; import tools.DatabaseConnection; import tools.FilePrinter; @@ -163,10 +164,12 @@ public class DueyProcessor { eq.setHands((short) rs.getInt("hands")); eq.setSpeed((short) rs.getInt("speed")); eq.setJump((short) rs.getInt("jump")); + eq.setFlag((byte) rs.getInt("flag")); eq.setOwner(rs.getString("owner")); dueypack = new DueyPackages(rs.getInt("PackageId"), eq); } else if (rs.getInt("type") == 2) { Item newItem = new Item(rs.getInt("itemid"), (short) 0, (short) rs.getInt("quantity")); + newItem.setFlag((byte) rs.getInt("flag")); newItem.setOwner(rs.getString("owner")); dueypack = new DueyPackages(rs.getInt("PackageId"), newItem); } else { @@ -186,13 +189,13 @@ public class DueyProcessor { ResultSet rs = null; try { con = DatabaseConnection.getConnection(); - ps = con.prepareStatement("SELECT Mesos FROM dueypackages WHERE RecieverId = ? and Checked = 1"); + ps = con.prepareStatement("SELECT Mesos FROM dueypackages WHERE ReceiverId = ? and Checked = 1"); ps.setInt(1, player.getId()); rs = ps.executeQuery(); if (rs.next()) { try { Connection con2 = DatabaseConnection.getConnection(); - pss = con2.prepareStatement("UPDATE dueypackages SET Checked = 0 where RecieverId = ?"); + pss = con2.prepareStatement("UPDATE dueypackages SET Checked = 0 where ReceiverId = ?"); pss.setInt(1, player.getId()); pss.executeUpdate(); pss.close(); @@ -225,31 +228,33 @@ public class DueyProcessor { } } - private static int getFee(int meso) { - int fee = 0; - if (meso >= 10000000) { - fee = meso / 25; + private static int getFee(long meso) { + long fee = 0; + if (meso >= 100000000) { + fee = (meso * 6) / 100; + } else if (meso >= 25000000) { + fee = (meso * 5) / 100; + } else if (meso >= 10000000) { + fee = (meso * 4) / 100; } else if (meso >= 5000000) { - fee = meso * 3 / 100; + fee = (meso * 3) / 100; } else if (meso >= 1000000) { - fee = meso / 50; + fee = (meso * 18) / 1000; } else if (meso >= 100000) { - fee = meso / 100; - } else if (meso >= 50000) { - fee = meso / 200; + fee = (meso * 8) / 1000; } - return fee; + return (int) fee; } private static void addMesoToDB(int mesos, String sName, int recipientID) { addItemToDB(null, 1, mesos, sName, recipientID); } - private static void addItemToDB(Item item, int quantity, int mesos, String sName, int recipientID) { + public static void addItemToDB(Item item, int quantity, int mesos, String sName, int recipientID) { Connection con = null; try { con = DatabaseConnection.getConnection(); - try (PreparedStatement ps = con.prepareStatement("INSERT INTO dueypackages (RecieverId, SenderName, Mesos, TimeStamp, Checked, Type) VALUES (?, ?, ?, ?, ?, ?)")) { + try (PreparedStatement ps = con.prepareStatement("INSERT INTO dueypackages (ReceiverId, SenderName, Mesos, TimeStamp, Checked, Type) VALUES (?, ?, ?, ?, ?, ?)")) { ps.setInt(1, recipientID); ps.setString(2, sName); ps.setInt(3, mesos); @@ -266,7 +271,7 @@ public class DueyProcessor { rs.next(); PreparedStatement ps2; if (item.getInventoryType().equals(MapleInventoryType.EQUIP)) { - ps2 = con.prepareStatement("INSERT INTO dueyitems (PackageId, itemid, quantity, upgradeslots, level, str, dex, `int`, luk, hp, mp, watk, matk, wdef, mdef, acc, avoid, hands, speed, jump, owner) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + ps2 = con.prepareStatement("INSERT INTO dueyitems (PackageId, itemid, quantity, upgradeslots, level, str, dex, `int`, luk, hp, mp, watk, matk, wdef, mdef, acc, avoid, hands, speed, jump, flag, owner) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); Equip eq = (Equip) item; ps2.setInt(2, eq.getItemId()); ps2.setInt(3, 1); @@ -287,12 +292,14 @@ public class DueyProcessor { ps2.setInt(18, eq.getHands()); ps2.setInt(19, eq.getSpeed()); ps2.setInt(20, eq.getJump()); - ps2.setString(21, eq.getOwner()); + ps2.setInt(21, eq.getFlag()); + ps2.setString(22, eq.getOwner()); } else { - ps2 = con.prepareStatement("INSERT INTO dueyitems (PackageId, itemid, quantity, owner) VALUES (?, ?, ?, ?)"); + ps2 = con.prepareStatement("INSERT INTO dueyitems (PackageId, itemid, quantity, flag, owner) VALUES (?, ?, ?, ?, ?)"); ps2.setInt(2, item.getItemId()); ps2.setInt(3, quantity); - ps2.setString(4, item.getOwner()); + ps2.setInt(4, item.getFlag()); + ps2.setString(5, item.getOwner()); } ps2.setInt(1, rs.getInt(1)); ps2.executeUpdate(); @@ -312,7 +319,7 @@ public class DueyProcessor { Connection con = null; try { con = DatabaseConnection.getConnection(); - try (PreparedStatement ps = con.prepareStatement("SELECT * FROM dueypackages dp LEFT JOIN dueyitems di ON dp.PackageId=di.PackageId WHERE RecieverId = ?")) { + try (PreparedStatement ps = con.prepareStatement("SELECT * FROM dueypackages dp LEFT JOIN dueyitems di ON dp.PackageId=di.PackageId WHERE ReceiverId = ?")) { ps.setInt(1, chr.getId()); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { @@ -337,13 +344,14 @@ public class DueyProcessor { c.lockClient(); try { final int fee = 5000; - if (mesos < 0 || ((long) mesos + fee + getFee(mesos)) > Integer.MAX_VALUE || (amount < 1 && mesos == 0)) { + final long sendMesos = (long) mesos + fee; + if (mesos < 0 || sendMesos > Integer.MAX_VALUE || (amount < 1 && mesos == 0)) { AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit with duey."); FilePrinter.printError(FilePrinter.EXPLOITS + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + " tried to use duey with mesos " + mesos + " and amount " + amount + "\r\n"); c.disconnect(true, false); return; } - int finalcost = mesos + fee + getFee(mesos); + int finalcost = mesos + fee; boolean send = false; if (c.getPlayer().getMeso() >= finalcost) { int accid = getAccIdFromCNAME(recipient, true); @@ -382,7 +390,8 @@ public class DueyProcessor { MapleInventoryManipulator.removeFromSlot(c, inv, itemPos, amount, true, false); } - addItemToDB(item, amount, mesos, c.getPlayer().getName(), getAccIdFromCNAME(recipient, false)); + MapleKarmaManipulator.toggleKarmaFlagToUntradeable(item); + addItemToDB(item, amount, mesos - getFee(mesos), c.getPlayer().getName(), getAccIdFromCNAME(recipient, false)); } else { if (item != null) { c.announce(MaplePacketCreator.sendDueyMSG(DueyProcessor.Actions.TOCLIENT_SEND_INCORRECT_REQUEST.getCode())); @@ -393,7 +402,7 @@ public class DueyProcessor { c.getPlayer().gainMeso(-finalcost, false); c.announce(MaplePacketCreator.sendDueyMSG(DueyProcessor.Actions.TOCLIENT_SEND_SUCCESSFULLY_SENT.getCode())); - addMesoToDB(mesos, c.getPlayer().getName(), getAccIdFromCNAME(recipient, false)); + addMesoToDB(mesos - getFee(mesos), c.getPlayer().getName(), getAccIdFromCNAME(recipient, false)); } if (rClient != null && rClient.isLoggedIn() && !rClient.getPlayer().isAwayFromWorld()) { @@ -460,14 +469,16 @@ public class DueyProcessor { } } - long gainmesos = 0; - long totalmesos = (long) dp.getMesos() + (long) c.getPlayer().getMeso(); + long gainmesos; + long totalmesos = (long) dp.getMesos() + c.getPlayer().getMeso(); - if (totalmesos < 0 || dp.getMesos() < 0) gainmesos = 0; - else { + if (totalmesos < 0 || dp.getMesos() < 0) { + gainmesos = 0; + } else { totalmesos = Math.min(totalmesos, Integer.MAX_VALUE); gainmesos = totalmesos - c.getPlayer().getMeso(); } + c.getPlayer().gainMeso((int)gainmesos, false); removeItemFromDB(packageid); diff --git a/src/client/processor/FredrickProcessor.java b/src/client/processor/FredrickProcessor.java index a30de71483..0944b39f69 100644 --- a/src/client/processor/FredrickProcessor.java +++ b/src/client/processor/FredrickProcessor.java @@ -33,7 +33,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; import server.maps.MapleHiredMerchant; import tools.DatabaseConnection; diff --git a/src/client/processor/MakerProcessor.java b/src/client/processor/MakerProcessor.java new file mode 100644 index 0000000000..ce6d414f89 --- /dev/null +++ b/src/client/processor/MakerProcessor.java @@ -0,0 +1,476 @@ +/* + 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 client.processor; + +import client.MapleClient; +import client.inventory.Equip; +import client.inventory.Item; +import client.inventory.MapleInventoryType; +import constants.ItemConstants; +import constants.ServerConstants; +import client.inventory.manipulator.MapleInventoryManipulator; +import constants.EquipType; +import constants.GameConstants; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import server.MakerItemFactory; +import server.MapleItemInformationProvider; +import tools.FilePrinter; +import tools.MaplePacketCreator; +import tools.Pair; +import tools.data.input.SeekableLittleEndianAccessor; + +/** + * + * @author Ronan + */ +public class MakerProcessor { + private static MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); + + public static void makerAction(SeekableLittleEndianAccessor slea, MapleClient c) { + c.lockClient(); + try { + int type = slea.readInt(); + int toCreate = slea.readInt(); + int toDisassemble = -1, pos = -1; + boolean makerSucceeded = true; + + MakerItemFactory.MakerItemCreateEntry recipe; + Map reagentids = new LinkedHashMap<>(); + int stimulantid = -1; + + if(type == 3) { // building monster crystal + int fromLeftover = toCreate; + toCreate = ii.getMakerCrystalFromLeftover(toCreate); + if(toCreate == -1) { + c.announce(MaplePacketCreator.serverNotice(1, ii.getName(toCreate) + " is unavailable for Monster Crystal conversion.")); + return; + } + + recipe = MakerItemFactory.generateLeftoverCrystalEntry(fromLeftover); + } else if(type == 4) { // disassembling + slea.readInt(); // 1... probably inventory type + pos = slea.readInt(); + + Item it = c.getPlayer().getInventory(MapleInventoryType.EQUIP).getItem((short) pos); + if(it != null && it.getItemId() == toCreate) { + Pair p; + + if((p = generateDisassemblyInfo(toCreate)) != null) { + recipe = MakerItemFactory.generateDisassemblyCrystalEntry(p.getLeft(), p.getRight()); + toDisassemble = toCreate; + toCreate = ii.getMakerCrystalFromEquip(toCreate); + } else { + c.announce(MaplePacketCreator.serverNotice(1, ii.getName(toCreate) + " is unavailable for Monster Crystal disassembly.")); + return; + } + } else { + c.announce(MaplePacketCreator.serverNotice(1, "An unknown error occurred when trying to apply that item for disassembly.")); + return; + } + } else { + if(ItemConstants.isEquipment(toCreate)) { // only equips uses stimulant and reagents + if(slea.readByte() != 0) { // stimulant + stimulantid = getMakerStimulant(toCreate); + if(!c.getAbstractPlayerInteraction().haveItem(stimulantid)) { + stimulantid = -1; + } + } + + int reagents = Math.min(slea.readInt(), getMakerReagentSlots(toCreate)); + for(int i = 0; i < reagents; i++) { // crystals + int reagentid = slea.readInt(); + if(ItemConstants.isMakerReagent(reagentid)) { + Short rs = reagentids.get(reagentid); + if(rs == null) { + reagentids.put(reagentid, (short) 1); + } else { + reagentids.put(reagentid, (short) (rs + 1)); + } + } + } + + List> toUpdate = new LinkedList<>(); + for(Map.Entry r : reagentids.entrySet()) { + int qty = c.getAbstractPlayerInteraction().getItemQuantity(r.getKey()); + + if(qty < r.getValue()) { + toUpdate.add(new Pair<>(r.getKey(), (short) qty)); + } + } + + // remove those not present on player inventory + if(!toUpdate.isEmpty()) { + for(Pair rp : toUpdate) { + if(rp.getRight() > 0) { + reagentids.put(rp.getLeft(), rp.getRight()); + } else { + reagentids.remove(rp.getLeft()); + } + } + } + + if(!reagentids.isEmpty()) { + if(!removeOddMakerReagents(toCreate, reagentids)) { + c.announce(MaplePacketCreator.serverNotice(1, "You can only use WATK and MATK Strengthening Gems on weapon items.")); + return; + } + } + } + + recipe = MakerItemFactory.getItemCreateEntry(toCreate, stimulantid, reagentids); + } + + short createStatus = getCreateStatus(c, recipe); + + switch(createStatus) { + case -1:// non-available for Maker itemid has been tried to forge + FilePrinter.printError(FilePrinter.EXPLOITS, "Player " + c.getPlayer().getName() + " tried to craft itemid " + toCreate + " using the Maker skill."); + c.announce(MaplePacketCreator.serverNotice(1, "The requested item could not be crafted on this operation.")); + break; + + case 1: // no items + c.announce(MaplePacketCreator.serverNotice(1, "You don't have all required items in your inventory to make " + recipe.getRewardAmount() + " " + ii.getName(toCreate) + ".")); + break; + + case 2: // no meso + c.announce(MaplePacketCreator.serverNotice(1, "You don't have enough mesos (" + GameConstants.numberWithCommas(recipe.getCost()) + ") to complete this operation.")); + break; + + case 3: // no req level + c.announce(MaplePacketCreator.serverNotice(1, "You don't have enough level to complete this operation.")); + break; + + case 4: // no req skill level + c.announce(MaplePacketCreator.serverNotice(1, "You don't have enough Maker level to complete this operation.")); + break; + + default: + if (MapleInventoryManipulator.checkSpace(c, toCreate, (short) recipe.getRewardAmount(), "")) { + for (Pair p : recipe.getReqItems()) { + c.getAbstractPlayerInteraction().gainItem(p.getLeft(), (short) -p.getRight()); + } + + if(toDisassemble != -1) { + MapleInventoryManipulator.removeFromSlot(c, MapleInventoryType.EQUIP, (short) pos, (short) 1, false); + c.announce(MaplePacketCreator.getShowItemGain(toDisassemble, (short) -1, true)); + } + + int cost = recipe.getCost(); + if(stimulantid == -1 && reagentids.isEmpty()) { + if(cost > 0) c.getPlayer().gainMeso(-cost); + + c.getPlayer().setCS(true); + c.getAbstractPlayerInteraction().gainItem(toCreate, (short) recipe.getRewardAmount()); + c.getPlayer().setCS(false); + } else { + if(stimulantid != -1) c.getAbstractPlayerInteraction().gainItem(stimulantid, (short) -1); + if(!reagentids.isEmpty()) { + for(Map.Entry r : reagentids.entrySet()) { + c.getAbstractPlayerInteraction().gainItem(r.getKey(), (short) (-1 * r.getValue())); + } + } + + if(cost > 0) c.getPlayer().gainMeso(-cost); + makerSucceeded = addBoostedMakerItem(c, toCreate, stimulantid, reagentids); + } + + if(makerSucceeded) c.announce(MaplePacketCreator.serverNotice(1, "You have successfully created " + recipe.getRewardAmount() + " " + ii.getName(toCreate) + ".")); + else c.getPlayer().dropMessage(5, "The Maker skill lights up, but the skill winds up as if nothing happened."); + + c.announce(MaplePacketCreator.showMakerEffect(makerSucceeded)); + c.getPlayer().getMap().broadcastMessage(c.getPlayer(), MaplePacketCreator.showForeignMakerEffect(c.getPlayer().getId(), makerSucceeded), false); + + if(toCreate == 4260003 && c.getPlayer().getQuestStatus(6033) == 1) { + c.getAbstractPlayerInteraction().setQuestProgress(6033, 1); + } + } else { + c.announce(MaplePacketCreator.serverNotice(1, "Your inventory is full.")); + } + } + } finally { + c.unlockClient(); + } + } + + // checks and prevents hackers from PE'ing Maker operations with invalid operations + private static boolean removeOddMakerReagents(int toCreate, Map reagentids) { + Map reagentType = new LinkedHashMap<>(); + List toRemove = new LinkedList<>(); + + boolean isWeapon = ItemConstants.isWeapon(toCreate) || ServerConstants.USE_MAKER_PERMISSIVE_ATKUP; + + for(Map.Entry r : reagentids.entrySet()) { + int curRid = r.getKey(); + int type = r.getKey() / 100; + + if(type < 42502 && !isWeapon) { // only weapons should gain w.att/m.att from these. + return false; //toRemove.add(curRid); + } else { + Integer tableRid = reagentType.get(type); + + if(tableRid != null) { + if(tableRid < curRid) { + toRemove.add(tableRid); + reagentType.put(type, curRid); + } else { + toRemove.add(curRid); + } + } else { + reagentType.put(type, curRid); + } + } + } + + // removing less effective gems of repeated type + for(Integer i : toRemove) { + reagentids.remove(i); + } + + // only quantity 1 of each gem will be accepted by the Maker skill + for(Integer i : reagentids.keySet()) { + reagentids.put(i, (short) 1); + } + + return true; + } + + private static int getMakerReagentSlots(int itemId) { + try { + int eqpLevel = ii.getEquipStats(itemId).get("reqLevel"); + + if(eqpLevel < 78) { + return 1; + } else if(eqpLevel >= 78 && eqpLevel < 108) { + return 2; + } else { + return 3; + } + } catch(NullPointerException npe) { + return 0; + } + } + + private static int getMakerStimulant(int itemId) { + EquipType et = EquipType.getEquipTypeById(itemId); + + switch(et) { + case GLOVES: + return 4130000; + + case SHOES: + return 4130001; + + case SWORD: + return 4130002; + + case AXE: + return 4130003; + + case MACE: + return 4130004; + + case SWORD_2H: + return 4130005; + + case AXE_2H: + return 4130006; + + case MACE_2H: + return 4130007; + + case SPEAR: + return 4130008; + + case POLEARM: + return 4130009; + + case WAND: + return 4130010; + + case STAFF: + return 4130011; + + case BOW: + return 4130012; + + case CROSSBOW: + return 4130013; + + case DAGGER: + return 4130014; + + case CLAW: + return 4130015; + + case KNUCKLER: + return 4130016; + + case PISTOL: + return 4130017; + + case CAP: + return 4130018; + + case COAT: + return 4130019; + + case PANTS: + return 4130020; + + case LONGCOAT: + return 4130021; + + case SHIELD: + return 4130022; + + default: + return -1; + } + } + + private static Pair generateDisassemblyInfo(int itemId) { + int recvFee = ii.getMakerDisassembledFee(itemId); + if(recvFee > -1) { + int recvQty = ii.getMakerDisassembledQuantity(itemId); + if(recvQty > 0) { + return new Pair<>(recvFee, recvQty); + } + } + + return null; + } + + private static short getCreateStatus(MapleClient c, MakerItemFactory.MakerItemCreateEntry recipe) { + if(recipe == null) { + return -1; + } + + if(!hasItems(c, recipe)) { + return 1; + } + + if(c.getPlayer().getMeso() < recipe.getCost()) { + return 2; + } + + if(c.getPlayer().getLevel() < recipe.getReqLevel()) { + return 3; + } + + if(c.getPlayer().getSkillLevel((c.getPlayer().getJob().getId() / 1000) * 10000000 + 1007) < recipe.getReqSkillLevel()) { + return 4; + } + + return 0; + } + + private static boolean hasItems(MapleClient c, MakerItemFactory.MakerItemCreateEntry recipe) { + for (Pair p : recipe.getReqItems()) { + int itemId = p.getLeft(); + if (c.getPlayer().getInventory(ItemConstants.getInventoryType(itemId)).countById(itemId) < p.getRight()) { + return false; + } + } + return true; + } + + private static boolean addBoostedMakerItem(MapleClient c, int itemid, int stimulantid, Map reagentids) { + if(stimulantid != -1 && !ii.rollSuccessChance(90.0)) { + return false; + } + + Item item = ii.getEquipById(itemid); + if(item == null) return false; + + Equip eqp = (Equip)item; + if(ItemConstants.isAccessory(item.getItemId()) && eqp.getUpgradeSlots() <= 0) eqp.setUpgradeSlots(3); + + if(ServerConstants.USE_ENHANCED_CRAFTING == true) { + if(!(c.getPlayer().isGM() && ServerConstants.USE_PERFECT_GM_SCROLL)) { + eqp.setUpgradeSlots((byte)(eqp.getUpgradeSlots() + 1)); + } + item = MapleItemInformationProvider.getInstance().scrollEquipWithId(eqp, 2049100, true, 2049100, c.getPlayer().isGM()); + } + + if(!reagentids.isEmpty()) { + Map stats = new LinkedHashMap<>(); + List randOption = new LinkedList<>(); + List randStat = new LinkedList<>(); + + for(Map.Entry r : reagentids.entrySet()) { + Pair reagentBuff = ii.getMakerReagentStatUpgrade(r.getKey()); + + if(reagentBuff != null) { + String s = reagentBuff.getLeft(); + + if(s.substring(0, 4).contains("rand")) { + if(s.substring(4).equals("Stat")) { + randStat.add((short) (reagentBuff.getRight() * r.getValue())); + } else { + randOption.add((short) (reagentBuff.getRight() * r.getValue())); + } + } else { + String stat = s.substring(3); + + if(!stat.equals("ReqLevel")) { // improve req level... really? + switch (stat) { + case "MaxHP": + stat = "MHP"; + break; + + case "MaxMP": + stat = "MMP"; + break; + } + + Integer d = stats.get(stat); + if(d == null) { + stats.put(stat, reagentBuff.getRight() * r.getValue()); + } else { + stats.put(stat, d + (reagentBuff.getRight() * r.getValue())); + } + } + } + } + } + + ii.improveEquipStats(eqp, stats); + + for(Short sh : randStat) { + ii.scrollOptionEquipWithChaos(eqp, sh, false); + } + + for(Short sh : randOption) { + ii.scrollOptionEquipWithChaos(eqp, sh, true); + } + } + + if(stimulantid != -1) { + eqp = ii.randomizeUpgradeStats(eqp); + } + + MapleInventoryManipulator.addFromDrop(c, item, false, -1); + c.announce(MaplePacketCreator.getShowItemGain(itemid, (short) 1, true)); + return true; + } +} diff --git a/src/client/processor/StorageProcessor.java b/src/client/processor/StorageProcessor.java new file mode 100644 index 0000000000..38e847505a --- /dev/null +++ b/src/client/processor/StorageProcessor.java @@ -0,0 +1,173 @@ +/* + This file is part of the OdinMS Maple Story Server + Copyright (C) 2008 Patrick Huy + Matthias Butz + Jan Christian Meyer + + 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 client.processor; + +import client.MapleClient; +import client.MapleCharacter; +import client.autoban.AutobanFactory; +import client.inventory.Item; +import client.inventory.MapleInventory; +import client.inventory.MapleInventoryType; +import client.inventory.manipulator.MapleKarmaManipulator; +import constants.ItemConstants; +import constants.ServerConstants; +import client.inventory.manipulator.MapleInventoryManipulator; +import server.MapleItemInformationProvider; +import server.MapleStorage; +import tools.FilePrinter; +import tools.MaplePacketCreator; +import tools.data.input.SeekableLittleEndianAccessor; + +/** + * + * @author Matze + */ +public class StorageProcessor { + + public static void storageAction(SeekableLittleEndianAccessor slea, MapleClient c) { + MapleCharacter chr = c.getPlayer(); + MapleStorage storage = chr.getStorage(); + byte mode = slea.readByte(); + + if (chr.getLevel() < 15){ + chr.dropMessage(1, "You may only use the storage once you have reached level 15."); + c.announce(MaplePacketCreator.enableActions()); + return; + } + + c.lockClient(); + try { + if (mode == 4) { // take out + byte type = slea.readByte(); + byte slot = slea.readByte(); + if (slot < 0 || slot > storage.getSlots()) { // removal starts at zero + AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit with storage."); + FilePrinter.print(FilePrinter.EXPLOITS + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + " tried to work with storage slot " + slot + "\r\n"); + c.disconnect(true, false); + return; + } + slot = storage.getSlot(MapleInventoryType.getByType(type), slot); + Item item = storage.getItem(slot); + if (item != null) { + if (MapleItemInformationProvider.getInstance().isPickupRestricted(item.getItemId()) && chr.haveItemWithId(item.getItemId(), true)) { + c.announce(MaplePacketCreator.getStorageError((byte) 0x0C)); + return; + } + if (chr.getMap().getId() == 910000000) { + if (chr.getMeso() < 1000) { + c.announce(MaplePacketCreator.getStorageError((byte) 0x0B)); + return; + } else { + chr.gainMeso(-1000, false); + } + } + if (MapleInventoryManipulator.checkSpace(c, item.getItemId(), item.getQuantity(), item.getOwner())) { + item = storage.takeOut(slot);//actually the same but idc + String itemName = MapleItemInformationProvider.getInstance().getName(item.getItemId()); + FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " took out " + item.getQuantity() + " " + itemName + " (" + item.getItemId() + ")\r\n"); + chr.setUsedStorage(); + MapleKarmaManipulator.toggleKarmaFlagToUntradeable(item); + MapleInventoryManipulator.addFromDrop(c, item, false); + storage.sendTakenOut(c, item.getInventoryType()); + } else { + c.announce(MaplePacketCreator.getStorageError((byte) 0x0A)); + } + } + } else if (mode == 5) { // store + short slot = slea.readShort(); + int itemId = slea.readInt(); + short quantity = slea.readShort(); + MapleInventoryType slotType = ItemConstants.getInventoryType(itemId); + MapleInventory Inv = chr.getInventory(slotType); + if (slot < 1 || slot > Inv.getSlotLimit()) { //player inv starts at one + AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit with storage."); + FilePrinter.print(FilePrinter.EXPLOITS + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + " tried to store item at slot " + slot + "\r\n"); + c.disconnect(true, false); + return; + } + if (quantity < 1 || chr.getItemQuantity(itemId, false) < quantity) { + c.announce(MaplePacketCreator.enableActions()); + return; + } + if (storage.isFull()) { + c.announce(MaplePacketCreator.getStorageError((byte) 0x11)); + return; + } + short meso = (short) (chr.getMap().getId() == 910000000 ? -500 : -100); + if (chr.getMeso() < meso) { + c.announce(MaplePacketCreator.getStorageError((byte) 0x0B)); + } else { + MapleInventoryType invType = ItemConstants.getInventoryType(itemId); + Item item = chr.getInventory(invType).getItem(slot).copy(); + if (item.getItemId() == itemId && (item.getQuantity() >= quantity || ItemConstants.isRechargeable(itemId))) { + if (ItemConstants.isRechargeable(itemId)) { + quantity = item.getQuantity(); + } + chr.gainMeso(meso, false, true, false); + MapleKarmaManipulator.toggleKarmaFlagToUntradeable(item); + MapleInventoryManipulator.removeFromSlot(c, invType, slot, quantity, false); + item.setQuantity(quantity); + storage.store(item); + storage.sendStored(c, ItemConstants.getInventoryType(itemId)); + String itemName = MapleItemInformationProvider.getInstance().getName(item.getItemId()); + FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " stored " + item.getQuantity() + " " + itemName + " (" + item.getItemId() + ")\r\n"); + chr.setUsedStorage(); + } + } + } else if (mode == 6) { // arrange items + if(ServerConstants.USE_STORAGE_ITEM_SORT) storage.arrangeItems(c); + c.announce(MaplePacketCreator.enableActions()); + } else if (mode == 7) { // meso + int meso = slea.readInt(); + int storageMesos = storage.getMeso(); + int playerMesos = chr.getMeso(); + if ((meso > 0 && storageMesos >= meso) || (meso < 0 && playerMesos >= -meso)) { + if (meso < 0 && (storageMesos - meso) < 0) { + meso = -2147483648 + storageMesos; + if (meso < playerMesos) { + c.announce(MaplePacketCreator.enableActions()); + return; + } + } else if (meso > 0 && (playerMesos + meso) < 0) { + meso = 2147483647 - playerMesos; + if (meso > storageMesos) { + c.announce(MaplePacketCreator.enableActions()); + return; + } + } + storage.setMeso(storageMesos - meso); + chr.gainMeso(meso, false, true, false); + FilePrinter.print(FilePrinter.STORAGE + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + (meso > 0 ? " took out " : " stored ") + Math.abs(meso) + " mesos\r\n"); + chr.setUsedStorage(); + } else { + c.announce(MaplePacketCreator.enableActions()); + return; + } + storage.sendMeso(c); + } else if (mode == 8) {// close + storage.close(); + } + } finally { + c.unlockClient(); + } + } +} diff --git a/src/constants/GameConstants.java b/src/constants/GameConstants.java index 0a3647ea0b..39741a0371 100644 --- a/src/constants/GameConstants.java +++ b/src/constants/GameConstants.java @@ -4,6 +4,9 @@ import java.util.HashMap; import java.util.Map; import client.MapleJob; import constants.skills.Aran; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; import server.maps.MapleMap; import server.maps.FieldLimit; import server.quest.MapleQuest; @@ -14,8 +17,6 @@ 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"}; - - final static Map jobNames = new HashMap<>(); public static final int[] OWL_DATA = new int[]{1082002, 2070005, 2070006, 1022047, 1102041, 2044705, 2340000, 2040017, 1092030, 2040804}; // Ronan's rates upgrade system @@ -23,15 +24,9 @@ public class GameConstants { private static final int[] MESO_RATE_GAIN = {1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105}; private static final int[] EXP_RATE_GAIN = {1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610}; //fibonacci :3 - public static boolean availableDeveloperRoom = false; - - public static void setAvailableDeveloperRoom() { - availableDeveloperRoom = true; - } - - public static boolean canEnterDeveloperRoom() { - return availableDeveloperRoom; - } + private final static Map jobNames = new HashMap<>(); + private final static NumberFormat nfFormatter = new DecimalFormat("#,###,###,###"); + private final static NumberFormat nfParser = NumberFormat.getInstance(ServerConstants.USE_UNITPRICE_WITH_COMMA ? Locale.FRANCE : Locale.UK); public static int getPlayerBonusDropRate(int slot) { return(DROP_RATE_GAIN[slot]); @@ -117,6 +112,20 @@ public class GameConstants { } } + public static boolean isPodiumHallOfFameMap(int mapid) { + switch(mapid) { + case 102000004: // warrior + case 101000004: // magician + case 100000204: // bowman + case 103000008: // thief + case 120000105: // pirate + return true; + + default: + return false; + } + } + public static byte getHallOfFameBranch(MapleJob job, int mapid) { if(!isHallOfFameMap(mapid)) { return (byte) (26 + 4 * (mapid / 100000000)); // custom, 400 pnpcs available per continent @@ -396,4 +405,21 @@ public class GameConstants { return i + sufixes[i % 10]; } } + + public synchronized static String numberWithCommas(int i) { + if(!ServerConstants.USE_DISPLAY_NUMBERS_WITH_COMMA) { + return nfFormatter.format(i); // will display number on whatever locale is currently assigned on NumberFormat + } else { + return NumberFormat.getNumberInstance(Locale.UK).format(i); + } + } + + public synchronized static Number parseNumber(String value) { + try { + return nfParser.parse(value); + } catch(Exception e) { + e.printStackTrace(); + return 0.0f; + } + } } diff --git a/src/constants/ItemConstants.java b/src/constants/ItemConstants.java index f965f73600..d03b3b2e11 100644 --- a/src/constants/ItemConstants.java +++ b/src/constants/ItemConstants.java @@ -37,9 +37,11 @@ public final class ItemConstants { public final static int LOCK = 0x01; public final static int SPIKES = 0x02; + public final static int KARMA_USE = 0x02; public final static int COLD = 0x04; public final static int UNTRADEABLE = 0x08; - public final static int KARMA = 0x10; + public final static int KARMA_EQP = 0x10; + public final static int KARMA_UNTRADEABLE = 0x20; // let 0x20 until it's proven something uses this public final static int PET_COME = 0x80; public final static int ACCOUNT_SHARING = 0x100; @@ -190,6 +192,11 @@ public final class ItemConstants { int itemType = itemId / 10000; return itemType == 503 || itemType == 514; } + + public static boolean isMapleLife(int itemId) { + int itemType = itemId / 10000; + return itemType == 543 && itemId != 5430000; + } public static boolean isWeapon(int itemId) { return itemId >= 1302000 && itemId < 1492024; @@ -198,8 +205,16 @@ public final class ItemConstants { public static boolean isEquipment(int itemId) { return itemId < 2000000 && itemId != 0; } - + public static boolean isMedal(int itemId) { return itemId >= 1140000 && itemId < 1143000; } + + public static boolean isWeddingRing(int itemId) { + return itemId >= 1112803 && itemId <= 1112809; + } + + public static boolean isWeddingToken(int itemId) { + return itemId >= 4031357 && itemId <= 4031364; + } } diff --git a/src/constants/ServerConstants.java b/src/constants/ServerConstants.java index a6ffe16648..16d0e80aa4 100644 --- a/src/constants/ServerConstants.java +++ b/src/constants/ServerConstants.java @@ -5,7 +5,7 @@ import java.util.Properties; public class ServerConstants { //Thread Tracker Configuration - public static final boolean USE_THREAD_TRACKER = true; //[SEVERE] This deadlock auditing thing will bloat the memory as fast as the time frame one takes to lose track of a raindrop on a tempesting day. Only for debugging purposes. + public static final boolean USE_THREAD_TRACKER = false; //[SEVERE] This deadlock auditing thing will bloat the memory as fast as the time frame one takes to lose track of a raindrop on a tempesting day. Only for debugging purposes. //Database Configuration public static String DB_URL = ""; @@ -20,12 +20,12 @@ public class ServerConstants { public static final int CHANNEL_LOAD = 100; //Max players per channel (limit actually used to calculate the World server capacity). public static final long RESPAWN_INTERVAL = 10 * 1000; //10 seconds, 10000. - public static final long PURGING_INTERVAL = 5 * 60 * 1000; + public static final long PURGING_INTERVAL = 5 * 60 * 1000; public static final long RANKING_INTERVAL = 60 * 60 * 1000; //60 minutes, 3600000. public static final long COUPON_INTERVAL = 60 * 60 * 1000; //60 minutes, 3600000. - public static final boolean ENABLE_PIC = false; //Pick true/false to enable or disable Pic. Delete character needs this feature ENABLED. - public static final boolean ENABLE_PIN = false; //Pick true/false to enable or disable Pin. + public static final boolean ENABLE_PIC = true; //Pick true/false to enable or disable Pic. Delete character needs this feature ENABLED. + public static final boolean ENABLE_PIN = true; //Pick true/false to enable or disable Pin. 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. @@ -38,13 +38,16 @@ public class ServerConstants { public static boolean SHUTDOWNHOOK; //Server Flags - public static final boolean USE_CUSTOM_KEYSET = true; //Enables auto-setup of the HeavenMS's custom keybindings when creating characters. - public static final boolean USE_MAXRANGE_ECHO_OF_HERO = true; - public static final boolean USE_MAXRANGE = true; //Will send and receive packets from all events on a map, rather than those of only view range. + public static final boolean USE_CUSTOM_KEYSET = false; //Enables auto-setup of the HeavenMS's custom keybindings when creating characters. public static final boolean USE_DEBUG = false; //Will enable some text prints on the client, oriented for debugging purposes. public static final boolean USE_DEBUG_SHOW_RCVD_PACKET = false; //Prints on the cmd all received packet ids. public static final boolean USE_DEBUG_SHOW_INFO_EQPEXP = false; //Prints on the cmd all equip exp gain info. + + public static final boolean USE_MAXRANGE_ECHO_OF_HERO = true; + public static final boolean USE_MAXRANGE = true; //Will send and receive packets from all events on a map, rather than those of only view range. public static final boolean USE_MTS = false; + public static final boolean USE_AUTOHIDE_GM = true; //When enabled, GMs are automatically hidden when joining. Thanks to Steven Deblois (steven1152). + public static final boolean USE_BUYBACK_SYSTEM = false; //Enables the HeavenMS-builtin buyback system, to be used by dead players when clicking the MTS button. public static final boolean USE_FAMILY_SYSTEM = false; public static final boolean USE_DUEY = true; public static final boolean USE_RANDOMIZE_HPMP_GAIN = true; //Enables randomizing on MaxHP/MaxMP gains and INT accounting for the MaxMP gain. @@ -52,12 +55,13 @@ public class ServerConstants { public static final boolean USE_ITEM_SORT = true; //Enables inventory "Item Sort/Merge" feature. public static final boolean USE_ITEM_SORT_BY_NAME = false; //Item sorting based on name rather than id. public static final boolean USE_PARTY_SEARCH = false; - public static final boolean USE_PARTY_FOR_STARTERS = true; //Players level 10 or below can create/invite other players on the given level range. + public static final boolean USE_PARTY_FOR_STARTERS = false; //Players level 10 or below can create/invite other players on the given level range. + public static final boolean USE_AUTOASSIGN_STARTERS_AP = true; //Beginners level 10 or below have their AP autoassigned (they can't choose to levelup a stat). Set true if the localhost doesn't support AP assigning for beginners level 10 or below. public static final boolean USE_AUTOBAN = false; //Commands the server to detect infractors automatically. public static final boolean USE_AUTOSAVE = true; //Enables server autosaving feature (saves characters to DB each 1 hour). - public static final boolean USE_SERVER_AUTOASSIGNER = true; //HeavenMS-builtin autoassigner, uses algorithm based on distributing AP accordingly with required secondary stat on equipments. + public static final boolean USE_SERVER_AUTOASSIGNER = false; //HeavenMS-builtin autoassigner, uses algorithm based on distributing AP accordingly with required secondary stat on equipments. public static final boolean USE_REFRESH_RANK_MOVE = true; - public static final boolean USE_ENFORCE_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_HPMP_SWAP = true; //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. @@ -67,32 +71,38 @@ 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_MAKER_FEE_HEURISTICS = true; //Apply compiled values for stimulants and reagents into the Maker fee calculations (max error revolves around 50k mesos). Set false to use basic constant values instead (results are never higher than requested by the client-side). 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 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_BANISHABLE_TOWN_SCROLL = false; //Enables town scrolls to act as if it's a "player banish", rendering the antibanish scroll effect available. + 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. //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. public static final boolean USE_ANNOUNCE_CHANGEJOB = false; //Automatic message sent to acquantainces when changing jobs. + + //Maker Configuration + public static final boolean USE_MAKER_PERMISSIVE_ATKUP = false; //Allows players to use attack-based strengthening gems on non-weapon items. + public static final boolean USE_MAKER_FEE_HEURISTICS = true; //Apply compiled values for stimulants and reagents into the Maker fee calculations (max error revolves around 50k mesos). Set false to use basic constant values instead (results are never higher than requested by the client-side). + + //Commands Configuration + public static final boolean BLOCK_GENERATE_CASH_ITEM = false; //Prevents creation of cash items with the item/drop command. //Server Rates And Experience - public static final int EXP_RATE = 10; - public static final int MESO_RATE = 10; - public static final int DROP_RATE = 10; - public static final int QUEST_RATE = 5; //Multiplier for Exp & Meso gains when completing a quest. Only available when USE_QUEST_RATE is true. Stacks with server Exp & Meso rates. - public static final double EQUIP_EXP_RATE = 1.0; //Rate for equipment exp gain, grows linearly. Set 1.0 for default (about 100~200 same-level range mobs killed to pass equip from level 1 to 2). + public static final int EXP_RATE = 1; + public static final int MESO_RATE = 1; + public static final int DROP_RATE = 1; + public static final int TRAVEL_RATE = 1; //Means of transportation rides/departs using 1/N of the default time. + public static final int QUEST_RATE = 1; //Multiplier for Exp & Meso gains when completing a quest. Only available when USE_QUEST_RATE is true. Stacks with server Exp & Meso rates. + public static final double EQUIP_EXP_RATE = 1.0; //Rate for equipment exp gain, grows linearly. Set 1.0 for default (about 100~200 same-level range mobs killed to pass equip from level 1 to 2). public static final double PARTY_BONUS_EXP_RATE = 1.0; //Rate for the party exp reward. public static final double PQ_BONUS_EXP_RATE = 0.5; //Rate for the PQ exp reward. public static final int PARTY_EXPERIENCE_MOD = 1; //Change for event stuff. - //Commands Configuration - public static final boolean BLOCK_GENERATE_CASH_ITEM = false; //Prevents creation of cash items with the item/drop command. - //Miscellaneous Configuration public static String TIMEZONE = "-GMT3"; + public static boolean USE_DISPLAY_NUMBERS_WITH_COMMA = true; //Enforce comma on displayed strings (use this when USE_UNITPRICE_WITH_COMMA is active and you still want to display comma-separated thousands). public static boolean USE_UNITPRICE_WITH_COMMA = true; //Set this accordingly with the layout of the unitPrices on Item.wz XML's, whether it's using commas or dots to represent fractions. public static final byte MIN_UNDERLEVEL_TO_EXP_GAIN = 5; //Characters are unable to get EXP from a mob if their level are under this threshold, only if "USE_ENFORCE_MOB_LEVEL_RANGE" is enabled. For bosses, this attribute is doubled. public static final byte MAX_MONITORED_BUFFSTATS = 5; //Limits accounting for "dormant" buff effects, that should take place when stronger stat buffs expires. @@ -101,45 +111,50 @@ public class ServerConstants { public static final long BLOCK_NPC_RACE_CONDT = (long)(0.5 * 1000); //Time the player client must wait before reopening a conversation with an NPC. public static final long PET_LOOT_UPON_ATTACK = (long)(0.7 * 1000); //Time the pet must wait before trying to pick items up. public static final int TOT_MOB_QUEST_REQUIREMENT = 0; //Overwrites old 999-mobs requirement for the ToT questline with new requirement value, set 0 for default. + public static final int MOB_REACTOR_REFRESH_TIME = 0; //Overwrites refresh time for those reactors oriented to inflict damage to bosses (Ice Queen, Riche), set 0 for default. //Dangling Items/Locks Configuration public static final int ITEM_EXPIRE_TIME = 3 * 60 * 1000; //Time before items start disappearing. Recommended to be set up to 3 minutes. - public static final int KITE_EXPIRE_TIME = 60 * 60 * 1000; //Time before new year (cash item) kite disappears. + public static final int KITE_EXPIRE_TIME = 60 * 60 * 1000; //Time before kites (cash item) disappears. public static final int ITEM_MONITOR_TIME = 5 * 60 * 1000; //Interval between item monitoring tasks on maps, which checks for dangling (null) item objects on the map item history. - public static final int LOCK_MONITOR_TIME = 30 * 1000; //Waiting time for a lock to be released. If it reach timed out, a critical server deadlock has made present. + public static final int LOCK_MONITOR_TIME = 30 * 1000; //Waiting time for a lock to be released. If it reaches timeout, a critical server deadlock has made present. public static final int ITEM_EXPIRE_CHECK = 10 * 1000; //Interval between item expiring tasks on maps, which checks and makes disappear expired items. public static final int ITEM_LIMIT_ON_MAP = 200; //Max number of items allowed on a map. public static final int MAP_VISITED_SIZE = 5; //Max length for last mapids visited by a player. This is used to recover and update drops on these maps accordingly with player actions. //Some Gameplay Enhancing Configurations //Scroll Configuration - public static final boolean USE_PERFECT_GM_SCROLL = true; //Scrolls from GMs never uses up slots nor fails. - public static final boolean USE_PERFECT_SCROLLING = true; //Scrolls doesn't use slots upon failure. - public static final boolean USE_ENHANCED_CHSCROLL = true; //Equips even more powerful with chaos upgrade. - public static final boolean USE_ENHANCED_CRAFTING = true; //Apply chaos scroll on every equip crafted. + public static final boolean USE_PERFECT_GM_SCROLL = false; //Scrolls from GMs never uses up slots nor fails. + public static final boolean USE_PERFECT_SCROLLING = false; //Scrolls doesn't use slots upon failure. + public static final boolean USE_ENHANCED_CHSCROLL = false; //Equips even more powerful with chaos upgrade. + public static final boolean USE_ENHANCED_CRAFTING = false; //Apply chaos scroll on every equip crafted. public static final int SCROLL_CHANCE_RATE = 0; //Number of rolls for success on a scroll, set 0 for default. public static final int CHSCROLL_STAT_RATE = 1; //Number of rolls of stat upgrade on a successfully applied chaos scroll, set 1 for default. public static final int CHSCROLL_STAT_RANGE = 6; //Stat upgrade range (-N, N) on chaos scrolls. //Beginner Skills Configuration - public static final boolean USE_ULTRA_NIMBLE_FEET = true; //Haste-like speed & jump upgrade. - public static final boolean USE_ULTRA_RECOVERY = true; //Massive recovery amounts overtime. - public static final boolean USE_ULTRA_THREE_SNAILS = true; //Massive damage on shell toss. + public static final boolean USE_ULTRA_NIMBLE_FEET = false; //Haste-like speed & jump upgrade. + public static final boolean USE_ULTRA_RECOVERY = false; //Massive recovery amounts overtime. + public static final boolean USE_ULTRA_THREE_SNAILS = false; //Massive damage on shell toss. //Character Configuration - public static final boolean USE_ADD_SLOTS_BY_LEVEL = true; //Slots are added each 20 levels. - public static final boolean USE_ADD_RATES_BY_LEVEL = true; //Rates are added each 20 levels. - public static final boolean USE_STACK_COUPON_RATES = true; //Multiple coupons effects builds up together. - public static final boolean USE_PERFECT_PITCH = true; //For lvl 30 or above, each lvlup grants player 1 perfect pitch. - public static final int FAME_GAIN_BY_QUEST = 4; //Fame gain each N quest completes, set 0 to disable. + public static final boolean USE_ADD_SLOTS_BY_LEVEL = false; //Slots are added each 20 levels. + public static final boolean USE_ADD_RATES_BY_LEVEL = false; //Rates are added each 20 levels. + public static final boolean USE_STACK_COUPON_RATES = false; //Multiple coupons effects builds up together. + public static final boolean USE_PERFECT_PITCH = false; //For lvl 30 or above, each lvlup grants player 1 perfect pitch. + public static final int FAME_GAIN_BY_QUEST = 0; //Fame gain each N quest completes, set 0 to disable. + //Guild Configuration + public static final int CREATE_GUILD_COST = 1500000; + public static final int CHANGE_EMBLEM_COST = 5000000; + //Equipment Configuration - public static final boolean USE_EQUIPMNT_LVLUP_SLOTS = true;//Equips can upgrade slots at level up. - public static final boolean USE_EQUIPMNT_LVLUP_POWER = true;//Enable more powerful stat upgrades at equip level up. - public static final boolean USE_SPIKES_AVOID_BANISH = true; //Shoes equipped with spikes prevents mobs from banishing wearer. + public static final boolean USE_EQUIPMNT_LVLUP_SLOTS = false;//Equips can upgrade slots at level up. + public static final boolean USE_EQUIPMNT_LVLUP_POWER = false;//Enable more powerful stat upgrades at equip level up. + public static final boolean USE_SPIKES_AVOID_BANISH = false; //Shoes equipped with spikes prevents mobs from banishing wearer. public static final int MAX_EQUIPMNT_LVLUP_STAT_UP = 10000; //Max stat upgrade an equipment can have on a levelup. public static final int MAX_EQUIPMNT_STAT = 32767; //Max stat on an equipment by leveling up. - public static final int USE_EQUIPMNT_LVLUP = 7; //All equips lvlup at max level of N, set 1 to disable. + public static final int USE_EQUIPMNT_LVLUP = 1; //All equips lvlup at max level of N, set 1 to disable. //Map-Chair Configuration public static final boolean USE_CHAIR_EXTRAHEAL = true; //Enable map chairs to further recover player's HP and MP (player must have the Chair Mastery skill). @@ -151,7 +166,7 @@ public class ServerConstants { public static final int PLAYERNPC_INITIAL_Y = 262; //Map frame height for putting PlayerNPCs. public static final int PLAYERNPC_AREA_X = 320; //Initial width gap between PlayerNPCs. public static final int PLAYERNPC_AREA_Y = 160; //Initial height gap between PlayerNPCs. - public static final int PLAYERNPC_AREA_STEPS = 4; //Max number of times gap is shortened to comport PlayerNPCs + public static final int PLAYERNPC_AREA_STEPS = 4; //Max number of times gap is shortened to comport PlayerNPCs. public static final boolean PLAYERNPC_ORGANIZE_AREA = true; //Automatically rearranges PlayerNPCs on the map if there is no space set the new NPC. Current distance gap between NPCs is decreased to solve this issue. public static final boolean PLAYERNPC_AUTODEPLOY = true; //Makes PlayerNPC automatically deployed on the Hall of Fame at the instant one reaches max level. If false, eligible players must talk to 1st job instructor to deploy a NPC. @@ -173,10 +188,24 @@ public class ServerConstants { public static final long EVENT_LOBBY_DELAY = 10; //Cooldown duration in seconds before reopening an event lobby. //Dojo Configuration - public static final boolean USE_DEADLY_DOJO = false; //Should bosses really use 1HP,1MP attacks in dojo? + public static final boolean USE_DEADLY_DOJO = true; //Should bosses really use 1HP,1MP attacks in dojo? public static final int DOJO_ENERGY_ATK = 100; //Dojo energy gain when deal attack public static final int DOJO_ENERGY_DMG = 20; //Dojo energy gain when recv attack + //Wedding Configuration + public static final int WEDDING_RESERVATION_DELAY = 3; //Minimum idle slots before processing a wedding reservation. + public static final int WEDDING_RESERVATION_TIMEOUT = 10; //Limit time in minutes for the couple to show up before cancelling the wedding reservation. + public static final int WEDDING_RESERVATION_INTERVAL = 60; //Time between wedding starts in minutes. + public static final int WEDDING_BLESS_EXP = 30000; //Exp gained per bless count. + public static final boolean WEDDING_BLESSER_SHOWFX = true; //Pops bubble sprite effect on players blessing the couple. Setting this false shows the blessing effect on the couple instead. + + //Buyback Configuration + public static final boolean USE_BUYBACK_WITH_MESOS = false; //Enables usage of either mesos or NX for the buyback fee. + public static final int BUYBACK_FEE = 7770; //Sets the amount needed to buyback. + public static final int BUYBACK_MESO_MULTIPLIER = 1000; //Sets a multiplier for the fee when using meso as the charge unit. + public static final int BUYBACK_RETURN_MINUTES = 1; //Sets the maximum amount of time the player must wait before decide to buyback. + public static final int BUYBACK_COOLDOWN_MINUTES = 7; //Sets the time the player must wait before using buyback again. + //Event End Timestamp public static final long EVENT_END_TIMESTAMP = 1428897600000L; diff --git a/src/constants/skills/Warrior.java b/src/constants/skills/Warrior.java index da2bcc3624..050f8a4f15 100644 --- a/src/constants/skills/Warrior.java +++ b/src/constants/skills/Warrior.java @@ -9,5 +9,6 @@ package constants.skills; * @author Tyler */ public class Warrior { + public static final int IMPROVED_HPREC = 1000000; public static final int IMPROVED_MAXHP = 1000001; } diff --git a/src/net/MapleServerHandler.java b/src/net/MapleServerHandler.java index a8d4c17340..0217b9ef07 100644 --- a/src/net/MapleServerHandler.java +++ b/src/net/MapleServerHandler.java @@ -85,6 +85,10 @@ public class MapleServerHandler extends IoHandlerAdapter { @Override public void exceptionCaught(IoSession session, Throwable cause) throws Exception { + if (cause instanceof org.apache.mina.core.write.WriteToClosedSessionException) { + return; + } + System.out.println("disconnect by exception"); cause.printStackTrace(); diff --git a/src/net/PacketProcessor.java b/src/net/PacketProcessor.java index 431d1e73c4..8ed1a6a01a 100644 --- a/src/net/PacketProcessor.java +++ b/src/net/PacketProcessor.java @@ -100,7 +100,8 @@ public final class PacketProcessor { registerHandler(RecvOpcode.PONG, new KeepAliveHandler()); registerHandler(RecvOpcode.CUSTOM_PACKET, new CustomPacketHandler()); - if (channel < 0) {//login + if (channel < 0) { + //LOGIN HANDLERS registerHandler(RecvOpcode.ACCEPT_TOS, new AcceptToSHandler()); registerHandler(RecvOpcode.AFTER_LOGIN, new AfterLoginHandler()); registerHandler(RecvOpcode.SERVERLIST_REREQUEST, new ServerlistRequestHandler()); @@ -124,6 +125,9 @@ public final class PacketProcessor { registerHandler(RecvOpcode.VIEW_ALL_PIC_REGISTER, new ViewAllPicRegisterHandler()); } else { //CHANNEL HANDLERS + registerHandler(RecvOpcode.NAME_TRANSFER, new TransferNameHandler()); + registerHandler(RecvOpcode.CHECK_CHAR_NAME, new TransferNameResultHandler()); + registerHandler(RecvOpcode.WORLD_TRANSFER, new TransferWorldHandler()); registerHandler(RecvOpcode.CHANGE_CHANNEL, new ChangeChannelHandler()); registerHandler(RecvOpcode.STRANGE_DATA, LoginRequiringNoOpHandler.getInstance()); registerHandler(RecvOpcode.GENERAL_CHAT, new GeneralChatHandler()); @@ -249,6 +253,8 @@ public final class PacketProcessor { registerHandler(RecvOpcode.MONSTER_CARNIVAL, new MonsterCarnivalHandler()); registerHandler(RecvOpcode.REMOTE_STORE, new RemoteStoreHandler()); registerHandler(RecvOpcode.WEDDING_ACTION, new WeddingHandler()); + registerHandler(RecvOpcode.WEDDING_TALK, new WeddingTalkHandler()); + registerHandler(RecvOpcode.WEDDING_TALK_MORE, new WeddingTalkMoreHandler()); registerHandler(RecvOpcode.WATER_OF_LIFE, new UseWaterOfLifeHandler()); registerHandler(RecvOpcode.ADMIN_CHAT, new AdminChatHandler()); registerHandler(RecvOpcode.MOVE_DRAGON, new MoveDragonHandler()); diff --git a/src/net/RecvOpcode.java b/src/net/RecvOpcode.java index a0c88c0b44..1b72921b8f 100644 --- a/src/net/RecvOpcode.java +++ b/src/net/RecvOpcode.java @@ -37,6 +37,8 @@ public enum RecvOpcode { PLAYER_DC(0x0C), VIEW_ALL_CHAR(0x0D), PICK_ALL_CHAR(0x0E), + NAME_TRANSFER(0x10), + WORLD_TRANSFER(0x12), CHAR_SELECT(0x13), PLAYER_LOGGEDIN(0x14), CHECK_CHAR_NAME(0x15), @@ -114,7 +116,6 @@ public enum RecvOpcode { QUEST_ACTION(0x6B), //lolno SKILL_MACRO(0x6E), - SPOUSE_CHAT(0x6F), USE_ITEM_REWARD(0x70), MAKER_SKILL(0x71), USE_REMOTE(0x74), @@ -122,6 +123,7 @@ public enum RecvOpcode { ADMIN_CHAT(0x76), MULTI_CHAT(0x77), WHISPER(0x78), + SPOUSE_CHAT(0x79), MESSENGER(0x7A), PLAYER_INTERACTION(0x7B), PARTY_OPERATION(0x7C), @@ -137,6 +139,8 @@ public enum RecvOpcode { RPS_ACTION(0x88), RING_ACTION(0x89), WEDDING_ACTION(0x8A), + WEDDING_TALK(0x8B), + WEDDING_TALK_MORE(0x8B), ALLIANCE_OPERATION(0x8F), OPEN_FAMILY(0x92), ADD_FAMILY(0x93), @@ -187,7 +191,7 @@ public enum RecvOpcode { CLOSE_ITEMUI(0xEC), USE_ITEMUI(0xED), MTS_OPERATION(0xFD), - USE_MAPLELIFE(0xFE), + USE_MAPLELIFE(0x100), USE_HAMMER(0x104); private int code = -2; diff --git a/src/net/SendOpcode.java b/src/net/SendOpcode.java index 0ffd7d6857..049467bf0d 100644 --- a/src/net/SendOpcode.java +++ b/src/net/SendOpcode.java @@ -67,7 +67,7 @@ public enum SendOpcode { OPEN_FULL_CLIENT_DOWNLOAD_LINK(0x28), MEMO_RESULT(0x29), MAP_TRANSFER_RESULT(0x2A), - ANTI_MACRO_RESULT(0x2B), + WEDDING_PHOTO(0x2B), //ANTI_MACRO_RESULT(0x2B), CLAIM_RESULT(0x2D), CLAIM_AVAILABLE_TIME(0x2E), CLAIM_STATUS_CHANGED(0x2F), @@ -326,12 +326,11 @@ public enum SendOpcode { CHARGE_PARAM_RESULT(0x143), QUERY_CASH_RESULT(0x144), CASHSHOP_OPERATION(0x145), - - //check whether the character's name is ok.(0x146), - //this name can be used, press ok if you wish.(0x147), - //rule for character name change (needed lots of bytes)(0x148), - //rule for character transfer(0x14A), - //cannot receive gacha stamps(0x14B), + CASHSHOP_GIFT_INFO_RESULT(0x147), + CASHSHOP_CHECK_NAME_CHANGE(0x148), + CASHSHOP_CHECK_NAME_CHANGE_POSSIBLE_RESULT(0x149), + CASHSHOP_REGISTER_NEW_CHARACTER_RESULT(0x14A), + CASHSHOP_CHECK_TRANSFER_WORLD_POSSIBLE_RESULT(0x14B), CASHSHOP_GACHAPON_STAMP_RESULT(0x14C), CASHSHOP_CASH_ITEM_GACHAPON_RESULT(0x14D), CASHSHOP_CASH_GACHAPON_OPEN_RESULT(0x14E), @@ -344,6 +343,8 @@ public enum SendOpcode { ENABLE_TV(0x157), MTS_OPERATION2(0x15B), MTS_OPERATION(0x15C), + MAPLELIFE_RESULT(0x15D), + MAPLELIFE_ERROR(0x15E), VICIOUS_HAMMER(0x162), VEGA_SCROLL(0x166); private int code = -2; diff --git a/src/net/server/Server.java b/src/net/server/Server.java index 94c3d5a174..8b7a724977 100644 --- a/src/net/server/Server.java +++ b/src/net/server/Server.java @@ -104,6 +104,7 @@ public class Server { private final List processDiseaseAnnouncePlayers = new LinkedList<>(); private final List registeredDiseaseAnnouncePlayers = new LinkedList<>(); + private boolean availableDeveloperRoom = false; private boolean online = false; public static long uptime = System.currentTimeMillis(); @@ -133,6 +134,14 @@ public class Server { public NewYearCardRecord removeNewYearCard(int cardid) { return newyears.remove(cardid); } + + public void setAvailableDeveloperRoom() { + availableDeveloperRoom = true; + } + + public boolean canEnterDeveloperRoom() { + return availableDeveloperRoom; + } private void loadPlayerNpcMapStepFromDb() { try { @@ -141,8 +150,8 @@ public class Server { ResultSet rs = ps.executeQuery(); while(rs.next()) { - int world = rs.getInt("world"), map = rs.getInt("map"), step = rs.getInt("step"); - worlds.get(world).setPlayerNpcMapStep(map, step, true); + int world = rs.getInt("world"), map = rs.getInt("map"), step = rs.getInt("step"), podium = rs.getInt("podium"); + worlds.get(world).setPlayerNpcMapData(map, step, podium); } rs.close(); @@ -401,9 +410,10 @@ public class Server { worldRecommendedList.add(new Pair<>(i, p.getProperty("whyamirecommended" + i))); worlds.add(world); channels.add(new HashMap()); + long bootTime = System.currentTimeMillis(); for (int j = 0; j < Integer.parseInt(p.getProperty("channels" + i)); j++) { int channelid = j + 1; - Channel channel = new Channel(i, channelid); + Channel channel = new Channel(i, channelid, bootTime); world.addChannel(channel); channels.get(i).put(channelid, channel.getIP()); } diff --git a/src/net/server/channel/Channel.java b/src/net/server/channel/Channel.java index 6213488f75..b4c3b45499 100644 --- a/src/net/server/channel/Channel.java +++ b/src/net/server/channel/Channel.java @@ -24,6 +24,7 @@ package net.server.channel; import java.io.File; import java.net.InetSocketAddress; import java.util.ArrayList; +import java.util.LinkedList; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -41,6 +42,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import net.MapleServerHandler; import net.mina.MapleCodecFactory; +import net.server.Server; +import net.server.world.World; import net.server.PlayerStorage; import net.server.world.MapleParty; import net.server.world.MaplePartyCharacter; @@ -65,6 +68,7 @@ import server.maps.MapleMap; import server.maps.MapleMapFactory; import server.maps.MapleMiniDungeon; import tools.MaplePacketCreator; +import tools.Pair; import client.MapleCharacter; import constants.ServerConstants; import server.maps.MapleMiniDungeonInfo; @@ -92,15 +96,30 @@ public final class Channel { private Map dojoParty = new HashMap<>(); private Map dungeons = new HashMap<>(); + private List chapelReservationQueue = new LinkedList<>(); + private List cathedralReservationQueue = new LinkedList<>(); + private ScheduledFuture chapelReservationTask; + private ScheduledFuture cathedralReservationTask; + + private Integer ongoingChapel = null; + private Boolean ongoingChapelType = null; + private Set ongoingChapelGuests = null; + private Integer ongoingCathedral = null; + private Boolean ongoingCathedralType = null; + private Set ongoingCathedralGuests = null; + private long ongoingStartTime; + private ReentrantReadWriteLock merchantLock = new MonitoredReentrantReadWriteLock(MonitoredLockType.MERCHANT, true); private ReadLock merchRlock = merchantLock.readLock(); private WriteLock merchWlock = merchantLock.writeLock(); private Lock lock = new MonitoredReentrantLock(MonitoredLockType.CHANNEL, true); - public Channel(final int world, final int channel) { + public Channel(final int world, final int channel, long startTime) { this.world = world; this.channel = channel; + + this.ongoingStartTime = startTime + 10000; // rude approach to a world's last channel boot time, placeholder for the 1st wedding reservation ever this.mapFactory = new MapleMapFactory(null, MapleDataProviderFactory.getDataProvider(new File(System.getProperty("wzpath") + "/Map.wz")), MapleDataProviderFactory.getDataProvider(new File(System.getProperty("wzpath") + "/String.wz")), world, channel); try { eventSM = new EventScriptManager(this, getEvents()); @@ -528,4 +547,297 @@ public final class Channel { lock.unlock(); } } + + public Pair>> getNextWeddingReservation(boolean cathedral) { + Integer ret; + + lock.lock(); + try { + List weddingReservationQueue = (cathedral ? cathedralReservationQueue : chapelReservationQueue); + if(weddingReservationQueue.isEmpty()) return null; + + ret = weddingReservationQueue.remove(0); + if(ret == null) return null; + } finally { + lock.unlock(); + } + + World wserv = Server.getInstance().getWorld(world); + + Pair coupleId = wserv.getMarriageQueuedCouple(ret); + Pair> typeGuests = wserv.removeMarriageQueued(ret); + + Pair couple = new Pair<>(MapleCharacter.getNameById(coupleId.getLeft()), MapleCharacter.getNameById(coupleId.getRight())); + wserv.dropMessage(6, couple.getLeft() + " and " + couple.getRight() + "'s wedding is going to be started at " + (cathedral ? "Cathedral" : "Chapel") + " on Channel " + channel + "."); + + return new Pair<>(typeGuests.getLeft(), new Pair<>(ret, typeGuests.getRight())); + } + + public boolean isWeddingReserved(Integer weddingId) { + World wserv = Server.getInstance().getWorld(world); + + lock.lock(); + try { + return wserv.isMarriageQueued(weddingId) || weddingId.equals(ongoingCathedral) || weddingId.equals(ongoingChapel); + } finally { + lock.unlock(); + } + } + + public int getWeddingReservationStatus(Integer weddingId, boolean cathedral) { + if(weddingId == null) return -1; + + lock.lock(); + try { + if(cathedral) { + if(weddingId.equals(ongoingCathedral)) return 0; + + for(int i = 0; i < cathedralReservationQueue.size(); i++) { + if(weddingId.equals(cathedralReservationQueue.get(i))) { + return i + 1; + } + } + } else { + if(weddingId.equals(ongoingChapel)) return 0; + + for(int i = 0; i < chapelReservationQueue.size(); i++) { + if(weddingId.equals(chapelReservationQueue.get(i))) { + return i + 1; + } + } + } + + return -1; + } finally { + lock.unlock(); + } + } + + public int pushWeddingReservation(Integer weddingId, boolean cathedral, boolean premium, Integer groomId, Integer brideId) { + if(weddingId == null || isWeddingReserved(weddingId)) return -1; + + World wserv = Server.getInstance().getWorld(world); + wserv.putMarriageQueued(weddingId, cathedral, premium, groomId, brideId); + + lock.lock(); + try { + List weddingReservationQueue = (cathedral ? cathedralReservationQueue : chapelReservationQueue); + + int delay = ServerConstants.WEDDING_RESERVATION_DELAY - 1 - weddingReservationQueue.size(); + for(int i = 0; i < delay; i++) { + weddingReservationQueue.add(null); // push empty slots to fill the waiting time + } + + weddingReservationQueue.add(weddingId); + return weddingReservationQueue.size(); + } finally { + lock.unlock(); + } + } + + public boolean isOngoingWeddingGuest(boolean cathedral, int playerId) { + lock.lock(); + try { + if(cathedral) { + return ongoingCathedralGuests != null && ongoingCathedralGuests.contains(playerId); + } else { + return ongoingChapelGuests != null && ongoingChapelGuests.contains(playerId); + } + } finally { + lock.unlock(); + } + } + + public Integer getOngoingWedding(boolean cathedral) { + lock.lock(); + try { + return cathedral ? ongoingCathedral : ongoingChapel; + } finally { + lock.unlock(); + } + } + + public boolean getOngoingWeddingType(boolean cathedral) { + lock.lock(); + try { + return cathedral ? ongoingCathedralType : ongoingChapelType; + } finally { + lock.unlock(); + } + } + + public void closeOngoingWedding(boolean cathedral) { + lock.lock(); + try { + if(cathedral) { + ongoingCathedral = null; + ongoingCathedralType = null; + ongoingCathedralGuests = null; + } else { + ongoingChapel = null; + ongoingChapelType = null; + ongoingChapelGuests = null; + } + } finally { + lock.unlock(); + } + } + + public void setOngoingWedding(final boolean cathedral, Boolean premium, Integer weddingId, Set guests) { + lock.lock(); + try { + if(cathedral) { + ongoingCathedral = weddingId; + ongoingCathedralType = premium; + ongoingCathedralGuests = guests; + } else { + ongoingChapel = weddingId; + ongoingChapelType = premium; + ongoingChapelGuests = guests; + } + } finally { + lock.unlock(); + } + + ongoingStartTime = System.currentTimeMillis(); + if(weddingId != null) { + ScheduledFuture weddingTask = TimerManager.getInstance().schedule(new Runnable() { + @Override + public void run() { + closeOngoingWedding(cathedral); + } + }, ServerConstants.WEDDING_RESERVATION_TIMEOUT * 60 * 1000); + + if(cathedral) { + cathedralReservationTask = weddingTask; + } else { + chapelReservationTask = weddingTask; + } + } + } + + public synchronized boolean acceptOngoingWedding(final boolean cathedral) { // couple succeeded to show up and started the ceremony + if(cathedral) { + if(cathedralReservationTask == null) return false; + + cathedralReservationTask.cancel(false); + cathedralReservationTask = null; + } else { + if(chapelReservationTask == null) return false; + + chapelReservationTask.cancel(false); + chapelReservationTask = null; + } + + return true; + } + + private static String getTimeLeft(long futureTime) { + StringBuilder str = new StringBuilder(); + long leftTime = futureTime - System.currentTimeMillis(); + + if(leftTime < 0) { + return null; + } + + byte mode = 0; + if(leftTime / (60*1000) > 0) { + mode++; //counts minutes + + if(leftTime / (60*60*1000) > 0) + mode++; //counts hours + } + + switch(mode) { + case 2: + int hours = (int) ((leftTime / (1000*60*60))); + str.append(hours + " hours, "); + + case 1: + int minutes = (int) ((leftTime / (1000*60)) % 60); + str.append(minutes + " minutes, "); + + default: + int seconds = (int) (leftTime / 1000) % 60 ; + str.append(seconds + " seconds"); + } + + return str.toString(); + } + + public long getWeddingTicketExpireTime(int resSlot) { + return ongoingStartTime + getRelativeWeddingTicketExpireTime(resSlot); + } + + public static long getRelativeWeddingTicketExpireTime(int resSlot) { + return (resSlot * ServerConstants.WEDDING_RESERVATION_INTERVAL * 60 * 1000); + } + + public String getWeddingReservationTimeLeft(Integer weddingId) { + if(weddingId == null) return null; + + lock.lock(); + try { + boolean cathedral = true; + + int resStatus; + resStatus = getWeddingReservationStatus(weddingId, true); + if(resStatus < 0) { + cathedral = false; + resStatus = getWeddingReservationStatus(weddingId, false); + + if(resStatus < 0) { + return null; + } + } + + String venue = (cathedral ? "Cathedral" : "Chapel"); + if(resStatus == 0) { + return venue + " - RIGHT NOW"; + } + + return venue + " - " + getTimeLeft(ongoingStartTime + (resStatus * ServerConstants.WEDDING_RESERVATION_INTERVAL * 60 * 1000)) + " from now"; + } finally { + lock.unlock(); + } + } + + public Pair getWeddingCoupleForGuest(int guestId, boolean cathedral) { + lock.lock(); + try { + return (isOngoingWeddingGuest(cathedral, guestId)) ? Server.getInstance().getWorld(world).getRelationshipCouple(getOngoingWedding(cathedral)) : null; + } finally { + lock.unlock(); + } + } + + public void dropMessage(int type, String message) { + for (MapleCharacter player : getPlayerStorage().getAllCharacters()) { + player.dropMessage(type, message); + } + } + + public void debugMarriageStatus() { + System.out.println(" ----- WORLD DATA -----"); + Server.getInstance().getWorld(world).debugMarriageStatus(); + + System.out.println(" ----- CH. " + channel + " -----"); + System.out.println(" ----- CATHEDRAL -----"); + System.out.println("Current Queue: " + cathedralReservationQueue); + System.out.println("Cancel Task: " + (cathedralReservationTask != null)); + System.out.println("Ongoing wid: " + ongoingCathedral); + System.out.println(); + System.out.println("Ongoing wid: " + ongoingCathedral + " isPremium: " + ongoingCathedralType); + System.out.println("Guest list: " + ongoingCathedralGuests); + System.out.println(); + System.out.println(" ----- CHAPEL -----"); + System.out.println("Current Queue: " + chapelReservationQueue); + System.out.println("Cancel Task: " + (chapelReservationTask != null)); + System.out.println("Ongoing wid: " + ongoingChapel); + System.out.println(); + System.out.println("Ongoing wid: " + ongoingChapel + " isPremium: " + ongoingChapelType); + System.out.println("Guest list: " + ongoingChapelGuests); + System.out.println(); + System.out.println("Starttime: " + ongoingStartTime); + } } \ No newline at end of file diff --git a/src/net/server/channel/handlers/AdminCommandHandler.java b/src/net/server/channel/handlers/AdminCommandHandler.java index 9ee38ab1e5..e52b814754 100644 --- a/src/net/server/channel/handlers/AdminCommandHandler.java +++ b/src/net/server/channel/handlers/AdminCommandHandler.java @@ -25,7 +25,6 @@ import java.util.Arrays; import java.util.List; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; import server.MapleItemInformationProvider; import server.life.MapleLifeFactory; import server.life.MapleMonster; @@ -39,6 +38,7 @@ import client.MapleCharacter; import client.MapleClient; import client.inventory.MapleInventory; import client.inventory.MapleInventoryType; +import client.inventory.manipulator.MapleInventoryManipulator; public final class AdminCommandHandler extends AbstractMaplePacketHandler { diff --git a/src/net/server/channel/handlers/CashOperationHandler.java b/src/net/server/channel/handlers/CashOperationHandler.java index 8e0bdb827d..4e77bec907 100644 --- a/src/net/server/channel/handlers/CashOperationHandler.java +++ b/src/net/server/channel/handlers/CashOperationHandler.java @@ -37,7 +37,7 @@ import net.AbstractMaplePacketHandler; import server.CashShop; import server.CashShop.CashItem; import server.CashShop.CashItemFactory; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import tools.FilePrinter; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; @@ -72,6 +72,11 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler { } if (action == 0x03) { // Item + if(ItemConstants.isMapleLife(cItem.getItemId()) && chr.getLevel() < 30) { + c.announce(MaplePacketCreator.enableActions()); + return; + } + Item item = cItem.toItem(); cs.addToInventory(item); c.announce(MaplePacketCreator.showBoughtCashItem(item, c.getAccID())); @@ -165,6 +170,9 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler { return; } if (chr.getStorage().gainSlots(4)) { + FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " bought 4 slots to their account storage.\r\n"); + chr.setUsedStorage(); + c.announce(MaplePacketCreator.showBoughtStorageSlots(chr.getStorage().getSlots())); cs.gainCash(cash, -4000); c.announce(MaplePacketCreator.showCash(chr)); @@ -196,6 +204,10 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler { c.announce(MaplePacketCreator.showBoughtCharacterSlot(c.getCharacterSlots())); cs.gainCash(cash, -cItem.getPrice()); c.announce(MaplePacketCreator.showCash(chr)); + } else { + chr.dropMessage(1, "You have already used up all 12 extra character slots."); + c.announce(MaplePacketCreator.enableActions()); + return; } } else if (action == 0x0D) { // Take from Cash Inventory Item item = cs.findByCashId(slea.readInt()); @@ -208,15 +220,11 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler { c.announce(MaplePacketCreator.takeFromCashInventory(item)); if(item instanceof Equip) { - Equip equip = (Equip) item; - if(equip.getRingId() >= 0) { - MapleRing ring = MapleRing.loadFromDb(equip.getRingId()); - if (ring.getItemId() > 1112012) { - chr.addFriendshipRing(ring); - } else { - chr.addCrushRing(ring); - } - } + Equip equip = (Equip) item; + if(equip.getRingId() >= 0) { + MapleRing ring = MapleRing.loadFromDb(equip.getRingId()); + chr.addPlayerRing(ring); + } } } } else if (action == 0x0E) { // Put into Cash Inventory @@ -236,8 +244,8 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler { mi.removeSlot(item.getPosition()); c.announce(MaplePacketCreator.putIntoCashInventory(item, c.getAccID())); } else if (action == 0x1D) { //crush ring (action 28) - slea.readInt();//Birthday - // if (checkBirthday(c, birthday)) { //We're using a default birthday, so why restrict rings to only people who know of it? + int birthday = slea.readInt(); + if (checkBirthday(c, birthday)) { int toCharge = slea.readInt(); int SN = slea.readInt(); String recipientName = slea.readMapleAsciiString(); @@ -271,9 +279,10 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler { partner.showNote(); } } - /* } else { - chr.dropMessage("The birthday you entered was incorrect."); - }*/ + } else { + c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC4)); + c.announce(MaplePacketCreator.enableActions()); + } c.announce(MaplePacketCreator.showCash(c.getPlayer())); } else if (action == 0x20) { // everything is 1 meso... @@ -288,8 +297,8 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler { } c.announce(MaplePacketCreator.showCash(c.getPlayer())); } else if (action == 0x23) { //Friendship :3 - slea.readInt(); //Birthday - // if (checkBirthday(c, birthday)) { + int birthday = slea.readInt(); + if (checkBirthday(c, birthday)) { int payment = slea.readByte(); slea.skip(3); //0s int snID = slea.readInt(); @@ -320,17 +329,18 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler { partner.showNote(); } } - /* } else { - chr.dropMessage("The birthday you entered was incorrect."); - } */ + } else { + c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC4)); + c.announce(MaplePacketCreator.enableActions()); + } c.announce(MaplePacketCreator.showCash(c.getPlayer())); } else { - System.out.println(slea); + System.out.println("Unhandled action: " + action + "\n" + slea); } } - private static boolean checkBirthday(MapleClient c, int idate) { + public static boolean checkBirthday(MapleClient c, int idate) { int year = idate / 10000; int month = (idate - year * 10000) / 100; int day = idate - year * 10000 - month * 100; @@ -339,7 +349,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler { cal.set(year, month - 1, day); return c.checkBirthDate(cal); } - + public static boolean canBuy(CashItem item, int cash) { return item != null && item.isOnSale() && item.getPrice() <= cash; } diff --git a/src/net/server/channel/handlers/ChangeMapHandler.java b/src/net/server/channel/handlers/ChangeMapHandler.java index 0a5d40e733..795163a639 100644 --- a/src/net/server/channel/handlers/ChangeMapHandler.java +++ b/src/net/server/channel/handlers/ChangeMapHandler.java @@ -25,7 +25,7 @@ import java.net.InetAddress; import java.net.UnknownHostException; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MaplePortal; import server.MapleTrade; import server.maps.MapleMap; diff --git a/src/net/server/channel/handlers/CouponCodeHandler.java b/src/net/server/channel/handlers/CouponCodeHandler.java index 55bd9cbb0f..30f2757080 100644 --- a/src/net/server/channel/handlers/CouponCodeHandler.java +++ b/src/net/server/channel/handlers/CouponCodeHandler.java @@ -27,7 +27,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import tools.DatabaseConnection; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; diff --git a/src/net/server/channel/handlers/EnterCashShopHandler.java b/src/net/server/channel/handlers/EnterCashShopHandler.java index c1008e5838..fb0d4724e2 100644 --- a/src/net/server/channel/handlers/EnterCashShopHandler.java +++ b/src/net/server/channel/handlers/EnterCashShopHandler.java @@ -61,6 +61,7 @@ public class EnterCashShopHandler extends AbstractMaplePacketHandler { Server.getInstance().getPlayerBuffStorage().addBuffsToStorage(mc.getId(), mc.getAllBuffs()); Server.getInstance().getPlayerBuffStorage().addDiseasesToStorage(mc.getId(), mc.getAllDiseases()); mc.setAwayFromWorld(true); + mc.notifyMapTransferToPartner(-1); mc.cancelAllBuffs(true); mc.cancelAllDebuffs(); mc.cancelBuffExpireTask(); diff --git a/src/net/server/channel/handlers/EnterMTSHandler.java b/src/net/server/channel/handlers/EnterMTSHandler.java index 8fac19a214..30da949350 100644 --- a/src/net/server/channel/handlers/EnterMTSHandler.java +++ b/src/net/server/channel/handlers/EnterMTSHandler.java @@ -21,10 +21,6 @@ */ package net.server.channel.handlers; -import client.*; -import client.inventory.Equip; -import client.inventory.Item; - import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -33,118 +29,138 @@ import java.util.ArrayList; import java.util.List; import constants.ServerConstants; +import client.MapleCharacter; +import client.MapleClient; +import client.inventory.Equip; +import client.inventory.Item; +import client.processor.BuybackProcessor; import net.AbstractMaplePacketHandler; import net.server.Server; import server.MTSItemInfo; +import server.maps.FieldLimit; import server.maps.MapleMiniDungeonInfo; import tools.DatabaseConnection; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; + public final class EnterMTSHandler extends AbstractMaplePacketHandler { @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - if (!ServerConstants.USE_MTS){ - c.announce(MaplePacketCreator.enableActions()); - return; - } - - if(MapleMiniDungeonInfo.isDungeonMap(c.getPlayer().getMapId())) { - c.announce(MaplePacketCreator.serverNotice(5, "Changing channels or entering Cash Shop or MTS are disabled when inside a Mini-Dungeon.")); - c.announce(MaplePacketCreator.enableActions()); - return; - } - MapleCharacter chr = c.getPlayer(); - if (!chr.isAlive()) { + + if(!chr.isAlive() && ServerConstants.USE_BUYBACK_SYSTEM) { + BuybackProcessor.processBuyback(c); c.announce(MaplePacketCreator.enableActions()); - return; - } - if (chr.getLevel() < 10) { - c.announce(MaplePacketCreator.blockedMessage2(5)); - c.announce(MaplePacketCreator.enableActions()); - return; - } - - chr.unregisterChairBuff(); - Server.getInstance().getPlayerBuffStorage().addBuffsToStorage(chr.getId(), chr.getAllBuffs()); - Server.getInstance().getPlayerBuffStorage().addDiseasesToStorage(chr.getId(), chr.getAllDiseases()); - chr.setAwayFromWorld(true); - chr.cancelAllBuffs(true); - chr.cancelAllDebuffs(); - chr.cancelBuffExpireTask(); - chr.cancelDiseaseExpireTask(); - chr.cancelSkillCooldownTask(); - chr.cancelExpirationTask(); - - chr.forfeitExpirableQuests(); - chr.cancelQuestExpirationTask(); - - chr.saveToDB(); - chr.getMap().removePlayer(c.getPlayer()); - try { - c.announce(MaplePacketCreator.openCashShop(c, true)); - } catch (Exception ex) { - ex.printStackTrace(); - } - chr.getCashShop().open(true);// xD - c.announce(MaplePacketCreator.enableCSUse()); - c.announce(MaplePacketCreator.MTSWantedListingOver(0, 0)); - c.announce(MaplePacketCreator.showMTSCash(c.getPlayer())); - List items = new ArrayList<>(); - int pages = 0; - try { - Connection con = DatabaseConnection.getConnection(); - PreparedStatement ps = con.prepareStatement("SELECT * FROM mts_items WHERE tab = 1 AND transfer = 0 ORDER BY id DESC LIMIT 16, 16"); - ResultSet rs = ps.executeQuery(); - while (rs.next()) { - if (rs.getInt("type") != 1) { - Item i = new Item(rs.getInt("itemid"), (short) 0, (short) rs.getInt("quantity")); - i.setOwner(rs.getString("owner")); - items.add(new MTSItemInfo(i, rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1), rs.getInt("id"), rs.getInt("seller"), rs.getString("sellername"), rs.getString("sell_ends"))); - } else { - Equip equip = new Equip(rs.getInt("itemid"), (byte) rs.getInt("position"), -1); - equip.setOwner(rs.getString("owner")); - equip.setQuantity((short) 1); - equip.setAcc((short) rs.getInt("acc")); - equip.setAvoid((short) rs.getInt("avoid")); - equip.setDex((short) rs.getInt("dex")); - equip.setHands((short) rs.getInt("hands")); - equip.setHp((short) rs.getInt("hp")); - equip.setInt((short) rs.getInt("int")); - equip.setJump((short) rs.getInt("jump")); - equip.setVicious((short) rs.getInt("vicious")); - equip.setFlag((byte) rs.getInt("flag")); - equip.setLuk((short) rs.getInt("luk")); - equip.setMatk((short) rs.getInt("matk")); - equip.setMdef((short) rs.getInt("mdef")); - equip.setMp((short) rs.getInt("mp")); - equip.setSpeed((short) rs.getInt("speed")); - equip.setStr((short) rs.getInt("str")); - equip.setWatk((short) rs.getInt("watk")); - equip.setWdef((short) rs.getInt("wdef")); - equip.setUpgradeSlots((byte) rs.getInt("upgradeslots")); - equip.setLevel((byte) rs.getInt("level")); - items.add(new MTSItemInfo((Item) equip, rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1), rs.getInt("id"), rs.getInt("seller"), rs.getString("sellername"), rs.getString("sell_ends"))); - } + } else { + if (!ServerConstants.USE_MTS) { + c.announce(MaplePacketCreator.enableActions()); + return; + } + + if(MapleMiniDungeonInfo.isDungeonMap(c.getPlayer().getMapId())) { + c.announce(MaplePacketCreator.serverNotice(5, "Changing channels or entering Cash Shop or MTS are disabled when inside a Mini-Dungeon.")); + c.announce(MaplePacketCreator.enableActions()); + return; } - rs.close(); - ps.close(); - ps = con.prepareStatement("SELECT COUNT(*) FROM mts_items"); - rs = ps.executeQuery(); - if (rs.next()) { - pages = (int) Math.ceil(rs.getInt(1) / 16); + if (FieldLimit.CANNOTMIGRATE.check(chr.getMap().getFieldLimit())) { + chr.dropMessage(1, "You can't do it here in this map."); + c.announce(MaplePacketCreator.enableActions()); + return; } - rs.close(); - ps.close(); - con.close(); - } catch (SQLException e) { - e.printStackTrace(); + + if (!chr.isAlive()) { + c.announce(MaplePacketCreator.enableActions()); + return; + } + if (chr.getLevel() < 10) { + c.announce(MaplePacketCreator.blockedMessage2(5)); + c.announce(MaplePacketCreator.enableActions()); + return; + } + + chr.unregisterChairBuff(); + Server.getInstance().getPlayerBuffStorage().addBuffsToStorage(chr.getId(), chr.getAllBuffs()); + Server.getInstance().getPlayerBuffStorage().addDiseasesToStorage(chr.getId(), chr.getAllDiseases()); + chr.setAwayFromWorld(true); + chr.notifyMapTransferToPartner(-1); + chr.cancelAllBuffs(true); + chr.cancelAllDebuffs(); + chr.cancelBuffExpireTask(); + chr.cancelDiseaseExpireTask(); + chr.cancelSkillCooldownTask(); + chr.cancelExpirationTask(); + + chr.forfeitExpirableQuests(); + chr.cancelQuestExpirationTask(); + + chr.saveToDB(); + chr.getMap().removePlayer(c.getPlayer()); + try { + c.announce(MaplePacketCreator.openCashShop(c, true)); + } catch (Exception ex) { + ex.printStackTrace(); + } + chr.getCashShop().open(true);// xD + c.announce(MaplePacketCreator.enableCSUse()); + c.announce(MaplePacketCreator.MTSWantedListingOver(0, 0)); + c.announce(MaplePacketCreator.showMTSCash(c.getPlayer())); + List items = new ArrayList<>(); + int pages = 0; + try { + Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT * FROM mts_items WHERE tab = 1 AND transfer = 0 ORDER BY id DESC LIMIT 16, 16"); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + if (rs.getInt("type") != 1) { + Item i = new Item(rs.getInt("itemid"), (short) 0, (short) rs.getInt("quantity")); + i.setOwner(rs.getString("owner")); + items.add(new MTSItemInfo(i, rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1), rs.getInt("id"), rs.getInt("seller"), rs.getString("sellername"), rs.getString("sell_ends"))); + } else { + Equip equip = new Equip(rs.getInt("itemid"), (byte) rs.getInt("position"), -1); + equip.setOwner(rs.getString("owner")); + equip.setQuantity((short) 1); + equip.setAcc((short) rs.getInt("acc")); + equip.setAvoid((short) rs.getInt("avoid")); + equip.setDex((short) rs.getInt("dex")); + equip.setHands((short) rs.getInt("hands")); + equip.setHp((short) rs.getInt("hp")); + equip.setInt((short) rs.getInt("int")); + equip.setJump((short) rs.getInt("jump")); + equip.setVicious((short) rs.getInt("vicious")); + equip.setFlag((byte) rs.getInt("flag")); + equip.setLuk((short) rs.getInt("luk")); + equip.setMatk((short) rs.getInt("matk")); + equip.setMdef((short) rs.getInt("mdef")); + equip.setMp((short) rs.getInt("mp")); + equip.setSpeed((short) rs.getInt("speed")); + equip.setStr((short) rs.getInt("str")); + equip.setWatk((short) rs.getInt("watk")); + equip.setWdef((short) rs.getInt("wdef")); + equip.setUpgradeSlots((byte) rs.getInt("upgradeslots")); + equip.setLevel((byte) rs.getInt("level")); + items.add(new MTSItemInfo((Item) equip, rs.getInt("price") + 100 + (int) (rs.getInt("price") * 0.1), rs.getInt("id"), rs.getInt("seller"), rs.getString("sellername"), rs.getString("sell_ends"))); + } + } + rs.close(); + ps.close(); + + ps = con.prepareStatement("SELECT COUNT(*) FROM mts_items"); + rs = ps.executeQuery(); + if (rs.next()) { + pages = (int) Math.ceil(rs.getInt(1) / 16); + } + rs.close(); + ps.close(); + con.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + c.announce(MaplePacketCreator.sendMTS(items, 1, 0, 0, pages)); + c.announce(MaplePacketCreator.transferInventory(getTransfer(chr.getId()))); + c.announce(MaplePacketCreator.notYetSoldInv(getNotYetSold(chr.getId()))); } - c.announce(MaplePacketCreator.sendMTS(items, 1, 0, 0, pages)); - c.announce(MaplePacketCreator.transferInventory(getTransfer(chr.getId()))); - c.announce(MaplePacketCreator.notYetSoldInv(getNotYetSold(chr.getId()))); } private List getNotYetSold(int cid) { diff --git a/src/net/server/channel/handlers/FamilyAddHandler.java b/src/net/server/channel/handlers/FamilyAddHandler.java index ce8e86ee76..cbbf17d308 100644 --- a/src/net/server/channel/handlers/FamilyAddHandler.java +++ b/src/net/server/channel/handlers/FamilyAddHandler.java @@ -33,6 +33,7 @@ import tools.data.input.SeekableLittleEndianAccessor; * @author Jay Estrella */ public final class FamilyAddHandler extends AbstractMaplePacketHandler { + @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { if (!ServerConstants.USE_FAMILY_SYSTEM){ return; diff --git a/src/net/server/channel/handlers/GeneralChatHandler.java b/src/net/server/channel/handlers/GeneralChatHandler.java index 6e06e9884e..b822499d76 100644 --- a/src/net/server/channel/handlers/GeneralChatHandler.java +++ b/src/net/server/channel/handlers/GeneralChatHandler.java @@ -21,17 +21,18 @@ along with this program. If not, see . */ package net.server.channel.handlers; -import tools.FilePrinter; -import tools.MaplePacketCreator; -import tools.data.input.SeekableLittleEndianAccessor; import client.MapleCharacter; import client.MapleClient; import client.autoban.AutobanFactory; import client.command.Commands; import java.text.SimpleDateFormat; import java.util.Calendar; +import net.AbstractMaplePacketHandler; +import tools.FilePrinter; +import tools.MaplePacketCreator; +import tools.data.input.SeekableLittleEndianAccessor; -public final class GeneralChatHandler extends net.AbstractMaplePacketHandler { +public final class GeneralChatHandler extends AbstractMaplePacketHandler { private static boolean isCommandIssue(char heading, MapleCharacter chr) { if(chr.gmLevel() > 1 && heading == '!') { return true; @@ -59,7 +60,7 @@ public final class GeneralChatHandler extends net.AbstractMaplePacketHandler { String[] sp = s.split(" "); sp[0] = sp[0].toLowerCase().substring(1); - if(Commands.executeHeavenMSPlayerCommand(c, sp, heading)) { + if(Commands.executeHeavenMsPlayerCommand(c, sp, heading)) { String command = ""; for (String used : sp) { command += used + " "; @@ -85,4 +86,3 @@ public final class GeneralChatHandler extends net.AbstractMaplePacketHandler { } } } - diff --git a/src/net/server/channel/handlers/GuildOperationHandler.java b/src/net/server/channel/handlers/GuildOperationHandler.java index 83d2f78b06..55317a7d33 100644 --- a/src/net/server/channel/handlers/GuildOperationHandler.java +++ b/src/net/server/channel/handlers/GuildOperationHandler.java @@ -22,8 +22,9 @@ package net.server.channel.handlers; import net.server.guild.MapleGuildResponse; -import net.server.guild.MapleGuildCharacter; import net.server.guild.MapleGuild; +import constants.GameConstants; +import constants.ServerConstants; import client.MapleClient; import net.AbstractMaplePacketHandler; import tools.data.input.SeekableLittleEndianAccessor; @@ -107,8 +108,8 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler { c.getPlayer().dropMessage(1, "You cannot create a new Guild while in one."); return; } - if (mc.getMeso() < MapleGuild.CREATE_GUILD_COST) { - c.getPlayer().dropMessage(1, "You do not have enough mesos to create a Guild."); + if (mc.getMeso() < ServerConstants.CREATE_GUILD_COST) { + c.getPlayer().dropMessage(1, "You do not have " + GameConstants.numberWithCommas(ServerConstants.CREATE_GUILD_COST) + " mesos to create a Guild."); return; } String guildName = slea.readMapleAsciiString(); @@ -122,7 +123,7 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler { c.announce(MaplePacketCreator.genericGuildMessage((byte) 0x1c)); return; } - mc.gainMeso(-MapleGuild.CREATE_GUILD_COST, true, false, true); + mc.gainMeso(-ServerConstants.CREATE_GUILD_COST, true, false, true); mc.getMGC().setGuildId(gid); Server.getInstance().getGuild(mc.getGuildId(), mc.getWorld(), mc); // initialize guild structure @@ -254,8 +255,8 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler { System.out.println("[hax] " + mc.getName() + " tried to change guild emblem without being the guild leader."); return; } - if (mc.getMeso() < MapleGuild.CHANGE_EMBLEM_COST) { - c.announce(MaplePacketCreator.serverNotice(1, "You do not have enough mesos to change the Guild emblem.")); + if (mc.getMeso() < ServerConstants.CHANGE_EMBLEM_COST) { + c.announce(MaplePacketCreator.serverNotice(1, "You do not have " + GameConstants.numberWithCommas(ServerConstants.CHANGE_EMBLEM_COST) + " mesos to change the Guild emblem.")); return; } short bg = slea.readShort(); @@ -269,7 +270,7 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler { Server.getInstance().allianceMessage(alliance.getId(), MaplePacketCreator.getGuildAlliances(alliance, c.getWorld()), -1, -1); } - mc.gainMeso(-MapleGuild.CHANGE_EMBLEM_COST, true, false, true); + mc.gainMeso(-ServerConstants.CHANGE_EMBLEM_COST, true, false, true); respawnPlayer(mc); break; case 0x10: diff --git a/src/net/server/channel/handlers/InventoryMergeHandler.java b/src/net/server/channel/handlers/InventoryMergeHandler.java index 710feba27c..94700cb3a3 100644 --- a/src/net/server/channel/handlers/InventoryMergeHandler.java +++ b/src/net/server/channel/handlers/InventoryMergeHandler.java @@ -23,7 +23,7 @@ package net.server.channel.handlers; import constants.ServerConstants; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; import client.MapleCharacter; diff --git a/src/net/server/channel/handlers/ItemMoveHandler.java b/src/net/server/channel/handlers/ItemMoveHandler.java index 74d36e4b3f..07629891f7 100644 --- a/src/net/server/channel/handlers/ItemMoveHandler.java +++ b/src/net/server/channel/handlers/ItemMoveHandler.java @@ -24,7 +24,7 @@ package net.server.channel.handlers; import client.MapleClient; import client.inventory.MapleInventoryType; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; diff --git a/src/net/server/channel/handlers/ItemRewardHandler.java b/src/net/server/channel/handlers/ItemRewardHandler.java index 1d40cf261a..e40ee239ed 100644 --- a/src/net/server/channel/handlers/ItemRewardHandler.java +++ b/src/net/server/channel/handlers/ItemRewardHandler.java @@ -28,7 +28,7 @@ import constants.ItemConstants; import java.util.List; import net.AbstractMaplePacketHandler; import net.server.Server; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; import server.MapleItemInformationProvider.RewardItem; import tools.MaplePacketCreator; diff --git a/src/net/server/channel/handlers/MTSHandler.java b/src/net/server/channel/handlers/MTSHandler.java index 3104dde80c..39ae1ab8b4 100644 --- a/src/net/server/channel/handlers/MTSHandler.java +++ b/src/net/server/channel/handlers/MTSHandler.java @@ -33,7 +33,7 @@ import net.AbstractMaplePacketHandler; import net.server.Server; import net.server.channel.Channel; import server.MTSItemInfo; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; import tools.DatabaseConnection; import tools.MaplePacketCreator; @@ -50,6 +50,8 @@ public final class MTSHandler extends AbstractMaplePacketHandler { @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { + // TODO add karma-to-untradeable flag on sold items here + if (!c.getPlayer().getCashShop().isOpened()) { return; } diff --git a/src/net/server/channel/handlers/MakerSkillHandler.java b/src/net/server/channel/handlers/MakerSkillHandler.java index 15826ec0a1..77062f96fb 100644 --- a/src/net/server/channel/handlers/MakerSkillHandler.java +++ b/src/net/server/channel/handlers/MakerSkillHandler.java @@ -21,29 +21,9 @@ */ package net.server.channel.handlers; -import java.util.LinkedList; -import java.util.List; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; - -import constants.ItemConstants; import client.MapleClient; -import client.inventory.Equip; -import client.inventory.Item; -import client.inventory.MapleInventoryType; -import constants.EquipType; -import constants.ServerConstants; -import java.text.DecimalFormat; -import java.text.NumberFormat; +import client.processor.MakerProcessor; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; -import server.MapleItemInformationProvider; -import server.MakerItemFactory; -import server.MakerItemFactory.MakerItemCreateEntry; -import tools.FilePrinter; -import tools.Pair; -import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; /** @@ -51,423 +31,9 @@ import tools.data.input.SeekableLittleEndianAccessor; * @author Jay Estrella, Ronan */ public final class MakerSkillHandler extends AbstractMaplePacketHandler { - private static MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - int type = slea.readInt(); - int toCreate = slea.readInt(); - int toDisassemble = -1, pos = -1; - boolean makerSucceeded = true; - - MakerItemCreateEntry recipe; - Map reagentids = new LinkedHashMap<>(); - int stimulantid = -1; - - if(type == 3) { // building monster crystal - int fromLeftover = toCreate; - toCreate = ii.getMakerCrystalFromLeftover(toCreate); - if(toCreate == -1) { - c.announce(MaplePacketCreator.serverNotice(1, ii.getName(toCreate) + " is unavailable for Monster Crystal conversion.")); - return; - } - - recipe = MakerItemFactory.generateLeftoverCrystalEntry(fromLeftover); - } else if(type == 4) { // disassembling - slea.readInt(); // 1... probably inventory type - pos = slea.readInt(); - - Item it = c.getPlayer().getInventory(MapleInventoryType.EQUIP).getItem((short) pos); - if(it != null && it.getItemId() == toCreate) { - Pair p; - - if((p = generateDisassemblyInfo(toCreate)) != null) { - recipe = MakerItemFactory.generateDisassemblyCrystalEntry(p.getLeft(), p.getRight()); - toDisassemble = toCreate; - toCreate = ii.getMakerCrystalFromEquip(toCreate); - } else { - c.announce(MaplePacketCreator.serverNotice(1, ii.getName(toCreate) + " is unavailable for Monster Crystal disassembly.")); - return; - } - } else { - c.announce(MaplePacketCreator.serverNotice(1, "An unknown error occurred when trying to apply that item for disassembly.")); - return; - } - } else { - if(ItemConstants.isEquipment(toCreate)) { // only equips uses stimulant and reagents - if(slea.readByte() != 0) { // stimulant - stimulantid = getMakerStimulant(toCreate); - if(!c.getAbstractPlayerInteraction().haveItem(stimulantid)) { - stimulantid = -1; - } - } - - int reagents = Math.min(slea.readInt(), getMakerReagentSlots(toCreate)); - for(int i = 0; i < reagents; i++) { // crystals - int reagentid = slea.readInt(); - if(ItemConstants.isMakerReagent(reagentid)) { - Short rs = reagentids.get(reagentid); - if(rs == null) { - reagentids.put(reagentid, (short) 1); - } else { - reagentids.put(reagentid, (short) (rs + 1)); - } - } - } - - List> toUpdate = new LinkedList<>(); - for(Entry r : reagentids.entrySet()) { - int qty = c.getAbstractPlayerInteraction().getItemQuantity(r.getKey()); - - if(qty < r.getValue()) { - toUpdate.add(new Pair<>(r.getKey(), (short) qty)); - } - } - - // remove those not present on player inventory - if(!toUpdate.isEmpty()) { - for(Pair rp : toUpdate) { - if(rp.getRight() > 0) { - reagentids.put(rp.getLeft(), rp.getRight()); - } else { - reagentids.remove(rp.getLeft()); - } - } - } - - if(!reagentids.isEmpty()) removeOddMakerReagents(toCreate, reagentids); - } - - recipe = MakerItemFactory.getItemCreateEntry(toCreate, stimulantid, reagentids); - } - - short createStatus = getCreateStatus(c, recipe); - - switch(createStatus) { - case -1:// non-available for Maker itemid has been tried to forge - FilePrinter.printError(FilePrinter.EXPLOITS, "Player " + c.getPlayer().getName() + " tried to craft itemid " + toCreate + " using the Maker skill."); - c.announce(MaplePacketCreator.serverNotice(1, "The requested item could not be crafted on this operation.")); - break; - - case 1: // no items - c.announce(MaplePacketCreator.serverNotice(1, "You don't have all required items in your inventory to make " + recipe.getRewardAmount() + " " + ii.getName(toCreate) + ".")); - break; - - case 2: // no meso - NumberFormat nf = new DecimalFormat("#,###,###,###"); - c.announce(MaplePacketCreator.serverNotice(1, "You don't have enough mesos (" + nf.format(recipe.getCost()) + ") to complete this operation.")); - break; - - case 3: // no req level - c.announce(MaplePacketCreator.serverNotice(1, "You don't have enough level to complete this operation.")); - break; - - case 4: // no req skill level - c.announce(MaplePacketCreator.serverNotice(1, "You don't have enough Maker level to complete this operation.")); - break; - - default: - if (MapleInventoryManipulator.checkSpace(c, toCreate, (short) recipe.getRewardAmount(), "")) { - for (Pair p : recipe.getReqItems()) { - c.getAbstractPlayerInteraction().gainItem(p.getLeft(), (short) -p.getRight()); - } - - if(toDisassemble != -1) { - MapleInventoryManipulator.removeFromSlot(c, MapleInventoryType.EQUIP, (short) pos, (short) 1, false); - c.announce(MaplePacketCreator.getShowItemGain(toDisassemble, (short) -1, true)); - } - - int cost = recipe.getCost(); - if(stimulantid == -1 && reagentids.isEmpty()) { - if(cost > 0) c.getPlayer().gainMeso(-cost); - - c.getPlayer().setCS(true); - c.getAbstractPlayerInteraction().gainItem(toCreate, (short) recipe.getRewardAmount()); - c.getPlayer().setCS(false); - } else { - if(stimulantid != -1) c.getAbstractPlayerInteraction().gainItem(stimulantid, (short) -1); - if(!reagentids.isEmpty()) { - for(Entry r : reagentids.entrySet()) { - c.getAbstractPlayerInteraction().gainItem(r.getKey(), (short) (-1 * r.getValue())); - } - } - - if(cost > 0) c.getPlayer().gainMeso(-cost); - makerSucceeded = addBoostedMakerItem(c, toCreate, stimulantid, reagentids); - } - - if(makerSucceeded) c.announce(MaplePacketCreator.serverNotice(1, "You have successfully created " + recipe.getRewardAmount() + " " + ii.getName(toCreate) + ".")); - else c.getPlayer().dropMessage(5, "The Maker skill lights up, but the skill winds up as if nothing happened."); - - c.announce(MaplePacketCreator.showMakerEffect(makerSucceeded)); - c.getPlayer().getMap().broadcastMessage(c.getPlayer(), MaplePacketCreator.showForeignMakerEffect(c.getPlayer().getId(), makerSucceeded), false); - - if(toCreate == 4260003 && c.getPlayer().getQuestStatus(6033) == 1) { - c.getAbstractPlayerInteraction().setQuestProgress(6033, 1); - } - } else { - c.announce(MaplePacketCreator.serverNotice(1, "Your inventory is full.")); - } - } - } - - // checks and prevents hackers from PE'ing Maker operations with invalid operations - private static void removeOddMakerReagents(int toCreate, Map reagentids) { - Map reagentType = new LinkedHashMap<>(); - List toRemove = new LinkedList<>(); - - boolean isWeapon = ItemConstants.isWeapon(toCreate); - - for(Entry r : reagentids.entrySet()) { - int curRid = r.getKey(); - int type = r.getKey() / 100; - - if(type < 42502 && !isWeapon) { // only weapons should gain w.att/m.att from these. - toRemove.add(curRid); - } else { - Integer tableRid = reagentType.get(type); - - if(tableRid != null) { - if(tableRid < curRid) { - toRemove.add(tableRid); - reagentType.put(type, curRid); - } else { - toRemove.add(curRid); - } - } else { - reagentType.put(type, curRid); - } - } - } - - // removing less effective gems of repeated type - for(Integer i : toRemove) { - reagentids.remove(i); - } - - // only quantity 1 of each gem will be accepted by the Maker skill - for(Integer i : reagentids.keySet()) { - reagentids.put(i, (short) 1); - } - } - - private static int getMakerReagentSlots(int itemId) { - try { - int eqpLevel = ii.getEquipStats(itemId).get("reqLevel"); - - if(eqpLevel < 78) { - return 1; - } else if(eqpLevel >= 78 && eqpLevel < 108) { - return 2; - } else { - return 3; - } - } catch(NullPointerException npe) { - return 0; - } - } - - private static int getMakerStimulant(int itemId) { - EquipType et = EquipType.getEquipTypeById(itemId); - - switch(et) { - case GLOVES: - return 4130000; - - case SHOES: - return 4130001; - - case SWORD: - return 4130002; - - case AXE: - return 4130003; - - case MACE: - return 4130004; - - case SWORD_2H: - return 4130005; - - case AXE_2H: - return 4130006; - - case MACE_2H: - return 4130007; - - case SPEAR: - return 4130008; - - case POLEARM: - return 4130009; - - case WAND: - return 4130010; - - case STAFF: - return 4130011; - - case BOW: - return 4130012; - - case CROSSBOW: - return 4130013; - - case DAGGER: - return 4130014; - - case CLAW: - return 4130015; - - case KNUCKLER: - return 4130016; - - case PISTOL: - return 4130017; - - case CAP: - return 4130018; - - case COAT: - return 4130019; - - case PANTS: - return 4130020; - - case LONGCOAT: - return 4130021; - - case SHIELD: - return 4130022; - - default: - return -1; - } - } - - private static Pair generateDisassemblyInfo(int itemId) { - int recvFee = ii.getMakerDisassembledFee(itemId); - if(recvFee > -1) { - int recvQty = ii.getMakerDisassembledQuantity(itemId); - if(recvQty > 0) { - return new Pair<>(recvFee, recvQty); - } - } - - return null; - } - - private static short getCreateStatus(MapleClient c, MakerItemCreateEntry recipe) { - if(recipe == null) { - return -1; - } - - if(!hasItems(c, recipe)) { - return 1; - } - - if(c.getPlayer().getMeso() < recipe.getCost()) { - return 2; - } - - if(c.getPlayer().getLevel() < recipe.getReqLevel()) { - return 3; - } - - if(c.getPlayer().getSkillLevel((c.getPlayer().getJob().getId() / 1000) * 10000000 + 1007) < recipe.getReqSkillLevel()) { - return 4; - } - - return 0; - } - - private static boolean hasItems(MapleClient c, MakerItemCreateEntry recipe) { - for (Pair p : recipe.getReqItems()) { - int itemId = p.getLeft(); - if (c.getPlayer().getInventory(ItemConstants.getInventoryType(itemId)).countById(itemId) < p.getRight()) { - return false; - } - } - return true; - } - - private static boolean addBoostedMakerItem(MapleClient c, int itemid, int stimulantid, Map reagentids) { - if(stimulantid != -1 && !ii.rollSuccessChance(90.0)) { - return false; - } - - Item item = ii.getEquipById(itemid); - if(item == null) return false; - - Equip eqp = (Equip)item; - if(ItemConstants.isAccessory(item.getItemId()) && eqp.getUpgradeSlots() <= 0) eqp.setUpgradeSlots(3); - - if(ServerConstants.USE_ENHANCED_CRAFTING == true) { - if(!(c.getPlayer().isGM() && ServerConstants.USE_PERFECT_GM_SCROLL)) { - eqp.setUpgradeSlots((byte)(eqp.getUpgradeSlots() + 1)); - } - item = MapleItemInformationProvider.getInstance().scrollEquipWithId(eqp, 2049100, true, 2049100, c.getPlayer().isGM()); - } - - if(!reagentids.isEmpty()) { - Map stats = new LinkedHashMap<>(); - List randOption = new LinkedList<>(); - List randStat = new LinkedList<>(); - - for(Entry r : reagentids.entrySet()) { - Pair reagentBuff = ii.getMakerReagentStatUpgrade(r.getKey()); - - if(reagentBuff != null) { - String s = reagentBuff.getLeft(); - - if(s.substring(0, 4).contains("rand")) { - if(s.substring(4).equals("Stat")) { - randStat.add((short) (reagentBuff.getRight() * r.getValue())); - } else { - randOption.add((short) (reagentBuff.getRight() * r.getValue())); - } - } else { - String stat = s.substring(3); - - if(!stat.equals("ReqLevel")) { // improve req level... really? - switch (stat) { - case "MaxHP": - stat = "MHP"; - break; - - case "MaxMP": - stat = "MMP"; - break; - } - - Integer d = stats.get(stat); - if(d == null) { - stats.put(stat, reagentBuff.getRight() * r.getValue()); - } else { - stats.put(stat, d + (reagentBuff.getRight() * r.getValue())); - } - } - } - } - } - - ii.improveEquipStats(eqp, stats); - - for(Short sh : randStat) { - ii.scrollOptionEquipWithChaos(eqp, sh, false); - } - - for(Short sh : randOption) { - ii.scrollOptionEquipWithChaos(eqp, sh, true); - } - } - - if(stimulantid != -1) { - eqp = ii.randomizeUpgradeStats(eqp); - } - - MapleInventoryManipulator.addFromDrop(c, item, false, -1); - c.announce(MaplePacketCreator.getShowItemGain(itemid, (short) 1, true)); - return true; + MakerProcessor.makerAction(slea, c); } } diff --git a/src/net/server/channel/handlers/MoveLifeHandler.java b/src/net/server/channel/handlers/MoveLifeHandler.java index c9b70d8598..b79c423aea 100644 --- a/src/net/server/channel/handlers/MoveLifeHandler.java +++ b/src/net/server/channel/handlers/MoveLifeHandler.java @@ -27,8 +27,8 @@ import java.awt.Point; import java.util.ArrayList; import java.util.List; import server.life.MapleMonster; -import server.life.MobAttackInfo; -import server.life.MobAttackInfoFactory; +//import server.life.MobAttackInfo; +//import server.life.MobAttackInfoFactory; import server.life.MobSkill; import server.life.MobSkillFactory; import server.maps.MapleMapObject; @@ -95,11 +95,19 @@ public final class MoveLifeHandler extends AbstractMovementPacketHandler { toUse = null; } else if (monster.canUseSkill(toUse)) { toUse.applyEffect(c.getPlayer(), monster, true, banishPlayers); - //System.out.println("Applied: " + nextCastSkill + " Level: " + nextCastSkillLevel); - } + } else { + toUse = null; + } } else { - MobAttackInfo mobAttack = MobAttackInfoFactory.getMobAttackInfo(monster, attackId); - //System.out.println("Attacked"); + long curtime = System.currentTimeMillis(); + if(curtime >= monster.getNextBasicSkillTime()) { // dont use the special attack too often, chase the player f3 + //MobAttackInfo mobAttack = MobAttackInfoFactory.getMobAttackInfo(monster, attackId); + //monster.setNextBasicSkillTime(curtime); + + toUse = null; // paliative measure for suspicious mob movement + } else { + toUse = null; + } } } diff --git a/src/net/server/channel/handlers/NoteActionHandler.java b/src/net/server/channel/handlers/NoteActionHandler.java index acb8e9ae9b..ef50564fce 100644 --- a/src/net/server/channel/handlers/NoteActionHandler.java +++ b/src/net/server/channel/handlers/NoteActionHandler.java @@ -33,6 +33,7 @@ import client.MapleClient; import java.sql.Connection; public final class NoteActionHandler extends AbstractMaplePacketHandler { + @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { int action = slea.readByte(); if (action == 0 && c.getPlayer().getCashShop().getAvailableNotes() > 0) { diff --git a/src/net/server/channel/handlers/PetAutoPotHandler.java b/src/net/server/channel/handlers/PetAutoPotHandler.java index 04f7ba928e..bff88e4dac 100644 --- a/src/net/server/channel/handlers/PetAutoPotHandler.java +++ b/src/net/server/channel/handlers/PetAutoPotHandler.java @@ -28,7 +28,7 @@ import client.inventory.Item; import client.inventory.MapleInventory; import client.inventory.MapleInventoryType; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; import server.MapleStatEffect; import tools.Pair; diff --git a/src/net/server/channel/handlers/PetFoodHandler.java b/src/net/server/channel/handlers/PetFoodHandler.java index 694752c6b9..44427ba916 100644 --- a/src/net/server/channel/handlers/PetFoodHandler.java +++ b/src/net/server/channel/handlers/PetFoodHandler.java @@ -27,9 +27,8 @@ import client.inventory.MapleInventoryType; import client.inventory.MaplePet; import client.autoban.AutobanManager; import client.inventory.Item; -import tools.Randomizer; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; @@ -71,8 +70,13 @@ public final class PetFoodHandler extends AbstractMaplePacketHandler { return; } - // 50% chance to get +1 closeness - pet.gainClosenessFullness(chr, (Randomizer.nextInt(101) <= 50) ? 1 : 0, 30, 1); + c.lockClient(); + try { + pet.gainClosenessFullness(chr, (pet.getFullness() <= 75) ? 1 : 0, 30, 1); // 25+ "emptyness" to get +1 closeness + } finally { + c.unlockClient(); + } + MapleInventoryManipulator.removeFromSlot(c, MapleInventoryType.USE, pos, (short) 1, false); } } diff --git a/src/net/server/channel/handlers/PlayerInteractionHandler.java b/src/net/server/channel/handlers/PlayerInteractionHandler.java index 10ee894bec..a042977343 100644 --- a/src/net/server/channel/handlers/PlayerInteractionHandler.java +++ b/src/net/server/channel/handlers/PlayerInteractionHandler.java @@ -26,13 +26,14 @@ import client.MapleClient; import client.autoban.AutobanFactory; import client.inventory.Item; import client.inventory.MapleInventoryType; +import client.inventory.manipulator.MapleInventoryManipulator; +import client.inventory.manipulator.MapleKarmaManipulator; import constants.ItemConstants; import constants.ServerConstants; import java.util.Arrays; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; import server.MapleItemInformationProvider; import server.MapleTrade; import constants.GameConstants; @@ -407,7 +408,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler { if (chr.getTrade() != null) { if ((quantity <= item.getQuantity() && quantity >= 0) || ItemConstants.isRechargeable(item.getItemId())) { if (ii.isDropRestricted(item.getItemId())) { // ensure that undroppable items do not make it to the trade window - if (!((item.getFlag() & ItemConstants.KARMA) == ItemConstants.KARMA)) { + if (!MapleKarmaManipulator.hasKarmaFlag(item)) { c.announce(MaplePacketCreator.serverNotice(1, "That item is untradeable.")); c.announce(MaplePacketCreator.enableActions()); return; diff --git a/src/net/server/channel/handlers/PlayerLoggedinHandler.java b/src/net/server/channel/handlers/PlayerLoggedinHandler.java index b9240c4ce6..830f88de7f 100644 --- a/src/net/server/channel/handlers/PlayerLoggedinHandler.java +++ b/src/net/server/channel/handlers/PlayerLoggedinHandler.java @@ -58,6 +58,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Map; import server.life.MobSkill; +import tools.packets.Wedding; public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler { @@ -137,7 +138,9 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler { c.announce(MaplePacketCreator.getCharInfo(player)); if (!player.isHidden()) { - player.toggleHide(true); + if(player.isGM() && ServerConstants.USE_AUTOHIDE_GM) { + player.toggleHide(true); + } } player.sendKeymap(); player.sendMacros(); @@ -277,6 +280,16 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler { player.updateCouponRates(); player.receivePartyMemberHP(); + + if(player.getPartnerId() > 0) { + int partnerId = player.getPartnerId(); + final MapleCharacter partner = c.getWorldServer().getPlayerStorage().getCharacterById(partnerId); + + if(partner != null && !partner.isAwayFromWorld()) { + player.announce(Wedding.OnNotifyWeddingPartnerTransfer(partnerId, partner.getMapId())); + partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(player.getId(), player.getMapId())); + } + } } private static void showDueyNotification(MapleClient c, MapleCharacter player) { @@ -286,13 +299,13 @@ public final class PlayerLoggedinHandler extends AbstractMaplePacketHandler { ResultSet rs = null; try { con = DatabaseConnection.getConnection(); - ps = con.prepareStatement("SELECT Mesos FROM dueypackages WHERE RecieverId = ? and Checked = 1"); + ps = con.prepareStatement("SELECT Mesos FROM dueypackages WHERE ReceiverId = ? and Checked = 1"); ps.setInt(1, player.getId()); rs = ps.executeQuery(); if (rs.next()) { try { Connection con2 = DatabaseConnection.getConnection(); - pss = con2.prepareStatement("UPDATE dueypackages SET Checked = 0 where RecieverId = ?"); + pss = con2.prepareStatement("UPDATE dueypackages SET Checked = 0 where ReceiverId = ?"); pss.setInt(1, player.getId()); pss.executeUpdate(); pss.close(); diff --git a/src/net/server/channel/handlers/RangedAttackHandler.java b/src/net/server/channel/handlers/RangedAttackHandler.java index 8b46293025..2deb447c78 100644 --- a/src/net/server/channel/handlers/RangedAttackHandler.java +++ b/src/net/server/channel/handlers/RangedAttackHandler.java @@ -21,7 +21,7 @@ along with this program. If not, see . */ package net.server.channel.handlers; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; import server.MapleStatEffect; import tools.MaplePacketCreator; diff --git a/src/net/server/channel/handlers/RingActionHandler.java b/src/net/server/channel/handlers/RingActionHandler.java index b9ee5d093d..318bef7768 100644 --- a/src/net/server/channel/handlers/RingActionHandler.java +++ b/src/net/server/channel/handlers/RingActionHandler.java @@ -25,86 +25,490 @@ package net.server.channel.handlers; //import java.sql.PreparedStatement; import client.MapleClient; import client.MapleCharacter; +import client.inventory.MapleInventoryType; +import client.processor.DueyProcessor; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; //import tools.DatabaseConnection; import net.AbstractMaplePacketHandler; +import client.inventory.manipulator.MapleInventoryManipulator; +import tools.DatabaseConnection; import tools.data.input.SeekableLittleEndianAccessor; //import scripting.npc.NPCScriptManager; +import tools.Pair; import tools.MaplePacketCreator; +import tools.packets.Wedding; +import net.server.world.World; +import net.server.channel.Channel; +import server.MapleItemInformationProvider; +import client.MapleRing; +import client.inventory.Equip; +import client.inventory.Item; /** * @author Jvlaple + * @author Ronan (major overhaul on Ring handling mechanics) */ public final class RingActionHandler extends AbstractMaplePacketHandler { - public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - byte mode = slea.readByte(); - MapleCharacter player = c.getPlayer(); - switch (mode) { - case 0: //Send - String partnerName = slea.readMapleAsciiString(); - MapleCharacter partner = c.getChannelServer().getPlayerStorage().getCharacterByName(partnerName); - if (partnerName.equalsIgnoreCase(player.getName())) { - c.getPlayer().dropMessage(1, "You cannot put your own name in it."); - return; - } else if (partner == null) { - c.getPlayer().dropMessage(1, partnerName + " was not found on this channel. If you are both logged in, please make sure you are in the same channel."); - return; - } else if (partner.getGender() == player.getGender()) { - c.getPlayer().dropMessage(1, "Your partner is the same gender as you."); - return; - } //else if (player.isMarried() && partner.isMarried()) - // NPCScriptManager.getInstance().start(partner.getClient(), 9201002, "marriagequestion", player); - break; - case 1: //Cancel send - c.getPlayer().dropMessage(1, "You've cancelled the request."); - boolean accepted = slea.readByte() > 0; - String proposerName = slea.readMapleAsciiString(); - if (accepted) { - c.announce(MaplePacketCreator.sendEngagementRequest(proposerName)); - } - break; - case 2: - slea.readByte(); //type - case 3: //Drop Ring - /* - if (player.getPartner() != null) { - try { - Connection con = DatabaseConnection.getConnection(); - int pid = 0; - if (player.getGender() == 0) - pid = player.getId(); - else - pid = player.getPartner().getId();//we have an engagements SQL? - PreparedStatement ps = con.prepareStatement("DELETE FROM engagements WHERE husbandid = ?"); - ps.setInt(1, pid); - ps.executeUpdate(); - ps.close(); - ps = con.prepareStatement("UPDATE characters SET marriagequest = 0 WHERE id = ?, and WHERE id = ?"); - ps.setInt(1, player.getId()); - ps.setInt(2, player.getPartner().getId()); - ps.executeUpdate(); - ps.close(); - con.close(); - } catch (Exception ex) { - ex.printStackTrace(); - } - c.getPlayer().dropMessage(1, "Your engagement has been broken up."); - break; - }*/ - break; - case 9: // groom's wishlist - int amount = slea.readShort(); - if (amount > 10) { - amount = 10; - } - String[] items = new String[10]; - for (int i = 0; i < amount; i++) { - items[i] = slea.readMapleAsciiString(); - } - c.announce(MaplePacketCreator.sendGroomWishlist()); //WTF< - break; - default: - System.out.println("NEW RING ACTION " + mode); - break; + private static int getBoxId(int useItemId) { + return useItemId == 2240000 ? 4031357 : (useItemId == 2240001 ? 4031359 : (useItemId == 2240002 ? 4031361 : (useItemId == 2240003 ? 4031363 : (1112300 + (useItemId - 2240004))))); + } + + public static void sendEngageProposal(final MapleClient c, final String name, final int itemid) { + final int newBoxId = getBoxId(itemid); + final MapleCharacter target = c.getChannelServer().getPlayerStorage().getCharacterByName(name); + final MapleCharacter source = c.getPlayer(); + + // TODO: get the correct packet bytes for these popups + if (source.isMarried()) { + source.dropMessage(1, "You're already married!"); + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } else if (source.getPartnerId() > 0) { + source.dropMessage(1, "You're already engaged!"); + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } else if (source.getMarriageItemId() > 0) { + source.dropMessage(1, "You're already engaging someone!"); + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } else if (target == null) { + source.dropMessage(1, "Unable to find " + name + " on this channel."); + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } else if (target == source) { + source.dropMessage(1, "You can't engage yourself."); + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } else if(target.getLevel() < 50) { + source.dropMessage(1, "You can only propose to someone level 50 or higher."); + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } else if(source.getLevel() < 50) { + source.dropMessage(1, "You can only propose being level 50 or higher."); + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } else if (!target.getMap().equals(source.getMap())) { + source.dropMessage(1, "Make sure your partner is on the same map!"); + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } else if (!source.haveItem(itemid) || itemid < 2240000 || itemid > 2240015) { + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } else if (target.isMarried()) { + source.dropMessage(1, "The player is already married!"); + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } else if (target.getPartnerId() > 0 || target.getMarriageItemId() > 0) { + source.dropMessage(1, "The player is already engaged!"); + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } else if (target.getGender() != 1) { + source.dropMessage(1, "You may only propose to a girl!"); + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } else if (!MapleInventoryManipulator.checkSpace(c, newBoxId, 1, "")) { + source.dropMessage(5, "You don't have a ETC slot available right now!"); + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } else if (!MapleInventoryManipulator.checkSpace(target.getClient(), newBoxId + 1, 1, "")) { + source.dropMessage(5, "The girl you proposed doesn't have a ETC slot available right now."); + source.announce(Wedding.OnMarriageResult((byte) 0)); + return; + } + + source.setMarriageItemId(itemid); + target.announce(Wedding.OnMarriageRequest(source.getName(), source.getId())); + } + + private static void eraseEngagementOffline(int characterId) { + try { + Connection con = DatabaseConnection.getConnection(); + eraseEngagementOffline(characterId, con); + con.close(); + } catch(SQLException sqle) { + sqle.printStackTrace(); } } + + private static void eraseEngagementOffline(int characterId, Connection con) throws SQLException { + PreparedStatement ps = con.prepareStatement("UPDATE characters SET marriageItemId=-1, partnerId=-1 WHERE id=?"); + ps.setInt(1, characterId); + ps.executeUpdate(); + + ps.close(); + } + + private static void breakEngagementOffline(int characterId) { + try { + Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT marriageItemId FROM characters WHERE id=?"); + ps.setInt(1, characterId); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + int marriageItemId = rs.getInt("marriageItemId"); + + if (marriageItemId > 0) { + PreparedStatement ps2 = con.prepareStatement("UPDATE inventoryitems SET expiration=0 WHERE itemid=? AND characterid=?"); + ps2.setInt(1, marriageItemId); + ps2.setInt(2, characterId); + + ps2.executeUpdate(); + ps2.close(); + } + } + rs.close(); + ps.close(); + + eraseEngagementOffline(characterId, con); + + con.close(); + } catch (SQLException ex) { + System.out.println("Error updating offline breakup " + ex.getMessage()); + } + } + + private synchronized static void breakMarriage(MapleCharacter chr) { + int partnerid = chr.getPartnerId(); + if(partnerid <= 0) return; + + chr.getClient().getWorldServer().deleteRelationship(chr.getId(), partnerid); + MapleRing.removeRing(chr.getMarriageRing()); + + MapleCharacter partner = chr.getClient().getWorldServer().getPlayerStorage().getCharacterById(partnerid); + if(partner == null) { + eraseEngagementOffline(partnerid); + } else { + partner.dropMessage(5, chr.getName() + " has decided to break up the marriage."); + + //partner.announce(Wedding.OnMarriageResult((byte) 0)); ok, how to gracefully unengage someone without the need to cc? + partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(0, 0)); + resetRingId(partner); + partner.setPartnerId(-1); + partner.setMarriageItemId(-1); + partner.addMarriageRing(null); + } + + chr.dropMessage(5, "You have successfully break the marriage with " + MapleCharacter.getNameById(partnerid) + "."); + + //chr.announce(Wedding.OnMarriageResult((byte) 0)); + chr.announce(Wedding.OnNotifyWeddingPartnerTransfer(0, 0)); + resetRingId(chr); + chr.setPartnerId(-1); + chr.setMarriageItemId(-1); + chr.addMarriageRing(null); + } + + private static void resetRingId(MapleCharacter player) { + int ringitemid = player.getMarriageRing().getItemId(); + + Item it = player.getInventory(MapleInventoryType.EQUIP).findById(ringitemid); + if(it == null) { + it = player.getInventory(MapleInventoryType.EQUIPPED).findById(ringitemid); + } + + if(it != null) { + Equip eqp = (Equip) it; + eqp.setRingId(-1); + } + } + + private synchronized static void breakEngagement(MapleCharacter chr) { + int partnerid = chr.getPartnerId(); + int marriageitemid = chr.getMarriageItemId(); + + chr.getClient().getWorldServer().deleteRelationship(chr.getId(), partnerid); + + MapleCharacter partner = chr.getClient().getWorldServer().getPlayerStorage().getCharacterById(partnerid); + if(partner == null) { + breakEngagementOffline(partnerid); + } else { + partner.dropMessage(5, chr.getName() + " has decided to break up the engagement."); + + int partnerMarriageitemid = marriageitemid + ((chr.getGender() == 0) ? 1 : -1); + if(partner.haveItem(partnerMarriageitemid)) { + MapleInventoryManipulator.removeById(partner.getClient(), MapleInventoryType.ETC, partnerMarriageitemid, (short) 1, false, false); + } + + //partner.announce(Wedding.OnMarriageResult((byte) 0)); ok, how to gracefully unengage someone without the need to cc? + partner.announce(Wedding.OnNotifyWeddingPartnerTransfer(0, 0)); + partner.setPartnerId(-1); + partner.setMarriageItemId(-1); + } + + if(chr.haveItem(marriageitemid)) { + MapleInventoryManipulator.removeById(chr.getClient(), MapleInventoryType.ETC, marriageitemid, (short) 1, false, false); + } + chr.dropMessage(5, "You have successfully break the engagement with " + MapleCharacter.getNameById(partnerid) + "."); + + //chr.announce(Wedding.OnMarriageResult((byte) 0)); + chr.announce(Wedding.OnNotifyWeddingPartnerTransfer(0, 0)); + chr.setPartnerId(-1); + chr.setMarriageItemId(-1); + } + + public static void breakMarriageRing(MapleCharacter chr, final int wItemId) { + final MapleInventoryType type = MapleInventoryType.getByType((byte) (wItemId / 1000000)); + final Item wItem = chr.getInventory(type).findById(wItemId); + final boolean weddingToken = (wItem != null && type == MapleInventoryType.ETC && wItemId / 10000 == 403); + final boolean weddingRing = (wItem != null && wItemId / 10 == 111280); + + if (weddingRing) { + if(chr.getPartnerId() > 0) { + breakMarriage(chr); + } + + chr.getMap().disappearingItemDrop(chr, chr, wItem, chr.getPosition()); + } else if (weddingToken) { + if (chr.getPartnerId() > 0) { + breakEngagement(chr); + } + + chr.getMap().disappearingItemDrop(chr, chr, wItem, chr.getPosition()); + } + } + + public static void giveMarriageRings(MapleCharacter player, MapleCharacter partner, int marriageRingId) { + int ringid = MapleRing.createRing(marriageRingId, player, partner); + MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); + + Item ringObj = ii.getEquipById(marriageRingId); + Equip ringEqp = (Equip) ringObj; + ringEqp.setRingId(ringid); + player.addMarriageRing(MapleRing.loadFromDb(ringid)); + MapleInventoryManipulator.addFromDrop(player.getClient(), ringEqp, false, -1); + player.broadcastMarriageMessage(); + + ringObj = ii.getEquipById(marriageRingId); + ringEqp = (Equip) ringObj; + ringEqp.setRingId(ringid + 1); + partner.addMarriageRing(MapleRing.loadFromDb(ringid + 1)); + MapleInventoryManipulator.addFromDrop(partner.getClient(), ringEqp, false, -1); + partner.broadcastMarriageMessage(); + } + + @Override + public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { + byte mode = slea.readByte(); + String name; + byte slot; + switch(mode) { + case 0: // Send Proposal + sendEngageProposal(c, slea.readMapleAsciiString(), slea.readInt()); + break; + + case 1: // Cancel Proposal + if(c.getPlayer().getMarriageItemId() / 1000000 != 4) { + c.getPlayer().setMarriageItemId(-1); + } + break; + + case 2: // Accept/Deny Proposal + final boolean accepted = slea.readByte() > 0; + name = slea.readMapleAsciiString(); + final int id = slea.readInt(); + + final MapleCharacter source = c.getWorldServer().getPlayerStorage().getCharacterByName(name); + final MapleCharacter target = c.getPlayer(); + + if (source == null) { + target.announce(MaplePacketCreator.enableActions()); + return; + } + + final int itemid = source.getMarriageItemId(); + if (target.getPartnerId() > 0 || source.getId() != id || itemid <= 0 || !source.haveItem(itemid) || source.getPartnerId() > 0 || !source.isAlive() || !target.isAlive()) { + target.announce(MaplePacketCreator.enableActions()); + return; + } + + if (accepted) { + final int newItemId = getBoxId(itemid); + if (!MapleInventoryManipulator.checkSpace(c, newItemId, 1, "") || !MapleInventoryManipulator.checkSpace(source.getClient(), newItemId, 1, "")) { + target.announce(MaplePacketCreator.enableActions()); + return; + } + + try { + MapleInventoryManipulator.removeById(source.getClient(), MapleInventoryType.USE, itemid, 1, false, false); + + int marriageId = c.getWorldServer().createRelationship(source.getId(), target.getId()); + source.setPartnerId(target.getId()); // engage them (new marriageitemid, partnerid for both) + target.setPartnerId(source.getId()); + + source.setMarriageItemId(newItemId); + target.setMarriageItemId(newItemId + 1); + + MapleInventoryManipulator.addById(source.getClient(), newItemId, (short) 1); + MapleInventoryManipulator.addById(c, (newItemId + 1), (short) 1); + + source.announce(Wedding.OnMarriageResult(marriageId, source, false)); + target.announce(Wedding.OnMarriageResult(marriageId, source, false)); + + source.announce(Wedding.OnNotifyWeddingPartnerTransfer(target.getId(), target.getMapId())); + target.announce(Wedding.OnNotifyWeddingPartnerTransfer(source.getId(), source.getMapId())); + } catch (Exception e) { + System.out.println("Error with engagement " + e.getMessage()); + } + } else { + source.dropMessage(1, "She has politely declined your engagement request."); + source.announce(Wedding.OnMarriageResult((byte) 0)); + + source.setMarriageItemId(-1); + } + break; + + case 3: // Break Engagement + breakMarriageRing(c.getPlayer(), slea.readInt()); + break; + + case 5: // Invite %s to Wedding + name = slea.readMapleAsciiString(); + int marriageId = slea.readInt(); + slot = slea.readByte(); // this is an int + + int itemId; + try { + itemId = c.getPlayer().getInventory(MapleInventoryType.ETC).getItem(slot).getItemId(); + } catch(NullPointerException npe) { + c.announce(MaplePacketCreator.enableActions()); + return; + } + + if((itemId != 4031377 && itemId != 4031395) || !c.getPlayer().haveItem(itemId)) { + c.announce(MaplePacketCreator.enableActions()); + return; + } + + String groom = c.getPlayer().getName(), bride = MapleCharacter.getNameById(c.getPlayer().getPartnerId()); + int guest = MapleCharacter.getIdByName(name); + if (groom == null || bride == null || groom.equals("") || bride.equals("") || guest <= 0) { + c.getPlayer().dropMessage(5, "Unable to find " + name + "!"); + return; + } + + try { + World wserv = c.getWorldServer(); + Pair registration = wserv.getMarriageQueuedLocation(marriageId); + + if(registration != null) { + if(wserv.addMarriageGuest(marriageId, guest)) { + boolean cathedral = registration.getLeft(); + int newItemId = cathedral ? 4031407 : 4031406; + + Channel cserv = c.getChannelServer(); + int resStatus = cserv.getWeddingReservationStatus(marriageId, cathedral); + if(resStatus > 0) { + long expiration = cserv.getWeddingTicketExpireTime(resStatus + 1); + + MapleCharacter guestChr = c.getWorldServer().getPlayerStorage().getCharacterById(guest); + if(guestChr != null && MapleInventoryManipulator.checkSpace(guestChr.getClient(), newItemId, 1, "") && MapleInventoryManipulator.addById(guestChr.getClient(), newItemId, (short) 1, expiration)) { + guestChr.dropMessage(6, "[WEDDING] You've been invited to " + groom + " and " + bride + "'s Wedding!"); + } else { + c.getPlayer().sendNote(name, "You've been invited to " + groom + " and " + bride + "'s Wedding! Receive your invitation from Duey!", (byte) 0); + + Item weddingTicket = new Item(newItemId, (short) 0, (short) 1); + weddingTicket.setExpiration(expiration); + + DueyProcessor.addItemToDB(weddingTicket, 1, 0, groom, guest); + } + } else { + c.getPlayer().dropMessage(5, "Wedding is already under way. You cannot invite any more guests for the event."); + } + } else { + c.getPlayer().dropMessage(5, "'" + name + "' is already invited for your marriage."); + } + } else { + c.getPlayer().dropMessage(5, "Invitation was not sent to '" + name + "'. Either the time for your marriage reservation already came or it was not found."); + } + + } catch (SQLException ex) { + ex.printStackTrace(); + return; + } + + c.getAbstractPlayerInteraction().gainItem(itemId, (short) -1); + break; + + case 6: // Open Wedding Invitation + slot = (byte) slea.readInt(); + int invitationid = slea.readInt(); + + if(invitationid == 4031406 || invitationid == 4031407) { + Item item = c.getPlayer().getInventory(MapleInventoryType.ETC).getItem(slot); + if(item == null || item.getItemId() != invitationid) { + c.announce(MaplePacketCreator.enableActions()); + return; + } + + // collision case: most soon-to-come wedding will show up + Pair coupleId = c.getWorldServer().getWeddingCoupleForGuest(c.getPlayer().getId(), invitationid == 4031407); + if (coupleId != null) { + int groomId = coupleId.getLeft(), brideId = coupleId.getRight(); + c.announce(Wedding.sendWeddingInvitation(MapleCharacter.getNameById(groomId), MapleCharacter.getNameById(brideId))); + } + } + + break; + + case 9: // Groom and Bride's Wishlist + short size = slea.readShort(); + List itemnames = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + itemnames.add(slea.readMapleAsciiString()); + } + + //System.out.println("G&B WISHLIST: " + itemnames); + + /* + if (c.getPlayer().getMarriageItemId() > -1) { + switch(c.getPlayer().getMarriageItemId()) { + case 10: // Premium Cathedral + c.getAbstractPlayerInteraction().gainItem(4031375, (short)1); + c.getAbstractPlayerInteraction().gainItem(4031395, (short)15); + break; + case 11: // Normal Cathedral + c.getAbstractPlayerInteraction().gainItem(4031480, (short)1); + c.getAbstractPlayerInteraction().gainItem(4031395, (short)15); + break; + case 20: // Premium Chapel + c.getAbstractPlayerInteraction().gainItem(4031376, (short)1); + c.getAbstractPlayerInteraction().gainItem(4031377, (short)15); + break; + case 21: // Normal Chapel + c.getAbstractPlayerInteraction().gainItem(4031481, (short)1); + c.getAbstractPlayerInteraction().gainItem(4031377, (short)15); + break; + default: { + System.out.println("Invalid Wedding Type for player " + c.getPlayer().getName() + "!"); + break; + } + } + + //c.getPlayer().setMarriageItemId(-1); ????? + } + + if (c.getPlayer().getWishlist() == null) { + c.getPlayer().registerWishlist(itemnames); + } + + if (c.getPlayer().getWedding() != null) { + if (c.getPlayer().getGender() == 0 ? c.getPlayer().getWedding().isExistantGroom(c.getPlayer().getId()) : c.getPlayer().getWedding().isExistantBride(c.getPlayer().getId())) { + c.getPlayer().getWedding().registerWishlist(c.getPlayer().getGender() == 1, itemnames); + } + } + */ + break; + + default: + System.out.println("Unhandled RING_ACTION Mode: " + slea.toString()); + break; + } + + c.getSession().write(MaplePacketCreator.enableActions()); + } } diff --git a/src/net/server/channel/handlers/ScrollHandler.java b/src/net/server/channel/handlers/ScrollHandler.java index 029368bcbd..2605330923 100644 --- a/src/net/server/channel/handlers/ScrollHandler.java +++ b/src/net/server/channel/handlers/ScrollHandler.java @@ -34,7 +34,7 @@ import constants.ItemConstants; import java.util.ArrayList; import java.util.List; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; @@ -107,11 +107,19 @@ public final class ScrollHandler extends AbstractMaplePacketHandler { } final List mods = new ArrayList<>(); if (scrollSuccess == Equip.ScrollResult.CURSE) { - mods.add(new ModifyInventory(3, toScroll)); - if (dst < 0) { - c.getPlayer().getInventory(MapleInventoryType.EQUIPPED).removeItem(toScroll.getPosition()); + if(!ItemConstants.isWeddingRing(toScroll.getItemId())) { + mods.add(new ModifyInventory(3, toScroll)); + if (dst < 0) { + c.getPlayer().getInventory(MapleInventoryType.EQUIPPED).removeItem(toScroll.getPosition()); + } else { + c.getPlayer().getInventory(MapleInventoryType.EQUIP).removeItem(toScroll.getPosition()); + } } else { - c.getPlayer().getInventory(MapleInventoryType.EQUIP).removeItem(toScroll.getPosition()); + scrolled = toScroll; + scrollSuccess = Equip.ScrollResult.FAIL; + + mods.add(new ModifyInventory(3, scrolled)); + mods.add(new ModifyInventory(0, scrolled)); } } else { mods.add(new ModifyInventory(3, scrolled)); diff --git a/src/net/server/channel/handlers/SkillBookHandler.java b/src/net/server/channel/handlers/SkillBookHandler.java index 6f953afbbd..84318aad0a 100644 --- a/src/net/server/channel/handlers/SkillBookHandler.java +++ b/src/net/server/channel/handlers/SkillBookHandler.java @@ -29,7 +29,7 @@ import client.inventory.Item; import client.inventory.MapleInventoryType; import java.util.Map; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; import tools.MaplePacketCreator; import tools.Randomizer; diff --git a/src/net/server/channel/handlers/SpawnPetHandler.java b/src/net/server/channel/handlers/SpawnPetHandler.java index e1ff85376a..5110b53fff 100644 --- a/src/net/server/channel/handlers/SpawnPetHandler.java +++ b/src/net/server/channel/handlers/SpawnPetHandler.java @@ -32,7 +32,7 @@ import net.AbstractMaplePacketHandler; import provider.MapleDataProvider; import provider.MapleDataProviderFactory; import provider.MapleDataTool; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; diff --git a/src/net/server/channel/handlers/SpouseChatHandler.java b/src/net/server/channel/handlers/SpouseChatHandler.java index c9640ad59a..10b822fec9 100644 --- a/src/net/server/channel/handlers/SpouseChatHandler.java +++ b/src/net/server/channel/handlers/SpouseChatHandler.java @@ -21,36 +21,29 @@ */ package net.server.channel.handlers; -//import client.MapleCharacter; +import client.MapleCharacter; import client.MapleClient; -//import client.command.CommandProcessor; import net.AbstractMaplePacketHandler; -//import tools.MaplePacketCreator; +import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; public final class SpouseChatHandler extends AbstractMaplePacketHandler { + @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - System.out.println(slea.toString()); -// slea.readMapleAsciiString();//recipient -// String msg = slea.readMapleAsciiString(); -// if (!CommandProcessor.processCommand(c, msg)) -// if (c.getPlayer().isMarried()) { -// MapleCharacter wife = c.getChannelServer().getPlayerStorage().getCharacterById(c.getPlayer().getPartnerId()); -// if (wife != null) { -// wife.getClient().announce(MaplePacketCreator.sendSpouseChat(c.getPlayer(), msg)); -// c.announce(MaplePacketCreator.sendSpouseChat(c.getPlayer(), msg)); -// } else -// try { -// if (c.getChannelServer().getWorldInterface().isConnected(wife.getName())) { -// c.getChannelServer().getWorldInterface().sendSpouseChat(c.getPlayer().getName(), wife.getName(), msg); -// c.announce(MaplePacketCreator.sendSpouseChat(c.getPlayer(), msg)); -// } else -// c.getPlayer().message("You are either not married or your spouse is currently offline."); -// } catch (Exception e) { -// e.printStackTrace(); -// c.getPlayer().message("You are either not married or your spouse is currently offline."); -// c.getChannelServer().reconnectWorld(); -// } -// } + slea.readMapleAsciiString();//recipient + String msg = slea.readMapleAsciiString(); + + int partnerId = c.getPlayer().getPartnerId(); + if (partnerId > 0) { // yay marriage + MapleCharacter spouse = c.getWorldServer().getPlayerStorage().getCharacterById(partnerId); + if (spouse != null) { + spouse.announce(MaplePacketCreator.OnCoupleMessage(c.getPlayer().getName(), msg, true)); + c.announce(MaplePacketCreator.OnCoupleMessage(c.getPlayer().getName(), msg, true)); + } else { + c.getPlayer().dropMessage(5, "Your spouse is currently offline."); + } + } else { + c.getPlayer().dropMessage(5, "You don't have a spouse."); + } } } diff --git a/src/net/server/channel/handlers/StorageHandler.java b/src/net/server/channel/handlers/StorageHandler.java index 45057e2548..6f353a5b4b 100644 --- a/src/net/server/channel/handlers/StorageHandler.java +++ b/src/net/server/channel/handlers/StorageHandler.java @@ -21,20 +21,9 @@ */ package net.server.channel.handlers; -import client.MapleCharacter; import client.MapleClient; -import client.autoban.AutobanFactory; -import client.inventory.Item; -import client.inventory.MapleInventory; -import client.inventory.MapleInventoryType; -import constants.ItemConstants; -import constants.ServerConstants; +import client.processor.StorageProcessor; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; -import server.MapleItemInformationProvider; -import server.MapleStorage; -import tools.FilePrinter; -import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; /** @@ -44,122 +33,6 @@ import tools.data.input.SeekableLittleEndianAccessor; public final class StorageHandler extends AbstractMaplePacketHandler { @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - MapleCharacter chr = c.getPlayer(); - byte mode = slea.readByte(); - final MapleStorage storage = chr.getStorage(); - - if (chr.getLevel() < 15){ - chr.dropMessage(1, "You may only use the storage once you have reached level 15."); - c.announce(MaplePacketCreator.enableActions()); - return; - } - if (mode == 4) { // take out - byte type = slea.readByte(); - byte slot = slea.readByte(); - if (slot < 0 || slot > storage.getSlots()) { // removal starts at zero - AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit with storage."); - FilePrinter.print(FilePrinter.EXPLOITS + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + " tried to work with storage slot " + slot + "\r\n"); - c.disconnect(true, false); - return; - } - slot = storage.getSlot(MapleInventoryType.getByType(type), slot); - Item item = storage.getItem(slot); - if (item != null) { - if (MapleItemInformationProvider.getInstance().isPickupRestricted(item.getItemId()) && chr.haveItemWithId(item.getItemId(), true)) { - c.announce(MaplePacketCreator.getStorageError((byte) 0x0C)); - return; - } - if (chr.getMap().getId() == 910000000) { - if (chr.getMeso() < 1000) { - c.announce(MaplePacketCreator.getStorageError((byte) 0x0B)); - return; - } else { - chr.gainMeso(-1000, false); - } - } - if (MapleInventoryManipulator.checkSpace(c, item.getItemId(), item.getQuantity(), item.getOwner())) { - item = storage.takeOut(slot);//actually the same but idc - String itemName = MapleItemInformationProvider.getInstance().getName(item.getItemId()); - FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " took out " + item.getQuantity() + " " + itemName + " (" + item.getItemId() + ")\r\n"); - if ((item.getFlag() & ItemConstants.KARMA) == ItemConstants.KARMA) { - item.setFlag((byte) (item.getFlag() ^ ItemConstants.KARMA)); //items with scissors of karma used on them are reset once traded - } - MapleInventoryManipulator.addFromDrop(c, item, false); - storage.sendTakenOut(c, item.getInventoryType()); - } else { - c.announce(MaplePacketCreator.getStorageError((byte) 0x0A)); - } - } - } else if (mode == 5) { // store - short slot = slea.readShort(); - int itemId = slea.readInt(); - short quantity = slea.readShort(); - MapleInventoryType slotType = ItemConstants.getInventoryType(itemId); - MapleInventory Inv = chr.getInventory(slotType); - if (slot < 1 || slot > Inv.getSlotLimit()) { //player inv starts at one - AutobanFactory.PACKET_EDIT.alert(c.getPlayer(), c.getPlayer().getName() + " tried to packet edit with storage."); - FilePrinter.print(FilePrinter.EXPLOITS + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + " tried to store item at slot " + slot + "\r\n"); - c.disconnect(true, false); - return; - } - if (quantity < 1 || chr.getItemQuantity(itemId, false) < quantity) { - c.announce(MaplePacketCreator.enableActions()); - return; - } - if (storage.isFull()) { - c.announce(MaplePacketCreator.getStorageError((byte) 0x11)); - return; - } - short meso = (short) (chr.getMap().getId() == 910000000 ? -500 : -100); - if (chr.getMeso() < meso) { - c.announce(MaplePacketCreator.getStorageError((byte) 0x0B)); - } else { - MapleInventoryType invType = ItemConstants.getInventoryType(itemId); - Item item = chr.getInventory(invType).getItem(slot).copy(); - if (item.getItemId() == itemId && (item.getQuantity() >= quantity || ItemConstants.isRechargeable(itemId))) { - if (ItemConstants.isRechargeable(itemId)) { - quantity = item.getQuantity(); - } - chr.gainMeso(meso, false, true, false); - MapleInventoryManipulator.removeFromSlot(c, invType, slot, quantity, false); - item.setQuantity(quantity); - storage.store(item); - storage.sendStored(c, ItemConstants.getInventoryType(itemId)); - String itemName = MapleItemInformationProvider.getInstance().getName(item.getItemId()); - FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " stored " + item.getQuantity() + " " + itemName + " (" + item.getItemId() + ")\r\n"); - } - } - } else if (mode == 6) { // arrange items - if(ServerConstants.USE_STORAGE_ITEM_SORT) storage.arrangeItems(c); - c.announce(MaplePacketCreator.enableActions()); - } else if (mode == 7) { // meso - int meso = slea.readInt(); - int storageMesos = storage.getMeso(); - int playerMesos = chr.getMeso(); - if ((meso > 0 && storageMesos >= meso) || (meso < 0 && playerMesos >= -meso)) { - if (meso < 0 && (storageMesos - meso) < 0) { - meso = -2147483648 + storageMesos; - if (meso < playerMesos) { - c.announce(MaplePacketCreator.enableActions()); - return; - } - } else if (meso > 0 && (playerMesos + meso) < 0) { - meso = 2147483647 - playerMesos; - if (meso > storageMesos) { - c.announce(MaplePacketCreator.enableActions()); - return; - } - } - storage.setMeso(storageMesos - meso); - chr.gainMeso(meso, false, true, false); - FilePrinter.print(FilePrinter.STORAGE + c.getPlayer().getName() + ".txt", c.getPlayer().getName() + (meso > 0 ? " took out " : " stored ") + Math.abs(meso) + " mesos\r\n"); - } else { - c.announce(MaplePacketCreator.enableActions()); - return; - } - storage.sendMeso(c); - } else if (mode == 8) {// close - storage.close(); - } + StorageProcessor.storageAction(slea, c); } } \ No newline at end of file diff --git a/src/net/server/channel/handlers/TakeDamageHandler.java b/src/net/server/channel/handlers/TakeDamageHandler.java index eee44f5703..39e4343a43 100644 --- a/src/net/server/channel/handlers/TakeDamageHandler.java +++ b/src/net/server/channel/handlers/TakeDamageHandler.java @@ -42,7 +42,7 @@ import java.util.ArrayList; import java.util.List; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleStatEffect; import server.life.MapleLifeFactory.loseItem; import server.life.MapleMonster; diff --git a/src/net/server/channel/handlers/TransferNameHandler.java b/src/net/server/channel/handlers/TransferNameHandler.java new file mode 100644 index 0000000000..dafd5c7972 --- /dev/null +++ b/src/net/server/channel/handlers/TransferNameHandler.java @@ -0,0 +1,46 @@ +/* + This file is part of the HeavenMS (MapleSolaxiaV2) 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.channel.handlers; + +import client.MapleClient; +import net.AbstractMaplePacketHandler; +import tools.MaplePacketCreator; +import tools.data.input.SeekableLittleEndianAccessor; + +/** + * + * @author Ronan + */ +public final class TransferNameHandler extends AbstractMaplePacketHandler { + + @Override + public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { + slea.readInt(); //cid + int birthday = slea.readInt(); + if (!CashOperationHandler.checkBirthday(c, birthday)) { + c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC4)); + c.announce(MaplePacketCreator.enableActions()); + return; + } + + c.announce(MaplePacketCreator.sendNameTransferRules(4)); + } +} \ No newline at end of file diff --git a/src/net/server/channel/handlers/TransferNameResultHandler.java b/src/net/server/channel/handlers/TransferNameResultHandler.java new file mode 100644 index 0000000000..35e4c18e26 --- /dev/null +++ b/src/net/server/channel/handlers/TransferNameResultHandler.java @@ -0,0 +1,40 @@ +/* + This file is part of the HeavenMS (MapleSolaxiaV2) 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.channel.handlers; + +import client.MapleCharacter; +import client.MapleClient; +import net.AbstractMaplePacketHandler; +import tools.MaplePacketCreator; +import tools.data.input.SeekableLittleEndianAccessor; + +/** + * + * @author Ronan + */ +public final class TransferNameResultHandler extends AbstractMaplePacketHandler { + + @Override + public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { + String name = slea.readMapleAsciiString(); + c.announce(MaplePacketCreator.sendNameTransferCheck(MapleCharacter.canCreateChar(name))); + } +} \ No newline at end of file diff --git a/src/net/server/channel/handlers/TransferWorldHandler.java b/src/net/server/channel/handlers/TransferWorldHandler.java new file mode 100644 index 0000000000..806a336804 --- /dev/null +++ b/src/net/server/channel/handlers/TransferWorldHandler.java @@ -0,0 +1,46 @@ +/* + This file is part of the HeavenMS (MapleSolaxiaV2) 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.channel.handlers; + +import client.MapleClient; +import net.AbstractMaplePacketHandler; +import tools.MaplePacketCreator; +import tools.data.input.SeekableLittleEndianAccessor; + +/** + * + * @author Ronan + */ +public final class TransferWorldHandler extends AbstractMaplePacketHandler { + + @Override + public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { + slea.readInt(); //cid + int birthday = slea.readInt(); + if (!CashOperationHandler.checkBirthday(c, birthday)) { + c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC4)); + c.announce(MaplePacketCreator.enableActions()); + return; + } + + c.announce(MaplePacketCreator.sendWorldTransferRules(9)); + } +} \ No newline at end of file diff --git a/src/net/server/channel/handlers/UseCashItemHandler.java b/src/net/server/channel/handlers/UseCashItemHandler.java index 789863aee0..f8c4367a7e 100644 --- a/src/net/server/channel/handlers/UseCashItemHandler.java +++ b/src/net/server/channel/handlers/UseCashItemHandler.java @@ -27,12 +27,15 @@ import client.MapleJob; import client.MapleStat; import client.Skill; import client.SkillFactory; +import client.creator.veteran.*; import client.inventory.Equip; import client.inventory.Equip.ScrollResult; import client.inventory.Item; import client.inventory.MapleInventoryType; import client.inventory.MaplePet; import client.inventory.ModifyInventory; +import client.inventory.manipulator.MapleInventoryManipulator; +import client.inventory.manipulator.MapleKarmaManipulator; import constants.GameConstants; import constants.ItemConstants; import constants.ServerConstants; @@ -45,7 +48,6 @@ import java.util.List; import net.AbstractMaplePacketHandler; import net.server.Server; import scripting.npc.NPCScriptManager; -import server.MapleInventoryManipulator; import server.MapleItemInformationProvider; import server.MapleShop; import server.MapleShopFactory; @@ -64,12 +66,15 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler { @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { final MapleCharacter player = c.getPlayer(); - if (System.currentTimeMillis() - player.getLastUsedCashItem() < 3000) { + + long timeNow = System.currentTimeMillis(); + if (timeNow - player.getLastUsedCashItem() < 3000) { player.dropMessage(1, "You have used a cash item recently. Wait a moment, then try again."); c.announce(MaplePacketCreator.enableActions()); return; } - player.setLastUsedCashItem(System.currentTimeMillis()); + player.setLastUsedCashItem(timeNow); + MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); slea.readShort(); int itemId = slea.readInt(); @@ -389,7 +394,7 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler { if (item == null) //hack { return; - } else if (((item.getFlag() & ItemConstants.UNTRADEABLE) == ItemConstants.UNTRADEABLE) || ii.isDropRestricted(item.getItemId())) { + } else if (((item.getFlag() & ItemConstants.UNTRADEABLE) == ItemConstants.UNTRADEABLE) || (ii.isDropRestricted(item.getItemId()) && !MapleKarmaManipulator.hasKarmaFlag(item))) { player.dropMessage(1, "You cannot trade this item."); c.announce(MaplePacketCreator.enableActions()); return; @@ -511,6 +516,56 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler { } }, 1000 * 10); remove(c, itemId); + } else if (itemType == 543) { + if(itemId == 5432000 && !c.gainCharacterSlot()) { + player.dropMessage(1, "You have already used up all 12 extra character slots."); + c.announce(MaplePacketCreator.enableActions()); + return; + } + + String name = slea.readMapleAsciiString(); + int face = slea.readInt(); + int hair = slea.readInt(); + int haircolor = slea.readInt(); + int skin = slea.readInt(); + int gender = slea.readInt(); + int jobid = slea.readInt(); + int improveSp = slea.readInt(); + + int createStatus; + switch(jobid) { + case 0: + createStatus = WarriorCreator.createCharacter(c, name, face, hair + haircolor, skin, gender, improveSp); + break; + + case 1: + createStatus = MagicianCreator.createCharacter(c, name, face, hair + haircolor, skin, gender, improveSp); + break; + + case 2: + createStatus = BowmanCreator.createCharacter(c, name, face, hair + haircolor, skin, gender, improveSp); + break; + + case 3: + createStatus = ThiefCreator.createCharacter(c, name, face, hair + haircolor, skin, gender, improveSp); + break; + + default: + createStatus = PirateCreator.createCharacter(c, name, face, hair + haircolor, skin, gender, improveSp); + } + + if(createStatus == 0) { + c.announce(MaplePacketCreator.sendMapleLifeError(0)); // success! + + player.showHint("#bSuccess#k on creation of the new character through the Maple Life card."); + remove(c, itemId); + } else { + if(createStatus == -1) { // check name + c.announce(MaplePacketCreator.sendMapleLifeNameError()); + } else { + c.announce(MaplePacketCreator.sendMapleLifeError(-1 * createStatus)); + } + } } else if (itemType == 545) { // MiuMiu's travel store if (player.getShop() == null) { MapleShop shop = MapleShopFactory.getInstance().getShop(1338); @@ -527,14 +582,18 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler { MapleInventoryType type = MapleInventoryType.getByType((byte) slea.readInt()); short slot = (short) slea.readInt(); Item item = c.getPlayer().getInventory(type).getItem(slot); - if (item == null || item.getQuantity() <= 0 || (item.getFlag() & ItemConstants.KARMA) > 0 && ii.isKarmaAble(item.getItemId())) { + if (item == null || item.getQuantity() <= 0 || MapleKarmaManipulator.hasKarmaFlag(item) || !ii.isKarmaAble(item.getItemId())) { c.announce(MaplePacketCreator.enableActions()); return; } - if (!type.equals(MapleInventoryType.USE)) { - item.setFlag((byte) ItemConstants.KARMA); + + if(MapleKarmaManipulator.hasUsedKarmaFlag(item)) { + c.getPlayer().dropMessage(6, "Scissors of Karma was already used on this item."); + c.announce(MaplePacketCreator.enableActions()); + return; } - + + MapleKarmaManipulator.setKarmaFlag(item); c.getPlayer().forceUpdateItem(item); remove(c, itemId); c.announce(MaplePacketCreator.enableActions()); diff --git a/src/net/server/channel/handlers/UseCatchItemHandler.java b/src/net/server/channel/handlers/UseCatchItemHandler.java index 2965970592..85c93c01b6 100644 --- a/src/net/server/channel/handlers/UseCatchItemHandler.java +++ b/src/net/server/channel/handlers/UseCatchItemHandler.java @@ -27,7 +27,7 @@ import client.inventory.MapleInventoryType; import client.autoban.AutobanManager; import constants.ItemConstants; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.life.MapleMonster; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; diff --git a/src/net/server/channel/handlers/UseItemHandler.java b/src/net/server/channel/handlers/UseItemHandler.java index c0cf69b2ca..ec7f107b65 100644 --- a/src/net/server/channel/handlers/UseItemHandler.java +++ b/src/net/server/channel/handlers/UseItemHandler.java @@ -29,7 +29,7 @@ import client.inventory.MapleInventoryType; import constants.ItemConstants; import constants.ServerConstants; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; diff --git a/src/net/server/channel/handlers/UseMapleLifeHandler.java b/src/net/server/channel/handlers/UseMapleLifeHandler.java index b28f3aafac..77f781e7fb 100644 --- a/src/net/server/channel/handlers/UseMapleLifeHandler.java +++ b/src/net/server/channel/handlers/UseMapleLifeHandler.java @@ -1,8 +1,6 @@ /* - This file is part of the OdinMS Maple Story Server - Copyright (C) 2008 Patrick Huy - Matthias Butz - Jan Christian Meyer + 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 @@ -21,6 +19,7 @@ */ package net.server.channel.handlers; +import client.MapleCharacter; import client.MapleClient; import net.AbstractMaplePacketHandler; import tools.MaplePacketCreator; @@ -28,10 +27,28 @@ import tools.data.input.SeekableLittleEndianAccessor; /** * - * @author Jay Estrella + * @author RonanLana */ public class UseMapleLifeHandler extends AbstractMaplePacketHandler { + @Override public void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - c.announce(MaplePacketCreator.charNameResponse(slea.readMapleAsciiString(), false)); + MapleCharacter player = c.getPlayer(); + long timeNow = System.currentTimeMillis(); + + if(timeNow - player.getLastUsedCashItem() < 3000) { + player.dropMessage(5, "Please wait a moment before trying again."); + c.announce(MaplePacketCreator.sendMapleLifeError(3)); + c.announce(MaplePacketCreator.enableActions()); + return; + } + player.setLastUsedCashItem(timeNow); + + String name = slea.readMapleAsciiString(); + if(MapleCharacter.canCreateChar(name)) { + c.announce(MaplePacketCreator.sendMapleLifeCharacterInfo()); + } else { + c.announce(MaplePacketCreator.sendMapleLifeNameError()); + } + c.announce(MaplePacketCreator.enableActions()); } } diff --git a/src/net/server/channel/handlers/UseMountFoodHandler.java b/src/net/server/channel/handlers/UseMountFoodHandler.java index f54c8483cb..04e9649a35 100644 --- a/src/net/server/channel/handlers/UseMountFoodHandler.java +++ b/src/net/server/channel/handlers/UseMountFoodHandler.java @@ -22,30 +22,51 @@ package net.server.channel.handlers; import client.MapleClient; +import client.MapleCharacter; +import client.MapleMount; +import client.inventory.Item; import client.inventory.MapleInventoryType; import constants.ExpTable; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; /** * @author PurpleMadness + * @author Ronan */ public final class UseMountFoodHandler extends AbstractMaplePacketHandler { + @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - slea.skip(6); + slea.skip(4); + short pos = slea.readShort(); int itemid = slea.readInt(); - if (c.getPlayer().getInventory(MapleInventoryType.USE).findById(itemid) != null) { - if (c.getPlayer().getMount() != null && c.getPlayer().getMount().getTiredness() > 0) { - c.getPlayer().getMount().setTiredness(Math.max(c.getPlayer().getMount().getTiredness() - 30, 0)); - c.getPlayer().getMount().setExp(2 * c.getPlayer().getMount().getLevel() + 6 + c.getPlayer().getMount().getExp()); - int level = c.getPlayer().getMount().getLevel(); - boolean levelup = c.getPlayer().getMount().getExp() >= ExpTable.getMountExpNeededForLevel(level) && level < 31; + + MapleCharacter chr = c.getPlayer(); + MapleMount mount = chr.getMount(); + Item item = chr.getInventory(MapleInventoryType.USE).getItem(pos); + if (item != null && item.getItemId() == itemid && mount != null) { + float healedFactor; + c.lockClient(); + try { + int curTiredness = mount.getTiredness(); + int healedTiredness = Math.min(curTiredness, 30); + + healedFactor = (float) healedTiredness / 30; + mount.setTiredness(curTiredness - healedTiredness); + } finally { + c.unlockClient(); + } + + if (healedFactor > 0.0f) { + mount.setExp(mount.getExp() + (int) Math.ceil(healedFactor * (2 * mount.getLevel() + 6))); + int level = mount.getLevel(); + boolean levelup = mount.getExp() >= ExpTable.getMountExpNeededForLevel(level) && level < 31; if (levelup) { - c.getPlayer().getMount().setLevel(level + 1); + mount.setLevel(level + 1); } - c.getPlayer().getMap().broadcastMessage(MaplePacketCreator.updateMount(c.getPlayer().getId(), c.getPlayer().getMount(), levelup)); + chr.getMap().broadcastMessage(MaplePacketCreator.updateMount(chr.getId(), mount, levelup)); MapleInventoryManipulator.removeById(c, MapleInventoryType.USE, itemid, 1, true, false); } } diff --git a/src/net/server/channel/handlers/UseSolomonHandler.java b/src/net/server/channel/handlers/UseSolomonHandler.java index 032ee13efc..78db56df87 100644 --- a/src/net/server/channel/handlers/UseSolomonHandler.java +++ b/src/net/server/channel/handlers/UseSolomonHandler.java @@ -25,7 +25,7 @@ import client.MapleClient; import client.inventory.Item; import client.inventory.MapleInventoryType; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; diff --git a/src/net/server/channel/handlers/UseSummonBagHandler.java b/src/net/server/channel/handlers/UseSummonBagHandler.java index 4e4e2866af..704588172e 100644 --- a/src/net/server/channel/handlers/UseSummonBagHandler.java +++ b/src/net/server/channel/handlers/UseSummonBagHandler.java @@ -26,7 +26,7 @@ import client.inventory.Item; import client.inventory.MapleInventoryType; import tools.Randomizer; import net.AbstractMaplePacketHandler; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; import server.life.MapleLifeFactory; import tools.MaplePacketCreator; diff --git a/src/net/server/channel/handlers/WeddingHandler.java b/src/net/server/channel/handlers/WeddingHandler.java index 8ea4397480..903fadb363 100644 --- a/src/net/server/channel/handlers/WeddingHandler.java +++ b/src/net/server/channel/handlers/WeddingHandler.java @@ -1,39 +1,93 @@ /* - * To change this template, choose Tools | Templates + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates * and open the template in the editor. */ + package net.server.channel.handlers; -import client.MapleCharacter; -import client.MapleClient; import client.inventory.Item; import client.inventory.MapleInventoryType; +import client.MapleCharacter; +import client.MapleClient; import constants.ItemConstants; +import tools.DatabaseConnection; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import net.AbstractMaplePacketHandler; +import client.inventory.manipulator.MapleInventoryManipulator; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; +import tools.packets.Wedding; /** * - * @author Kevin + * @author Eric */ -public class WeddingHandler extends AbstractMaplePacketHandler { - - @Override - public void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - //System.out.println("Wedding Packet: " + slea); - MapleCharacter chr = c.getPlayer(); - byte operation = slea.readByte(); - switch (operation) { - case 0x06://Add an item to the Wedding Registry - short slot = slea.readShort(); - int itemid = slea.readInt(); - short quantity = slea.readShort(); - MapleInventoryType type = ItemConstants.getInventoryType(itemid); - Item item = chr.getInventory(type).getItem(slot); - if (itemid == item.getItemId() && quantity <= item.getQuantity()) { - c.announce(MaplePacketCreator.addItemToWeddingRegistry(chr, item)); +public final class WeddingHandler extends AbstractMaplePacketHandler { + /* + public static final void OnWeddingProgress(byte action, MapleClient c) { + // -- Pelvis Bebop: + // 0x00: "We are gathered here today..." + // 0x01: "Very well! I pronounce you..." + // 0x02: "You two truly are a sight to..." + // 0x03: Wedding Ceremony Ended, initialize the Wedding Effect upon the two married characters + // -- High Priest John: (Unknown action bytes) + // 0x00: " " + // 0x01: " " + // 0x02: "Do you wish to bless this couple?..." + // 0x03: Wedding Ceremony Ended, initialize the Wedding Effect upon the two married characters + if (c.getPlayer().getWedding() != null) { + if (c.getPlayer().getGender() == 0 ? c.getPlayer().getWedding().isExistantGroom(c.getPlayer().getId()) : c.getPlayer().getWedding().isExistantBride(c.getPlayer().getId())) { + c.getPlayer().getMap().broadcastMessage(Wedding.OnWeddingProgress(action == 2, c.getPlayer().getId(), c.getPlayer().getPartnerId(), (byte)(action+1))); + c.getPlayer().getWedding().incrementStage(); + c.getPlayer().getPartner().getWedding().incrementStage(); // pls don't b a bitch and throw npe ):< + if (action == 2) { + c.getPlayer().setMarried(true); + c.getChannelServer().getPlayerStorage().getCharacterById(c.getPlayer().getPartnerId()).setMarried(true); } + } + } + c.announce(MaplePacketCreator.enableActions()); + } + + public static final void OnWeddingGiftResult(SeekableLittleEndianAccessor slea, MapleClient c) { + System.out.println("New WEDDING_GIFT_RESULT: " + slea.toString()); + byte mode = slea.readByte(); + switch(mode) { + case 0x06: // "SEND ITEM" + short slot = slea.readShort(); // isn't this a byte? o.O + int itemId = slea.readInt(); + short quantity = slea.readShort(); + if (c.getPlayer().getInventory(ItemConstants.getInventoryType(itemId)).getItem((byte)slot).getItemId() == itemId && c.getPlayer().getInventory(InventoryConstants.getInventoryType(itemId)).getItem((byte)slot).getQuantity() >= quantity) { + if (c.getPlayer().getWedding() == null) { + c.getPlayer().startWedding(); // TODO + } + List itemnames = new ArrayList<>(); + Item item = c.getPlayer().getInventory(ItemConstants.getInventoryType(itemId)).getItem((byte)slot); + boolean bride = false; + c.getPlayer().getWedding().registerWishlistItem(item, bride); + c.announce(Wedding.OnWeddingGiftResult((byte)11, itemnames, c.getPlayer().getWedding().getWishlistItems(bride))); // todo: remove item from inventory if success + } + case 0x08: // "EXIT" + if (slea.available() != 0) { + System.out.println("WEDDING_GIFT_RESULT: " + slea.toString()); + } + c.announce(MaplePacketCreator.enableActions()); + break; + default: { + System.out.println("Unknown Mode Found: " + mode + " : " + slea.toString()); + } } } -} + */ + + @Override + public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { + c.announce(MaplePacketCreator.enableActions()); + } +} \ No newline at end of file diff --git a/src/net/server/channel/handlers/WeddingTalkHandler.java b/src/net/server/channel/handlers/WeddingTalkHandler.java new file mode 100644 index 0000000000..40bee34039 --- /dev/null +++ b/src/net/server/channel/handlers/WeddingTalkHandler.java @@ -0,0 +1,53 @@ +/* + This file is part of the HeavenMS (MapleSolaxiaV2) 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.channel.handlers; + +import client.MapleClient; +import net.AbstractMaplePacketHandler; +import scripting.event.EventInstanceManager; +import tools.MaplePacketCreator; +import tools.data.input.SeekableLittleEndianAccessor; +import tools.packets.Wedding; + +/** + * + * @author Ronan + */ +public final class WeddingTalkHandler extends AbstractMaplePacketHandler { + + @Override + public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { + byte action = slea.readByte(); + if(action == 1) { + EventInstanceManager eim = c.getPlayer().getEventInstance(); + + if(eim != null && !(c.getPlayer().getId() == eim.getIntProperty("groomId") || c.getPlayer().getId() == eim.getIntProperty("brideId"))) { + c.announce(Wedding.OnWeddingProgress(false, 0, 0, (byte) 2)); + } else { + c.announce(Wedding.OnWeddingProgress(true, 0, 0, (byte) 3)); + } + } else { + c.announce(Wedding.OnWeddingProgress(true, 0, 0, (byte) 3)); + } + + c.announce(MaplePacketCreator.enableActions()); + } +} \ No newline at end of file diff --git a/src/net/server/channel/handlers/WeddingTalkMoreHandler.java b/src/net/server/channel/handlers/WeddingTalkMoreHandler.java new file mode 100644 index 0000000000..8165d3c01f --- /dev/null +++ b/src/net/server/channel/handlers/WeddingTalkMoreHandler.java @@ -0,0 +1,47 @@ +/* + This file is part of the HeavenMS (MapleSolaxiaV2) 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.channel.handlers; + +import client.MapleClient; +import net.AbstractMaplePacketHandler; +import scripting.event.EventInstanceManager; +import tools.MaplePacketCreator; +import tools.data.input.SeekableLittleEndianAccessor; +import tools.packets.Wedding; + +/** + * + * @author Ronan + */ +public final class WeddingTalkMoreHandler extends AbstractMaplePacketHandler { + + @Override + public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { + EventInstanceManager eim = c.getPlayer().getEventInstance(); + if(eim != null && !(c.getPlayer().getId() == eim.getIntProperty("groomId") || c.getPlayer().getId() == eim.getIntProperty("brideId"))) { + eim.gridInsert(c.getPlayer(), 1); + c.getPlayer().dropMessage(5, "High Priest John: Your blessings have been added to their love. What a noble act for a lovely couple!"); + } + + c.announce(Wedding.OnWeddingProgress(true, 0, 0, (byte) 3)); + c.announce(MaplePacketCreator.enableActions()); + } +} \ No newline at end of file diff --git a/src/net/server/guild/MapleGuild.java b/src/net/server/guild/MapleGuild.java index 67cccda656..af647714cb 100644 --- a/src/net/server/guild/MapleGuild.java +++ b/src/net/server/guild/MapleGuild.java @@ -45,9 +45,7 @@ import tools.MaplePacketCreator; import tools.locks.MonitoredLockType; public class MapleGuild { - public final static int CREATE_GUILD_COST = 1500000; - public final static int CHANGE_EMBLEM_COST = 5000000; - + private enum BCOp { NONE, DISBAND, EMBLEMCHANGE } diff --git a/src/net/server/handlers/login/CreateCharHandler.java b/src/net/server/handlers/login/CreateCharHandler.java index dd7320e50d..86431bd7e3 100644 --- a/src/net/server/handlers/login/CreateCharHandler.java +++ b/src/net/server/handlers/login/CreateCharHandler.java @@ -21,20 +21,12 @@ */ package net.server.handlers.login; +import client.MapleClient; +import client.creator.novice.*; import net.AbstractMaplePacketHandler; -import net.server.Server; -import server.MapleItemInformationProvider; import tools.FilePrinter; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; -import client.MapleCharacter; -import client.MapleClient; -import client.MapleJob; -import client.MapleSkinColor; -import client.autoban.AutobanFactory; -import client.inventory.Item; -import client.inventory.MapleInventory; -import client.inventory.MapleInventoryType; public final class CreateCharHandler extends AbstractMaplePacketHandler { @@ -60,83 +52,43 @@ public final class CreateCharHandler extends AbstractMaplePacketHandler { @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - String name = slea.readMapleAsciiString(); - if (!MapleCharacter.canCreateChar(name)) { - return; - } - MapleCharacter newchar = MapleCharacter.getDefault(c); - newchar.setWorld(c.getWorld()); - - int job = slea.readInt(); + String name = slea.readMapleAsciiString(); + int job = slea.readInt(); int face = slea.readInt(); int hair = slea.readInt(); - int hairColor = slea.readInt(); + int haircolor = slea.readInt(); int skincolor = slea.readInt(); - newchar.setSkinColor(MapleSkinColor.getById(skincolor)); int top = slea.readInt(); int bottom = slea.readInt(); int shoes = slea.readInt(); int weapon = slea.readInt(); - newchar.setGender(slea.readByte()); - newchar.setName(name); - newchar.setHair(hair + hairColor); - newchar.setFace(face); - - int [] items = new int [] {weapon, top, bottom, shoes, hair, face}; + int gender = slea.readByte(); + + int [] items = new int [] {weapon, top, bottom, shoes, hair, face}; for (int i = 0; i < items.length; i++){ if (!isLegal(items[i])) { - AutobanFactory.PACKET_EDIT.alert(newchar, name + " tried to packet edit in character creation."); - FilePrinter.printError(FilePrinter.EXPLOITS + newchar + ".txt", "Tried to packet edit in char creation."); + FilePrinter.printError(FilePrinter.EXPLOITS + name + ".txt", "Owner from account '" + c.getAccountName() + "' tried to packet edit in char creation."); c.disconnect(true, false); return; } } - - if (job == 0) { // Knights of Cygnus - newchar.setJob(MapleJob.NOBLESSE); - newchar.setMapId(130030000); - newchar.getInventory(MapleInventoryType.ETC).addItem(new Item(4161047, (short) 0, (short) 1)); + + int status; + if (job == 0) { // Knights of Cygnus + status = NoblesseCreator.createCharacter(c, name, face, hair + haircolor, skincolor, top, bottom, shoes, weapon, gender); } else if (job == 1) { // Adventurer - newchar.setJob(MapleJob.BEGINNER); - newchar.setMapId(/*specialJobType == 2 ? 3000600 : */10000); - newchar.getInventory(MapleInventoryType.ETC).addItem(new Item(4161001, (short) 0, (short) 1)); + status = BeginnerCreator.createCharacter(c, name, face, hair + haircolor, skincolor, top, bottom, shoes, weapon, gender); } else if (job == 2) { // Aran - newchar.setJob(MapleJob.LEGEND); - newchar.setMapId(914000000); - newchar.getInventory(MapleInventoryType.ETC).addItem(new Item(4161048, (short) 0, (short) 1)); + status = LegendCreator.createCharacter(c, name, face, hair + haircolor, skincolor, top, bottom, shoes, weapon, gender); } else { c.announce(MaplePacketCreator.deleteCharResponse(0, 9)); return; } - - MapleInventory equipped = newchar.getInventory(MapleInventoryType.EQUIPPED); - MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); - Item eq_top = ii.getEquipById(top); - eq_top.setPosition((byte) -5); - equipped.addFromDB(eq_top); - - Item eq_bottom = ii.getEquipById(bottom); - eq_bottom.setPosition((byte) -6); - equipped.addFromDB(eq_bottom); - - Item eq_shoes = ii.getEquipById(shoes); - eq_shoes.setPosition((byte) -7); - equipped.addFromDB(eq_shoes); - - Item eq_weapon = ii.getEquipById(weapon); - eq_weapon.setPosition((byte) -11); - equipped.addFromDB(eq_weapon.copy()); - - if (!newchar.insertNewChar()) { - c.announce(MaplePacketCreator.deleteCharResponse(0, 9)); - return; - } - c.announce(MaplePacketCreator.addNewCharEntry(newchar)); - - Server.getInstance().createCharacterid(newchar.getAccountID(), newchar.getId(), newchar.getWorld()); - Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.sendYellowTip("[NEW CHAR]: " + c.getAccountName() + " has created a new character with IGN " + name)); + if (status == -2) { + c.announce(MaplePacketCreator.deleteCharResponse(0, 9)); + } } } \ No newline at end of file diff --git a/src/net/server/handlers/login/ViewCharHandler.java b/src/net/server/handlers/login/ViewCharHandler.java index 99ccfbf475..3014c606b6 100644 --- a/src/net/server/handlers/login/ViewCharHandler.java +++ b/src/net/server/handlers/login/ViewCharHandler.java @@ -30,6 +30,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; import net.AbstractMaplePacketHandler; +import net.server.Server; import tools.DatabaseConnection; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; @@ -42,6 +43,8 @@ public final class ViewCharHandler extends AbstractMaplePacketHandler { List worlds; List chars; + int wlen = Server.getInstance().getWorlds().size(); + Connection con = DatabaseConnection.getConnection(); try (PreparedStatement ps = con.prepareStatement("SELECT world, id FROM characters WHERE accountid = ?")) { ps.setInt(1, c.getAccID()); @@ -51,6 +54,8 @@ public final class ViewCharHandler extends AbstractMaplePacketHandler { try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { int cworld = rs.getByte("world"); + if(cworld >= wlen) continue; + boolean inside = false; for (int w : worlds) { if (w == cworld) { diff --git a/src/net/server/worker/WeddingReservationWorker.java b/src/net/server/worker/WeddingReservationWorker.java new file mode 100644 index 0000000000..cfa93fa193 --- /dev/null +++ b/src/net/server/worker/WeddingReservationWorker.java @@ -0,0 +1,56 @@ +/* + This file is part of the HeavenMS (MapleSolaxiaV2) MapleStory Server + Copyleft (L) 2017 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 java.util.Set; +import net.server.world.World; +import net.server.channel.Channel; +import tools.Pair; + +/** + * @author Ronan + */ +public class WeddingReservationWorker extends BaseWorker implements Runnable { + + @Override + public void run() { + for(Channel ch : wserv.getChannels()) { + Pair>> wedding; + + wedding = ch.getNextWeddingReservation(true); // start cathedral + if(wedding != null) { + ch.setOngoingWedding(true, wedding.getLeft(), wedding.getRight().getLeft(), wedding.getRight().getRight()); + } else { + ch.setOngoingWedding(true, null, null, null); + } + + wedding = ch.getNextWeddingReservation(false); // start chapel + if(wedding != null) { + ch.setOngoingWedding(false, wedding.getLeft(), wedding.getRight().getLeft(), wedding.getRight().getRight()); + } else { + ch.setOngoingWedding(false, null, null, null); + } + } + } + + public WeddingReservationWorker(World world) { + super(world); + } +} diff --git a/src/net/server/world/World.java b/src/net/server/world/World.java index 6d2a273c1c..c2eae5bcf9 100644 --- a/src/net/server/world/World.java +++ b/src/net/server/world/World.java @@ -31,7 +31,9 @@ import constants.ServerConstants; import java.sql.Connection; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -59,6 +61,7 @@ import net.server.worker.CharacterAutosaverWorker; import net.server.worker.MountTirednessWorker; import net.server.worker.PetFullnessWorker; import net.server.worker.TimedMapObjectWorker; +import net.server.worker.WeddingReservationWorker; import net.server.PlayerStorage; import net.server.Server; import net.server.channel.Channel; @@ -82,13 +85,18 @@ public class World { private String eventmsg; private List channels = new ArrayList<>(); private Map pnpcStep = new HashMap<>(); + private Map pnpcPodium = new HashMap<>(); private Map messengers = new HashMap<>(); private AtomicInteger runningMessengerId = new AtomicInteger(); private Map families = new LinkedHashMap<>(); + private Map relationships = new HashMap<>(); + private Map> relationshipCouples = new HashMap<>(); private Map gsStore = new HashMap<>(); private PlayerStorage players = new PlayerStorage(); private Set queuedGuilds = new HashSet<>(); + private Map, Pair>> queuedMarriages = new HashMap<>(); + private Map> marriageGuests = new HashMap<>(); private Map parties = new HashMap<>(); private AtomicInteger runningPartyId = new AtomicInteger(); @@ -119,6 +127,7 @@ public class World { private Lock timedMapObjectLock = new MonitoredReentrantLock(MonitoredLockType.MAP_OBJS, true); private ScheduledFuture charactersSchedule; + private ScheduledFuture marriagesSchedule; public World(int world, int flag, String eventmsg, int exprate, int droprate, int mesorate, int questrate) { this.id = world; @@ -134,10 +143,13 @@ public class World { petUpdate = System.currentTimeMillis(); mountUpdate = petUpdate; - petsSchedule = TimerManager.getInstance().register(new PetFullnessWorker(this), 60 * 1000, 60 * 1000); - mountsSchedule = TimerManager.getInstance().register(new MountTirednessWorker(this), 60 * 1000, 60 * 1000); - timedMapObjectsSchedule = TimerManager.getInstance().register(new TimedMapObjectWorker(this), 60 * 1000, 60 * 1000); - charactersSchedule = TimerManager.getInstance().register(new CharacterAutosaverWorker(this), 60 * 60 * 1000, 60 * 60 * 1000); + TimerManager tman = TimerManager.getInstance(); + petsSchedule = tman.register(new PetFullnessWorker(this), 60 * 1000, 60 * 1000); + mountsSchedule = tman.register(new MountTirednessWorker(this), 60 * 1000, 60 * 1000); + timedMapObjectsSchedule = tman.register(new TimedMapObjectWorker(this), 60 * 1000, 60 * 1000); + charactersSchedule = tman.register(new CharacterAutosaverWorker(this), 60 * 60 * 1000, 60 * 60 * 1000); + marriagesSchedule = tman.register(new WeddingReservationWorker(this), ServerConstants.WEDDING_RESERVATION_INTERVAL * 60 * 1000, ServerConstants.WEDDING_RESERVATION_INTERVAL * 60 * 1000); + } public List getChannels() { @@ -409,6 +421,96 @@ public class World { queuedGuilds.remove(guildId); } + public boolean isMarriageQueued(int marriageId) { + return queuedMarriages.containsKey(marriageId); + } + + public Pair getMarriageQueuedLocation(int marriageId) { + Pair, Pair> qm = queuedMarriages.get(marriageId); + return (qm != null) ? qm.getLeft() : null; + } + + public Pair getMarriageQueuedCouple(int marriageId) { + Pair, Pair> qm = queuedMarriages.get(marriageId); + return (qm != null) ? qm.getRight() : null; + } + + public void putMarriageQueued(int marriageId, boolean cathedral, boolean premium, int groomId, int brideId) { + queuedMarriages.put(marriageId, new Pair<>(new Pair<>(cathedral, premium), new Pair<>(groomId, brideId))); + marriageGuests.put(marriageId, new HashSet()); + } + + public Pair> removeMarriageQueued(int marriageId) { + Boolean type = queuedMarriages.remove(marriageId).getLeft().getRight(); + Set guests = marriageGuests.remove(marriageId); + + return new Pair<>(type, guests); + } + + public synchronized boolean addMarriageGuest(int marriageId, int playerId) { + Set guests = marriageGuests.get(marriageId); + if(guests != null) { + if(guests.contains(playerId)) return false; + + guests.add(playerId); + return true; + } + + return false; + } + + public Pair getWeddingCoupleForGuest(int guestId, Boolean cathedral) { + for(Channel ch : channels) { + Pair p = ch.getWeddingCoupleForGuest(guestId, cathedral); + if(p != null) { + return p; + } + } + + List possibleWeddings = new LinkedList<>(); + for(Entry> mg : new HashSet<>(marriageGuests.entrySet())) { + if(mg.getValue().contains(guestId)) { + Pair loc = getMarriageQueuedLocation(mg.getKey()); + if(loc != null && cathedral.equals(loc.getLeft())) { + possibleWeddings.add(mg.getKey()); + } + } + } + + int pwSize = possibleWeddings.size(); + if(pwSize == 0) { + return null; + } else if(pwSize > 1) { + int selectedPw = -1; + int selectedPos = Integer.MAX_VALUE; + + for(Integer pw : possibleWeddings) { + for(Channel ch : channels) { + int pos = ch.getWeddingReservationStatus(pw, cathedral); + if(pos != -1) { + if(pos < selectedPos) { + selectedPos = pos; + selectedPw = pw; + break; + } + } + } + } + + if(selectedPw == -1) return null; + + possibleWeddings.clear(); + possibleWeddings.add(selectedPw); + } + + return getMarriageQueuedCouple(possibleWeddings.get(0)); + } + + public void debugMarriageStatus() { + System.out.println("Queued marriages: " + queuedMarriages); + System.out.println("Guest list: " + marriageGuests); + } + public MapleParty createParty(MaplePartyCharacter chrfor) { int partyid = runningPartyId.getAndIncrement(); MapleParty party = new MapleParty(partyid, chrfor); @@ -1089,34 +1191,54 @@ public class World { } public void setPlayerNpcMapStep(int mapid, int step) { - setPlayerNpcMapStep(mapid, step, false); + setPlayerNpcMapData(mapid, step, -1, false); } - public void setPlayerNpcMapStep(int mapid, int step, boolean silent) { + public void setPlayerNpcMapPodiumData(int mapid, int podium) { + setPlayerNpcMapData(mapid, -1, podium, false); + } + + public void setPlayerNpcMapData(int mapid, int step, int podium) { + setPlayerNpcMapData(mapid, step, podium, true); + } + + private static void executePlayerNpcMapDataUpdate(Connection con, boolean isPodium, Map pnpcData, int value, int worldid, int mapid) throws SQLException { + PreparedStatement ps; + if(pnpcData.containsKey(mapid)) { + ps = con.prepareStatement("UPDATE playernpcs_field SET " + (isPodium ? "podium" : "step") + " = ? WHERE world = ? AND map = ?"); + } else { + ps = con.prepareStatement("INSERT INTO playernpcs_field (" + (isPodium ? "podium" : "step") + ", world, map) VALUES (?, ?, ?)"); + } + + ps.setInt(1, value); + ps.setInt(2, worldid); + ps.setInt(3, mapid); + ps.executeUpdate(); + + ps.close(); + } + + private void setPlayerNpcMapData(int mapid, int step, int podium, boolean silent) { if(!silent) { try { Connection con = DatabaseConnection.getConnection(); - - PreparedStatement ps; - if(pnpcStep.containsKey(mapid)) { - ps = con.prepareStatement("UPDATE playernpcs_field SET step = ? WHERE world = ? AND map = ?"); - } else { - ps = con.prepareStatement("INSERT INTO playernpcs_field (step, world, map) VALUES (?, ?, ?)"); + + if(step != -1) { + executePlayerNpcMapDataUpdate(con, false, pnpcStep, step, id, mapid); } - - ps.setInt(1, step); - ps.setInt(2, id); - ps.setInt(3, mapid); - ps.executeUpdate(); - - ps.close(); + + if(podium != -1) { + executePlayerNpcMapDataUpdate(con, true, pnpcPodium, podium, id, mapid); + } + con.close(); } catch (SQLException e) { e.printStackTrace(); } } - pnpcStep.put(mapid, (byte) step); + if(step != -1) pnpcStep.put(mapid, (byte) step); + if(podium != -1) pnpcPodium.put(mapid, (short) podium); } public int getPlayerNpcMapStep(int mapid) { @@ -1127,6 +1249,19 @@ public class World { } } + public int getPlayerNpcMapPodiumData(int mapid) { + try { + return pnpcPodium.get(mapid); + } catch (NullPointerException npe) { + return 1; + } + } + + public void resetPlayerNpcMapData() { + pnpcStep.clear(); + pnpcPodium.clear(); + } + public void setServerMessage(String msg) { for (Channel ch : channels) { ch.setServerMessage(msg); @@ -1140,33 +1275,160 @@ public class World { } public List> getAvailableItemBundles(int itemid) { - List> hmsAvailable = new ArrayList<>(); + List> hmsAvailable = new ArrayList<>(); - for (MapleHiredMerchant hm : getActiveMerchants()) { - List itemBundles = hm.sendAvailableBundles(itemid); - - for(MaplePlayerShopItem mpsi : itemBundles) { - hmsAvailable.add(new Pair<>(mpsi, (AbstractMapleMapObject) hm)); - } + for (MapleHiredMerchant hm : getActiveMerchants()) { + List itemBundles = hm.sendAvailableBundles(itemid); + + for(MaplePlayerShopItem mpsi : itemBundles) { + hmsAvailable.add(new Pair<>(mpsi, (AbstractMapleMapObject) hm)); + } + } + + for (MaplePlayerShop ps : getActivePlayerShops()) { + List itemBundles = ps.sendAvailableBundles(itemid); + + for(MaplePlayerShopItem mpsi : itemBundles) { + hmsAvailable.add(new Pair<>(mpsi, (AbstractMapleMapObject) ps)); + } + } + + Collections.sort(hmsAvailable, new Comparator>() { + @Override + public int compare(Pair p1, Pair p2) { + return p1.getLeft().getPrice() - p2.getLeft().getPrice(); + } + }); + + hmsAvailable.subList(0, Math.min(hmsAvailable.size(), 200)); //truncates the list to have up to 200 elements + return hmsAvailable; + } + + private void pushRelationshipCouple(Pair> couple) { + int mid = couple.getLeft(), hid = couple.getRight().getLeft(), wid = couple.getRight().getRight(); + relationshipCouples.put(mid, couple.getRight()); + relationships.put(hid, mid); + relationships.put(wid, mid); + } + + public Pair getRelationshipCouple(int relationshipId) { + Pair rc = relationshipCouples.get(relationshipId); + + if(rc == null) { + Pair> couple = getRelationshipCoupleFromDb(relationshipId, true); + if(couple == null) return null; + + pushRelationshipCouple(couple); + rc = couple.getRight(); + } + + return rc; + } + + public int getRelationshipId(int playerId) { + Integer ret = relationships.get(playerId); + + if(ret == null) { + Pair> couple = getRelationshipCoupleFromDb(playerId, false); + if(couple == null) return -1; + + pushRelationshipCouple(couple); + ret = couple.getLeft(); + } + + return ret; + } + + private static Pair> getRelationshipCoupleFromDb(int id, boolean usingMarriageId) { + try { + Connection con = DatabaseConnection.getConnection(); + Integer mid = null, hid = null, wid = null; + + PreparedStatement ps; + if(usingMarriageId) { + ps = con.prepareStatement("SELECT * FROM marriages WHERE marriageid = ?"); + ps.setInt(1, id); + } else { + ps = con.prepareStatement("SELECT * FROM marriages WHERE husbandid = ? OR wifeid = ?"); + ps.setInt(1, id); + ps.setInt(2, id); } - for (MaplePlayerShop ps : getActivePlayerShops()) { - List itemBundles = ps.sendAvailableBundles(itemid); - - for(MaplePlayerShopItem mpsi : itemBundles) { - hmsAvailable.add(new Pair<>(mpsi, (AbstractMapleMapObject) ps)); - } + ResultSet rs = ps.executeQuery(); + if(rs.next()) { + mid = rs.getInt("marriageid"); + hid = rs.getInt("husbandid"); + wid = rs.getInt("wifeid"); } + + rs.close(); + ps.close(); + con.close(); + + return (mid == null) ? null : new Pair<>(mid, new Pair<>(hid, wid)); + } catch (SQLException se) { + se.printStackTrace(); + return null; + } + } + + public int createRelationship(int groomId, int brideId) { + int ret = addRelationshipToDb(groomId, brideId); + + pushRelationshipCouple(new Pair<>(ret, new Pair<>(groomId, brideId))); + return ret; + } + + private static int addRelationshipToDb(int groomId, int brideId) { + try { + Connection con = DatabaseConnection.getConnection(); - Collections.sort(hmsAvailable, new Comparator>() { - @Override - public int compare(Pair p1, Pair p2) { - return p1.getLeft().getPrice() - p2.getLeft().getPrice(); - } - }); + PreparedStatement ps = con.prepareStatement("INSERT INTO marriages (husbandid, wifeid) VALUES (?, ?)", Statement.RETURN_GENERATED_KEYS); + ps.setInt(1, groomId); + ps.setInt(2, brideId); + ps.executeUpdate(); - hmsAvailable.subList(0, Math.min(hmsAvailable.size(), 200)); //truncates the list to have up to 200 elements - return hmsAvailable; + ResultSet rs = ps.getGeneratedKeys(); + rs.next(); + int ret = rs.getInt(1); + + rs.close(); + ps.close(); + con.close(); + return ret; + } catch (SQLException se) { + se.printStackTrace(); + return -1; + } + } + + public void deleteRelationship(int playerId, int partnerId) { + int relationshipId = relationships.get(playerId); + deleteRelationshipFromDb(relationshipId); + + relationshipCouples.remove(relationshipId); + relationships.remove(playerId); + relationships.remove(partnerId); + } + + private static void deleteRelationshipFromDb(int playerId) { + try { + Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("DELETE FROM marriages WHERE marriageid = ?"); + ps.setInt(1, playerId); + ps.executeUpdate(); + + ps.close(); + con.close(); + } catch (SQLException se) { + se.printStackTrace(); + } + } + + public void dropMessage(int type, String message) { + for (MapleCharacter player : getPlayerStorage().getAllCharacters()) { + player.dropMessage(type, message); + } } public final void shutdown() { diff --git a/src/provider/wz/XMLDomMapleData.java b/src/provider/wz/XMLDomMapleData.java index 980ced48cc..09cbccf271 100644 --- a/src/provider/wz/XMLDomMapleData.java +++ b/src/provider/wz/XMLDomMapleData.java @@ -21,6 +21,7 @@ */ package provider.wz; +import constants.GameConstants; import constants.ServerConstants; import java.awt.Point; import java.io.File; @@ -45,7 +46,6 @@ import org.xml.sax.SAXException; public class XMLDomMapleData implements MapleData { private Node node; private File imageDataDir; - private NumberFormat nf; public XMLDomMapleData(FileInputStream fis, File imageDataDir) { try { @@ -61,12 +61,10 @@ public class XMLDomMapleData implements MapleData { throw new RuntimeException(e); } this.imageDataDir = imageDataDir; - this.nf = NumberFormat.getInstance(ServerConstants.USE_UNITPRICE_WITH_COMMA ? Locale.FRANCE : Locale.UK); } private XMLDomMapleData(Node node) { this.node = node; - this.nf = NumberFormat.getInstance(ServerConstants.USE_UNITPRICE_WITH_COMMA ? Locale.FRANCE : Locale.UK); } @Override @@ -117,52 +115,44 @@ public class XMLDomMapleData implements MapleData { NamedNodeMap attributes = node.getAttributes(); MapleDataType type = getType(); switch (type) { - case DOUBLE: - case FLOAT: - case INT: - case SHORT: { - String value = attributes.getNamedItem("value").getNodeValue(); - Number nval; - - try { - nval = nf.parse(value); - } - catch(java.text.ParseException pe) { - pe.printStackTrace(); - nval = 0.0f; - } - - switch (type) { case DOUBLE: - return nval.doubleValue(); case FLOAT: - return nval.floatValue(); case INT: - return nval.intValue(); - case SHORT: - return nval.shortValue(); + case SHORT: { + String value = attributes.getNamedItem("value").getNodeValue(); + Number nval = GameConstants.parseNumber(value); + + switch (type) { + case DOUBLE: + return nval.doubleValue(); + case FLOAT: + return nval.floatValue(); + case INT: + return nval.intValue(); + case SHORT: + return nval.shortValue(); + default: + return null; + } + } + case STRING: + case UOL: { + String value = attributes.getNamedItem("value").getNodeValue(); + return value; + } + case VECTOR: { + String x = attributes.getNamedItem("x").getNodeValue(); + String y = attributes.getNamedItem("y").getNodeValue(); + return new Point(Integer.parseInt(x), Integer.parseInt(y)); + } + case CANVAS: { + String width = attributes.getNamedItem("width").getNodeValue(); + String height = attributes.getNamedItem("height").getNodeValue(); + return new FileStoredPngMapleCanvas(Integer.parseInt(width), Integer.parseInt(height), new File( + imageDataDir, getName() + ".png")); + } default: return null; - } - } - case STRING: - case UOL: { - String value = attributes.getNamedItem("value").getNodeValue(); - return value; - } - case VECTOR: { - String x = attributes.getNamedItem("x").getNodeValue(); - String y = attributes.getNamedItem("y").getNodeValue(); - return new Point(Integer.parseInt(x), Integer.parseInt(y)); - } - case CANVAS: { - String width = attributes.getNamedItem("width").getNodeValue(); - String height = attributes.getNamedItem("height").getNodeValue(); - return new FileStoredPngMapleCanvas(Integer.parseInt(width), Integer.parseInt(height), new File( - imageDataDir, getName() + ".png")); - } - default: - return null; } } diff --git a/src/scripting/AbstractPlayerInteraction.java b/src/scripting/AbstractPlayerInteraction.java index 873a21e958..1dba33b4ee 100644 --- a/src/scripting/AbstractPlayerInteraction.java +++ b/src/scripting/AbstractPlayerInteraction.java @@ -36,7 +36,7 @@ import net.server.world.MaplePartyCharacter; import scripting.event.EventInstanceManager; import scripting.event.EventManager; import scripting.npc.NPCScriptManager; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; import server.expeditions.MapleExpedition; import server.expeditions.MapleExpeditionType; @@ -61,6 +61,7 @@ import client.inventory.MapleInventory; import client.inventory.MapleInventoryType; import client.inventory.MaplePet; import client.inventory.ModifyInventory; +import constants.GameConstants; import constants.ItemConstants; import constants.ServerConstants; import server.life.MapleNPC; @@ -899,6 +900,10 @@ public class AbstractPlayerInteraction { public void environmentChange(String env, int mode) { getPlayer().getMap().broadcastMessage(MaplePacketCreator.environmentChange(env, mode)); } + + public String numberWithCommas(int number) { + return GameConstants.numberWithCommas(number); + } public Pyramid getPyramid() { return (Pyramid) getPlayer().getPartyQuest(); @@ -946,4 +951,30 @@ public class AbstractPlayerInteraction { public boolean startDungeonInstance(int dungeonid) { return c.getChannelServer().addMiniDungeon(dungeonid); } + + public boolean canGetFirstJob(int jobType) { + if (ServerConstants.USE_AUTOASSIGN_STARTERS_AP) { + return true; + } + + MapleCharacter chr = this.getPlayer(); + + switch(jobType) { + case 1: + return chr.getStr() >= 35; + + case 2: + return chr.getInt() >= 20; + + case 3: + case 4: + return chr.getDex() >= 25; + + case 5: + return chr.getDex() >= 20; + + default: + return true; + } + } } diff --git a/src/scripting/event/EventManager.java b/src/scripting/event/EventManager.java index def406db73..052c4977bf 100644 --- a/src/scripting/event/EventManager.java +++ b/src/scripting/event/EventManager.java @@ -100,14 +100,14 @@ public class EventManager { } } - private List convertToIntegerArray(List list) { + private static List convertToIntegerArray(List list) { List intList = new ArrayList<>(); for(Double d: list) intList.add(d.intValue()); return intList; } - public long getLobbyDelay() { + public static long getLobbyDelay() { return ServerConstants.EVENT_LOBBY_DELAY; } @@ -251,7 +251,7 @@ public class EventManager { return name; } - public int availableLobbyInstance() { + private int availableLobbyInstance() { List lr = getLobbyRange(); int lb = 0, hb = 0; @@ -525,8 +525,6 @@ public class EventManager { wserv.removeGuildQueued(guildId); Integer leaderId = queuedGuildLeaders.remove(guildId); - exportReadyGuild(guildId); - int place = 1; for(Integer i: queuedGuilds) { exportMovedQueueToGuild(i, place); @@ -587,8 +585,9 @@ public class EventManager { public boolean attemptStartGuildInstance() { MapleCharacter chr = null; + List guildInstance = null; while(chr == null) { - List guildInstance = getNextGuildQueue(); + guildInstance = getNextGuildQueue(); if(guildInstance == null) { return false; } @@ -596,7 +595,12 @@ public class EventManager { chr = cserv.getPlayerStorage().getCharacterById(guildInstance.get(1)); } - return startInstance(chr); + if(startInstance(chr)) { + exportReadyGuild(guildInstance.get(0)); + return true; + } else { + return false; + } } public void startQuest(MapleCharacter chr, int id, int npcid) { @@ -615,6 +619,10 @@ public class EventManager { } } + public static int getTransportationTime(int travelTime) { + return (int) Math.ceil(travelTime / ServerConstants.TRAVEL_RATE); + } + private void fillEimQueue() { Thread t = new Thread(new EventManagerWorker()); //call new thread to fill up readied instances queue t.start(); diff --git a/src/scripting/npc/NPCConversationManager.java b/src/scripting/npc/NPCConversationManager.java index d99d1a7c02..96decc56e3 100644 --- a/src/scripting/npc/NPCConversationManager.java +++ b/src/scripting/npc/NPCConversationManager.java @@ -360,7 +360,7 @@ public class NPCConversationManager extends AbstractPlayerInteraction { getPlayer().changeJob(MapleJob.getById(a)); } - public void changeJob(MapleJob job){ + public void changeJob(MapleJob job) { getPlayer().changeJob(job); } @@ -526,6 +526,10 @@ public class NPCConversationManager extends AbstractPlayerInteraction { return true; } + public boolean isUsingOldPqNpcStyle() { + return ServerConstants.USE_OLD_GMS_STYLED_PQ_NPCS && this.getPlayer().getParty() != null; + } + public Object[] getAvailableMasteryBooks() { return MapleItemInformationProvider.getInstance().usableMasteryBooks(this.getPlayer()).toArray(); } diff --git a/src/scripting/reactor/ReactorActionManager.java b/src/scripting/reactor/ReactorActionManager.java index 1653a47ca0..20c91e1556 100644 --- a/src/scripting/reactor/ReactorActionManager.java +++ b/src/scripting/reactor/ReactorActionManager.java @@ -27,6 +27,7 @@ import client.inventory.Equip; import client.inventory.Item; import client.inventory.MapleInventoryType; import constants.ItemConstants; +import constants.ServerConstants; import java.awt.Point; import java.util.ArrayList; import java.util.List; @@ -255,6 +256,11 @@ public class ReactorActionManager extends AbstractPlayerInteraction { } public void hitMonsterWithReactor(int id, int hitsToKill) { // until someone comes with a better solution, why not? + int customTime = ServerConstants.MOB_REACTOR_REFRESH_TIME; + if(customTime > 0) { + reactor.setDelay(customTime); + } + MapleMap map = reactor.getMap(); MapleMonster mm = map.getMonsterById(id); if(mm != null) { diff --git a/src/server/MapleItemInformationProvider.java b/src/server/MapleItemInformationProvider.java index eaef9ca8d9..9344e61ac5 100644 --- a/src/server/MapleItemInformationProvider.java +++ b/src/server/MapleItemInformationProvider.java @@ -1350,8 +1350,8 @@ public class MapleItemInformationProvider { return null; } scriptedItem script = new scriptedItem(MapleDataTool.getInt("spec/npc", getItemData(itemId), 0), - MapleDataTool.getString("spec/script", getItemData(itemId), ""), - MapleDataTool.getInt("spec/runOnPickup", getItemData(itemId), 0) == 1); + MapleDataTool.getString("spec/script", getItemData(itemId), ""), + MapleDataTool.getInt("spec/runOnPickup", getItemData(itemId), 0) == 1); scriptedItemCache.put(itemId, script); return scriptedItemCache.get(itemId); } @@ -1538,8 +1538,12 @@ public class MapleItemInformationProvider { public boolean canWearEquipment(MapleCharacter chr, Equip equip, int dst) { int id = equip.getItemId(); - String islot = getEquipmentSlot(id); + if(ItemConstants.isWeddingRing(id) && chr.hasJustMarried()) { + chr.dropMessage(5, "The Wedding Ring cannot be equipped on this map."); // will dc everyone due to doubled couple effect + return false; + } + String islot = getEquipmentSlot(id); if (!EquipSlot.getFromTextSlot(islot).isAllowed(dst, isCash(id))) { equip.wear(false); String itemName = MapleItemInformationProvider.getInstance().getName(equip.getItemId()); diff --git a/src/server/MapleShop.java b/src/server/MapleShop.java index 12c0a5a14f..18dab7152f 100644 --- a/src/server/MapleShop.java +++ b/src/server/MapleShop.java @@ -21,6 +21,7 @@ */ package server; +import client.inventory.manipulator.MapleInventoryManipulator; import client.MapleClient; import client.inventory.Item; import client.inventory.MapleInventoryType; @@ -89,11 +90,12 @@ public class MapleShop { } MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); if (item.getPrice() > 0) { - if (c.getPlayer().getMeso() >= (long) item.getPrice() * quantity) { + int amount = (int)Math.min((float) item.getPrice() * quantity, Integer.MAX_VALUE); + if (c.getPlayer().getMeso() >= amount) { if (MapleInventoryManipulator.checkSpace(c, itemId, quantity, "")) { if (!ItemConstants.isRechargeable(itemId)) { //Pets can't be bought from shops MapleInventoryManipulator.addById(c, itemId, quantity); - c.getPlayer().gainMeso(-(item.getPrice() * quantity), false); + c.getPlayer().gainMeso(-amount, false); } else { short slotMax = ii.getSlotMax(c, item.getItemId()); quantity = slotMax; @@ -108,16 +110,18 @@ public class MapleShop { c.announce(MaplePacketCreator.shopTransaction((byte) 2)); } else if (item.getPitch() > 0) { - if (c.getPlayer().getInventory(MapleInventoryType.ETC).countById(4310000) >= (long) item.getPitch() * quantity) { + int amount = (int)Math.min((float) item.getPitch() * quantity, Integer.MAX_VALUE); + + if (c.getPlayer().getInventory(MapleInventoryType.ETC).countById(4310000) >= amount) { if (MapleInventoryManipulator.checkSpace(c, itemId, quantity, "")) { if (!ItemConstants.isRechargeable(itemId)) { MapleInventoryManipulator.addById(c, itemId, quantity); - MapleInventoryManipulator.removeById(c, MapleInventoryType.ETC, 4310000, item.getPitch() * quantity, false, false); + MapleInventoryManipulator.removeById(c, MapleInventoryType.ETC, 4310000, amount, false, false); } else { short slotMax = ii.getSlotMax(c, item.getItemId()); quantity = slotMax; MapleInventoryManipulator.addById(c, itemId, quantity); - MapleInventoryManipulator.removeById(c, MapleInventoryType.ETC, 4310000, item.getPitch() * quantity, false, false); + MapleInventoryManipulator.removeById(c, MapleInventoryType.ETC, 4310000, amount, false, false); } c.announce(MaplePacketCreator.shopTransaction((byte) 0)); } else diff --git a/src/server/MapleStatEffect.java b/src/server/MapleStatEffect.java index 01d03be4c3..d24f14baa7 100644 --- a/src/server/MapleStatEffect.java +++ b/src/server/MapleStatEffect.java @@ -21,6 +21,7 @@ */ package server; +import client.inventory.manipulator.MapleInventoryManipulator; import java.awt.Point; import java.awt.Rectangle; import java.util.ArrayList; diff --git a/src/server/MapleTrade.java b/src/server/MapleTrade.java index aede92f33f..ebce040a57 100644 --- a/src/server/MapleTrade.java +++ b/src/server/MapleTrade.java @@ -32,7 +32,9 @@ import client.MapleCharacter; import client.inventory.Item; import client.inventory.MapleInventory; import client.inventory.MapleInventoryType; -import constants.ItemConstants; +import client.inventory.manipulator.MapleInventoryManipulator; +import client.inventory.manipulator.MapleKarmaManipulator; +import constants.GameConstants; import constants.ServerConstants; import tools.Pair; @@ -57,22 +59,22 @@ public class MapleTrade { this.number = number; } - private static int getFee(int meso) { - int fee = 0; + private static int getFee(long meso) { + long fee = 0; if (meso >= 100000000) { - fee = (int) Math.round(0.06 * meso); + fee = (meso * 6) / 100; } else if (meso >= 25000000) { - fee = meso / 20; + fee = (meso * 5) / 100; } else if (meso >= 10000000) { - fee = meso / 25; + fee = (meso * 4) / 100; } else if (meso >= 5000000) { - fee = (int) Math.round(.03 * meso); + fee = (meso * 3) / 100; } else if (meso >= 1000000) { - fee = (int) Math.round(.018 * meso); + fee = (meso * 18) / 1000; } else if (meso >= 100000) { - fee = meso / 125; + fee = (meso * 8) / 1000; } - return fee; + return (int) fee; } private void lockTrade() { @@ -91,14 +93,23 @@ public class MapleTrade { items.clear(); meso = 0; for (Item item : exchangeItems) { - if ((item.getFlag() & ItemConstants.KARMA) == ItemConstants.KARMA) - item.setFlag((byte) (item.getFlag() ^ ItemConstants.KARMA)); //items with scissors of karma used on them are reset once traded - + MapleKarmaManipulator.toggleKarmaFlagToUntradeable(item); MapleInventoryManipulator.addFromDrop(chr.getClient(), item, show); } + if (exchangeMeso > 0) { - chr.gainMeso(exchangeMeso - getFee(exchangeMeso), show, true, show); + int fee = getFee(exchangeMeso); + + chr.gainMeso(exchangeMeso - fee, show, true, show); + if(fee > 0) { + chr.dropMessage(1, "Transaction completed. You received " + GameConstants.numberWithCommas(exchangeMeso - fee) + " mesos due to trade fees."); + } else { + chr.dropMessage(1, "Transaction completed. You received " + GameConstants.numberWithCommas(exchangeMeso) + " mesos."); + } + } else { + chr.dropMessage(1, "Transaction completed."); } + exchangeMeso = 0; if (exchangeItems != null) { exchangeItems.clear(); diff --git a/src/server/life/ChangeableStats.java b/src/server/life/ChangeableStats.java index 210a65ea5f..61b3de01af 100644 --- a/src/server/life/ChangeableStats.java +++ b/src/server/life/ChangeableStats.java @@ -38,13 +38,13 @@ public class ChangeableStats extends OverrideMonsterStats { final double mod = (double) newLevel / (double) stats.getLevel(); final double hpRatio = (double) stats.getHp() / (double) stats.getExp(); final double pqMod = (pqMob ? 1.5 : 1.0); // god damn - hp = (int) Math.round((!stats.isBoss() ? GameConstants.getMonsterHP(newLevel) : (stats.getHp() * mod)) * pqMod); // right here lol - exp = (int) Math.round((!stats.isBoss() ? (GameConstants.getMonsterHP(newLevel) / hpRatio) : (stats.getExp())) * pqMod); - mp = (int) Math.round(stats.getMp() * mod * pqMod); - watk = (int) Math.round(stats.getPADamage() * mod); - matk = (int) Math.round(stats.getMADamage() * mod); - wdef = Math.min(stats.isBoss() ? 30 : 20, (int) Math.round(stats.getPDDamage() * mod)); - mdef = Math.min(stats.isBoss() ? 30 : 20, (int) Math.round(stats.getMDDamage() * mod)); + hp = Math.min((int) Math.round((!stats.isBoss() ? GameConstants.getMonsterHP(newLevel) : (stats.getHp() * mod)) * pqMod), Integer.MAX_VALUE); // right here lol + exp = Math.min((int) Math.round((!stats.isBoss() ? (GameConstants.getMonsterHP(newLevel) / hpRatio) : (stats.getExp())) * pqMod), Integer.MAX_VALUE); + mp = Math.min((int) Math.round(stats.getMp() * mod * pqMod), Integer.MAX_VALUE); + watk = Math.min((int) Math.round(stats.getPADamage() * mod), Integer.MAX_VALUE); + matk = Math.min((int) Math.round(stats.getMADamage() * mod), Integer.MAX_VALUE); + wdef = Math.min(Math.min(stats.isBoss() ? 30 : 20, (int) Math.round(stats.getPDDamage() * mod)), Integer.MAX_VALUE); + mdef = Math.min(Math.min(stats.isBoss() ? 30 : 20, (int) Math.round(stats.getMDDamage() * mod)), Integer.MAX_VALUE); level = newLevel; } diff --git a/src/server/life/MapleMonster.java b/src/server/life/MapleMonster.java index 0c34b588b0..412545cc6d 100644 --- a/src/server/life/MapleMonster.java +++ b/src/server/life/MapleMonster.java @@ -70,6 +70,7 @@ public class MapleMonster extends AbstractLoadedMapleLife { private AtomicInteger hp = new AtomicInteger(1); private AtomicLong maxHpPlusHeal = new AtomicLong(1); private int mp; + private long nextBasicSkillTime; private WeakReference controller = new WeakReference<>(null); private boolean controllerHasAggro, controllerKnowsAboutAggro; private Collection listeners = new LinkedList<>(); @@ -394,7 +395,7 @@ public class MapleMonster extends AbstractLoadedMapleLife { MapleParty p = mc.getParty(); if (p != null) { int pID = p.getId(); - long pXP = (long)xp + (partyExp.containsKey(pID) ? partyExp.get(pID) : 0); + long pXP = (long) xp + (partyExp.containsKey(pID) ? partyExp.get(pID) : 0); partyExp.put(pID, (int)Math.min(pXP, Integer.MAX_VALUE)); } else { if(mc.getLevel() >= minThresholdLevel) { @@ -460,7 +461,7 @@ public class MapleMonster extends AbstractLoadedMapleLife { MapleCharacter controller = getController(); if (controller != null) { // this can/should only happen when a hidden gm attacks the monster - controller.getClient().announce(MaplePacketCreator.stopControllingMonster(this.getObjectId())); + controller.announce(MaplePacketCreator.stopControllingMonster(this.getObjectId())); controller.stopControllingMonster(this); } @@ -1138,6 +1139,14 @@ public class MapleMonster extends AbstractLoadedMapleLife { } } + public long getNextBasicSkillTime() { + return nextBasicSkillTime; + } + + public void setNextBasicSkillTime(long time) { + nextBasicSkillTime = time + 4200; + } + public int getNoSkills() { return this.stats.getNoSkills(); } diff --git a/src/server/life/MaplePlayerNPC.java b/src/server/life/MaplePlayerNPC.java index 466c9f8886..e32e71ba7a 100644 --- a/src/server/life/MaplePlayerNPC.java +++ b/src/server/life/MaplePlayerNPC.java @@ -21,6 +21,8 @@ */ package server.life; +import server.life.positioner.MaplePlayerNPCPositioner; +import server.life.positioner.MaplePlayerNPCPodium; import java.awt.Point; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -41,6 +43,7 @@ import client.MapleClient; import client.inventory.Item; import client.inventory.MapleInventoryType; import constants.GameConstants; +import constants.ServerConstants; import net.server.Server; import net.server.channel.Channel; import net.server.world.World; @@ -58,7 +61,7 @@ import tools.Pair; * @author Ronan */ public class MaplePlayerNPC extends AbstractMapleMapObject { - private static final Map runningPlayerNpcScriptIds = new HashMap<>(); + private static final Map> availablePlayerNpcScriptIds = new HashMap<>(); private static final AtomicInteger runningOverallRank = new AtomicInteger(); private static final List runningWorldRank = new ArrayList<>(); private static final Map, AtomicInteger> runningWorldJobRank = new HashMap<>(); @@ -220,7 +223,6 @@ public class MaplePlayerNPC extends AbstractMapleMapObject { try { Connection con = DatabaseConnection.getConnection(); - getRunningPlayerNpcScriptIds(con); getRunningOverallRanks(con); getRunningWorldRanks(con); getRunningWorldJobRanks(con); @@ -231,25 +233,6 @@ public class MaplePlayerNPC extends AbstractMapleMapObject { } } - private static void getRunningPlayerNpcScriptIds(Connection con) throws SQLException { - PreparedStatement ps = con.prepareStatement("SELECT floor(scriptid / 100), max(scriptid) FROM playernpcs GROUP BY floor(scriptid / 100) ORDER BY floor(scriptid / 100)"); - ResultSet rs = ps.executeQuery(); - - while(rs.next()) { - byte branch = (byte) (rs.getInt(1) % 100); - int maxid = rs.getInt(2); - - if(branch < 26) { - runningPlayerNpcScriptIds.put(branch, new AtomicInteger(maxid)); - } else { - runningPlayerNpcScriptIds.put((byte)(branch - ((branch - 26) % 4)), new AtomicInteger(maxid)); - } - } - - rs.close(); - ps.close(); - } - private static void getRunningOverallRanks(Connection con) throws SQLException { PreparedStatement ps = con.prepareStatement("SELECT max(overallrank) FROM playernpcs"); ResultSet rs = ps.executeQuery(); @@ -356,17 +339,69 @@ public class MaplePlayerNPC extends AbstractMapleMapObject { } } - private static MaplePlayerNPC createPlayerNPCInternal(MapleMap map, Point pos, MapleCharacter chr) { - if(pos == null) { - pos = map.getNextPlayerNpcPosition(); - if(pos == null) { - return null; + private static void fetchAvailableScriptIdsFromDb(byte branch, List list) { + try { + int branchLen = (branch < 26) ? 100 : 400; + int branchSid = 9900000 + (branch * 100); + int nextBranchSid = branchSid + branchLen; + Set usedScriptIds = new HashSet<>(); + + Connection con = DatabaseConnection.getConnection(); + PreparedStatement ps = con.prepareStatement("SELECT scriptid FROM playernpcs WHERE scriptid >= ? AND scriptid < ? ORDER BY scriptid"); + ps.setInt(1, branchSid); + ps.setInt(2, nextBranchSid); + + ResultSet rs = ps.executeQuery(); + while(rs.next()) { + usedScriptIds.add(rs.getInt(1)); + } + + List availables = new ArrayList<>(20); + int j = 0; + for(int i = branchSid; i < nextBranchSid; i++) { + if(!usedScriptIds.contains(i)) { + availables.add(i); + j++; + + if(j == 20) { + break; + } + } + } + + rs.close(); + ps.close(); + con.close(); + + for(int i = availables.size() - 1; i >= 0; i--) { + list.add(availables.get(i)); + } + } catch(SQLException sqle) { + sqle.printStackTrace(); + } + } + + private static int getNextScriptId(byte branch) { + List availablesBranch = availablePlayerNpcScriptIds.get(branch); + + if(availablesBranch == null) { + availablesBranch = new ArrayList<>(20); + availablePlayerNpcScriptIds.put(branch, availablesBranch); + } + + if(availablesBranch.isEmpty()) { + fetchAvailableScriptIdsFromDb(branch, availablesBranch); + + if(availablesBranch.isEmpty()) { + return -1; } } - + + return availablesBranch.remove(availablesBranch.size() - 1); + } + + private static MaplePlayerNPC createPlayerNPCInternal(MapleMap map, Point pos, MapleCharacter chr) { int mapId = map.getId(); - int worldId = chr.getWorld(); - int jobId = (chr.getJob().getId() / 100) * 100; /* if(!canSpawnPlayerNpc(chr.getName(), mapId)) { @@ -376,18 +411,28 @@ public class MaplePlayerNPC extends AbstractMapleMapObject { byte branch = GameConstants.getHallOfFameBranch(chr.getJob(), mapId); - int scriptId; - try { - scriptId = runningPlayerNpcScriptIds.get(branch).getAndIncrement(); + int scriptId = getNextScriptId(branch); + if (scriptId == -1) { + return null; + } + + if(pos == null) { + if(GameConstants.isPodiumHallOfFameMap(map.getId())) { + pos = MaplePlayerNPCPodium.getNextPlayerNpcPosition(map); + } else { + pos = MaplePlayerNPCPositioner.getNextPlayerNpcPosition(map); + } - if(!GameConstants.canPnpcBranchUseScriptId(branch, scriptId)) { + if(pos == null) { return null; } - } catch (NullPointerException npe) { - scriptId = 9900000 + (branch * 100); - runningPlayerNpcScriptIds.put(branch, new AtomicInteger(scriptId + 1)); } + if(ServerConstants.USE_DEBUG) System.out.println("GOT SID " + scriptId + " POS " + pos); + + int worldId = chr.getWorld(); + int jobId = (chr.getJob().getId() / 100) * 100; + MaplePlayerNPC ret; int npcId; try { @@ -516,8 +561,9 @@ public class MaplePlayerNPC extends AbstractMapleMapObject { } public static boolean spawnPlayerNPC(int mapid, Point pos, MapleCharacter chr) { - MaplePlayerNPC pn = processPlayerNPCInternal(chr.getClient().getChannelServer().getMapFactory().getMap(mapid), pos, chr, true).getLeft(); + if(chr == null) return false; + MaplePlayerNPC pn = processPlayerNPCInternal(chr.getClient().getChannelServer().getMapFactory().getMap(mapid), pos, chr, true).getLeft(); if(pn != null) { for (Channel channel : Server.getInstance().getChannelsFromWorld(chr.getWorld())) { MapleMap m = channel.getMapFactory().getMap(mapid); @@ -547,6 +593,8 @@ public class MaplePlayerNPC extends AbstractMapleMapObject { } public static void removePlayerNPC(MapleCharacter chr) { + if(chr == null) return; + List updateMapids = processPlayerNPCInternal(null, null, chr, false).getRight(); int worldid = updateMapids.remove(0); @@ -608,8 +656,10 @@ public class MaplePlayerNPC extends AbstractMapleMapObject { PreparedStatement ps = con.prepareStatement("SELECT DISTINCT world, map FROM playernpcs"); ResultSet rs = ps.executeQuery(); + int wsize = Server.getInstance().getWorlds().size(); while(rs.next()) { int world = rs.getInt("world"), map = rs.getInt("map"); + if(world >= wsize) continue; World w = Server.getInstance().getWorld(world); for (Channel channel : w.getChannels()) { @@ -636,6 +686,14 @@ public class MaplePlayerNPC extends AbstractMapleMapObject { ps.executeUpdate(); ps.close(); + ps = con.prepareStatement("DELETE FROM playernpcs_field"); + ps.executeUpdate(); + ps.close(); + + for(World w : Server.getInstance().getWorlds()) { + w.resetPlayerNpcMapData(); + } + con.close(); } catch (SQLException e) { e.printStackTrace(); diff --git a/src/server/life/MaplePlayerNPCFactory.java b/src/server/life/MaplePlayerNPCFactory.java index 182c66b19c..b262da2d75 100644 --- a/src/server/life/MaplePlayerNPCFactory.java +++ b/src/server/life/MaplePlayerNPCFactory.java @@ -19,12 +19,12 @@ */ package server.life; -import constants.GameConstants; import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.List; import java.util.LinkedList; +import net.server.Server; import provider.MapleData; import provider.MapleDataProvider; import provider.MapleDataProviderFactory; @@ -52,7 +52,7 @@ public class MaplePlayerNPCFactory { if(thisData != null) { MapleData md = thisData.getChildByPath("anthem/brazil"); if(md != null) { - GameConstants.setAvailableDeveloperRoom(); + Server.getInstance().setAvailableDeveloperRoom(); } } } diff --git a/src/server/life/positioner/MaplePlayerNPCPodium.java b/src/server/life/positioner/MaplePlayerNPCPodium.java new file mode 100644 index 0000000000..4a345bef62 --- /dev/null +++ b/src/server/life/positioner/MaplePlayerNPCPodium.java @@ -0,0 +1,155 @@ +/* + 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 server.life.positioner; + +import constants.ServerConstants; +import java.awt.Point; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import net.server.Server; +import net.server.channel.Channel; +import server.life.MaplePlayerNPC; +import server.maps.MapleMap; +import server.maps.MapleMapObject; +import server.maps.MapleMapObjectType; +import tools.MaplePacketCreator; + +/** + * + * @author RonanLana + * + * Note: the podium uses getGroundBelow that in its turn uses inputted posY decremented by 7. + * Podium system will implement increase-by-7 to negate that behaviour. + */ +public class MaplePlayerNPCPodium { + private static int getPlatformPosX(int platform) { + switch(platform) { + case 0: + return -50; + + case 1: + return -170; + + default: + return 70; + } + } + + private static int getPlatformPosY(int platform) { + switch(platform) { + case 0: + return -47; + + default: + return 40; + } + } + + private static Point calcNextPos(int rank, int step) { + int podiumPlatform = rank / step; + int relativePos = (rank % step) + 1; + + Point pos = new Point(getPlatformPosX(podiumPlatform) + ((100 * relativePos) / (step + 1)), getPlatformPosY(podiumPlatform)); + return pos; + } + + private static Point rearrangePlayerNpcs(MapleMap map, int newStep, List pnpcs) { + int i = 0; + for(MaplePlayerNPC pn : pnpcs) { + pn.updatePlayerNPCPosition(map, calcNextPos(i, newStep)); + i++; + } + + return calcNextPos(i, newStep); + } + + private static Point reorganizePlayerNpcs(MapleMap map, int newStep, List mmoList) { + if(!mmoList.isEmpty()) { + if(ServerConstants.USE_DEBUG) System.out.println("Reorganizing pnpc map, step " + newStep); + + List playerNpcs = new ArrayList<>(mmoList.size()); + for(MapleMapObject mmo : mmoList) { + playerNpcs.add((MaplePlayerNPC) mmo); + } + + Collections.sort(playerNpcs, new Comparator() { + @Override + public int compare(MaplePlayerNPC p1, MaplePlayerNPC p2) { + return p1.getScriptId() - p2.getScriptId(); // scriptid as playernpc history + } + }); + + for(Channel ch : Server.getInstance().getChannelsFromWorld(map.getWorld())) { + MapleMap m = ch.getMapFactory().getMap(map.getId()); + + for(MaplePlayerNPC pn : playerNpcs) { + m.removeMapObject(pn); + m.broadcastMessage(MaplePacketCreator.removeNPCController(pn.getObjectId())); + m.broadcastMessage(MaplePacketCreator.removePlayerNPC(pn.getObjectId())); + } + } + + Point ret = rearrangePlayerNpcs(map, newStep, playerNpcs); + + for(Channel ch : Server.getInstance().getChannelsFromWorld(map.getWorld())) { + MapleMap m = ch.getMapFactory().getMap(map.getId()); + + for(MaplePlayerNPC pn : playerNpcs) { + m.addPlayerNPCMapObject(pn); + m.broadcastMessage(MaplePacketCreator.spawnPlayerNPC(pn)); + m.broadcastMessage(MaplePacketCreator.getPlayerNPC(pn)); + } + } + + return ret; + } + + return null; + } + + private static int encodePodiumData(int podiumStep, int podiumCount) { + return (podiumCount * (1 << 5)) + podiumStep; + } + + private static Point getNextPlayerNpcPosition(MapleMap map, int podiumData) { // automated playernpc position thanks to Ronan + int podiumStep = podiumData % (1 << 5), podiumCount = (podiumData / (1 << 5)); + + if(podiumCount >= 3 * podiumStep) { + if(podiumStep >= ServerConstants.PLAYERNPC_AREA_STEPS) return null; + + List mmoList = map.getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.PLAYER_NPC)); + Server.getInstance().getWorld(map.getWorld()).setPlayerNpcMapPodiumData(map.getId(), encodePodiumData(podiumStep + 1, podiumCount + 1)); + return reorganizePlayerNpcs(map, podiumStep + 1, mmoList); + } else { + Server.getInstance().getWorld(map.getWorld()).setPlayerNpcMapPodiumData(map.getId(), encodePodiumData(podiumStep, podiumCount + 1)); + return calcNextPos(podiumCount, podiumStep); + } + } + + public static Point getNextPlayerNpcPosition(MapleMap map) { + Point pos = getNextPlayerNpcPosition(map, Server.getInstance().getWorld(map.getWorld()).getPlayerNpcMapPodiumData(map.getId())); + if(pos == null) return null; + + return map.getGroundBelow(pos); + } +} diff --git a/src/server/life/positioner/MaplePlayerNPCPositioner.java b/src/server/life/positioner/MaplePlayerNPCPositioner.java new file mode 100644 index 0000000000..01bd6a492e --- /dev/null +++ b/src/server/life/positioner/MaplePlayerNPCPositioner.java @@ -0,0 +1,243 @@ +/* + 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 server.life.positioner; + +import constants.ServerConstants; +import java.awt.Point; +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import net.server.Server; +import net.server.channel.Channel; +import server.life.MaplePlayerNPC; +import server.maps.MapleMap; +import server.maps.MapleMapObject; +import server.maps.MapleMapObjectType; +import tools.MaplePacketCreator; + +/** + * + * @author RonanLana + * + */ +public class MaplePlayerNPCPositioner { + + private static boolean isPlayerNpcNearby(List otherPos, Point searchPos, int xLimit, int yLimit) { + int xLimit2 = xLimit / 2, yLimit2 = yLimit / 2; + + Rectangle searchRect = new Rectangle(searchPos.x - xLimit2, searchPos.y - yLimit2, xLimit, yLimit); + for(Point pos : otherPos) { + Rectangle otherRect = new Rectangle(pos.x - xLimit2, pos.y - yLimit2, xLimit, yLimit); + + if(otherRect.intersects(searchRect)) { + return true; + } + } + + return false; + } + + private static int calcDx(int newStep) { + return ServerConstants.PLAYERNPC_AREA_X / (newStep + 1); + } + + private static int calcDy(int newStep) { + return (ServerConstants.PLAYERNPC_AREA_Y / 2) + (ServerConstants.PLAYERNPC_AREA_Y / (1 << (newStep + 1))); + } + + private static List rearrangePlayerNpcPositions(MapleMap map, int newStep, int pnpcsSize) { + Rectangle mapArea = map.getMapArea(); + + int leftPx = mapArea.x + ServerConstants.PLAYERNPC_INITIAL_X, px, py = mapArea.y + ServerConstants.PLAYERNPC_INITIAL_Y; + int outx = mapArea.x + mapArea.width - ServerConstants.PLAYERNPC_INITIAL_X, outy = mapArea.y + mapArea.height; + int cx = calcDx(newStep), cy = calcDy(newStep); + + List otherPlayerNpcs = new LinkedList<>(); + while(py < outy) { + px = leftPx; + + while(px < outx) { + Point searchPos = map.getPointBelow(new Point(px, py)); + if(searchPos != null) { + if(!isPlayerNpcNearby(otherPlayerNpcs, searchPos, cx, cy)) { + otherPlayerNpcs.add(searchPos); + + if(otherPlayerNpcs.size() == pnpcsSize) { + return otherPlayerNpcs; + } + } + } + + px += cx; + } + + py += cy; + } + + return null; + } + + private static Point rearrangePlayerNpcs(MapleMap map, int newStep, List pnpcs) { + Rectangle mapArea = map.getMapArea(); + + int leftPx = mapArea.x + ServerConstants.PLAYERNPC_INITIAL_X, px, py = mapArea.y + ServerConstants.PLAYERNPC_INITIAL_Y; + int outx = mapArea.x + mapArea.width - ServerConstants.PLAYERNPC_INITIAL_X, outy = mapArea.y + mapArea.height; + int cx = calcDx(newStep), cy = calcDy(newStep); + + List otherPlayerNpcs = new LinkedList<>(); + int i = 0; + + while(py < outy) { + px = leftPx; + + while(px < outx) { + Point searchPos = map.getPointBelow(new Point(px, py)); + if(searchPos != null) { + if(!isPlayerNpcNearby(otherPlayerNpcs, searchPos, cx, cy)) { + if(i == pnpcs.size()) { + return searchPos; + } + + MaplePlayerNPC pn = pnpcs.get(i); + i++; + + pn.updatePlayerNPCPosition(map, searchPos); + otherPlayerNpcs.add(searchPos); + } + } + + px += cx; + } + + py += cy; + } + + return null; // this area should not be reached under any scenario + } + + private static Point reorganizePlayerNpcs(MapleMap map, int newStep, List mmoList) { + if(!mmoList.isEmpty()) { + if(ServerConstants.USE_DEBUG) System.out.println("Reorganizing pnpc map, step " + newStep); + + List playerNpcs = new ArrayList<>(mmoList.size()); + for(MapleMapObject mmo : mmoList) { + playerNpcs.add((MaplePlayerNPC) mmo); + } + + Collections.sort(playerNpcs, new Comparator() { + @Override + public int compare(MaplePlayerNPC p1, MaplePlayerNPC p2) { + return p1.getScriptId() - p2.getScriptId(); // scriptid as playernpc history + } + }); + + for(Channel ch : Server.getInstance().getChannelsFromWorld(map.getWorld())) { + MapleMap m = ch.getMapFactory().getMap(map.getId()); + + for(MaplePlayerNPC pn : playerNpcs) { + m.removeMapObject(pn); + m.broadcastMessage(MaplePacketCreator.removeNPCController(pn.getObjectId())); + m.broadcastMessage(MaplePacketCreator.removePlayerNPC(pn.getObjectId())); + } + } + + Point ret = rearrangePlayerNpcs(map, newStep, playerNpcs); + + for(Channel ch : Server.getInstance().getChannelsFromWorld(map.getWorld())) { + MapleMap m = ch.getMapFactory().getMap(map.getId()); + + for(MaplePlayerNPC pn : playerNpcs) { + m.addPlayerNPCMapObject(pn); + m.broadcastMessage(MaplePacketCreator.spawnPlayerNPC(pn)); + m.broadcastMessage(MaplePacketCreator.getPlayerNPC(pn)); + } + } + + return ret; + } + + return null; + } + + private static Point getNextPlayerNpcPosition(MapleMap map, int initStep) { // automated playernpc position thanks to Ronan + List mmoList = map.getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.PLAYER_NPC)); + List otherPlayerNpcs = new LinkedList<>(); + for(MapleMapObject mmo : mmoList) { + otherPlayerNpcs.add(mmo.getPosition()); + } + + int cx = calcDx(initStep), cy = calcDy(initStep); + Rectangle mapArea = map.getMapArea(); + int outx = mapArea.x + mapArea.width - ServerConstants.PLAYERNPC_INITIAL_X, outy = mapArea.y + mapArea.height; + boolean reorganize = false; + + int i = initStep; + while(i < ServerConstants.PLAYERNPC_AREA_STEPS) { + int leftPx = mapArea.x + ServerConstants.PLAYERNPC_INITIAL_X, px, py = mapArea.y + ServerConstants.PLAYERNPC_INITIAL_Y; + + while(py < outy) { + px = leftPx; + + while(px < outx) { + Point searchPos = map.getPointBelow(new Point(px, py)); + if(searchPos != null) { + if(!isPlayerNpcNearby(otherPlayerNpcs, searchPos, cx, cy)) { + if(i > initStep) { + Server.getInstance().getWorld(map.getWorld()).setPlayerNpcMapStep(map.getId(), i); + } + + if(reorganize && ServerConstants.PLAYERNPC_ORGANIZE_AREA) { + return reorganizePlayerNpcs(map, i, mmoList); + } + + return searchPos; + } + } + + px += cx; + } + + py += cy; + } + + reorganize = true; + i++; + + cx = calcDx(i); + cy = calcDy(i); + if(ServerConstants.PLAYERNPC_ORGANIZE_AREA) { + otherPlayerNpcs = rearrangePlayerNpcPositions(map, i, mmoList.size()); + } + } + + if(i > initStep) { + Server.getInstance().getWorld(map.getWorld()).setPlayerNpcMapStep(map.getId(), ServerConstants.PLAYERNPC_AREA_STEPS - 1); + } + return null; + } + + public static Point getNextPlayerNpcPosition(MapleMap map) { + return getNextPlayerNpcPosition(map, Server.getInstance().getWorld(map.getWorld()).getPlayerNpcMapStep(map.getId())); + } +} diff --git a/src/server/maps/MapleHiredMerchant.java b/src/server/maps/MapleHiredMerchant.java index fda133f27c..db726acf01 100644 --- a/src/server/maps/MapleHiredMerchant.java +++ b/src/server/maps/MapleHiredMerchant.java @@ -27,8 +27,9 @@ import client.inventory.Item; import client.inventory.ItemFactory; import client.inventory.MapleInventory; import client.inventory.MapleInventoryType; +import client.inventory.manipulator.MapleInventoryManipulator; +import client.inventory.manipulator.MapleKarmaManipulator; import com.mysql.jdbc.Statement; -import constants.ItemConstants; import constants.ServerConstants; import java.sql.Connection; import java.sql.PreparedStatement; @@ -41,7 +42,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import tools.locks.MonitoredReentrantLock; import net.server.Server; -import server.MapleInventoryManipulator; import server.MapleItemInformationProvider; import tools.DatabaseConnection; import tools.MaplePacketCreator; @@ -214,11 +214,9 @@ public class MapleHiredMerchant extends AbstractMapleMapObject { return; } - if ((newItem.getFlag() & ItemConstants.KARMA) == ItemConstants.KARMA) { - newItem.setFlag((byte) (newItem.getFlag() ^ ItemConstants.KARMA)); - } + MapleKarmaManipulator.toggleKarmaFlagToUntradeable(newItem); - int price = (int) Math.min((long)pItem.getPrice() * quantity, Integer.MAX_VALUE); + int price = (int) Math.min((float) pItem.getPrice() * quantity, Integer.MAX_VALUE); if (c.getPlayer().getMeso() >= price) { if (canBuy(c, newItem)) { c.getPlayer().gainMeso(-price, false); diff --git a/src/server/maps/MapleMap.java b/src/server/maps/MapleMap.java index 94c75b5fa3..81b1948e2a 100644 --- a/src/server/maps/MapleMap.java +++ b/src/server/maps/MapleMap.java @@ -83,6 +83,7 @@ import server.life.SpawnPoint; import server.partyquest.MonsterCarnival; import server.partyquest.MonsterCarnivalParty; //import server.partyquest.Pyramid; +import scripting.event.EventManager; import scripting.event.EventInstanceManager; import server.life.MaplePlayerNPC; import server.life.MonsterListener; @@ -191,6 +192,14 @@ public class MapleMap { return event; } + public Rectangle getMapArea() { + return mapArea; + } + + public int getWorld() { + return world; + } + public void broadcastMessage(MapleCharacter source, final byte[] packet) { chrRLock.lock(); try { @@ -596,7 +605,9 @@ public class MapleMap { MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); for (final MonsterDropEntry de : dropEntry) { - if (Randomizer.nextInt(999999) < (long) de.chance * chRate) { + int dropChance = (int) Math.min((float) de.chance * chRate, Integer.MAX_VALUE); + + if (Randomizer.nextInt(999999) < dropChance) { if (droptype == 3) { pos.x = (int) (mobpos + ((d % 2 == 0) ? (40 * ((d + 1) / 2)) : -(40 * (d / 2)))); } else { @@ -1147,35 +1158,19 @@ public class MapleMap { } public void broadcastBalrogVictory(String leaderName) { - for (Channel cserv : Server.getInstance().getWorld(world).getChannels()) { - for (MapleCharacter player : cserv.getPlayerStorage().getAllCharacters()) { - player.dropMessage(6, "[VICTORY] " + leaderName + "'s party has successfully defeated the Balrog! Praise to them, they finished with " + countAlivePlayers() + " players alive."); - } - } + Server.getInstance().getWorld(world).dropMessage(6, "[VICTORY] " + leaderName + "'s party has successfully defeated the Balrog! Praise to them, they finished with " + countAlivePlayers() + " players alive."); } public void broadcastHorntailVictory() { - for (Channel cserv : Server.getInstance().getWorld(world).getChannels()) { - for (MapleCharacter player : cserv.getPlayerStorage().getAllCharacters()) { - player.dropMessage(6, "[VICTORY] To the crew that have finally conquered Horned Tail after numerous attempts, I salute thee! You are the true heroes of Leafre!!"); - } - } + Server.getInstance().getWorld(world).dropMessage(6, "[VICTORY] To the crew that have finally conquered Horned Tail after numerous attempts, I salute thee! You are the true heroes of Leafre!!"); } public void broadcastZakumVictory() { - for (Channel cserv : Server.getInstance().getWorld(world).getChannels()) { - for (MapleCharacter player : cserv.getPlayerStorage().getAllCharacters()) { - player.dropMessage(6, "[VICTORY] At last, the tree of evil that for so long overwhelmed Ossyria has fallen. To the crew that managed to finally conquer Zakum, after numerous attempts, victory! You are the true heroes of Ossyria!!"); - } - } + Server.getInstance().getWorld(world).dropMessage(6, "[VICTORY] At last, the tree of evil that for so long overwhelmed Ossyria has fallen. To the crew that managed to finally conquer Zakum, after numerous attempts, victory! You are the true heroes of Ossyria!!"); } public void broadcastPinkBeanVictory(int channel) { - for (Channel cserv : Server.getInstance().getWorld(world).getChannels()) { - for (MapleCharacter player : cserv.getPlayerStorage().getAllCharacters()) { - player.dropMessage(6, "[VICTORY] In a swift stroke of sorts, the crew that has attempted Pink Bean at channel " + channel + " has ultimately defeated it. The Temple of Time shines radiantly once again, the day finally coming back, as the crew that managed to finally conquer it returns victoriously from the battlefield!!"); - } - } + Server.getInstance().getWorld(world).dropMessage(6, "[VICTORY] In a swift stroke of sorts, the crew that has attempted Pink Bean at channel " + channel + " has ultimately defeated it. The Temple of Time shines radiantly once again, the day finally coming back, as the crew that managed to finally conquer it returns victoriously from the battlefield!!"); } public void killMonster(final MapleMonster monster, final MapleCharacter chr, final boolean withDrops) { @@ -1742,6 +1737,10 @@ public class MapleMap { spos.y--;//shouldn't be null! return spos; } + + public Point getPointBelow(Point pos) { + return calcPointBelow(pos); + } public void spawnRevives(final MapleMonster monster) { monster.setMap(this); @@ -2279,10 +2278,13 @@ public class MapleMap { chr.cancelEffectFromBuffStat(MapleBuffStat.MONSTER_RIDING); chr.cancelBuffStats(MapleBuffStat.MONSTER_RIDING); } - if (mapid == 923010000 && getMonsterById(9300102) == null) { // Kenta's Mount Quest - spawnMonsterOnGroundBelow(MapleLifeFactory.getMonster(9300102), new Point(77, 426)); + if (mapid == 923010000) { // Kenta's Mount Quest + if(getMonsterById(9300102) == null) { + spawnMonsterOnGroundBelow(MapleLifeFactory.getMonster(9300102), new Point(77, 426)); + } } else if (mapid == 200090060) { // To Rien - chr.announce(MaplePacketCreator.getClock(60)); + int travelTime = EventManager.getTransportationTime(1 * 60 * 1000); + chr.announce(MaplePacketCreator.getClock(travelTime / 1000)); TimerManager.getInstance().schedule(new Runnable() { @Override @@ -2291,9 +2293,10 @@ public class MapleMap { chr.changeMap(140020300, 0); } } - }, 60 * 1000); + }, travelTime); } else if (mapid == 200090070) { // To Lith Harbor - chr.announce(MaplePacketCreator.getClock(60)); + int travelTime = EventManager.getTransportationTime(1 * 60 * 1000); + chr.announce(MaplePacketCreator.getClock(travelTime / 1000)); TimerManager.getInstance().schedule(new Runnable() { @Override @@ -2302,9 +2305,10 @@ public class MapleMap { chr.changeMap(104000000, 3); } } - }, 60 * 1000); + }, travelTime); } else if (mapid == 200090030) { // To Ereve (SkyFerry) - chr.getClient().announce(MaplePacketCreator.getClock(2 * 60)); + int travelTime = EventManager.getTransportationTime(2 * 60 * 1000); + chr.announce(MaplePacketCreator.getClock(travelTime / 1000)); TimerManager.getInstance().schedule(new Runnable() { @Override @@ -2313,9 +2317,10 @@ public class MapleMap { chr.changeMap(130000210, 0); } } - }, 2 * 60 * 1000); + }, travelTime); } else if (mapid == 200090031) { // To Victoria Island (SkyFerry) - chr.getClient().announce(MaplePacketCreator.getClock(2 * 60)); + int travelTime = EventManager.getTransportationTime(2 * 60 * 1000); + chr.announce(MaplePacketCreator.getClock(travelTime / 1000)); TimerManager.getInstance().schedule(new Runnable() { @Override @@ -2324,9 +2329,10 @@ public class MapleMap { chr.changeMap(101000400, 0); } } - }, 2 * 60 * 1000); + }, travelTime); } else if (mapid == 200090021) { // To Orbis (SkyFerry) - chr.getClient().announce(MaplePacketCreator.getClock(8 * 60)); + int travelTime = EventManager.getTransportationTime(8 * 60 * 1000); + chr.announce(MaplePacketCreator.getClock(travelTime / 1000)); TimerManager.getInstance().schedule(new Runnable() { @Override @@ -2335,9 +2341,10 @@ public class MapleMap { chr.changeMap(200000161, 0); } } - }, 8 * 60 * 1000); + }, travelTime); } else if (mapid == 200090020) { // To Ereve From Orbis (SkyFerry) - chr.getClient().announce(MaplePacketCreator.getClock(8 * 60)); + int travelTime = EventManager.getTransportationTime(8 * 60 * 1000); + chr.announce(MaplePacketCreator.getClock(travelTime / 1000)); TimerManager.getInstance().schedule(new Runnable() { @Override @@ -2346,7 +2353,7 @@ public class MapleMap { chr.changeMap(130000210, 0); } } - }, 8 * 60 * 1000); + }, travelTime); } else if (mapid == 103040400) { if (chr.getEventInstance() != null) { chr.getEventInstance().movePlayer(chr); @@ -2366,13 +2373,13 @@ public class MapleMap { } } if (chr.isHidden()) { - broadcastGMMessage(chr, MaplePacketCreator.spawnPlayerMapObject(chr), false); + broadcastGMMessage(chr, MaplePacketCreator.spawnEnterPlayerMapObject(chr), false); chr.announce(MaplePacketCreator.getGMEffect(0x10, (byte) 1)); List> dsstat = Collections.singletonList(new Pair(MapleBuffStat.DARKSIGHT, 0)); broadcastGMMessage(chr, MaplePacketCreator.giveForeignBuff(chr.getId(), dsstat), false); } else { - broadcastMessage(chr, MaplePacketCreator.spawnPlayerMapObject(chr), false); + broadcastMessage(chr, MaplePacketCreator.spawnEnterPlayerMapObject(chr), false); } sendObjectPlacement(chr.getClient()); @@ -2699,7 +2706,7 @@ public class MapleMap { broadcastMessage(MaplePacketCreator.serverNotice(type, message)); } - private boolean isNonRangedType(MapleMapObjectType type) { + private static boolean isNonRangedType(MapleMapObjectType type) { switch (type) { case NPC: case PLAYER: @@ -2726,7 +2733,11 @@ public class MapleMap { } for (MapleMapObject o : objects) { - if (o.getType() == MapleMapObjectType.SUMMON) { + if (isNonRangedType(o.getType())) { + o.sendSpawnData(mapleClient); + } else if (o.getType() == MapleMapObjectType.MONSTER) { + updateMonsterController((MapleMonster) o); + } else if (o.getType() == MapleMapObjectType.SUMMON) { MapleSummon summon = (MapleSummon) o; if (summon.getOwner() == chr) { if (chr.isSummonsEmpty() || !chr.containsSummon(summon)) { @@ -2737,15 +2748,10 @@ public class MapleMap { objectWLock.unlock(); } - continue; + //continue; } } } - if (isNonRangedType(o.getType())) { - o.sendSpawnData(mapleClient); - } else if (o.getType() == MapleMapObjectType.MONSTER) { - updateMonsterController((MapleMonster) o); - } } if (chr != null) { @@ -3789,195 +3795,4 @@ public class MapleMap { spawnMonsterOnGroundBelow(m, targetPoint); } } - - private static boolean isPlayerNpcNearby(List otherPos, Point searchPos, int xLimit, int yLimit) { - int xLimit2 = xLimit / 2, yLimit2 = yLimit / 2; - - Rectangle searchRect = new Rectangle(searchPos.x - xLimit2, searchPos.y - yLimit2, xLimit, yLimit); - for(Point pos : otherPos) { - Rectangle otherRect = new Rectangle(pos.x - xLimit2, pos.y - yLimit2, xLimit, yLimit); - - if(otherRect.intersects(searchRect)) { - return true; - } - } - - return false; - } - - private static int calcDx(int newStep) { - return ServerConstants.PLAYERNPC_AREA_X / (newStep + 1); - } - - private static int calcDy(int newStep) { - return (ServerConstants.PLAYERNPC_AREA_Y / 2) + (ServerConstants.PLAYERNPC_AREA_Y / (1 << (newStep + 1))); - } - - private List rearrangePlayerNpcPositions(int newStep, int pnpcsSize) { - int leftPx = mapArea.x + ServerConstants.PLAYERNPC_INITIAL_X, px, py = mapArea.y + ServerConstants.PLAYERNPC_INITIAL_Y; - int outx = mapArea.x + mapArea.width - ServerConstants.PLAYERNPC_INITIAL_X, outy = mapArea.y + mapArea.height; - int cx = calcDx(newStep), cy = calcDy(newStep); - - List otherPlayerNpcs = new LinkedList<>(); - while(py < outy) { - px = leftPx; - - while(px < outx) { - Point searchPos = calcPointBelow(new Point(px, py)); - if(searchPos != null) { - if(!isPlayerNpcNearby(otherPlayerNpcs, searchPos, cx, cy)) { - otherPlayerNpcs.add(searchPos); - - if(otherPlayerNpcs.size() == pnpcsSize) { - return otherPlayerNpcs; - } - } - } - - px += cx; - } - - py += cy; - } - - return null; - } - - private Point rearrangePlayerNpcs(int newStep, List pnpcs) { - int leftPx = mapArea.x + ServerConstants.PLAYERNPC_INITIAL_X, px, py = mapArea.y + ServerConstants.PLAYERNPC_INITIAL_Y; - int outx = mapArea.x + mapArea.width - ServerConstants.PLAYERNPC_INITIAL_X, outy = mapArea.y + mapArea.height; - int cx = calcDx(newStep), cy = calcDy(newStep); - - List otherPlayerNpcs = new LinkedList<>(); - int i = 0; - - while(py < outy) { - px = leftPx; - - while(px < outx) { - Point searchPos = calcPointBelow(new Point(px, py)); - if(searchPos != null) { - if(!isPlayerNpcNearby(otherPlayerNpcs, searchPos, cx, cy)) { - if(i == pnpcs.size()) { - return searchPos; - } - - MaplePlayerNPC pn = pnpcs.get(i); - i++; - - pn.updatePlayerNPCPosition(this, searchPos); - otherPlayerNpcs.add(searchPos); - } - } - - px += cx; - } - - py += cy; - } - - return null; // this area should not be reached under any scenario - } - - private Point reorganizePlayerNpcs(int newStep, List mmoList) { - if(!mmoList.isEmpty()) { - List playerNpcs = new ArrayList<>(mmoList.size()); - for(MapleMapObject mmo : mmoList) { - playerNpcs.add((MaplePlayerNPC) mmo); - } - - Collections.sort(playerNpcs, new Comparator() { - @Override - public int compare(MaplePlayerNPC p1, MaplePlayerNPC p2) { - return p1.getScriptId() - p2.getScriptId(); // scriptid as playernpc history - } - }); - - for(Channel ch : Server.getInstance().getChannelsFromWorld(world)) { - MapleMap map = ch.getMapFactory().getMap(mapid); - - for(MaplePlayerNPC pn : playerNpcs) { - map.removeMapObject(pn); - map.broadcastMessage(MaplePacketCreator.removeNPCController(pn.getObjectId())); - map.broadcastMessage(MaplePacketCreator.removePlayerNPC(pn.getObjectId())); - } - } - - Point ret = rearrangePlayerNpcs(newStep, playerNpcs); - - for(Channel ch : Server.getInstance().getChannelsFromWorld(world)) { - MapleMap map = ch.getMapFactory().getMap(mapid); - - for(MaplePlayerNPC pn : playerNpcs) { - map.addPlayerNPCMapObject(pn); - map.broadcastMessage(MaplePacketCreator.spawnPlayerNPC(pn)); - map.broadcastMessage(MaplePacketCreator.getPlayerNPC(pn)); - } - } - - return ret; - } - - return null; - } - - public Point getNextPlayerNpcPosition() { - return getNextPlayerNpcPosition(Server.getInstance().getWorld(world).getPlayerNpcMapStep(mapid)); - } - - private Point getNextPlayerNpcPosition(int initStep) { // automated playernpc position thanks to Ronan - List mmoList = getMapObjectsInRange(new Point(0, 0), Double.POSITIVE_INFINITY, Arrays.asList(MapleMapObjectType.PLAYER_NPC)); - List otherPlayerNpcs = new LinkedList<>(); - for(MapleMapObject mmo : mmoList) { - otherPlayerNpcs.add(mmo.getPosition()); - } - - int cx = calcDx(initStep), cy = calcDy(initStep); - int outx = mapArea.x + mapArea.width - ServerConstants.PLAYERNPC_INITIAL_X, outy = mapArea.y + mapArea.height; - boolean reorganize = false; - - int i = initStep; - while(i < ServerConstants.PLAYERNPC_AREA_STEPS) { - int leftPx = mapArea.x + ServerConstants.PLAYERNPC_INITIAL_X, px, py = mapArea.y + ServerConstants.PLAYERNPC_INITIAL_Y; - - while(py < outy) { - px = leftPx; - - while(px < outx) { - Point searchPos = calcPointBelow(new Point(px, py)); - if(searchPos != null) { - if(!isPlayerNpcNearby(otherPlayerNpcs, searchPos, cx, cy)) { - if(i > initStep) { - Server.getInstance().getWorld(world).setPlayerNpcMapStep(mapid, i); - } - - if(reorganize && ServerConstants.PLAYERNPC_ORGANIZE_AREA) { - return reorganizePlayerNpcs(i, mmoList); - } - - return searchPos; - } - } - - px += cx; - } - - py += cy; - } - - reorganize = true; - i++; - - cx = calcDx(i); - cy = calcDy(i); - if(ServerConstants.PLAYERNPC_ORGANIZE_AREA) { - otherPlayerNpcs = rearrangePlayerNpcPositions(i, mmoList.size()); - } - } - - if(i > initStep) { - Server.getInstance().getWorld(world).setPlayerNpcMapStep(mapid, ServerConstants.PLAYERNPC_AREA_STEPS - 1); - } - return null; - } } diff --git a/src/server/maps/MaplePlayerShop.java b/src/server/maps/MaplePlayerShop.java index d885270b2b..984c2c8199 100644 --- a/src/server/maps/MaplePlayerShop.java +++ b/src/server/maps/MaplePlayerShop.java @@ -26,7 +26,8 @@ import client.MapleClient; import client.inventory.Item; import client.inventory.MapleInventory; import client.inventory.MapleInventoryType; -import constants.ItemConstants; +import client.inventory.manipulator.MapleInventoryManipulator; +import client.inventory.manipulator.MapleKarmaManipulator; import constants.ServerConstants; import java.util.ArrayList; import java.util.Collections; @@ -38,7 +39,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import tools.locks.MonitoredReentrantLock; import net.SendOpcode; -import server.MapleInventoryManipulator; import server.MapleItemInformationProvider; import tools.MaplePacketCreator; import tools.Pair; @@ -237,13 +237,11 @@ public class MaplePlayerShop extends AbstractMapleMapObject { return; } - if ((newItem.getFlag() & ItemConstants.KARMA) == ItemConstants.KARMA) { - newItem.setFlag((byte) (newItem.getFlag() ^ ItemConstants.KARMA)); - } + MapleKarmaManipulator.toggleKarmaFlagToUntradeable(newItem); visitorLock.lock(); try { - int price = (int) Math.min((long)pItem.getPrice() * quantity, Integer.MAX_VALUE); + int price = (int) Math.min((float)pItem.getPrice() * quantity, Integer.MAX_VALUE); if (c.getPlayer().getMeso() >= price) { if (canBuy(c, newItem)) { diff --git a/src/server/quest/actions/ItemAction.java b/src/server/quest/actions/ItemAction.java index e06c16c40d..f5560fe4bb 100644 --- a/src/server/quest/actions/ItemAction.java +++ b/src/server/quest/actions/ItemAction.java @@ -34,7 +34,7 @@ import java.util.LinkedList; import java.util.List; import provider.MapleData; import provider.MapleDataTool; -import server.MapleInventoryManipulator; +import client.inventory.manipulator.MapleInventoryManipulator; import server.quest.MapleQuest; import server.quest.MapleQuestActionType; import tools.MaplePacketCreator; diff --git a/src/tools/MaplePacketCreator.java b/src/tools/MaplePacketCreator.java index b513eb1fb3..14f20d6da4 100644 --- a/src/tools/MaplePacketCreator.java +++ b/src/tools/MaplePacketCreator.java @@ -189,7 +189,7 @@ public class MaplePacketCreator { mplew.writeInt(0); } - private static void addCharLook(final MaplePacketLittleEndianWriter mplew, MapleCharacter chr, boolean mega) { + protected static void addCharLook(final MaplePacketLittleEndianWriter mplew, MapleCharacter chr, boolean mega) { mplew.write(chr.getGender()); mplew.write(chr.getSkinColor().getId()); // skin color mplew.writeInt(chr.getFace()); // face @@ -346,15 +346,15 @@ public class MaplePacketCreator { } } - private static void addItemInfo(final MaplePacketLittleEndianWriter mplew, Item item) { - addItemInfo(mplew, item, false); - } - private static void addExpirationTime(final MaplePacketLittleEndianWriter mplew, long time) { mplew.writeLong(getTime(time)); } - private static void addItemInfo(final MaplePacketLittleEndianWriter mplew, Item item, boolean zeroPosition) { + private static void addItemInfo(final MaplePacketLittleEndianWriter mplew, Item item) { + addItemInfo(mplew, item, false); + } + + protected static void addItemInfo(final MaplePacketLittleEndianWriter mplew, Item item, boolean zeroPosition) { MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); boolean isCash = ii.isCash(item.getItemId()); boolean isPet = item.getPetId() > -1; @@ -1360,17 +1360,6 @@ public class MaplePacketCreator { return spawnMonsterInternal(life, false, newSpawn, false, 0, false); } - /** - * Gets a spawn monster packet. - * - * @param life The monster to spawn. - * @param newSpawn Is it a new spawn? - * @return The spawn monster packet. - */ - public static byte[] spawnHPQMonster(MapleMonster life, boolean newSpawn) { - return spawnMonsterInternal(life, false, newSpawn, false, 0, false); - } - /** * Gets a spawn monster packet. * @@ -1598,6 +1587,7 @@ public class MaplePacketCreator { * @param skillLevel The level of the skill to use. * @return The move response packet. */ + public static byte[] moveMonsterResponse(int objectid, short moveid, int currentMp, boolean useSkills, int skillId, int skillLevel) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(13); mplew.writeShort(SendOpcode.MOVE_MONSTER_RESPONSE.getValue()); @@ -1609,7 +1599,7 @@ public class MaplePacketCreator { mplew.write(skillLevel); return mplew.getPacket(); } - + /** * Gets a general chat packet. * @@ -1813,7 +1803,16 @@ public class MaplePacketCreator { * @param chr The character to spawn to other clients. * @return The spawn player packet. */ + public static byte[] spawnPlayerMapObject(MapleCharacter chr) { + return spawnPlayerMapObject(chr, false); + } + + public static byte[] spawnEnterPlayerMapObject(MapleCharacter chr) { + return spawnPlayerMapObject(chr, true); + } + + private static byte[] spawnPlayerMapObject(MapleCharacter chr, boolean enteringField) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); mplew.writeShort(SendOpcode.SPAWN_PLAYER.getValue()); mplew.writeInt(chr.getId()); @@ -1916,8 +1915,17 @@ public class MaplePacketCreator { mplew.writeInt(chr.getInventory(MapleInventoryType.CASH).countById(5110000)); mplew.writeInt(chr.getItemEffect()); mplew.writeInt(ItemConstants.getInventoryType(chr.getChair()) == MapleInventoryType.SETUP ? chr.getChair() : 0); - mplew.writePos(chr.getPosition()); - mplew.write(chr.getStance()); + + if(enteringField) { + Point spawnPos = new Point(chr.getPosition()); + spawnPos.y -= 42; + mplew.writePos(spawnPos); + mplew.write(6); + } else { + mplew.writePos(chr.getPosition()); + mplew.write(chr.getStance()); + } + mplew.writeShort(0);//chr.getFh() mplew.write(0); MaplePet[] pet = chr.getPets(); @@ -2515,6 +2523,30 @@ public class MaplePacketCreator { return mplew.getPacket(); } + public static byte[] sendMapleLifeCharacterInfo() { + final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SendOpcode.MAPLELIFE_RESULT.getValue()); + mplew.writeInt(0); + return mplew.getPacket(); + } + + public static byte[] sendMapleLifeNameError() { + final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SendOpcode.MAPLELIFE_RESULT.getValue()); + mplew.writeInt(2); + mplew.writeInt(3); + mplew.write(0); + return mplew.getPacket(); + } + + public static byte[] sendMapleLifeError(int code) { + final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SendOpcode.MAPLELIFE_ERROR.getValue()); + mplew.write(0); + mplew.writeInt(code); + return mplew.getPacket(); + } + public static byte[] charNameResponse(String charname, boolean nameUsed) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); mplew.writeShort(SendOpcode.CHAR_NAME_RESPONSE.getValue()); @@ -4525,13 +4557,27 @@ public class MaplePacketCreator { return mplew.getPacket(); } - public static byte[] sendSpouseChat(MapleCharacter wife, String msg) { + /* + public static byte[] sendSpouseChat(MapleCharacter partner, String msg) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); mplew.writeShort(SendOpcode.SPOUSE_CHAT.getValue()); - mplew.writeMapleAsciiString(wife.getName()); + mplew.writeMapleAsciiString(partner.getName()); mplew.writeMapleAsciiString(msg); return mplew.getPacket(); } + */ + + public static byte[] OnCoupleMessage(String fiance, String text, boolean spouse) { + MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SendOpcode.SPOUSE_CHAT.getValue()); + mplew.write(spouse ? 5 : 4); // v2 = CInPacket::Decode1(a1) - 4; + if (spouse) { // if ( v2 ) { + mplew.writeMapleAsciiString(fiance); + } + mplew.write(spouse ? 5 : 1); + mplew.writeMapleAsciiString(text); + return mplew.getPacket(); + } public static byte[] addMessengerPlayer(String from, MapleCharacter chr, int position, int channel) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); @@ -5548,37 +5594,37 @@ public class MaplePacketCreator { Map equip = npc.getEquips(); Map myEquip = new LinkedHashMap<>(); Map maskedEquip = new LinkedHashMap<>(); - for(short position : equip.keySet()){ + for(short position : equip.keySet()) { short pos = (byte) (position * -1); - if(pos < 100 && myEquip.get(pos) == null){ + if(pos < 100 && myEquip.get(pos) == null) { myEquip.put(pos, equip.get(position)); - }else if((pos > 100 || pos == -128) && pos != 111){ // don't ask. o.o + } else if((pos > 100 && pos != 111) || pos == -128) { // don't ask. o.o pos -= 100; - if(myEquip.get(pos) != null){ + if(myEquip.get(pos) != null) { maskedEquip.put(pos, myEquip.get(pos)); } myEquip.put(pos, equip.get(position)); - }else if(myEquip.get(pos) != null){ + } else if(myEquip.get(pos) != null) { maskedEquip.put(pos, equip.get(position)); } } - for(Entry entry : myEquip.entrySet()){ + for(Entry entry : myEquip.entrySet()) { mplew.write(entry.getKey()); mplew.writeInt(entry.getValue()); } mplew.write(0xFF); - for(Entry entry : maskedEquip.entrySet()){ + for(Entry entry : maskedEquip.entrySet()) { mplew.write(entry.getKey()); mplew.writeInt(entry.getValue()); } mplew.write(0xFF); Integer cWeapon = equip.get((byte) -111); - if(cWeapon != null){ + if(cWeapon != null) { mplew.writeInt(cWeapon); - }else{ + } else { mplew.writeInt(0); } - for(int i = 0; i < 3; i++){ + for(int i = 0; i < 3; i++) { mplew.writeInt(0); } return mplew.getPacket(); @@ -5589,7 +5635,6 @@ public class MaplePacketCreator { mplew.writeShort(SendOpcode.IMITATED_NPC_DATA.getValue()); mplew.write(0x00); mplew.writeInt(oid); - for(int i = 0; i < ServerConstants.DEBUG_VALUES[0]; i++) mplew.writeInt(0); return mplew.getPacket(); } @@ -5734,7 +5779,52 @@ public class MaplePacketCreator { } return mplew.getPacket(); } - + + /* 1: cannot find char info, + 2: cannot transfer under 20, + 3: cannot send banned, + 4: cannot send married, + 5: cannot send guild leader, + 6: cannot send if account already requested transfer, + 7: cannot transfer within 30days, + 8: must quit family, + 9: unknown error + */ + public static byte[] sendWorldTransferRules(int error) { + final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SendOpcode.CASHSHOP_CHECK_TRANSFER_WORLD_POSSIBLE_RESULT.getValue()); + mplew.writeInt(0); + mplew.write(0); + mplew.write(error); + mplew.writeInt(0); + + return mplew.getPacket(); + } + + /* 1: name change already submitted + 2: name change within a month + 3: recently banned + 4: unknown error + */ + public static byte[] sendNameTransferRules(int error) { + final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SendOpcode.CASHSHOP_CHECK_NAME_CHANGE_POSSIBLE_RESULT.getValue()); + mplew.writeInt(0); + mplew.write(error); + mplew.writeInt(0); + + return mplew.getPacket(); + } + + public static byte[] sendNameTransferCheck(boolean canUseName) { + final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SendOpcode.CASHSHOP_CHECK_NAME_CHANGE.getValue()); + mplew.writeShort(0); + mplew.writeBool(!canUseName); + + return mplew.getPacket(); + } + public static byte[] showMTSCash(MapleCharacter p) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); mplew.writeShort(SendOpcode.MTS_OPERATION2.getValue()); @@ -6215,11 +6305,30 @@ public class MaplePacketCreator { public static byte[] showItemLevelup() { return showSpecialEffect(15); } - + + public static byte[] showBuybackEffect() { + final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SendOpcode.SHOW_ITEM_GAIN_INCHAT.getValue()); + mplew.write(11); + mplew.writeInt(0); + + return mplew.getPacket(); + } + + public static byte[] showForeignBuybackEffect(int cid) { + final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SendOpcode.SHOW_FOREIGN_EFFECT.getValue()); + mplew.writeInt(cid); + mplew.write(11); + mplew.writeInt(0); + + return mplew.getPacket(); + } + /** - * 6 = Exp did not drop (Safety Charms) 7 = Enter portal sound 8 = Job - * change 9 = Quest complete 10 = Recovery 14 = Monster book pickup 15 = - * Equipment levelup 16 = Maker Skill Success 19 = Exp card [500, 200, 50] + * 0 = Levelup 6 = Exp did not drop (Safety Charms) 7 = Enter portal sound 8 = Job + * change 9 = Quest complete 10 = Recovery 11 = Buff effect 14 = Monster book pickup 15 = + * Equipment levelup 16 = Maker Skill Success 17 = Buff effect w/ sfx 19 = Exp card [500, 200, 50] 21 = Wheel of destiny 26 = Spirit Stone * * @param effect * @return @@ -6619,49 +6728,6 @@ public class MaplePacketCreator { return mplew.getPacket(); } - public static byte[] sendEngagementRequest(String name) { - final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); - mplew.writeShort(SendOpcode.MARRIAGE_REQUEST.getValue()); // has requested engagement. Will you accept this proposal? - mplew.write(0); - mplew.writeMapleAsciiString(name); // name - mplew.writeInt(10); // playerid - return mplew.getPacket(); - } - - public static byte[] sendGroomWishlist() { - final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); - mplew.writeShort(SendOpcode.MARRIAGE_REQUEST.getValue()); // has requested engagement. Will you accept this proposal? - mplew.write(9); - return mplew.getPacket(); - } - - public static byte[] sendBrideWishList(List items) { - final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); - mplew.writeShort(SendOpcode.WEDDING_GIFT_RESULT.getValue()); - mplew.write(0x0A); - mplew.writeLong(-1); // ? - mplew.writeInt(0); // ? - mplew.write(items.size()); - for (Item item : items) { - addItemInfo(mplew, item, true); - } - return mplew.getPacket(); - } - - public static byte[] addItemToWeddingRegistry(MapleCharacter chr, Item item) { - final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); - mplew.writeShort(SendOpcode.WEDDING_GIFT_RESULT.getValue()); - mplew.write(0x0B); - mplew.writeInt(0); - for (int i = 0; i < 0; i++) // f4 - { - mplew.write(0); - } - - addItemInfo(mplew, item, true); - return mplew.getPacket(); - } - public static byte[] sendFamilyJoinResponse(boolean accepted, String added) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); mplew.writeShort(SendOpcode.FAMILY_JOIN_REQUEST_RESULT.getValue()); @@ -6849,7 +6915,7 @@ public class MaplePacketCreator { public static byte[] marriageMessage(int type, String charname) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); mplew.writeShort(SendOpcode.NOTIFY_MARRIAGE.getValue()); - mplew.write(type); + mplew.write(type); // 0: guild, 1: family mplew.writeMapleAsciiString("> " + charname); //To fix the stupid packet lol return mplew.getPacket(); @@ -6969,17 +7035,26 @@ public class MaplePacketCreator { mplew.writeInt(0); mplew.writeInt(ring.getItemId()); } - mplew.writeShort(chr.getMarriageRing() != null ? 1 : 0); - int marriageId = 30000; - if (chr.getMarriageRing() != null) { - mplew.writeInt(marriageId); - mplew.writeInt(chr.getId()); - mplew.writeInt(chr.getMarriageRing().getPartnerChrId()); - mplew.writeShort(3); - mplew.writeInt(chr.getMarriageRing().getRingId()); - mplew.writeInt(chr.getMarriageRing().getPartnerRingId()); - mplew.writeAsciiString(StringUtil.getRightPaddedStr(chr.getGender() == 0 ? chr.getName() : chr.getMarriageRing().getPartnerName(), '\0', 13)); - mplew.writeAsciiString(StringUtil.getRightPaddedStr(chr.getGender() == 0 ? chr.getMarriageRing().getPartnerName() : chr.getName(), '\0', 13)); + + if(chr.getPartnerId() > 0) { + MapleRing marriageRing = chr.getMarriageRing(); + + mplew.writeShort(1); + mplew.writeInt(chr.getRelationshipId()); + mplew.writeInt(chr.getGender() == 0 ? chr.getId() : chr.getPartnerId()); + mplew.writeInt(chr.getGender() == 0 ? chr.getPartnerId() : chr.getId()); + mplew.writeShort((marriageRing != null) ? 3 : 1); + if (marriageRing != null) { + mplew.writeInt(marriageRing.getItemId()); + mplew.writeInt(marriageRing.getItemId()); + } else { + mplew.writeInt(1112803); // Engagement Ring's Outcome (doesn't matter for engagement) + mplew.writeInt(1112803); // Engagement Ring's Outcome (doesn't matter for engagement) + } + mplew.writeAsciiString(StringUtil.getRightPaddedStr(chr.getGender() == 0 ? chr.getName() : MapleCharacter.getNameById(chr.getPartnerId()), '\0', 13)); + mplew.writeAsciiString(StringUtil.getRightPaddedStr(chr.getGender() == 0 ? MapleCharacter.getNameById(chr.getPartnerId()) : chr.getName(), '\0', 13)); + } else { + mplew.writeShort(0); } } @@ -7257,23 +7332,6 @@ public class MaplePacketCreator { return mplew.getPacket(); } - - public static byte[] showCashShopSurprise(int itemid) { - final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); - mplew.writeShort(ServerConstants.DEBUG_VALUES[0]); - - for(int i = 0; i < ServerConstants.DEBUG_VALUES[1]; i++) { - mplew.write(0); - } - - mplew.writeInt(itemid); - - for(int i = 0; i < ServerConstants.DEBUG_VALUES[2]; i++) { - mplew.write(0); - } - - return mplew.getPacket(); - } public static byte[] showCashInventory(MapleClient c) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); diff --git a/src/tools/packets/Wedding.java b/src/tools/packets/Wedding.java new file mode 100644 index 0000000000..97ef9bb347 --- /dev/null +++ b/src/tools/packets/Wedding.java @@ -0,0 +1,452 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +package tools.packets; + +import client.inventory.Item; +import client.inventory.MapleInventoryType; +import client.MapleCharacter; +import java.util.ArrayList; +import java.util.List; +import tools.MaplePacketCreator; +import tools.StringUtil; +import tools.data.output.MaplePacketLittleEndianWriter; + +/** + * CField_Wedding, CField_WeddingPhoto, CWeddingMan, OnMarriageResult, and all Wedding/Marriage enum/structs. + * + * @author Eric + */ +public class Wedding extends MaplePacketCreator { + private static final short MARRIAGE_REQUEST = 0x48; + private static final short MARRIAGE_RESULT = 0x49; + private static final short WEDDING_GIFT_RESULT = 0x4A; + private static final short NOTIFY_MARRIED_PARTNER_MAP_TRANSFER = 0x4B; + private static final short WEDDING_PHOTO = 0x2B; + private static final short WEDDING_PROGRESS = 0x140; + private static final short WEDDING_CEREMONY_END = 0x141; + + /* + 00000000 CWeddingMan struc ; (sizeof=0x104) + 00000000 vfptr dd ? ; offset + 00000004 ___u1 $01CBC6800BD386B8A8FD818EAD990BEC ? + 0000000C m_mCharIDToMarriageNo ZMap ? + 00000024 m_mReservationPending ZMap,unsigned long> ? + 0000003C m_mReservationPendingGroom ZMap,unsigned long> ? + 00000054 m_mReservationPendingBride ZMap,unsigned long> ? + 0000006C m_mReservationStartUser ZMap ? + 00000084 m_mReservationCompleted ZMap,unsigned long> ? + 0000009C m_mGroomWishList ZMap > >,unsigned long> ? + 000000B4 m_mBrideWishList ZMap > >,unsigned long> ? + 000000CC m_mEngagementPending ZMap,unsigned long> ? + 000000E4 m_nCurrentWeddingState dd ? + 000000E8 m_dwCurrentWeddingNo dd ? + 000000EC m_dwCurrentWeddingMap dd ? + 000000F0 m_bIsReservationLoaded dd ? + 000000F4 m_dwNumGuestBless dd ? + 000000F8 m_bPhotoSuccess dd ? + 000000FC m_tLastUpdate dd ? + 00000100 m_bStartWeddingCeremony dd ? + 00000104 CWeddingMan ends + */ + + public class Field_Wedding { + public int m_nNoticeCount; + public int m_nCurrentStep; + public int m_nBlessStartTime; + } + + public class Field_WeddingPhoto { + public boolean m_bPictureTook; + } + + public class GW_WeddingReservation { + public int dwReservationNo; + public int dwGroom, dwBride; + public String sGroomName, sBrideName; + public int usWeddingType; + } + + public class WeddingWishList { + public MapleCharacter pUser; + public int dwMarriageNo; + public int nGender; + public int nWLType; + public int nSlotCount; + public List asWishList = new ArrayList<>(); + public int usModifiedFlag; // dword + public boolean bLoaded; + } + + public class GW_WeddingWishList { + public final int WEDDINGWL_MAX = 0xA; // enum WEDDINGWL + public int dwReservationNo; + public byte nGender; + public String sItemName; + } + + public enum MarriageStatus { + SINGLE(0x0), + ENGAGED(0x1), + RESERVED(0x2), + MARRIED(0x3); + private int ms; + private MarriageStatus(int ms) { + this.ms = ms; + } + + public int getMarriageStatus() { + return ms; + } + } + + public enum MarriageRequest { + AddMarriageRecord(0x0), + SetMarriageRecord(0x1), + DeleteMarriageRecord(0x2), + LoadReservation(0x3), + AddReservation(0x4), + DeleteReservation(0x5), + GetReservation(0x6); + private int req; + private MarriageRequest(int req) { + this.req = req; + } + + public int getMarriageRequest() { + return req; + } + } + + public enum WeddingType { + CATHEDRAL(0x1), + VEGAS(0x2), + CATHEDRAL_PREMIUM(0xA), + CATHEDRAL_NORMAL(0xB), + VEGAS_PREMIUM(0x14), + VEGAS_NORMAL(0x15); + private int wt; + private WeddingType(int wt) { + this.wt = wt; + } + + public int getType() { + return wt; + } + } + + public enum WeddingMap { + WEDDINGTOWN(680000000), + CHAPEL_STARTMAP(680000110), + CATHEDRAL_STARTMAP(680000210), + PHOTOMAP(680000300), + EXITMAP(680000500); + private int wm; + private WeddingMap(int wm) { + this.wm = wm; + } + + public int getMap() { + return wm; + } + } + + public enum WeddingItem { + WR_MOONSTONE(1112803), // Wedding Ring + WR_STARGEM(1112806), + WR_GOLDENHEART(1112807), + WR_SILVERSWAN(1112809), + ERB_MOONSTONE(2240000), // Engagement Ring Box + ERB_STARGEM(2240001), + ERB_GOLDENHEART(2240002), + ERB_SILVERSWAN(2240003), + ERBE_MOONSTONE(4031357), // Engagement Ring Box (Empty) + ER_MOONSTONE(4031358), // Engagement Ring + ERBE_STARGEM(4031359), + ER_STARGEM(4031360), + ERBE_GOLDENHEART(4031361), + ER_GOLDENHEART(4031362), + ERBE_SILVERSWAN(4031363), + ER_SILVERSWAN(4031364), + PARENTS_BLESSING(4031373), // Parents Blessing + OFFICIATORS_PERMISSION(4031374), // Officiator's Permission + WR_CATHEDRAL_PREMIUM(4031375), // Wedding Ring? + WR_VEGAS_PREMIUM(4031376), + IB_VEGAS(4031377), // toSend invitation + IB_CATHEDRAL(4031395), // toSend invitation + IG_VEGAS(4031406), // rcvd invitation + IG_CATHEDRAL(4031407), // rcvd invitation + OB_FORCOUPLE(4031424), // Onyx Box? For Couple + WR_CATHEDRAL_NORMAL(4031480), // Wedding Ring? + WR_VEGAS_NORMAL(4031481), + WT_CATHEDRAL_NORMAL(5251000), // Wedding Ticket + WT_VEGAS_NORMAL(5251001), + WT_VEGAS_PREMIUM(5251002), + WT_CATHEDRAL_PREMIUM(5251003); + private int wi; + private WeddingItem(int wi) { + this.wi = wi; + } + + public int getItem() { + return wi; + } + } + + /** + * has requested engagement. Will you accept this proposal? + * + * @param name + * @param playerid + * @return mplew + */ + public static byte[] OnMarriageRequest(String name, int playerid) { + MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(MARRIAGE_REQUEST); + mplew.write(0); //mode, 0 = engage, 1 = cancel, 2 = answer.. etc + mplew.writeMapleAsciiString(name); // name + mplew.writeInt(playerid); // playerid + return mplew.getPacket(); + } + + /** + * A quick rundown of how (I think based off of enough BMS searching) WeddingPhoto_OnTakePhoto works: + * - We send this packet with (first) the Groom / Bride IGNs + * - We then send a fieldId (unsure about this part at the moment, 90% sure it's the id of the map) + * - After this, we write an integer of the amount of characters within the current map (which is the Cake Map -- exclude users within Exit Map) + * - Once we've retrieved the size of the characters, we begin to write information about them (Encode their name, guild, etc info) + * - Now that we've Encoded our character data, we begin to Encode the ScreenShotPacket which requires a TemplateID, IGN, and their positioning + * - Finally, after encoding all of our data, we send this packet out to a MapGen application server + * - The MapGen server will then retrieve the packet byte array and convert the bytes into a ImageIO 2D JPG output + * - The result after converting into a JPG will then be remotely uploaded to /weddings/ with ReservedGroomName_ReservedBrideName to be displayed on the web server. + * + * - Will no longer continue Wedding Photos, needs a WvsMapGen :( + * + * @param ReservedGroomName The groom IGN of the wedding + * @param ReservedBrideName The bride IGN of the wedding + * @param m_dwField The current field id (the id of the cake map, ex. 680000300) + * @param m_uCount The current user count (equal to m_dwUsers.size) + * @param m_dwUsers The List of all MapleCharacter guests within the current cake map to be encoded + * @return mplew (MaplePacket) Byte array to be converted and read for byte[]->ImageIO + */ + public static byte[] OnTakePhoto(String ReservedGroomName, String ReservedBrideName, int m_dwField, List m_dwUsers) { // OnIFailedAtWeddingPhotos + MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(WEDDING_PHOTO); // v53 header, convert -> v83 + mplew.writeMapleAsciiString(ReservedGroomName); + mplew.writeMapleAsciiString(ReservedBrideName); + mplew.writeInt(m_dwField); // field id? + mplew.writeInt(m_dwUsers.size()); + + for (MapleCharacter guest : m_dwUsers) { + // Begin Avatar Encoding + addCharLook(mplew, guest, false); // CUser::EncodeAvatar + mplew.writeInt(30000); // v20 = *(_DWORD *)(v13 + 2192) -- new groom marriage ID?? + mplew.writeInt(30000); // v20 = *(_DWORD *)(v13 + 2192) -- new bride marriage ID?? + mplew.writeMapleAsciiString(guest.getName()); + mplew.writeMapleAsciiString(guest.getGuildId() > 0 && guest.getGuild() != null ? guest.getGuild().getName() : ""); + mplew.writeShort(guest.getGuildId() > 0 && guest.getGuild() != null ? guest.getGuild().getLogoBG() : 0); + mplew.write(guest.getGuildId() > 0 && guest.getGuild() != null ? guest.getGuild().getLogoBGColor() : 0); + mplew.writeShort(guest.getGuildId() > 0 && guest.getGuild() != null ? guest.getGuild().getLogo() : 0); + mplew.write(guest.getGuildId() > 0 && guest.getGuild() != null ? guest.getGuild().getLogoColor() : 0); + mplew.writeShort(guest.getPosition().x); // v18 = *(_DWORD *)(v13 + 3204); + mplew.writeShort(guest.getPosition().y); // v20 = *(_DWORD *)(v13 + 3208); + // Begin Screenshot Encoding + mplew.write(1); // // if ( *(_DWORD *)(v13 + 288) ) { COutPacket::Encode1(&thisa, v20); + // CPet::EncodeScreenShotPacket(*(CPet **)(v13 + 288), &thisa); + mplew.writeInt(1); // dwTemplateID + mplew.writeMapleAsciiString(guest.getName()); // m_sName + mplew.writeShort(guest.getPosition().x); // m_ptCurPos.x + mplew.writeShort(guest.getPosition().y); // m_ptCurPos.y + mplew.write(guest.getStance()); // guest.m_bMoveAction + } + + return mplew.getPacket(); + } + + /** + * Enable spouse chat and their engagement ring without @relog + * + * @param marriageId + * @param chr + * @param wedding + * @return mplew + */ + public static byte[] OnMarriageResult(int marriageId, MapleCharacter chr, boolean wedding) { + MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(MARRIAGE_RESULT); + mplew.write(11); + mplew.writeInt(marriageId); + mplew.writeInt(chr.getGender() == 0 ? chr.getId() : chr.getPartnerId()); + mplew.writeInt(chr.getGender() == 0 ? chr.getPartnerId() : chr.getId()); + mplew.writeShort(wedding ? 3 : 1); + if (wedding) { + mplew.writeInt(chr.getMarriageItemId()); + mplew.writeInt(chr.getMarriageItemId()); + } else { + mplew.writeInt(1112803); // Engagement Ring's Outcome (doesn't matter for engagement) + mplew.writeInt(1112803); // Engagement Ring's Outcome (doesn't matter for engagement) + } + mplew.writeAsciiString(StringUtil.getRightPaddedStr(chr.getGender() == 0 ? chr.getName() : MapleCharacter.getNameById(chr.getPartnerId()), '\0', 13)); + mplew.writeAsciiString(StringUtil.getRightPaddedStr(chr.getGender() == 0 ? MapleCharacter.getNameById(chr.getPartnerId()) : chr.getName(), '\0', 13)); + + return mplew.getPacket(); + } + + /** + * To exit the Engagement Window (Waiting for her response...), we send a GMS-like pop-up. + * + * @param msg + * @return mplew + */ + public static byte[] OnMarriageResult(final byte msg) { + MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(MARRIAGE_RESULT); + mplew.write(msg); + if (msg == 36) { + mplew.write(1); + mplew.writeMapleAsciiString("You are now engaged."); + } + return mplew.getPacket(); + } + + /** + * The World Map includes 'loverPos' in which this packet controls + * + * @param partner + * @param mapid + * @return mplew + */ + public static byte[] OnNotifyWeddingPartnerTransfer(int partner, int mapid) { + MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(NOTIFY_MARRIED_PARTNER_MAP_TRANSFER); + mplew.writeInt(mapid); + mplew.writeInt(partner); + + return mplew.getPacket(); + } + + /** + * The wedding packet to display Pelvis Bebop and enable the Wedding Ceremony Effect between two characters + * CField_Wedding::OnWeddingProgress - Stages + * CField_Wedding::OnWeddingCeremonyEnd - Wedding Ceremony Effect + * + * @param SetBlessEffect + * @param groom + * @param bride + * @param step + * @return mplew + */ + public static byte[] OnWeddingProgress(boolean SetBlessEffect, int groom, int bride, byte step) { + MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SetBlessEffect ? WEDDING_CEREMONY_END : WEDDING_PROGRESS); + if (!SetBlessEffect) { // in order for ceremony packet to send, byte step = 2 must be sent first + mplew.write(step); + } + mplew.writeInt(groom); + mplew.writeInt(bride); + return mplew.getPacket(); + } + + /** + * When we open a Wedding Invitation, we display the Bride & Groom + * + * @param groom + * @param bride + * @return mplew + */ + public static byte[] sendWeddingInvitation(String groom, String bride) { + MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(MARRIAGE_RESULT); + mplew.write(15); + mplew.writeMapleAsciiString(groom); + mplew.writeMapleAsciiString(bride); + mplew.writeShort(1); // 0 = Cathedral Normal?, 1 = Cathedral Premium?, 2 = Chapel Normal? + return mplew.getPacket(); + } + + public static byte[] sendWishList() { // fuck my life + MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(MARRIAGE_REQUEST); + mplew.write(9); + return mplew.getPacket(); + } + + /** + * Handles all of WeddingWishlist packets + * + * @param mode + * @param itemnames + * @param items + * @return mplew + */ + public static byte[] OnWeddingGiftResult(byte mode, List itemnames, List items) { + // if (itemnames == null || itemnames.size() < 1) { // for now lol + // itemnames = new ArrayList<>(); + // itemnames.add("mesos"); + // itemnames.add("rare items"); + // itemnames.add("more mesos"); + // } + MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(WEDDING_GIFT_RESULT); + mplew.write(mode); + switch(mode) { + case 0x09: { // Load Wedding Registry + mplew.write(itemnames.size()); + for (String names : itemnames) { + mplew.writeMapleAsciiString(names); + } + mplew.write(itemnames.size()); + for (String names : itemnames) { + mplew.writeMapleAsciiString(names); + } + // need to load items somehow + break; + } + case 0xA: // Load Bride's Wishlist + case 0xF: // 10, 15, 16 = CWishListRecvDlg::OnPacket + case 0xB: { // Add Item to Wedding Registry + // 11 : You have sent a gift | 12 : You cannot give more than one present for each wishlist | 13 : Failed to send the gift. | 14 : Failed to send the gift. + if (mode == 0xB) { + mplew.write(itemnames.size()); + for (String names : itemnames) { + mplew.writeMapleAsciiString(names); + } + } + switch (items.get((items.size() - 1)).getInventoryType()) { + case EQUIP: + mplew.writeLong(4); + break; + case USE: + mplew.writeLong(8); + break; + case SETUP: + mplew.writeLong(16); + break; + case ETC: + mplew.writeLong(32); + break; + default: // impossible flag, cash item can't be sent + if (items.get((items.size() - 1)).getInventoryType() != MapleInventoryType.CASH) { + mplew.writeLong(0); + } + } + if (mode == 0xA) { // random unknown bytes involved within Bride's Wishlist + mplew.writeInt(0); + } + mplew.write(items.size()); + for (Item item : items) { + MaplePacketCreator.addItemInfo(mplew, item, true); + } + break; + } + default: { + System.out.println("Unknown Wishlist Mode: " + mode); + break; + } + } + return mplew.getPacket(); + } +} \ No newline at end of file diff --git a/tools/MapleMobBookUpdate/lib/MonsterBook_updated.img.xml b/tools/MapleMobBookUpdate/lib/MonsterBook_updated.img.xml index efa529f9a5..dff7aa8e37 100644 --- a/tools/MapleMobBookUpdate/lib/MonsterBook_updated.img.xml +++ b/tools/MapleMobBookUpdate/lib/MonsterBook_updated.img.xml @@ -137,13 +137,16 @@ - - - - - - - + + + + + + + + + + @@ -188,15 +191,16 @@ - - - - - - - - - + + + + + + + + + + @@ -227,13 +231,14 @@ - - - - - - - + + + + + + + + @@ -442,13 +447,14 @@ - - - - - - - + + + + + + + + @@ -678,7 +684,6 @@ - @@ -1099,6 +1104,7 @@ + @@ -1132,6 +1138,8 @@ + + @@ -1340,10 +1348,11 @@ - - - - + + + + + @@ -7739,8 +7748,9 @@ - - + + + @@ -8207,19 +8217,18 @@ - - - - - - - - - - - - - + + + + + + + + + + + + @@ -10714,9 +10723,10 @@ - - - + + + + @@ -12406,11 +12416,12 @@ - - - - - + + + + + + @@ -12600,27 +12611,29 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -14267,9 +14280,10 @@ - - - + + + + @@ -14517,10 +14531,10 @@ - - - - + + + + @@ -14703,6 +14717,8 @@ + + @@ -14740,6 +14756,7 @@ + @@ -14783,7 +14800,8 @@ - + + diff --git a/wz/Etc.wz/Commodity.img.xml b/wz/Etc.wz/Commodity.img.xml index 3f24f07709..dd76a9bf3b 100644 --- a/wz/Etc.wz/Commodity.img.xml +++ b/wz/Etc.wz/Commodity.img.xml @@ -80844,13 +80844,13 @@ - + - - - + + + - + @@ -80864,23 +80864,23 @@ - + - + - + - + - - - + + + - + - + @@ -81918,43 +81918,43 @@ - + - + - + - + - + - + - + - + - - - + + + - + - + - + - + - + - + @@ -82192,21 +82192,21 @@ - + - + - + - + diff --git a/wz/Item.wz/Consume/0202.img.xml b/wz/Item.wz/Consume/0202.img.xml index 98b1371c58..1a7597047c 100644 --- a/wz/Item.wz/Consume/0202.img.xml +++ b/wz/Item.wz/Consume/0202.img.xml @@ -537,7 +537,6 @@ - diff --git a/wz/Map.wz/Map/Map1/100000204.img.xml b/wz/Map.wz/Map/Map1/100000204.img.xml index e61625502d..3d72de07ad 100644 --- a/wz/Map.wz/Map/Map1/100000204.img.xml +++ b/wz/Map.wz/Map/Map1/100000204.img.xml @@ -134,8 +134,8 @@ - - + + @@ -162,9 +162,9 @@ + - @@ -175,11 +175,171 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wz/Map.wz/Map/Map1/101000004.img.xml b/wz/Map.wz/Map/Map1/101000004.img.xml index c79b89114e..eb4b3819bb 100644 --- a/wz/Map.wz/Map/Map1/101000004.img.xml +++ b/wz/Map.wz/Map/Map1/101000004.img.xml @@ -162,9 +162,9 @@ + - @@ -324,390 +324,534 @@ - + + + + + + + + + - + - + + + + + + + + + - - + + - + + + + + + + + + - - + + - - + + - + - - + + - + - - + + - + + + + + + + + + - - + + - + + + + + + + + + - - + + - + + + + + + + + + - - + + - + + + + + + + + + + + + + + + + + - - + + - + - - + + - + + + + + + + + + - - + + - - - - - - - + + + + + + + - + - - + + - + + + + + + + + + - - + + - + + + + + + + + + - - + + - + + + + + + + + + + + + + + + + + - - + + - - + + - + - - + + - + - - + + - + + + + + + + + + + + + + + + + + - - + + - + - - + + - + + + + + + + + + - - + + - + + + + + + + + + - - + + - + + + + + + + + + - - + + - + - - + + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + diff --git a/wz/Map.wz/Map/Map1/102000004.img.xml b/wz/Map.wz/Map/Map1/102000004.img.xml index eed42933d6..0a844798a6 100644 --- a/wz/Map.wz/Map/Map1/102000004.img.xml +++ b/wz/Map.wz/Map/Map1/102000004.img.xml @@ -46,18 +46,6 @@ - - - - - - - - - - - - @@ -69,7 +57,7 @@ - + @@ -81,6 +69,18 @@ + + + + + + + + + + + + @@ -184,32 +184,178 @@ - + - - - - + + + + - + - - - - - - + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -217,7 +363,7 @@ - + diff --git a/wz/Map.wz/Map/Map1/103000008.img.xml b/wz/Map.wz/Map/Map1/103000008.img.xml index e30ffac9d4..6a1af9a120 100644 --- a/wz/Map.wz/Map/Map1/103000008.img.xml +++ b/wz/Map.wz/Map/Map1/103000008.img.xml @@ -161,9 +161,9 @@ + - @@ -174,19 +174,171 @@ - + - + - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wz/Map.wz/Map/Map1/120000105.img.xml b/wz/Map.wz/Map/Map1/120000105.img.xml index 37f1ec49ed..5ead3d6081 100644 --- a/wz/Map.wz/Map/Map1/120000105.img.xml +++ b/wz/Map.wz/Map/Map1/120000105.img.xml @@ -149,9 +149,9 @@ + - @@ -210,30 +210,174 @@ - + - + - + - - - - - - + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -241,36 +385,36 @@ - + - + - + - - + + - + - - + + - + - + diff --git a/wz/Map.wz/Map/Map2/211010000.img.xml b/wz/Map.wz/Map/Map2/211010000.img.xml index 928fd5016e..c986e3c0e8 100644 --- a/wz/Map.wz/Map/Map2/211010000.img.xml +++ b/wz/Map.wz/Map/Map2/211010000.img.xml @@ -3454,7 +3454,7 @@ - + diff --git a/wz/Map.wz/Map/Map2/211020000.img.xml b/wz/Map.wz/Map/Map2/211020000.img.xml index 426f205cb1..cdb134d92e 100644 --- a/wz/Map.wz/Map/Map2/211020000.img.xml +++ b/wz/Map.wz/Map/Map2/211020000.img.xml @@ -3815,7 +3815,7 @@ - + diff --git a/wz/Map.wz/Map/Map2/211050000.img.xml b/wz/Map.wz/Map/Map2/211050000.img.xml index 7bbde287f9..2b223fc19a 100644 --- a/wz/Map.wz/Map/Map2/211050000.img.xml +++ b/wz/Map.wz/Map/Map2/211050000.img.xml @@ -3610,7 +3610,7 @@ - + diff --git a/wz/Map.wz/Map/Map2/222010300.img.xml b/wz/Map.wz/Map/Map2/222010300.img.xml index fe03f2faa4..812cdfae31 100644 --- a/wz/Map.wz/Map/Map2/222010300.img.xml +++ b/wz/Map.wz/Map/Map2/222010300.img.xml @@ -6011,7 +6011,7 @@ - + diff --git a/wz/Map.wz/Map/Map9/910500200.img.xml b/wz/Map.wz/Map/Map9/910500200.img.xml index 19173750e1..06d97a9e6a 100644 --- a/wz/Map.wz/Map/Map9/910500200.img.xml +++ b/wz/Map.wz/Map/Map9/910500200.img.xml @@ -4346,16 +4346,16 @@ - - + + - - + + diff --git a/wz/Quest.wz/QuestInfo.img.xml b/wz/Quest.wz/QuestInfo.img.xml index 571b160919..454de13c47 100644 --- a/wz/Quest.wz/QuestInfo.img.xml +++ b/wz/Quest.wz/QuestInfo.img.xml @@ -18198,7 +18198,7 @@ Able to proceed to 'Merry-go-round in Kampung' as next quest. - + diff --git a/wz/Sound.wz/Field.img.xml b/wz/Sound.wz/Field.img.xml index 2213c14dc4..c946c981f3 100644 --- a/wz/Sound.wz/Field.img.xml +++ b/wz/Sound.wz/Field.img.xml @@ -85,4 +85,27 @@ + + + + + + + + + diff --git a/wz/String.wz/MonsterBook.img.xml b/wz/String.wz/MonsterBook.img.xml index efa529f9a5..dff7aa8e37 100644 --- a/wz/String.wz/MonsterBook.img.xml +++ b/wz/String.wz/MonsterBook.img.xml @@ -137,13 +137,16 @@ - - - - - - - + + + + + + + + + + @@ -188,15 +191,16 @@ - - - - - - - - - + + + + + + + + + + @@ -227,13 +231,14 @@ - - - - - - - + + + + + + + + @@ -442,13 +447,14 @@ - - - - - - - + + + + + + + + @@ -678,7 +684,6 @@ - @@ -1099,6 +1104,7 @@ + @@ -1132,6 +1138,8 @@ + + @@ -1340,10 +1348,11 @@ - - - - + + + + + @@ -7739,8 +7748,9 @@ - - + + + @@ -8207,19 +8217,18 @@ - - - - - - - - - - - - - + + + + + + + + + + + + @@ -10714,9 +10723,10 @@ - - - + + + + @@ -12406,11 +12416,12 @@ - - - - - + + + + + + @@ -12600,27 +12611,29 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + @@ -14267,9 +14280,10 @@ - - - + + + + @@ -14517,10 +14531,10 @@ - - - - + + + + @@ -14703,6 +14717,8 @@ + + @@ -14740,6 +14756,7 @@ + @@ -14783,7 +14800,8 @@ - + +