From 3ecbd1013b90678e178c0e4a4d2b53e1513f1419 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 1 Jan 2025 19:31:12 -0800 Subject: [PATCH] fix: update player container on shutdown (#1704) * update activity log on shutdown * fix online notification * update container on shutdown --- dChatServer/ChatPacketHandler.cpp | 52 +++--- dChatServer/ChatServer.cpp | 266 +++++++++++++++--------------- dChatServer/PlayerContainer.cpp | 33 +++- dChatServer/PlayerContainer.h | 8 +- 4 files changed, 201 insertions(+), 158 deletions(-) diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index 891119dc..40901440 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -29,33 +29,35 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { auto& player = Game::playerContainer.GetPlayerDataMutable(playerID); if (!player) return; - auto friendsList = Database::Get()->GetFriendsList(playerID); - for (const auto& friendData : friendsList) { - FriendData fd; - fd.isFTP = false; // not a thing in DLU - fd.friendID = friendData.friendID; - GeneralUtils::SetBit(fd.friendID, eObjectBits::PERSISTENT); - GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER); + if (player.friends.empty()) { + auto friendsList = Database::Get()->GetFriendsList(playerID); + for (const auto& friendData : friendsList) { + FriendData fd; + fd.isFTP = false; // not a thing in DLU + fd.friendID = friendData.friendID; + GeneralUtils::SetBit(fd.friendID, eObjectBits::PERSISTENT); + GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER); - fd.isBestFriend = friendData.isBestFriend; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs - if (fd.isBestFriend) player.countOfBestFriends += 1; - fd.friendName = friendData.friendName; + fd.isBestFriend = friendData.isBestFriend; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs + if (fd.isBestFriend) player.countOfBestFriends += 1; + fd.friendName = friendData.friendName; - //Now check if they're online: - const auto& fr = Game::playerContainer.GetPlayerData(fd.friendID); + //Now check if they're online: + const auto& fr = Game::playerContainer.GetPlayerData(fd.friendID); - if (fr) { - fd.isOnline = true; - fd.zoneID = fr.zoneID; + if (fr) { + fd.isOnline = true; + fd.zoneID = fr.zoneID; - //Since this friend is online, we need to update them on the fact that we've just logged in: - SendFriendUpdate(fr, player, 1, fd.isBestFriend); - } else { - fd.isOnline = false; - fd.zoneID = LWOZONEID(); + //Since this friend is online, we need to update them on the fact that we've just logged in: + SendFriendUpdate(fr, player, 1, fd.isBestFriend); + } else { + fd.isOnline = false; + fd.zoneID = LWOZONEID(); + } + + player.friends.push_back(fd); } - - player.friends.push_back(fd); } //Now, we need to send the friendlist to the server they came from: @@ -140,7 +142,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) { // Prevent GM friend spam // If the player we are trying to be friends with is not a civilian and we are a civilian, abort the process - if (requestee.gmLevel > eGameMasterLevel::CIVILIAN && requestor.gmLevel == eGameMasterLevel::CIVILIAN ) { + if (requestee.gmLevel > eGameMasterLevel::CIVILIAN && requestor.gmLevel == eGameMasterLevel::CIVILIAN) { SendFriendResponse(requestor, requestee, eAddFriendResponseType::MYTHRAN); return; } @@ -400,8 +402,8 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) { bitStream.Write(Game::playerContainer.GetSimCount()); bitStream.Write(request.displayIndividualPlayers); bitStream.Write(request.displayZoneData); - if (request.displayZoneData || request.displayIndividualPlayers){ - for (auto& [playerID, playerData ]: Game::playerContainer.GetAllPlayers()){ + if (request.displayZoneData || request.displayIndividualPlayers) { + for (auto& [playerID, playerData] : Game::playerContainer.GetAllPlayers()) { if (!playerData) continue; bitStream.Write(0); // structure packing if (request.displayIndividualPlayers) bitStream.Write(LUWString(playerData.playerName)); diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index b4959992..dcfb4038 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -122,6 +122,8 @@ int main(int argc, char** argv) { uint32_t framesSinceMasterDisconnect = 0; uint32_t framesSinceLastSQLPing = 0; + auto lastTime = std::chrono::high_resolution_clock::now(); + Game::logger->Flush(); // once immediately before main loop while (!Game::ShouldShutdown()) { //Check if we're still connected to master: @@ -132,7 +134,11 @@ int main(int argc, char** argv) { break; //Exit our loop, shut down. } else framesSinceMasterDisconnect = 0; - //In world we'd update our other systems here. + const auto currentTime = std::chrono::high_resolution_clock::now(); + const float deltaTime = std::chrono::duration(currentTime - lastTime).count(); + lastTime = currentTime; + + Game::playerContainer.Update(deltaTime); //Check for packets here: Game::server->ReceiveFromMaster(); //ReceiveFromMaster also handles the master packets if needed. @@ -168,7 +174,7 @@ int main(int argc, char** argv) { t += std::chrono::milliseconds(chatFrameDelta); //Chat can run at a lower "fps" std::this_thread::sleep_until(t); } - + Game::playerContainer.Shutdown(); //Delete our objects here: Database::Destroy("ChatServer"); delete Game::server; @@ -197,150 +203,150 @@ void HandlePacket(Packet* packet) { inStream.Read(chatMessageID); switch (chatMessageID) { - case MessageType::Chat::GM_MUTE: - Game::playerContainer.MuteUpdate(packet); - break; + case MessageType::Chat::GM_MUTE: + Game::playerContainer.MuteUpdate(packet); + break; - case MessageType::Chat::CREATE_TEAM: - Game::playerContainer.CreateTeamServer(packet); - break; + case MessageType::Chat::CREATE_TEAM: + Game::playerContainer.CreateTeamServer(packet); + break; - case MessageType::Chat::GET_FRIENDS_LIST: - ChatPacketHandler::HandleFriendlistRequest(packet); - break; + case MessageType::Chat::GET_FRIENDS_LIST: + ChatPacketHandler::HandleFriendlistRequest(packet); + break; - case MessageType::Chat::GET_IGNORE_LIST: - ChatIgnoreList::GetIgnoreList(packet); - break; + case MessageType::Chat::GET_IGNORE_LIST: + ChatIgnoreList::GetIgnoreList(packet); + break; - case MessageType::Chat::ADD_IGNORE: - ChatIgnoreList::AddIgnore(packet); - break; + case MessageType::Chat::ADD_IGNORE: + ChatIgnoreList::AddIgnore(packet); + break; - case MessageType::Chat::REMOVE_IGNORE: - ChatIgnoreList::RemoveIgnore(packet); - break; + case MessageType::Chat::REMOVE_IGNORE: + ChatIgnoreList::RemoveIgnore(packet); + break; - case MessageType::Chat::TEAM_GET_STATUS: - ChatPacketHandler::HandleTeamStatusRequest(packet); - break; + case MessageType::Chat::TEAM_GET_STATUS: + ChatPacketHandler::HandleTeamStatusRequest(packet); + break; - case MessageType::Chat::ADD_FRIEND_REQUEST: - //this involves someone sending the initial request, the response is below, response as in from the other player. - //We basically just check to see if this player is online or not and route the packet. - ChatPacketHandler::HandleFriendRequest(packet); - break; + case MessageType::Chat::ADD_FRIEND_REQUEST: + //this involves someone sending the initial request, the response is below, response as in from the other player. + //We basically just check to see if this player is online or not and route the packet. + ChatPacketHandler::HandleFriendRequest(packet); + break; - case MessageType::Chat::ADD_FRIEND_RESPONSE: - //This isn't the response a server sent, rather it is a player's response to a received request. - //Here, we'll actually have to add them to eachother's friend lists depending on the response code. - ChatPacketHandler::HandleFriendResponse(packet); - break; + case MessageType::Chat::ADD_FRIEND_RESPONSE: + //This isn't the response a server sent, rather it is a player's response to a received request. + //Here, we'll actually have to add them to eachother's friend lists depending on the response code. + ChatPacketHandler::HandleFriendResponse(packet); + break; - case MessageType::Chat::REMOVE_FRIEND: - ChatPacketHandler::HandleRemoveFriend(packet); - break; + case MessageType::Chat::REMOVE_FRIEND: + ChatPacketHandler::HandleRemoveFriend(packet); + break; - case MessageType::Chat::GENERAL_CHAT_MESSAGE: - ChatPacketHandler::HandleChatMessage(packet); - break; + case MessageType::Chat::GENERAL_CHAT_MESSAGE: + ChatPacketHandler::HandleChatMessage(packet); + break; - case MessageType::Chat::PRIVATE_CHAT_MESSAGE: - //This message is supposed to be echo'd to both the sender and the receiver - //BUT: they have to have different responseCodes, so we'll do some of the ol hacky wacky to fix that right up. - ChatPacketHandler::HandlePrivateChatMessage(packet); - break; + case MessageType::Chat::PRIVATE_CHAT_MESSAGE: + //This message is supposed to be echo'd to both the sender and the receiver + //BUT: they have to have different responseCodes, so we'll do some of the ol hacky wacky to fix that right up. + ChatPacketHandler::HandlePrivateChatMessage(packet); + break; - case MessageType::Chat::TEAM_INVITE: - ChatPacketHandler::HandleTeamInvite(packet); - break; + case MessageType::Chat::TEAM_INVITE: + ChatPacketHandler::HandleTeamInvite(packet); + break; - case MessageType::Chat::TEAM_INVITE_RESPONSE: - ChatPacketHandler::HandleTeamInviteResponse(packet); - break; + case MessageType::Chat::TEAM_INVITE_RESPONSE: + ChatPacketHandler::HandleTeamInviteResponse(packet); + break; - case MessageType::Chat::TEAM_LEAVE: - ChatPacketHandler::HandleTeamLeave(packet); - break; + case MessageType::Chat::TEAM_LEAVE: + ChatPacketHandler::HandleTeamLeave(packet); + break; - case MessageType::Chat::TEAM_SET_LEADER: - ChatPacketHandler::HandleTeamPromote(packet); - break; + case MessageType::Chat::TEAM_SET_LEADER: + ChatPacketHandler::HandleTeamPromote(packet); + break; - case MessageType::Chat::TEAM_KICK: - ChatPacketHandler::HandleTeamKick(packet); - break; + case MessageType::Chat::TEAM_KICK: + ChatPacketHandler::HandleTeamKick(packet); + break; - case MessageType::Chat::TEAM_SET_LOOT: - ChatPacketHandler::HandleTeamLootOption(packet); - break; - case MessageType::Chat::GMLEVEL_UPDATE: - ChatPacketHandler::HandleGMLevelUpdate(packet); - break; - case MessageType::Chat::LOGIN_SESSION_NOTIFY: - Game::playerContainer.InsertPlayer(packet); - break; - case MessageType::Chat::GM_ANNOUNCE:{ - // we just forward this packet to every connected server - inStream.ResetReadPointer(); - Game::server->Send(inStream, packet->systemAddress, true); // send to everyone except origin - } - break; - case MessageType::Chat::UNEXPECTED_DISCONNECT: - Game::playerContainer.RemovePlayer(packet); - break; - case MessageType::Chat::WHO: - ChatPacketHandler::HandleWho(packet); - break; - case MessageType::Chat::SHOW_ALL: - ChatPacketHandler::HandleShowAll(packet); - break; - case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE: - case MessageType::Chat::WORLD_DISCONNECT_REQUEST: - case MessageType::Chat::WORLD_PROXIMITY_RESPONSE: - case MessageType::Chat::WORLD_PARCEL_RESPONSE: - case MessageType::Chat::TEAM_MISSED_INVITE_CHECK: - case MessageType::Chat::GUILD_CREATE: - case MessageType::Chat::GUILD_INVITE: - case MessageType::Chat::GUILD_INVITE_RESPONSE: - case MessageType::Chat::GUILD_LEAVE: - case MessageType::Chat::GUILD_KICK: - case MessageType::Chat::GUILD_GET_STATUS: - case MessageType::Chat::GUILD_GET_ALL: - case MessageType::Chat::BLUEPRINT_MODERATED: - case MessageType::Chat::BLUEPRINT_MODEL_READY: - case MessageType::Chat::PROPERTY_READY_FOR_APPROVAL: - case MessageType::Chat::PROPERTY_MODERATION_CHANGED: - case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED: - case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED_REPORT: - case MessageType::Chat::MAIL: - case MessageType::Chat::WORLD_INSTANCE_LOCATION_REQUEST: - case MessageType::Chat::REPUTATION_UPDATE: - case MessageType::Chat::SEND_CANNED_TEXT: - case MessageType::Chat::CHARACTER_NAME_CHANGE_REQUEST: - case MessageType::Chat::CSR_REQUEST: - case MessageType::Chat::CSR_REPLY: - case MessageType::Chat::GM_KICK: - case MessageType::Chat::WORLD_ROUTE_PACKET: - case MessageType::Chat::GET_ZONE_POPULATIONS: - case MessageType::Chat::REQUEST_MINIMUM_CHAT_MODE: - case MessageType::Chat::MATCH_REQUEST: - case MessageType::Chat::UGCMANIFEST_REPORT_MISSING_FILE: - case MessageType::Chat::UGCMANIFEST_REPORT_DONE_FILE: - case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT: - case MessageType::Chat::UGCC_REQUEST: - case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE: - case MessageType::Chat::ACHIEVEMENT_NOTIFY: - case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW: - case MessageType::Chat::PLAYER_READY: - case MessageType::Chat::GET_DONATION_TOTAL: - case MessageType::Chat::UPDATE_DONATION: - case MessageType::Chat::PRG_CSR_COMMAND: - case MessageType::Chat::HEARTBEAT_REQUEST_FROM_WORLD: - case MessageType::Chat::UPDATE_FREE_TRIAL_STATUS: - LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chatMessageID).data(), chatMessageID); - break; - default: - LOG("Unknown CHAT Message id: %i", chatMessageID); + case MessageType::Chat::TEAM_SET_LOOT: + ChatPacketHandler::HandleTeamLootOption(packet); + break; + case MessageType::Chat::GMLEVEL_UPDATE: + ChatPacketHandler::HandleGMLevelUpdate(packet); + break; + case MessageType::Chat::LOGIN_SESSION_NOTIFY: + Game::playerContainer.InsertPlayer(packet); + break; + case MessageType::Chat::GM_ANNOUNCE: { + // we just forward this packet to every connected server + inStream.ResetReadPointer(); + Game::server->Send(inStream, packet->systemAddress, true); // send to everyone except origin + } + break; + case MessageType::Chat::UNEXPECTED_DISCONNECT: + Game::playerContainer.ScheduleRemovePlayer(packet); + break; + case MessageType::Chat::WHO: + ChatPacketHandler::HandleWho(packet); + break; + case MessageType::Chat::SHOW_ALL: + ChatPacketHandler::HandleShowAll(packet); + break; + case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE: + case MessageType::Chat::WORLD_DISCONNECT_REQUEST: + case MessageType::Chat::WORLD_PROXIMITY_RESPONSE: + case MessageType::Chat::WORLD_PARCEL_RESPONSE: + case MessageType::Chat::TEAM_MISSED_INVITE_CHECK: + case MessageType::Chat::GUILD_CREATE: + case MessageType::Chat::GUILD_INVITE: + case MessageType::Chat::GUILD_INVITE_RESPONSE: + case MessageType::Chat::GUILD_LEAVE: + case MessageType::Chat::GUILD_KICK: + case MessageType::Chat::GUILD_GET_STATUS: + case MessageType::Chat::GUILD_GET_ALL: + case MessageType::Chat::BLUEPRINT_MODERATED: + case MessageType::Chat::BLUEPRINT_MODEL_READY: + case MessageType::Chat::PROPERTY_READY_FOR_APPROVAL: + case MessageType::Chat::PROPERTY_MODERATION_CHANGED: + case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED: + case MessageType::Chat::PROPERTY_BUILDMODE_CHANGED_REPORT: + case MessageType::Chat::MAIL: + case MessageType::Chat::WORLD_INSTANCE_LOCATION_REQUEST: + case MessageType::Chat::REPUTATION_UPDATE: + case MessageType::Chat::SEND_CANNED_TEXT: + case MessageType::Chat::CHARACTER_NAME_CHANGE_REQUEST: + case MessageType::Chat::CSR_REQUEST: + case MessageType::Chat::CSR_REPLY: + case MessageType::Chat::GM_KICK: + case MessageType::Chat::WORLD_ROUTE_PACKET: + case MessageType::Chat::GET_ZONE_POPULATIONS: + case MessageType::Chat::REQUEST_MINIMUM_CHAT_MODE: + case MessageType::Chat::MATCH_REQUEST: + case MessageType::Chat::UGCMANIFEST_REPORT_MISSING_FILE: + case MessageType::Chat::UGCMANIFEST_REPORT_DONE_FILE: + case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT: + case MessageType::Chat::UGCC_REQUEST: + case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE: + case MessageType::Chat::ACHIEVEMENT_NOTIFY: + case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW: + case MessageType::Chat::PLAYER_READY: + case MessageType::Chat::GET_DONATION_TOTAL: + case MessageType::Chat::UPDATE_DONATION: + case MessageType::Chat::PRG_CSR_COMMAND: + case MessageType::Chat::HEARTBEAT_REQUEST_FROM_WORLD: + case MessageType::Chat::UPDATE_FREE_TRIAL_STATUS: + LOG("Unhandled CHAT Message id: %s (%i)", StringifiedEnum::ToString(chatMessageID).data(), chatMessageID); + break; + default: + LOG("Unknown CHAT Message id: %i", chatMessageID); } } diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index d1141ce7..6e2fd6f8 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -57,13 +57,32 @@ void PlayerContainer::InsertPlayer(Packet* packet) { LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID()); Database::Get()->UpdateActivityLog(data.playerID, eActivityType::PlayerLoggedIn, data.zoneID.GetMapID()); + m_PlayersToRemove.erase(playerId); } -void PlayerContainer::RemovePlayer(Packet* packet) { +void PlayerContainer::ScheduleRemovePlayer(Packet* packet) { CINSTREAM_SKIP_HEADER; - LWOOBJID playerID; + LWOOBJID playerID{ LWOOBJID_EMPTY }; inStream.Read(playerID); + constexpr float updatePlayerOnLogoutTime = 20.0f; + if (playerID != LWOOBJID_EMPTY) m_PlayersToRemove.insert_or_assign(playerID, updatePlayerOnLogoutTime); +} +void PlayerContainer::Update(const float deltaTime) { + for (auto it = m_PlayersToRemove.begin(); it != m_PlayersToRemove.end();) { + auto& [id, time] = *it; + time -= deltaTime; + + if (time <= 0.0f) { + RemovePlayer(id); + it = m_PlayersToRemove.erase(it); + } else { + ++it; + } + } +} + +void PlayerContainer::RemovePlayer(const LWOOBJID playerID) { //Before they get kicked, we need to also send a message to their friends saying that they disconnected. const auto& player = GetPlayerData(playerID); @@ -417,3 +436,13 @@ const PlayerData& PlayerContainer::GetPlayerData(const LWOOBJID& playerID) { const PlayerData& PlayerContainer::GetPlayerData(const std::string& playerName) { return GetPlayerDataMutable(playerName); } + +void PlayerContainer::Shutdown() { + m_Players.erase(LWOOBJID_EMPTY); + while (!m_Players.empty()) { + const auto& [id, playerData] = *m_Players.begin(); + Database::Get()->UpdateActivityLog(id, eActivityType::PlayerLoggedOut, playerData.zoneID.GetMapID()); + m_Players.erase(m_Players.begin()); + } + for (auto* team : mTeams) if (team) delete team; +} diff --git a/dChatServer/PlayerContainer.h b/dChatServer/PlayerContainer.h index 9a17f927..c888de08 100644 --- a/dChatServer/PlayerContainer.h +++ b/dChatServer/PlayerContainer.h @@ -62,10 +62,12 @@ class PlayerContainer { public: void Initialize(); void InsertPlayer(Packet* packet); - void RemovePlayer(Packet* packet); + void ScheduleRemovePlayer(Packet* packet); + void RemovePlayer(const LWOOBJID playerID); void MuteUpdate(Packet* packet); void CreateTeamServer(Packet* packet); void BroadcastMuteUpdate(LWOOBJID player, time_t time); + void Shutdown(); const PlayerData& GetPlayerData(const LWOOBJID& playerID); const PlayerData& GetPlayerData(const std::string& playerName); @@ -89,11 +91,15 @@ public: uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; } uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; } + void Update(const float deltaTime); + bool PlayerBeingRemoved(const LWOOBJID playerID) { return m_PlayersToRemove.contains(playerID); } + private: LWOOBJID m_TeamIDCounter = 0; std::map m_Players; std::vector mTeams; std::unordered_map m_Names; + std::map m_PlayersToRemove; uint32_t m_MaxNumberOfBestFriends = 5; uint32_t m_MaxNumberOfFriends = 50; uint32_t m_PlayerCount = 0;