From 0e551429d389ec47a422b64d6cf8958b390493ad Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 5 May 2025 07:04:43 -0700 Subject: [PATCH] chore: move all teams logic to its own namespace to consolidate logic (#1777) * Add invite initial response msg re-do team leave logic to send more accurate messages Players are still able to leave the team with the same results as before, however now the correct messages are sent to team chats (no fixes for local teams). * chore: move team logic to separate container Makes it easier to follow team logic when you're not bouncing between 3 classes in 3 files. Consolidates all team logic to 1 namespace in TeamContainer. No logic changes were done, only renaming and fixing errors from the moving. TeamData should be replaced with unique_ptrs at some point so the Shutdown method can be removed from TeamContainer. --- dChatServer/CMakeLists.txt | 3 +- dChatServer/ChatPacketHandler.cpp | 397 +----------------- dChatServer/ChatPacketHandler.h | 35 +- dChatServer/ChatServer.cpp | 20 +- dChatServer/ChatWebAPI.cpp | 28 +- dChatServer/JSONUtils.cpp | 16 +- dChatServer/JSONUtils.h | 8 +- dChatServer/PlayerContainer.cpp | 252 +---------- dChatServer/PlayerContainer.h | 28 +- dChatServer/TeamContainer.cpp | 669 ++++++++++++++++++++++++++++++ dChatServer/TeamContainer.h | 59 +++ 11 files changed, 787 insertions(+), 728 deletions(-) create mode 100644 dChatServer/TeamContainer.cpp create mode 100644 dChatServer/TeamContainer.h diff --git a/dChatServer/CMakeLists.txt b/dChatServer/CMakeLists.txt index 313df6d5..8554ac19 100644 --- a/dChatServer/CMakeLists.txt +++ b/dChatServer/CMakeLists.txt @@ -1,9 +1,10 @@ set(DCHATSERVER_SOURCES "ChatIgnoreList.cpp" "ChatPacketHandler.cpp" - "PlayerContainer.cpp" "ChatWebAPI.cpp" "JSONUtils.cpp" + "PlayerContainer.cpp" + "TeamContainer.cpp" ) add_executable(ChatServer "ChatServer.cpp") diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index 3690e511..5722dfa0 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -19,6 +19,7 @@ #include "StringifiedEnum.h" #include "eGameMasterLevel.h" #include "ChatPackets.h" +#include "TeamContainer.h" void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { //Get from the packet which player we want to do something with: @@ -447,7 +448,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) { switch (channel) { case eChatChannel::TEAM: { - auto* team = Game::playerContainer.GetTeam(playerID); + auto* team = TeamContainer::GetTeam(playerID); if (team == nullptr) return; for (const auto memberId : team->memberIDs) { @@ -563,400 +564,6 @@ void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const P SEND_PACKET; } - -void ChatPacketHandler::HandleTeamInvite(Packet* packet) { - CINSTREAM_SKIP_HEADER; - - LWOOBJID playerID; - LUWString invitedPlayer; - - inStream.Read(playerID); - inStream.IgnoreBytes(4); - inStream.Read(invitedPlayer); - - const auto& player = Game::playerContainer.GetPlayerData(playerID); - - if (!player) return; - - auto* team = Game::playerContainer.GetTeam(playerID); - - if (team == nullptr) { - team = Game::playerContainer.CreateTeam(playerID); - } - - const auto& other = Game::playerContainer.GetPlayerData(invitedPlayer.GetAsString()); - - if (!other) return; - - if (Game::playerContainer.GetTeam(other.playerID) != nullptr) { - return; - } - - if (team->memberIDs.size() > 3) { - // no more teams greater than 4 - - LOG("Someone tried to invite a 5th player to a team"); - return; - } - - SendTeamInvite(other, player); - - LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str()); - - bool failed = false; - for (const auto& ignore : other.ignoredPlayers) { - if (ignore.playerId == player.playerID) { - failed = true; - break; - } - } - - ChatPackets::TeamInviteInitialResponse response{}; - response.inviteFailedToSend = failed; - response.playerName = invitedPlayer.string; - ChatPackets::SendRoutedMsg(response, playerID, player.worldServerSysAddr); -} - -void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) { - CINSTREAM_SKIP_HEADER; - LWOOBJID playerID = LWOOBJID_EMPTY; - inStream.Read(playerID); - uint32_t size = 0; - inStream.Read(size); - char declined = 0; - inStream.Read(declined); - LWOOBJID leaderID = LWOOBJID_EMPTY; - inStream.Read(leaderID); - - LOG("Invite reponse received: %llu -> %llu (%d)", playerID, leaderID, declined); - - if (declined) { - return; - } - - auto* team = Game::playerContainer.GetTeam(leaderID); - - if (team == nullptr) { - LOG("Failed to find team for leader (%llu)", leaderID); - - team = Game::playerContainer.GetTeam(playerID); - } - - if (team == nullptr) { - LOG("Failed to find team for player (%llu)", playerID); - return; - } - - Game::playerContainer.AddMember(team, playerID); -} - -void ChatPacketHandler::HandleTeamLeave(Packet* packet) { - CINSTREAM_SKIP_HEADER; - LWOOBJID playerID = LWOOBJID_EMPTY; - inStream.Read(playerID); - uint32_t size = 0; - inStream.Read(size); - - auto* team = Game::playerContainer.GetTeam(playerID); - - LOG("(%llu) leaving team", playerID); - - if (team != nullptr) { - Game::playerContainer.RemoveMember(team, playerID, false, false, true); - } -} - -void ChatPacketHandler::HandleTeamKick(Packet* packet) { - CINSTREAM_SKIP_HEADER; - - LWOOBJID playerID = LWOOBJID_EMPTY; - LUWString kickedPlayer; - - inStream.Read(playerID); - inStream.IgnoreBytes(4); - inStream.Read(kickedPlayer); - - - LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.GetAsString().c_str()); - - const auto& kicked = Game::playerContainer.GetPlayerData(kickedPlayer.GetAsString()); - - LWOOBJID kickedId = LWOOBJID_EMPTY; - - if (kicked) { - kickedId = kicked.playerID; - } else { - kickedId = Game::playerContainer.GetId(kickedPlayer.string); - } - - if (kickedId == LWOOBJID_EMPTY) return; - - auto* team = Game::playerContainer.GetTeam(playerID); - - if (team != nullptr) { - if (team->leaderID != playerID || team->leaderID == kickedId) return; - - Game::playerContainer.RemoveMember(team, kickedId, false, true, false); - } -} - -void ChatPacketHandler::HandleTeamPromote(Packet* packet) { - CINSTREAM_SKIP_HEADER; - - LWOOBJID playerID = LWOOBJID_EMPTY; - LUWString promotedPlayer; - - inStream.Read(playerID); - inStream.IgnoreBytes(4); - inStream.Read(promotedPlayer); - - LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.GetAsString().c_str()); - - const auto& promoted = Game::playerContainer.GetPlayerData(promotedPlayer.GetAsString()); - - if (!promoted) return; - - auto* team = Game::playerContainer.GetTeam(playerID); - - if (team != nullptr) { - if (team->leaderID != playerID) return; - - Game::playerContainer.PromoteMember(team, promoted.playerID); - } -} - -void ChatPacketHandler::HandleTeamLootOption(Packet* packet) { - CINSTREAM_SKIP_HEADER; - LWOOBJID playerID = LWOOBJID_EMPTY; - inStream.Read(playerID); - uint32_t size = 0; - inStream.Read(size); - - char option; - inStream.Read(option); - - auto* team = Game::playerContainer.GetTeam(playerID); - - if (team != nullptr) { - if (team->leaderID != playerID) return; - - team->lootFlag = option; - - Game::playerContainer.TeamStatusUpdate(team); - - Game::playerContainer.UpdateTeamsOnWorld(team, false); - } -} - -void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) { - CINSTREAM_SKIP_HEADER; - LWOOBJID playerID = LWOOBJID_EMPTY; - inStream.Read(playerID); - - auto* team = Game::playerContainer.GetTeam(playerID); - const auto& data = Game::playerContainer.GetPlayerData(playerID); - - if (team != nullptr && data) { - LOG_DEBUG("Player %llu is requesting team status", playerID); - if (team->local && data.zoneID.GetMapID() != team->zoneId.GetMapID() && data.zoneID.GetCloneID() != team->zoneId.GetCloneID()) { - Game::playerContainer.RemoveMember(team, playerID, false, false, false, true); - - return; - } - - if (team->memberIDs.size() <= 1 && !team->local) { - Game::playerContainer.DisbandTeam(team, LWOOBJID_EMPTY, u""); - - return; - } - - if (!team->local) { - ChatPacketHandler::SendTeamSetLeader(data, team->leaderID); - } else { - ChatPacketHandler::SendTeamSetLeader(data, LWOOBJID_EMPTY); - } - - Game::playerContainer.TeamStatusUpdate(team); - - const auto leaderName = GeneralUtils::UTF8ToUTF16(data.playerName); - - for (const auto memberId : team->memberIDs) { - const auto& otherMember = Game::playerContainer.GetPlayerData(memberId); - - if (memberId == playerID) continue; - - const auto memberName = Game::playerContainer.GetName(memberId); - - if (otherMember) { - ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, data.playerID, data.zoneID); - } - ChatPacketHandler::SendTeamAddPlayer(data, false, team->local, false, memberId, memberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0)); - } - - Game::playerContainer.UpdateTeamsOnWorld(team, false); - } -} - -void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); - bitStream.Write(receiver.playerID); - - //portion that will get routed: - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::TEAM_INVITE); - - bitStream.Write(LUWString(sender.playerName.c_str())); - bitStream.Write(sender.playerID); - - SystemAddress sysAddr = receiver.worldServerSysAddr; - SEND_PACKET; -} - -void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); - bitStream.Write(receiver.playerID); - - //portion that will get routed: - CMSGHEADER; - - bitStream.Write(receiver.playerID); - bitStream.Write(MessageType::Game::TEAM_INVITE_CONFIRM); - - bitStream.Write(bLeaderIsFreeTrial); - bitStream.Write(i64LeaderID); - bitStream.Write(i64LeaderZoneID); - bitStream.Write(0); // BinaryBuffe, no clue what's in here - bitStream.Write(ucLootFlag); - bitStream.Write(ucNumOfOtherPlayers); - bitStream.Write(ucResponseCode); - bitStream.Write(wsLeaderName.size()); - for (const auto character : wsLeaderName) { - bitStream.Write(character); - } - - SystemAddress sysAddr = receiver.worldServerSysAddr; - SEND_PACKET; -} - -void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); - bitStream.Write(receiver.playerID); - - //portion that will get routed: - CMSGHEADER; - - bitStream.Write(receiver.playerID); - bitStream.Write(MessageType::Game::TEAM_GET_STATUS_RESPONSE); - - bitStream.Write(i64LeaderID); - bitStream.Write(i64LeaderZoneID); - bitStream.Write(0); // BinaryBuffe, no clue what's in here - bitStream.Write(ucLootFlag); - bitStream.Write(ucNumOfOtherPlayers); - bitStream.Write(wsLeaderName.size()); - for (const auto character : wsLeaderName) { - bitStream.Write(character); - } - - SystemAddress sysAddr = receiver.worldServerSysAddr; - SEND_PACKET; -} - -void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); - bitStream.Write(receiver.playerID); - - //portion that will get routed: - CMSGHEADER; - - bitStream.Write(receiver.playerID); - bitStream.Write(MessageType::Game::TEAM_SET_LEADER); - - bitStream.Write(i64PlayerID); - - SystemAddress sysAddr = receiver.worldServerSysAddr; - SEND_PACKET; -} - -void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); - bitStream.Write(receiver.playerID); - - //portion that will get routed: - CMSGHEADER; - - bitStream.Write(receiver.playerID); - bitStream.Write(MessageType::Game::TEAM_ADD_PLAYER); - - bitStream.Write(bIsFreeTrial); - bitStream.Write(bLocal); - bitStream.Write(bNoLootOnDeath); - bitStream.Write(i64PlayerID); - bitStream.Write(wsPlayerName.size()); - for (const auto character : wsPlayerName) { - bitStream.Write(character); - } - bitStream.Write1(); - if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) { - zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0); - } - bitStream.Write(zoneID); - - SystemAddress sysAddr = receiver.worldServerSysAddr; - SEND_PACKET; -} - -void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); - bitStream.Write(receiver.playerID); - - //portion that will get routed: - CMSGHEADER; - - bitStream.Write(receiver.playerID); - bitStream.Write(MessageType::Game::TEAM_REMOVE_PLAYER); - - bitStream.Write(bDisband); - bitStream.Write(bIsKicked); - bitStream.Write(bIsLeaving); - bitStream.Write(bLocal); - bitStream.Write(i64LeaderID); - bitStream.Write(i64PlayerID); - bitStream.Write(wsPlayerName.size()); - for (const auto character : wsPlayerName) { - bitStream.Write(character); - } - - SystemAddress sysAddr = receiver.worldServerSysAddr; - SEND_PACKET; -} - -void ChatPacketHandler::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); - bitStream.Write(receiver.playerID); - - //portion that will get routed: - CMSGHEADER; - - bitStream.Write(receiver.playerID); - bitStream.Write(MessageType::Game::TEAM_SET_OFF_WORLD_FLAG); - - bitStream.Write(i64PlayerID); - if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) { - zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0); - } - bitStream.Write(zoneID); - - SystemAddress sysAddr = receiver.worldServerSysAddr; - SEND_PACKET; -} - void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const PlayerData& playerData, uint8_t notifyType, uint8_t isBestFriend) { /*chat notification is displayed if log in / out and friend is updated in friends list [u8] - update type diff --git a/dChatServer/ChatPacketHandler.h b/dChatServer/ChatPacketHandler.h index ca5c3648..29a20587 100644 --- a/dChatServer/ChatPacketHandler.h +++ b/dChatServer/ChatPacketHandler.h @@ -35,13 +35,13 @@ enum class eChatChannel : uint8_t { enum class eChatMessageResponseCode : uint8_t { - SENT = 0, - NOTONLINE, - GENERALERROR, - RECEIVEDNEWWHISPER, - NOTFRIENDS, - SENDERFREETRIAL, - RECEIVERFREETRIAL, + SENT = 0, + NOTONLINE, + GENERALERROR, + RECEIVEDNEWWHISPER, + NOTFRIENDS, + SENDERFREETRIAL, + RECEIVERFREETRIAL, }; namespace ChatPacketHandler { @@ -52,33 +52,14 @@ namespace ChatPacketHandler { void HandleGMLevelUpdate(Packet* packet); void HandleWho(Packet* packet); void HandleShowAll(Packet* packet); - void HandleChatMessage(Packet* packet); void HandlePrivateChatMessage(Packet* packet); - void SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode); - void HandleTeamInvite(Packet* packet); - void HandleTeamInviteResponse(Packet* packet); - void HandleTeamLeave(Packet* packet); - void HandleTeamKick(Packet* packet); - void HandleTeamPromote(Packet* packet); - void HandleTeamLootOption(Packet* packet); - void HandleTeamStatusRequest(Packet* packet); void OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr); - void SendTeamInvite(const PlayerData& receiver, const PlayerData& sender); - void SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName); - void SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName); - void SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID); - void SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID); - - /* Sends a message to the provided `receiver` with information about the updated team. If `i64LeaderID` is not LWOOBJID_EMPTY, the client will update the leader to that new playerID. */ - void SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName); - void SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID); - //FriendData is the player we're SENDING this stuff to. Player is the friend that changed state. void SendFriendUpdate(const PlayerData& friendData, const PlayerData& playerData, uint8_t notifyType, uint8_t isBestFriend); - + void SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode); void SendFriendRequest(const PlayerData& receiver, const PlayerData& sender); void SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready = 0U, uint8_t isBestFriendRequest = 0U); void SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful); diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index d6728a9a..5c673e27 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -20,6 +20,7 @@ #include "MessageType/World.h" #include "ChatIgnoreList.h" #include "StringifiedEnum.h" +#include "TeamContainer.h" #include "Game.h" #include "Server.h" @@ -95,7 +96,7 @@ int main(int argc, char** argv) { // seyup the chat api web server bool web_server_enabled = Game::config->GetValue("web_server_enabled") == "1"; ChatWebAPI chatwebapi; - if (web_server_enabled && !chatwebapi.Startup()){ + if (web_server_enabled && !chatwebapi.Startup()) { // if we want the web api and it fails to start, exit LOG("Failed to start web server, shutting down."); Database::Destroy("ChatServer"); @@ -197,6 +198,7 @@ int main(int argc, char** argv) { std::this_thread::sleep_until(t); } Game::playerContainer.Shutdown(); + TeamContainer::Shutdown(); //Delete our objects here: Database::Destroy("ChatServer"); delete Game::server; @@ -234,7 +236,7 @@ void HandlePacket(Packet* packet) { break; case MessageType::Chat::CREATE_TEAM: - Game::playerContainer.CreateTeamServer(packet); + TeamContainer::CreateTeamServer(packet); break; case MessageType::Chat::GET_FRIENDS_LIST: @@ -254,7 +256,7 @@ void HandlePacket(Packet* packet) { break; case MessageType::Chat::TEAM_GET_STATUS: - ChatPacketHandler::HandleTeamStatusRequest(packet); + TeamContainer::HandleTeamStatusRequest(packet); break; case MessageType::Chat::ADD_FRIEND_REQUEST: @@ -284,27 +286,27 @@ void HandlePacket(Packet* packet) { break; case MessageType::Chat::TEAM_INVITE: - ChatPacketHandler::HandleTeamInvite(packet); + TeamContainer::HandleTeamInvite(packet); break; case MessageType::Chat::TEAM_INVITE_RESPONSE: - ChatPacketHandler::HandleTeamInviteResponse(packet); + TeamContainer::HandleTeamInviteResponse(packet); break; case MessageType::Chat::TEAM_LEAVE: - ChatPacketHandler::HandleTeamLeave(packet); + TeamContainer::HandleTeamLeave(packet); break; case MessageType::Chat::TEAM_SET_LEADER: - ChatPacketHandler::HandleTeamPromote(packet); + TeamContainer::HandleTeamPromote(packet); break; case MessageType::Chat::TEAM_KICK: - ChatPacketHandler::HandleTeamKick(packet); + TeamContainer::HandleTeamKick(packet); break; case MessageType::Chat::TEAM_SET_LOOT: - ChatPacketHandler::HandleTeamLootOption(packet); + TeamContainer::HandleTeamLootOption(packet); break; case MessageType::Chat::GMLEVEL_UPDATE: ChatPacketHandler::HandleGMLevelUpdate(packet); diff --git a/dChatServer/ChatWebAPI.cpp b/dChatServer/ChatWebAPI.cpp index 49903ced..c083c183 100644 --- a/dChatServer/ChatWebAPI.cpp +++ b/dChatServer/ChatWebAPI.cpp @@ -28,7 +28,7 @@ typedef struct mg_http_message mg_http_message; namespace { const char* json_content_type = "Content-Type: application/json\r\n"; - std::map, WebAPIHTTPRoute> Routes {}; + std::map, WebAPIHTTPRoute> Routes{}; } bool ValidateAuthentication(const mg_http_message* http_msg) { @@ -54,7 +54,7 @@ void HandlePlayersRequest(HTTPReply& reply, std::string body) { } void HandleTeamsRequest(HTTPReply& reply, std::string body) { - const json data = Game::playerContainer.GetTeamContainer(); + const json data = TeamContainer::GetTeamContainer(); reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK; reply.message = data.empty() ? "{\"error\":\"No Teams Online\"}" : data.dump(); } @@ -87,12 +87,12 @@ void HandleInvalidRoute(HTTPReply& reply) { void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_msg) { HTTPReply reply; - + if (!http_msg) { reply.status = eHTTPStatusCode::BAD_REQUEST; reply.message = "{\"error\":\"Invalid Request\"}"; } else if (ValidateAuthentication(http_msg)) { - + // convert method from cstring to std string std::string method_string(http_msg->method.buf, http_msg->method.len); // get mehtod from mg to enum @@ -105,8 +105,8 @@ void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_ms // convert body from cstring to std string std::string body(http_msg->body.buf, http_msg->body.len); - - const auto routeItr = Routes.find({method, uri}); + + const auto routeItr = Routes.find({ method, uri }); if (routeItr != Routes.end()) { const auto& [_, route] = *routeItr; @@ -122,11 +122,11 @@ void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_ms void HandleRequests(mg_connection* connection, int request, void* request_data) { switch (request) { - case MG_EV_HTTP_MSG: - HandleHTTPMessage(connection, static_cast(request_data)); - break; - default: - break; + case MG_EV_HTTP_MSG: + HandleHTTPMessage(connection, static_cast(request_data)); + break; + default: + break; } } @@ -172,19 +172,19 @@ bool ChatWebAPI::Startup() { .path = v1_route + "players", .method = eHTTPMethod::GET, .handle = HandlePlayersRequest - }); + }); RegisterHTTPRoutes({ .path = v1_route + "teams", .method = eHTTPMethod::GET, .handle = HandleTeamsRequest - }); + }); RegisterHTTPRoutes({ .path = v1_route + "announce", .method = eHTTPMethod::POST, .handle = HandleAnnounceRequest - }); + }); return true; } diff --git a/dChatServer/JSONUtils.cpp b/dChatServer/JSONUtils.cpp index 1c32409c..116961fb 100644 --- a/dChatServer/JSONUtils.cpp +++ b/dChatServer/JSONUtils.cpp @@ -18,19 +18,12 @@ void to_json(json& data, const PlayerData& playerData) { void to_json(json& data, const PlayerContainer& playerContainer) { data = json::array(); - for(auto& playerData : playerContainer.GetAllPlayers()) { + for (auto& playerData : playerContainer.GetAllPlayers()) { if (playerData.first == LWOOBJID_EMPTY) continue; data.push_back(playerData.second); } } -void to_json(json& data, const TeamContainer& teamContainer) { - for (auto& teamData : Game::playerContainer.GetTeams()) { - if (!teamData) continue; - data.push_back(*teamData); - } -} - void to_json(json& data, const TeamData& teamData) { data["id"] = teamData.teamID; data["loot_flag"] = teamData.lootFlag; @@ -48,6 +41,13 @@ void to_json(json& data, const TeamData& teamData) { } } +void TeamContainer::to_json(json& data, const TeamContainer::Data& teamContainer) { + for (auto& teamData : TeamContainer::GetTeams()) { + if (!teamData) continue; + data.push_back(*teamData); + } +} + std::string JSONUtils::CheckRequiredData(const json& data, const std::vector& requiredData) { json check; check["error"] = json::array(); diff --git a/dChatServer/JSONUtils.h b/dChatServer/JSONUtils.h index a46a1667..ccdd5359 100644 --- a/dChatServer/JSONUtils.h +++ b/dChatServer/JSONUtils.h @@ -3,12 +3,18 @@ #include "json_fwd.hpp" #include "PlayerContainer.h" +#include "TeamContainer.h" + +/* Remember, to_json needs to be in the same namespace as the class its located in */ void to_json(nlohmann::json& data, const PlayerData& playerData); void to_json(nlohmann::json& data, const PlayerContainer& playerContainer); -void to_json(nlohmann::json& data, const TeamContainer& teamData); void to_json(nlohmann::json& data, const TeamData& teamData); +namespace TeamContainer { + void to_json(nlohmann::json& data, const TeamContainer::Data& teamData); +}; + namespace JSONUtils { // check required data for reqeust std::string CheckRequiredData(const nlohmann::json& data, const std::vector& requiredData); diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index b3323f81..ec53fa32 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -12,6 +12,7 @@ #include "ChatPackets.h" #include "dConfig.h" #include "MessageType/Chat.h" +#include "TeamContainer.h" void PlayerContainer::Initialize() { m_MaxNumberOfBestFriends = @@ -99,7 +100,7 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) { if (fd) ChatPacketHandler::SendFriendUpdate(fd, player, 0, fr.isBestFriend); } - auto* team = GetTeam(playerID); + auto* team = TeamContainer::GetTeam(playerID); if (team != nullptr) { const auto memberName = GeneralUtils::UTF8ToUTF16(player.playerName); @@ -109,7 +110,7 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) { if (!otherMember) continue; - ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 }); + TeamContainer::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 }); } } @@ -140,40 +141,6 @@ void PlayerContainer::MuteUpdate(Packet* packet) { BroadcastMuteUpdate(playerID, expire); } -void PlayerContainer::CreateTeamServer(Packet* packet) { - CINSTREAM_SKIP_HEADER; - LWOOBJID playerID; - inStream.Read(playerID); - size_t membersSize = 0; - inStream.Read(membersSize); - - if (membersSize >= 4) { - LOG("Tried to create a team with more than 4 players"); - return; - } - - std::vector members; - - members.reserve(membersSize); - - for (size_t i = 0; i < membersSize; i++) { - LWOOBJID member; - inStream.Read(member); - members.push_back(member); - } - - LWOZONEID zoneId; - - inStream.Read(zoneId); - - auto* team = CreateLocalTeam(members); - - if (team != nullptr) { - team->zoneId = zoneId; - UpdateTeamsOnWorld(team, false); - } -} - void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) { CBITSTREAM; BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::GM_MUTE); @@ -184,218 +151,6 @@ void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) { Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true); } -TeamData* PlayerContainer::CreateLocalTeam(std::vector members) { - if (members.empty()) { - return nullptr; - } - - TeamData* newTeam = nullptr; - - for (const auto member : members) { - auto* team = GetTeam(member); - - if (team != nullptr) { - RemoveMember(team, member, false, false, true); - } - - if (newTeam == nullptr) { - newTeam = CreateTeam(member, true); - } else { - AddMember(newTeam, member); - } - } - - newTeam->lootFlag = 1; - - TeamStatusUpdate(newTeam); - - return newTeam; -} - -TeamData* PlayerContainer::CreateTeam(LWOOBJID leader, bool local) { - auto* team = new TeamData(); - - team->teamID = ++m_TeamIDCounter; - team->leaderID = leader; - team->local = local; - - GetTeamsMut().push_back(team); - - AddMember(team, leader); - - return team; -} - -TeamData* PlayerContainer::GetTeam(LWOOBJID playerID) { - for (auto* team : GetTeams()) { - if (std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID) == team->memberIDs.end()) continue; - - return team; - } - - return nullptr; -} - -void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID) { - if (team->memberIDs.size() >= 4) { - LOG("Tried to add player to team that already had 4 players"); - const auto& player = GetPlayerData(playerID); - if (!player) return; - ChatPackets::SendSystemMessage(player.worldServerSysAddr, u"The teams is full! You have not been added to a team!"); - return; - } - - const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID); - - if (index != team->memberIDs.end()) return; - - team->memberIDs.push_back(playerID); - - const auto& leader = GetPlayerData(team->leaderID); - const auto& member = GetPlayerData(playerID); - - if (!leader || !member) return; - - const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName); - const auto memberName = GeneralUtils::UTF8ToUTF16(member.playerName); - - ChatPacketHandler::SendTeamInviteConfirm(member, false, leader.playerID, leader.zoneID, team->lootFlag, 0, 0, leaderName); - - if (!team->local) { - ChatPacketHandler::SendTeamSetLeader(member, leader.playerID); - } else { - ChatPacketHandler::SendTeamSetLeader(member, LWOOBJID_EMPTY); - } - - UpdateTeamsOnWorld(team, false); - - for (const auto memberId : team->memberIDs) { - const auto& otherMember = GetPlayerData(memberId); - - if (otherMember == member) continue; - - const auto otherMemberName = GetName(memberId); - - ChatPacketHandler::SendTeamAddPlayer(member, false, team->local, false, memberId, otherMemberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0)); - - if (otherMember) { - ChatPacketHandler::SendTeamAddPlayer(otherMember, false, team->local, false, member.playerID, memberName, member.zoneID); - } - } -} - -void PlayerContainer::RemoveMember(TeamData* team, LWOOBJID causingPlayerID, bool disband, bool kicked, bool leaving, bool silent) { - LOG_DEBUG("Player %llu is leaving team %i", causingPlayerID, team->teamID); - const auto index = std::ranges::find(team->memberIDs, causingPlayerID); - - if (index == team->memberIDs.end()) return; - - team->memberIDs.erase(index); - - const auto& member = GetPlayerData(causingPlayerID); - - const auto causingMemberName = GetName(causingPlayerID); - - if (member && !silent) { - ChatPacketHandler::SendTeamRemovePlayer(member, disband, kicked, leaving, team->local, LWOOBJID_EMPTY, causingPlayerID, causingMemberName); - } - - if (team->memberIDs.size() <= 1) { - DisbandTeam(team, causingPlayerID, causingMemberName); - } else /* team has enough members to be a team still */ { - team->leaderID = (causingPlayerID == team->leaderID) ? team->memberIDs[0] : team->leaderID; - for (const auto memberId : team->memberIDs) { - if (silent && memberId == causingPlayerID) { - continue; - } - - const auto& otherMember = GetPlayerData(memberId); - - if (!otherMember) continue; - - ChatPacketHandler::SendTeamRemovePlayer(otherMember, disband, kicked, leaving, team->local, team->leaderID, causingPlayerID, causingMemberName); - } - - UpdateTeamsOnWorld(team, false); - } -} - -void PlayerContainer::PromoteMember(TeamData* team, LWOOBJID newLeader) { - team->leaderID = newLeader; - - for (const auto memberId : team->memberIDs) { - const auto& otherMember = GetPlayerData(memberId); - - if (!otherMember) continue; - - ChatPacketHandler::SendTeamSetLeader(otherMember, newLeader); - } -} - -void PlayerContainer::DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName) { - const auto index = std::ranges::find(GetTeams(), team); - - if (index == GetTeams().end()) return; - LOG_DEBUG("Disbanding team %i", (*index)->teamID); - - for (const auto memberId : team->memberIDs) { - const auto& otherMember = GetPlayerData(memberId); - - if (!otherMember) continue; - - ChatPacketHandler::SendTeamSetLeader(otherMember, LWOOBJID_EMPTY); - ChatPacketHandler::SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, causingPlayerID, causingPlayerName); - } - - UpdateTeamsOnWorld(team, true); - - GetTeamsMut().erase(index); - - delete team; -} - -void PlayerContainer::TeamStatusUpdate(TeamData* team) { - const auto index = std::find(GetTeams().begin(), GetTeams().end(), team); - - if (index == GetTeams().end()) return; - - const auto& leader = GetPlayerData(team->leaderID); - - if (!leader) return; - - const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName); - - for (const auto memberId : team->memberIDs) { - const auto& otherMember = GetPlayerData(memberId); - - if (!otherMember) continue; - - if (!team->local) { - ChatPacketHandler::SendTeamStatus(otherMember, team->leaderID, leader.zoneID, team->lootFlag, 0, leaderName); - } - } - - UpdateTeamsOnWorld(team, false); -} - -void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::TEAM_GET_STATUS); - - bitStream.Write(team->teamID); - bitStream.Write(deleteTeam); - - if (!deleteTeam) { - bitStream.Write(team->lootFlag); - bitStream.Write(team->memberIDs.size()); - for (const auto memberID : team->memberIDs) { - bitStream.Write(memberID); - } - } - - Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true); -} - std::u16string PlayerContainer::GetName(LWOOBJID playerID) { const auto iter = m_Names.find(playerID); @@ -444,5 +199,4 @@ void PlayerContainer::Shutdown() { Database::Get()->UpdateActivityLog(id, eActivityType::PlayerLoggedOut, playerData.zoneID.GetMapID()); m_Players.erase(m_Players.begin()); } - for (auto* team : GetTeams()) if (team) delete team; } diff --git a/dChatServer/PlayerContainer.h b/dChatServer/PlayerContainer.h index f4cffa3d..20785747 100644 --- a/dChatServer/PlayerContainer.h +++ b/dChatServer/PlayerContainer.h @@ -11,10 +11,6 @@ enum class eGameMasterLevel : uint8_t; struct TeamData; -struct TeamContainer { - std::vector mTeams; -}; - struct IgnoreData { IgnoreData(const std::string& name, const LWOOBJID& id) : playerName{ name }, playerId{ id } {} inline bool operator==(const std::string& other) const noexcept { @@ -73,7 +69,6 @@ public: 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(); @@ -81,34 +76,19 @@ public: const PlayerData& GetPlayerData(const std::string& playerName); PlayerData& GetPlayerDataMutable(const LWOOBJID& playerID); PlayerData& GetPlayerDataMutable(const std::string& playerName); + std::u16string GetName(LWOOBJID playerID); + LWOOBJID GetId(const std::u16string& playerName); + void Update(const float deltaTime); + uint32_t GetPlayerCount() { return m_PlayerCount; }; uint32_t GetSimCount() { return m_SimCount; }; const std::map& GetAllPlayers() const { return m_Players; }; - - TeamData* CreateLocalTeam(std::vector members); - TeamData* CreateTeam(LWOOBJID leader, bool local = false); - TeamData* GetTeam(LWOOBJID playerID); - void AddMember(TeamData* team, LWOOBJID playerID); - void RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent = false); - void PromoteMember(TeamData* team, LWOOBJID newLeader); - void DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName); - void TeamStatusUpdate(TeamData* team); - void UpdateTeamsOnWorld(TeamData* team, bool deleteTeam); - std::u16string GetName(LWOOBJID playerID); - LWOOBJID GetId(const std::u16string& playerName); uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; } uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; } - const TeamContainer& GetTeamContainer() { return m_TeamContainer; } - std::vector& GetTeamsMut() { return m_TeamContainer.mTeams; }; - const std::vector& GetTeams() { return GetTeamsMut(); }; - - void Update(const float deltaTime); bool PlayerBeingRemoved(const LWOOBJID playerID) { return m_PlayersToRemove.contains(playerID); } private: - LWOOBJID m_TeamIDCounter = 0; std::map m_Players; - TeamContainer m_TeamContainer{}; std::unordered_map m_Names; std::map m_PlayersToRemove; uint32_t m_MaxNumberOfBestFriends = 5; diff --git a/dChatServer/TeamContainer.cpp b/dChatServer/TeamContainer.cpp new file mode 100644 index 00000000..024a0464 --- /dev/null +++ b/dChatServer/TeamContainer.cpp @@ -0,0 +1,669 @@ +#include "TeamContainer.h" + +#include "ChatPackets.h" + +#include "MessageType/Chat.h" +#include "MessageType/Game.h" + +#include "ChatPacketHandler.h" +#include "PlayerContainer.h" + +namespace { + TeamContainer::Data g_TeamContainer{}; + LWOOBJID g_TeamIDCounter = 0; +} + +const TeamContainer::Data& TeamContainer::GetTeamContainer() { + return g_TeamContainer; +} + +std::vector& TeamContainer::GetTeamsMut() { + return g_TeamContainer.mTeams; +} + +const std::vector& TeamContainer::GetTeams() { + return GetTeamsMut(); +} + +void TeamContainer::Shutdown() { + for (auto* team : g_TeamContainer.mTeams) if (team) delete team; +} + +void TeamContainer::HandleTeamInvite(Packet* packet) { + CINSTREAM_SKIP_HEADER; + + LWOOBJID playerID; + LUWString invitedPlayer; + + inStream.Read(playerID); + inStream.IgnoreBytes(4); + inStream.Read(invitedPlayer); + + const auto& player = Game::playerContainer.GetPlayerData(playerID); + + if (!player) return; + + auto* team = GetTeam(playerID); + + if (team == nullptr) { + team = CreateTeam(playerID); + } + + const auto& other = Game::playerContainer.GetPlayerData(invitedPlayer.GetAsString()); + + if (!other) return; + + if (GetTeam(other.playerID) != nullptr) { + return; + } + + if (team->memberIDs.size() > 3) { + // no more teams greater than 4 + + LOG("Someone tried to invite a 5th player to a team"); + return; + } + + SendTeamInvite(other, player); + + LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str()); + + bool failed = false; + for (const auto& ignore : other.ignoredPlayers) { + if (ignore.playerId == player.playerID) { + failed = true; + break; + } + } + + ChatPackets::TeamInviteInitialResponse response{}; + response.inviteFailedToSend = failed; + response.playerName = invitedPlayer.string; + ChatPackets::SendRoutedMsg(response, playerID, player.worldServerSysAddr); +} + +void TeamContainer::HandleTeamInviteResponse(Packet* packet) { + CINSTREAM_SKIP_HEADER; + LWOOBJID playerID = LWOOBJID_EMPTY; + inStream.Read(playerID); + uint32_t size = 0; + inStream.Read(size); + char declined = 0; + inStream.Read(declined); + LWOOBJID leaderID = LWOOBJID_EMPTY; + inStream.Read(leaderID); + + LOG("Invite reponse received: %llu -> %llu (%d)", playerID, leaderID, declined); + + if (declined) { + return; + } + + auto* team = GetTeam(leaderID); + + if (team == nullptr) { + LOG("Failed to find team for leader (%llu)", leaderID); + + team = GetTeam(playerID); + } + + if (team == nullptr) { + LOG("Failed to find team for player (%llu)", playerID); + return; + } + + AddMember(team, playerID); +} + +void TeamContainer::HandleTeamLeave(Packet* packet) { + CINSTREAM_SKIP_HEADER; + LWOOBJID playerID = LWOOBJID_EMPTY; + inStream.Read(playerID); + uint32_t size = 0; + inStream.Read(size); + + auto* team = GetTeam(playerID); + + LOG("(%llu) leaving team", playerID); + + if (team != nullptr) { + RemoveMember(team, playerID, false, false, true); + } +} + +void TeamContainer::HandleTeamKick(Packet* packet) { + CINSTREAM_SKIP_HEADER; + + LWOOBJID playerID = LWOOBJID_EMPTY; + LUWString kickedPlayer; + + inStream.Read(playerID); + inStream.IgnoreBytes(4); + inStream.Read(kickedPlayer); + + + LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.GetAsString().c_str()); + + const auto& kicked = Game::playerContainer.GetPlayerData(kickedPlayer.GetAsString()); + + LWOOBJID kickedId = LWOOBJID_EMPTY; + + if (kicked) { + kickedId = kicked.playerID; + } else { + kickedId = Game::playerContainer.GetId(kickedPlayer.string); + } + + if (kickedId == LWOOBJID_EMPTY) return; + + auto* team = GetTeam(playerID); + + if (team != nullptr) { + if (team->leaderID != playerID || team->leaderID == kickedId) return; + + RemoveMember(team, kickedId, false, true, false); + } +} + +void TeamContainer::HandleTeamPromote(Packet* packet) { + CINSTREAM_SKIP_HEADER; + + LWOOBJID playerID = LWOOBJID_EMPTY; + LUWString promotedPlayer; + + inStream.Read(playerID); + inStream.IgnoreBytes(4); + inStream.Read(promotedPlayer); + + LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.GetAsString().c_str()); + + const auto& promoted = Game::playerContainer.GetPlayerData(promotedPlayer.GetAsString()); + + if (!promoted) return; + + auto* team = GetTeam(playerID); + + if (team != nullptr) { + if (team->leaderID != playerID) return; + + PromoteMember(team, promoted.playerID); + } +} + +void TeamContainer::HandleTeamLootOption(Packet* packet) { + CINSTREAM_SKIP_HEADER; + LWOOBJID playerID = LWOOBJID_EMPTY; + inStream.Read(playerID); + uint32_t size = 0; + inStream.Read(size); + + char option; + inStream.Read(option); + + auto* team = GetTeam(playerID); + + if (team != nullptr) { + if (team->leaderID != playerID) return; + + team->lootFlag = option; + + TeamStatusUpdate(team); + + UpdateTeamsOnWorld(team, false); + } +} + +void TeamContainer::HandleTeamStatusRequest(Packet* packet) { + CINSTREAM_SKIP_HEADER; + LWOOBJID playerID = LWOOBJID_EMPTY; + inStream.Read(playerID); + + auto* team = GetTeam(playerID); + const auto& data = Game::playerContainer.GetPlayerData(playerID); + + if (team != nullptr && data) { + LOG_DEBUG("Player %llu is requesting team status", playerID); + if (team->local && data.zoneID.GetMapID() != team->zoneId.GetMapID() && data.zoneID.GetCloneID() != team->zoneId.GetCloneID()) { + RemoveMember(team, playerID, false, false, false, true); + + return; + } + + if (team->memberIDs.size() <= 1 && !team->local) { + DisbandTeam(team, LWOOBJID_EMPTY, u""); + + return; + } + + if (!team->local) { + SendTeamSetLeader(data, team->leaderID); + } else { + SendTeamSetLeader(data, LWOOBJID_EMPTY); + } + + TeamStatusUpdate(team); + + const auto leaderName = GeneralUtils::UTF8ToUTF16(data.playerName); + + for (const auto memberId : team->memberIDs) { + const auto& otherMember = Game::playerContainer.GetPlayerData(memberId); + + if (memberId == playerID) continue; + + const auto memberName = Game::playerContainer.GetName(memberId); + + if (otherMember) { + SendTeamSetOffWorldFlag(otherMember, data.playerID, data.zoneID); + } + SendTeamAddPlayer(data, false, team->local, false, memberId, memberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0)); + } + + UpdateTeamsOnWorld(team, false); + } +} + +void TeamContainer::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) { + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); + bitStream.Write(receiver.playerID); + + //portion that will get routed: + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::TEAM_INVITE); + + bitStream.Write(LUWString(sender.playerName.c_str())); + bitStream.Write(sender.playerID); + + SystemAddress sysAddr = receiver.worldServerSysAddr; + SEND_PACKET; +} + +void TeamContainer::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) { + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); + bitStream.Write(receiver.playerID); + + //portion that will get routed: + CMSGHEADER; + + bitStream.Write(receiver.playerID); + bitStream.Write(MessageType::Game::TEAM_INVITE_CONFIRM); + + bitStream.Write(bLeaderIsFreeTrial); + bitStream.Write(i64LeaderID); + bitStream.Write(i64LeaderZoneID); + bitStream.Write(0); // BinaryBuffe, no clue what's in here + bitStream.Write(ucLootFlag); + bitStream.Write(ucNumOfOtherPlayers); + bitStream.Write(ucResponseCode); + bitStream.Write(wsLeaderName.size()); + for (const auto character : wsLeaderName) { + bitStream.Write(character); + } + + SystemAddress sysAddr = receiver.worldServerSysAddr; + SEND_PACKET; +} + +void TeamContainer::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) { + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); + bitStream.Write(receiver.playerID); + + //portion that will get routed: + CMSGHEADER; + + bitStream.Write(receiver.playerID); + bitStream.Write(MessageType::Game::TEAM_GET_STATUS_RESPONSE); + + bitStream.Write(i64LeaderID); + bitStream.Write(i64LeaderZoneID); + bitStream.Write(0); // BinaryBuffe, no clue what's in here + bitStream.Write(ucLootFlag); + bitStream.Write(ucNumOfOtherPlayers); + bitStream.Write(wsLeaderName.size()); + for (const auto character : wsLeaderName) { + bitStream.Write(character); + } + + SystemAddress sysAddr = receiver.worldServerSysAddr; + SEND_PACKET; +} + +void TeamContainer::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) { + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); + bitStream.Write(receiver.playerID); + + //portion that will get routed: + CMSGHEADER; + + bitStream.Write(receiver.playerID); + bitStream.Write(MessageType::Game::TEAM_SET_LEADER); + + bitStream.Write(i64PlayerID); + + SystemAddress sysAddr = receiver.worldServerSysAddr; + SEND_PACKET; +} + +void TeamContainer::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) { + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); + bitStream.Write(receiver.playerID); + + //portion that will get routed: + CMSGHEADER; + + bitStream.Write(receiver.playerID); + bitStream.Write(MessageType::Game::TEAM_ADD_PLAYER); + + bitStream.Write(bIsFreeTrial); + bitStream.Write(bLocal); + bitStream.Write(bNoLootOnDeath); + bitStream.Write(i64PlayerID); + bitStream.Write(wsPlayerName.size()); + for (const auto character : wsPlayerName) { + bitStream.Write(character); + } + bitStream.Write1(); + if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) { + zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0); + } + bitStream.Write(zoneID); + + SystemAddress sysAddr = receiver.worldServerSysAddr; + SEND_PACKET; +} + +void TeamContainer::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) { + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); + bitStream.Write(receiver.playerID); + + //portion that will get routed: + CMSGHEADER; + + bitStream.Write(receiver.playerID); + bitStream.Write(MessageType::Game::TEAM_REMOVE_PLAYER); + + bitStream.Write(bDisband); + bitStream.Write(bIsKicked); + bitStream.Write(bIsLeaving); + bitStream.Write(bLocal); + bitStream.Write(i64LeaderID); + bitStream.Write(i64PlayerID); + bitStream.Write(wsPlayerName.size()); + for (const auto character : wsPlayerName) { + bitStream.Write(character); + } + + SystemAddress sysAddr = receiver.worldServerSysAddr; + SEND_PACKET; +} + +void TeamContainer::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) { + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); + bitStream.Write(receiver.playerID); + + //portion that will get routed: + CMSGHEADER; + + bitStream.Write(receiver.playerID); + bitStream.Write(MessageType::Game::TEAM_SET_OFF_WORLD_FLAG); + + bitStream.Write(i64PlayerID); + if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) { + zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0); + } + bitStream.Write(zoneID); + + SystemAddress sysAddr = receiver.worldServerSysAddr; + SEND_PACKET; +} + +void TeamContainer::CreateTeamServer(Packet* packet) { + CINSTREAM_SKIP_HEADER; + LWOOBJID playerID; + inStream.Read(playerID); + size_t membersSize = 0; + inStream.Read(membersSize); + + if (membersSize >= 4) { + LOG("Tried to create a team with more than 4 players"); + return; + } + + std::vector members; + + members.reserve(membersSize); + + for (size_t i = 0; i < membersSize; i++) { + LWOOBJID member; + inStream.Read(member); + members.push_back(member); + } + + LWOZONEID zoneId; + + inStream.Read(zoneId); + + auto* team = CreateLocalTeam(members); + + if (team != nullptr) { + team->zoneId = zoneId; + UpdateTeamsOnWorld(team, false); + } +} + +TeamData* TeamContainer::CreateLocalTeam(std::vector members) { + if (members.empty()) { + return nullptr; + } + + TeamData* newTeam = nullptr; + + for (const auto member : members) { + auto* team = GetTeam(member); + + if (team != nullptr) { + RemoveMember(team, member, false, false, true); + } + + if (newTeam == nullptr) { + newTeam = CreateTeam(member, true); + } else { + AddMember(newTeam, member); + } + } + + newTeam->lootFlag = 1; + + TeamStatusUpdate(newTeam); + + return newTeam; +} + +TeamData* TeamContainer::CreateTeam(LWOOBJID leader, bool local) { + auto* team = new TeamData(); + + team->teamID = ++g_TeamIDCounter; + team->leaderID = leader; + team->local = local; + + GetTeamsMut().push_back(team); + + AddMember(team, leader); + + return team; +} + +TeamData* TeamContainer::GetTeam(LWOOBJID playerID) { + for (auto* team : GetTeams()) { + if (std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID) == team->memberIDs.end()) continue; + + return team; + } + + return nullptr; +} + +void TeamContainer::AddMember(TeamData* team, LWOOBJID playerID) { + if (team->memberIDs.size() >= 4) { + LOG("Tried to add player to team that already had 4 players"); + const auto& player = Game::playerContainer.GetPlayerData(playerID); + if (!player) return; + ChatPackets::SendSystemMessage(player.worldServerSysAddr, u"The teams is full! You have not been added to a team!"); + return; + } + + const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID); + + if (index != team->memberIDs.end()) return; + + team->memberIDs.push_back(playerID); + + const auto& leader = Game::playerContainer.GetPlayerData(team->leaderID); + const auto& member = Game::playerContainer.GetPlayerData(playerID); + + if (!leader || !member) return; + + const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName); + const auto memberName = GeneralUtils::UTF8ToUTF16(member.playerName); + + SendTeamInviteConfirm(member, false, leader.playerID, leader.zoneID, team->lootFlag, 0, 0, leaderName); + + if (!team->local) { + SendTeamSetLeader(member, leader.playerID); + } else { + SendTeamSetLeader(member, LWOOBJID_EMPTY); + } + + UpdateTeamsOnWorld(team, false); + + for (const auto memberId : team->memberIDs) { + const auto& otherMember = Game::playerContainer.GetPlayerData(memberId); + + if (otherMember == member) continue; + + const auto otherMemberName = Game::playerContainer.GetName(memberId); + + SendTeamAddPlayer(member, false, team->local, false, memberId, otherMemberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0)); + + if (otherMember) { + SendTeamAddPlayer(otherMember, false, team->local, false, member.playerID, memberName, member.zoneID); + } + } +} + +void TeamContainer::RemoveMember(TeamData* team, LWOOBJID causingPlayerID, bool disband, bool kicked, bool leaving, bool silent) { + LOG_DEBUG("Player %llu is leaving team %i", causingPlayerID, team->teamID); + const auto index = std::ranges::find(team->memberIDs, causingPlayerID); + + if (index == team->memberIDs.end()) return; + + team->memberIDs.erase(index); + + const auto& member = Game::playerContainer.GetPlayerData(causingPlayerID); + + const auto causingMemberName = Game::playerContainer.GetName(causingPlayerID); + + if (member && !silent) { + SendTeamRemovePlayer(member, disband, kicked, leaving, team->local, LWOOBJID_EMPTY, causingPlayerID, causingMemberName); + } + + if (team->memberIDs.size() <= 1) { + DisbandTeam(team, causingPlayerID, causingMemberName); + } else /* team has enough members to be a team still */ { + team->leaderID = (causingPlayerID == team->leaderID) ? team->memberIDs[0] : team->leaderID; + for (const auto memberId : team->memberIDs) { + if (silent && memberId == causingPlayerID) { + continue; + } + + const auto& otherMember = Game::playerContainer.GetPlayerData(memberId); + + if (!otherMember) continue; + + SendTeamRemovePlayer(otherMember, disband, kicked, leaving, team->local, team->leaderID, causingPlayerID, causingMemberName); + } + + UpdateTeamsOnWorld(team, false); + } +} + +void TeamContainer::PromoteMember(TeamData* team, LWOOBJID newLeader) { + team->leaderID = newLeader; + + for (const auto memberId : team->memberIDs) { + const auto& otherMember = Game::playerContainer.GetPlayerData(memberId); + + if (!otherMember) continue; + + SendTeamSetLeader(otherMember, newLeader); + } +} + +void TeamContainer::DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName) { + const auto index = std::ranges::find(GetTeams(), team); + + if (index == GetTeams().end()) return; + LOG_DEBUG("Disbanding team %i", (*index)->teamID); + + for (const auto memberId : team->memberIDs) { + const auto& otherMember = Game::playerContainer.GetPlayerData(memberId); + + if (!otherMember) continue; + + SendTeamSetLeader(otherMember, LWOOBJID_EMPTY); + SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, causingPlayerID, causingPlayerName); + } + + UpdateTeamsOnWorld(team, true); + + GetTeamsMut().erase(index); + + delete team; +} + +void TeamContainer::TeamStatusUpdate(TeamData* team) { + const auto index = std::find(GetTeams().begin(), GetTeams().end(), team); + + if (index == GetTeams().end()) return; + + const auto& leader = Game::playerContainer.GetPlayerData(team->leaderID); + + if (!leader) return; + + const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName); + + for (const auto memberId : team->memberIDs) { + const auto& otherMember = Game::playerContainer.GetPlayerData(memberId); + + if (!otherMember) continue; + + if (!team->local) { + SendTeamStatus(otherMember, team->leaderID, leader.zoneID, team->lootFlag, 0, leaderName); + } + } + + UpdateTeamsOnWorld(team, false); +} + +void TeamContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) { + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::TEAM_GET_STATUS); + + bitStream.Write(team->teamID); + bitStream.Write(deleteTeam); + + if (!deleteTeam) { + bitStream.Write(team->lootFlag); + bitStream.Write(team->memberIDs.size()); + for (const auto memberID : team->memberIDs) { + bitStream.Write(memberID); + } + } + + Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true); +} diff --git a/dChatServer/TeamContainer.h b/dChatServer/TeamContainer.h new file mode 100644 index 00000000..a316cf71 --- /dev/null +++ b/dChatServer/TeamContainer.h @@ -0,0 +1,59 @@ +// Darkflame Universe +// Copyright 2025 + +#ifndef TEAMCONTAINER_H +#define TEAMCONTAINER_H + +#include +#include +#include + +#include "dCommonVars.h" + +struct Packet; +struct PlayerData; +struct TeamData; + +namespace TeamContainer { + struct Data { + std::vector mTeams; + }; + + void Shutdown(); + + void HandleTeamInvite(Packet* packet); + void HandleTeamInviteResponse(Packet* packet); + void HandleTeamLeave(Packet* packet); + void HandleTeamKick(Packet* packet); + void HandleTeamPromote(Packet* packet); + void HandleTeamLootOption(Packet* packet); + void HandleTeamStatusRequest(Packet* packet); + + void SendTeamInvite(const PlayerData& receiver, const PlayerData& sender); + void SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName); + void SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName); + void SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID); + void SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID); + + /* Sends a message to the provided `receiver` with information about the updated team. If `i64LeaderID` is not LWOOBJID_EMPTY, the client will update the leader to that new playerID. */ + void SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName); + void SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID); + + void CreateTeamServer(Packet* packet); + + TeamData* CreateLocalTeam(std::vector members); + TeamData* CreateTeam(LWOOBJID leader, bool local = false); + TeamData* GetTeam(LWOOBJID playerID); + void AddMember(TeamData* team, LWOOBJID playerID); + void RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent = false); + void PromoteMember(TeamData* team, LWOOBJID newLeader); + void DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName); + void TeamStatusUpdate(TeamData* team); + void UpdateTeamsOnWorld(TeamData* team, bool deleteTeam); + + const TeamContainer::Data& GetTeamContainer(); + std::vector& GetTeamsMut(); + const std::vector& GetTeams(); +}; + +#endif //!TEAMCONTAINER_H