From e5e84848a9dc44983fc36618478dae85e49a6749 Mon Sep 17 00:00:00 2001 From: zhenyan121 <3367366583@qq.com> Date: Fri, 26 Jun 2026 17:05:09 +0800 Subject: [PATCH] feat(gameplay): add player logout and disconnect handling --- include/Cubed/gameplay/client_world.hpp | 3 +- include/Cubed/gameplay/packet.hpp | 6 ++++ include/Cubed/gameplay/server_player.hpp | 9 ++++- include/Cubed/gameplay/server_world.hpp | 2 +- src/app.cpp | 1 + src/gameplay/client_world.cpp | 18 ++++++++++ src/gameplay/network_client.cpp | 8 ++++- src/gameplay/server_player.cpp | 17 +++++++-- src/gameplay/server_world.cpp | 45 ++++++++++++++++++++---- src/gameplay/session.cpp | 6 ++++ src/proto/auth/auth.proto | 8 +++++ 11 files changed, 110 insertions(+), 13 deletions(-) diff --git a/include/Cubed/gameplay/client_world.hpp b/include/Cubed/gameplay/client_world.hpp index 240bb81..a691c88 100644 --- a/include/Cubed/gameplay/client_world.hpp +++ b/include/Cubed/gameplay/client_world.hpp @@ -46,7 +46,7 @@ public: void receive_time(const UpdateTime& rsp); void receive_other_player(const PlayerInfoRsp& rsp); - + void receive_player_logout(const LogoutRsp& rsp); int rendering_distance() const; void rendering_distance(int rendering_distance); void start_client_thread(std::string_view uuid); @@ -62,6 +62,7 @@ public: const std::vector& render_player_data() const; glm::vec3 sunlight_dir() const; void receive_chunk(const ChunkDataRsp& data); + void exit(); template void register_timer(std::string_view id, TickType threshold, Fn&& f) { m_timers.emplace(std::piecewise_construct, diff --git a/include/Cubed/gameplay/packet.hpp b/include/Cubed/gameplay/packet.hpp index c6a044f..c57f827 100644 --- a/include/Cubed/gameplay/packet.hpp +++ b/include/Cubed/gameplay/packet.hpp @@ -11,6 +11,8 @@ using Packet = std::shared_ptr>; enum class PacketEnum { LOGIN_REQ = 1001, LOGIN_RSP = 1002, + LOGOUT_REQ = 1003, + LOGOUT_RSP = 1004, PLAYER_INFO = 2001, PLAYER_POS = 2002, PLAYER_INFO_RSP = 2003, @@ -35,6 +37,10 @@ template constexpr uint16_t get_packet_id() { return to_num(LOGIN_REQ); } else if constexpr (is_same_v) { return to_num(LOGIN_RSP); + } else if constexpr (is_same_v) { + return to_num(LOGOUT_REQ); + } else if constexpr (is_same_v) { + return to_num(LOGOUT_RSP); } else if constexpr (is_same_v) { return to_num(PLAYER_INFO); } else if constexpr (is_same_v) { diff --git a/include/Cubed/gameplay/server_player.hpp b/include/Cubed/gameplay/server_player.hpp index 6c77781..0d89b60 100644 --- a/include/Cubed/gameplay/server_player.hpp +++ b/include/Cubed/gameplay/server_player.hpp @@ -1,6 +1,8 @@ #pragma once #include "Cubed/gameplay/chunk_pos.hpp" +#include "Cubed/gameplay/game_time.hpp" +#include #include #include #include @@ -11,19 +13,24 @@ class Session; class ServerPlayer { public: ServerPlayer(std::string_view name, std::string_view uuid, - ServerWorld& m_world, std::shared_ptr session); + ServerWorld& m_world, std::shared_ptr session, + TickType gametick); const glm::vec3& get_pos() const; const std::string& get_name() const; const std::string& get_uuid() const; std::shared_ptr get_session() const; void update_pos(float x, float y, float z); + void update_sync_gametick(TickType gametick); + bool is_disconnect(TickType current_gametick) const; private: + static constexpr TickType TIMEOUT = 200; std::string m_name; std::string m_uuid; glm::vec3 m_pos{0.0f}; ServerWorld& m_world; ChunkPos m_last_chunk_pos{0, 0}; std::shared_ptr m_session; + std::atomic m_last_gametick{0}; }; } // namespace Cubed diff --git a/include/Cubed/gameplay/server_world.hpp b/include/Cubed/gameplay/server_world.hpp index 30f1ad4..d67f74e 100644 --- a/include/Cubed/gameplay/server_world.hpp +++ b/include/Cubed/gameplay/server_world.hpp @@ -24,7 +24,7 @@ public: ServerWorld(); ~ServerWorld(); void player_join(std::string_view name, std::string_view uuid); - void handle_player_exit(const std::string& name); + void handle_player_exit(const std::string& uuid); void init_world(); void need_gen(std::optional uuid); void update(); diff --git a/src/app.cpp b/src/app.cpp index e90927a..7ef141b 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -284,6 +284,7 @@ void App::run() { update(); render(); } + m_client_world.exit(); } static Gait player_gait = Gait::WALK; void App::update() { diff --git a/src/gameplay/client_world.cpp b/src/gameplay/client_world.cpp index e7de9b1..dd39f4f 100644 --- a/src/gameplay/client_world.cpp +++ b/src/gameplay/client_world.cpp @@ -200,6 +200,18 @@ void ClientWorld::receive_other_player(const PlayerInfoRsp& rsp) { } } +void ClientWorld::receive_player_logout(const LogoutRsp& rsp) { + { + std::lock_guard lock(m_other_players_mutex); + int sum = m_other_players.erase(rsp.uuid()); + if (sum == 0) { + Logger::warn("Player {} not find", rsp.uuid()); + } else { + Logger::info("Player {} erase", rsp.uuid()); + } + } +} + void ClientWorld::init(std::string_view player_name, std::shared_ptr client) { m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4); @@ -405,6 +417,12 @@ void ClientWorld::receive_chunk(const ChunkDataRsp& data) { }); } +void ClientWorld::exit() { + LogoutReq req; + req.set_uuid(m_player.get_uuid()); + m_client->send(make_packet(req)); +} + void ClientWorld::update(float delta_time) { m_player.update(delta_time); { diff --git a/src/gameplay/network_client.cpp b/src/gameplay/network_client.cpp index 0442278..bf4526b 100644 --- a/src/gameplay/network_client.cpp +++ b/src/gameplay/network_client.cpp @@ -99,13 +99,19 @@ asio::awaitable NetworkClient::read_loop() { if (rsp.ParseFromArray(body_data.data(), body_data.size())) { m_world.receive_time(rsp); } - } + } break; case to_num(PacketEnum::PLAYER_INFO_RSP): { PlayerInfoRsp rsp; if (rsp.ParseFromArray(body_data.data(), body_data.size())) { m_world.receive_other_player(rsp); } } break; + case to_num(PacketEnum::LOGOUT_RSP): { + LogoutRsp rsp; + if (rsp.ParseFromArray(body_data.data(), body_data.size())) { + m_world.receive_player_logout(rsp); + } + } break; } } } catch (const asio::system_error& e) { diff --git a/src/gameplay/server_player.cpp b/src/gameplay/server_player.cpp index b7db622..ff0c0e6 100644 --- a/src/gameplay/server_player.cpp +++ b/src/gameplay/server_player.cpp @@ -3,8 +3,10 @@ #include "Cubed/gameplay/server_world.hpp" namespace Cubed { ServerPlayer::ServerPlayer(std::string_view name, std::string_view uuid, - ServerWorld& world, std::shared_ptr session) - : m_name(name), m_uuid(uuid), m_world(world), m_session(session) {} + ServerWorld& world, std::shared_ptr session, + TickType gametick) + : m_name(name), m_uuid(uuid), m_world(world), m_session(session), + m_last_gametick(gametick) {} const glm::vec3& ServerPlayer::get_pos() const { return m_pos; } const std::string& ServerPlayer::get_name() const { return m_name; } const std::string& ServerPlayer::get_uuid() const { return m_uuid; } @@ -18,4 +20,15 @@ void ServerPlayer::update_pos(float x, float y, float z) { m_last_chunk_pos = chunk_pos; } } + +void ServerPlayer::update_sync_gametick(TickType gametick) { + m_last_gametick = gametick; +} +bool ServerPlayer::is_disconnect(TickType current_gametick) const { + if (current_gametick - m_last_gametick > TIMEOUT) { + return true; + } + return false; +} + } // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/server_world.cpp b/src/gameplay/server_world.cpp index 597949a..d286629 100644 --- a/src/gameplay/server_world.cpp +++ b/src/gameplay/server_world.cpp @@ -46,6 +46,22 @@ void ServerWorld::send_time() { } void ServerWorld::init_world() { + + register_timer("player disconnect", 5, [this]() { + std::vector disconnect; + { + std::shared_lock lock(m_player_mutex); + for (auto& [uuid, player] : m_players) { + if (player.is_disconnect(m_game_ticks)) { + disconnect.emplace_back(uuid); + } + } + } + for (auto& uuid : disconnect) { + handle_player_exit(uuid); + } + }); + m_cave_carcer.init(ChunkGenerator::seed()); m_river_worm.init(ChunkGenerator::seed()); m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4); @@ -53,9 +69,6 @@ void ServerWorld::init_world() { auto t1 = std::chrono::system_clock::now(); - // init players - // m_players.emplace(HASH::str("TestPlayer"), Player(*this, "TestPlayer")); - start_gen_thread(); init_chunks(); auto t2 = std::chrono::system_clock::now(); @@ -409,6 +422,7 @@ void ServerWorld::sync_player_pos(const std::string& uuid, float x, float y, return; } it->second.update_pos(x, y, z); + it->second.update_sync_gametick(m_game_ticks); name = it->second.get_name(); } @@ -442,9 +456,9 @@ void ServerWorld::handle_player_login(const std::string& name, Logger::info("Player {} (uuid {}) join the world", name, uuid); { std::lock_guard lock(m_player_mutex); - m_players.emplace(std::piecewise_construct, - std::forward_as_tuple(std::string(uuid)), - std::forward_as_tuple(name, uuid, *this, session)); + m_players.emplace( + std::piecewise_construct, std::forward_as_tuple(std::string(uuid)), + std::forward_as_tuple(name, uuid, *this, session, m_game_ticks)); } m_uuid_to_name.emplace(uuid, name); LoginRsp rsp; @@ -458,13 +472,29 @@ void ServerWorld::handle_player_exit(const std::string& uuid) { std::lock_guard lock(m_player_mutex); auto it = m_players.find(uuid); if (it != m_players.end()) { - + Logger::info("Player {} Exit the Server", it->second.get_name()); m_players.erase(it); } else { Logger::error("Player {} isn't in Server", uuid); + return; } } + m_uuid_to_name.erase(uuid); + + std::vector> sessions; + { + std::shared_lock lock(m_player_mutex); + for (auto& [uuid, player] : m_players) { + sessions.emplace_back(player.get_session()); + } + } + + for (auto& s : sessions) { + LogoutRsp rsp; + rsp.set_uuid(uuid); + s->send(make_packet(rsp)); + } } glm::vec3 ServerWorld::get_player_pos(const std::string& uuid) const { @@ -519,6 +549,7 @@ void ServerWorld::handle_chunk_req(const std::string& uuid, ChunkPos pos) { auto it = m_players.find(uuid); if (it != m_players.end()) { s = it->second.get_session(); + it->second.update_sync_gametick(m_game_ticks); } } if (!s) { diff --git a/src/gameplay/session.cpp b/src/gameplay/session.cpp index b367d96..d336ded 100644 --- a/src/gameplay/session.cpp +++ b/src/gameplay/session.cpp @@ -90,6 +90,12 @@ asio::awaitable Session::read_loop() { m_server_world.handle_block_change(req); } } + if (cmd_id == to_num(PacketEnum::LOGOUT_REQ)) { + LogoutReq req; + if (req.ParseFromArray(body_data.data(), body_data.size())) { + m_server_world.handle_player_exit(req.uuid()); + } + } } } catch (const asio::system_error& e) { auto ec = e.code(); diff --git a/src/proto/auth/auth.proto b/src/proto/auth/auth.proto index 8cfb5ac..ccd1b33 100644 --- a/src/proto/auth/auth.proto +++ b/src/proto/auth/auth.proto @@ -9,3 +9,11 @@ message LoginRsp { string uuid = 2; } +message LogoutReq { + string uuid = 1; +} + +message LogoutRsp { + string uuid = 1; +} +