Commit ecf7a81a authored by zhaodice's avatar zhaodice Committed by GitHub
Browse files

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.
parent 934fb587
...@@ -8,6 +8,7 @@ import emu.grasscutter.data.excels.DungeonData; ...@@ -8,6 +8,7 @@ import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.SceneType; import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
...@@ -80,19 +81,21 @@ public class DungeonManager { ...@@ -80,19 +81,21 @@ public class DungeonManager {
} }
public void exitDungeon(Player player) { 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; return;
} }
// Get previous scene // Get previous scene
int prevScene = player.getScene().getPrevScene() > 0 ? player.getScene().getPrevScene() : 3; int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3;
// Get previous position // Get previous position
DungeonData dungeonData = player.getScene().getDungeonData(); DungeonData dungeonData = scene.getDungeonData();
Position prevPos = new Position(GameConstants.START_POSITION); Position prevPos = new Position(GameConstants.START_POSITION);
if (dungeonData != null) { if (dungeonData != null) {
ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, player.getScene().getPrevScenePoint()); ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
if (entry != null) { if (entry != null) {
prevPos.set(entry.getPointData().getTranPos()); prevPos.set(entry.getPointData().getTranPos());
......
...@@ -2,6 +2,7 @@ package emu.grasscutter.game.player; ...@@ -2,6 +2,7 @@ package emu.grasscutter.game.player;
import dev.morphia.annotations.*; import dev.morphia.annotations.*;
import emu.grasscutter.GameConstants; import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.PlayerLevelData; import emu.grasscutter.data.excels.PlayerLevelData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
...@@ -1211,13 +1212,6 @@ public class Player { ...@@ -1211,13 +1212,6 @@ public class Player {
this.getProfile().syncWithCharacter(this); 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 // Load from db
this.getAvatars().loadFromDatabase(); this.getAvatars().loadFromDatabase();
this.getInventory().loadFromDatabase(); this.getInventory().loadFromDatabase();
...@@ -1232,6 +1226,7 @@ public class Player { ...@@ -1232,6 +1226,7 @@ public class Player {
getServer().registerPlayer(this); getServer().registerPlayer(this);
getProfile().setPlayer(this); // Set online getProfile().setPlayer(this); // Set online
} }
} }
public void onLogin() { public void onLogin() {
...@@ -1291,13 +1286,13 @@ public class Player { ...@@ -1291,13 +1286,13 @@ public class Player {
} }
public void onLogout() { public void onLogout() {
try{
// stop stamina calculation // stop stamina calculation
getStaminaManager().stopSustainedStaminaHandler(); getStaminaManager().stopSustainedStaminaHandler();
// force to leave the dungeon // force to leave the dungeon (inside has a "if")
if (getScene().getSceneType() == SceneType.SCENE_DUNGEON) {
this.getServer().getDungeonManager().exitDungeon(this); this.getServer().getDungeonManager().exitDungeon(this);
}
// Leave world // Leave world
if (this.getWorld() != null) { if (this.getWorld() != null) {
this.getWorld().removePlayer(this); this.getWorld().removePlayer(this);
...@@ -1319,6 +1314,20 @@ public class Player { ...@@ -1319,6 +1314,20 @@ public class Player {
//reset wood //reset wood
getDeforestationManager().resetWood(); getDeforestationManager().resetWood();
}catch (Throwable e){
e.printStackTrace();
Grasscutter.getLogger().warn("Player (UID {}) save failure", getUid());
}finally {
removeFromServer();
}
}
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 { public enum SceneLoadState {
......
...@@ -13,8 +13,10 @@ public class GameServerInitializer extends KcpServerInitializer { ...@@ -13,8 +13,10 @@ public class GameServerInitializer extends KcpServerInitializer {
@Override @Override
protected void initChannel(UkcpChannel ch) throws Exception { protected void initChannel(UkcpChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline(); ChannelPipeline pipeline=null;
GameSession session = new GameSession(server); if(ch!=null){
pipeline.addLast(session); pipeline = ch.pipeline();
}
new GameSession(server,pipeline);
} }
} }
...@@ -3,6 +3,8 @@ package emu.grasscutter.server.game; ...@@ -3,6 +3,8 @@ package emu.grasscutter.server.game;
import java.io.File; import java.io.File;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.Map;
import java.util.Set; import java.util.Set;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
...@@ -17,9 +19,11 @@ import emu.grasscutter.server.event.game.SendPacketEvent; ...@@ -17,9 +19,11 @@ import emu.grasscutter.server.event.game.SendPacketEvent;
import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import io.jpower.kcp.netty.UkcpChannel;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
import static emu.grasscutter.Configuration.*; import static emu.grasscutter.Configuration.*;
...@@ -37,10 +41,29 @@ public class GameSession extends KcpChannel { ...@@ -37,10 +41,29 @@ public class GameSession extends KcpChannel {
private long lastPingTime; private long lastPingTime;
private int lastClientSeq = 10; 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) { public GameSession(GameServer server) {
this(server,null);
}
public GameSession(GameServer server, ChannelPipeline pipeline) {
this.server = server; this.server = server;
this.state = SessionState.WAITING_FOR_TOKEN; this.state = SessionState.WAITING_FOR_TOKEN;
this.lastPingTime = System.currentTimeMillis(); this.lastPingTime = System.currentTimeMillis();
this.pipeline = pipeline;
if(pipeline!=null) {
pipeline.addLast(this);
}
} }
public GameServer getServer() { public GameServer getServer() {
...@@ -127,10 +150,14 @@ public class GameSession extends KcpChannel { ...@@ -127,10 +150,14 @@ public class GameSession extends KcpChannel {
// Save after disconnecting // Save after disconnecting
if (this.isLoggedIn()) { if (this.isLoggedIn()) {
Player player = getPlayer();
// Call logout event. // Call logout event.
getPlayer().onLogout(); player.onLogout();
// Remove from server. }
getServer().getPlayers().remove(getPlayer().getUid()); try {
pipeline.remove(this);
} catch (Throwable ignore) {
} }
} }
......
...@@ -11,6 +11,7 @@ import emu.grasscutter.net.packet.PacketOpcodes; ...@@ -11,6 +11,7 @@ import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.GetPlayerTokenReqOuterClass.GetPlayerTokenReq; import emu.grasscutter.net.proto.GetPlayerTokenReqOuterClass.GetPlayerTokenReq;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketHandler;
import emu.grasscutter.server.event.game.PlayerCreationEvent; 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;
import emu.grasscutter.server.game.GameSession.SessionState; import emu.grasscutter.server.game.GameSession.SessionState;
import emu.grasscutter.server.packet.send.PacketGetPlayerTokenRsp; import emu.grasscutter.server.packet.send.PacketGetPlayerTokenRsp;
...@@ -20,28 +21,45 @@ public class HandlerGetPlayerTokenReq extends PacketHandler { ...@@ -20,28 +21,45 @@ public class HandlerGetPlayerTokenReq extends PacketHandler {
@Override @Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { 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); GetPlayerTokenReq req = GetPlayerTokenReq.parseFrom(payload);
// Authenticate // Authenticate
Account account = DatabaseHelper.getAccountById(req.getAccountUid()); Account account = DatabaseHelper.getAccountById(req.getAccountUid());
if (account == null) { if (account == null || !account.getToken().equals(req.getAccountToken())) {
return;
}
// Check token
if (!account.getToken().equals(req.getAccountToken())) {
return; return;
} }
// Set account // Set account
session.setAccount(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 // Get player
Player player = DatabaseHelper.getPlayerByAccount(account); Player player = DatabaseHelper.getPlayerByAccount(account);
......
package emu.grasscutter.server.packet.recv; package emu.grasscutter.server.packet.recv;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.inventory.Inventory;
import emu.grasscutter.game.inventory.InventoryTab; import emu.grasscutter.game.inventory.InventoryTab;
...@@ -45,6 +46,7 @@ public class HandlerQuickUseWidgetReq extends PacketHandler { ...@@ -45,6 +46,7 @@ public class HandlerQuickUseWidgetReq extends PacketHandler {
BasePacket rsp = new BasePacket(PacketOpcodes.QuickUseWidgetRsp); BasePacket rsp = new BasePacket(PacketOpcodes.QuickUseWidgetRsp);
rsp.setData(proto); rsp.setData(proto);
session.send(rsp); 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! // but no effects in the game, feel free to implement it!
} }
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment