diff --git a/.gitignore b/.gitignore index 22f9e8efc681b5dd2158eba95d00a98db699fb22..bc14de98c2796a6a1ee51aa35596060267d5a4cc 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ /freekill-asio /server/* !/server/init.sql +!/server/gamedb_init.sql /freekill.server.config.json /freekill.server.*log /freekill.*log diff --git a/server/gamedb_init.sql b/server/gamedb_init.sql new file mode 100644 index 0000000000000000000000000000000000000000..73fb98b366a6cce9f9b83d41c41d3229e7f2647e --- /dev/null +++ b/server/gamedb_init.sql @@ -0,0 +1,12 @@ +-- SPDX-License-Identifier: GPL-3.0-or-later + +CREATE TABLE IF NOT EXISTS gameSaves ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + uid INTEGER NOT NULL, + mode TEXT NOT NULL, + data BLOB NOT NULL, + UNIQUE(uid, mode) +); + +CREATE INDEX IF NOT EXISTS idx_gameSaves_uid ON gameSaves(uid); +CREATE INDEX IF NOT EXISTS idx_gameSaves_mode ON gameSaves(mode); \ No newline at end of file diff --git a/src/server/gamelogic/rpc-dispatchers.cpp b/src/server/gamelogic/rpc-dispatchers.cpp index e3cbedbee763f1860efed5c5a981c6f857a25bfb..a8136d8132740bec0651aef389376826ff7cf6a0 100644 --- a/src/server/gamelogic/rpc-dispatchers.cpp +++ b/src/server/gamelogic/rpc-dispatchers.cpp @@ -379,6 +379,83 @@ static _rpcRet _rpc_Room_decreaseRefCount(const JsonRpcPacket &packet) { return { true, nullVal }; } +static _rpcRet _rpc_Player_saveState(const JsonRpcPacket &packet) { + if (!(packet.param_count == 2 && + std::holds_alternative(packet.param1) && + std::holds_alternative(packet.param2) + )) { + return { false, nullVal }; + } + + auto connId = std::get(packet.param1); + auto jsonData = std::get(packet.param2); + + auto player = Server::instance().user_manager().findPlayerByConnId(connId).lock(); + if (!player) { + return { false, nullVal }; + } + + player->saveState(jsonData); + return { true, nullVal }; +} + +static _rpcRet _rpc_Player_getSaveState(const JsonRpcPacket &packet) { + if (!(packet.param_count == 1 && + std::holds_alternative(packet.param1) + )) { + return { false, nullVal }; + } + + auto connId = std::get(packet.param1); + + auto player = Server::instance().user_manager().findPlayerByConnId(connId).lock(); + if (!player) { + return { false, nullVal }; + } + + std::string result = player->getSaveState(); + return { true, result }; +} + +static _rpcRet _rpc_Player_saveGlobalState(const JsonRpcPacket &packet) { + if (!(packet.param_count == 2 && + std::holds_alternative(packet.param1) && + std::holds_alternative(packet.param2) + )) { + return { false, nullVal }; + } + + auto connId = std::get(packet.param1); + auto jsonData = std::get(packet.param2); + + auto player = Server::instance().user_manager().findPlayerByConnId(connId).lock(); + if (!player) { + return { false, nullVal }; + } + + player->saveGlobalState(jsonData); + return { true, nullVal }; +} + +static _rpcRet _rpc_Player_getGlobalSaveState(const JsonRpcPacket &packet) { + if (!(packet.param_count == 1 && + std::holds_alternative(packet.param1) + )) { + return { false, nullVal }; + } + + auto connId = std::get(packet.param1); + + auto player = Server::instance().user_manager().findPlayerByConnId(connId).lock(); + if (!player) { + return { false, nullVal }; + } + + std::string result = player->getGlobalSaveState(); + return { true, result }; +} + + // 收官:getRoom std::string RpcDispatchers::getPlayerObject(Player &p) { @@ -499,6 +576,10 @@ const JsonRpc::RpcMethodMap RpcDispatchers::ServerRpcMethods { { "ServerPlayer_setThinking", _rpc_Player_setThinking }, { "ServerPlayer_setDied", _rpc_Player_setDied }, { "ServerPlayer_emitKick", _rpc_Player_emitKick }, + { "ServerPlayer_saveState", _rpc_Player_saveState }, + { "ServerPlayer_getSaveState", _rpc_Player_getSaveState }, + { "ServerPlayer_saveGlobalState", _rpc_Player_saveGlobalState }, + { "ServerPlayer_getGlobalSaveState", _rpc_Player_getGlobalSaveState }, { "Room_delay", _rpc_Room_delay }, { "Room_updatePlayerWinRate", _rpc_Room_updatePlayerWinRate }, diff --git a/src/server/server.cpp b/src/server/server.cpp index 145574b4a835e9a6970211de056490777c870f3d..a3cdeb1f70f5648e3efb704018618bfa18fc75c4 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -36,6 +36,7 @@ Server::Server() : m_socket { nullptr } { main_thread_id = std::this_thread::get_id(); db = std::make_unique(); + gamedb = std::make_unique("./server/game.db", "./server/gamedb_init.sql"); // 初始化 reloadConfig(); refreshMd5(); @@ -116,6 +117,10 @@ Sqlite3 &Server::database() { return *db; } +Sqlite3 &Server::gameDatabase() { + return *gamedb; +} + Shell &Server::shell() { return *m_shell; } diff --git a/src/server/server.h b/src/server/server.h index 16abde0aad2bc9a209d68bec848f2f90d00ff6f4..ddbab8c5d3a962939a2ce520e9b3e112ca76ef9d 100644 --- a/src/server/server.h +++ b/src/server/server.h @@ -51,6 +51,7 @@ public: UserManager &user_manager(); RoomManager &room_manager(); Sqlite3 &database(); + Sqlite3 &gameDatabase(); // gamedb的getter Shell &shell(); void sendEarlyPacket(ClientSocket &client, const std::string_view &type, const std::string_view &msg); @@ -88,6 +89,7 @@ private: std::unique_ptr m_socket; std::unique_ptr db; + std::unique_ptr gamedb; // 存档变量 std::mutex transaction_mutex; std::unordered_map> m_threads; diff --git a/src/server/user/player.cpp b/src/server/user/player.cpp index 8557338bebd89ca5cd63dd5590fe8de4cb8b2b5a..8679e2614e127865490ecf15f381a20cb2f1a51e 100644 --- a/src/server/user/player.cpp +++ b/src/server/user/player.cpp @@ -384,3 +384,64 @@ void Player::onReadyChanged() { room->doBroadcastNotify(room->getPlayers(), "ReadyChanged", Cbor::encodeArray({ id, ready })); } + +void Player::saveState(std::string_view jsonData) { + auto room_base = getRoom().lock(); + if (!room_base) return; + auto room = dynamic_pointer_cast(room_base); + if (!room) return; + std::string mode { room->getGameMode() }; + writeSaveState(mode, jsonData); +} + +std::string Player::getSaveState() { + auto room_base = getRoom().lock(); + if (!room_base) return "{}"; + auto room = dynamic_pointer_cast(room_base); + if (!room) return "{}"; + std::string mode { room->getGameMode() }; + return readSaveState(mode); +} + +void Player::saveGlobalState(std::string_view jsonData) { + writeSaveState("__global__", jsonData); +} + +std::string Player::getGlobalSaveState() { + return readSaveState("__global__"); +} + +void Player::writeSaveState(std::string mode, std::string_view jsonData) { + if (!Sqlite3::checkString(mode)) { + spdlog::error("Invalid mode string for saveState: {}", mode); + return; + } + + auto hexData = toHex(jsonData); + auto &gamedb = Server::instance().gameDatabase(); + auto sql = fmt::format("REPLACE INTO gameSaves (uid, mode, data) VALUES ({},'{}',X'{}')", id, mode, hexData); + + gamedb.exec(sql);; +} + +std::string Player::readSaveState(std::string mode) { + if (!Sqlite3::checkString(mode)) { + spdlog::error("Invalid mode string for readSaveState: {}", mode); + return "{}"; + } + + auto sql = fmt::format("SELECT data FROM gameSaves WHERE uid = {} AND mode = '{}'", id, mode); + + auto result = Server::instance().gameDatabase().select(sql); + if (result.empty() || result[0].count("data") == 0 || result[0]["data"] == "#null") { + return "{}"; + } + + const auto& data = result[0]["data"]; + if (!data.empty() && (data[0] == '{' || data[0] == '[')) { + return data; + } + + spdlog::warn("Returned data is not valid JSON: {}", data); + return "{}"; +} \ No newline at end of file diff --git a/src/server/user/player.h b/src/server/user/player.h index 8c842fa5d1debd74bc4afb807393e6b8a7f1f82b..1b751953be41ec7c34b5bbb34590bc40e36e43cb 100644 --- a/src/server/user/player.h +++ b/src/server/user/player.h @@ -96,6 +96,13 @@ public: void resumeGameTimer(); int getGameTime(); + // 模式存档 + void saveState(std::string_view jsonData); + std::string getSaveState(); + // 全局存档 + void saveGlobalState(std::string_view jsonData); + std::string getGlobalSaveState(); + private: int id = 0; std::string screenName; // screenName should not be same. @@ -125,4 +132,7 @@ private: int64_t gameTime = 0; // 在这个房间的有效游戏时长(秒) int64_t gameTimerStartTimestamp; + + void writeSaveState(std::string mode, std::string_view jsonData); + std::string readSaveState(std::string mode); };