Trade results + Map-Ownership & Fishing + P. EXP distribution rework
Fixed an issue where players could lose priority over recently dropped items after switching maps. Adjusted EXP bonus buffs to also cover party bonus gains. Fixed items taken back from merchants not properly checking for stacking opportunities in player inventory. Fixed some merchant references in visitors' player object not being properly cleared when owner closes shop. Adjusted merchants to automatically close as soon as the merchant owner finishes maintenance process with it having no items in store. Added trade result opcodes. Trade results now should work almost as intended originally. Implemented server-side check for portal distance when deploying player shops and merchants. Implemented server-side check for whether local or remote IP is being used when logging in a local/remote server (this should mitigate a few of the issues people may find when trying to log in game world). Implemented commands designed for management of opened IO sessions. Fixed chalkboard not showing up for owner player when changing maps. Added "time left" functionality for merchant owners managing the opened store. Fixed skillbooks not showing properly for other players in the map. Fixed commands using lowercased-version of content inputted by player. Implemented the Fredrick expected fee on using the Store Bank service. Implemented "exclusive invitation management" in the system. Inviters are notified the invited players are already managing an invite, should it be visually "in-progress" for that one. Implemented "map ownership". Non-map owners are unable to farm in an area if they are not party members with the owner or until the ownership rescinds. Adjusted inventory sort feature, now sorting projectile items in such a fashion that commonly stronger versions comes before the basic ones. Added a visual effect that shows up when obtaining Aran skills. Revised party EXP gain system. Party bonuses now accounts a fraction of the accumulated EXP gained by members when defeating a mob, and raw EXP gained by a player is kept the same regardless of him/her being in a party or not (thus a bonus being REALLY a bonus). Implemented a custom fishing system in the source, on which during "seasonal" times (that gets arbitrarily defined by both day-of-year and time-of-day) fishes are more likely to be hooked. Such likelihood also improved depending on the amount of mesos spent as lure.
This commit is contained in:
@@ -1661,3 +1661,54 @@ 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.
|
||||
|
||||
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.
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
@@ -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)) {
|
||||
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();
|
||||
|
||||
@@ -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)) {
|
||||
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();
|
||||
|
||||
@@ -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)) {
|
||||
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();
|
||||
|
||||
@@ -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)) {
|
||||
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();
|
||||
|
||||
@@ -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)) {
|
||||
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();
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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,
|
||||
|
||||
+118
-13
@@ -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++) {
|
||||
@@ -1579,6 +1585,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);
|
||||
@@ -1948,6 +1959,16 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
return client.getAbstractPlayerInteraction().canHold(itemid, quantity);
|
||||
}
|
||||
|
||||
public boolean canHoldUniques(List<Integer> 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);
|
||||
return bv != null && bv.equals(Corsair.BATTLE_SHIP);
|
||||
@@ -4739,6 +4760,32 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
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() {
|
||||
@@ -5025,8 +5072,12 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
if(merchant == null) return;
|
||||
|
||||
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
|
||||
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.");
|
||||
}
|
||||
} finally {
|
||||
chrLock.unlock();
|
||||
}
|
||||
|
||||
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()) + "> ";
|
||||
}
|
||||
|
||||
List<String> 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9845,6 +9942,14 @@ public class MapleCharacter extends AbstractMapleCharacterObject {
|
||||
jailExpiration = 0;
|
||||
}
|
||||
|
||||
public String getLastCommandMessage() {
|
||||
return this.commandtext;
|
||||
}
|
||||
|
||||
public void setLastCommandMessage(String text) {
|
||||
this.commandtext = text;
|
||||
}
|
||||
|
||||
public int getRewardPoints() {
|
||||
Connection con = null;
|
||||
PreparedStatement ps = null;
|
||||
|
||||
@@ -118,13 +118,19 @@ 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;
|
||||
this.receive = receive;
|
||||
@@ -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();
|
||||
@@ -1001,7 +1016,7 @@ public class MapleClient {
|
||||
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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
@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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class ReportBugCommand extends Command {
|
||||
player.dropMessage(5, "Message too short and not sent. Please do @bug <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);
|
||||
|
||||
@@ -46,7 +46,7 @@ public class WhatDropsFromCommand extends Command {
|
||||
player.dropMessage(5, "Please do @whatdropsfrom <monster name>");
|
||||
return;
|
||||
}
|
||||
String monsterName = joinStringFrom(params, 0);
|
||||
String monsterName = player.getLastCommandMessage();
|
||||
String output = "";
|
||||
int limit = 3;
|
||||
Iterator<Pair<Integer, String>> listIterator = MapleMonsterInformationProvider.getMobsIDsFromName(monsterName).iterator();
|
||||
|
||||
@@ -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<Pair<Integer, String>> listIterator = MapleItemInformationProvider.getInstance().getItemDataByName(searchString).iterator();
|
||||
if(listIterator.hasNext()) {
|
||||
|
||||
@@ -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 [<playername>] <gainnx>");
|
||||
player.yellowMessage("Syntax: !givenx [nx, mp, np] [<playername>] <gainnx>");
|
||||
return;
|
||||
}
|
||||
|
||||
String recv;
|
||||
int value;
|
||||
String recv, typeStr = "nx";
|
||||
int value, type = 1;
|
||||
if (params.length > 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.");
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
@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 <newrate>");
|
||||
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."));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<MapleCharacter> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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,7 +48,13 @@ 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;
|
||||
@@ -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` = ?");
|
||||
|
||||
@@ -435,8 +435,8 @@ public class MapleInventory implements Iterable<Item> {
|
||||
}
|
||||
}
|
||||
|
||||
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<Pair<Item, MapleInventoryType>> items) {
|
||||
|
||||
@@ -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<Pair<Item, MapleInventoryType>> 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<Pair<Item, MapleInventoryType>> items) {
|
||||
if (!MapleInventory.checkSpotsAndOwnership(chr, items)) {
|
||||
List<Integer> itemids = new LinkedList<>();
|
||||
for (Pair<Item, MapleInventoryType> 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<Pair<Integer, Integer>> expiredCids) {
|
||||
List<String> expiredCnames = new LinkedList<>();
|
||||
for (Pair<Integer, Integer> 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<Pair<Integer, Integer>> expiredCids = new LinkedList<>();
|
||||
List<Pair<Pair<Integer, String>, 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<Integer, Integer> 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<Integer, Integer> 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<Integer, Integer> 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<Pair<Integer, String>, 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,8 +290,10 @@ public class FredrickProcessor {
|
||||
List<Pair<Item, MapleInventoryType>> 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;
|
||||
}
|
||||
|
||||
@@ -98,6 +313,7 @@ public class FredrickProcessor {
|
||||
}
|
||||
|
||||
chr.announce(MaplePacketCreator.fredrickMessage((byte) 0x1E));
|
||||
removeFredrickLog(chr.getId());
|
||||
} else {
|
||||
chr.message("An unknown error has occured.");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_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.
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<Integer, MapleHiredMerchant> hiredMerchants = new HashMap<>();
|
||||
private final Map<Integer, Integer> storedVars = new HashMap<>();
|
||||
private Set<Integer> playersAway = new HashSet<>();
|
||||
private List<MapleExpedition> expeditions = new ArrayList<>();
|
||||
private List<MapleExpeditionType> expedType = new ArrayList<>();
|
||||
private Set<MapleMap> ownedMaps = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<MapleMap, Boolean>()));
|
||||
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,20 +970,31 @@ public final class Channel {
|
||||
}
|
||||
}
|
||||
|
||||
public void registerOwnedMap(MapleMap map) {
|
||||
ownedMaps.add(map);
|
||||
}
|
||||
|
||||
public void unregisterOwnedMap(MapleMap map) {
|
||||
ownedMaps.remove(map);
|
||||
}
|
||||
|
||||
public void runCheckOwnedMapsSchedule() {
|
||||
if (!ownedMaps.isEmpty()) {
|
||||
List<MapleMap> ownedMapsList;
|
||||
|
||||
synchronized (ownedMaps) {
|
||||
ownedMapsList = new ArrayList<>(ownedMaps);
|
||||
}
|
||||
|
||||
for (MapleMap map : ownedMapsList) {
|
||||
map.checkMapOwnerActivity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getChannelSchedulerIndex(int mapid) {
|
||||
if(mapid >= 250000000) {
|
||||
if(mapid >= 900000000) {
|
||||
return 3;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
} else {
|
||||
if(mapid >= 200000000) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
int section = 1000000000 / ServerConstants.CHANNEL_LOCKS;
|
||||
return mapid / section;
|
||||
}
|
||||
|
||||
public void registerMobStatus(int mapid, MonsterStatusEffect mse, Runnable cancelAction, long duration) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -89,26 +89,15 @@ public final class AllianceOperationHandler extends AbstractMaplePacketHandler {
|
||||
String guildName = slea.readMapleAsciiString();
|
||||
|
||||
if (alliance.getGuilds().size() == alliance.getCapacity()) {
|
||||
chr.dropMessage("Your alliance cannot comport any more guilds at the moment.");
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (c.tryacquireClient()) { // thanks Thora for finding out an exploit within cash operations
|
||||
try {
|
||||
final int action = slea.readByte();
|
||||
if (action == 0x03 || action == 0x1E) {
|
||||
slea.readByte();
|
||||
@@ -273,7 +275,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
|
||||
} else {
|
||||
|
||||
/* if (partner.getGender() == chr.getGender()) {
|
||||
chr.dropMessage("You and your partner are the same gender, please buy a friendship ring.");
|
||||
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
|
||||
@@ -341,7 +343,7 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
|
||||
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.");
|
||||
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) {
|
||||
@@ -369,6 +371,12 @@ public final class CashOperationHandler extends AbstractMaplePacketHandler {
|
||||
} else {
|
||||
System.out.println("Unhandled action: " + action + "\n" + slea);
|
||||
}
|
||||
} finally {
|
||||
c.releaseClient();
|
||||
}
|
||||
} else {
|
||||
c.announce(MaplePacketCreator.enableActions());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean checkBirthday(MapleClient c, int idate) {
|
||||
|
||||
@@ -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,7 +79,9 @@ public final class ChangeMapHandler extends AbstractMaplePacketHandler {
|
||||
MaplePortal portal = chr.getMap().getPortal(startwp);
|
||||
slea.readByte();
|
||||
boolean wheel = slea.readShort() > 0;
|
||||
if (targetid != -1 && !chr.isAlive()) {
|
||||
|
||||
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
|
||||
@@ -98,7 +100,7 @@ public final class ChangeMapHandler extends AbstractMaplePacketHandler {
|
||||
chr.respawn(map.getReturnMapId());
|
||||
}
|
||||
}
|
||||
} else if (targetid != -1) {
|
||||
} else {
|
||||
if(chr.isGM()) {
|
||||
MapleMap to = chr.getWarpMap(targetid);
|
||||
chr.changeMap(to, to.getPortal(0));
|
||||
@@ -139,6 +141,8 @@ public final class ChangeMapHandler extends AbstractMaplePacketHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (portal != null && !portal.getPortalStatus()) {
|
||||
c.announce(MaplePacketCreator.blockedMessage(1));
|
||||
c.announce(MaplePacketCreator.enableActions());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
if (MapleInviteCoordinator.answerInvite(InviteType.PARTY, c.getPlayer().getId(), cfrom.getPartyId(), false).getLeft() == InviteResult.DENIED) {
|
||||
cfrom.getClient().announce(MaplePacketCreator.partyStatusMessage(23, c.getPlayer().getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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> invited = new java.util.LinkedList<Invited>();
|
||||
private long nextPruneTime = currentServerTime() + 20 * 60 * 1000;
|
||||
|
||||
@Override
|
||||
public final void handlePacket(SeekableLittleEndianAccessor slea, MapleClient c) {
|
||||
if (currentServerTime() >= nextPruneTime) {
|
||||
Iterator<Invited> 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<Invited> 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;
|
||||
|
||||
@@ -72,6 +72,28 @@ class PairedQuicksort {
|
||||
} while (i <= j);
|
||||
}
|
||||
|
||||
private void PartitionByItemIdReverse(int Esq, int Dir, ArrayList<Item> 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<Item> 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<Item> 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<Item> A, int[] range) {
|
||||
if (range != null) {
|
||||
PartitionByItemIdReverse(range[0], range[1], A);
|
||||
}
|
||||
}
|
||||
|
||||
public PairedQuicksort(ArrayList<Item> 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++) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
if (player.attemptCatchFish(meso)) {
|
||||
player.getMap().disappearingMesoDrop(meso, player, player, player.getPosition());
|
||||
} else {
|
||||
player.getMap().spawnMesoDrop(meso, player.getPosition(), player, player, true, (byte) 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,15 +24,21 @@ 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) {
|
||||
c.tryacquireClient();
|
||||
try {
|
||||
String input;
|
||||
byte mode = slea.readByte();
|
||||
MapleCharacter player = c.getPlayer();
|
||||
@@ -40,15 +46,21 @@ public final class MessengerHandler extends AbstractMaplePacketHandler {
|
||||
MapleMessenger messenger = player.getMessenger();
|
||||
switch (mode) {
|
||||
case 0x00:
|
||||
if (messenger == null) {
|
||||
int messengerid = slea.readInt();
|
||||
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(0);
|
||||
} else {
|
||||
messenger = world.getMessenger(messengerid);
|
||||
if (messenger != null) {
|
||||
Pair<InviteResult, MapleCharacter> 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) {
|
||||
@@ -56,20 +68,32 @@ public final class MessengerHandler extends AbstractMaplePacketHandler {
|
||||
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 {
|
||||
MapleInviteCoordinator.answerInvite(InviteType.MESSENGER, player.getId(), messengerid, false);
|
||||
}
|
||||
break;
|
||||
case 0x02:
|
||||
player.closePlayerMessenger();
|
||||
break;
|
||||
case 0x03:
|
||||
if (messenger.getMembers().size() < 3) {
|
||||
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"));
|
||||
}
|
||||
@@ -86,14 +110,7 @@ public final class MessengerHandler extends AbstractMaplePacketHandler {
|
||||
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));
|
||||
}
|
||||
} else {
|
||||
world.declineChat(targeted, player.getName());
|
||||
}
|
||||
world.declineChat(targeted, player);
|
||||
break;
|
||||
case 0x06:
|
||||
if (messenger != null) {
|
||||
@@ -103,5 +120,8 @@ public final class MessengerHandler extends AbstractMaplePacketHandler {
|
||||
}
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
c.releaseClient();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,6 +106,10 @@ public final class PartyOperationHandler extends AbstractMaplePacketHandler {
|
||||
}
|
||||
case 3: { // join
|
||||
int partyid = slea.readInt();
|
||||
|
||||
Pair<InviteResult, MapleCharacter> 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) {
|
||||
@@ -122,6 +131,9 @@ public final class PartyOperationHandler extends AbstractMaplePacketHandler {
|
||||
} else {
|
||||
c.announce(MaplePacketCreator.serverNotice(5, "You can't join the party as you are already in one."));
|
||||
}
|
||||
} else {
|
||||
c.announce(MaplePacketCreator.serverNotice(5, "You couldn't join the party due to an expired invitation request."));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 4: { // invite
|
||||
@@ -154,7 +166,11 @@ public final class PartyOperationHandler extends AbstractMaplePacketHandler {
|
||||
c.announce(MaplePacketCreator.partyCreated(party, partyplayer.getId()));
|
||||
}
|
||||
if (party.getMembers().size() < 6) {
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -235,6 +237,14 @@ public final class PlayerInteractionHandler extends AbstractMaplePacketHandler {
|
||||
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,11 +269,13 @@ 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()) {
|
||||
@@ -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,16 +742,16 @@ 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)) {
|
||||
if (merchant.getItems().isEmpty()) {
|
||||
merchant.closeOwnerMerchant(chr);
|
||||
} else {
|
||||
merchant.clearMessages();
|
||||
merchant.setOpen(true);
|
||||
merchant.getMap().broadcastMessage(MaplePacketCreator.updateHiredMerchantBox(merchant));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chr.setHiredMerchant(null);
|
||||
c.announce(MaplePacketCreator.enableActions());
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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<String> strLines = new LinkedList<>();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<Integer, Object> invites;
|
||||
final ConcurrentHashMap<Integer, MapleCharacter> inviteFrom;
|
||||
final ConcurrentHashMap<Integer, Integer> inviteTimeouts;
|
||||
|
||||
private InviteType() {
|
||||
invites = new ConcurrentHashMap<>();
|
||||
inviteTimeouts = new ConcurrentHashMap<>();
|
||||
inviteFrom = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
private Map<Integer, Object> getRequestsTable() {
|
||||
return invites;
|
||||
}
|
||||
|
||||
private Map<Integer, Integer> 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<InviteResult, MapleCharacter> answerInvite(InviteType type, int targetCid, Object referenceFrom, boolean answer) {
|
||||
Map<Integer, Object> 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<Integer, Integer> timeoutTable = it.getRequestsTimeoutTable();
|
||||
|
||||
if (!timeoutTable.isEmpty()) {
|
||||
Set<Entry<Integer, Integer>> entrySet = new HashSet<>(timeoutTable.entrySet());
|
||||
for (Entry<Integer, Integer> e : entrySet) {
|
||||
int eVal = e.getValue();
|
||||
|
||||
if (eVal > 5) { // 3min to expire
|
||||
it.removeRequest(e.getKey());
|
||||
} else {
|
||||
timeoutTable.put(e.getKey(), eVal + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,10 +320,15 @@ public class MapleSessionCoordinator {
|
||||
|
||||
MapleClient client = getSessionClient(session);
|
||||
if (client != null) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AntiMulticlientResult attemptLoginSession(IoSession session, String nibbleHwid, int accountId, boolean routineCheck) {
|
||||
if (!ServerConstants.DETERRED_MULTICLIENT) {
|
||||
@@ -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) {
|
||||
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<Entry<Integer, MapleClient>> elist = new ArrayList<>(onlineClients.entrySet());
|
||||
Collections.sort(elist, new Comparator<Entry<Integer, MapleClient>>() {
|
||||
@Override
|
||||
public int compare(Entry<Integer, MapleClient> e1, Entry<Integer, MapleClient> e2) {
|
||||
return e1.getKey().compareTo(e2.getKey());
|
||||
}
|
||||
});
|
||||
|
||||
System.out.println("Current online clients: ");
|
||||
for (Entry<Integer, MapleClient> e : elist) {
|
||||
System.out.println(" " + e.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
if (!onlineRemoteHwids.isEmpty()) {
|
||||
List<String> 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<Entry<String, Set<IoSession>>> elist = new ArrayList<>(loginRemoteHosts.entrySet());
|
||||
|
||||
Collections.sort(elist, new Comparator<Entry<String, Set<IoSession>>>() {
|
||||
@Override
|
||||
public int compare(Entry<String, Set<IoSession>> e1, Entry<String, Set<IoSession>> e2) {
|
||||
return e1.getKey().compareTo(e2.getKey());
|
||||
}
|
||||
});
|
||||
|
||||
System.out.println("Current login sessions: ");
|
||||
for (Entry<String, Set<IoSession>> 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<Entry<Integer, MapleClient>> elist = new ArrayList<>(onlineClients.entrySet());
|
||||
Collections.sort(elist, new Comparator<Entry<Integer, MapleClient>>() {
|
||||
@Override
|
||||
public int compare(Entry<Integer, MapleClient> e1, Entry<Integer, MapleClient> e2) {
|
||||
return e1.getKey().compareTo(e2.getKey());
|
||||
}
|
||||
});
|
||||
|
||||
str += ("Current online clients:\r\n");
|
||||
for (Entry<Integer, MapleClient> e : elist) {
|
||||
str += (" " + e.getKey() + "\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!onlineRemoteHwids.isEmpty()) {
|
||||
List<String> 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<Entry<String, Set<IoSession>>> elist = new ArrayList<>(loginRemoteHosts.entrySet());
|
||||
|
||||
Collections.sort(elist, new Comparator<Entry<String, Set<IoSession>>>() {
|
||||
@Override
|
||||
public int compare(Entry<String, Set<IoSession>> e1, Entry<String, Set<IoSession>> e2) {
|
||||
return e1.getKey().compareTo(e2.getKey());
|
||||
}
|
||||
});
|
||||
|
||||
str += ("Current login sessions:\r\n");
|
||||
for (Entry<String, Set<IoSession>> e : elist) {
|
||||
str += (" " + e.getKey() + ", IP: " + e.getValue() + "\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
c.getAbstractPlayerInteraction().npcTalk(2140000, str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<InviteResult, MapleCharacter> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
|
||||
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<InviteResult, MapleCharacter> 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) {
|
||||
|
||||
@@ -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() {
|
||||
public final byte[] getPacket(String targetName) {
|
||||
if (value >= MANAGING_INVITE.value) {
|
||||
return MaplePacketCreator.responseGuildMessage((byte) value, targetName);
|
||||
} else {
|
||||
return MaplePacketCreator.genericGuildMessage((byte) value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,13 @@ import tools.FilePrinter;
|
||||
import tools.MaplePacketCreator;
|
||||
import tools.data.input.SeekableLittleEndianAccessor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public final class CreateCharHandler extends AbstractMaplePacketHandler {
|
||||
|
||||
private static int[] IDs = {
|
||||
private final static Set<Integer> 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,8 +56,17 @@ 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();
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.server.worker;
|
||||
|
||||
import client.processor.FredrickProcessor;
|
||||
|
||||
/**
|
||||
* @author Ronan
|
||||
*/
|
||||
public class FredrickWorker implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
FredrickProcessor.runFredrickSchedule();
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package net.server.worker;
|
||||
|
||||
import net.server.coordinator.MapleInviteCoordinator;
|
||||
|
||||
/**
|
||||
* @author Ronan
|
||||
*/
|
||||
public class InvitationWorker implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
MapleInviteCoordinator.runTimeoutSchedule();
|
||||
}
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<Channel> channels = new ArrayList<>();
|
||||
private Map<Integer, Byte> pnpcStep = new HashMap<>();
|
||||
@@ -155,10 +162,14 @@ public class World {
|
||||
private ScheduledFuture<?> timedMapObjectsSchedule;
|
||||
private MonitoredReentrantLock timedMapObjectLock = MonitoredReentrantLockFactory.createLock(MonitoredLockType.WORLD_MAPOBJS, true);
|
||||
|
||||
private Map<MapleCharacter, Integer> fishingAttempters = Collections.synchronizedMap(new WeakHashMap<MapleCharacter, Integer>());
|
||||
|
||||
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<MapleCharacter> chars) {
|
||||
SortedMap<Integer, MapleCharacter> charsMap = new TreeMap<>();
|
||||
for(MapleCharacter chr : chars) {
|
||||
@@ -985,17 +1007,26 @@ public class World {
|
||||
|
||||
public void messengerInvite(String sender, int messengerid, String target, int fromchannel) {
|
||||
if (isConnected(target)) {
|
||||
MapleMessenger messenger = getPlayerStorage().getCharacterByName(target).getMessenger();
|
||||
MapleCharacter targetChr = getPlayerStorage().getCharacterByName(target);
|
||||
if (targetChr != null) {
|
||||
MapleMessenger messenger = targetChr.getMessenger();
|
||||
if (messenger == null) {
|
||||
getPlayerStorage().getCharacterByName(target).getClient().announce(MaplePacketCreator.messengerInvite(sender, messengerid));
|
||||
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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addMessengerPlayer(MapleMessenger messenger, String namefrom, int fromchannel, int position) {
|
||||
for (MapleMessengerCharacter messengerchar : messenger.getMembers()) {
|
||||
@@ -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<MapleCharacter> 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<MapleParty> 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;
|
||||
|
||||
|
||||
@@ -849,14 +849,14 @@ 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) {
|
||||
MapleCharacter.SkillEntry skillEntry = getPlayer().getSkills().get(skill);
|
||||
|
||||
if (skillEntry != null) {
|
||||
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);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
package scripting.event;
|
||||
|
||||
import tools.exceptions.EventInstanceInProgressException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+130
-30
@@ -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<Item> items = new ArrayList<>();
|
||||
private List<Item> 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<Integer> 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);
|
||||
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);
|
||||
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,67 +357,111 @@ 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 (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());
|
||||
c2.getClient().announce(MaplePacketCreator.getTradeInvite(c1));
|
||||
|
||||
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);
|
||||
cancelTrade(c1, TradeResult.NO_RESPONSE);
|
||||
MapleInviteCoordinator.answerInvite(InviteType.TRADE, c2.getId(), c1.getId(), false);
|
||||
}
|
||||
} else {
|
||||
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) {
|
||||
Pair<InviteResult, MapleCharacter> 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));
|
||||
@@ -378,6 +470,10 @@ public class MapleTrade {
|
||||
} else {
|
||||
c1.message("The other player has already closed the trade.");
|
||||
}
|
||||
} else {
|
||||
c1.message("This trade invitation already rescinded.");
|
||||
cancelTrade(c1, TradeResult.NO_RESPONSE);
|
||||
}
|
||||
}
|
||||
|
||||
public static void declineTrade(MapleCharacter c) {
|
||||
@@ -385,11 +481,15 @@ public class MapleTrade {
|
||||
if (trade != null) {
|
||||
if (trade.getPartner() != null) {
|
||||
MapleCharacter other = trade.getPartner().getChr();
|
||||
other.getTrade().cancel();
|
||||
other.setTrade(null);
|
||||
if (MapleInviteCoordinator.answerInvite(InviteType.TRADE, c.getId(), other.getId(), false).getLeft() == InviteResult.DENIED) {
|
||||
other.message(c.getName() + " has declined your trade request.");
|
||||
}
|
||||
trade.cancel();
|
||||
|
||||
other.getTrade().cancel(TradeResult.PARTNER_CANCEL.getValue());
|
||||
other.setTrade(null);
|
||||
|
||||
}
|
||||
trade.cancel(TradeResult.NO_RESPONSE.getValue());
|
||||
c.setTrade(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,28 +461,31 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
return takenDamage.containsKey(chr.getId());
|
||||
}
|
||||
|
||||
private void distributeExperienceToParty(int pid, float exp, int killer, int killerLevel, Set<MapleCharacter> underleveled, int minThresholdLevel) {
|
||||
List<MapleCharacter> members = new LinkedList<>();
|
||||
MapleCharacter pchar = getMap().getAnyCharacterFromParty(pid);
|
||||
if(pchar != null) {
|
||||
for(MapleCharacter chr : pchar.getPartyMembersOnSameMap()) {
|
||||
members.add(chr);
|
||||
}
|
||||
} else {
|
||||
MapleCharacter chr = getMap().getCharacterById(killer);
|
||||
if(chr == null) return;
|
||||
private void distributeExperienceToParty(int pid, float exp, int mostDamageCid, int minThresholdLevel, int killerLevel, Set<MapleCharacter> underleveled, Map<MapleCharacter, Float> 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
|
||||
|
||||
members.add(chr);
|
||||
List<MapleCharacter> members;
|
||||
if(pchar != null) {
|
||||
members = pchar.getPartyMembersOnSameMap();
|
||||
} else {
|
||||
members = new LinkedList<>();
|
||||
}
|
||||
|
||||
List<MapleCharacter> 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 {
|
||||
@@ -490,17 +493,17 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
}
|
||||
}
|
||||
|
||||
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<MapleCharacter, Float> personalExpReward, Map<MapleCharacter, Float> partyExpReward) {
|
||||
Set<MapleCharacter> 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<MapleCharacter, Float> personalExpReward = new HashMap<>();
|
||||
Map<MapleCharacter, Float> partyExpReward = new HashMap<>();
|
||||
|
||||
EventInstanceManager eim = getMap().getEventInstance();
|
||||
int minThresholdLevel = calcThresholdLevel(eim != null), killerLevel = Integer.MAX_VALUE;
|
||||
int exp = getExp();
|
||||
@@ -553,21 +571,21 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
xp += exp2;
|
||||
}
|
||||
|
||||
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) {
|
||||
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 {
|
||||
if(mc.getLevel() >= minThresholdLevel) {
|
||||
//NO EXP WILL BE GIVEN for those who are underleveled!
|
||||
giveExpToCharacter(mc, xp, isKiller, 1);
|
||||
}
|
||||
} else {
|
||||
underleveled.add(mc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!expDist.isEmpty()) { // locate on world server the partyid of the missing characters
|
||||
World wserv = map.getWorldServer();
|
||||
@@ -587,52 +605,78 @@ public class MapleMonster extends AbstractLoadedMapleLife {
|
||||
}
|
||||
}
|
||||
|
||||
int mostDamageCid = this.getHighestDamagerId();
|
||||
for (Entry<Integer, Float> 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);
|
||||
}
|
||||
|
||||
propagateExperienceGains(personalExpReward, partyExpReward);
|
||||
}
|
||||
|
||||
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);
|
||||
private float getStatusExpMultiplier(MapleCharacter attacker) {
|
||||
float multiplier = 1.0f;
|
||||
|
||||
int partyExp = 0;
|
||||
if (attacker.getHp() > 0) {
|
||||
exp *= attacker.getExpRate();
|
||||
|
||||
Integer expBonus = attacker.getBuffedValue(MapleBuffStat.EXP_INCREASE);
|
||||
if (expBonus != null) { // exp increase buff found thanks to HighKey21
|
||||
exp += 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);
|
||||
}
|
||||
// thanks Prophecy & Aika for finding out Holy Symbol not being applied on party bonuses
|
||||
Integer holySymbol = attacker.getBuffedValue(MapleBuffStat.HOLY_SYMBOL);
|
||||
if (holySymbol != null) {
|
||||
personalExp *= 1.0 + (holySymbol.doubleValue() / 100.0);
|
||||
multiplier *= (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);
|
||||
MonsterStatusEffect mse = stati.get(MonsterStatus.SHOWDOWN);
|
||||
if (mse != null) {
|
||||
multiplier *= (1.0 + (mse.getStati().get(MonsterStatus.SHOWDOWN).doubleValue() / 100.0));
|
||||
}
|
||||
} finally {
|
||||
statiLock.unlock();
|
||||
}
|
||||
} else {
|
||||
personalExp = Integer.MAX_VALUE;
|
||||
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
attacker.gainExp(personalExp, partyExp, true, false, isKiller);
|
||||
attacker.increaseEquipExp(personalExp);
|
||||
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 player buff found thanks to HighKey21
|
||||
personalExp += expBonus;
|
||||
}
|
||||
|
||||
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 {
|
||||
partyExp = 0.0f;
|
||||
}
|
||||
|
||||
int _partyExp = expValueToInteger(partyExp);
|
||||
|
||||
attacker.gainExp(_personalExp, _partyExp, true, false, false);
|
||||
attacker.increaseEquipExp(_personalExp);
|
||||
attacker.updateQuestMobCount(getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
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());
|
||||
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);
|
||||
}
|
||||
c.getPlayer().changeMap(to, pto); //late resolving makes this harder but prevents us from loading the whole world at once
|
||||
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.");
|
||||
}
|
||||
}
|
||||
if (!changed) {
|
||||
c.announce(MaplePacketCreator.enableActions());
|
||||
|
||||
@@ -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;
|
||||
@@ -192,6 +193,14 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
synchronized (items) {
|
||||
@@ -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,6 +241,7 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
|
||||
}
|
||||
|
||||
private int getQuantityLeft(int itemid) {
|
||||
synchronized (items) {
|
||||
int count = 0;
|
||||
|
||||
for (MaplePlayerShopItem mpsi : items) {
|
||||
@@ -238,6 +252,7 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public void buy(MapleClient c, int item, short quantity) {
|
||||
synchronized (items) {
|
||||
@@ -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,18 +384,19 @@ 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);
|
||||
if(player != null) {
|
||||
@@ -413,6 +433,10 @@ public class MapleHiredMerchant extends AbstractMapleMapObject {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (ServerConstants.USE_ENFORCE_MERCHANT_SAVE) {
|
||||
c.getPlayer().saveCharToDB(false);
|
||||
}
|
||||
|
||||
synchronized (items) {
|
||||
items.clear();
|
||||
}
|
||||
@@ -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<MaplePlayerShopItem> 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() {
|
||||
|
||||
@@ -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<Integer, String> 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;
|
||||
@@ -1089,6 +1092,18 @@ public class MapleMap {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
try {
|
||||
@@ -2254,23 +2269,13 @@ public class MapleMap {
|
||||
}
|
||||
|
||||
public MapleCharacter getAnyCharacterFromParty(int partyid) {
|
||||
chrRLock.lock();
|
||||
try {
|
||||
Set<Integer> 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();
|
||||
}
|
||||
}
|
||||
|
||||
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,6 +2492,7 @@ public class MapleMap {
|
||||
} finally {
|
||||
objectWLock.unlock();
|
||||
}
|
||||
|
||||
if (chr.getPlayerShop() != null) {
|
||||
addMapObject(chr.getPlayerShop());
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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/",
|
||||
|
||||
@@ -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 <code>operation</code>:<br> 2: Trade cancelled by the
|
||||
* other character<br> 7: Trade successful<br> 8: Trade unsuccessful<br>
|
||||
* 9: Cannot carry more one-of-a-kind items<br> 12: Cannot trade on different maps<br>
|
||||
* 13: Cannot trade, game files damaged<br>
|
||||
*
|
||||
* @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,19 +4372,19 @@ public class MaplePacketCreator {
|
||||
}
|
||||
|
||||
/**
|
||||
* 'Char' has denied your guild invitation.
|
||||
* Gets a Heracle/guild message packet.
|
||||
*
|
||||
* @param charname
|
||||
* @return
|
||||
* Possible values for <code>code</code>:<br> 28: guild name already in use<br>
|
||||
* 31: problem in locating players during agreement<br> 33/40: already joined a guild<br>
|
||||
* 35: Cannot make guild<br> 36: problem in player agreement<br> 38: problem during forming guild<br>
|
||||
* 41: max number of players in joining guild<br> 42: character can't be found this channel<br>
|
||||
* 45/48: character not in guild<br> 52: problem in disbanding guild<br> 56: admin cannot make guild<br>
|
||||
* 57: problem in increasing guild size<br>
|
||||
*
|
||||
*
|
||||
* @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());
|
||||
@@ -4392,6 +4392,24 @@ public class MaplePacketCreator {
|
||||
return mplew.getPacket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a guild message packet appended with target name.
|
||||
*
|
||||
* 53: player not accepting guild invites<br>
|
||||
* 54: player already managing an invite<br> 55: player denied an invite<br>
|
||||
*
|
||||
* @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();
|
||||
mplew.writeShort(SendOpcode.GUILD_OPERATION.getValue());
|
||||
@@ -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<Pair<Item, MapleInventoryType>> 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<MapleHiredMerchant.SoldItem> sold = hm.getSold();
|
||||
mplew.write(sold.size());
|
||||
@@ -6349,6 +6368,16 @@ public class MaplePacketCreator {
|
||||
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
|
||||
* - Skill Window. 0x05 - Keyboard Settings. 0x06 - Quest window. 0x09 -
|
||||
@@ -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);
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package scripting.event;
|
||||
package tools.exceptions;
|
||||
|
||||
|
||||
/**
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -247,9 +247,28 @@
|
||||
<int name="delay" value="60"/>
|
||||
</canvas>
|
||||
</imgdir>
|
||||
<canvas name="Fail" width="98" height="38">
|
||||
<vector name="origin" x="49" y="37"/>
|
||||
<imgdir name="Fail">
|
||||
<canvas name="0" width="33" height="104">
|
||||
<vector name="origin" x="21" y="106"/>
|
||||
<int name="delay" value="60"/>
|
||||
</canvas>
|
||||
<canvas name="1" width="35" height="95">
|
||||
<vector name="origin" x="22" y="107"/>
|
||||
<int name="delay" value="60"/>
|
||||
</canvas>
|
||||
<canvas name="2" width="37" height="99">
|
||||
<vector name="origin" x="23" y="107"/>
|
||||
<int name="delay" value="60"/>
|
||||
</canvas>
|
||||
<canvas name="3" width="35" height="106">
|
||||
<vector name="origin" x="22" y="107"/>
|
||||
<int name="delay" value="60"/>
|
||||
</canvas>
|
||||
<canvas name="4" width="33" height="106">
|
||||
<vector name="origin" x="21" y="106"/>
|
||||
<int name="delay" value="60"/>
|
||||
</canvas>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="Combo">
|
||||
<imgdir name="0">
|
||||
|
||||
@@ -15075,6 +15075,10 @@
|
||||
<int name="id" value="4032496"/>
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
<imgdir name="2">
|
||||
<int name="id" value="4032491"/>
|
||||
<int name="count" value="-1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
@@ -15288,6 +15292,10 @@
|
||||
<int name="id" value="4032496"/>
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
<imgdir name="2">
|
||||
<int name="id" value="4032495"/>
|
||||
<int name="count" value="-1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
@@ -15501,6 +15509,12 @@
|
||||
</imgdir>
|
||||
<imgdir name="28219">
|
||||
<imgdir name="0">
|
||||
<imgdir name="item">
|
||||
<imgdir name="0">
|
||||
<int name="id" value="4032493"/>
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="1">
|
||||
<int name="exp" value="5000"/>
|
||||
@@ -15513,6 +15527,10 @@
|
||||
<int name="id" value="4032496"/>
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
<imgdir name="2">
|
||||
<int name="id" value="4032493"/>
|
||||
<int name="count" value="-1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
@@ -15736,6 +15754,12 @@
|
||||
</imgdir>
|
||||
<imgdir name="28238">
|
||||
<imgdir name="0">
|
||||
<imgdir name="item">
|
||||
<imgdir name="0">
|
||||
<int name="id" value="4032492"/>
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="1">
|
||||
<int name="exp" value="5000"/>
|
||||
@@ -15748,6 +15772,10 @@
|
||||
<int name="id" value="4032496"/>
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
<imgdir name="2">
|
||||
<int name="item" value="4032492"/>
|
||||
<int name="count" value="-1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
@@ -15934,10 +15962,22 @@
|
||||
</imgdir>
|
||||
<imgdir name="1">
|
||||
<int name="exp" value="700"/>
|
||||
<imgdir name="item">
|
||||
<imgdir name="0">
|
||||
<int name="id" value="4032494"/>
|
||||
<int name="count" value="-1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="28256">
|
||||
<imgdir name="0">
|
||||
<imgdir name="item">
|
||||
<imgdir name="0">
|
||||
<int name="id" value="4032494"/>
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="1">
|
||||
<int name="exp" value="5000"/>
|
||||
@@ -15950,6 +15990,10 @@
|
||||
<int name="id" value="4032496"/>
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
<imgdir name="2">
|
||||
<int name="id" value="4032494"/>
|
||||
<int name="count" value="-1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
|
||||
@@ -29056,6 +29056,12 @@
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="item">
|
||||
<imgdir name="0">
|
||||
<int name="id" value="4032491"/>
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="28180">
|
||||
@@ -29715,6 +29721,12 @@
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="item">
|
||||
<imgdir name="0">
|
||||
<int name="id" value="4032495"/>
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="0">
|
||||
<int name="npc" value="1032001"/>
|
||||
@@ -30367,6 +30379,12 @@
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="item">
|
||||
<imgdir name="0">
|
||||
<int name="id" value="4032493"/>
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="0">
|
||||
<int name="npc" value="1052001"/>
|
||||
@@ -30968,6 +30986,12 @@
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="item">
|
||||
<imgdir name="0">
|
||||
<int name="id" value="4032492"/>
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="0">
|
||||
<int name="npc" value="1012100"/>
|
||||
@@ -31542,6 +31566,12 @@
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="item">
|
||||
<imgdir name="0">
|
||||
<int name="id" value="4032494"/>
|
||||
<int name="count" value="1"/>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
</imgdir>
|
||||
<imgdir name="0">
|
||||
<int name="npc" value="1090000"/>
|
||||
|
||||
Reference in New Issue
Block a user