mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
With new binaries in place, master moves everything onto them while the server keeps running (the dashboard's Live update, /liveupdate or SIGUSR2 to master): - database migrations of the new build first; a failure stops there - UGC finishes the jobs it is running (queued rows stay pending), auth restarts, chat hands its teams to master for the next chat server; master starts the new processes and retries ones that don't come back - once the new chat server is up (CHAT_SERVER_READY) every world connects at once and sends its players again (LoginSessionNotify resync, no login logged) - every world instance is replaced with an instance migration: public worlds and private ones (same password) at once, properties after the old instance saved and froze the property (MIGRATE_PREPARE: no building, claiming or saving there any more), activity zones and character select once their players left or after a wait; empty instances just stop, zones in prestart_worlds get a new one first - the dashboard restarts last and picks the status up again Players land where they stood (position carried in CarriedPlayerState, also on properties and Moon Base). Draining instances get no new players (InstanceMigration::AcceptsNewPlayers) and show as "Moving players" in the world list. The order lives in LiveUpdateMachine.h without master state and is unit tested; master's glue is LiveUpdateCoordinator. Master itself is not replaced. Message IDs are appended only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
558 lines
17 KiB
C++
558 lines
17 KiB
C++
#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;
|
|
}
|
|
|
|
ChatHandoff TeamContainer::MakeHandoff() {
|
|
ChatHandoff handoff;
|
|
for (const auto* team : GetTeams()) {
|
|
if (!team) continue;
|
|
auto& carried = handoff.teams.emplace_back();
|
|
carried.teamId = team->teamID;
|
|
carried.leaderId = team->leaderID;
|
|
carried.members = team->memberIDs;
|
|
carried.lootFlag = team->lootFlag;
|
|
carried.local = team->local;
|
|
carried.zoneId = team->zoneId.GetMapID();
|
|
carried.instanceId = team->zoneId.GetInstanceID();
|
|
carried.cloneId = team->zoneId.GetCloneID();
|
|
}
|
|
return handoff;
|
|
}
|
|
|
|
void TeamContainer::Restore(const ChatHandoff& handoff) {
|
|
for (const auto& carried : handoff.teams) {
|
|
if (carried.members.empty()) continue;
|
|
auto* team = new TeamData();
|
|
team->teamID = carried.teamId;
|
|
team->leaderID = carried.leaderId;
|
|
team->memberIDs = carried.members;
|
|
team->lootFlag = carried.lootFlag;
|
|
team->local = carried.local;
|
|
team->zoneId = LWOZONEID(carried.zoneId, carried.instanceId, carried.cloneId);
|
|
GetTeamsMut().push_back(team);
|
|
g_TeamIDCounter = std::max(g_TeamIDCounter, carried.teamId);
|
|
}
|
|
LOG("Took over %zu team(s) from the last chat server", handoff.teams.size());
|
|
}
|
|
|
|
const TeamContainer::Data& TeamContainer::GetTeamContainer() {
|
|
return g_TeamContainer;
|
|
}
|
|
|
|
std::vector<TeamData*>& TeamContainer::GetTeamsMut() {
|
|
return g_TeamContainer.mTeams;
|
|
}
|
|
|
|
const std::vector<TeamData*>& TeamContainer::GetTeams() {
|
|
return GetTeamsMut();
|
|
}
|
|
|
|
void TeamContainer::Shutdown() {
|
|
for (auto* team : g_TeamContainer.mTeams) if (team) delete team;
|
|
}
|
|
|
|
void TeamContainer::HandleTeamInvite(const ChatPackets::TeamInvite& invite, const SystemAddress& sysAddr) {
|
|
const LWOOBJID playerID = invite.playerID;
|
|
const LUWString& invitedPlayer = invite.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;
|
|
}
|
|
}
|
|
|
|
ClientPackets::TeamInviteInitialResponse response{};
|
|
response.inviteFailedToSend = failed;
|
|
response.playerName = invitedPlayer.string;
|
|
ChatPacketHandler::SendRouted(playerID, player.worldServerSysAddr, response, player.worldServerSysAddr == UNASSIGNED_SYSTEM_ADDRESS);
|
|
}
|
|
|
|
void TeamContainer::HandleTeamInviteResponse(const ChatPackets::TeamInviteResponse& response, const SystemAddress& sysAddr) {
|
|
const LWOOBJID playerID = response.playerID;
|
|
const char declined = response.declined;
|
|
const LWOOBJID leaderID = response.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(const ChatPackets::TeamLeave& request, const SystemAddress& sysAddr) {
|
|
const LWOOBJID playerID = request.playerID;
|
|
|
|
auto* team = GetTeam(playerID);
|
|
|
|
LOG("(%llu) leaving team", playerID);
|
|
|
|
if (team != nullptr) {
|
|
RemoveMember(team, playerID, false, false, true);
|
|
}
|
|
}
|
|
|
|
void TeamContainer::HandleTeamKick(const ChatPackets::TeamKick& request, const SystemAddress& sysAddr) {
|
|
const LWOOBJID playerID = request.playerID;
|
|
const LUWString& kickedPlayer = request.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(const ChatPackets::TeamSetLeader& request, const SystemAddress& sysAddr) {
|
|
const LWOOBJID playerID = request.playerID;
|
|
const LUWString& promotedPlayer = request.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(const ChatPackets::TeamSetLoot& request, const SystemAddress& sysAddr) {
|
|
const LWOOBJID playerID = request.playerID;
|
|
const char option = request.lootFlag;
|
|
|
|
auto* team = GetTeam(playerID);
|
|
|
|
if (team != nullptr) {
|
|
if (team->leaderID != playerID) return;
|
|
|
|
team->lootFlag = option;
|
|
|
|
TeamStatusUpdate(team);
|
|
|
|
UpdateTeamsOnWorld(team, false);
|
|
}
|
|
}
|
|
|
|
void TeamContainer::HandleTeamStatusRequest(const ChatPackets::TeamGetStatus& request, const SystemAddress& sysAddr) {
|
|
const LWOOBJID playerID = request.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) {
|
|
ClientPackets::TeamInvite invite;
|
|
invite.senderName = LUWString(sender.playerName.c_str());
|
|
invite.senderID = sender.playerID;
|
|
ChatPacketHandler::SendRouted(receiver.playerID, receiver.worldServerSysAddr, invite);
|
|
}
|
|
|
|
void TeamContainer::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
|
|
ClientPackets::TeamInviteConfirm msg;
|
|
msg.target = receiver.playerID;
|
|
msg.bLeaderIsFreeTrial = bLeaderIsFreeTrial;
|
|
msg.i64LeaderID = i64LeaderID;
|
|
msg.i64LeaderZoneID = i64LeaderZoneID;
|
|
msg.ucLootFlag = ucLootFlag;
|
|
msg.ucNumOfOtherPlayers = ucNumOfOtherPlayers;
|
|
msg.ucResponseCode = ucResponseCode;
|
|
msg.wsLeaderName = wsLeaderName;
|
|
ChatPacketHandler::SendRouted(receiver.playerID, receiver.worldServerSysAddr, msg);
|
|
}
|
|
|
|
void TeamContainer::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
|
|
ClientPackets::TeamGetStatusResponse msg;
|
|
msg.target = receiver.playerID;
|
|
msg.i64LeaderID = i64LeaderID;
|
|
msg.i64LeaderZoneID = i64LeaderZoneID;
|
|
msg.ucLootFlag = ucLootFlag;
|
|
msg.ucNumOfOtherPlayers = ucNumOfOtherPlayers;
|
|
msg.wsLeaderName = wsLeaderName;
|
|
ChatPacketHandler::SendRouted(receiver.playerID, receiver.worldServerSysAddr, msg);
|
|
}
|
|
|
|
void TeamContainer::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
|
|
ClientPackets::TeamSetLeader msg;
|
|
msg.target = receiver.playerID;
|
|
msg.i64PlayerID = i64PlayerID;
|
|
ChatPacketHandler::SendRouted(receiver.playerID, receiver.worldServerSysAddr, msg);
|
|
}
|
|
|
|
void TeamContainer::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
|
|
ClientPackets::TeamAddPlayer msg;
|
|
msg.target = receiver.playerID;
|
|
msg.bIsFreeTrial = bIsFreeTrial;
|
|
msg.bLocal = bLocal;
|
|
msg.bNoLootOnDeath = bNoLootOnDeath;
|
|
msg.i64PlayerID = i64PlayerID;
|
|
msg.wsPlayerName = wsPlayerName;
|
|
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
|
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
|
}
|
|
msg.zoneID = zoneID;
|
|
ChatPacketHandler::SendRouted(receiver.playerID, receiver.worldServerSysAddr, msg);
|
|
}
|
|
|
|
void TeamContainer::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
|
|
ClientPackets::TeamRemovePlayer msg;
|
|
msg.target = receiver.playerID;
|
|
msg.bDisband = bDisband;
|
|
msg.bIsKicked = bIsKicked;
|
|
msg.bIsLeaving = bIsLeaving;
|
|
msg.bLocal = bLocal;
|
|
msg.i64LeaderID = i64LeaderID;
|
|
msg.i64PlayerID = i64PlayerID;
|
|
msg.wsPlayerName = wsPlayerName;
|
|
ChatPacketHandler::SendRouted(receiver.playerID, receiver.worldServerSysAddr, msg);
|
|
}
|
|
|
|
void TeamContainer::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
|
|
ClientPackets::TeamSetOffWorldFlag msg;
|
|
msg.target = receiver.playerID;
|
|
msg.i64PlayerID = i64PlayerID;
|
|
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
|
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
|
}
|
|
msg.zoneID = zoneID;
|
|
ChatPacketHandler::SendRouted(receiver.playerID, receiver.worldServerSysAddr, msg);
|
|
}
|
|
|
|
void TeamContainer::CreateTeamServer(const ChatPackets::CreateTeam& request, const SystemAddress& sysAddr) {
|
|
// Requests for more than 4 players (ChatPackets::CreateTeam::MAX_MEMBERS) were dropped when read
|
|
const std::vector<LWOOBJID>& members = request.members;
|
|
const LWOZONEID zoneId = request.zoneID;
|
|
|
|
auto* team = CreateLocalTeam(members);
|
|
|
|
if (team != nullptr) {
|
|
team->zoneId = zoneId;
|
|
UpdateTeamsOnWorld(team, false);
|
|
}
|
|
}
|
|
|
|
TeamData* TeamContainer::CreateLocalTeam(std::vector<LWOOBJID> 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 = 0;
|
|
|
|
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) {
|
|
ChatPackets::TeamUpdate update;
|
|
update.teamID = team->teamID;
|
|
update.deleteTeam = deleteTeam;
|
|
|
|
if (!deleteTeam) {
|
|
update.lootFlag = team->lootFlag;
|
|
update.members = team->memberIDs;
|
|
}
|
|
|
|
update.Broadcast();
|
|
}
|