From ecf7a81ad12c571f80922ddf0b6054428ea5a160 Mon Sep 17 00:00:00 2001 From: zhaodice <63996691+zhaodice@users.noreply.github.com> Date: Sun, 5 Jun 2022 19:23:19 +0800 Subject: [PATCH] Fix unable to save game data occasionally (#1194) * Fix unable to save game data occasionally * No self-kicking * Game data synchronization * finally * prevent duplicated saving * reverse changing * keep the previous code * Update GameServerInitializer.java * Update GameSession.java * remove sanity check because of try block * a session needs can be created without a pipeline. --- .../game/dungeons/DungeonManager.java | 11 ++- .../emu/grasscutter/game/player/Player.java | 73 +++++++++++-------- .../server/game/GameServerInitializer.java | 8 +- .../grasscutter/server/game/GameSession.java | 37 ++++++++-- .../packet/recv/HandlerGetPlayerTokenReq.java | 48 ++++++++---- .../packet/recv/HandlerQuickUseWidgetReq.java | 2 + 6 files changed, 120 insertions(+), 59 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java index b2fae2fa4..367120bb8 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java @@ -8,6 +8,7 @@ import emu.grasscutter.data.excels.DungeonData; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.SceneType; import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.world.Scene; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.server.game.GameServer; @@ -80,19 +81,21 @@ public class DungeonManager { } public void exitDungeon(Player player) { - if (player.getScene().getSceneType() != SceneType.SCENE_DUNGEON) { + Scene scene = player.getScene(); + + if (scene==null || scene.getSceneType() != SceneType.SCENE_DUNGEON) { return; } // Get previous scene - int prevScene = player.getScene().getPrevScene() > 0 ? player.getScene().getPrevScene() : 3; + int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3; // Get previous position - DungeonData dungeonData = player.getScene().getDungeonData(); + DungeonData dungeonData = scene.getDungeonData(); Position prevPos = new Position(GameConstants.START_POSITION); if (dungeonData != null) { - ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, player.getScene().getPrevScenePoint()); + ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint()); if (entry != null) { prevPos.set(entry.getPointData().getTranPos()); diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index a35821852..ac22900dd 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -2,6 +2,7 @@ package emu.grasscutter.game.player; import dev.morphia.annotations.*; import emu.grasscutter.GameConstants; +import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.excels.PlayerLevelData; import emu.grasscutter.database.DatabaseHelper; @@ -1211,13 +1212,6 @@ public class Player { this.getProfile().syncWithCharacter(this); } - // Check if player object exists in server - // TODO - optimize - Player exists = this.getServer().getPlayerByUid(getUid()); - if (exists != null) { - exists.getSession().close(); - } - // Load from db this.getAvatars().loadFromDatabase(); this.getInventory().loadFromDatabase(); @@ -1226,12 +1220,13 @@ public class Player { this.getFriendsList().loadFromDatabase(); this.getMailHandler().loadFromDatabase(); this.getQuestManager().loadFromDatabase(); - + // Add to gameserver (Always handle last) if (getSession().isActive()) { getServer().registerPlayer(this); getProfile().setPlayer(this); // Set online } + } public void onLogin() { @@ -1291,34 +1286,48 @@ public class Player { } public void onLogout() { - // stop stamina calculation - getStaminaManager().stopSustainedStaminaHandler(); + try{ + // stop stamina calculation + getStaminaManager().stopSustainedStaminaHandler(); - // force to leave the dungeon - if (getScene().getSceneType() == SceneType.SCENE_DUNGEON) { + // force to leave the dungeon (inside has a "if") this.getServer().getDungeonManager().exitDungeon(this); + + // Leave world + if (this.getWorld() != null) { + this.getWorld().removePlayer(this); + } + + // Status stuff + this.getProfile().syncWithCharacter(this); + this.getProfile().setPlayer(null); // Set offline + + this.getCoopRequests().clear(); + + // Save to db + this.save(); + this.getTeamManager().saveAvatars(); + this.getFriendsList().save(); + + // Call quit event. + PlayerQuitEvent event = new PlayerQuitEvent(this); event.call(); + + //reset wood + getDeforestationManager().resetWood(); + + }catch (Throwable e){ + e.printStackTrace(); + Grasscutter.getLogger().warn("Player (UID {}) save failure", getUid()); + }finally { + removeFromServer(); } - // Leave world - if (this.getWorld() != null) { - this.getWorld().removePlayer(this); - } + } - // Status stuff - this.getProfile().syncWithCharacter(this); - this.getProfile().setPlayer(null); // Set offline - - this.getCoopRequests().clear(); - - // Save to db - this.save(); - this.getTeamManager().saveAvatars(); - this.getFriendsList().save(); - - // Call quit event. - PlayerQuitEvent event = new PlayerQuitEvent(this); event.call(); - - //reset wood - getDeforestationManager().resetWood(); + public void removeFromServer() { + // Remove from server. + //Note: DON'T DELETE BY UID,BECAUSE THERE ARE MULTIPLE SAME UID PLAYERS WHEN DUPLICATED LOGIN! + //so I decide to delete by object rather than uid + getServer().getPlayers().values().removeIf(player1 -> player1 == this); } public enum SceneLoadState { diff --git a/src/main/java/emu/grasscutter/server/game/GameServerInitializer.java b/src/main/java/emu/grasscutter/server/game/GameServerInitializer.java index 1c5f51388..6c48e30c1 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServerInitializer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServerInitializer.java @@ -13,8 +13,10 @@ public class GameServerInitializer extends KcpServerInitializer { @Override protected void initChannel(UkcpChannel ch) throws Exception { - ChannelPipeline pipeline = ch.pipeline(); - GameSession session = new GameSession(server); - pipeline.addLast(session); + ChannelPipeline pipeline=null; + if(ch!=null){ + pipeline = ch.pipeline(); + } + new GameSession(server,pipeline); } } diff --git a/src/main/java/emu/grasscutter/server/game/GameSession.java b/src/main/java/emu/grasscutter/server/game/GameSession.java index fefc73d15..397d5ba6f 100644 --- a/src/main/java/emu/grasscutter/server/game/GameSession.java +++ b/src/main/java/emu/grasscutter/server/game/GameSession.java @@ -3,6 +3,8 @@ package emu.grasscutter.server.game; import java.io.File; import java.net.InetSocketAddress; import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.Map; import java.util.Set; import emu.grasscutter.Grasscutter; @@ -17,9 +19,11 @@ import emu.grasscutter.server.event.game.SendPacketEvent; import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.Utils; +import io.jpower.kcp.netty.UkcpChannel; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.Configuration.*; @@ -36,11 +40,30 @@ public class GameSession extends KcpChannel { private int clientTime; private long lastPingTime; private int lastClientSeq = 10; - + + private final ChannelPipeline pipeline; + @Override + public void close() { + setState(SessionState.INACTIVE); + //send disconnection pack in case of reconnection + try { + send(new BasePacket(PacketOpcodes.ServerDisconnectClientNotify)); + }catch (Throwable ignore){ + + } + super.close(); + } public GameSession(GameServer server) { + this(server,null); + } + public GameSession(GameServer server, ChannelPipeline pipeline) { this.server = server; this.state = SessionState.WAITING_FOR_TOKEN; this.lastPingTime = System.currentTimeMillis(); + this.pipeline = pipeline; + if(pipeline!=null) { + pipeline.addLast(this); + } } public GameServer getServer() { @@ -124,13 +147,17 @@ public class GameSession extends KcpChannel { // Set state so no more packets can be handled this.setState(SessionState.INACTIVE); - + // Save after disconnecting if (this.isLoggedIn()) { + Player player = getPlayer(); // Call logout event. - getPlayer().onLogout(); - // Remove from server. - getServer().getPlayers().remove(getPlayer().getUid()); + player.onLogout(); + } + try { + pipeline.remove(this); + } catch (Throwable ignore) { + } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java index 1e68377a4..6546b6f1b 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerGetPlayerTokenReq.java @@ -11,6 +11,7 @@ import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.GetPlayerTokenReqOuterClass.GetPlayerTokenReq; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.server.event.game.PlayerCreationEvent; +import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession.SessionState; import emu.grasscutter.server.packet.send.PacketGetPlayerTokenRsp; @@ -20,28 +21,45 @@ public class HandlerGetPlayerTokenReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - // Max players limit - if (ACCOUNT.maxPlayer > -1 && Grasscutter.getGameServer().getPlayers().size() >= ACCOUNT.maxPlayer) { - session.close(); - return; - } - + GetPlayerTokenReq req = GetPlayerTokenReq.parseFrom(payload); // Authenticate Account account = DatabaseHelper.getAccountById(req.getAccountUid()); - if (account == null) { - return; - } - - // Check token - if (!account.getToken().equals(req.getAccountToken())) { + if (account == null || !account.getToken().equals(req.getAccountToken())) { return; } // Set account session.setAccount(account); - + + + // Check if player object exists in server + // NOTE: CHECKING MUST SITUATED HERE (BEFORE getPlayerByUid)! because to save firstly ,to load secondly !!! + // TODO - optimize + boolean kicked = false; + Player exists = Grasscutter.getGameServer().getPlayerByAccountId(account.getId()); + if (exists != null) { + GameSession existsSession = exists.getSession(); + if (existsSession != session) {// No self-kicking + exists.onLogout();//must save immediately , or the below will load old data + existsSession.close(); + Grasscutter.getLogger().warn("Player {} was kicked due to duplicated login", account.getUsername()); + kicked = true; + } + } + + //NOTE: If there are 5 online players, max count of player is 5, + // a new client want to login by kicking one of them , + // I think it should be allowed + if(!kicked) { + // Max players limit + if (ACCOUNT.maxPlayer > -1 && Grasscutter.getGameServer().getPlayers().size() >= ACCOUNT.maxPlayer) { + session.close(); + return; + } + } + // Get player Player player = DatabaseHelper.getPlayerByAccount(account); @@ -57,10 +75,10 @@ public class HandlerGetPlayerTokenReq extends PacketHandler { // Save to db DatabaseHelper.generatePlayerUid(player, nextPlayerUid); } - + // Set player object for session session.setPlayer(player); - + // Load player from database player.loadFromDatabase(); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerQuickUseWidgetReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerQuickUseWidgetReq.java index 6e2b6097f..6e0149649 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerQuickUseWidgetReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerQuickUseWidgetReq.java @@ -1,5 +1,6 @@ package emu.grasscutter.server.packet.recv; +import emu.grasscutter.Grasscutter; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.inventory.InventoryTab; @@ -45,6 +46,7 @@ public class HandlerQuickUseWidgetReq extends PacketHandler { BasePacket rsp = new BasePacket(PacketOpcodes.QuickUseWidgetRsp); rsp.setData(proto); session.send(rsp); + Grasscutter.getLogger().warn("class has no effects in the game, feel free to implement it"); // but no effects in the game, feel free to implement it! } }