#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); }