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