DarkflameServer/dChatServer/TeamContainer.cpp
David Markowitz 0e551429d3
chore: move all teams logic to its own namespace to consolidate logic (#1777)
* Add invite initial response msg

re-do team leave logic to send more accurate messages

Players are still able to leave the team with the same results as before, however now the correct messages are sent to team chats (no fixes for local teams).

* chore: move team logic to separate container

Makes it easier to follow team logic when you're not bouncing between 3 classes in 3 files.  Consolidates all team logic to 1 namespace in TeamContainer.  No logic changes were done, only renaming and fixing errors from the moving. TeamData should be replaced with unique_ptrs at some point so the Shutdown method can be removed from TeamContainer.
2025-05-05 09:04:43 -05:00

670 lines
18 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;
}
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(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<uint32_t>(0); // BinaryBuffe, no clue what's in here
bitStream.Write(ucLootFlag);
bitStream.Write(ucNumOfOtherPlayers);
bitStream.Write(ucResponseCode);
bitStream.Write<uint32_t>(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<uint32_t>(0); // BinaryBuffe, no clue what's in here
bitStream.Write(ucLootFlag);
bitStream.Write(ucNumOfOtherPlayers);
bitStream.Write<uint32_t>(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<uint32_t>(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<uint32_t>(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<LWOOBJID> 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<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 = 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<char>(team->memberIDs.size());
for (const auto memberID : team->memberIDs) {
bitStream.Write(memberID);
}
}
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
}