From d5682a5f65152c8781e0cdd94ef554a74b2d4a5d Mon Sep 17 00:00:00 2001 From: P0nk Date: Tue, 8 Aug 2023 21:51:12 +0200 Subject: [PATCH] Add client disconnection logic to TransitionService Problem: disconnecting requires access to CharacterSaver, which is not available in Client. Having it in a service like this solves that problem. Next step is to migrate all calls to Client#disconnect and Client#forceDisconnect to their TransitionService counterparts. --- src/main/java/client/Client.java | 8 +- .../java/client/autoban/AutobanManager.java | 8 +- .../java/client/command/CommandContext.java | 6 +- .../command/commands/gm2/SummonCommand.java | 2 +- src/main/java/net/ChannelDependencies.java | 6 +- src/main/java/net/PacketProcessor.java | 2 +- src/main/java/net/server/Server.java | 8 +- .../handlers/ChangeChannelHandler.java | 10 +- src/main/java/service/ChannelService.java | 79 ------ src/main/java/service/TransitionService.java | 245 ++++++++++++++++++ 10 files changed, 272 insertions(+), 102 deletions(-) delete mode 100644 src/main/java/service/ChannelService.java create mode 100644 src/main/java/service/TransitionService.java diff --git a/src/main/java/client/Client.java b/src/main/java/client/Client.java index 27a21e4b4e..34dc92a406 100644 --- a/src/main/java/client/Client.java +++ b/src/main/java/client/Client.java @@ -305,6 +305,10 @@ public class Client extends ChannelInboundHandlerAdapter { return loggedIn; } + public boolean isInTransition() { + return serverTransition; + } + public boolean hasBannedIP() { boolean ret = false; try (Connection con = DatabaseConnection.getConnection(); @@ -833,7 +837,7 @@ public class Client extends ChannelInboundHandlerAdapter { } } - private synchronized boolean tryDisconnect() { + public synchronized boolean tryDisconnect() { if (disconnecting) { return false; } @@ -924,7 +928,7 @@ public class Client extends ChannelInboundHandlerAdapter { } } - private void clear() { + public void clear() { // player hard reference removal thanks to Steve (kaito1410) if (this.player != null) { this.player.empty(true); // clears schedules and stuff diff --git a/src/main/java/client/autoban/AutobanManager.java b/src/main/java/client/autoban/AutobanManager.java index a227ca7e1c..9232799439 100644 --- a/src/main/java/client/autoban/AutobanManager.java +++ b/src/main/java/client/autoban/AutobanManager.java @@ -7,6 +7,7 @@ package client.autoban; import client.Character; import config.YamlConfig; +import net.netty.GameViolationException; import net.server.Server; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -118,11 +119,10 @@ public class AutobanManager { if (this.timestamp[type] == time) { this.timestampcounter[type]++; if (this.timestampcounter[type] >= times) { - if (YamlConfig.config.server.USE_AUTOBAN) { - chr.getClient().disconnect(false, false); - } - log.info("Autoban - Chr {} was caught spamming TYPE {} and has been disconnected", chr, type); + if (YamlConfig.config.server.USE_AUTOBAN) { + throw new GameViolationException("Auto ban"); + } } } else { this.timestamp[type] = time; diff --git a/src/main/java/client/command/CommandContext.java b/src/main/java/client/command/CommandContext.java index 4e443048e0..69aab059dd 100644 --- a/src/main/java/client/command/CommandContext.java +++ b/src/main/java/client/command/CommandContext.java @@ -3,15 +3,15 @@ package client.command; import database.character.CharacterSaver; import database.drop.DropProvider; import server.shop.ShopFactory; -import service.ChannelService; +import service.TransitionService; /** * @author Ponk */ public record CommandContext(CommandsExecutor commandsExecutor, DropProvider dropProvider, ShopFactory shopFactory, - CharacterSaver characterSaver, ChannelService channelService) { + CharacterSaver characterSaver, TransitionService transitionService) { public CommandContext with(CommandsExecutor ce) { - return new CommandContext(ce, this.dropProvider, this.shopFactory, this.characterSaver, this.channelService); + return new CommandContext(ce, this.dropProvider, this.shopFactory, this.characterSaver, this.transitionService); } } diff --git a/src/main/java/client/command/commands/gm2/SummonCommand.java b/src/main/java/client/command/commands/gm2/SummonCommand.java index 338085d52a..39d45979f6 100644 --- a/src/main/java/client/command/commands/gm2/SummonCommand.java +++ b/src/main/java/client/command/commands/gm2/SummonCommand.java @@ -63,7 +63,7 @@ public class SummonCommand extends Command { if (player.getClient().getChannel() != victim.getClient().getChannel()) {//And then change channel if needed. victim.dropMessage("Changing channel, please wait a moment."); - ctx.channelService().changeChannel(victim.getClient(), player.getClient().getChannel()); + ctx.transitionService().changeChannel(victim.getClient(), player.getClient().getChannel()); } try { diff --git a/src/main/java/net/ChannelDependencies.java b/src/main/java/net/ChannelDependencies.java index 16ac982455..59597e105d 100644 --- a/src/main/java/net/ChannelDependencies.java +++ b/src/main/java/net/ChannelDependencies.java @@ -7,8 +7,8 @@ import database.character.CharacterLoader; import database.character.CharacterSaver; import database.drop.DropProvider; import server.shop.ShopFactory; -import service.ChannelService; import service.NoteService; +import service.TransitionService; import java.util.Objects; @@ -18,7 +18,7 @@ import java.util.Objects; public record ChannelDependencies( CharacterLoader characterLoader, CharacterSaver characterSaver, NoteService noteService, FredrickProcessor fredrickProcessor, MakerProcessor makerProcessor, DropProvider dropProvider, - CommandsExecutor commandsExecutor, ShopFactory shopFactory, ChannelService channelService + CommandsExecutor commandsExecutor, ShopFactory shopFactory, TransitionService transitionService ) { public ChannelDependencies { @@ -30,6 +30,6 @@ public record ChannelDependencies( Objects.requireNonNull(dropProvider); Objects.requireNonNull(commandsExecutor); Objects.requireNonNull(shopFactory); - Objects.requireNonNull(channelService); + Objects.requireNonNull(transitionService); } } diff --git a/src/main/java/net/PacketProcessor.java b/src/main/java/net/PacketProcessor.java index 34fcd99c28..b84ce52b70 100644 --- a/src/main/java/net/PacketProcessor.java +++ b/src/main/java/net/PacketProcessor.java @@ -140,7 +140,7 @@ public final class PacketProcessor { registerHandler(RecvOpcode.NAME_TRANSFER, new TransferNameHandler()); registerHandler(RecvOpcode.CHECK_CHAR_NAME, new TransferNameResultHandler()); registerHandler(RecvOpcode.WORLD_TRANSFER, new TransferWorldHandler()); - registerHandler(RecvOpcode.CHANGE_CHANNEL, new ChangeChannelHandler(channelDeps.channelService())); + registerHandler(RecvOpcode.CHANGE_CHANNEL, new ChangeChannelHandler(channelDeps.transitionService())); registerHandler(RecvOpcode.STRANGE_DATA, LoginRequiringNoOpHandler.getInstance()); registerHandler(RecvOpcode.GENERAL_CHAT, new GeneralChatHandler(channelDeps.commandsExecutor())); registerHandler(RecvOpcode.WHISPER, new WhisperHandler()); diff --git a/src/main/java/net/server/Server.java b/src/main/java/net/server/Server.java index 9da12df0a6..7c8d065e14 100644 --- a/src/main/java/net/server/Server.java +++ b/src/main/java/net/server/Server.java @@ -76,8 +76,8 @@ import server.expeditions.ExpeditionBossLog; import server.life.PlayerNPCFactory; import server.quest.Quest; import server.shop.ShopFactory; -import service.ChannelService; import service.NoteService; +import service.TransitionService; import tools.DatabaseConnection; import tools.Pair; @@ -982,17 +982,17 @@ public class Server { MonsterCardDao monsterCardDao = new MonsterCardDao(connection); CharacterLoader characterLoader = new CharacterLoader(monsterCardDao); CharacterSaver characterSaver = new CharacterSaver(monsterCardDao); - ChannelService channelService = new ChannelService(characterSaver); + TransitionService transitionService = new TransitionService(characterSaver); NoteService noteService = new NoteService(new NoteDao(connection)); MakerProcessor makerProcessor = new MakerProcessor(new MakerInfoProvider(new MakerDao(connection))); FredrickProcessor fredrickProcessor = new FredrickProcessor(noteService); DropProvider dropProvider = new DropProvider(new DropDao(connection)); ShopFactory shopFactory = new ShopFactory(new ShopDao(connection)); CommandContext commandContext = new CommandContext(null, dropProvider, shopFactory, - characterSaver, channelService); + characterSaver, transitionService); CommandsExecutor commandsExecutor = new CommandsExecutor(commandContext); ChannelDependencies channelDependencies = new ChannelDependencies(characterLoader, characterSaver, noteService, - fredrickProcessor, makerProcessor, dropProvider, commandsExecutor, shopFactory, channelService); + fredrickProcessor, makerProcessor, dropProvider, commandsExecutor, shopFactory, transitionService); PacketProcessor.registerGameHandlerDependencies(channelDependencies); diff --git a/src/main/java/net/server/channel/handlers/ChangeChannelHandler.java b/src/main/java/net/server/channel/handlers/ChangeChannelHandler.java index c8ee0b9cc1..3bc40f50cf 100644 --- a/src/main/java/net/server/channel/handlers/ChangeChannelHandler.java +++ b/src/main/java/net/server/channel/handlers/ChangeChannelHandler.java @@ -27,16 +27,16 @@ import net.AbstractPacketHandler; import net.netty.GameViolationException; import net.packet.InPacket; import net.server.Server; -import service.ChannelService; +import service.TransitionService; /** * @author Matze */ public final class ChangeChannelHandler extends AbstractPacketHandler { - private final ChannelService channelService; + private final TransitionService transitionService; - public ChangeChannelHandler(ChannelService channelService) { - this.channelService = channelService; + public ChangeChannelHandler(TransitionService transitionService) { + this.transitionService = transitionService; } @Override @@ -51,6 +51,6 @@ public final class ChangeChannelHandler extends AbstractPacketHandler { return; } - channelService.changeChannel(c, channel); + transitionService.changeChannel(c, channel); } } diff --git a/src/main/java/service/ChannelService.java b/src/main/java/service/ChannelService.java deleted file mode 100644 index 9cbc6c5fb5..0000000000 --- a/src/main/java/service/ChannelService.java +++ /dev/null @@ -1,79 +0,0 @@ -package service; - -import client.Client; -import client.inventory.InventoryType; -import database.character.CharacterSaver; -import net.server.Server; -import server.maps.FieldLimit; -import server.maps.MiniDungeonInfo; -import tools.PacketCreator; - -import java.io.IOException; -import java.net.InetAddress; - -public class ChannelService { - private final Server server = Server.getInstance(); - private final CharacterSaver chrSaver; - - public ChannelService(CharacterSaver characterSaver) { - this.chrSaver = characterSaver; - } - - public void changeChannel(Client c, int channel) { - var chr = c.getPlayer(); - if (chr.isBanned()) { - c.disconnect(false, false); - return; - } - - if (!chr.isAlive() || FieldLimit.CANNOTMIGRATE.check(chr.getMap().getFieldLimit())) { - c.sendPacket(PacketCreator.enableActions()); - return; - } - - if (MiniDungeonInfo.isDungeonMap(chr.getMapId())) { - c.sendPacket(PacketCreator.serverNotice(5, "Changing channels or entering Cash Shop or MTS are disabled when inside a Mini-Dungeon.")); - c.sendPacket(PacketCreator.enableActions()); - return; - } - - String[] socket = Server.getInstance().getInetSocket(c, c.getWorld(), channel); - if (socket == null) { - c.sendPacket(PacketCreator.serverNotice(1, "Channel " + channel + " is currently disabled. Try another channel.")); - c.sendPacket(PacketCreator.enableActions()); - return; - } - - chr.closePlayerInteractions(); - chr.closePartySearchInteractions(); - - chr.unregisterChairBuff(); - server.getPlayerBuffStorage().addBuffsToStorage(chr.getId(), chr.getAllBuffs()); - server.getPlayerBuffStorage().addDiseasesToStorage(chr.getId(), chr.getAllDiseases()); - chr.setDisconnectedFromChannelWorld(); - chr.notifyMapTransferToPartner(-1); - chr.removeIncomingInvites(); - chr.cancelAllBuffs(true); - chr.cancelAllDebuffs(); - chr.cancelBuffExpireTask(); - chr.cancelDiseaseExpireTask(); - chr.cancelSkillCooldownTask(); - chr.cancelQuestExpirationTask(); - //Cancelling magicdoor? Nope - //Cancelling mounts? Noty - - chr.getInventory(InventoryType.EQUIPPED).checked(false); //test - chr.getMap().removePlayer(chr); - chr.clearBanishPlayerData(); - c.getChannelServer().removePlayer(chr); - - chrSaver.save(chr); - - chr.setSessionTransitionState(); - try { - c.sendPacket(PacketCreator.getChannelChange(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]))); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/src/main/java/service/TransitionService.java b/src/main/java/service/TransitionService.java new file mode 100644 index 0000000000..0d435f32c7 --- /dev/null +++ b/src/main/java/service/TransitionService.java @@ -0,0 +1,245 @@ +package service; + +import client.BuddyList; +import client.Client; +import client.inventory.InventoryType; +import config.YamlConfig; +import constants.id.MapId; +import database.character.CharacterSaver; +import net.server.Server; +import net.server.guild.Guild; +import net.server.guild.GuildCharacter; +import net.server.guild.GuildPackets; +import net.server.world.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import scripting.event.EventInstanceManager; +import server.ThreadManager; +import server.maps.FieldLimit; +import server.maps.MapleMap; +import server.maps.MiniDungeonInfo; +import tools.PacketCreator; + +import java.io.IOException; +import java.net.InetAddress; + +public class TransitionService { + private static final Logger log = LoggerFactory.getLogger(TransitionService.class); + private final Server server = Server.getInstance(); + private final CharacterSaver chrSaver; + + public TransitionService(CharacterSaver characterSaver) { + this.chrSaver = characterSaver; + } + + public void changeChannel(Client c, int channel) { + var chr = c.getPlayer(); + if (chr.isBanned()) { + disconnect(c, false, false); + return; + } + + if (!chr.isAlive() || FieldLimit.CANNOTMIGRATE.check(chr.getMap().getFieldLimit())) { + c.sendPacket(PacketCreator.enableActions()); + return; + } + + if (MiniDungeonInfo.isDungeonMap(chr.getMapId())) { + c.sendPacket(PacketCreator.serverNotice(5, "Changing channels or entering Cash Shop or MTS are disabled when inside a Mini-Dungeon.")); + c.sendPacket(PacketCreator.enableActions()); + return; + } + + String[] socket = Server.getInstance().getInetSocket(c, c.getWorld(), channel); + if (socket == null) { + c.sendPacket(PacketCreator.serverNotice(1, "Channel " + channel + " is currently disabled. Try another channel.")); + c.sendPacket(PacketCreator.enableActions()); + return; + } + + chr.closePlayerInteractions(); + chr.closePartySearchInteractions(); + + chr.unregisterChairBuff(); + server.getPlayerBuffStorage().addBuffsToStorage(chr.getId(), chr.getAllBuffs()); + server.getPlayerBuffStorage().addDiseasesToStorage(chr.getId(), chr.getAllDiseases()); + chr.setDisconnectedFromChannelWorld(); + chr.notifyMapTransferToPartner(-1); + chr.removeIncomingInvites(); + chr.cancelAllBuffs(true); + chr.cancelAllDebuffs(); + chr.cancelBuffExpireTask(); + chr.cancelDiseaseExpireTask(); + chr.cancelSkillCooldownTask(); + chr.cancelQuestExpirationTask(); + //Cancelling magicdoor? Nope + //Cancelling mounts? Noty + + chr.getInventory(InventoryType.EQUIPPED).checked(false); //test + chr.getMap().removePlayer(chr); + chr.clearBanishPlayerData(); + c.getChannelServer().removePlayer(chr); + + chrSaver.save(chr); + + chr.setSessionTransitionState(); + try { + c.sendPacket(PacketCreator.getChannelChange(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1]))); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // TODO: take code from Client#disconnect & forceDisconnect. Move it here. + // It's not gonna be easy to move all instances of c.disconnect, but it has to be done. + public void disconnect(final Client c, final boolean shutdown, final boolean cashShop) { + if (c.tryDisconnect()) { + ThreadManager.getInstance().newTask(() -> disconnectInternal(c, shutdown, cashShop)); + } + } + + public void forceDisconnect(Client c) { + if (c.tryDisconnect()) { + disconnectInternal(c, true, false); + } + } + + private void disconnectInternal(Client c, boolean shutdown, boolean cashShop) { + var chr = c.getPlayer(); + if (chr != null && chr.isLoggedin() && chr.getClient() != null) { + final int messengerid = chr.getMessenger() == null ? 0 : chr.getMessenger().getId(); + final BuddyList bl = chr.getBuddylist(); + final MessengerCharacter messengerChr = new MessengerCharacter(chr, 0); + final GuildCharacter guildChr = chr.getMGC(); + final Guild guild = chr.getGuild(); + + chr.cancelMagicDoor(); + + final World wserv = c.getWorldServer(); // obviously wserv is NOT null if this chr was online on it + try { + removePlayer(c, wserv, c.isInTransition()); + + final int channel = c.getChannel(); + if (!(channel == -1 || shutdown)) { + if (!cashShop) { + if (!c.isInTransition()) { // meaning not changing channels + if (messengerid > 0) { + wserv.leaveMessenger(messengerid, messengerChr); + } + + chr.forfeitExpirableQuests(); //This is for those quests that you have to stay logged in for a certain amount of time + + if (guild != null) { + final Server server = Server.getInstance(); + server.setGuildMemberOnline(chr, false, chr.getClient().getChannel()); + chr.sendPacket(GuildPackets.showGuildInfo(chr)); + } + if (bl != null) { + wserv.loggedOff(chr.getName(), chr.getId(), channel, chr.getBuddylist().getBuddyIds()); + } + } + } else { + if (!c.isInTransition()) { // if dc inside of cash shop. + if (bl != null) { + wserv.loggedOff(chr.getName(), chr.getId(), channel, chr.getBuddylist().getBuddyIds()); + } + } + } + } + } catch (final Exception e) { + log.error("Account stuck", e); + } finally { + if (!c.isInTransition()) { + if (guildChr != null) { + guildChr.setCharacter(null); + } + wserv.removePlayer(chr); + //getChannelServer().removePlayer(player); already being done + + chr.cancelAllDebuffs(); + chrSaver.save(chr); + + chr.logOff(); + if (YamlConfig.config.server.INSTANT_NAME_CHANGE) { + chr.doPendingNameChange(); + } + c.clear(); + } else { + c.getChannelServer().removePlayer(chr); + + chr.cancelAllDebuffs(); + chrSaver.save(chr); + } + } + } + } + + private void removePlayer(Client c, World world, boolean serverTransition) { + var chr = c.getPlayer(); + try { + chr.setDisconnectedFromChannelWorld(); + chr.notifyMapTransferToPartner(-1); + chr.removeIncomingInvites(); + chr.cancelAllBuffs(true); + + chr.closePlayerInteractions(); + chr.closePartySearchInteractions(); + + if (!serverTransition) { // thanks MedicOP for detecting an issue with party leader change on changing channels + removePartyPlayer(c, world); + + EventInstanceManager eim = chr.getEventInstance(); + if (eim != null) { + eim.playerDisconnected(chr); + } + + if (chr.getMonsterCarnival() != null) { + chr.getMonsterCarnival().playerDisconnected(chr.getId()); + } + + if (chr.getAriantColiseum() != null) { + chr.getAriantColiseum().playerDisconnected(chr); + } + } + + if (chr.getMap() != null) { + int mapId = chr.getMapId(); + chr.getMap().removePlayer(chr); + if (MapId.isDojo(mapId)) { + c.getChannelServer().freeDojoSectionIfEmpty(mapId); + } + + if (chr.getMap().getHPDec() > 0) { + world.removePlayerHpDecrease(chr); + } + } + + } catch (final Throwable t) { + log.error("Account stuck", t); + } + } + + private void removePartyPlayer(Client c, World world) { + var chr = c.getPlayer(); + MapleMap map = chr.getMap(); + final Party party = chr.getParty(); + final int idz = chr.getId(); + + if (party != null) { + final PartyCharacter chrp = new PartyCharacter(chr); + chrp.setOnline(false); + world.updateParty(party.getId(), PartyOperation.LOG_ONOFF, chrp); + if (party.getLeader().getId() == idz && map != null) { + PartyCharacter lchr = null; + for (PartyCharacter pchr : party.getMembers()) { + if (pchr != null && pchr.getId() != idz && (lchr == null || lchr.getLevel() <= pchr.getLevel()) && map.getCharacterById(pchr.getId()) != null) { + lchr = pchr; + } + } + if (lchr != null) { + world.updateParty(party.getId(), PartyOperation.CHANGE_LEADER, lchr); + } + } + } + } +}