diff --git a/docs/mychanges_ptbr.txt b/docs/mychanges_ptbr.txt index 9f5614badc..e9613c79d5 100644 --- a/docs/mychanges_ptbr.txt +++ b/docs/mychanges_ptbr.txt @@ -1660,4 +1660,55 @@ Incrementado mensagem custom de venda de produtos pelo mercante. 13 - 14 Fevereiro 2019, Corrigido limites na função isWeapon, que não contabilizaria certos itens corretamente. -Resolvido comportamento de puppets usando o novo sistema de aggro. \ No newline at end of file +Resolvido comportamento de puppets usando o novo sistema de aggro. + +18 Fevereiro 2019, +Corrigido um caso onde jogador em party poderia perder prioridade sobre loots recentes no chão ao mudar de mapas. +Ajustado contabilização de buffs no ganho de exp, agora bonus de party também contabiliza ganho pelos buffs. +Corrigido mercantes não checando estoque de itens apropriadamente ao pegar de volta itens colocados nos mercantes. +Corrigido referências a lojas de jogadores não sendo devidamente liberadas para visitantes quando dono fecha a loja, levando a problemas de update visuais no mapa ao realizar certas ações. +Ajustado mercantes para automaticamente fechar a loja assim que dono tenta sair da loja deixando a mesma vazia. +Adicionado opcodes de resultado de trades entre jogadores. +Incrementado resultados de trades, agora utilizando um conjunto de respostas mais parecido com o do GMS. + +19 - 20 Fevereiro 2019, +Implementado checagem de distância a portais no mapa em criação de loja/mercante de jogador. +Implementado checagem de IP localhost ao tentar logar em server com IP de produção. +Ajustado quests de door bosses, agora entregando emblemas a jogadores no início e retirando os mesmos ao terminar a quest. O item emblema é um dos requerimentos para acessar o boss da área. +Corrigido problema no sistema de login utilizando flag de detecção de múltiplas sessões abertas não tratando hashcodes negativos de IP. +Adicionado comandos de desenvolvedor que permitem visualizar situações de conexão de jogadores (avaliação de stack de IP abertos no servidor e sessões logadas). + +21 Fevereiro 2019, +Corrigido chalkboard não aparecendo corretamente para o usuário do item ao trocar mapas. +Revisado chalkboard agora removendo unidade do inventário ao usar. +Implementado checagem por chalkboard em salas do FM. +Adicionado opcodes de mensagens genéricas de guild. + +22 Fevereiro 2019, +Corrigido líder de aliança de guilds não sendo permitido convidar guilds logo após aumentar a capacidade da aliança. +Corrigido impossibilidade de abrir novos mercantes logo assim que último mercante expirou. +Implementado visualização do "tempo de sobra" ao reentrar no mercante (somente sendo dono da loja). +Ajustado resultado de skillbooks não aparecendo para todos jogadores no mapa. +Corrigido alguns comandos utilizando letras minúsculas em conteúdos de mensagens. + +23 Fevereiro 2019, +Adicionado funcionalidade de colocar tipo de cash a adicionar no comando GiveNx. +Implementado taxas de manutenção de mesos/itens no Fredrick. Após 100 dias, itens guardados são removidos conforme descrição de utilização das lojas. +Incrementado comportamento do NPC Fredrick. Como na descrição do serviço (ao abrir mercantes), itens guardados agora podem ser removidos após 100 dias, e há a perda de 1% da valia do montante vendido a cada dia que se passa sem os itens serem recuperados pelo jogador. + +25 - 28 Fevereiro 2019, +Adicionado número de locks disponíveis para seção de login e controle de item factory. +Incrementado quantidade de locks de atividades disponíveis por canal, visando uma carga relevante de pessoas online. +Corrigido abertura de inventário (comportamento do trade) sendo feita mesmo em cenários onde não é possível nem convidar o outro jogador. +Implementado sistema de gerenciamento de convites para as diversas modalidades no jogo (party, guild, messenger, etc). +Ajustado mercantes, agora salvando dados do jogador dono da loja a todo movimento de itens na loja não-aberta (quando o mesmo ainda está gerenciando itens na loja). +Implementado funcionalidade de arrendamento de mapas. Jogadores podem se tornar donos de mapa e evitar outros de farmar nos mesmos. +Ajustado ordenador de inventários, agora deixando itens projéteis (flechas, shurikens e balas) em ordem confortável para uso do jogador. + +01 Março 2019, +Adicionado efeito visual em ganho de Aran skills. +Implementado mecânica de pescaria no código-fonte. + +09 Março 2019, +Corrigido membros de party não recebendo bonus devidamente após membros sairem do mapa/party em alguns casos. +Revisado sistema de experiência em party. Ganhos de bonus agora levam em conta valores-base que membros de party ganham ao derrotar um mob para definir o ganho geral da equipe. Contabilização de ganhos remodelado, buscando por um modo de distribuição mais coerente. \ No newline at end of file diff --git a/launchtest.bat b/launchtest.bat new file mode 100644 index 0000000000..a44a45e8fb --- /dev/null +++ b/launchtest.bat @@ -0,0 +1,61 @@ +REM 'launchtest.bat' Author: Tochi +@echo off +set a=0 +title HeavenMS: Offline +color 1b +:clear +cls +echo HeavenMS Server Launcher +echo. +echo Commands: +echo ------------------------------------------------------------- +echo start - Start HeavenMS server +echo shutdown - Shut down HeavenMS server and close Launcher File +echo restart - Restart HeavenMS Launcher File +echo clear - Clear this window +echo ------------------------------------------------------------- +echo. + +:command +set /p s="Enter command: " +if "%s%"=="start" goto :start +if "%s%"=="shutdown" goto :shutdown +if "%s%"=="restart" goto :restart +if "%s%"=="clear" goto :clear +echo Wrong Command. +echo. +goto :command + +:start +if "%a%"=="1" ( +echo HeavenMS is already active! +echo. +goto :command +) +color 4c +echo This might take a while.... +echo. +title HeavenMS: activating +echo Server Launching... +start /b launch.bat +color 2a +title HeavenMS: Online +set a=1 +ping localhost -w 10000 >nul +echo. +goto :command + +:shutdown +color 4c +title HeavenMS: Shutting Down... +echo The Server Launcher will be close in a few seconds. +ping localhost -w 100000 >nul +taskkill /im cmd.exe + +:restart +color 4c +title HeavenMS: Restarting... +echo Please type 'start' in command box after bat file have been restarted. +ping localhost -w 100000 >nul +start launch.bat +taskkill /im cmd.exe \ No newline at end of file diff --git a/scripts/npc/2010009.js b/scripts/npc/2010009.js index 03f1b62737..3e01a7ae29 100644 --- a/scripts/npc/2010009.js +++ b/scripts/npc/2010009.js @@ -56,7 +56,7 @@ function action(mode, type, selection) { cm.sendNext("Guild Union is just as it says, a union of a number of guilds to form a super group. I am in charge of managing these Guild Unions."); cm.dispose(); } else if (selection == 1) { - cm.sendNext("To make a Guild Union, two and only two Guild Masters need to be in a party and both must be present on this room on the same channel. The leader of this party will be assigned as the Guild Union Master."); + cm.sendNext("To make a Guild Union, two and only #btwo Guild Masters need to be in a party#k and #bboth must be present on this room#k on the same channel. The leader of this party will be assigned as the Guild Union Master.\r\n\r\nInitially, #bonly two guilds#k can make part of the new Union, but over the time you can #rexpand#k the Union capacity by talking to me when the time comes and investing in an estipulated fee."); cm.dispose(); } else if(selection == 2) { if(!cm.isLeader()) { @@ -80,7 +80,7 @@ function action(mode, type, selection) { var rank = cm.getPlayer().getMGC().getAllianceRank(); if (rank == 1) - cm.sendYesNo("Do you want to increase your Alliance by one guild slot? The fee for this procedure is #b" + increaseCost + " mesos#k."); + cm.sendYesNo("Do you want to increase your Alliance by #rone guild#k slot? The fee for this procedure is #b" + increaseCost + " mesos#k."); else { cm.sendNext("Only the Guild Union Master can expand the number of guilds in the Union."); cm.dispose(); diff --git a/scripts/npc/9201128.js b/scripts/npc/9201128.js index 64b0c24817..6fea1725b9 100644 --- a/scripts/npc/9201128.js +++ b/scripts/npc/9201128.js @@ -1,5 +1,6 @@ var map = 677000004; var quest = 28179; +var questItem = 4032491; var status = -1; function start(mode, type, selection) { @@ -15,7 +16,12 @@ function action(mode, type, selection) { } if (status == 0) { if (cm.isQuestStarted(quest)) { - cm.sendYesNo("Would you like to move to #b#m" + map + "##k?"); + if (cm.haveItem(questItem)) { + cm.sendYesNo("Would you like to move to #b#m" + map + "##k?"); + } else { + cm.sendOk("The entrance is blocked by a force that can only be lifted by those holding an emblem."); + cm.dispose(); + } } else { cm.sendOk("The entrance is blocked by a strange force."); cm.dispose(); diff --git a/scripts/npc/9201129.js b/scripts/npc/9201129.js index 58d4230c2f..44362c3756 100644 --- a/scripts/npc/9201129.js +++ b/scripts/npc/9201129.js @@ -1,5 +1,6 @@ var map = 677000000; var quest = 28198; +var questItem = 4032495; var status = -1; function start(mode, type, selection) { @@ -15,7 +16,12 @@ function action(mode, type, selection) { } if (status == 0) { if (cm.isQuestStarted(quest)) { - cm.sendYesNo("Would you like to move to #b#m" + map + "##k?"); + if (cm.haveItem(questItem)) { + cm.sendYesNo("Would you like to move to #b#m" + map + "##k?"); + } else { + cm.sendOk("The entrance is blocked by a force that can only be lifted by those holding an emblem."); + cm.dispose(); + } } else { cm.sendOk("The entrance is blocked by a strange force."); cm.dispose(); diff --git a/scripts/npc/9201130.js b/scripts/npc/9201130.js index 797499a0c8..795bcde1d5 100644 --- a/scripts/npc/9201130.js +++ b/scripts/npc/9201130.js @@ -1,5 +1,6 @@ var map = 677000008; var quest = 28219; +var questItem = 4032493; var status = -1; function start(mode, type, selection) { @@ -15,7 +16,12 @@ function action(mode, type, selection) { } if (status == 0) { if (cm.isQuestStarted(quest)) { - cm.sendYesNo("Would you like to move to #b#m" + map + "##k?"); + if (cm.haveItem(questItem)) { + cm.sendYesNo("Would you like to move to #b#m" + map + "##k?"); + } else { + cm.sendOk("The entrance is blocked by a force that can only be lifted by those holding an emblem."); + cm.dispose(); + } } else { cm.sendOk("The entrance is blocked by a strange force."); cm.dispose(); diff --git a/scripts/npc/9201131.js b/scripts/npc/9201131.js index 1a1b8efb69..ab545d7784 100644 --- a/scripts/npc/9201131.js +++ b/scripts/npc/9201131.js @@ -1,5 +1,6 @@ var map = 677000002; var quest = 28238; +var questItem = 4032492; var status = -1; function start(mode, type, selection) { @@ -15,7 +16,12 @@ function action(mode, type, selection) { } if (status == 0) { if (cm.isQuestStarted(quest)) { - cm.sendYesNo("Would you like to move to #b#m" + map + "##k?"); + if (cm.haveItem(questItem)) { + cm.sendYesNo("Would you like to move to #b#m" + map + "##k?"); + } else { + cm.sendOk("The entrance is blocked by a force that can only be lifted by those holding an emblem."); + cm.dispose(); + } } else { cm.sendOk("The entrance is blocked by a strange force."); cm.dispose(); diff --git a/scripts/npc/9201132.js b/scripts/npc/9201132.js index 4b69ee3774..a790f832f6 100644 --- a/scripts/npc/9201132.js +++ b/scripts/npc/9201132.js @@ -1,5 +1,6 @@ var map = 677000006; var quest = 28256; +var questItem = 4032494; var status = -1; function start(mode, type, selection) { @@ -15,7 +16,12 @@ function action(mode, type, selection) { } if (status == 0) { if (cm.isQuestStarted(quest)) { - cm.sendYesNo("Would you like to move to #b#m" + map + "##k?"); + if (cm.haveItem(questItem)) { + cm.sendYesNo("Would you like to move to #b#m" + map + "##k?"); + } else { + cm.sendOk("The entrance is blocked by a force that can only be lifted by those holding an emblem."); + cm.dispose(); + } } else { cm.sendOk("The entrance is blocked by a strange force."); cm.dispose(); diff --git a/scripts/npc/9977777.js b/scripts/npc/9977777.js index 9591063262..6e3a3079d6 100644 --- a/scripts/npc/9977777.js +++ b/scripts/npc/9977777.js @@ -87,6 +87,8 @@ function writeFeatureTab_PlayerSocialNetwork() { addFeature("Engagement & Wedding system with ring effects."); addFeature("Equipments displays to everyone it's level & EXP info."); addFeature("Further improved the existent minigame mechanics."); + addFeature("Trade complete using handshake synchronization."); + addFeature("GMS-like trade results displaying after transactions."); } function writeFeatureTab_CashItems() { @@ -123,11 +125,11 @@ function writeFeatureTab_MonstersMapsReactors() { addFeature("Added meso drop data for many missing mobs."); addFeature("Monsterbook displays updated drop data info."); addFeature("Every skill/mastery book is now obtainable."); - addFeature("Enhanced aggro system: real-time DPS aggro detection."); + addFeature("Enhanced aggro system, with real-time DPS detection."); + addFeature("Puppets keep targeted mobs nearby on new aggro."); addFeature("Mobs now can drop more than one of the same equip."); addFeature("Mobs only drop items collectable by the player/party."); addFeature("Mobs shouldn't fall from foothold too often now."); - addFeature("Puppets holds targeted mobs nearby on new aggro feat."); addFeature("Properly applying MP cost on non-skill mob moves."); addFeature("Limited underling mob spawns."); addFeature("Implemented mob banish by touch & skill move."); @@ -173,6 +175,7 @@ function writeFeatureTab_Playerpotentials() { addFeature("Reviewed keybinding system."); addFeature("Character slots per world/server-wide."); addFeature("Optional cash shop inventory separated by classes."); + addFeature("Players manage 'same-typed' invites exclusively."); } function writeFeatureTab_Serverpotentials() { @@ -188,6 +191,7 @@ function writeFeatureTab_Serverpotentials() { addFeature("Players-appointed bestsellers for Owl and Cash Shop."); addFeature("Tweaked pet/mount hunger to a balanced growth rate."); addFeature("Consistent experience and meso gain system."); + addFeature("Fredrick Store Bank warn/delete unclaimed items."); 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."); @@ -198,6 +202,8 @@ function writeFeatureTab_Serverpotentials() { addFeature("M. book announcer displays info based on demand."); addFeature("Custom jail system."); addFeature("Custom buyback system, uses mesos / NX, via MTS."); + addFeature("Custom fishing system having 'seasonal' catch times."); + addFeature("Custom map leasing system."); addFeature("Delete Character."); addFeature("Smooth view-all-char, now showing all account chars."); addFeature("Centralized servertime, boosting handler performance."); diff --git a/sql/db_database.sql b/sql/db_database.sql index 645d8bada3..aaaf8c9f6b 100644 --- a/sql/db_database.sql +++ b/sql/db_database.sql @@ -12866,6 +12866,15 @@ CREATE TABLE IF NOT EXISTS `family_character` ( INDEX (cid, familyid) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; +CREATE TABLE IF NOT EXISTS `fredstorage` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `cid` int(10) unsigned NOT NULL, + `daynotes` int(4) unsigned NOT NULL, + `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY `cid_2` (`cid`), + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ; + CREATE TABLE IF NOT EXISTS `gifts` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `to` int(11) NOT NULL, diff --git a/src/client/MapleCharacter.java b/src/client/MapleCharacter.java index a481d7c72a..ccc2792c6b 100644 --- a/src/client/MapleCharacter.java +++ b/src/client/MapleCharacter.java @@ -54,6 +54,7 @@ import java.util.regex.Pattern; import net.server.PlayerBuffValueHolder; import net.server.PlayerCoolDownValueHolder; import net.server.Server; +import net.server.coordinator.MapleInviteCoordinator; import net.server.guild.MapleAlliance; import net.server.guild.MapleGuild; import net.server.guild.MapleGuildCharacter; @@ -125,6 +126,7 @@ import client.inventory.PetDataFactory; import client.inventory.manipulator.MapleCashidGenerator; import client.inventory.manipulator.MapleInventoryManipulator; import client.newyear.NewYearCardRecord; +import client.processor.FredrickProcessor; import constants.ExpTable; import constants.GameConstants; import constants.ItemConstants; @@ -167,7 +169,7 @@ import net.server.audit.locks.factory.MonitoredReentrantLockFactory; public class MapleCharacter extends AbstractMapleCharacterObject { private static final MapleItemInformationProvider ii = MapleItemInformationProvider.getInstance(); private static final String LEVEL_200 = "[Congrats] %s has reached Level %d! 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", + private static final String[] BLOCKED_NAMES = {"admin", "owner", "moderator", "intern", "donor", "administrator", "FREDRICK", "help", "helper", "alert", "notice", "maplestory", "fuck", "wizet", "fucking", "negro", "fuk", "fuc", "penis", "pussy", "asshole", "gay", "nigger", "homo", "suck", "cum", "shit", "shitty", "condom", "security", "official", "rape", "nigga", "sex", "tit", "boner", "orgy", "clit", "asshole", "fatass", "bitch", "support", "gamemaster", "cock", "gaay", "gm", "operate", "master", "sysop", "party", "GameMaster", "community", "message", "event", "test", "meso", "Scania", "yata", "AsiaSoft", "henesys"}; @@ -208,6 +210,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject { private boolean usedStorage = false; private String name; private String chalktext; + private String commandtext; private String dataString; private String search = null; private AtomicBoolean mapTransitioning = new AtomicBoolean(true); // player client is currently trying to change maps or log in the game map @@ -313,7 +316,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject { private short extraRecInterval; private int targetHpBarHash = 0; private long targetHpBarTime = 0; - private long nextUnderlevelTime = 0; + private long nextWarningTime = 0; private int banishMap = -1; private int banishSp = -1; private long banishTime = 0; @@ -1055,9 +1058,12 @@ public class MapleCharacter extends AbstractMapleCharacterObject { gainSp(spGain, GameConstants.getSkillBook(newJob.getId()), true); } - if (newJob.getId() % 10 > 1) { + // thanks xinyifly for finding out job advancements awarding APs + /* + if (newJob.getId() % 10 >= 1) { gainAp(5, true); } + */ if (!isGM()) { for (byte i = 1; i < 5; i++) { @@ -1578,6 +1584,10 @@ public class MapleCharacter extends AbstractMapleCharacterObject { } } } + + public void removeIncomingInvites() { + MapleInviteCoordinator.removePlayerIncomingInvites(id); + } private void changeMapInternal(final MapleMap to, final Point pos, final byte[] warpPacket) { if(!canWarpMap) return; @@ -1586,6 +1596,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject { this.unregisterChairBuff(); this.clearBanishPlayerData(); + MapleTrade.cancelTrade(this, MapleTrade.TradeResult.UNSUCCESSFUL_ANOTHER_MAP); this.closePlayerInteractions(); client.announce(warpPacket); @@ -1947,6 +1958,16 @@ public class MapleCharacter extends AbstractMapleCharacterObject { public boolean canHold(int itemid, int quantity) { return client.getAbstractPlayerInteraction().canHold(itemid, quantity); } + + public boolean canHoldUniques(List itemids) { + for (Integer itemid : itemids) { + if (ii.isPickupRestricted(itemid) && this.haveItem(itemid)) { + return false; + } + } + + return true; + } public boolean isRidingBattleship() { Integer bv = getBuffedValue(MapleBuffStat.MONSTER_RIDING); @@ -4131,15 +4152,15 @@ public class MapleCharacter extends AbstractMapleCharacterObject { public int getChair() { return chair.get(); } - + public String getChalkboard() { return this.chalktext; } - + public MapleClient getClient() { return client; } - + public final List getCompletedQuests() { synchronized (quests) { List ret = new LinkedList<>(); @@ -4738,6 +4759,32 @@ public class MapleCharacter extends AbstractMapleCharacterObject { public int getMerchantMeso() { return merchantmeso; } + + public int getMerchantNetMeso() { + int elapsedDays = 0; + + try { + Connection con = DatabaseConnection.getConnection(); + + try (PreparedStatement ps = con.prepareStatement("SELECT `timestamp` FROM `fredstorage` WHERE `cid` = ?")) { + ps.setInt(1, id); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + elapsedDays = FredrickProcessor.timestampElapsedDays(rs.getTimestamp(1), System.currentTimeMillis()); + } + } + } + + con.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + + if (elapsedDays > 100) elapsedDays = 100; + + int netMeso = (merchantmeso * (100 - elapsedDays)) / 100; + return netMeso; + } public int getMesosTraded() { return mesosTraded; @@ -4980,7 +5027,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject { } public void closeTrade() { - MapleTrade.cancelTrade(this); + MapleTrade.cancelTrade(this, MapleTrade.TradeResult.PARTNER_CANCEL); } public void closePlayerShop() { @@ -5024,9 +5071,13 @@ public class MapleCharacter extends AbstractMapleCharacterObject { MapleHiredMerchant merchant = this.getHiredMerchant(); if(merchant == null) return; - if(closeMerchant) { - merchant.removeVisitor(this); - this.setHiredMerchant(null); + if (closeMerchant) { + if (merchant.isOwner(this) && merchant.getItems().isEmpty()) { + merchant.forceClose(); + } else { + merchant.removeVisitor(this); + this.setHiredMerchant(null); + } } else { if (merchant.isOwner(this)) { merchant.setOpen(true); @@ -5674,6 +5725,10 @@ public class MapleCharacter extends AbstractMapleCharacterObject { return guildid > 0 && guildRank < 3; } + public boolean attemptCatchFish(int baitLevel) { + return GameConstants.isFishingArea(mapid) && this.getPosition().getY() > 0 && ItemConstants.isFishingChair(chair.get()) && this.getWorldServer().registerFisherPlayer(this, baitLevel); + } + public void leaveMap() { releaseControlledMonsters(); visibleMapObjects.clear(); @@ -5681,6 +5736,10 @@ public class MapleCharacter extends AbstractMapleCharacterObject { if (hpDecreaseTask != null) { hpDecreaseTask.cancel(false); } + + if (map.unclaimOwnership(this)) { + map.dropMessage(5, "This lawn is now free real estate."); + } } private int getChangedJobSp(MapleJob newJob) { @@ -6950,7 +7009,12 @@ public class MapleCharacter extends AbstractMapleCharacterObject { } private void unsitChairInternal() { - if (chair.get() != 0) { + int chairid = chair.get(); + if (chairid != 0) { + if (ItemConstants.isFishingChair(chairid)) { + this.getWorldServer().unregisterFisherPlayer(this); + } + setChair(0); if (unregisterChairBuff()) { getMap().broadcastMessage(this, MaplePacketCreator.cancelForeignChairSkillEffect(this.getId()), false); @@ -8085,10 +8149,14 @@ public class MapleCharacter extends AbstractMapleCharacterObject { } public void sendNote(String to, String msg, byte fame) throws SQLException { + sendNote(to, this.getName(), msg, fame); + } + + public static void sendNote(String to, String from, String msg, byte fame) throws SQLException { Connection con = DatabaseConnection.getConnection(); try (PreparedStatement ps = con.prepareStatement("INSERT INTO notes (`to`, `from`, `message`, `timestamp`, `fame`) VALUES (?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, to); - ps.setString(2, this.getName()); + ps.setString(2, from); ps.setString(3, msg); ps.setLong(4, Server.getInstance().getCurrentTime()); ps.setByte(5, fame); @@ -8262,7 +8330,7 @@ public class MapleCharacter extends AbstractMapleCharacterObject { } public synchronized void withdrawMerchantMesos() { - int merchantMeso = this.getMerchantMeso(); + int merchantMeso = this.getMerchantNetMeso(); if (merchantMeso > 0) { int possible = Integer.MAX_VALUE - merchantMeso; @@ -8275,6 +8343,17 @@ public class MapleCharacter extends AbstractMapleCharacterObject { this.setMerchantMeso(0); } } + } else { + int playerMeso = this.getMeso(); + int nextMeso = playerMeso + merchantMeso; + + if (nextMeso < 0) { + this.gainMeso(-playerMeso, false); + this.setMerchantMeso(merchantMeso + playerMeso); + } else { + this.gainMeso(merchantMeso, false); + this.setMerchantMeso(0); + } } } @@ -8461,6 +8540,8 @@ public class MapleCharacter extends AbstractMapleCharacterObject { } public void changeName(String name) { + FredrickProcessor.removeFredrickReminders(this.getId()); + this.name = name; try { Connection con = DatabaseConnection.getConnection(); @@ -8830,16 +8911,32 @@ public class MapleCharacter extends AbstractMapleCharacterObject { } public void showUnderleveledInfo(MapleMonster mob) { - chrLock.lock(); - try { - long curTime = Server.getInstance().getCurrentTime(); - if(nextUnderlevelTime < curTime) { - nextUnderlevelTime = curTime + (60 * 1000); // show underlevel info again after 1 minute - - showHint("You have gained #rno experience#k from defeating #e#b" + mob.getName() + "#k#n (lv. #b" + mob.getLevel() + "#k)! Take note you must have around the same level as the mob to start earning EXP from it."); + long curTime = Server.getInstance().getCurrentTime(); + if(nextWarningTime < curTime) { + nextWarningTime = curTime + (60 * 1000); // show underlevel info again after 1 minute + + showHint("You have gained #rno experience#k from defeating #e#b" + mob.getName() + "#k#n (lv. #b" + mob.getLevel() + "#k)! Take note you must have around the same level as the mob to start earning EXP from it."); + } + } + + public void showMapOwnershipInfo(MapleCharacter mapOwner) { + long curTime = Server.getInstance().getCurrentTime(); + if(nextWarningTime < curTime) { + nextWarningTime = curTime + (60 * 1000); // show underlevel info again after 1 minute + + String medal = ""; + Item medalItem = mapOwner.getInventory(MapleInventoryType.EQUIPPED).getItem((short) -49); + if (medalItem != null) { + medal = "<" + ii.getName(medalItem.getItemId()) + "> "; } - } finally { - chrLock.unlock(); + + List strLines = new LinkedList<>(); + strLines.add(""); + strLines.add(""); + strLines.add(""); + strLines.add(this.getClient().getChannelServer().getServerMessage().isEmpty() ? 0 : 1, "Get off my lawn!!"); + + this.announce(MaplePacketCreator.getAvatarMega(mapOwner, medal, this.getClient().getChannel(), 5390006, strLines, true)); } } @@ -9844,6 +9941,14 @@ public class MapleCharacter extends AbstractMapleCharacterObject { public void removeJailExpirationTime() { jailExpiration = 0; } + + public String getLastCommandMessage() { + return this.commandtext; + } + + public void setLastCommandMessage(String text) { + this.commandtext = text; + } public int getRewardPoints() { Connection con = null; diff --git a/src/client/MapleClient.java b/src/client/MapleClient.java index 55089dd751..7cd65524ed 100644 --- a/src/client/MapleClient.java +++ b/src/client/MapleClient.java @@ -118,12 +118,18 @@ public class MapleClient { private final Semaphore actionsSemaphore = new Semaphore(7); private final Lock lock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT, true); private final Lock encoderLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT_ENCODER, true); - private static final Lock loginLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT_LOGIN, true); + private static final Lock loginLocks[] = new Lock[200]; // thanks Masterrulax & try2hack for pointing out a bottleneck issue here private int votePoints; private int voteTime = -1; private int visibleWorlds; private long lastNpcClick; private long sessionId; + + static { + for (int i = 0; i < 200; i++) { + loginLocks[i] = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CLIENT_LOGIN, true); + } + } public MapleClient(MapleAESOFB send, MapleAESOFB receive, IoSession session) { this.send = send; @@ -434,6 +440,7 @@ public class MapleClient { } public int finishLogin() { + Lock loginLock = loginLocks[this.getAccID() % 200]; loginLock.lock(); try { if (getLoginState() > LOGIN_NOTLOGGEDIN) { // 0 = LOGIN_NOTLOGGEDIN, 1= LOGIN_SERVER_TRANSITION, 2 = LOGIN_LOGGEDIN @@ -535,8 +542,15 @@ public class MapleClient { ps.setString(1, login); rs = ps.executeQuery(); if (rs.next()) { - boolean banned = (rs.getByte("banned") == 1); accId = rs.getInt("id"); + if (accId == 0) { + // odd case where accId is actually attributed as 0 (further on this leads to getLoginState ACCID = 0, an absurd), thanks Thora for finding this issue + return 15; + } else if (accId < 0) { + FilePrinter.printError(FilePrinter.LOGIN_EXCEPTION, "Tried to login with accid " + accId); + } + + boolean banned = (rs.getByte("banned") == 1); gmlevel = 0; pin = rs.getString("pin"); pic = rs.getString("pic"); @@ -861,6 +875,7 @@ public class MapleClient { try { player.setDisconnectedFromChannelWorld(); player.notifyMapTransferToPartner(-1); + player.removeIncomingInvites(); player.cancelAllBuffs(true); player.closePlayerInteractions(); @@ -1000,8 +1015,8 @@ public class MapleClient { MapleSessionCoordinator.getInstance().closeSession(session, false); session.removeAttribute(MapleClient.CLIENT_KEY); } - - engines.clear(); + + engines = null; // thanks Tochi for pointing out a NPE here } } @@ -1413,7 +1428,7 @@ public class MapleClient { } if (player.getTrade() != null) { - MapleTrade.cancelTrade(getPlayer()); + MapleTrade.cancelTrade(getPlayer(), MapleTrade.TradeResult.PARTNER_CANCEL); } MapleHiredMerchant merchant = player.getHiredMerchant(); @@ -1429,6 +1444,7 @@ public class MapleClient { server.getPlayerBuffStorage().addDiseasesToStorage(player.getId(), player.getAllDiseases()); player.setDisconnectedFromChannelWorld(); player.notifyMapTransferToPartner(-1); + player.removeIncomingInvites(); player.cancelAllBuffs(true); player.cancelAllDebuffs(); player.cancelBuffExpireTask(); diff --git a/src/client/command/CommandsExecutor.java b/src/client/command/CommandsExecutor.java index 012a784ef0..3a899c2231 100644 --- a/src/client/command/CommandsExecutor.java +++ b/src/client/command/CommandsExecutor.java @@ -93,8 +93,16 @@ public class CommandsExecutor { } private void handleInternal(MapleClient client, String message){ - final String[] spitedMessage = message.toLowerCase().substring(1).split("[ ]"); - final String commandName = spitedMessage[0]; + final String splitRegex = "[ ]"; + String[] splitedMessage = message.substring(1).split(splitRegex, 2); + if (splitedMessage.length < 2) { + splitedMessage = new String[]{splitedMessage[0], ""}; + } + + client.getPlayer().setLastCommandMessage(splitedMessage[1]); // thanks Tochi & Nulliphite for noticing string messages being marshalled lowercase + final String commandName = splitedMessage[0].toLowerCase(); + final String[] lowercaseParams = splitedMessage[1].toLowerCase().split(splitRegex); + final RegisteredCommand command = registeredCommands.get(commandName); if (command == null){ client.getPlayer().yellowMessage("Command '" + commandName + "' is not available. See @commands for a list of available commands."); @@ -105,8 +113,8 @@ public class CommandsExecutor { return; } String[] params; - if (spitedMessage.length > 1) { - params = Arrays.copyOfRange(spitedMessage, 1, spitedMessage.length); + if (lowercaseParams.length > 0) { + params = Arrays.copyOfRange(lowercaseParams, 0, lowercaseParams.length); } else { params = new String[]{}; } @@ -194,6 +202,7 @@ public class CommandsExecutor { addCommand("luk", StatLukCommand.class); addCommand("enableauth", EnableAuthCommand.class); addCommand("toggleexp", ToggleExpCommand.class); + addCommand("mylawn", MapOwnerClaimCommand.class); commandsNameDesc.add(levelCommandsCursor); } @@ -331,6 +340,7 @@ public class CommandsExecutor { addCommand("droprate", 4, DropRateCommand.class); addCommand("questrate", 4, QuestRateCommand.class); addCommand("travelrate", 4, TravelRateCommand.class); + addCommand("fishrate", 4, FishingRateCommand.class); addCommand("itemvac", 4, ItemVacCommand.class); addCommand("forcevac", 4, ForceVacCommand.class); addCommand("zakum", 4, ZakumCommand.class); @@ -356,6 +366,8 @@ public class CommandsExecutor { addCommand("set", 5, SetCommand.class); addCommand("showpackets", 5, ShowPacketsCommand.class); addCommand("showmovelife", 5, ShowMoveLifeCommand.class); + addCommand("showsessions", 5, ShowSessionsCommand.class); + addCommand("iplist", 5, IpListCommand.class); commandsNameDesc.add(levelCommandsCursor); } diff --git a/src/client/command/commands/gm0/GachaCommand.java b/src/client/command/commands/gm0/GachaCommand.java index 44762e9a43..a5f604c14f 100644 --- a/src/client/command/commands/gm0/GachaCommand.java +++ b/src/client/command/commands/gm0/GachaCommand.java @@ -27,7 +27,6 @@ import client.command.Command; import client.MapleClient; import server.MapleItemInformationProvider; import server.gachapon.MapleGachapon; -import tools.MaplePacketCreator; public class GachaCommand extends Command { { @@ -37,7 +36,7 @@ public class GachaCommand extends Command { @Override public void execute(MapleClient c, String[] params) { MapleGachapon.Gachapon gacha = null; - String search = joinStringFrom(params,0); + String search = c.getPlayer().getLastCommandMessage(); String gachaName = ""; String [] names = {"Henesys", "Ellinia", "Perion", "Kerning City", "Sleepywood", "Mushroom Shrine", "Showa Spa Male", "Showa Spa Female", "New Leaf City", "Nautilus Harbor"}; int [] ids = {9100100, 9100101, 9100102, 9100103, 9100104, 9100105, 9100106, 9100107, 9100109, 9100117}; diff --git a/src/client/command/commands/gm0/GmCommand.java b/src/client/command/commands/gm0/GmCommand.java index c625022789..b9f2a553f9 100644 --- a/src/client/command/commands/gm0/GmCommand.java +++ b/src/client/command/commands/gm0/GmCommand.java @@ -50,7 +50,7 @@ public class GmCommand extends Command { player.dropMessage(5, "Your message was too short. Please provide as much detail as possible."); return; } - String message = joinStringFrom(params, 0); + String message = player.getLastCommandMessage(); Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.sendYellowTip("[GM MESSAGE]:" + MapleCharacter.makeMapleReadable(player.getName()) + ": " + message)); Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.serverNotice(1, message)); FilePrinter.printError(FilePrinter.COMMAND_GM, MapleCharacter.makeMapleReadable(player.getName()) + ": " + message); diff --git a/src/client/command/commands/gm0/MapOwnerClaimCommand.java b/src/client/command/commands/gm0/MapOwnerClaimCommand.java new file mode 100644 index 0000000000..eac30717e6 --- /dev/null +++ b/src/client/command/commands/gm0/MapOwnerClaimCommand.java @@ -0,0 +1,62 @@ +/* + This file is part of the HeavenMS MapleStory Server, commands OdinMS-based + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +/* + @Author: Ronan +*/ +package client.command.commands.gm0; + +import client.command.Command; +import client.MapleCharacter; +import client.MapleClient; +import constants.ServerConstants; + +public class MapOwnerClaimCommand extends Command { + { + setDescription(""); + } + + @Override + public void execute(MapleClient c, String[] params) { + if (c.tryacquireClient()) { + try { + MapleCharacter chr = c.getPlayer(); + + if (ServerConstants.USE_MAP_OWNERSHIP_SYSTEM) { + if (chr.getEventInstance() == null) { + if (chr.getMap().unclaimOwnership(chr)) { + chr.dropMessage(5, "This lawn is now free real estate."); + } else if (chr.getMap().claimOwnership(chr)) { + chr.dropMessage(5, "You have leased this lawn for a while, until you leave here or after 1 minute of inactivity."); + } else { + chr.dropMessage(5, "This lawn has already been leased by another player."); + } + } else { + chr.dropMessage(5, "This lawn cannot be leased."); + } + } else { + chr.dropMessage(5, "Feature unavailable."); + } + } finally { + c.releaseClient(); + } + } + } +} diff --git a/src/client/command/commands/gm0/ReportBugCommand.java b/src/client/command/commands/gm0/ReportBugCommand.java index 06dfff78b6..27bdbd102f 100644 --- a/src/client/command/commands/gm0/ReportBugCommand.java +++ b/src/client/command/commands/gm0/ReportBugCommand.java @@ -43,7 +43,7 @@ public class ReportBugCommand extends Command { player.dropMessage(5, "Message too short and not sent. Please do @bug "); return; } - String message = joinStringFrom(params, 0); + String message = player.getLastCommandMessage(); Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.sendYellowTip("[BUG]:" + MapleCharacter.makeMapleReadable(player.getName()) + ": " + message)); Server.getInstance().broadcastGMMessage(c.getWorld(), MaplePacketCreator.serverNotice(1, message)); FilePrinter.printError(FilePrinter.COMMAND_BUG, MapleCharacter.makeMapleReadable(player.getName()) + ": " + message); diff --git a/src/client/command/commands/gm1/WhatDropsFromCommand.java b/src/client/command/commands/gm1/WhatDropsFromCommand.java index cd1eecee99..e13c9c6339 100644 --- a/src/client/command/commands/gm1/WhatDropsFromCommand.java +++ b/src/client/command/commands/gm1/WhatDropsFromCommand.java @@ -46,7 +46,7 @@ public class WhatDropsFromCommand extends Command { player.dropMessage(5, "Please do @whatdropsfrom "); return; } - String monsterName = joinStringFrom(params, 0); + String monsterName = player.getLastCommandMessage(); String output = ""; int limit = 3; Iterator> listIterator = MapleMonsterInformationProvider.getMobsIDsFromName(monsterName).iterator(); diff --git a/src/client/command/commands/gm1/WhoDropsCommand.java b/src/client/command/commands/gm1/WhoDropsCommand.java index 8da3801911..955c1a1b83 100644 --- a/src/client/command/commands/gm1/WhoDropsCommand.java +++ b/src/client/command/commands/gm1/WhoDropsCommand.java @@ -51,7 +51,7 @@ public class WhoDropsCommand extends Command { if (c.tryacquireClient()) { try { - String searchString = joinStringFrom(params, 0); + String searchString = player.getLastCommandMessage(); String output = ""; Iterator> listIterator = MapleItemInformationProvider.getInstance().getItemDataByName(searchString).iterator(); if(listIterator.hasNext()) { diff --git a/src/client/command/commands/gm3/GiveNxCommand.java b/src/client/command/commands/gm3/GiveNxCommand.java index f3d4dd12c0..cae5b4bf0e 100644 --- a/src/client/command/commands/gm3/GiveNxCommand.java +++ b/src/client/command/commands/gm3/GiveNxCommand.java @@ -36,15 +36,37 @@ public class GiveNxCommand extends Command { public void execute(MapleClient c, String[] params) { MapleCharacter player = c.getPlayer(); if (params.length < 1) { - player.yellowMessage("Syntax: !givenx [] "); + player.yellowMessage("Syntax: !givenx [nx, mp, np] [] "); return; } - String recv; - int value; + String recv, typeStr = "nx"; + int value, type = 1; if (params.length > 1) { - recv = params[0]; - value = Integer.parseInt(params[1]); + if (params[0].length() == 2) { + switch (params[0]) { + case "mp": // maplePoint + type = 2; + break; + case "np": // nxPrepaid + type = 4; + break; + default: + type = 1; + } + typeStr = params[0]; + + if (params.length > 2) { + recv = params[1]; + value = Integer.parseInt(params[2]); + } else { + recv = c.getPlayer().getName(); + value = Integer.parseInt(params[1]); + } + } else { + recv = params[0]; + value = Integer.parseInt(params[1]); + } } else { recv = c.getPlayer().getName(); value = Integer.parseInt(params[0]); @@ -52,8 +74,8 @@ public class GiveNxCommand extends Command { MapleCharacter victim = c.getWorldServer().getPlayerStorage().getCharacterByName(recv); if (victim != null) { - victim.getCashShop().gainCash(1, value); - player.message("NX given."); + victim.getCashShop().gainCash(type, value); + player.message(typeStr.toUpperCase() + " given."); } else { player.message("Player '" + recv + "' could not be found."); } diff --git a/src/client/command/commands/gm3/MusicCommand.java b/src/client/command/commands/gm3/MusicCommand.java index 9f383d781f..988a3574e9 100644 --- a/src/client/command/commands/gm3/MusicCommand.java +++ b/src/client/command/commands/gm3/MusicCommand.java @@ -57,7 +57,7 @@ public class MusicCommand extends Command { return; } - String song = joinStringFrom(params, 0); + String song = player.getLastCommandMessage(); for (String s : GameConstants.GAME_SONGS) { if (s.equalsIgnoreCase(song)) { // thanks Masterrulax for finding an issue here player.getMap().broadcastMessage(MaplePacketCreator.musicChange(s)); diff --git a/src/client/command/commands/gm3/NoticeCommand.java b/src/client/command/commands/gm3/NoticeCommand.java index 9ee0011f2f..8726b33cb5 100644 --- a/src/client/command/commands/gm3/NoticeCommand.java +++ b/src/client/command/commands/gm3/NoticeCommand.java @@ -37,6 +37,6 @@ public class NoticeCommand extends Command { @Override public void execute(MapleClient c, String[] params) { MapleCharacter player = c.getPlayer(); - Server.getInstance().broadcastMessage(c.getWorld(), MaplePacketCreator.serverNotice(6, "[Notice] " + joinStringFrom(params, 0))); + Server.getInstance().broadcastMessage(c.getWorld(), MaplePacketCreator.serverNotice(6, "[Notice] " + player.getLastCommandMessage())); } } diff --git a/src/client/command/commands/gm4/FishingRateCommand.java b/src/client/command/commands/gm4/FishingRateCommand.java new file mode 100644 index 0000000000..9fb325113c --- /dev/null +++ b/src/client/command/commands/gm4/FishingRateCommand.java @@ -0,0 +1,48 @@ +/* + This file is part of the HeavenMS MapleStory Server, commands OdinMS-based + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +/* + @Author: Ronan +*/ +package client.command.commands.gm4; + +import client.command.Command; +import client.MapleClient; +import client.MapleCharacter; +import tools.MaplePacketCreator; + +public class FishingRateCommand extends Command { + { + setDescription(""); + } + + @Override + public void execute(MapleClient c, String[] params) { + MapleCharacter player = c.getPlayer(); + if (params.length < 1) { + player.yellowMessage("Syntax: !fishrate "); + return; + } + + int fishrate = Math.max(Integer.parseInt(params[0]), 1); + c.getWorldServer().setFishingRate(fishrate); + c.getWorldServer().broadcastPacket(MaplePacketCreator.serverNotice(6, "[Rate] Fishing Rate has been changed to " + fishrate + "x.")); + } +} diff --git a/src/client/command/commands/gm4/ServerMessageCommand.java b/src/client/command/commands/gm4/ServerMessageCommand.java index 428133eded..ee2a657055 100644 --- a/src/client/command/commands/gm4/ServerMessageCommand.java +++ b/src/client/command/commands/gm4/ServerMessageCommand.java @@ -34,7 +34,7 @@ public class ServerMessageCommand extends Command { @Override public void execute(MapleClient c, String[] params) { - //MapleCharacter player = c.getPlayer(); - c.getWorldServer().setServerMessage(joinStringFrom(params, 0)); + MapleCharacter player = c.getPlayer(); + c.getWorldServer().setServerMessage(player.getLastCommandMessage()); } } diff --git a/src/client/command/commands/gm5/DebugCommand.java b/src/client/command/commands/gm5/DebugCommand.java index 7346f522cb..517ca62525 100644 --- a/src/client/command/commands/gm5/DebugCommand.java +++ b/src/client/command/commands/gm5/DebugCommand.java @@ -77,7 +77,7 @@ public class DebugCommand extends Command { break; case "packet": - player.getMap().broadcastMessage(MaplePacketCreator.customPacket(joinStringFrom(params, 1))); + //player.getMap().broadcastMessage(MaplePacketCreator.customPacket(joinStringFrom(params, 1))); break; case "portal": diff --git a/src/client/command/commands/gm5/IpListCommand.java b/src/client/command/commands/gm5/IpListCommand.java new file mode 100644 index 0000000000..8442aac894 --- /dev/null +++ b/src/client/command/commands/gm5/IpListCommand.java @@ -0,0 +1,60 @@ +/* + 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.command.commands.gm5; + +import java.util.Collection; +import client.MapleClient; +import client.MapleCharacter; +import client.command.Command; +import constants.GameConstants; +import net.server.Server; +import net.server.world.World; + +/** + * + * @author Mist + * @author Blood (Tochi) + * @author Ronan + */ +public class IpListCommand extends Command { + { + setDescription(""); + } + + @Override + public void execute(MapleClient c, String[] params) { + String str = "Player-IP relation:"; + + for (World w : Server.getInstance().getWorlds()) { + Collection chars = w.getPlayerStorage().getAllCharacters(); + + if (!chars.isEmpty()) { + str += "\r\n" + GameConstants.WORLD_NAMES[w.getId()] + "\r\n"; + + for (MapleCharacter chr : chars) { + str += " " + chr.getName() + " - " + chr.getClient().getSession().getRemoteAddress() + "\r\n"; + } + } + } + + c.getAbstractPlayerInteraction().npcTalk(22000, str); + } + +} \ No newline at end of file diff --git a/src/client/command/commands/gm5/ShowSessionsCommand.java b/src/client/command/commands/gm5/ShowSessionsCommand.java new file mode 100644 index 0000000000..f48d0cfd84 --- /dev/null +++ b/src/client/command/commands/gm5/ShowSessionsCommand.java @@ -0,0 +1,39 @@ +/* + 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.command.commands.gm5; + +import client.MapleClient; +import client.command.Command; +import net.server.coordinator.MapleSessionCoordinator; + +/** + * + * @author Ronan + */ +public class ShowSessionsCommand extends Command { + { + setDescription(""); + } + + @Override + public void execute(MapleClient c, String[] params) { + MapleSessionCoordinator.getInstance().printSessionTrace(c); + } +} diff --git a/src/client/inventory/ItemFactory.java b/src/client/inventory/ItemFactory.java index db943ba385..49fa62af2e 100644 --- a/src/client/inventory/ItemFactory.java +++ b/src/client/inventory/ItemFactory.java @@ -28,10 +28,10 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.List; import java.util.concurrent.locks.Lock; -import tools.DatabaseConnection; -import tools.Pair; import net.server.audit.locks.MonitoredLockType; import net.server.audit.locks.factory.MonitoredReentrantLockFactory; +import tools.DatabaseConnection; +import tools.Pair; /** * @@ -48,8 +48,14 @@ public enum ItemFactory { CASH_OVERALL(7, true); private final int value; private final boolean account; - private static final Lock lock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.ITEM, true); - + private static final Lock locks[] = new Lock[200]; // thanks Masterrulax for pointing out a bottleneck issue here + + static { + for (int i = 0; i < 200; i++) { + locks[i] = MonitoredReentrantLockFactory.createLock(MonitoredLockType.ITEM, true); + } + } + private ItemFactory(int value, boolean account) { this.value = value; this.account = account; @@ -192,6 +198,7 @@ public enum ItemFactory { PreparedStatement pse = null; ResultSet rs = null; + Lock lock = locks[id % 200]; lock.lock(); try { StringBuilder query = new StringBuilder(); @@ -357,6 +364,7 @@ public enum ItemFactory { PreparedStatement pse = null; ResultSet rs = null; + Lock lock = locks[id % 200]; lock.lock(); try { ps = con.prepareStatement("DELETE FROM `inventorymerchant` WHERE `characterid` = ?"); diff --git a/src/client/inventory/MapleInventory.java b/src/client/inventory/MapleInventory.java index 3ebb08e710..104dfc9b13 100644 --- a/src/client/inventory/MapleInventory.java +++ b/src/client/inventory/MapleInventory.java @@ -435,8 +435,8 @@ public class MapleInventory implements Iterable { } } - public static boolean checkSpot(MapleCharacter chr, Item item) { - return !chr.getInventory(item.getInventoryType()).isFull(); + public static boolean checkSpot(MapleCharacter chr, Item item) { // thanks Vcoc for noticing pshops not checking item stacks when taking item back + return checkSpotsAndOwnership(chr, Collections.singletonList(new Pair<>(item, item.getInventoryType()))); } public static boolean checkSpots(MapleCharacter chr, List> items) { diff --git a/src/client/processor/FredrickProcessor.java b/src/client/processor/FredrickProcessor.java index 49311315a0..429000e075 100644 --- a/src/client/processor/FredrickProcessor.java +++ b/src/client/processor/FredrickProcessor.java @@ -31,9 +31,16 @@ import client.inventory.MapleInventory; import client.inventory.MapleInventoryType; import java.sql.Connection; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.LinkedList; import java.util.List; import client.inventory.manipulator.MapleInventoryManipulator; +import constants.ServerConstants; +import java.util.Collections; +import net.server.Server; +import net.server.world.World; import server.MapleItemInformationProvider; import server.maps.MapleHiredMerchant; import tools.DatabaseConnection; @@ -43,14 +50,220 @@ import tools.Pair; /** * - * @author RonanLana - synchronization of Fredrick modules + * @author RonanLana - synchronization of Fredrick modules & operation results */ public class FredrickProcessor { - private static boolean canRetrieveFromFredrick(MapleCharacter chr, List> items) { - if (!chr.canHoldMeso(chr.getMerchantMeso())) { - return false; + + private static int[] dailyReminders = new int[]{2, 5, 10, 15, 30, 60, 90, Integer.MAX_VALUE}; + + private static byte canRetrieveFromFredrick(MapleCharacter chr, List> items) { + if (!MapleInventory.checkSpotsAndOwnership(chr, items)) { + List itemids = new LinkedList<>(); + for (Pair it : items) { + itemids.add(it.getLeft().getItemId()); + } + + if (chr.canHoldUniques(itemids)) { + return 0x22; + } else { + return 0x20; + } + } + + int netMeso = chr.getMerchantNetMeso(); + if (netMeso > 0) { + if (!chr.canHoldMeso(netMeso)) { + return 0x1F; + } + } else { + if (chr.getMeso() < -1 * netMeso) { + return 0x21; + } + } + + return 0x0; + } + + public static int timestampElapsedDays(Timestamp then, long timeNow) { + return (int) ((timeNow - then.getTime()) / (1000 * 60 * 60 * 24)); + } + + private static String fredrickReminderMessage(int daynotes) { + String msg; + + if (daynotes < 4) { + msg = "Hi customer! I am Fredrick, the Union Chief of the Hired Merchant Union. A reminder that " + dailyReminders[daynotes] + " days have passed since you used our service. Please reclaim your stored goods at FM Entrance."; + } else { + msg = "Hi customer! I am Fredrick, the Union Chief of the Hired Merchant Union. " + dailyReminders[daynotes] + " days have passed since you used our service. Consider claiming back the items before we move them away for refund."; + } + + return msg; + } + + private static void removeFredrickLog(int cid) { + try { + Connection con = DatabaseConnection.getConnection(); + removeFredrickLog(con, cid); + con.close(); + } catch (SQLException sqle) { + sqle.printStackTrace(); + } + } + + private static void removeFredrickLog(Connection con, int cid) throws SQLException { + try (PreparedStatement ps = con.prepareStatement("DELETE FROM `fredstorage` WHERE `cid` = ?")) { + ps.setInt(1, cid); + ps.execute(); + } + } + + public static void insertFredrickLog(int cid) { + try { + Connection con = DatabaseConnection.getConnection(); + + removeFredrickLog(con, cid); + try (PreparedStatement ps = con.prepareStatement("INSERT INTO `fredstorage` (`cid`, `daynotes`, `timestamp`) VALUES (?, 0, ?)")) { + ps.setInt(1, cid); + ps.setTimestamp(2, new Timestamp(System.currentTimeMillis())); + ps.execute(); + } + + con.close(); + } catch (SQLException sqle) { + sqle.printStackTrace(); + } + } + + public static void removeFredrickReminders(int cid) { + removeFredrickReminders(Collections.singletonList(new Pair<>(cid, 0))); + } + + private static void removeFredrickReminders(List> expiredCids) { + List expiredCnames = new LinkedList<>(); + for (Pair id : expiredCids) { + String name = MapleCharacter.getNameById(id.getLeft()); + if (name != null) { + expiredCnames.add(name); + } + } + + try { + Connection con = DatabaseConnection.getConnection(); + try (PreparedStatement ps = con.prepareStatement("DELETE FROM `notes` WHERE `from` LIKE ? AND `to` LIKE ?")) { + ps.setString(1, "FREDRICK"); + + for (String cname : expiredCnames) { + ps.setString(2, cname); + ps.executeBatch(); + } + } + con.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public static void runFredrickSchedule() { + try { + Connection con = DatabaseConnection.getConnection(); + + List> expiredCids = new LinkedList<>(); + List, Integer>> notifCids = new LinkedList<>(); + try (PreparedStatement ps = con.prepareStatement("SELECT * FROM fredstorage f LEFT JOIN (SELECT id, name, world, lastLogoutTime FROM characters) AS c ON c.id = f.cid")) { + try (ResultSet rs = ps.executeQuery()) { + long curTime = System.currentTimeMillis(); + + while (rs.next()) { + int cid = rs.getInt("cid"); + int world = rs.getInt("world"); + Timestamp ts = rs.getTimestamp("timestamp"); + int daynotes = Math.min(dailyReminders.length - 1, rs.getInt("daynotes")); + + int elapsedDays = timestampElapsedDays(ts, curTime); + if (elapsedDays > 100) { + expiredCids.add(new Pair<>(cid, world)); + } else { + int notifDay = dailyReminders[daynotes]; + + if (elapsedDays >= notifDay) { + do { + daynotes++; + notifDay = dailyReminders[daynotes]; + } while (elapsedDays >= notifDay); + + Timestamp logoutTs = rs.getTimestamp("lastLogoutTime"); + int inactivityDays = timestampElapsedDays(logoutTs, curTime); + + if (inactivityDays < 7 || daynotes >= dailyReminders.length - 1) { // don't spam inactive players + String name = rs.getString("name"); + notifCids.add(new Pair<>(new Pair<>(cid, name), daynotes)); + } + } + } + } + } + } + + if (!expiredCids.isEmpty()) { + try (PreparedStatement ps = con.prepareStatement("DELETE FROM `inventoryitems` WHERE `type` = ? AND `characterid` = ?")) { + ps.setInt(1, ItemFactory.MERCHANT.getValue()); + + for (Pair cid : expiredCids) { + ps.setInt(2, cid.getLeft()); + ps.addBatch(); + } + + ps.executeBatch(); + } + + try (PreparedStatement ps = con.prepareStatement("UPDATE `characters` SET `MerchantMesos` = 0 WHERE `id` = ?")) { + for (Pair cid : expiredCids) { + ps.setInt(1, cid.getLeft()); + ps.addBatch(); + + World wserv = Server.getInstance().getWorld(cid.getRight()); + if (wserv != null) { + MapleCharacter chr = wserv.getPlayerStorage().getCharacterById(cid.getLeft()); + if (chr != null) { + chr.setMerchantMeso(0); + } + } + } + + ps.executeBatch(); + } + + removeFredrickReminders(expiredCids); + + try (PreparedStatement ps = con.prepareStatement("DELETE FROM `fredstorage` WHERE `cid` = ?")) { + for (Pair cid : expiredCids) { + ps.setInt(1, cid.getLeft()); + ps.addBatch(); + } + + ps.executeBatch(); + } + } + + if (!notifCids.isEmpty()) { + try (PreparedStatement ps = con.prepareStatement("UPDATE `fredstorage` SET `daynotes` = ? WHERE `cid` = ?")) { + for (Pair, Integer> cid : notifCids) { + ps.setInt(1, cid.getRight()); + ps.setInt(2, cid.getLeft().getLeft()); + ps.addBatch(); + + String msg = fredrickReminderMessage(cid.getRight() - 1); + MapleCharacter.sendNote(cid.getLeft().getRight(), "FREDRICK", msg, (byte) 0); + } + + ps.executeBatch(); + } + } + + con.close(); + } catch (SQLException e) { + e.printStackTrace(); } - return MapleInventory.checkSpotsAndOwnership(chr, items); } private static boolean deleteFredrickItems(int cid) { @@ -77,13 +290,15 @@ public class FredrickProcessor { List> items; try { items = ItemFactory.MERCHANT.loadItems(chr.getId(), false); - if (!canRetrieveFromFredrick(chr, items)) { - chr.announce(MaplePacketCreator.fredrickMessage((byte) 0x21)); + + byte response = canRetrieveFromFredrick(chr, items); + if (response != 0) { + chr.announce(MaplePacketCreator.fredrickMessage(response)); return; } - + chr.withdrawMerchantMesos(); - + if (deleteFredrickItems(chr.getId())) { MapleHiredMerchant merchant = chr.getHiredMerchant(); @@ -98,6 +313,7 @@ public class FredrickProcessor { } chr.announce(MaplePacketCreator.fredrickMessage((byte) 0x1E)); + removeFredrickLog(chr.getId()); } else { chr.message("An unknown error has occured."); } diff --git a/src/constants/GameConstants.java b/src/constants/GameConstants.java index d8cfc98e9f..b77cfe6423 100644 --- a/src/constants/GameConstants.java +++ b/src/constants/GameConstants.java @@ -592,6 +592,10 @@ public class GameConstants { return isDojo(mapid) || isPyramid(mapid); } + public static boolean isFishingArea(int mapid) { + return mapid == 120010000 || mapid == 251000100 || mapid == 541010110; + } + public static boolean isFinisherSkill(int skillId) { return skillId > 1111002 && skillId < 1111007 || skillId == 11111002 || skillId == 11111003; } diff --git a/src/constants/ItemConstants.java b/src/constants/ItemConstants.java index d1624daf66..fbbbfc900b 100644 --- a/src/constants/ItemConstants.java +++ b/src/constants/ItemConstants.java @@ -224,6 +224,10 @@ public final class ItemConstants { return itemId < 2000000 && itemId != 0; } + public static boolean isFishingChair(int itemId) { + return itemId == 3011000; + } + public static boolean isMedal(int itemId) { return itemId >= 1140000 && itemId < 1143000; } diff --git a/src/constants/ServerConstants.java b/src/constants/ServerConstants.java index 554dc7f76b..cd07a535c8 100644 --- a/src/constants/ServerConstants.java +++ b/src/constants/ServerConstants.java @@ -20,6 +20,7 @@ public class ServerConstants { public static final int WLDLIST_SIZE = 21; //Max possible worlds on the server. public static final int CHANNEL_SIZE = 20; //Max possible channels per world (which is 20, based on the channel list on login phase). public static final int CHANNEL_LOAD = 100; //Max players per channel (limit actually used to calculate the World server capacity). + public static final int CHANNEL_LOCKS = 20; //Total number of structure management locks each channel has. public static final long RESPAWN_INTERVAL = 10 * 1000; //10 seconds, 10000. public static final long PURGING_INTERVAL = 5 * 60 * 1000; @@ -92,6 +93,7 @@ public class ServerConstants { public static final boolean USE_ENFORCE_ITEM_SUGGESTION = false;//Forces the Owl of Minerva and the Cash Shop to always display the defined item array instead of those featured by the players. public static final boolean USE_ENFORCE_UNMERCHABLE_CASH = true;//Forces players to not sell CASH items via merchants. public static final boolean USE_ENFORCE_UNMERCHABLE_PET = true; //Forces players to not sell pets via merchants. (since non-named pets gets dirty name and other possible DB-related issues) + public static final boolean USE_ENFORCE_MERCHANT_SAVE = true; //Forces automatic DB save on merchant owners, at every item movement on shop. public static final boolean USE_ENFORCE_MDOOR_POSITION = false; //Forces mystic door to be spawned near spawnpoints. public static final boolean USE_SPAWN_LOOT_ON_ANIMATION = false;//Makes loot appear some time after the mob has been killed (following the mob death animation, instead of instantly). public static final boolean USE_SPAWN_RELEVANT_LOOT = true; //Forces to only spawn loots that are collectable by the player or any of their party members. @@ -103,8 +105,9 @@ public class ServerConstants { public static final boolean USE_MULTIPLE_SAME_EQUIP_DROP = true;//Enables multiple drops by mobs of the same equipment, number of possible drops based on the quantities provided at the drop data. public static final boolean USE_BANISHABLE_TOWN_SCROLL = true; //Enables town scrolls to act as if it's a "player banish", rendering the antibanish scroll effect available. public static final boolean USE_ENABLE_FULL_RESPAWN = true; //At respawn task, always respawn missing mobs when they're available. Spawn count doesn't depend on how many players are currently there. - public static final boolean USE_ENABLE_CHAT_LOG = true; //Write in-game chat to log - public static final boolean USE_REBIRTH_SYSTEM = false; //Flag to enable/disable rebirth system + public static final boolean USE_ENABLE_CHAT_LOG = false; //Write in-game chat to log + public static final boolean USE_REBIRTH_SYSTEM = false; //Flag to enable/disable rebirth system + public static final boolean USE_MAP_OWNERSHIP_SYSTEM = true; //Flag to enable/disable map ownership system //Events/PQs Configuration public static final boolean USE_OLD_GMS_STYLED_PQ_NPCS = true; //Enables PQ NPCs with similar behaviour to old GMS style, that skips info about the PQs and immediately tries to register the party in. @@ -137,10 +140,11 @@ public class ServerConstants { public static final int DROP_RATE = 10; public static final int BOSS_DROP_RATE = 10; //NOTE: Boss drop rate OVERRIDES common drop rate, for bosses-only. 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 int FISHING_RATE = 10; //Multiplier for success likelihood on meso thrown during fishing. public static final int TRAVEL_RATE = 10; //Means of transportation rides/departs using 1/N of the default time. 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 float PARTY_BONUS_EXP_RATE = 1.0f; //Rate for the party exp bonus 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. diff --git a/src/net/PacketProcessor.java b/src/net/PacketProcessor.java index ae8ec01675..2b8d264305 100644 --- a/src/net/PacketProcessor.java +++ b/src/net/PacketProcessor.java @@ -231,6 +231,7 @@ public final class PacketProcessor { registerHandler(RecvOpcode.ADMIN_COMMAND, new AdminCommandHandler()); registerHandler(RecvOpcode.ADMIN_LOG, new AdminLogHandler()); registerHandler(RecvOpcode.ALLIANCE_OPERATION, new AllianceOperationHandler()); + registerHandler(RecvOpcode.DENY_ALLIANCE_REQUEST, new DenyAllianceRequestHandler()); registerHandler(RecvOpcode.USE_SOLOMON_ITEM, new UseSolomonHandler()); registerHandler(RecvOpcode.USE_GACHA_EXP, new UseGachaExpHandler()); registerHandler(RecvOpcode.NEW_YEAR_CARD_REQUEST, new NewYearCardHandler()); diff --git a/src/net/opcodes/RecvOpcode.java b/src/net/opcodes/RecvOpcode.java index c6b5ba1517..15a2278227 100644 --- a/src/net/opcodes/RecvOpcode.java +++ b/src/net/opcodes/RecvOpcode.java @@ -144,6 +144,7 @@ public enum RecvOpcode { WEDDING_TALK(0x8B), WEDDING_TALK_MORE(0x8B), ALLIANCE_OPERATION(0x8F), + DENY_ALLIANCE_REQUEST(0x90), OPEN_FAMILY(0x92), ADD_FAMILY(0x93), ACCEPT_FAMILY(0x96), diff --git a/src/net/server/Server.java b/src/net/server/Server.java index ca0bdbbc9c..3632e139d0 100644 --- a/src/net/server/Server.java +++ b/src/net/server/Server.java @@ -60,6 +60,8 @@ import net.server.guild.MapleGuildCharacter; import net.server.worker.CharacterDiseaseWorker; import net.server.worker.CouponWorker; import net.server.worker.EventRecallCoordinatorWorker; +import net.server.worker.FredrickWorker; +import net.server.worker.InvitationWorker; import net.server.worker.LoginCoordinatorWorker; import net.server.worker.LoginStorageWorker; import net.server.worker.RankingCommandWorker; @@ -398,11 +400,12 @@ public class Server { int bossdroprate = getWorldProperty(p, "bossdroprate", i, ServerConstants.BOSS_DROP_RATE); int questrate = getWorldProperty(p, "questrate", i, ServerConstants.QUEST_RATE); int travelrate = getWorldProperty(p, "travelrate", i, ServerConstants.TRAVEL_RATE); + int fishingrate = getWorldProperty(p, "fishrate", i, ServerConstants.FISHING_RATE); World world = new World(i, Integer.parseInt(p.getProperty("flag" + i)), p.getProperty("eventmessage" + i), - exprate, droprate, bossdroprate, mesorate, questrate, travelrate); + exprate, droprate, bossdroprate, mesorate, questrate, travelrate, fishingrate); worldRecommendedList.add(new Pair<>(i, p.getProperty("whyamirecommended" + i))); worlds.add(world); @@ -915,6 +918,8 @@ public class Server { tMan.register(new LoginCoordinatorWorker(), 60 * 60 * 1000, timeLeft); tMan.register(new EventRecallCoordinatorWorker(), 60 * 60 * 1000, timeLeft); tMan.register(new LoginStorageWorker(), 2 * 60 * 1000, 2 * 60 * 1000); + tMan.register(new FredrickWorker(), 60 * 60 * 1000, 60 * 60 * 1000); + tMan.register(new InvitationWorker(), 30 * 1000, 30 * 1000); long timeToTake = System.currentTimeMillis(); SkillFactory.loadAllSkills(); diff --git a/src/net/server/channel/Channel.java b/src/net/server/channel/Channel.java index 2a84b75d1a..08b9aab87f 100644 --- a/src/net/server/channel/Channel.java +++ b/src/net/server/channel/Channel.java @@ -33,6 +33,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.WeakHashMap; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; @@ -89,18 +90,19 @@ public final class Channel { private String ip, serverMessage; private MapleMapFactory mapFactory; private EventScriptManager eventSM; - private MobStatusScheduler mobStatusSchedulers[] = new MobStatusScheduler[4]; - private MobAnimationScheduler mobAnimationSchedulers[] = new MobAnimationScheduler[4]; - private MobClearSkillScheduler mobClearSkillSchedulers[] = new MobClearSkillScheduler[4]; - private MobMistScheduler mobMistSchedulers[] = new MobMistScheduler[4]; - private FaceExpressionScheduler faceExpressionSchedulers[] = new FaceExpressionScheduler[4]; - private EventScheduler eventSchedulers[] = new EventScheduler[4]; - private OverallScheduler channelSchedulers[] = new OverallScheduler[4]; + private MobStatusScheduler mobStatusSchedulers[] = new MobStatusScheduler[ServerConstants.CHANNEL_LOCKS]; + private MobAnimationScheduler mobAnimationSchedulers[] = new MobAnimationScheduler[ServerConstants.CHANNEL_LOCKS]; + private MobClearSkillScheduler mobClearSkillSchedulers[] = new MobClearSkillScheduler[ServerConstants.CHANNEL_LOCKS]; + private MobMistScheduler mobMistSchedulers[] = new MobMistScheduler[ServerConstants.CHANNEL_LOCKS]; + private FaceExpressionScheduler faceExpressionSchedulers[] = new FaceExpressionScheduler[ServerConstants.CHANNEL_LOCKS]; + private EventScheduler eventSchedulers[] = new EventScheduler[ServerConstants.CHANNEL_LOCKS]; + private OverallScheduler channelSchedulers[] = new OverallScheduler[ServerConstants.CHANNEL_LOCKS]; private Map hiredMerchants = new HashMap<>(); private final Map storedVars = new HashMap<>(); private Set playersAway = new HashSet<>(); private List expeditions = new ArrayList<>(); private List expedType = new ArrayList<>(); + private Set ownedMaps = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap())); private MapleEvent event; private boolean finishedShutdown = false; private int usedDojo = 0; @@ -130,7 +132,7 @@ public final class Channel { private ReadLock merchRlock = merchantLock.readLock(); private WriteLock merchWlock = merchantLock.writeLock(); - private MonitoredReentrantLock faceLock[] = new MonitoredReentrantLock[4]; + private MonitoredReentrantLock faceLock[] = new MonitoredReentrantLock[ServerConstants.CHANNEL_LOCKS]; private MonitoredReentrantLock lock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHANNEL, true); @@ -168,7 +170,7 @@ public final class Channel { dojoTask[i] = null; } - for(int i = 0; i < 4; i++) { + for(int i = 0; i < ServerConstants.CHANNEL_LOCKS; i++) { faceLock[i] = MonitoredReentrantLockFactory.createLock(MonitoredLockType.CHANNEL_FACEEXPRS, true); mobStatusSchedulers[i] = new MobStatusScheduler(); @@ -235,7 +237,7 @@ public final class Channel { } } - for(int i = 0; i < 4; i++) { + for(int i = 0; i < ServerConstants.CHANNEL_LOCKS; i++) { if(mobStatusSchedulers[i] != null) { mobStatusSchedulers[i].dispose(); mobStatusSchedulers[i] = null; @@ -285,7 +287,7 @@ public final class Channel { } private void emptyLocks() { - for(int i = 0; i < 4; i++) { + for(int i = 0; i < ServerConstants.CHANNEL_LOCKS; i++) { faceLock[i] = faceLock[i].dispose(); } @@ -968,22 +970,33 @@ public final class Channel { } } - private static int getChannelSchedulerIndex(int mapid) { - if(mapid >= 250000000) { - if(mapid >= 900000000) { - return 3; - } else { - return 2; + public void registerOwnedMap(MapleMap map) { + ownedMaps.add(map); + } + + public void unregisterOwnedMap(MapleMap map) { + ownedMaps.remove(map); + } + + public void runCheckOwnedMapsSchedule() { + if (!ownedMaps.isEmpty()) { + List ownedMapsList; + + synchronized (ownedMaps) { + ownedMapsList = new ArrayList<>(ownedMaps); } - } else { - if(mapid >= 200000000) { - return 1; - } else { - return 0; + + for (MapleMap map : ownedMapsList) { + map.checkMapOwnerActivity(); } } } + private static int getChannelSchedulerIndex(int mapid) { + int section = 1000000000 / ServerConstants.CHANNEL_LOCKS; + return mapid / section; + } + public void registerMobStatus(int mapid, MonsterStatusEffect mse, Runnable cancelAction, long duration) { registerMobStatus(mapid, mse, cancelAction, duration, null, -1); } diff --git a/src/net/server/channel/handlers/AbstractDealDamageHandler.java b/src/net/server/channel/handlers/AbstractDealDamageHandler.java index 47aa1e9681..854b6f0979 100644 --- a/src/net/server/channel/handlers/AbstractDealDamageHandler.java +++ b/src/net/server/channel/handlers/AbstractDealDamageHandler.java @@ -136,6 +136,10 @@ public abstract class AbstractDealDamageHandler extends AbstractMaplePacketHandl } protected synchronized void applyAttack(AttackInfo attack, final MapleCharacter player, int attackCount) { + if (player.getMap().isOwnershipRestricted(player)) { + return; + } + Skill theSkill = null; MapleStatEffect attackEffect = null; final int job = player.getJob().getId(); diff --git a/src/net/server/channel/handlers/AdminCommandHandler.java b/src/net/server/channel/handlers/AdminCommandHandler.java index 2da805ecb6..8e440a44a4 100644 --- a/src/net/server/channel/handlers/AdminCommandHandler.java +++ b/src/net/server/channel/handlers/AdminCommandHandler.java @@ -132,7 +132,6 @@ public final class AdminCommandHandler extends AbstractMaplePacketHandler { MapleMonster monster = (MapleMonster) monsterx.get(x); if (monster.getId() == mobToKill) { c.getPlayer().getMap().killMonster(monster, c.getPlayer(), true); - //monster.giveExpToCharacter(c.getPlayer(), monster.getExp(), true, 1); already being done } } break; diff --git a/src/net/server/channel/handlers/AllianceOperationHandler.java b/src/net/server/channel/handlers/AllianceOperationHandler.java index d514b5d3fe..0c74c76142 100644 --- a/src/net/server/channel/handlers/AllianceOperationHandler.java +++ b/src/net/server/channel/handlers/AllianceOperationHandler.java @@ -61,7 +61,7 @@ public final class AllianceOperationHandler extends AbstractMaplePacketHandler { } } else { if (b == 4) { - chr.dropMessage("Your guild is already registered on a Guild Alliance."); + chr.dropMessage(5, "Your guild is already registered on a guild alliance."); c.announce(MaplePacketCreator.enableActions()); return; } @@ -88,27 +88,16 @@ public final class AllianceOperationHandler extends AbstractMaplePacketHandler { case 0x03: // Send Invite String guildName = slea.readMapleAsciiString(); - if(alliance.getGuilds().size() == alliance.getCapacity()) { - chr.dropMessage("Your alliance cannot comport any more guilds at the moment."); + if (alliance.getGuilds().size() == alliance.getCapacity()) { + chr.dropMessage(5, "Your alliance cannot comport any more guilds at the moment."); } else { - MapleGuild mg = Server.getInstance().getGuildByName(guildName); - if(mg == null) { - chr.dropMessage("The entered guild does not exist."); - } - else { - MapleCharacter victim = mg.getMGC(mg.getLeaderId()).getCharacter(); - - if (victim == null) { - chr.dropMessage("The master of the guild that you offered an invitation is currently not online."); - } else { - victim.getClient().announce(MaplePacketCreator.sendAllianceInvitation(alliance.getId(), chr)); - } - } + MapleAlliance.sendInvitation(c, guildName, alliance.getId()); } break; case 0x04: { // Accept Invite - if (chr.getGuild().getAllianceId() != 0 || chr.getGuildRank() != 1 || chr.getGuildId() < 1) { + MapleGuild guild = chr.getGuild(); + if (guild.getAllianceId() != 0 || chr.getGuildRank() != 1 || chr.getGuildId() < 1) { return; } @@ -120,6 +109,15 @@ public final class AllianceOperationHandler extends AbstractMaplePacketHandler { return; } + if (!MapleAlliance.answerInvitation(c.getPlayer().getId(), guild.getName(), alliance.getId(), true)) { + return; + } + + if (alliance.getGuilds().size() == alliance.getCapacity()) { + chr.dropMessage(5, "Your alliance cannot comport any more guilds at the moment."); + return; + } + int guildid = chr.getGuildId(); Server.getInstance().addGuildtoAlliance(alliance.getId(), guildid); @@ -132,7 +130,7 @@ public final class AllianceOperationHandler extends AbstractMaplePacketHandler { Server.getInstance().allianceMessage(alliance.getId(), MaplePacketCreator.addGuildToAlliance(alliance, guildid, c), -1, -1); Server.getInstance().allianceMessage(alliance.getId(), MaplePacketCreator.updateAllianceInfo(alliance, c.getWorld()), -1, -1); Server.getInstance().allianceMessage(alliance.getId(), MaplePacketCreator.allianceNotice(alliance.getId(), alliance.getNotice()), -1, -1); - chr.getGuild().dropMessage("Your guild has joined the [" + alliance.getName() + "] union."); + guild.dropMessage("Your guild has joined the [" + alliance.getName() + "] union."); break; } diff --git a/src/net/server/channel/handlers/CashOperationHandler.java b/src/net/server/channel/handlers/CashOperationHandler.java index 34fbcc7ecb..9ebb36ccb6 100644 --- a/src/net/server/channel/handlers/CashOperationHandler.java +++ b/src/net/server/channel/handlers/CashOperationHandler.java @@ -57,317 +57,325 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler { return; } - final int action = slea.readByte(); - if (action == 0x03 || action == 0x1E) { - slea.readByte(); - final int useNX = slea.readInt(); - final int snCS = slea.readInt(); - CashItem cItem = CashItemFactory.getItem(snCS); - if (!canBuy(chr, cItem, cs.getCash(useNX))) { - FilePrinter.printError(FilePrinter.ITEM, "Denied to sell cash item with SN " + snCS); // preventing NPE here thanks to MedicOP - c.enableCSActions(); - return; - } - - if (action == 0x03) { // Item - if (ItemConstants.isCashStore(cItem.getItemId()) && chr.getLevel() < 16) { - c.enableCSActions(); - return; - } else if (ItemConstants.isRateCoupon(cItem.getItemId()) && !ServerConstants.USE_SUPPLY_RATE_COUPONS) { - chr.dropMessage(1, "Rate coupons are currently unavailable to purchase."); - c.enableCSActions(); - return; - } else if (ItemConstants.isMapleLife(cItem.getItemId()) && chr.getLevel() < 30) { - c.enableCSActions(); - return; - } - - Item item = cItem.toItem(); - cs.addToInventory(item); - c.announce(MaplePacketCreator.showBoughtCashItem(item, c.getAccID())); - } else { // Package - List cashPackage = CashItemFactory.getPackage(cItem.getItemId()); - for (Item item : cashPackage) { - cs.addToInventory(item); - } - c.announce(MaplePacketCreator.showBoughtCashPackage(cashPackage, c.getAccID())); - } - cs.gainCash(useNX, cItem, chr.getWorld()); - c.announce(MaplePacketCreator.showCash(chr)); - } else if (action == 0x04) {//TODO check for gender - int birthday = slea.readInt(); - CashItem cItem = CashItemFactory.getItem(slea.readInt()); - Map recipient = MapleCharacter.getCharacterFromDatabase(slea.readMapleAsciiString()); - String message = slea.readMapleAsciiString(); - if (!canBuy(chr, cItem, cs.getCash(4)) || message.length() < 1 || message.length() > 73) { - c.enableCSActions(); - return; - } - if (!checkBirthday(c, birthday)) { - c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC4)); - return; - } else if (recipient == null) { - c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xA9)); - return; - } else if (recipient.get("accountid").equals(String.valueOf(c.getAccID()))) { - c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xA8)); - return; - } - cs.gift(Integer.parseInt(recipient.get("id")), chr.getName(), message, cItem.getSN()); - c.announce(MaplePacketCreator.showGiftSucceed(recipient.get("name"), cItem)); - cs.gainCash(4, cItem, chr.getWorld()); - c.announce(MaplePacketCreator.showCash(chr)); + if (c.tryacquireClient()) { // thanks Thora for finding out an exploit within cash operations try { - chr.sendNote(recipient.get("name"), chr.getName() + " has sent you a gift! Go check out the Cash Shop.", (byte) 0); //fame or not - } catch (SQLException ex) { - ex.printStackTrace(); - } - MapleCharacter receiver = c.getChannelServer().getPlayerStorage().getCharacterByName(recipient.get("name")); - if (receiver != null) receiver.showNote(); - } else if (action == 0x05) { // Modify wish list - cs.clearWishList(); - for (byte i = 0; i < 10; i++) { - int sn = slea.readInt(); - CashItem cItem = CashItemFactory.getItem(sn); - if (cItem != null && cItem.isOnSale() && sn != 0) { - cs.addToWishList(sn); - } - } - c.announce(MaplePacketCreator.showWishList(chr, true)); - } else if (action == 0x06) { // Increase Inventory Slots - slea.skip(1); - int cash = slea.readInt(); - byte mode = slea.readByte(); - if (mode == 0) { - byte type = slea.readByte(); - if (cs.getCash(cash) < 4000) { - c.enableCSActions(); - return; - } - if (chr.gainSlots(type, 4, false)) { - c.announce(MaplePacketCreator.showBoughtInventorySlots(type, chr.getSlots(type))); - cs.gainCash(cash, -4000); - c.announce(MaplePacketCreator.showCash(chr)); - } - } else { - CashItem cItem = CashItemFactory.getItem(slea.readInt()); - int type = (cItem.getItemId() - 9110000) / 1000; - if (!canBuy(chr, cItem, cs.getCash(cash))) { - c.enableCSActions(); - return; - } - if (chr.gainSlots(type, 8, false)) { - c.announce(MaplePacketCreator.showBoughtInventorySlots(type, chr.getSlots(type))); - cs.gainCash(cash, cItem, chr.getWorld()); - c.announce(MaplePacketCreator.showCash(chr)); - } - } - } else if (action == 0x07) { // Increase Storage Slots - slea.skip(1); - int cash = slea.readInt(); - byte mode = slea.readByte(); - if (mode == 0) { - if (cs.getCash(cash) < 4000) { - c.enableCSActions(); - return; - } - if (chr.getStorage().gainSlots(4)) { - FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " bought 4 slots to their account storage."); - chr.setUsedStorage(); - - c.announce(MaplePacketCreator.showBoughtStorageSlots(chr.getStorage().getSlots())); - cs.gainCash(cash, -4000); - c.announce(MaplePacketCreator.showCash(chr)); - } - } else { - CashItem cItem = CashItemFactory.getItem(slea.readInt()); - - if (!canBuy(chr, cItem, cs.getCash(cash))) { - c.enableCSActions(); - return; - } - if (chr.getStorage().gainSlots(8)) { // thanks ABaldParrot & Thora for detecting storage issues here - FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " bought 8 slots to their account storage."); - chr.setUsedStorage(); - - c.announce(MaplePacketCreator.showBoughtStorageSlots(chr.getStorage().getSlots())); - cs.gainCash(cash, cItem, chr.getWorld()); - c.announce(MaplePacketCreator.showCash(chr)); - } - } - } else if (action == 0x08) { // Increase Character Slots - slea.skip(1); - int cash = slea.readInt(); - CashItem cItem = CashItemFactory.getItem(slea.readInt()); - - if (!canBuy(chr, cItem, cs.getCash(cash))) { - c.enableCSActions(); - return; - } - - if (c.gainCharacterSlot()) { - c.announce(MaplePacketCreator.showBoughtCharacterSlot(c.getCharacterSlots())); - cs.gainCash(cash, cItem, chr.getWorld()); - c.announce(MaplePacketCreator.showCash(chr)); - } else { - chr.dropMessage(1, "You have already used up all 12 extra character slots."); - c.enableCSActions(); - return; - } - } else if (action == 0x0D) { // Take from Cash Inventory - Item item = cs.findByCashId(slea.readInt()); - if (item == null) { - c.enableCSActions(); - return; - } - if (chr.getInventory(item.getInventoryType()).addItem(item) != -1) { - cs.removeFromInventory(item); - c.announce(MaplePacketCreator.takeFromCashInventory(item)); - - if(item instanceof Equip) { - 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 - int cashId = slea.readInt(); - slea.skip(4); - - byte invType = slea.readByte(); - if (invType < 1 || invType > 5) { - c.disconnect(false, false); - return; - } - - MapleInventory mi = chr.getInventory(MapleInventoryType.getByType(invType)); - Item item = mi.findByCashId(cashId); - if (item == null) { - c.enableCSActions(); - return; - } else if (c.getPlayer().getPetIndex(item.getPetId()) > -1) { - chr.getClient().announce(MaplePacketCreator.serverNotice(1, "You cannot put the pet you currently equip into the Cash Shop inventory.")); - c.enableCSActions(); - return; - } else if (ItemConstants.isWeddingRing(item.getItemId()) || ItemConstants.isWeddingToken(item.getItemId())) { - chr.getClient().announce(MaplePacketCreator.serverNotice(1, "You cannot put relationship items into the Cash Shop inventory.")); - c.enableCSActions(); - return; - } - cs.addToInventory(item); - mi.removeSlot(item.getPosition()); - c.announce(MaplePacketCreator.putIntoCashInventory(item, c.getAccID())); - } else if (action == 0x1D) { //crush ring (action 28) - int birthday = slea.readInt(); - if (checkBirthday(c, birthday)) { - int toCharge = slea.readInt(); - int SN = slea.readInt(); - String recipientName = slea.readMapleAsciiString(); - String text = slea.readMapleAsciiString(); - CashItem itemRing = CashItemFactory.getItem(SN); - MapleCharacter partner = c.getChannelServer().getPlayerStorage().getCharacterByName(recipientName); - if (partner == null) { - chr.getClient().announce(MaplePacketCreator.serverNotice(1, "The partner you specified cannot be found.\r\nPlease make sure your partner is online and in the same channel.")); - } else { - - /* if (partner.getGender() == chr.getGender()) { - chr.dropMessage("You and your partner are the same gender, please buy a friendship ring."); + final int action = slea.readByte(); + if (action == 0x03 || action == 0x1E) { + slea.readByte(); + final int useNX = slea.readInt(); + final int snCS = slea.readInt(); + CashItem cItem = CashItemFactory.getItem(snCS); + if (!canBuy(chr, cItem, cs.getCash(useNX))) { + FilePrinter.printError(FilePrinter.ITEM, "Denied to sell cash item with SN " + snCS); // preventing NPE here thanks to MedicOP c.enableCSActions(); return; - }*/ //Gotta let them faggots marry too, hence why this is commented out <3 - - if(itemRing.toItem() instanceof Equip) { - Equip eqp = (Equip) itemRing.toItem(); - Pair rings = MapleRing.createRing(itemRing.getItemId(), chr, partner); - eqp.setRingId(rings.getLeft()); - cs.addToInventory(eqp); - c.announce(MaplePacketCreator.showBoughtCashItem(eqp, c.getAccID())); - cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight()); - cs.gainCash(toCharge, itemRing, chr.getWorld()); - chr.addCrushRing(MapleRing.loadFromDb(rings.getLeft())); - try { - chr.sendNote(partner.getName(), text, (byte) 1); - } catch (SQLException ex) { - ex.printStackTrace(); - } - partner.showNote(); - } - } - } else { - c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC4)); - } - - c.announce(MaplePacketCreator.showCash(c.getPlayer())); - } else if (action == 0x20) { - int serialNumber = slea.readInt(); // thanks GabrielSin for detecting a potential exploit with 1 meso cash items. - if (serialNumber / 10000000 != 8) { - c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC0)); - return; - } - - CashItem item = CashItemFactory.getItem(serialNumber); - if (item == null || !item.isOnSale()) { - c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC0)); - return; - } - - int itemId = item.getItemId(); - int itemPrice = item.getPrice(); - if (itemPrice <= 0) { - c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC0)); - return; - } - - if (chr.getMeso() >= itemPrice) { - if (chr.canHold(itemId)) { - chr.gainMeso(-itemPrice, false); - MapleInventoryManipulator.addById(c, itemId, (short) 1, "", -1); - c.announce(MaplePacketCreator.showBoughtQuestItem(itemId)); - } - } - c.announce(MaplePacketCreator.showCash(c.getPlayer())); - } else if (action == 0x23) { //Friendship :3 - int birthday = slea.readInt(); - if (checkBirthday(c, birthday)) { - int payment = slea.readByte(); - slea.skip(3); //0s - int snID = slea.readInt(); - CashItem itemRing = CashItemFactory.getItem(snID); - String sentTo = slea.readMapleAsciiString(); - int available = slea.readShort() - 1; - String text = slea.readAsciiString(available); - slea.readByte(); - MapleCharacter partner = c.getChannelServer().getPlayerStorage().getCharacterByName(sentTo); - if (partner == null) { - chr.dropMessage("The partner you specified cannot be found.\r\nPlease make sure your partner is online and in the same channel."); - } else { - // Need to check to make sure its actually an equip and the right SN... - if(itemRing.toItem() instanceof Equip) { - Equip eqp = (Equip) itemRing.toItem(); - Pair rings = MapleRing.createRing(itemRing.getItemId(), chr, partner); - eqp.setRingId(rings.getLeft()); - cs.addToInventory(eqp); - c.announce(MaplePacketCreator.showBoughtCashItem(eqp, c.getAccID())); - cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight()); - cs.gainCash(payment, -itemRing.getPrice()); - chr.addFriendshipRing(MapleRing.loadFromDb(rings.getLeft())); - try { - chr.sendNote(partner.getName(), text, (byte) 1); - } catch (SQLException ex) { - ex.printStackTrace(); - } - partner.showNote(); } + + if (action == 0x03) { // Item + if (ItemConstants.isCashStore(cItem.getItemId()) && chr.getLevel() < 16) { + c.enableCSActions(); + return; + } else if (ItemConstants.isRateCoupon(cItem.getItemId()) && !ServerConstants.USE_SUPPLY_RATE_COUPONS) { + chr.dropMessage(1, "Rate coupons are currently unavailable to purchase."); + c.enableCSActions(); + return; + } else if (ItemConstants.isMapleLife(cItem.getItemId()) && chr.getLevel() < 30) { + c.enableCSActions(); + return; + } + + Item item = cItem.toItem(); + cs.addToInventory(item); + c.announce(MaplePacketCreator.showBoughtCashItem(item, c.getAccID())); + } else { // Package + List cashPackage = CashItemFactory.getPackage(cItem.getItemId()); + for (Item item : cashPackage) { + cs.addToInventory(item); + } + c.announce(MaplePacketCreator.showBoughtCashPackage(cashPackage, c.getAccID())); + } + cs.gainCash(useNX, cItem, chr.getWorld()); + c.announce(MaplePacketCreator.showCash(chr)); + } else if (action == 0x04) {//TODO check for gender + int birthday = slea.readInt(); + CashItem cItem = CashItemFactory.getItem(slea.readInt()); + Map recipient = MapleCharacter.getCharacterFromDatabase(slea.readMapleAsciiString()); + String message = slea.readMapleAsciiString(); + if (!canBuy(chr, cItem, cs.getCash(4)) || message.length() < 1 || message.length() > 73) { + c.enableCSActions(); + return; + } + if (!checkBirthday(c, birthday)) { + c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC4)); + return; + } else if (recipient == null) { + c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xA9)); + return; + } else if (recipient.get("accountid").equals(String.valueOf(c.getAccID()))) { + c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xA8)); + return; + } + cs.gift(Integer.parseInt(recipient.get("id")), chr.getName(), message, cItem.getSN()); + c.announce(MaplePacketCreator.showGiftSucceed(recipient.get("name"), cItem)); + cs.gainCash(4, cItem, chr.getWorld()); + c.announce(MaplePacketCreator.showCash(chr)); + try { + chr.sendNote(recipient.get("name"), chr.getName() + " has sent you a gift! Go check out the Cash Shop.", (byte) 0); //fame or not + } catch (SQLException ex) { + ex.printStackTrace(); + } + MapleCharacter receiver = c.getChannelServer().getPlayerStorage().getCharacterByName(recipient.get("name")); + if (receiver != null) receiver.showNote(); + } else if (action == 0x05) { // Modify wish list + cs.clearWishList(); + for (byte i = 0; i < 10; i++) { + int sn = slea.readInt(); + CashItem cItem = CashItemFactory.getItem(sn); + if (cItem != null && cItem.isOnSale() && sn != 0) { + cs.addToWishList(sn); + } + } + c.announce(MaplePacketCreator.showWishList(chr, true)); + } else if (action == 0x06) { // Increase Inventory Slots + slea.skip(1); + int cash = slea.readInt(); + byte mode = slea.readByte(); + if (mode == 0) { + byte type = slea.readByte(); + if (cs.getCash(cash) < 4000) { + c.enableCSActions(); + return; + } + if (chr.gainSlots(type, 4, false)) { + c.announce(MaplePacketCreator.showBoughtInventorySlots(type, chr.getSlots(type))); + cs.gainCash(cash, -4000); + c.announce(MaplePacketCreator.showCash(chr)); + } + } else { + CashItem cItem = CashItemFactory.getItem(slea.readInt()); + int type = (cItem.getItemId() - 9110000) / 1000; + if (!canBuy(chr, cItem, cs.getCash(cash))) { + c.enableCSActions(); + return; + } + if (chr.gainSlots(type, 8, false)) { + c.announce(MaplePacketCreator.showBoughtInventorySlots(type, chr.getSlots(type))); + cs.gainCash(cash, cItem, chr.getWorld()); + c.announce(MaplePacketCreator.showCash(chr)); + } + } + } else if (action == 0x07) { // Increase Storage Slots + slea.skip(1); + int cash = slea.readInt(); + byte mode = slea.readByte(); + if (mode == 0) { + if (cs.getCash(cash) < 4000) { + c.enableCSActions(); + return; + } + if (chr.getStorage().gainSlots(4)) { + FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " bought 4 slots to their account storage."); + chr.setUsedStorage(); + + c.announce(MaplePacketCreator.showBoughtStorageSlots(chr.getStorage().getSlots())); + cs.gainCash(cash, -4000); + c.announce(MaplePacketCreator.showCash(chr)); + } + } else { + CashItem cItem = CashItemFactory.getItem(slea.readInt()); + + if (!canBuy(chr, cItem, cs.getCash(cash))) { + c.enableCSActions(); + return; + } + if (chr.getStorage().gainSlots(8)) { // thanks ABaldParrot & Thora for detecting storage issues here + FilePrinter.print(FilePrinter.STORAGE + c.getAccountName() + ".txt", c.getPlayer().getName() + " bought 8 slots to their account storage."); + chr.setUsedStorage(); + + c.announce(MaplePacketCreator.showBoughtStorageSlots(chr.getStorage().getSlots())); + cs.gainCash(cash, cItem, chr.getWorld()); + c.announce(MaplePacketCreator.showCash(chr)); + } + } + } else if (action == 0x08) { // Increase Character Slots + slea.skip(1); + int cash = slea.readInt(); + CashItem cItem = CashItemFactory.getItem(slea.readInt()); + + if (!canBuy(chr, cItem, cs.getCash(cash))) { + c.enableCSActions(); + return; + } + + if (c.gainCharacterSlot()) { + c.announce(MaplePacketCreator.showBoughtCharacterSlot(c.getCharacterSlots())); + cs.gainCash(cash, cItem, chr.getWorld()); + c.announce(MaplePacketCreator.showCash(chr)); + } else { + chr.dropMessage(1, "You have already used up all 12 extra character slots."); + c.enableCSActions(); + return; + } + } else if (action == 0x0D) { // Take from Cash Inventory + Item item = cs.findByCashId(slea.readInt()); + if (item == null) { + c.enableCSActions(); + return; + } + if (chr.getInventory(item.getInventoryType()).addItem(item) != -1) { + cs.removeFromInventory(item); + c.announce(MaplePacketCreator.takeFromCashInventory(item)); + + if(item instanceof Equip) { + 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 + int cashId = slea.readInt(); + slea.skip(4); + + byte invType = slea.readByte(); + if (invType < 1 || invType > 5) { + c.disconnect(false, false); + return; + } + + MapleInventory mi = chr.getInventory(MapleInventoryType.getByType(invType)); + Item item = mi.findByCashId(cashId); + if (item == null) { + c.enableCSActions(); + return; + } else if (c.getPlayer().getPetIndex(item.getPetId()) > -1) { + chr.getClient().announce(MaplePacketCreator.serverNotice(1, "You cannot put the pet you currently equip into the Cash Shop inventory.")); + c.enableCSActions(); + return; + } else if (ItemConstants.isWeddingRing(item.getItemId()) || ItemConstants.isWeddingToken(item.getItemId())) { + chr.getClient().announce(MaplePacketCreator.serverNotice(1, "You cannot put relationship items into the Cash Shop inventory.")); + c.enableCSActions(); + return; + } + cs.addToInventory(item); + mi.removeSlot(item.getPosition()); + c.announce(MaplePacketCreator.putIntoCashInventory(item, c.getAccID())); + } else if (action == 0x1D) { //crush ring (action 28) + int birthday = slea.readInt(); + if (checkBirthday(c, birthday)) { + int toCharge = slea.readInt(); + int SN = slea.readInt(); + String recipientName = slea.readMapleAsciiString(); + String text = slea.readMapleAsciiString(); + CashItem itemRing = CashItemFactory.getItem(SN); + MapleCharacter partner = c.getChannelServer().getPlayerStorage().getCharacterByName(recipientName); + if (partner == null) { + chr.getClient().announce(MaplePacketCreator.serverNotice(1, "The partner you specified cannot be found.\r\nPlease make sure your partner is online and in the same channel.")); + } else { + + /* if (partner.getGender() == chr.getGender()) { + chr.dropMessage(5, "You and your partner are the same gender, please buy a friendship ring."); + c.enableCSActions(); + return; + }*/ //Gotta let them faggots marry too, hence why this is commented out <3 + + if(itemRing.toItem() instanceof Equip) { + Equip eqp = (Equip) itemRing.toItem(); + Pair rings = MapleRing.createRing(itemRing.getItemId(), chr, partner); + eqp.setRingId(rings.getLeft()); + cs.addToInventory(eqp); + c.announce(MaplePacketCreator.showBoughtCashItem(eqp, c.getAccID())); + cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight()); + cs.gainCash(toCharge, itemRing, chr.getWorld()); + chr.addCrushRing(MapleRing.loadFromDb(rings.getLeft())); + try { + chr.sendNote(partner.getName(), text, (byte) 1); + } catch (SQLException ex) { + ex.printStackTrace(); + } + partner.showNote(); + } + } + } else { + c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC4)); + } + + c.announce(MaplePacketCreator.showCash(c.getPlayer())); + } else if (action == 0x20) { + int serialNumber = slea.readInt(); // thanks GabrielSin for detecting a potential exploit with 1 meso cash items. + if (serialNumber / 10000000 != 8) { + c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC0)); + return; + } + + CashItem item = CashItemFactory.getItem(serialNumber); + if (item == null || !item.isOnSale()) { + c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC0)); + return; + } + + int itemId = item.getItemId(); + int itemPrice = item.getPrice(); + if (itemPrice <= 0) { + c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC0)); + return; + } + + if (chr.getMeso() >= itemPrice) { + if (chr.canHold(itemId)) { + chr.gainMeso(-itemPrice, false); + MapleInventoryManipulator.addById(c, itemId, (short) 1, "", -1); + c.announce(MaplePacketCreator.showBoughtQuestItem(itemId)); + } + } + c.announce(MaplePacketCreator.showCash(c.getPlayer())); + } else if (action == 0x23) { //Friendship :3 + int birthday = slea.readInt(); + if (checkBirthday(c, birthday)) { + int payment = slea.readByte(); + slea.skip(3); //0s + int snID = slea.readInt(); + CashItem itemRing = CashItemFactory.getItem(snID); + String sentTo = slea.readMapleAsciiString(); + int available = slea.readShort() - 1; + String text = slea.readAsciiString(available); + slea.readByte(); + MapleCharacter partner = c.getChannelServer().getPlayerStorage().getCharacterByName(sentTo); + if (partner == null) { + chr.dropMessage(5, "The partner you specified cannot be found. Please make sure your partner is online and in the same channel."); + } else { + // Need to check to make sure its actually an equip and the right SN... + if(itemRing.toItem() instanceof Equip) { + Equip eqp = (Equip) itemRing.toItem(); + Pair rings = MapleRing.createRing(itemRing.getItemId(), chr, partner); + eqp.setRingId(rings.getLeft()); + cs.addToInventory(eqp); + c.announce(MaplePacketCreator.showBoughtCashItem(eqp, c.getAccID())); + cs.gift(partner.getId(), chr.getName(), text, eqp.getSN(), rings.getRight()); + cs.gainCash(payment, -itemRing.getPrice()); + chr.addFriendshipRing(MapleRing.loadFromDb(rings.getLeft())); + try { + chr.sendNote(partner.getName(), text, (byte) 1); + } catch (SQLException ex) { + ex.printStackTrace(); + } + partner.showNote(); + } + } + } else { + c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC4)); + } + + c.announce(MaplePacketCreator.showCash(c.getPlayer())); + } else { + System.out.println("Unhandled action: " + action + "\n" + slea); } - } else { - c.announce(MaplePacketCreator.showCashShopMessage((byte) 0xC4)); + } finally { + c.releaseClient(); } - - c.announce(MaplePacketCreator.showCash(c.getPlayer())); } else { - System.out.println("Unhandled action: " + action + "\n" + slea); + c.announce(MaplePacketCreator.enableActions()); } } diff --git a/src/net/server/channel/handlers/ChangeMapHandler.java b/src/net/server/channel/handlers/ChangeMapHandler.java index 746716040a..8f2bd4b4d3 100644 --- a/src/net/server/channel/handlers/ChangeMapHandler.java +++ b/src/net/server/channel/handlers/ChangeMapHandler.java @@ -52,7 +52,7 @@ public final class ChangeMapHandler extends AbstractMaplePacketHandler { return; } if (chr.getTrade() != null) { - MapleTrade.cancelTrade(chr); + MapleTrade.cancelTrade(chr, MapleTrade.TradeResult.UNSUCCESSFUL_ANOTHER_MAP); } if (slea.available() == 0) { //Cash Shop :) if(!chr.getCashShop().isOpened()) { @@ -79,66 +79,70 @@ public final class ChangeMapHandler extends AbstractMaplePacketHandler { MaplePortal portal = chr.getMap().getPortal(startwp); slea.readByte(); boolean wheel = slea.readShort() > 0; - if (targetid != -1 && !chr.isAlive()) { - MapleMap map = chr.getMap(); - if (wheel && chr.haveItemWithId(5510000, false)) { - // thanks lucasziron for showing revivePlayer() also being triggered by Wheel - - MapleInventoryManipulator.removeById(c, MapleInventoryType.CASH, 5510000, 1, true, false); - chr.announce(MaplePacketCreator.showWheelsLeft(chr.getItemQuantity(5510000, false))); + + if (targetid != -1) { + if (!chr.isAlive()) { + MapleMap map = chr.getMap(); + if (wheel && chr.haveItemWithId(5510000, false)) { + // thanks lucasziron for showing revivePlayer() also being triggered by Wheel - chr.updateHp(50); - chr.changeMap(map, map.findClosestPlayerSpawnpoint(chr.getPosition())); + MapleInventoryManipulator.removeById(c, MapleInventoryType.CASH, 5510000, 1, true, false); + chr.announce(MaplePacketCreator.showWheelsLeft(chr.getItemQuantity(5510000, false))); + + chr.updateHp(50); + chr.changeMap(map, map.findClosestPlayerSpawnpoint(chr.getPosition())); + } else { + boolean executeStandardPath = true; + if (chr.getEventInstance() != null) { + executeStandardPath = chr.getEventInstance().revivePlayer(chr); + } + if (executeStandardPath) { + chr.respawn(map.getReturnMapId()); + } + } } else { - boolean executeStandardPath = true; - if (chr.getEventInstance() != null) { - executeStandardPath = chr.getEventInstance().revivePlayer(chr); - } - if (executeStandardPath) { - chr.respawn(map.getReturnMapId()); - } - } - } else if (targetid != -1) { - if(chr.isGM()) { - MapleMap to = chr.getWarpMap(targetid); - chr.changeMap(to, to.getPortal(0)); - } - else { - final int divi = chr.getMapId() / 100; - boolean warp = false; - if (divi == 0) { - if (targetid == 10000) { - warp = true; - } - } else if (divi == 20100) { - if (targetid == 104000000) { - c.announce(MaplePacketCreator.lockUI(false)); - c.announce(MaplePacketCreator.disableUI(false)); - warp = true; - } - } else if (divi == 9130401) { // Only allow warp if player is already in Intro map, or else = hack - if (targetid == 130000000 || targetid / 100 == 9130401) { // Cygnus introduction - warp = true; - } - } else if (divi == 9140900) { // Aran Introduction - if (targetid == 914090011 || targetid == 914090012 || targetid == 914090013 || targetid == 140090000) { - warp = true; - } - } else if (divi / 10 == 1020) { // Adventurer movie clip Intro - if (targetid == 1020000) { - warp = true; - } - } else if(divi / 10 >= 980040 && divi / 10 <= 980045) { - if(targetid == 980040000) { - warp = true; - } - } - if (warp) { - final MapleMap to = chr.getWarpMap(targetid); + if(chr.isGM()) { + MapleMap to = chr.getWarpMap(targetid); chr.changeMap(to, to.getPortal(0)); } + else { + final int divi = chr.getMapId() / 100; + boolean warp = false; + if (divi == 0) { + if (targetid == 10000) { + warp = true; + } + } else if (divi == 20100) { + if (targetid == 104000000) { + c.announce(MaplePacketCreator.lockUI(false)); + c.announce(MaplePacketCreator.disableUI(false)); + warp = true; + } + } else if (divi == 9130401) { // Only allow warp if player is already in Intro map, or else = hack + if (targetid == 130000000 || targetid / 100 == 9130401) { // Cygnus introduction + warp = true; + } + } else if (divi == 9140900) { // Aran Introduction + if (targetid == 914090011 || targetid == 914090012 || targetid == 914090013 || targetid == 140090000) { + warp = true; + } + } else if (divi / 10 == 1020) { // Adventurer movie clip Intro + if (targetid == 1020000) { + warp = true; + } + } else if(divi / 10 >= 980040 && divi / 10 <= 980045) { + if(targetid == 980040000) { + warp = true; + } + } + if (warp) { + final MapleMap to = chr.getWarpMap(targetid); + chr.changeMap(to, to.getPortal(0)); + } + } } - } + } + if (portal != null && !portal.getPortalStatus()) { c.announce(MaplePacketCreator.blockedMessage(1)); c.announce(MaplePacketCreator.enableActions()); diff --git a/src/net/server/channel/handlers/ChangeMapSpecialHandler.java b/src/net/server/channel/handlers/ChangeMapSpecialHandler.java index b376e7867e..fdafc0d709 100644 --- a/src/net/server/channel/handlers/ChangeMapSpecialHandler.java +++ b/src/net/server/channel/handlers/ChangeMapSpecialHandler.java @@ -25,6 +25,7 @@ import client.MapleClient; import net.AbstractMaplePacketHandler; import server.MaplePortal; import server.MapleTrade; +import server.MapleTrade.TradeResult; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; @@ -44,7 +45,7 @@ public final class ChangeMapSpecialHandler extends AbstractMaplePacketHandler { return; } if (c.getPlayer().getTrade() != null) { - MapleTrade.cancelTrade(c.getPlayer()); + MapleTrade.cancelTrade(c.getPlayer(), TradeResult.UNSUCCESSFUL_ANOTHER_MAP); } portal.enterPortal(c); } diff --git a/src/net/server/channel/handlers/CloseChalkboardHandler.java b/src/net/server/channel/handlers/CloseChalkboardHandler.java index 8489d44316..497660d247 100644 --- a/src/net/server/channel/handlers/CloseChalkboardHandler.java +++ b/src/net/server/channel/handlers/CloseChalkboardHandler.java @@ -31,6 +31,8 @@ import tools.data.input.SeekableLittleEndianAccessor; * @author Xterminator */ public final class CloseChalkboardHandler extends AbstractMaplePacketHandler { + + @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { c.getPlayer().setChalkboard(null); c.getPlayer().getMap().broadcastMessage(MaplePacketCreator.useChalkboard(c.getPlayer(), true)); diff --git a/src/net/server/channel/handlers/DenyAllianceRequestHandler.java b/src/net/server/channel/handlers/DenyAllianceRequestHandler.java new file mode 100644 index 0000000000..bf5c63d771 --- /dev/null +++ b/src/net/server/channel/handlers/DenyAllianceRequestHandler.java @@ -0,0 +1,47 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +package net.server.channel.handlers; + +import client.MapleClient; +import client.MapleCharacter; +import net.AbstractMaplePacketHandler; +import net.server.guild.MapleAlliance; +import tools.data.input.SeekableLittleEndianAccessor; + +/** + * @author Ronan + */ +public final class DenyAllianceRequestHandler extends AbstractMaplePacketHandler { + + @Override + public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { + slea.readByte(); + String inviterName = slea.readMapleAsciiString(); + String guildName = slea.readMapleAsciiString(); + + MapleCharacter chr = c.getWorldServer().getPlayerStorage().getCharacterByName(inviterName); + if (chr != null) { + MapleAlliance alliance = chr.getAlliance(); + if (alliance != null) { + MapleAlliance.answerInvitation(c.getPlayer().getId(), guildName, alliance.getId(), false); + } + } + } +} \ No newline at end of file diff --git a/src/net/server/channel/handlers/DenyGuildRequestHandler.java b/src/net/server/channel/handlers/DenyGuildRequestHandler.java index f37ad4829e..e68e116c15 100644 --- a/src/net/server/channel/handlers/DenyGuildRequestHandler.java +++ b/src/net/server/channel/handlers/DenyGuildRequestHandler.java @@ -24,7 +24,7 @@ package net.server.channel.handlers; import client.MapleCharacter; import client.MapleClient; import net.AbstractMaplePacketHandler; -import tools.MaplePacketCreator; +import net.server.guild.MapleGuild; import tools.data.input.SeekableLittleEndianAccessor; /** @@ -32,11 +32,13 @@ import tools.data.input.SeekableLittleEndianAccessor; * @author Xterminator */ public final class DenyGuildRequestHandler extends AbstractMaplePacketHandler { + + @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { slea.readByte(); - MapleCharacter cfrom = c.getChannelServer().getPlayerStorage().getCharacterByName(slea.readMapleAsciiString()); + MapleCharacter cfrom = c.getWorldServer().getPlayerStorage().getCharacterByName(slea.readMapleAsciiString()); if (cfrom != null) { - cfrom.getClient().announce(MaplePacketCreator.denyGuildInvitation(c.getPlayer().getName())); + MapleGuild.answerInvitation(c.getPlayer().getId(), c.getPlayer().getName(), cfrom.getGuildId(), false); } } } diff --git a/src/net/server/channel/handlers/DenyPartyRequestHandler.java b/src/net/server/channel/handlers/DenyPartyRequestHandler.java index b5135c873a..19b18d84a3 100644 --- a/src/net/server/channel/handlers/DenyPartyRequestHandler.java +++ b/src/net/server/channel/handlers/DenyPartyRequestHandler.java @@ -24,15 +24,22 @@ package net.server.channel.handlers; import client.MapleCharacter; import client.MapleClient; import net.AbstractMaplePacketHandler; +import net.server.coordinator.MapleInviteCoordinator; +import net.server.coordinator.MapleInviteCoordinator.InviteResult; +import net.server.coordinator.MapleInviteCoordinator.InviteType; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; public final class DenyPartyRequestHandler extends AbstractMaplePacketHandler { + + @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { slea.readByte(); MapleCharacter cfrom = c.getChannelServer().getPlayerStorage().getCharacterByName(slea.readMapleAsciiString()); if (cfrom != null) { - cfrom.getClient().announce(MaplePacketCreator.partyStatusMessage(23, c.getPlayer().getName())); + if (MapleInviteCoordinator.answerInvite(InviteType.PARTY, c.getPlayer().getId(), cfrom.getPartyId(), false).getLeft() == InviteResult.DENIED) { + cfrom.getClient().announce(MaplePacketCreator.partyStatusMessage(23, c.getPlayer().getName())); + } } } } diff --git a/src/net/server/channel/handlers/EnterCashShopHandler.java b/src/net/server/channel/handlers/EnterCashShopHandler.java index 5898f96d68..f58b1a0d4a 100644 --- a/src/net/server/channel/handlers/EnterCashShopHandler.java +++ b/src/net/server/channel/handlers/EnterCashShopHandler.java @@ -68,6 +68,7 @@ public class EnterCashShopHandler extends AbstractMaplePacketHandler { Server.getInstance().getPlayerBuffStorage().addDiseasesToStorage(mc.getId(), mc.getAllDiseases()); mc.setAwayFromChannelWorld(); mc.notifyMapTransferToPartner(-1); + mc.removeIncomingInvites(); 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 56a2cbeff5..4bab7b410f 100644 --- a/src/net/server/channel/handlers/EnterMTSHandler.java +++ b/src/net/server/channel/handlers/EnterMTSHandler.java @@ -93,6 +93,7 @@ public final class EnterMTSHandler extends AbstractMaplePacketHandler { Server.getInstance().getPlayerBuffStorage().addDiseasesToStorage(chr.getId(), chr.getAllDiseases()); chr.setAwayFromChannelWorld(); chr.notifyMapTransferToPartner(-1); + chr.removeIncomingInvites(); chr.cancelAllBuffs(true); chr.cancelAllDebuffs(); chr.cancelBuffExpireTask(); diff --git a/src/net/server/channel/handlers/GuildOperationHandler.java b/src/net/server/channel/handlers/GuildOperationHandler.java index 92483e8c51..20bce7afb5 100644 --- a/src/net/server/channel/handlers/GuildOperationHandler.java +++ b/src/net/server/channel/handlers/GuildOperationHandler.java @@ -28,7 +28,6 @@ import constants.ServerConstants; import client.MapleClient; import net.AbstractMaplePacketHandler; import tools.data.input.SeekableLittleEndianAccessor; -import java.util.Iterator; import tools.MaplePacketCreator; import client.MapleCharacter; import net.server.Server; @@ -47,50 +46,8 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler { return true; } - private class Invited { - public String name; - public int gid; - public long expiration; - - public Invited(String n, int id) { - name = n.toLowerCase(); - gid = id; - expiration = currentServerTime() + 60 * 60 * 1000; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof Invited)) { - return false; - } - Invited oth = (Invited) other; - return (gid == oth.gid && name.equals(oth)); - } - - @Override - public int hashCode() { - int hash = 3; - hash = 83 * hash + (this.name != null ? this.name.hashCode() : 0); - hash = 83 * hash + this.gid; - return hash; - } - } - private java.util.List invited = new java.util.LinkedList(); - private long nextPruneTime = currentServerTime() + 20 * 60 * 1000; - @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - if (currentServerTime() >= nextPruneTime) { - Iterator itr = invited.iterator(); - Invited inv; - while (itr.hasNext()) { - inv = itr.next(); - if (currentServerTime() >= inv.expiration) { - itr.remove(); - } - } - nextPruneTime = currentServerTime() + 20 * 60 * 1000; - } MapleCharacter mc = c.getPlayer(); byte type = slea.readByte(); int allianceId = -1; @@ -134,16 +91,13 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler { if (mc.getGuildId() <= 0 || mc.getGuildRank() > 2) { return; } - String name = slea.readMapleAsciiString(); - MapleGuildResponse mgr = MapleGuild.sendInvite(c, name); + + String targetName = slea.readMapleAsciiString(); + MapleGuildResponse mgr = MapleGuild.sendInvitation(c, targetName); if (mgr != null) { - c.announce(mgr.getPacket()); - } else { - Invited inv = new Invited(name, mc.getGuildId()); - if (!invited.contains(inv)) { - invited.add(inv); - } - } + c.announce(mgr.getPacket(targetName)); + } else {} // already sent invitation, do nothing + break; case 0x06: if (mc.getGuildId() > 0) { @@ -156,28 +110,18 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler { System.out.println("[hax] " + mc.getName() + " attempted to join a guild with a different character id."); return; } - name = mc.getName().toLowerCase(); - Iterator itr = invited.iterator(); - boolean bOnList = false; - while (itr.hasNext()) { - Invited inv = itr.next(); - if (gid == inv.gid && name.equals(inv.name)) { - bOnList = true; - itr.remove(); - break; - } - } - if (!bOnList) { - System.out.println("[hax] " + mc.getName() + " is trying to join a guild that never invited him/her (or that the invitation has expired)"); + + if (!MapleGuild.answerInvitation(cid, mc.getName(), gid, true)) { return; } + mc.getMGC().setGuildId(gid); // joins the guild mc.getMGC().setGuildRank(5); // start at lowest rank mc.getMGC().setAllianceRank(5); int s = Server.getInstance().addGuildMember(mc.getMGC(), mc); if (s == 0) { - c.getPlayer().dropMessage(1, "The Guild you are trying to join is already full."); + c.getPlayer().dropMessage(1, "The guild you are trying to join is already full."); mc.getMGC().setGuildId(0); return; } @@ -193,7 +137,7 @@ public final class GuildOperationHandler extends AbstractMaplePacketHandler { break; case 0x07: cid = slea.readInt(); - name = slea.readMapleAsciiString(); + String name = slea.readMapleAsciiString(); if (cid != mc.getId() || !name.equals(mc.getName()) || mc.getGuildId() <= 0) { System.out.println("[hax] " + mc.getName() + " tried to quit guild under the name \"" + name + "\" and current guild id of " + mc.getGuildId() + "."); return; diff --git a/src/net/server/channel/handlers/InventorySortHandler.java b/src/net/server/channel/handlers/InventorySortHandler.java index 137cfcd0c7..c48a8d9390 100644 --- a/src/net/server/channel/handlers/InventorySortHandler.java +++ b/src/net/server/channel/handlers/InventorySortHandler.java @@ -72,6 +72,28 @@ class PairedQuicksort { } while (i <= j); } + private void PartitionByItemIdReverse(int Esq, int Dir, ArrayList A) { + Item x, w; + + i = Esq; + j = Dir; + + x = A.get((i + j) / 2); + do { + while (x.getItemId() < A.get(i).getItemId()) i++; + while (x.getItemId() > A.get(j).getItemId()) j--; + + if (i <= j) { + w = A.get(i); + A.set(i, A.get(j)); + A.set(j, w); + + i++; + j--; + } + } while (i <= j); + } + private void PartitionByName(int Esq, int Dir, ArrayList A) { Item x, w; @@ -163,10 +185,64 @@ class PairedQuicksort { if (i < Dir) MapleQuicksort(i, Dir, A, sort); } + private static int getItemSubtype(Item it) { + return it.getItemId() / 10000; + } + + private int[] BinarySearchElement(ArrayList A, int rangeId) { + int st = 0, en = A.size() - 1; + + int mid = -1, idx = -1; + while (en >= st) { + idx = (st + en) / 2; + mid = getItemSubtype(A.get(idx)); + + if (mid == rangeId) { + break; + } else if (mid < rangeId) { + st = idx + 1; + } else { + en = idx - 1; + } + } + + if (en < st) { + return null; + } + + st = idx - 1; + en = idx + 1; + while (st >= 0 && getItemSubtype(A.get(st)) == rangeId) { + st -= 1; + } + st += 1; + + while (en < A.size() && getItemSubtype(A.get(en)) == rangeId) { + en += 1; + } + en -= 1; + + return new int[]{st, en}; + } + + public void reverseSortSublist(ArrayList A, int[] range) { + if (range != null) { + PartitionByItemIdReverse(range[0], range[1], A); + } + } + public PairedQuicksort(ArrayList A, int primarySort, int secondarySort) { intersect = new ArrayList<>(); - if(A.size() > 0) MapleQuicksort(0, A.size() - 1, A, primarySort); + if(A.size() > 0) { + MapleQuicksort(0, A.size() - 1, A, primarySort); + + if (A.get(0).getInventoryType().equals(MapleInventoryType.USE)) { // thanks KDA & Vcoc for suggesting stronger projectiles coming before weaker ones + reverseSortSublist(A, BinarySearchElement(A, 206)); // arrows + reverseSortSublist(A, BinarySearchElement(A, 207)); // stars + reverseSortSublist(A, BinarySearchElement(A, 233)); // bullets + } + } intersect.add(0); for(int ind = 1; ind < A.size(); ind++) { diff --git a/src/net/server/channel/handlers/MesoDropHandler.java b/src/net/server/channel/handlers/MesoDropHandler.java index 878303ccdc..bed3e23e7b 100644 --- a/src/net/server/channel/handlers/MesoDropHandler.java +++ b/src/net/server/channel/handlers/MesoDropHandler.java @@ -47,7 +47,12 @@ public final class MesoDropHandler extends AbstractMaplePacketHandler { int meso = slea.readInt(); if (meso <= player.getMeso() && meso > 9 && meso < 50001) { player.gainMeso(-meso, false, true, false); - player.getMap().spawnMesoDrop(meso, player.getPosition(), player, player, true, (byte) 2); + + if (player.attemptCatchFish(meso)) { + player.getMap().disappearingMesoDrop(meso, player, player, player.getPosition()); + } else { + player.getMap().spawnMesoDrop(meso, player.getPosition(), player, player, true, (byte) 2); + } } } } \ No newline at end of file diff --git a/src/net/server/channel/handlers/MessengerHandler.java b/src/net/server/channel/handlers/MessengerHandler.java index 941b72f445..1d652a77a0 100644 --- a/src/net/server/channel/handlers/MessengerHandler.java +++ b/src/net/server/channel/handlers/MessengerHandler.java @@ -24,84 +24,104 @@ package net.server.channel.handlers; import client.MapleCharacter; import client.MapleClient; import net.AbstractMaplePacketHandler; +import net.server.coordinator.MapleInviteCoordinator; +import net.server.coordinator.MapleInviteCoordinator.InviteResult; +import net.server.coordinator.MapleInviteCoordinator.InviteType; import net.server.world.MapleMessenger; import net.server.world.MapleMessengerCharacter; import net.server.world.World; import tools.MaplePacketCreator; +import tools.Pair; import tools.data.input.SeekableLittleEndianAccessor; public final class MessengerHandler extends AbstractMaplePacketHandler { @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - String input; - byte mode = slea.readByte(); - MapleCharacter player = c.getPlayer(); - World world = c.getWorldServer(); - MapleMessenger messenger = player.getMessenger(); - switch (mode) { - case 0x00: - if (messenger == null) { + c.tryacquireClient(); + try { + String input; + byte mode = slea.readByte(); + MapleCharacter player = c.getPlayer(); + World world = c.getWorldServer(); + MapleMessenger messenger = player.getMessenger(); + switch (mode) { + case 0x00: int messengerid = slea.readInt(); - if (messengerid == 0) { - MapleMessengerCharacter messengerplayer = new MapleMessengerCharacter(player, 0); - messenger = world.createMessenger(messengerplayer); - player.setMessenger(messenger); - player.setMessengerPosition(0); - } else { - messenger = world.getMessenger(messengerid); - int position = messenger.getLowestPosition(); - MapleMessengerCharacter messengerplayer = new MapleMessengerCharacter(player, position); - if (messenger.getMembers().size() < 3) { + if (messenger == null) { + if (messengerid == 0) { + MapleInviteCoordinator.removeInvite(InviteType.MESSENGER, player.getId()); + + MapleMessengerCharacter messengerplayer = new MapleMessengerCharacter(player, 0); + messenger = world.createMessenger(messengerplayer); player.setMessenger(messenger); - player.setMessengerPosition(position); - world.joinMessenger(messenger.getId(), messengerplayer, player.getName(), messengerplayer.getChannel()); - } - } - } - break; - case 0x02: - player.closePlayerMessenger(); - break; - case 0x03: - if (messenger.getMembers().size() < 3) { - input = slea.readMapleAsciiString(); - MapleCharacter target = c.getChannelServer().getPlayerStorage().getCharacterByName(input); - if (target != null) { - if (target.getMessenger() == null) { - target.getClient().announce(MaplePacketCreator.messengerInvite(c.getPlayer().getName(), messenger.getId())); - c.announce(MaplePacketCreator.messengerNote(input, 4, 1)); + player.setMessengerPosition(0); } else { - c.announce(MaplePacketCreator.messengerChat(player.getName() + " : " + input + " is already using Maple Messenger")); + messenger = world.getMessenger(messengerid); + if (messenger != null) { + Pair inviteRes = MapleInviteCoordinator.answerInvite(InviteType.MESSENGER, player.getId(), messengerid, true); + InviteResult res = inviteRes.getLeft(); + if (res == InviteResult.ACCEPTED) { + int position = messenger.getLowestPosition(); + MapleMessengerCharacter messengerplayer = new MapleMessengerCharacter(player, position); + if (messenger.getMembers().size() < 3) { + player.setMessenger(messenger); + player.setMessengerPosition(position); + world.joinMessenger(messenger.getId(), messengerplayer, player.getName(), messengerplayer.getChannel()); + } + } else { + player.message("Could not verify your Maple Messenger accept since the invitation rescinded."); + } + } } } else { - if (world.find(input) > -1) { - world.messengerInvite(c.getPlayer().getName(), messenger.getId(), input, c.getChannel()); + MapleInviteCoordinator.answerInvite(InviteType.MESSENGER, player.getId(), messengerid, false); + } + break; + case 0x02: + player.closePlayerMessenger(); + break; + case 0x03: + if (messenger == null) { + c.announce(MaplePacketCreator.messengerChat(player.getName() + " : This Maple Messenger is currently unavailable. Please quit this chat.")); + } else if (messenger.getMembers().size() < 3) { + input = slea.readMapleAsciiString(); + MapleCharacter target = c.getChannelServer().getPlayerStorage().getCharacterByName(input); + if (target != null) { + if (target.getMessenger() == null) { + if (MapleInviteCoordinator.createInvite(InviteType.MESSENGER, c.getPlayer(), messenger.getId(), target.getId())) { + target.getClient().announce(MaplePacketCreator.messengerInvite(c.getPlayer().getName(), messenger.getId())); + c.announce(MaplePacketCreator.messengerNote(input, 4, 1)); + } else { + c.announce(MaplePacketCreator.messengerChat(player.getName() + " : " + input + " is already managing a Maple Messenger invitation")); + } + } else { + c.announce(MaplePacketCreator.messengerChat(player.getName() + " : " + input + " is already using Maple Messenger")); + } } else { - c.announce(MaplePacketCreator.messengerNote(input, 4, 0)); + if (world.find(input) > -1) { + world.messengerInvite(c.getPlayer().getName(), messenger.getId(), input, c.getChannel()); + } else { + c.announce(MaplePacketCreator.messengerNote(input, 4, 0)); + } } + } else { + c.announce(MaplePacketCreator.messengerChat(player.getName() + " : You cannot have more than 3 people in the Maple Messenger")); } - } else { - c.announce(MaplePacketCreator.messengerChat(player.getName() + " : You cannot have more than 3 people in the Maple Messenger")); - } - break; - case 0x05: - String targeted = slea.readMapleAsciiString(); - MapleCharacter target = c.getChannelServer().getPlayerStorage().getCharacterByName(targeted); - if (target != null) { - if (target.getMessenger() != null) { - target.getClient().announce(MaplePacketCreator.messengerNote(player.getName(), 5, 0)); + break; + case 0x05: + String targeted = slea.readMapleAsciiString(); + world.declineChat(targeted, player); + break; + case 0x06: + if (messenger != null) { + MapleMessengerCharacter messengerplayer = new MapleMessengerCharacter(player, player.getMessengerPosition()); + input = slea.readMapleAsciiString(); + world.messengerChat(messenger, input, messengerplayer.getName()); } - } else { - world.declineChat(targeted, player.getName()); - } - break; - case 0x06: - if (messenger != null) { - MapleMessengerCharacter messengerplayer = new MapleMessengerCharacter(player, player.getMessengerPosition()); - input = slea.readMapleAsciiString(); - world.messengerChat(messenger, input, messengerplayer.getName()); - } - break; + break; + } + } finally { + c.releaseClient(); } } } diff --git a/src/net/server/channel/handlers/PartyOperationHandler.java b/src/net/server/channel/handlers/PartyOperationHandler.java index 95727de6c3..cf7530c9b9 100644 --- a/src/net/server/channel/handlers/PartyOperationHandler.java +++ b/src/net/server/channel/handlers/PartyOperationHandler.java @@ -31,9 +31,14 @@ import tools.data.input.SeekableLittleEndianAccessor; import client.MapleCharacter; import client.MapleClient; import constants.ServerConstants; -import java.util.List; +import net.server.coordinator.MapleInviteCoordinator; +import net.server.coordinator.MapleInviteCoordinator.InviteResult; +import net.server.coordinator.MapleInviteCoordinator.InviteType; import scripting.event.EventInstanceManager; import server.maps.MapleMap; +import tools.Pair; + +import java.util.List; public final class PartyOperationHandler extends AbstractMaplePacketHandler { @@ -101,26 +106,33 @@ public final class PartyOperationHandler extends AbstractMaplePacketHandler { } case 3: { // join int partyid = slea.readInt(); - if (party == null) { - party = world.getParty(partyid); - if (party != null) { - if (party.getMembers().size() < 6) { - partyplayer = new MaplePartyCharacter(player); - player.getMap().addPartyMember(player); + + Pair inviteRes = MapleInviteCoordinator.answerInvite(InviteType.PARTY, player.getId(), partyid, true); + InviteResult res = inviteRes.getLeft(); + if (res == InviteResult.ACCEPTED) { + if (party == null) { + party = world.getParty(partyid); + if (party != null) { + if (party.getMembers().size() < 6) { + partyplayer = new MaplePartyCharacter(player); + player.getMap().addPartyMember(player); - world.updateParty(party.getId(), PartyOperation.JOIN, partyplayer); - player.receivePartyMemberHP(); - player.updatePartyMemberHP(); - - player.partyOperationUpdate(party, null); + world.updateParty(party.getId(), PartyOperation.JOIN, partyplayer); + player.receivePartyMemberHP(); + player.updatePartyMemberHP(); + + player.partyOperationUpdate(party, null); + } else { + c.announce(MaplePacketCreator.partyStatusMessage(17)); + } } else { - c.announce(MaplePacketCreator.partyStatusMessage(17)); + c.announce(MaplePacketCreator.serverNotice(5, "The person you have invited to the party is already in one.")); } } else { - c.announce(MaplePacketCreator.serverNotice(5, "The person you have invited to the party is already in one.")); + c.announce(MaplePacketCreator.serverNotice(5, "You can't join the party as you are already in one.")); } } else { - c.announce(MaplePacketCreator.serverNotice(5, "You can't join the party as you are already in one.")); + c.announce(MaplePacketCreator.serverNotice(5, "You couldn't join the party due to an expired invitation request.")); } break; } @@ -154,7 +166,11 @@ public final class PartyOperationHandler extends AbstractMaplePacketHandler { c.announce(MaplePacketCreator.partyCreated(party, partyplayer.getId())); } if (party.getMembers().size() < 6) { - invited.getClient().announce(MaplePacketCreator.partyInvite(player)); + if (MapleInviteCoordinator.createInvite(InviteType.PARTY, player, party.getId(), invited.getId())) { + invited.getClient().announce(MaplePacketCreator.partyInvite(player)); + } else { + c.announce(MaplePacketCreator.partyStatusMessage(22, invited.getName())); + } } else { c.announce(MaplePacketCreator.partyStatusMessage(17)); } diff --git a/src/net/server/channel/handlers/PlayerInteractionHandler.java b/src/net/server/channel/handlers/PlayerInteractionHandler.java index 0f74a86b60..32a13008f0 100644 --- a/src/net/server/channel/handlers/PlayerInteractionHandler.java +++ b/src/net/server/channel/handlers/PlayerInteractionHandler.java @@ -36,7 +36,6 @@ import net.AbstractMaplePacketHandler; import server.MapleItemInformationProvider; import server.MapleTrade; import constants.GameConstants; -import java.sql.SQLException; import server.maps.FieldLimit; import server.maps.MapleHiredMerchant; import server.maps.MapleMapObject; @@ -48,6 +47,9 @@ import tools.FilePrinter; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; +import java.awt.Point; +import java.sql.SQLException; + /** * * @author Matze @@ -146,7 +148,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler { } byte createType = slea.readByte(); - if (createType == 3) {// trade + if (createType == 3) { // trade MapleTrade.startTrade(chr); } else if (createType == 1) { // omok mini game int status = establishMiniroomStatus(chr, true); @@ -234,7 +236,15 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler { chr.getClient().announce(MaplePacketCreator.getMiniRoomError(status)); return; } - + + try { + Point cpos = chr.getPosition(); + if (chr.getMap().findClosestWarpPortal(cpos).getPosition().distance(cpos) < 120.0) { + chr.getClient().announce(MaplePacketCreator.getMiniRoomError(10)); + return; + } + } catch (NullPointerException npe) {} + String desc = slea.readMapleAsciiString(); slea.skip(3); int itemId = slea.readInt(); @@ -259,20 +269,22 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler { } } } else if (mode == Action.INVITE.getCode()) { - int otherPlayer = slea.readInt(); - if (chr.getId() == chr.getMap().getCharacterById(otherPlayer).getId()) { + int otherCid = slea.readInt(); + MapleCharacter other = chr.getMap().getCharacterById(otherCid); + if (other == null || chr.getId() == other.getId()) { return; } - MapleTrade.inviteTrade(chr, chr.getMap().getCharacterById(otherPlayer)); + + MapleTrade.inviteTrade(chr, other); } else if (mode == Action.DECLINE.getCode()) { MapleTrade.declineTrade(chr); } else if (mode == Action.VISIT.getCode()) { if (chr.getTrade() != null && chr.getTrade().getPartner() != null) { if (!chr.getTrade().isFullTrade() && !chr.getTrade().getPartner().isFullTrade()) { - MapleTrade.visitTrade(chr, chr.getTrade().getPartner().getChr()); + MapleTrade.visitTrade(chr, chr.getTrade().getPartner().getChr()); } else { - chr.getClient().announce(MaplePacketCreator.getMiniRoomError(2)); - return; + chr.getClient().announce(MaplePacketCreator.getMiniRoomError(2)); + return; } } else { if (isTradeOpen(chr)) return; @@ -329,7 +341,7 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler { } } else if (mode == Action.EXIT.getCode()) { if (chr.getTrade() != null) { - MapleTrade.cancelTrade(chr); + MapleTrade.cancelTrade(chr, MapleTrade.TradeResult.PARTNER_CANCEL); } else { chr.closePlayerShop(); chr.closeMiniGame(); @@ -627,6 +639,10 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler { c.announce(MaplePacketCreator.updateHiredMerchant(merchant, chr)); + if (ServerConstants.USE_ENFORCE_MERCHANT_SAVE) { + chr.saveCharToDB(false); + } + try { merchant.saveItems(false); // thanks Masterrulax for realizing yet another dupe with merchants/Fredrick } catch (SQLException ex) { @@ -726,14 +742,14 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler { MapleHiredMerchant merchant = chr.getHiredMerchant(); if(merchant != null) { - if (merchant.getItems().isEmpty() && merchant.isOwner(chr)) { - merchant.closeShop(c, false); - chr.setHasMerchant(false); - } if (merchant.isOwner(chr)) { - merchant.clearMessages(); - merchant.setOpen(true); - merchant.getMap().broadcastMessage(MaplePacketCreator.updateHiredMerchantBox(merchant)); + if (merchant.getItems().isEmpty()) { + merchant.closeOwnerMerchant(chr); + } else { + merchant.clearMessages(); + merchant.setOpen(true); + merchant.getMap().broadcastMessage(MaplePacketCreator.updateHiredMerchantBox(merchant)); + } } } diff --git a/src/net/server/channel/handlers/SkillBookHandler.java b/src/net/server/channel/handlers/SkillBookHandler.java index 84318aad0a..919d9c5fa8 100644 --- a/src/net/server/channel/handlers/SkillBookHandler.java +++ b/src/net/server/channel/handlers/SkillBookHandler.java @@ -32,7 +32,6 @@ import net.AbstractMaplePacketHandler; import client.inventory.manipulator.MapleInventoryManipulator; import server.MapleItemInformationProvider; import tools.MaplePacketCreator; -import tools.Randomizer; import tools.data.input.SeekableLittleEndianAccessor; public final class SkillBookHandler extends AbstractMaplePacketHandler { @@ -75,7 +74,9 @@ public final class SkillBookHandler extends AbstractMaplePacketHandler { } else { canuse = false; } - player.getClient().announce(MaplePacketCreator.skillBookSuccess(player, skill, maxlevel, canuse, success)); + + // thanks Vcoc for noting skill book result not showing for all in area + player.getMap().broadcastMessage(MaplePacketCreator.skillBookResult(player, skill, maxlevel, canuse, success)); } } } diff --git a/src/net/server/channel/handlers/SummonDamageHandler.java b/src/net/server/channel/handlers/SummonDamageHandler.java index 9c5c8ebc4b..5604eee16a 100644 --- a/src/net/server/channel/handlers/SummonDamageHandler.java +++ b/src/net/server/channel/handlers/SummonDamageHandler.java @@ -84,6 +84,9 @@ public final class SummonDamageHandler extends AbstractDealDamageHandler { allDamage.add(new SummonAttackEntry(monsterOid, damage)); } player.getMap().broadcastMessage(player, MaplePacketCreator.summonAttack(player.getId(), summon.getObjectId(), direction, allDamage), summon.getPosition()); + if (player.getMap().isOwnershipRestricted(player)) { + return; + } for (SummonAttackEntry attackEntry : allDamage) { int damage = attackEntry.getDamage(); MapleMonster target = player.getMap().getMonsterByOid(attackEntry.getMonsterOid()); diff --git a/src/net/server/channel/handlers/UseCashItemHandler.java b/src/net/server/channel/handlers/UseCashItemHandler.java index 5b5cb13540..00bd0c5b20 100644 --- a/src/net/server/channel/handlers/UseCashItemHandler.java +++ b/src/net/server/channel/handlers/UseCashItemHandler.java @@ -400,9 +400,16 @@ public final class UseCashItemHandler extends AbstractMaplePacketHandler { } else if (itemType == 533) { NPCScriptManager.getInstance().start(c, 9010009, null); } else if (itemType == 537) { + if (GameConstants.isFreeMarketRoom(player.getMapId())) { + player.dropMessage(5, "You cannot use the chalkboard here."); + player.getClient().announce(MaplePacketCreator.enableActions()); + return; + } + player.setChalkboard(slea.readMapleAsciiString()); player.getMap().broadcastMessage(MaplePacketCreator.useChalkboard(player, false)); player.getClient().announce(MaplePacketCreator.enableActions()); + remove(c, position, itemId); } else if (itemType == 539) { List strLines = new LinkedList<>(); for (int i = 0; i < 4; i++) { diff --git a/src/net/server/coordinator/MapleInviteCoordinator.java b/src/net/server/coordinator/MapleInviteCoordinator.java new file mode 100644 index 0000000000..ac5bf18946 --- /dev/null +++ b/src/net/server/coordinator/MapleInviteCoordinator.java @@ -0,0 +1,149 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +package net.server.coordinator; + +import client.MapleCharacter; +import tools.Pair; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; + +/** + * + * @author Ronan + */ +public class MapleInviteCoordinator { + + public enum InviteResult { + ACCEPTED, + DENIED, + NOT_FOUND; + } + + public enum InviteType { + //BUDDY, (not needed) + //FAMILY, (not implemented) + MESSENGER, + TRADE, + PARTY, + GUILD, + ALLIANCE; + + final ConcurrentHashMap invites; + final ConcurrentHashMap inviteFrom; + final ConcurrentHashMap inviteTimeouts; + + private InviteType() { + invites = new ConcurrentHashMap<>(); + inviteTimeouts = new ConcurrentHashMap<>(); + inviteFrom = new ConcurrentHashMap<>(); + } + + private Map getRequestsTable() { + return invites; + } + + private Map getRequestsTimeoutTable() { + return inviteTimeouts; + } + + private MapleCharacter removeRequest(Integer target) { + invites.remove(target); + MapleCharacter from = inviteFrom.remove(target); + inviteTimeouts.remove(target); + + return from; + } + + private boolean addRequest(MapleCharacter from, Object referenceFrom, int targetCid) { + Object v = invites.putIfAbsent(targetCid, referenceFrom); + if (v != null) { // there was already an entry + return false; + } + + inviteFrom.put(targetCid, from); + inviteTimeouts.put(targetCid, 0); + + return true; + } + + private boolean hasRequest(int targetCid) { + return invites.containsKey(targetCid); + } + } + + // note: referenceFrom is a specific value that represents the "common association" created between the sender/recver parties + public static boolean createInvite(InviteType type, MapleCharacter from, Object referenceFrom, int targetCid) { + return type.addRequest(from, referenceFrom, targetCid); + } + + public static boolean hasInvite(InviteType type, int targetCid) { + return type.hasRequest(targetCid); + } + + public static Pair answerInvite(InviteType type, int targetCid, Object referenceFrom, boolean answer) { + Map table = type.getRequestsTable(); + + MapleCharacter from = null; + InviteResult result = InviteResult.NOT_FOUND; + + Object reference = table.get(targetCid); + if (referenceFrom.equals(reference)) { + from = type.removeRequest(targetCid); + if (from != null && !from.isLoggedinWorld()) from = null; + + result = answer ? InviteResult.ACCEPTED : InviteResult.DENIED; + } + + return new Pair<>(result, from); + } + + public static void removeInvite(InviteType type, int targetCid) { + type.removeRequest(targetCid); + } + + public static void removePlayerIncomingInvites(int cid) { + for (InviteType it : InviteType.values()) { + it.removeRequest(cid); + } + } + + public static void runTimeoutSchedule() { + for (InviteType it : InviteType.values()) { + Map timeoutTable = it.getRequestsTimeoutTable(); + + if (!timeoutTable.isEmpty()) { + Set> entrySet = new HashSet<>(timeoutTable.entrySet()); + for (Entry e : entrySet) { + int eVal = e.getValue(); + + if (eVal > 5) { // 3min to expire + it.removeRequest(e.getKey()); + } else { + timeoutTable.put(e.getKey(), eVal + 1); + } + } + } + } + } +} diff --git a/src/net/server/coordinator/MapleSessionCoordinator.java b/src/net/server/coordinator/MapleSessionCoordinator.java index cc14b57c7d..234f684512 100644 --- a/src/net/server/coordinator/MapleSessionCoordinator.java +++ b/src/net/server/coordinator/MapleSessionCoordinator.java @@ -34,6 +34,8 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.HashMap; @@ -215,7 +217,7 @@ public class MapleSessionCoordinator { } private Lock getCoodinatorLock(String remoteHost) { - return poolLock.get(remoteHost.hashCode() % 100); + return poolLock.get(Math.abs(remoteHost.hashCode()) % 100); } private static String getRemoteIp(IoSession session) { @@ -318,7 +320,12 @@ public class MapleSessionCoordinator { MapleClient client = getSessionClient(session); if (client != null) { - onlineClients.remove(client.getAccID()); + MapleClient loggedClient = onlineClients.get(client.getAccID()); + + // do not remove an online game session here, only login session + if (loggedClient != null && loggedClient.getSessionId() == client.getSessionId()) { + onlineClients.remove(client.getAccID()); + } } } } @@ -466,15 +473,24 @@ public class MapleSessionCoordinator { } public void closeSession(IoSession session, Boolean immediately) { - String hwid = (String) session.removeAttribute(MapleClient.CLIENT_HWID); + String hwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID); // making sure to clean up calls to this function on login phase onlineRemoteHwids.remove(hwid); - hwid = (String) session.removeAttribute(MapleClient.CLIENT_NIBBLEHWID); // making sure to clean up calls to this function on login phase + hwid = (String) session.removeAttribute(MapleClient.CLIENT_HWID); onlineRemoteHwids.remove(hwid); MapleClient client = getSessionClient(session); if (client != null) { - onlineClients.remove(client.getAccID()); + if (hwid != null) { // is a game session + onlineClients.remove(client.getAccID()); + } else { + MapleClient loggedClient = onlineClients.get(client.getAccID()); + + // do not remove an online game session here, only login session + if (loggedClient != null && loggedClient.getSessionId() == client.getSessionId()) { + onlineClients.remove(client.getAccID()); + } + } } if (immediately != null) { @@ -523,4 +539,94 @@ public class MapleSessionCoordinator { public void runUpdateLoginHistory() { loginStorage.updateLoginHistory(); } + + public void printSessionTrace() { + if (!onlineClients.isEmpty()) { + List> elist = new ArrayList<>(onlineClients.entrySet()); + Collections.sort(elist, new Comparator>() { + @Override + public int compare(Entry e1, Entry e2) { + return e1.getKey().compareTo(e2.getKey()); + } + }); + + System.out.println("Current online clients: "); + for (Entry e : elist) { + System.out.println(" " + e.getKey()); + } + } + + if (!onlineRemoteHwids.isEmpty()) { + List slist = new ArrayList<>(onlineRemoteHwids); + Collections.sort(slist); + + System.out.println("Current online HWIDs: "); + for (String s : slist) { + System.out.println(" " + s); + } + } + + if (!loginRemoteHosts.isEmpty()) { + List>> elist = new ArrayList<>(loginRemoteHosts.entrySet()); + + Collections.sort(elist, new Comparator>>() { + @Override + public int compare(Entry> e1, Entry> e2) { + return e1.getKey().compareTo(e2.getKey()); + } + }); + + System.out.println("Current login sessions: "); + for (Entry> e : elist) { + System.out.println(" " + e.getKey() + ", size: " + e.getValue().size()); + } + } + } + + public void printSessionTrace(MapleClient c) { + String str = "Opened server sessions:\r\n\r\n"; + + if (!onlineClients.isEmpty()) { + List> elist = new ArrayList<>(onlineClients.entrySet()); + Collections.sort(elist, new Comparator>() { + @Override + public int compare(Entry e1, Entry e2) { + return e1.getKey().compareTo(e2.getKey()); + } + }); + + str += ("Current online clients:\r\n"); + for (Entry e : elist) { + str += (" " + e.getKey() + "\r\n"); + } + } + + if (!onlineRemoteHwids.isEmpty()) { + List slist = new ArrayList<>(onlineRemoteHwids); + Collections.sort(slist); + + str += ("Current online HWIDs:\r\n"); + for (String s : slist) { + str += (" " + s + "\r\n"); + } + } + + if (!loginRemoteHosts.isEmpty()) { + List>> elist = new ArrayList<>(loginRemoteHosts.entrySet()); + + Collections.sort(elist, new Comparator>>() { + @Override + public int compare(Entry> e1, Entry> e2) { + return e1.getKey().compareTo(e2.getKey()); + } + }); + + str += ("Current login sessions:\r\n"); + for (Entry> e : elist) { + str += (" " + e.getKey() + ", IP: " + e.getValue() + "\r\n"); + } + } + + c.getAbstractPlayerInteraction().npcTalk(2140000, str); + } } diff --git a/src/net/server/guild/MapleAlliance.java b/src/net/server/guild/MapleAlliance.java index 3320aa22cd..91c4a78517 100644 --- a/src/net/server/guild/MapleAlliance.java +++ b/src/net/server/guild/MapleAlliance.java @@ -29,11 +29,16 @@ import java.util.LinkedList; import java.util.List; import client.MapleCharacter; +import client.MapleClient; import net.server.Server; +import net.server.coordinator.MapleInviteCoordinator; +import net.server.coordinator.MapleInviteCoordinator.InviteResult; +import net.server.coordinator.MapleInviteCoordinator.InviteType; import net.server.world.MapleParty; import net.server.world.MaplePartyCharacter; import tools.DatabaseConnection; import tools.MaplePacketCreator; +import tools.Pair; /** * @@ -465,4 +470,50 @@ public class MapleAlliance { public void broadcastMessage(byte[] packet) { Server.getInstance().allianceMessage(allianceId, packet, -1, -1); } + + public static void sendInvitation(MapleClient c, String targetGuildName, int allianceId) { + MapleGuild mg = Server.getInstance().getGuildByName(targetGuildName); + if(mg == null) { + c.getPlayer().dropMessage(5, "The entered guild does not exist."); + } else { + if (mg.getAllianceId() > 0) { + c.getPlayer().dropMessage(5, "The entered guild is already registered on a guild alliance."); + } else { + MapleCharacter victim = mg.getMGC(mg.getLeaderId()).getCharacter(); + if (victim == null) { + c.getPlayer().dropMessage(5, "The master of the guild that you offered an invitation is currently not online."); + } else { + if (MapleInviteCoordinator.createInvite(InviteType.ALLIANCE, c.getPlayer(), allianceId, victim.getId())) { + victim.getClient().announce(MaplePacketCreator.allianceInvite(allianceId, c.getPlayer())); + } else { + c.getPlayer().dropMessage(5, "The master of the guild that you offered an invitation is currently managing another invite."); + } + } + } + } + } + + public static boolean answerInvitation(int targetId, String targetGuildName, int allianceId, boolean answer) { + Pair res = MapleInviteCoordinator.answerInvite(InviteType.ALLIANCE, targetId, allianceId, answer); + + String msg; + MapleCharacter sender = res.getRight(); + switch (res.getLeft()) { + case ACCEPTED: + return true; + + case DENIED: + msg = "[" + targetGuildName + "] guild has denied your guild alliance invitation."; + break; + + default: + msg = "The guild alliance request has not been accepted, since the invitation expired."; + } + + if (sender != null) { + sender.dropMessage(5, msg); + } + + return false; + } } diff --git a/src/net/server/guild/MapleGuild.java b/src/net/server/guild/MapleGuild.java index 2969cb7ce4..8fccd15f41 100644 --- a/src/net/server/guild/MapleGuild.java +++ b/src/net/server/guild/MapleGuild.java @@ -44,7 +44,11 @@ import net.server.Server; import net.server.channel.Channel; import tools.DatabaseConnection; import tools.MaplePacketCreator; +import tools.Pair; import net.server.audit.locks.MonitoredLockType; +import net.server.coordinator.MapleInviteCoordinator; +import net.server.coordinator.MapleInviteCoordinator.InviteType; +import net.server.coordinator.MapleInviteCoordinator.InviteResult; public class MapleGuild { @@ -702,7 +706,7 @@ public class MapleGuild { this.guildMessage(MaplePacketCreator.updateGP(this.id, this.gp)); } - public static MapleGuildResponse sendInvite(MapleClient c, String targetName) { + public static MapleGuildResponse sendInvitation(MapleClient c, String targetName) { MapleCharacter mc = c.getChannelServer().getPlayerStorage().getCharacterByName(targetName); if (mc == null) { return MapleGuildResponse.NOT_IN_CHANNEL; @@ -710,8 +714,37 @@ public class MapleGuild { if (mc.getGuildId() > 0) { return MapleGuildResponse.ALREADY_IN_GUILD; } - mc.getClient().announce(MaplePacketCreator.guildInvite(c.getPlayer().getGuildId(), c.getPlayer().getName())); - return null; + + MapleCharacter sender = c.getPlayer(); + if (MapleInviteCoordinator.createInvite(InviteType.GUILD, sender, sender.getGuildId(), mc.getId())) { + mc.getClient().announce(MaplePacketCreator.guildInvite(sender.getGuildId(), sender.getName())); + return null; + } else { + return MapleGuildResponse.MANAGING_INVITE; + } + } + + public static boolean answerInvitation(int targetId, String targetName, int guildId, boolean answer) { + Pair res = MapleInviteCoordinator.answerInvite(InviteType.GUILD, targetId, guildId, answer); + + MapleGuildResponse mgr; + MapleCharacter sender = res.getRight(); + switch (res.getLeft()) { + case ACCEPTED: + return true; + + case DENIED: + mgr = MapleGuildResponse.DENIED_INVITE; + break; + + default: + mgr = MapleGuildResponse.NOT_FOUND_INVITE; + } + + if (mgr != null && sender != null) { + sender.announce(mgr.getPacket(targetName)); + } + return false; } public static void displayGuildRanks(MapleClient c, int npcid) { diff --git a/src/net/server/guild/MapleGuildResponse.java b/src/net/server/guild/MapleGuildResponse.java index 9ed3b6090d..b8c04e1832 100644 --- a/src/net/server/guild/MapleGuildResponse.java +++ b/src/net/server/guild/MapleGuildResponse.java @@ -26,14 +26,22 @@ import tools.MaplePacketCreator; public enum MapleGuildResponse { NOT_IN_CHANNEL(0x2a), ALREADY_IN_GUILD(0x28), - NOT_IN_GUILD(0x2d); + NOT_IN_GUILD(0x2d), + NOT_FOUND_INVITE(0x2e), + MANAGING_INVITE(0x36), + DENIED_INVITE(0x37); + private int value; private MapleGuildResponse(int val) { value = val; } - public final byte[] getPacket() { - return MaplePacketCreator.genericGuildMessage((byte) value); + public final byte[] getPacket(String targetName) { + if (value >= MANAGING_INVITE.value) { + return MaplePacketCreator.responseGuildMessage((byte) value, targetName); + } else { + return MaplePacketCreator.genericGuildMessage((byte) value); + } } } diff --git a/src/net/server/handlers/login/CreateCharHandler.java b/src/net/server/handlers/login/CreateCharHandler.java index a6024c0dc0..3c6b4f7226 100644 --- a/src/net/server/handlers/login/CreateCharHandler.java +++ b/src/net/server/handlers/login/CreateCharHandler.java @@ -28,9 +28,13 @@ import tools.FilePrinter; import tools.MaplePacketCreator; import tools.data.input.SeekableLittleEndianAccessor; -public final class CreateCharHandler extends AbstractMaplePacketHandler { +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; - private static int[] IDs = { +public final class CreateCharHandler extends AbstractMaplePacketHandler { + + private final static Set IDs = new HashSet<>(Arrays.asList(new Integer[]{ 1302000, 1312004, 1322005, 1442079,// weapons 1040002, 1040006, 1040010, 1041002, 1041006, 1041010, 1041011, 1042167,// bottom 1060002, 1060006, 1061002, 1061008, 1062115, // top @@ -38,15 +42,10 @@ public final class CreateCharHandler extends AbstractMaplePacketHandler { 30000, 30010,30020, 30030, 31000, 31040, 31050,// hair 20000, 20001, 20002, 21000, 21001, 21002, 21201, 20401, 20402, 21700, 20100 //face //#NeverTrustStevenCode - }; + })); - private static boolean isLegal(int toCompare) { - for (int i = 0; i < IDs.length; i++) { - if (IDs[i] == toCompare) { - return true; - } - } - return false; + private static boolean isLegal(Integer toCompare) { + return IDs.contains(toCompare); } diff --git a/src/net/server/handlers/login/LoginPasswordHandler.java b/src/net/server/handlers/login/LoginPasswordHandler.java index 6940088b71..edfb2b65e9 100644 --- a/src/net/server/handlers/login/LoginPasswordHandler.java +++ b/src/net/server/handlers/login/LoginPasswordHandler.java @@ -38,8 +38,10 @@ import client.MapleClient; import java.sql.ResultSet; import java.sql.Statement; import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import org.apache.mina.core.session.IoSession; public final class LoginPasswordHandler implements MaplePacketHandler { @@ -54,9 +56,18 @@ public final class LoginPasswordHandler implements MaplePacketHandler { return HexTool.toString(digester.digest()).replace(" ", "").toLowerCase(); } + private static String getRemoteIp(IoSession session) { + return ((InetSocketAddress) session.getRemoteAddress()).getAddress().getHostAddress(); + } + @Override public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) { - + String remoteHost = getRemoteIp(c.getSession()); + if (remoteHost.startsWith("127.") && !ServerConstants.HOST.startsWith("127.")) { + c.announce(MaplePacketCreator.getLoginFailed(13)); // cannot login as localhost if it's not a test server + return; + } + String login = slea.readMapleAsciiString(); String pwd = slea.readMapleAsciiString(); c.setAccountName(login); diff --git a/src/net/server/worker/FishingWorker.java b/src/net/server/worker/FishingWorker.java new file mode 100644 index 0000000000..56eeb5fe46 --- /dev/null +++ b/src/net/server/worker/FishingWorker.java @@ -0,0 +1,37 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +package net.server.worker; + +import net.server.world.World; + +/** + * @author Ronan + */ +public class FishingWorker extends BaseWorker implements Runnable { + + @Override + public void run() { + wserv.runCheckFishingSchedule(); + } + + public FishingWorker(World world) { + super(world); + } +} diff --git a/src/net/server/worker/FredrickWorker.java b/src/net/server/worker/FredrickWorker.java new file mode 100644 index 0000000000..2845fb80ab --- /dev/null +++ b/src/net/server/worker/FredrickWorker.java @@ -0,0 +1,33 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +package net.server.worker; + +import client.processor.FredrickProcessor; + +/** + * @author Ronan + */ +public class FredrickWorker implements Runnable { + + @Override + public void run() { + FredrickProcessor.runFredrickSchedule(); + } +} diff --git a/src/net/server/worker/InvitationWorker.java b/src/net/server/worker/InvitationWorker.java new file mode 100644 index 0000000000..87a171bef4 --- /dev/null +++ b/src/net/server/worker/InvitationWorker.java @@ -0,0 +1,33 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +package net.server.worker; + +import net.server.coordinator.MapleInviteCoordinator; + +/** + * @author Ronan + */ +public class InvitationWorker implements Runnable { + + @Override + public void run() { + MapleInviteCoordinator.runTimeoutSchedule(); + } +} diff --git a/src/net/server/worker/MapOwnershipWorker.java b/src/net/server/worker/MapOwnershipWorker.java new file mode 100644 index 0000000000..adc201de26 --- /dev/null +++ b/src/net/server/worker/MapOwnershipWorker.java @@ -0,0 +1,40 @@ +/* + This file is part of the HeavenMS MapleStory Server + Copyleft (L) 2016 - 2018 RonanLana + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation version 3 as published by + the Free Software Foundation. You may not use, modify or distribute + this program under any other version of the GNU Affero General Public + License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ +package net.server.worker; + +import net.server.world.World; +import net.server.channel.Channel; + +/** + * @author Ronan + */ +public class MapOwnershipWorker extends BaseWorker implements Runnable { + + @Override + public void run() { + for (Channel ch : wserv.getChannels()) { + ch.runCheckOwnedMapsSchedule(); + } + } + + public MapOwnershipWorker(World world) { + super(world); + } +} diff --git a/src/net/server/world/World.java b/src/net/server/world/World.java index 3e73d53f47..062282a6a1 100644 --- a/src/net/server/world/World.java +++ b/src/net/server/world/World.java @@ -55,6 +55,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import java.util.Set; import java.util.HashSet; import java.util.PriorityQueue; +import java.util.WeakHashMap; import java.util.concurrent.ScheduledFuture; import scripting.event.EventInstanceManager; @@ -65,7 +66,9 @@ import server.maps.MaplePlayerShop; import server.maps.MaplePlayerShopItem; import server.maps.AbstractMapleMapObject; import net.server.worker.CharacterAutosaverWorker; +import net.server.worker.FishingWorker; import net.server.worker.HiredMerchantWorker; +import net.server.worker.MapOwnershipWorker; import net.server.worker.MountTirednessWorker; import net.server.worker.PetFullnessWorker; import net.server.worker.ServerMessageWorker; @@ -86,6 +89,10 @@ import net.server.audit.locks.MonitoredLockType; import net.server.audit.locks.MonitoredReentrantLock; import net.server.audit.locks.MonitoredReentrantReadWriteLock; import net.server.audit.locks.factory.MonitoredReentrantLockFactory; +import net.server.coordinator.MapleInviteCoordinator; +import net.server.coordinator.MapleInviteCoordinator.InviteResult; +import net.server.coordinator.MapleInviteCoordinator.InviteType; +import tools.packets.Fishing; /** * @@ -94,7 +101,7 @@ import net.server.audit.locks.factory.MonitoredReentrantLockFactory; */ public class World { - private int id, flag, exprate, droprate, bossdroprate, mesorate, questrate, travelrate; + private int id, flag, exprate, droprate, bossdroprate, mesorate, questrate, travelrate, fishingrate; private String eventmsg; private List channels = new ArrayList<>(); private Map pnpcStep = new HashMap<>(); @@ -155,10 +162,14 @@ public class World { private ScheduledFuture timedMapObjectsSchedule; private MonitoredReentrantLock timedMapObjectLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.WORLD_MAPOBJS, true); + private Map fishingAttempters = Collections.synchronizedMap(new WeakHashMap()); + private ScheduledFuture charactersSchedule; private ScheduledFuture marriagesSchedule; + private ScheduledFuture mapOwnershipSchedule; + private ScheduledFuture fishingSchedule; - public World(int world, int flag, String eventmsg, int exprate, int droprate, int bossdroprate, int mesorate, int questrate, int travelrate) { + public World(int world, int flag, String eventmsg, int exprate, int droprate, int bossdroprate, int mesorate, int questrate, int travelrate, int fishingrate) { this.id = world; this.flag = flag; this.eventmsg = eventmsg; @@ -168,7 +179,8 @@ public class World { this.mesorate = mesorate; this.questrate = questrate; this.travelrate = travelrate; - runningPartyId.set(1); + this.fishingrate = fishingrate; + runningPartyId.set(1000000001); // partyid must not clash with charid to solve update item looting issues, found thanks to Vcoc runningMessengerId.set(1); petUpdate = Server.getInstance().getCurrentTime(); @@ -186,6 +198,8 @@ public class World { 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); + mapOwnershipSchedule = tman.register(new MapOwnershipWorker(this), 20 * 1000, 20 * 1000); + fishingSchedule = tman.register(new FishingWorker(this), 10 * 1000, 10 * 1000); } @@ -362,14 +376,22 @@ public class World { return travelrate; } - public void setTravelRate(int quest) { - this.travelrate = quest; + public void setTravelRate(int travel) { + this.travelrate = travel; } public int getTransportationTime(int travelTime) { return (int) Math.ceil(travelTime / travelrate); } + public int getFishingRate() { + return fishingrate; + } + + public void setFishingRate(int quest) { + this.fishingrate = quest; + } + public void loadAccountCharactersView(Integer accountId, List chars) { SortedMap charsMap = new TreeMap<>(); for(MapleCharacter chr : chars) { @@ -985,14 +1007,23 @@ public class World { public void messengerInvite(String sender, int messengerid, String target, int fromchannel) { if (isConnected(target)) { - MapleMessenger messenger = getPlayerStorage().getCharacterByName(target).getMessenger(); - if (messenger == null) { - getPlayerStorage().getCharacterByName(target).getClient().announce(MaplePacketCreator.messengerInvite(sender, messengerid)); - MapleCharacter from = getChannel(fromchannel).getPlayerStorage().getCharacterByName(sender); - from.getClient().announce(MaplePacketCreator.messengerNote(target, 4, 1)); - } else { - MapleCharacter from = getChannel(fromchannel).getPlayerStorage().getCharacterByName(sender); - from.getClient().announce(MaplePacketCreator.messengerChat(sender + " : " + target + " is already using Maple Messenger")); + MapleCharacter targetChr = getPlayerStorage().getCharacterByName(target); + if (targetChr != null) { + MapleMessenger messenger = targetChr.getMessenger(); + if (messenger == null) { + MapleCharacter from = getChannel(fromchannel).getPlayerStorage().getCharacterByName(sender); + if (from != null) { + if (MapleInviteCoordinator.createInvite(InviteType.MESSENGER, from, messengerid, targetChr.getId())) { + targetChr.getClient().announce(MaplePacketCreator.messengerInvite(sender, messengerid)); + from.getClient().announce(MaplePacketCreator.messengerNote(target, 4, 1)); + } else { + from.announce(MaplePacketCreator.messengerChat(sender + " : " + target + " is already managing a Maple Messenger invitation")); + } + } + } else { + MapleCharacter from = getChannel(fromchannel).getPlayerStorage().getCharacterByName(sender); + from.getClient().announce(MaplePacketCreator.messengerChat(sender + " : " + target + " is already using Maple Messenger")); + } } } } @@ -1043,11 +1074,13 @@ public class World { } } - public void declineChat(String target, String namefrom) { - if (isConnected(target)) { - MapleCharacter chr = getPlayerStorage().getCharacterByName(target); - if (chr != null && chr.getMessenger() != null) { - chr.getClient().announce(MaplePacketCreator.messengerNote(namefrom, 5, 0)); + public void declineChat(String sender, MapleCharacter player) { + if (isConnected(sender)) { + MapleCharacter senderChr = getPlayerStorage().getCharacterByName(sender); + if (senderChr != null && senderChr.getMessenger() != null) { + if (MapleInviteCoordinator.answerInvite(InviteType.MESSENGER, player.getId(), senderChr.getMessenger().getId(), false).getLeft() == InviteResult.DENIED) { + senderChr.getClient().announce(MaplePacketCreator.messengerNote(player.getName(), 5, 0)); + } } } } @@ -1890,6 +1923,44 @@ public class World { } } + public boolean registerFisherPlayer(MapleCharacter chr, int baitLevel) { + synchronized (fishingAttempters) { + if (fishingAttempters.containsKey(chr)) { + return false; + } + + fishingAttempters.put(chr, baitLevel); + return true; + } + } + + public int unregisterFisherPlayer(MapleCharacter chr) { + Integer baitLevel = fishingAttempters.remove(chr); + if (baitLevel != null) { + return baitLevel; + } else { + return 0; + } + } + + public void runCheckFishingSchedule() { + double[] fishingLikelihoods = Fishing.fetchFishingLikelihood(); + double yearLikelihood = fishingLikelihoods[0], timeLikelihood = fishingLikelihoods[1]; + + if (!fishingAttempters.isEmpty()) { + List fishingAttemptersList; + + synchronized (fishingAttempters) { + fishingAttemptersList = new ArrayList<>(fishingAttempters.keySet()); + } + + for (MapleCharacter chr : fishingAttemptersList) { + int baitLevel = unregisterFisherPlayer(chr); + Fishing.doFishing(chr, baitLevel, yearLikelihood, timeLikelihood); + } + } + } + private void clearWorldData() { List pList; partyLock.lock(); @@ -1966,6 +2037,16 @@ public class World { marriagesSchedule = null; } + if(mapOwnershipSchedule != null) { + mapOwnershipSchedule.cancel(false); + mapOwnershipSchedule = null; + } + + if(fishingSchedule != null) { + fishingSchedule.cancel(false); + fishingSchedule = null; + } + players.disconnectAll(); players = null; diff --git a/src/scripting/AbstractPlayerInteraction.java b/src/scripting/AbstractPlayerInteraction.java index da8d4d6b31..e314184218 100644 --- a/src/scripting/AbstractPlayerInteraction.java +++ b/src/scripting/AbstractPlayerInteraction.java @@ -848,18 +848,18 @@ public class AbstractPlayerInteraction { } public void teachSkill(int skillid, byte level, byte masterLevel, long expiration, boolean force) { - Skill skill = SkillFactory.getSkill(skillid); - - if (!force && level > -1) { + Skill skill = SkillFactory.getSkill(skillid); MapleCharacter.SkillEntry skillEntry = getPlayer().getSkills().get(skill); - if (skillEntry != null) { - getPlayer().changeSkillLevel(skill, (byte) Math.max(skillEntry.skillevel, level), Math.max(skillEntry.masterlevel, masterLevel), expiration == -1 ? -1 : Math.max(skillEntry.expiration, expiration)); - return; + if (!force && level > -1) { + getPlayer().changeSkillLevel(skill, (byte) Math.max(skillEntry.skillevel, level), Math.max(skillEntry.masterlevel, masterLevel), expiration == -1 ? -1 : Math.max(skillEntry.expiration, expiration)); + return; + } + } else if (GameConstants.isAranSkills(skillid)) { + c.announce(MaplePacketCreator.showInfo("Effect/BasicEff.img/AranGetSkill")); } - } - - getPlayer().changeSkillLevel(skill, level, masterLevel, expiration); + + getPlayer().changeSkillLevel(skill, level, masterLevel, expiration); } public void removeEquipFromSlot(short slot) { diff --git a/src/scripting/event/EventManager.java b/src/scripting/event/EventManager.java index d051f2586d..b856f461d1 100644 --- a/src/scripting/event/EventManager.java +++ b/src/scripting/event/EventManager.java @@ -21,6 +21,7 @@ */ package scripting.event; +import tools.exceptions.EventInstanceInProgressException; import java.util.Collection; import java.util.HashMap; import java.util.Map; diff --git a/src/scripting/npc/NPCConversationManager.java b/src/scripting/npc/NPCConversationManager.java index e5c7a41bf9..906e33c2fa 100644 --- a/src/scripting/npc/NPCConversationManager.java +++ b/src/scripting/npc/NPCConversationManager.java @@ -435,6 +435,8 @@ public class NPCConversationManager extends AbstractPlayerInteraction { Server.getInstance().allianceMessage(alliance.getId(), MaplePacketCreator.getGuildAlliances(alliance, c.getWorld()), -1, -1); Server.getInstance().allianceMessage(alliance.getId(), MaplePacketCreator.allianceNotice(alliance.getId(), alliance.getNotice()), -1, -1); + + c.announce(MaplePacketCreator.updateAllianceInfo(alliance, c.getWorld())); // thanks Vcoc for finding an alliance update to leader issue } public void disbandAlliance(MapleClient c, int allianceId) { diff --git a/src/server/MapleTrade.java b/src/server/MapleTrade.java index a477144ac2..13a096f8b2 100644 --- a/src/server/MapleTrade.java +++ b/src/server/MapleTrade.java @@ -36,14 +36,38 @@ import client.inventory.manipulator.MapleInventoryManipulator; import client.inventory.manipulator.MapleKarmaManipulator; import constants.GameConstants; import constants.ServerConstants; +import net.server.coordinator.MapleInviteCoordinator; +import net.server.coordinator.MapleInviteCoordinator.InviteResult; +import net.server.coordinator.MapleInviteCoordinator.InviteType; import tools.Pair; /** * * @author Matze - * @author Ronan - concurrency safety & check available slots + * @author Ronan - concurrency safety & check available slots & trade results */ public class MapleTrade { + + public enum TradeResult { + NO_RESPONSE(1), + PARTNER_CANCEL(2), + SUCCESSFUL(7), + UNSUCCESSFUL(8), + UNSUCCESSFUL_UNIQUE_ITEM_LIMIT(9), + UNSUCCESSFUL_ANOTHER_MAP(12), + UNSUCCESSFUL_DAMAGED_FILES(13); + + private final int res; + + private TradeResult(int res) { + this.res = res; + } + + private byte getValue() { + return (byte) res; + } + } + private MapleTrade partner = null; private List items = new ArrayList<>(); private List exchangeItems; @@ -88,6 +112,7 @@ public class MapleTrade { } private void completeTrade() { + byte result; boolean show = ServerConstants.USE_DEBUG; items.clear(); meso = 0; @@ -106,18 +131,21 @@ public class MapleTrade { } else { chr.dropMessage(1, "Transaction completed. You received " + GameConstants.numberWithCommas(exchangeMeso) + " mesos."); } + + result = TradeResult.NO_RESPONSE.getValue(); } else { - chr.dropMessage(1, "Transaction completed."); + result = TradeResult.SUCCESSFUL.getValue(); } exchangeMeso = 0; if (exchangeItems != null) { exchangeItems.clear(); } - chr.getClient().announce(MaplePacketCreator.getTradeCompletion(number)); + + chr.getClient().announce(MaplePacketCreator.getTradeResult(number, result)); } - private void cancel() { + private void cancel(byte result) { boolean show = ServerConstants.USE_DEBUG; for (Item item : items) { @@ -134,7 +162,8 @@ public class MapleTrade { if (exchangeItems != null) { exchangeItems.clear(); } - chr.getClient().announce(MaplePacketCreator.getTradeCancel(number)); + + chr.getClient().announce(MaplePacketCreator.getTradeResult(number, result)); } private boolean isLocked() { @@ -224,6 +253,15 @@ public class MapleTrade { return MapleInventory.checkSpotsAndOwnership(chr, tradeItems); } + private boolean fitsUniquesInInventory() { + List exchangeItemids = new LinkedList<>(); + for (Item item : exchangeItems) { + exchangeItemids.add(item.getItemId()); + } + + return chr.canHoldUniques(exchangeItemids); + } + private synchronized boolean checkTradeCompleteHandshake(boolean updateSelf) { MapleTrade self, other; @@ -259,32 +297,42 @@ public class MapleTrade { partner.fetchExchangedItems(); if (!local.fitsMeso()) { - cancelTrade(c); + cancelTrade(local.getChr(), TradeResult.UNSUCCESSFUL); c.message("There is not enough meso inventory space to complete the trade."); partner.getChr().message("Partner does not have enough meso inventory space to complete the trade."); return; } else if (!partner.fitsMeso()) { - cancelTrade(c); + cancelTrade(partner.getChr(), TradeResult.UNSUCCESSFUL); c.message("Partner does not have enough meso inventory space to complete the trade."); partner.getChr().message("There is not enough meso inventory space to complete the trade."); return; } if (!local.fitsInInventory()) { - cancelTrade(c); - c.message("There is not enough inventory space to complete the trade."); - partner.getChr().message("Partner does not have enough inventory space to complete the trade."); + if (local.fitsUniquesInInventory()) { + cancelTrade(local.getChr(), TradeResult.UNSUCCESSFUL); + c.message("There is not enough inventory space to complete the trade."); + partner.getChr().message("Partner does not have enough inventory space to complete the trade."); + } else { + cancelTrade(local.getChr(), TradeResult.UNSUCCESSFUL_UNIQUE_ITEM_LIMIT); + partner.getChr().message("Partner cannot hold more than one one-of-a-kind item at a time."); + } return; } else if (!partner.fitsInInventory()) { - cancelTrade(c); - c.message("Partner does not have enough inventory space to complete the trade."); - partner.getChr().message("There is not enough inventory space to complete the trade."); + if (partner.fitsUniquesInInventory()) { + cancelTrade(partner.getChr(), TradeResult.UNSUCCESSFUL); + c.message("Partner does not have enough inventory space to complete the trade."); + partner.getChr().message("There is not enough inventory space to complete the trade."); + } else { + cancelTrade(partner.getChr(), TradeResult.UNSUCCESSFUL_UNIQUE_ITEM_LIMIT); + c.message("Partner cannot hold more than one one-of-a-kind item at a time."); + } return; } if (local.getChr().getLevel() < 15) { if (local.getChr().getMesosTraded() + local.exchangeMeso > 1000000) { - cancelTrade(c); + cancelTrade(local.getChr(), TradeResult.NO_RESPONSE); local.getChr().getClient().announce(MaplePacketCreator.serverNotice(1, "Characters under level 15 may not trade more than 1 million mesos per day.")); return; } else { @@ -292,7 +340,7 @@ public class MapleTrade { } } else if (partner.getChr().getLevel() < 15) { if (partner.getChr().getMesosTraded() + partner.exchangeMeso > 1000000) { - cancelTrade(c); + cancelTrade(partner.getChr(), TradeResult.NO_RESPONSE); partner.getChr().getClient().announce(MaplePacketCreator.serverNotice(1, "Characters under level 15 may not trade more than 1 million mesos per day.")); return; } else { @@ -309,74 +357,122 @@ public class MapleTrade { } } - private static void cancelTradeInternal(MapleCharacter chr) { + private static void cancelTradeInternal(MapleCharacter chr, byte selfResult, byte partnerResult) { MapleTrade trade = chr.getTrade(); if(trade == null) return; - trade.cancel(); + trade.cancel(selfResult); if (trade.getPartner() != null) { - trade.getPartner().cancel(); + trade.getPartner().cancel(partnerResult); trade.getPartner().getChr().setTrade(null); + + MapleInviteCoordinator.answerInvite(InviteType.TRADE, trade.getChr().getId(), trade.getPartner().getChr().getId(), false); + MapleInviteCoordinator.answerInvite(InviteType.TRADE, trade.getPartner().getChr().getId(), trade.getChr().getId(), false); } chr.setTrade(null); } - private synchronized void tradeCancelHandshake(boolean updateSelf) { + private synchronized void tradeCancelHandshake(boolean updateSelf, byte result) { + byte selfResult, partnerResult; MapleTrade self; - + + partnerResult = result; + selfResult = (result == TradeResult.PARTNER_CANCEL.getValue() ? TradeResult.NO_RESPONSE.getValue() : result); + if (updateSelf) { self = this; } else { self = this.getPartner(); } - cancelTradeInternal(self.getChr()); + cancelTradeInternal(self.getChr(), selfResult, partnerResult); } - private void cancelHandshake() { // handshake checkout thanks to Ronan - if (this.getChr().getId() < this.getPartner().getChr().getId()) { - this.tradeCancelHandshake(true); + private void cancelHandshake(byte result) { // handshake checkout thanks to Ronan + MapleTrade partner = this.getPartner(); + if (partner == null || this.getChr().getId() < partner.getChr().getId()) { + this.tradeCancelHandshake(true, result); } else { - this.getPartner().tradeCancelHandshake(false); + partner.tradeCancelHandshake(false, result); } } - public static void cancelTrade(MapleCharacter chr) { + public static void cancelTrade(MapleCharacter chr, TradeResult result) { MapleTrade trade = chr.getTrade(); if(trade == null) return; - trade.cancelHandshake(); + trade.cancelHandshake(result.getValue()); } public static void startTrade(MapleCharacter c) { if (c.getTrade() == null) { c.setTrade(new MapleTrade((byte) 0, c)); - c.getClient().announce(MaplePacketCreator.getTradeStart(c.getClient(), c.getTrade(), (byte) 0)); - } else { - c.message("You are already in a trade."); } } - + + private static boolean hasTradeInviteBack(MapleCharacter c1, MapleCharacter c2) { + MapleTrade other = c2.getTrade(); + if (other != null) { + MapleTrade otherPartner = other.getPartner(); + if (otherPartner != null) { + if (otherPartner.getChr().getId() == c1.getId()) { + return true; + } + } + } + + return false; + } + public static void inviteTrade(MapleCharacter c1, MapleCharacter c2) { - if (c2.getTrade() == null) { - c2.setTrade(new MapleTrade((byte) 1, c2)); - c2.getTrade().setPartner(c1.getTrade()); - c1.getTrade().setPartner(c2.getTrade()); - c2.getClient().announce(MaplePacketCreator.getTradeInvite(c1)); + if (MapleInviteCoordinator.hasInvite(InviteType.TRADE, c1.getId())) { + if (hasTradeInviteBack(c1, c2)) { + c1.message("You are already managing this player's trade invitation."); + } else { + c1.message("You are already managing someone's trade invitation."); + } + + return; + } else if (c1.getTrade().isFullTrade()) { + c1.message("You are already in a trade."); + return; + } + + if (MapleInviteCoordinator.createInvite(InviteType.TRADE, c1, c1.getId(), c2.getId())) { + if (c2.getTrade() == null) { + c2.setTrade(new MapleTrade((byte) 1, c2)); + c2.getTrade().setPartner(c1.getTrade()); + c1.getTrade().setPartner(c2.getTrade()); + + c1.getClient().announce(MaplePacketCreator.getTradeStart(c1.getClient(), c1.getTrade(), (byte) 0)); + c2.getClient().announce(MaplePacketCreator.tradeInvite(c1)); + } else { + c1.message("The other player is already trading with someone else."); + cancelTrade(c1, TradeResult.NO_RESPONSE); + MapleInviteCoordinator.answerInvite(InviteType.TRADE, c2.getId(), c1.getId(), false); + } } else { - c1.message("The other player is already trading with someone else."); - cancelTrade(c1); + c1.message("The other player is already managing someone else's trade invitation."); + cancelTrade(c1, TradeResult.NO_RESPONSE); } } public static void visitTrade(MapleCharacter c1, MapleCharacter c2) { - if (c1.getTrade() != null && c1.getTrade().getPartner() == c2.getTrade() && c2.getTrade() != null && c2.getTrade().getPartner() == c1.getTrade()) { - c2.getClient().announce(MaplePacketCreator.getTradePartnerAdd(c1)); - c1.getClient().announce(MaplePacketCreator.getTradeStart(c1.getClient(), c1.getTrade(), (byte) 1)); - c1.getTrade().setFullTrade(true); - c2.getTrade().setFullTrade(true); + Pair inviteRes = MapleInviteCoordinator.answerInvite(InviteType.TRADE, c1.getId(), c2.getId(), true); + + InviteResult res = inviteRes.getLeft(); + if (res == InviteResult.ACCEPTED) { + if (c1.getTrade() != null && c1.getTrade().getPartner() == c2.getTrade() && c2.getTrade() != null && c2.getTrade().getPartner() == c1.getTrade()) { + c2.getClient().announce(MaplePacketCreator.getTradePartnerAdd(c1)); + c1.getClient().announce(MaplePacketCreator.getTradeStart(c1.getClient(), c1.getTrade(), (byte) 1)); + c1.getTrade().setFullTrade(true); + c2.getTrade().setFullTrade(true); + } else { + c1.message("The other player has already closed the trade."); + } } else { - c1.message("The other player has already closed the trade."); + c1.message("This trade invitation already rescinded."); + cancelTrade(c1, TradeResult.NO_RESPONSE); } } @@ -385,11 +481,15 @@ public class MapleTrade { if (trade != null) { if (trade.getPartner() != null) { MapleCharacter other = trade.getPartner().getChr(); - other.getTrade().cancel(); + if (MapleInviteCoordinator.answerInvite(InviteType.TRADE, c.getId(), other.getId(), false).getLeft() == InviteResult.DENIED) { + other.message(c.getName() + " has declined your trade request."); + } + + other.getTrade().cancel(TradeResult.PARTNER_CANCEL.getValue()); other.setTrade(null); - other.message(c.getName() + " has declined your trade request."); + } - trade.cancel(); + trade.cancel(TradeResult.NO_RESPONSE.getValue()); c.setTrade(null); } } diff --git a/src/server/life/MapleMonster.java b/src/server/life/MapleMonster.java index 6ea511e450..b4e4f29a00 100644 --- a/src/server/life/MapleMonster.java +++ b/src/server/life/MapleMonster.java @@ -460,47 +460,50 @@ public class MapleMonster extends AbstractLoadedMapleLife { public boolean isAttackedBy(MapleCharacter chr) { return takenDamage.containsKey(chr.getId()); } - - private void distributeExperienceToParty(int pid, float exp, int killer, int killerLevel, Set underleveled, int minThresholdLevel) { - List members = new LinkedList<>(); - MapleCharacter pchar = getMap().getAnyCharacterFromParty(pid); + + private void distributeExperienceToParty(int pid, float exp, int mostDamageCid, int minThresholdLevel, int killerLevel, Set underleveled, Map partyExpReward) { + MapleCharacter pchar = getMap().getAnyCharacterFromParty(pid); // thanks G h o s t, Alfred, Vcoc, BHB for poiting out a bug in detecting party members after membership transactions in a party took place + + List members; if(pchar != null) { - for(MapleCharacter chr : pchar.getPartyMembersOnSameMap()) { - members.add(chr); - } + members = pchar.getPartyMembersOnSameMap(); } else { - MapleCharacter chr = getMap().getCharacterById(killer); - if(chr == null) return; - - members.add(chr); + members = new LinkedList<>(); } List expSharers = new LinkedList<>(); - int expSharersLevel = 0; + int expSharersMaxLevel = 1; + boolean hasMostDamageCid = false; for (MapleCharacter mc : members) { + if (mc.getId() == mostDamageCid) { + hasMostDamageCid = true; + } + if (mc.getLevel() >= minThresholdLevel) { //NO EXP WILL BE GIVEN for those who are underleveled! if (Math.abs(killerLevel - mc.getLevel()) < ServerConstants.MIN_RANGELEVEL_TO_EXP_LEECH) { // thanks Thora for pointing out leech level limitation - expSharersLevel += mc.getLevel(); + if (expSharersMaxLevel < mc.getLevel()) { + expSharersMaxLevel = mc.getLevel(); + } expSharers.add(mc); } } else { underleveled.add(mc); } } - - final int mostDamageCid = getHighestDamagerId(); - + + int numExpSharers = expSharers.size(); + + // PARTY BONUS: 2p -> +2% , 3p -> +4% , 4p -> +6% , 5p -> +8% , 6p -> +10% + // MOST DAMAGE BONUS: 1.5x bonus + final float partyModifier = numExpSharers <= 1 ? 0.0f : 0.02f * (numExpSharers - 1); + final float mostDamageModifier = hasMostDamageCid ? 1.5f : 1.0f; + final float partyExp = exp * partyModifier * mostDamageModifier; + for (MapleCharacter mc : expSharers) { - int id = mc.getId(); - boolean isKiller = killer == id; - boolean mostDamage = mostDamageCid == id; - float xp = ((0.80f * exp * mc.getLevel()) / expSharersLevel); - if (mostDamage) { - xp += (0.20f * exp); - } - giveExpToCharacter(mc, xp, isKiller, expSharers.size()); + float levelPenaltyModifier = (float) Math.sqrt(((float) mc.getLevel()) / expSharersMaxLevel); + partyExpReward.put(mc, partyExp * levelPenaltyModifier); } } @@ -518,11 +521,26 @@ public class MapleMonster extends AbstractLoadedMapleLife { } } + private void propagateExperienceGains(Map personalExpReward, Map partyExpReward) { + Set expRewardPlayers = new HashSet<>(personalExpReward.keySet()); + expRewardPlayers.addAll(partyExpReward.keySet()); + + for (MapleCharacter chr : expRewardPlayers) { + Float personalExp = personalExpReward.get(chr); + Float partyExp = partyExpReward.get(chr); + + this.giveExpToCharacter(chr, personalExp, partyExp); + } + } + private void distributeExperience(int killerId) { if (isAlive()) { return; } + Map personalExpReward = new HashMap<>(); + Map partyExpReward = new HashMap<>(); + EventInstanceManager eim = getMap().getEventInstance(); int minThresholdLevel = calcThresholdLevel(eim != null), killerLevel = Integer.MAX_VALUE; int exp = getExp(); @@ -553,18 +571,18 @@ public class MapleMonster extends AbstractLoadedMapleLife { xp += exp2; } - MapleParty p = mc.getParty(); - if (p != null) { - int pID = p.getId(); - float pXP = xp + (partyExp.containsKey(pID) ? partyExp.get(pID) : 0); - partyExp.put(pID, pXP); - } else { - if(mc.getLevel() >= minThresholdLevel) { - //NO EXP WILL BE GIVEN for those who are underleveled! - giveExpToCharacter(mc, xp, isKiller, 1); - } else { - underleveled.add(mc); + if(mc.getLevel() >= minThresholdLevel) { + //NO EXP WILL BE GIVEN for those who are underleveled! + personalExpReward.put(mc, xp); + + MapleParty p = mc.getParty(); + if (p != null) { // for party bonus exp + int pID = p.getId(); + float pXP = xp + (partyExp.containsKey(pID) ? partyExp.get(pID) : 0); + partyExp.put(pID, pXP); } + } else { + underleveled.add(mc); } } } @@ -587,52 +605,78 @@ public class MapleMonster extends AbstractLoadedMapleLife { } } + int mostDamageCid = this.getHighestDamagerId(); for (Entry party : partyExp.entrySet()) { - distributeExperienceToParty(party.getKey(), party.getValue(), killerId, killerLevel, underleveled, minThresholdLevel); + distributeExperienceToParty(party.getKey(), party.getValue(), mostDamageCid, minThresholdLevel, killerLevel, underleveled, partyExpReward); } for(MapleCharacter mc : underleveled) { mc.showUnderleveledInfo(this); } - } - - private void giveExpToCharacter(MapleCharacter attacker, float exp, boolean isKiller, int numExpSharers) { - //PARTY BONUS: 2p -> +2% , 3p -> +4% , 4p -> +6% , 5p -> +8% , 6p -> +10% - final float partyModifier = numExpSharers <= 1 ? 0.0f : 0.02f * (numExpSharers - 1); - int partyExp = 0; - if (attacker.getHp() > 0) { - exp *= attacker.getExpRate(); + propagateExperienceGains(personalExpReward, partyExpReward); + } + + private float getStatusExpMultiplier(MapleCharacter attacker) { + float multiplier = 1.0f; + + // thanks Prophecy & Aika for finding out Holy Symbol not being applied on party bonuses + Integer holySymbol = attacker.getBuffedValue(MapleBuffStat.HOLY_SYMBOL); + if (holySymbol != null) { + multiplier *= (1.0 + (holySymbol.doubleValue() / 100.0)); + } + + statiLock.lock(); + try { + MonsterStatusEffect mse = stati.get(MonsterStatus.SHOWDOWN); + if (mse != null) { + multiplier *= (1.0 + (mse.getStati().get(MonsterStatus.SHOWDOWN).doubleValue() / 100.0)); + } + } finally { + statiLock.unlock(); + } + + return multiplier; + } + + private static int expValueToInteger(double exp) { + if (exp > Integer.MAX_VALUE) { + exp = Integer.MAX_VALUE; + } else if (exp < Integer.MIN_VALUE) { + exp = Integer.MIN_VALUE; + } + + return (int) exp; + } + + private void giveExpToCharacter(MapleCharacter attacker, Float personalExp, Float partyExp) { + if (attacker.isAlive()) { + if (personalExp != null) { + personalExp *= getStatusExpMultiplier(attacker); + personalExp *= attacker.getExpRate(); + } else { + personalExp = 0.0f; + } Integer expBonus = attacker.getBuffedValue(MapleBuffStat.EXP_INCREASE); - if (expBonus != null) { // exp increase buff found thanks to HighKey21 - exp += expBonus; + if (expBonus != null) { // exp increase player buff found thanks to HighKey21 + personalExp += expBonus; } - - int personalExp = (int) exp; - if (exp <= Integer.MAX_VALUE) { // assuming no negative xp here - if (partyModifier > 0.0f) { - partyExp = (int) (personalExp * partyModifier * ServerConstants.PARTY_BONUS_EXP_RATE); - } - Integer holySymbol = attacker.getBuffedValue(MapleBuffStat.HOLY_SYMBOL); - if (holySymbol != null) { - personalExp *= 1.0 + (holySymbol.doubleValue() / 100.0); - } - statiLock.lock(); - try { - if (stati.containsKey(MonsterStatus.SHOWDOWN)) { - personalExp *= (stati.get(MonsterStatus.SHOWDOWN).getStati().get(MonsterStatus.SHOWDOWN).doubleValue() / 100.0 + 1.0); - } - } finally { - statiLock.unlock(); - } + int _personalExp = expValueToInteger(personalExp); // assuming no negative xp here + + if (partyExp != null) { + partyExp *= getStatusExpMultiplier(attacker); + partyExp *= attacker.getExpRate(); + partyExp *= ServerConstants.PARTY_BONUS_EXP_RATE; } else { - personalExp = Integer.MAX_VALUE; + partyExp = 0.0f; } - attacker.gainExp(personalExp, partyExp, true, false, isKiller); - attacker.increaseEquipExp(personalExp); + int _partyExp = expValueToInteger(partyExp); + + attacker.gainExp(_personalExp, _partyExp, true, false, false); + attacker.increaseEquipExp(_personalExp); attacker.updateQuestMobCount(getId()); } } diff --git a/src/server/maps/MapleGenericPortal.java b/src/server/maps/MapleGenericPortal.java index 7ba6fe6b9d..be8b169c87 100644 --- a/src/server/maps/MapleGenericPortal.java +++ b/src/server/maps/MapleGenericPortal.java @@ -22,6 +22,8 @@ along with this program. If not, see . package server.maps; import client.MapleClient; +import client.MapleCharacter; +import constants.GameConstants; import java.awt.Point; import scripting.portal.PortalScriptManager; import server.MaplePortal; @@ -140,13 +142,18 @@ public class MapleGenericPortal implements MaplePortal { npe.printStackTrace(); } } else if (getTargetMapId() != 999999999) { - MapleMap to = c.getPlayer().getEventInstance() == null ? c.getChannelServer().getMapFactory().getMap(getTargetMapId()) : c.getPlayer().getEventInstance().getMapInstance(getTargetMapId()); - MaplePortal pto = to.getPortal(getTarget()); - if (pto == null) {// fallback for missing portals - no real life case anymore - interesting for not implemented areas - pto = to.getPortal(0); + MapleCharacter chr = c.getPlayer(); + if (!(chr.getChalkboard() != null && GameConstants.isFreeMarketRoom(getTargetMapId()))) { + MapleMap to = chr.getEventInstance() == null ? c.getChannelServer().getMapFactory().getMap(getTargetMapId()) : chr.getEventInstance().getMapInstance(getTargetMapId()); + MaplePortal pto = to.getPortal(getTarget()); + if (pto == null) {// fallback for missing portals - no real life case anymore - interesting for not implemented areas + pto = to.getPortal(0); + } + chr.changeMap(to, pto); //late resolving makes this harder but prevents us from loading the whole world at once + changed = true; + } else { + chr.dropMessage(5, "You cannot enter this map with the chalkboard opened."); } - c.getPlayer().changeMap(to, pto); //late resolving makes this harder but prevents us from loading the whole world at once - changed = true; } if (!changed) { c.announce(MaplePacketCreator.enableActions()); diff --git a/src/server/maps/MapleHiredMerchant.java b/src/server/maps/MapleHiredMerchant.java index 0f16f494e7..0fca0338c7 100644 --- a/src/server/maps/MapleHiredMerchant.java +++ b/src/server/maps/MapleHiredMerchant.java @@ -29,6 +29,7 @@ import client.inventory.MapleInventory; import client.inventory.MapleInventoryType; import client.inventory.manipulator.MapleInventoryManipulator; import client.inventory.manipulator.MapleKarmaManipulator; +import client.processor.FredrickProcessor; import com.mysql.jdbc.Statement; import constants.ServerConstants; import java.sql.Connection; @@ -191,6 +192,14 @@ public class MapleHiredMerchant extends AbstractMapleMapObject { visitorLock.unlock(); } } + + private void removeOwner(MapleCharacter owner) { + if (owner.getHiredMerchant() == this) { + owner.announce(MaplePacketCreator.hiredMerchantOwnerLeave()); + owner.announce(MaplePacketCreator.leaveHiredMerchant(0x00, 0x03)); + owner.setHiredMerchant(null); + } + } public void withdrawMesos(MapleCharacter chr) { if (isOwner(chr)) { @@ -220,6 +229,10 @@ public class MapleHiredMerchant extends AbstractMapleMapObject { removeFromSlot(slot); chr.announce(MaplePacketCreator.updateHiredMerchant(this, chr)); } + + if (ServerConstants.USE_ENFORCE_MERCHANT_SAVE) { + chr.saveCharToDB(false); + } } } @@ -228,15 +241,17 @@ public class MapleHiredMerchant extends AbstractMapleMapObject { } private int getQuantityLeft(int itemid) { - int count = 0; - - for (MaplePlayerShopItem mpsi : items) { - if (mpsi.getItem().getItemId() == itemid) { - count += (mpsi.getBundles() * mpsi.getItem().getQuantity()); + synchronized (items) { + int count = 0; + + for (MaplePlayerShopItem mpsi : items) { + if (mpsi.getItem().getItemId() == itemid) { + count += (mpsi.getBundles() * mpsi.getItem().getQuantity()); + } } + + return count; } - - return count; } public void buy(MapleClient c, int item, short quantity) { @@ -291,10 +306,14 @@ public class MapleHiredMerchant extends AbstractMapleMapObject { } } } else { - c.getPlayer().dropMessage(1, "Your inventory is full. Please clean a slot before buying this item."); + c.getPlayer().dropMessage(1, "Your inventory is full. Please clear a slot before buying this item."); + c.announce(MaplePacketCreator.enableActions()); + return; } } else { c.getPlayer().dropMessage(1, "You don't have enough mesos to purchase this item."); + c.announce(MaplePacketCreator.enableActions()); + return; } try { this.saveItems(false); @@ -365,17 +384,18 @@ public class MapleHiredMerchant extends AbstractMapleMapObject { public void closeOwnerMerchant(MapleCharacter chr) { if(this.isOwner(chr)) { - chr.announce(MaplePacketCreator.hiredMerchantOwnerLeave()); - chr.announce(MaplePacketCreator.leaveHiredMerchant(0x00, 0x03)); this.closeShop(chr.getClient(), false); chr.setHasMerchant(false); } } - public void closeShop(MapleClient c, boolean timeout) { + private void closeShop(MapleClient c, boolean timeout) { map.removeMapObject(this); map.broadcastMessage(MaplePacketCreator.removeHiredMerchantBox(ownerId)); c.getChannelServer().removeHiredMerchant(ownerId); + + this.removeAllVisitors(); + this.removeOwner(c.getPlayer()); try { MapleCharacter player = c.getWorldServer().getPlayerStorage().getCharacterById(ownerId); @@ -412,6 +432,10 @@ public class MapleHiredMerchant extends AbstractMapleMapObject { } catch (Exception e) { e.printStackTrace(); } + + if (ServerConstants.USE_ENFORCE_MERCHANT_SAVE) { + c.getPlayer().saveCharToDB(false); + } synchronized (items) { items.clear(); @@ -419,7 +443,7 @@ public class MapleHiredMerchant extends AbstractMapleMapObject { } catch (Exception e) { e.printStackTrace(); } - + Server.getInstance().getWorld(world).unregisterHiredMerchant(this); } @@ -611,6 +635,8 @@ public class MapleHiredMerchant extends AbstractMapleMapObject { Connection con = DatabaseConnection.getConnection(); ItemFactory.MERCHANT.saveItems(itemsWithType, bundles, this.ownerId, con); con.close(); + + FredrickProcessor.insertFredrickLog(this.ownerId); } private static boolean check(MapleCharacter chr, List items) { @@ -629,8 +655,12 @@ public class MapleHiredMerchant extends AbstractMapleMapObject { return channel; } - public int getTimeLeft() { - return (int) ((System.currentTimeMillis() - start) / 1000); + public int getTimeOpen() { + double openTime = (System.currentTimeMillis() - start) / 60000; + openTime /= 1440; // heuristics since engineered method to count time here is unknown + openTime *= 1318; + + return (int) Math.ceil(openTime); } public void clearMessages() { diff --git a/src/server/maps/MapleMap.java b/src/server/maps/MapleMap.java index c0e6908dea..2f2e7026b1 100644 --- a/src/server/maps/MapleMap.java +++ b/src/server/maps/MapleMap.java @@ -31,6 +31,7 @@ import client.inventory.MapleInventoryType; import client.inventory.MaplePet; import client.status.MonsterStatus; import client.status.MonsterStatusEffect; +import constants.GameConstants; import constants.ItemConstants; import constants.ServerConstants; import java.awt.Point; @@ -150,6 +151,8 @@ public class MapleMap { private Pair timeMob = null; private short mobInterval = 5000; private boolean allowSummons = true; // All maps should have this true at the beginning + private MapleCharacter mapOwner = null; + private long mapOwnerLastActivityTime = Long.MAX_VALUE; // HPQ private int riceCakes = 0; @@ -1088,6 +1091,18 @@ public class MapleMap { mdrop.unlockItem(); } } + + public final void disappearingMesoDrop(final int meso, final MapleMapObject dropper, final MapleCharacter owner, final Point pos) { + final Point droppos = calcDropPos(pos, pos); + final MapleMapItem mdrop = new MapleMapItem(meso, droppos, dropper, owner, owner.getClient(), (byte) 1, false); + + mdrop.lockItem(); + try { + broadcastItemDropMessage(mdrop, dropper.getPosition(), droppos, (byte) 3, mdrop.getPosition()); + } finally { + mdrop.unlockItem(); + } + } public MapleMonster getMonsterById(int id) { objectRLock.lock(); @@ -2254,23 +2269,13 @@ public class MapleMap { } public MapleCharacter getAnyCharacterFromParty(int partyid) { - chrRLock.lock(); - try { - Set list = mapParty.get(partyid); - if(list == null) return null; - - for(Integer cid : list) { - for (MapleCharacter c : this.characters) { - if (c.getId() == cid) { - return c; - } - } + for (MapleCharacter chr : this.getAllPlayers()) { + if (chr.getPartyId() == partyid) { + return chr; } - - return null; - } finally { - chrRLock.unlock(); } + + return null; } private void addPartyMemberInternal(MapleCharacter chr) { @@ -2451,6 +2456,14 @@ public class MapleMap { chr.removeSandboxItems(); + if (chr.getChalkboard() != null) { + if (!GameConstants.isFreeMarketRoom(mapid)) { + chr.announce(MaplePacketCreator.useChalkboard(chr, false)); // update player's chalkboard when changing maps found thanks to Vcoc + } else { + chr.setChalkboard(null); + } + } + if (chr.isHidden()) { broadcastGMSpawnPlayerMapObjectMessage(chr, chr, true); chr.announce(MaplePacketCreator.getGMEffect(0x10, (byte) 1)); @@ -2479,10 +2492,11 @@ public class MapleMap { } finally { objectWLock.unlock(); } + if (chr.getPlayerShop() != null) { addMapObject(chr.getPlayerShop()); } - + final MapleDragon dragon = chr.getDragon(); if (dragon != null) { dragon.setPosition(chr.getPosition()); @@ -2557,6 +2571,19 @@ public class MapleMap { return portal != null ? portal : getPortal(0); } + public MaplePortal findClosestWarpPortal(Point from) { + MaplePortal closest = null; + double shortestDistance = Double.POSITIVE_INFINITY; + for (MaplePortal portal : portals.values()) { + double distance = portal.getPosition().distanceSq(from); + if (portal.getType() == MaplePortal.MAP_PORTAL && distance < shortestDistance && portal.getTargetMapId() == 999999999) { + closest = portal; + shortestDistance = distance; + } + } + return closest; + } + public MaplePortal findClosestPlayerSpawnpoint(Point from) { MaplePortal closest = null; double shortestDistance = Double.POSITIVE_INFINITY; @@ -4021,6 +4048,58 @@ public class MapleMap { } } + public boolean claimOwnership(MapleCharacter chr) { + if (mapOwner == null) { + mapOwner = chr; + mapOwnerLastActivityTime = Server.getInstance().getCurrentTime(); + + getChannelServer().registerOwnedMap(this); + return true; + } else { + return chr == mapOwner; + } + } + + public boolean unclaimOwnership(MapleCharacter chr) { + if (mapOwner == chr) { + mapOwner = null; + mapOwnerLastActivityTime = Long.MAX_VALUE; + + getChannelServer().unregisterOwnedMap(this); + return true; + } else { + return false; + } + } + + private void refreshOwnership() { + mapOwnerLastActivityTime = Server.getInstance().getCurrentTime(); + } + + public boolean isOwnershipRestricted(MapleCharacter chr) { + MapleCharacter owner = mapOwner; + + if (owner != null) { + if (owner != chr && !owner.isPartyMember(chr)) { // thanks Vcoc & BHB for suggesting the map ownership feature + chr.showMapOwnershipInfo(owner); + return true; + } else { + this.refreshOwnership(); + } + } + + return false; + } + + public void checkMapOwnerActivity() { + long timeNow = Server.getInstance().getCurrentTime(); + if (timeNow - mapOwnerLastActivityTime > 60000) { + if (unclaimOwnership(mapOwner)) { + this.dropMessage(5, "This lawn is now free real estate."); + } + } + } + public void dispose() { for(MapleMonster mm : this.getMonsters()) { mm.dispose(); diff --git a/src/server/maps/MaplePlayerShop.java b/src/server/maps/MaplePlayerShop.java index 965f413651..43ba5746f9 100644 --- a/src/server/maps/MaplePlayerShop.java +++ b/src/server/maps/MaplePlayerShop.java @@ -146,6 +146,7 @@ public class MaplePlayerShop extends AbstractMapleMapObject { try { for (int i = 0; i < 3; i++) { if (visitors[i] != null && visitors[i].getId() == visitor.getId()) { + visitors[i].setPlayerShop(null); visitors[i] = null; visitor.setSlot(-1); @@ -298,7 +299,7 @@ public class MaplePlayerShop extends AbstractMapleMapObject { } } } else { - c.getPlayer().dropMessage(1, "Your inventory is full. Please clean a slot before buying this item."); + c.getPlayer().dropMessage(1, "Your inventory is full. Please clear a slot before buying this item."); c.announce(MaplePacketCreator.enableActions()); return false; } diff --git a/src/tools/FilePrinter.java b/src/tools/FilePrinter.java index f9e5feee6a..a6c40a9e88 100644 --- a/src/tools/FilePrinter.java +++ b/src/tools/FilePrinter.java @@ -28,6 +28,7 @@ public class FilePrinter { PACKET_LOG = "game/Log.txt", CASHITEM_BOUGHT = "interactions/CashLog.txt", EXCEPTION = "game/Exceptions.txt", + LOGIN_EXCEPTION = "game/LoginExceptions.txt", TRADE_EXCEPTION = "game/TradeExceptions.txt", SQL_EXCEPTION = "game/SqlExceptions.txt", PACKET_HANDLER = "game/packethandler/", diff --git a/src/tools/MaplePacketCreator.java b/src/tools/MaplePacketCreator.java index e26d6370d6..b0bd968959 100644 --- a/src/tools/MaplePacketCreator.java +++ b/src/tools/MaplePacketCreator.java @@ -1065,7 +1065,7 @@ public class MaplePacketCreator { mplew.writeShort(chr.getHp()); mplew.writeBool(false); mplew.writeLong(getTime(Server.getInstance().getCurrentTime())); - mplew.skip(14); + mplew.skip(18); return mplew.getPacket(); } @@ -1082,7 +1082,7 @@ public class MaplePacketCreator { mplew.writeInt(spawnPosition.x); // spawn position placement thanks to Arnah (Vertisy) mplew.writeInt(spawnPosition.y); mplew.writeLong(getTime(Server.getInstance().getCurrentTime())); - mplew.skip(14); + mplew.skip(18); return mplew.getPacket(); } @@ -3175,7 +3175,7 @@ public class MaplePacketCreator { return mplew.getPacket(); } - public static byte[] getTradeInvite(MapleCharacter c) { + public static byte[] tradeInvite(MapleCharacter c) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); mplew.writeShort(SendOpcode.PLAYER_INTERACTION.getValue()); mplew.write(PlayerInteractionHandler.Action.INVITE.getCode()); @@ -3309,21 +3309,22 @@ public class MaplePacketCreator { return mplew.getPacket(); } - public static byte[] getTradeCompletion(byte number) { + /** + * Possible values for operation:
2: Trade cancelled by the + * other character
7: Trade successful
8: Trade unsuccessful
+ * 9: Cannot carry more one-of-a-kind items
12: Cannot trade on different maps
+ * 13: Cannot trade, game files damaged
+ * + * @param number + * @param operation + * @return + */ + public static byte[] getTradeResult(byte number, byte operation) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(5); mplew.writeShort(SendOpcode.PLAYER_INTERACTION.getValue()); mplew.write(PlayerInteractionHandler.Action.EXIT.getCode()); mplew.write(number); - mplew.write(6); - return mplew.getPacket(); - } - - public static byte[] getTradeCancel(byte number) { - final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(5); - mplew.writeShort(SendOpcode.PLAYER_INTERACTION.getValue()); - mplew.write(PlayerInteractionHandler.Action.EXIT.getCode()); - mplew.write(number); - mplew.write(2); + mplew.write(operation); return mplew.getPacket(); } @@ -3783,11 +3784,9 @@ public class MaplePacketCreator { * party. 13: You have yet to join a party. * 16: Already have joined a party. 17: The party you're trying to join is * already in full capacity. 19: Unable to find the requested character in - * this channel. 21: Player is blocking any party invitations. 22: Player - * is taking care of another invitation. 23: Player denied request. - * 25: Cannot kick another user in this map. 28/29: Leadership can only be - * given to a party member in the vicinity. 30: Change leadership only on - * same channel. + * this channel. 25: Cannot kick another user in this map. 28/29: Leadership + * can only be given to a party member in the vicinity. 30: Change leadership + * only on same channel. * * @param message * @return @@ -3800,7 +3799,8 @@ public class MaplePacketCreator { } /** - * 23: 'Char' have denied request to the party. + * 21: Player is blocking any party invitations, 22: Player is taking care of + * another invitation, 23: Player have denied request to the party. * * @param message * @param charname @@ -4372,25 +4372,43 @@ public class MaplePacketCreator { } /** - * 'Char' has denied your guild invitation. + * Gets a Heracle/guild message packet. * - * @param charname - * @return + * Possible values for code:
28: guild name already in use
+ * 31: problem in locating players during agreement
33/40: already joined a guild
+ * 35: Cannot make guild
36: problem in player agreement
38: problem during forming guild
+ * 41: max number of players in joining guild
42: character can't be found this channel
+ * 45/48: character not in guild
52: problem in disbanding guild
56: admin cannot make guild
+ * 57: problem in increasing guild size
+ * + * + * @param code The response code. + * @return The guild message packet. */ - public static byte[] denyGuildInvitation(String charname) { - final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); - mplew.writeShort(SendOpcode.GUILD_OPERATION.getValue()); - mplew.write(0x37); - mplew.writeMapleAsciiString(charname); - return mplew.getPacket(); - } - public static byte[] genericGuildMessage(byte code) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); mplew.writeShort(SendOpcode.GUILD_OPERATION.getValue()); mplew.write(code); return mplew.getPacket(); } + + /** + * Gets a guild message packet appended with target name. + * + * 53: player not accepting guild invites
+ * 54: player already managing an invite
55: player denied an invite
+ * + * @param code The response code. + * @param targetName The initial player target of the invitation. + * @return The guild message packet. + */ + public static byte[] responseGuildMessage(byte code, String targetName) { + final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SendOpcode.GUILD_OPERATION.getValue()); + mplew.write(code); + mplew.writeMapleAsciiString(targetName); + return mplew.getPacket(); + } public static byte[] newGuildMember(MapleGuildCharacter mgc) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); @@ -4937,7 +4955,7 @@ public class MaplePacketCreator { return mplew.getPacket(); } - public static byte[] skillBookSuccess(MapleCharacter chr, int skillid, int maxlevel, boolean canuse, boolean success) { + public static byte[] skillBookResult(MapleCharacter chr, int skillid, int maxlevel, boolean canuse, boolean success) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); mplew.writeShort(SendOpcode.SKILL_LEARN_ITEM_RESULT.getValue()); mplew.writeInt(chr.getId()); @@ -5378,7 +5396,7 @@ public class MaplePacketCreator { mplew.writeInt(9030000); // Fredrick mplew.writeInt(32272); //id mplew.skip(5); - mplew.writeInt(chr.getMerchantMeso()); + mplew.writeInt(chr.getMerchantNetMeso()); mplew.write(0); try { List> items = ItemFactory.MERCHANT.loadItems(chr.getId(), false); @@ -5578,7 +5596,8 @@ public class MaplePacketCreator { } mplew.writeMapleAsciiString(hm.getOwner()); if (hm.isOwner(chr)) { - mplew.writeInt(hm.getTimeLeft()); + mplew.writeShort(0); + mplew.writeShort(hm.getTimeOpen()); mplew.write(firstTime ? 1 : 0); List sold = hm.getSold(); mplew.write(sold.size()); @@ -6348,6 +6367,16 @@ public class MaplePacketCreator { mplew.writeInt(1); return mplew.getPacket(); } + + public static byte[] showForeignInfo(int cid, String path) { + final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); + mplew.writeShort(SendOpcode.SHOW_FOREIGN_EFFECT.getValue()); + mplew.writeInt(cid); + mplew.write(0x17); + mplew.writeMapleAsciiString(path); + mplew.writeInt(1); + return mplew.getPacket(); + } /** * Sends a UI utility. 0x01 - Equipment Inventory. 0x02 - Stat Window. 0x03 @@ -6875,7 +6904,7 @@ public class MaplePacketCreator { return mplew.getPacket(); } - public static byte[] sendAllianceInvitation(int allianceid, MapleCharacter chr) { + public static byte[] allianceInvite(int allianceid, MapleCharacter chr) { final MaplePacketLittleEndianWriter mplew = new MaplePacketLittleEndianWriter(); mplew.writeShort(SendOpcode.ALLIANCE_OPERATION.getValue()); mplew.write(0x03); diff --git a/src/scripting/event/EventInstanceInProgressException.java b/src/tools/exceptions/EventInstanceInProgressException.java similarity index 97% rename from src/scripting/event/EventInstanceInProgressException.java rename to src/tools/exceptions/EventInstanceInProgressException.java index 128429562d..3a6d750908 100644 --- a/src/scripting/event/EventInstanceInProgressException.java +++ b/src/tools/exceptions/EventInstanceInProgressException.java @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package scripting.event; +package tools.exceptions; /** diff --git a/src/tools/packets/Fishing.java b/src/tools/packets/Fishing.java new file mode 100644 index 0000000000..b7d1c7c90a --- /dev/null +++ b/src/tools/packets/Fishing.java @@ -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 . +*/ +package tools.packets; + +import client.MapleCharacter; +import constants.GameConstants; +import constants.ItemConstants; +import constants.ServerConstants; +import server.MapleItemInformationProvider; +import tools.MaplePacketCreator; + +import java.util.Calendar; + +/** + * + * @author FateJiki (RaGeZONE) + * @author Ronan - timing pattern + */ +public class Fishing { + + private static double getFishingLikelihood(int x) { + return 50.0 + 7.0 * (7.0 * Math.sin(x)) * (Math.cos(Math.pow(x, 0.777))); + } + + public static double[] fetchFishingLikelihood() { + Calendar calendar = Calendar.getInstance(); + int dayOfYear = calendar.get(Calendar.DAY_OF_YEAR); + + int hours = calendar.get(Calendar.HOUR); + int minutes = calendar.get(Calendar.MINUTE); + int seconds = calendar.get(Calendar.SECOND); + + double yearLikelihood = getFishingLikelihood(dayOfYear); + double timeLikelihood = getFishingLikelihood(hours + minutes + seconds); + + return new double[]{yearLikelihood, timeLikelihood}; + } + + private static boolean hitFishingTime(MapleCharacter chr, int baitLevel, double yearLikelihood, double timeLikelihood) { + double baitLikelihood = 0.0002 * chr.getWorldServer().getFishingRate() * baitLevel; // can improve 10.0 at "max level 50000" on rate 1x + + if (ServerConstants.USE_DEBUG) { + chr.dropMessage(5, "----- FISHING RESULT -----"); + chr.dropMessage(5, "Likelihoods - Year: " + yearLikelihood + " Time: " + timeLikelihood + " Meso: " + baitLikelihood); + chr.dropMessage(5, "Score rolls - Year: " + (0.23 * yearLikelihood) + " Time: " + (0.77 * timeLikelihood) + " Meso: " + baitLikelihood); + } + + return (0.23 * yearLikelihood) + (0.77 * timeLikelihood) + (baitLikelihood) > 57.777; + } + + public static void doFishing(MapleCharacter chr, int baitLevel, double yearLikelihood, double timeLikelihood){ + // thanks Fadi, Vcoc for suggesting a custom fishing system + + if (!chr.isLoggedinWorld() || !chr.isAlive()) { + return; + } + + if (!GameConstants.isFishingArea(chr.getMapId())) { + chr.dropMessage("You are not in a fishing area!"); + return; + } + + if (chr.getLevel() < 30) { + chr.dropMessage(5, "You must be above level 30 to fish!"); + return; + } + + String fishingEffect; + if (!hitFishingTime(chr, baitLevel, yearLikelihood, timeLikelihood)) { + fishingEffect = "Effect/BasicEff.img/Catch/Fail"; + } else { + String rewardStr = ""; + fishingEffect = "Effect/BasicEff.img/Catch/Success"; + + int rand = (int)(3.0 * Math.random()); + switch(rand){ + case 0: + int mesoAward = (int)(1400.0 * Math.random() + 1201) * chr.getMesoRate() + (15 * chr.getLevel() / 5); + chr.gainMeso(mesoAward, true, true, true); + + rewardStr = mesoAward + " mesos."; + break; + case 1: + int expAward = (int)(645.0 * Math.random() + 620.0) * chr.getExpRate() + (15 * chr.getLevel() / 4); + chr.gainExp(expAward, true, true); + + rewardStr = expAward + " EXP."; + break; + case 2: + int itemid = getRandomItem(); + rewardStr = "a(n) " + MapleItemInformationProvider.getInstance().getName(itemid) + "."; + + if (chr.canHold(itemid)) { + chr.getClient().getAbstractPlayerInteraction().gainItem(itemid, true); + } else { + chr.showHint("Couldn't catch a(n) #r" + MapleItemInformationProvider.getInstance().getName(itemid) + "#k due to #e#b" + ItemConstants.getInventoryType(itemid) + "#k#n inventory limit."); + rewardStr += ".. but has goofed up due to full inventory."; + } + break; + } + + chr.getMap().dropMessage(6, chr.getName() + " found " + rewardStr); + } + + chr.announce(MaplePacketCreator.showInfo(fishingEffect)); + chr.getMap().broadcastMessage(chr, MaplePacketCreator.showForeignInfo(chr.getId(), fishingEffect), false); + } + + public static int getRandomItem(){ + int rand = (int)(100.0 * Math.random()); + int[] commons = {1002851, 2002020, 2002020, 2000006, 2000018, 2002018, 2002024, 2002027, 2002027, 2000018, 2000018, 2000018 , 2000018, 2002030, 2002018, 2000016}; // filler' up + int[] uncommons = {1000025, 1002662, 1002812, 1002850, 1002881, 1002880, 1012072, 4020009, 2043220, 2043022, 2040543, 2044420, 2040943, 2043713, 2044220, 2044120, 2040429, 2043220, 2040943}; // filler' uptoo + int[] rares = {1002859, 1002553, 01002762, 01002763, 01002764, 01002765, 01002766, 01002663, 1002788, 1002949, 2049100, 2340000, 2040822,2040822,2040822,2040822,2040822,2040822,2040822,2040822}; // filler' uplast + + if(rand >= 25){ + return commons[(int)(commons.length * Math.random())]; + } else if(rand <= 7 && rand >= 4){ + return uncommons[(int)(uncommons.length * Math.random())]; + } else { + return rares[(int)(rares.length * Math.random())]; + } + } + + private static void debugFishingLikelihood() { + long a[] = new long[365], b[] = new long[365]; + long hits = 0, hits10 = 0, total = 0; + + for (int i = 0; i < 365; i++) { + double yearLikelihood = getFishingLikelihood(i); + + int dayHits = 0, dayHits10 = 0; + for (int k = 0; k < 24; k++) { + for (int l = 0; l < 60; l++) { + for (int m = 0; m < 60; m++) { + double timeLikelihood = getFishingLikelihood(k + l + m); + + if ((0.23 * yearLikelihood) + (0.77 * timeLikelihood) > 57.777) { + hits++; + dayHits++; + } + + if ((0.23 * yearLikelihood) + (0.77 * timeLikelihood) + 10.0 > 57.777) { + hits10++; + dayHits10++; + } + + total++; + } + } + } + + a[i] = dayHits; + b[i] = dayHits10; + } + + long maxhit = 0, minhit = Long.MAX_VALUE; + for (int i = 0; i < 365; i++) { + if (maxhit < a[i]) { + maxhit = a[i]; + } + + if (minhit > a[i]) { + minhit = a[i]; + } + } + + long maxhit10 = 0, minhit10 = Long.MAX_VALUE; + for (int i = 0; i < 365; i++) { + if (maxhit10 < b[i]) { + maxhit10 = b[i]; + } + + if (minhit10 > b[i]) { + minhit10 = b[i]; + } + } + + System.out.println("Diary min " + minhit + " max " + maxhit); + System.out.println("Diary10 min " + minhit10 + " max " + maxhit10); + System.out.println("Hits: " + hits + "Hits10: " + hits10 + " Total: " + total + " -- %1000: " + (hits * 1000 / total) + ", +10 %1000: " + (hits10 * 1000 / total)); + } +} diff --git a/wz/Effect.wz/BasicEff.img.xml b/wz/Effect.wz/BasicEff.img.xml index 035e4ba7f2..5e0137afbd 100644 --- a/wz/Effect.wz/BasicEff.img.xml +++ b/wz/Effect.wz/BasicEff.img.xml @@ -247,9 +247,28 @@ - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/wz/Quest.wz/Act.img.xml b/wz/Quest.wz/Act.img.xml index 565a5f4226..b257813231 100644 --- a/wz/Quest.wz/Act.img.xml +++ b/wz/Quest.wz/Act.img.xml @@ -15075,6 +15075,10 @@ + + + + @@ -15288,6 +15292,10 @@ + + + + @@ -15501,6 +15509,12 @@ + + + + + + @@ -15513,6 +15527,10 @@ + + + + @@ -15736,6 +15754,12 @@ + + + + + + @@ -15748,6 +15772,10 @@ + + + + @@ -15934,10 +15962,22 @@ + + + + + + + + + + + + @@ -15950,6 +15990,10 @@ + + + + diff --git a/wz/Quest.wz/Check.img.xml b/wz/Quest.wz/Check.img.xml index 64365fca39..30c363625a 100644 --- a/wz/Quest.wz/Check.img.xml +++ b/wz/Quest.wz/Check.img.xml @@ -29056,6 +29056,12 @@ + + + + + + @@ -29715,6 +29721,12 @@ + + + + + + @@ -30367,6 +30379,12 @@ + + + + + + @@ -30968,6 +30986,12 @@ + + + + + + @@ -31542,6 +31566,12 @@ + + + + + +