Files
DarkflameServer/dChatServer/ChatPacketHandler.cpp
Aaron Kimbrell 109a936798 feat: live updates move every server onto a new build without a restart
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>
2026-09-28 22:31:23 -05:00

583 lines
24 KiB
C++

#include "ChatPacketHandler.h"
#include "PlayerContainer.h"
#include "Database.h"
#include <ctime>
#include <vector>
#include "BitStreamUtils.h"
#include "Game.h"
#include "dServer.h"
#include "GeneralUtils.h"
#include "Logger.h"
#include "eAddFriendResponseCode.h"
#include "eAddFriendResponseType.h"
#include "RakString.h"
#include "dConfig.h"
#include "eObjectBits.h"
#include "ServiceType.h"
#include "MessageType/Chat.h"
#include "MessageType/Client.h"
#include "MessageType/Game.h"
#include "StringifiedEnum.h"
#include "eGameMasterLevel.h"
#include "ChatPackets.h"
#include "TeamContainer.h"
void ChatPacketHandler::SendRouted(const LWOOBJID target, const SystemAddress& world, const LUBitStream& msg, const bool broadcast) {
ChatPackets::WorldRoutePacket route;
route.targetID = target;
route.routed = &msg;
RakNet::BitStream bitStream;
route.WritePacket(bitStream);
Game::server->Send(bitStream, world, broadcast);
}
void ChatPacketHandler::LoadFriends(PlayerData& player) {
player.friends.clear();
player.countOfBestFriends = 0;
for (const auto& friendData : Database::Get()->GetFriendsList(player.playerID)) {
FriendData fd;
fd.isFTP = false;
fd.friendID = friendData.friendID;
GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER);
fd.isBestFriend = friendData.isBestFriend;
if (fd.isBestFriend) player.countOfBestFriends += 1;
fd.friendName = friendData.friendName;
const auto& online = Game::playerContainer.GetPlayerData(fd.friendID);
fd.isOnline = static_cast<bool>(online);
fd.zoneID = online ? online.zoneID : LWOZONEID();
player.friends.push_back(fd);
}
}
void ChatPacketHandler::HandleFriendlistRequest(const ChatPackets::GetFriendsList& request, const SystemAddress& sysAddr) {
//Get from the packet which player we want to do something with:
const LWOOBJID playerID = request.playerID;
auto& player = Game::playerContainer.GetPlayerDataMutable(playerID);
if (!player) return;
auto friendsList = Database::Get()->GetFriendsList(playerID);
for (const auto& friendData : friendsList) {
FriendData fd;
fd.isFTP = false; // not a thing in DLU
fd.friendID = friendData.friendID;
GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER);
fd.isBestFriend = friendData.isBestFriend; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs
if (fd.isBestFriend) player.countOfBestFriends += 1;
fd.friendName = friendData.friendName;
//Now check if they're online:
const auto& fr = Game::playerContainer.GetPlayerData(fd.friendID);
if (fr) {
fd.isOnline = true;
fd.zoneID = fr.zoneID;
//Since this friend is online, we need to update them on the fact that we've just logged in:
if (player.isLogin) SendFriendUpdate(fr, player, 1, fd.isBestFriend);
} else {
fd.isOnline = false;
fd.zoneID = LWOZONEID();
}
player.friends.push_back(fd);
}
//Now, we need to send the friendlist to the server they came from:
ClientPackets::GetFriendsListResponse response;
response.friends = player.friends;
SendRouted(playerID, player.worldServerSysAddr, response);
}
void ChatPacketHandler::HandleFriendRequest(const ChatPackets::AddFriendRequest& request, const SystemAddress& sysAddr) {
const LWOOBJID requestorPlayerID = request.playerID;
const char isBestFriendRequest = request.isBestFriendRequest;
auto playerName = request.friendName.GetAsString();
auto& requestor = Game::playerContainer.GetPlayerDataMutable(requestorPlayerID);
if (!requestor) {
LOG("No requestor player %llu sent to %s found.", requestorPlayerID, playerName.c_str());
return;
}
// you cannot friend yourself
if (requestor.playerName == playerName) {
SendFriendResponse(requestor, requestor, eAddFriendResponseType::GENERALERROR);
return;
};
// Intentional copy
PlayerData requestee = Game::playerContainer.GetPlayerData(playerName);
// Check if player is online first
if (isBestFriendRequest && !requestee) {
for (auto& friendDataCandidate : requestor.friends) {
if (friendDataCandidate.friendName != playerName) continue;
// Setup the needed info since you can add a best friend offline.
requestee.playerID = friendDataCandidate.friendID;
requestee.playerName = friendDataCandidate.friendName;
requestee.zoneID = LWOZONEID();
FriendData requesteeFriendData{};
requesteeFriendData.friendID = requestor.playerID;
requesteeFriendData.friendName = requestor.playerName;
requesteeFriendData.isFTP = false;
requesteeFriendData.isOnline = false;
requesteeFriendData.zoneID = requestor.zoneID;
requestee.friends.push_back(requesteeFriendData);
requestee.worldServerSysAddr = UNASSIGNED_SYSTEM_ADDRESS;
break;
}
}
// If at this point we dont have a target, then they arent online and we cant send the request.
// Send the response code that corresponds to what the error is.
if (!requestee) {
requestee.playerName = playerName;
auto responseType = Database::Get()->GetCharacterInfo(playerName)
? eAddFriendResponseType::NOTONLINE
: eAddFriendResponseType::INVALIDCHARACTER;
SendFriendResponse(requestor, requestee, responseType);
return;
}
// Prevent GM friend spam
// If the player we are trying to be friends with is not a civilian and we are a civilian, abort the process
if (requestee.gmLevel > eGameMasterLevel::CIVILIAN && requestor.gmLevel == eGameMasterLevel::CIVILIAN) {
SendFriendResponse(requestor, requestee, eAddFriendResponseType::MYTHRAN);
return;
}
if (isBestFriendRequest) {
uint8_t oldBestFriendStatus{};
uint8_t bestFriendStatus{};
auto bestFriendInfo = Database::Get()->GetBestFriendStatus(requestorPlayerID, requestee.playerID);
if (bestFriendInfo) {
// Get the IDs
LWOOBJID queryPlayerID = bestFriendInfo->playerCharacterId;
LWOOBJID queryFriendID = bestFriendInfo->friendCharacterId;
oldBestFriendStatus = bestFriendInfo->bestFriendStatus;
bestFriendStatus = oldBestFriendStatus;
// Set the bits
GeneralUtils::SetBit(queryPlayerID, eObjectBits::CHARACTER);
GeneralUtils::SetBit(queryFriendID, eObjectBits::CHARACTER);
// Since this player can either be the friend of someone else or be friends with someone else
// their column in the database determines what bit gets set. When the value hits 3, they
// are now best friends with the other player.
if (queryPlayerID == requestorPlayerID) {
bestFriendStatus |= 1ULL << 0;
} else {
bestFriendStatus |= 1ULL << 1;
}
}
// Only do updates if there was a change in the bff status.
if (oldBestFriendStatus != bestFriendStatus) {
auto maxBestFriends = Game::playerContainer.GetMaxNumberOfBestFriends();
if (requestee.countOfBestFriends >= maxBestFriends || requestor.countOfBestFriends >= maxBestFriends) {
if (requestee.countOfBestFriends >= maxBestFriends) {
SendFriendResponse(requestor, requestee, eAddFriendResponseType::THEIRFRIENDLISTFULL, false);
}
if (requestor.countOfBestFriends >= maxBestFriends) {
SendFriendResponse(requestor, requestee, eAddFriendResponseType::YOURFRIENDSLISTFULL, false);
}
} else {
// Then update the database with this new info.
Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee.playerID, bestFriendStatus);
// Sent the best friend update here if the value is 3
if (bestFriendStatus == 3U) {
if (requestee.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
if (requestor.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
for (auto& friendData : requestor.friends) {
if (friendData.friendID == requestee.playerID) {
friendData.isBestFriend = true;
}
}
requestor.countOfBestFriends += 1;
auto& toModify = Game::playerContainer.GetPlayerDataMutable(playerName);
if (toModify) {
for (auto& friendData : toModify.friends) {
if (friendData.friendID == requestor.playerID) {
friendData.isBestFriend = true;
}
}
toModify.countOfBestFriends += 1;
}
}
}
} else {
if (requestor.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::WAITINGAPPROVAL, true, true);
}
} else {
auto maxFriends = Game::playerContainer.GetMaxNumberOfFriends();
if (requestee.friends.size() >= maxFriends) {
SendFriendResponse(requestor, requestee, eAddFriendResponseType::THEIRFRIENDLISTFULL, false);
} else if (requestor.friends.size() >= maxFriends) {
SendFriendResponse(requestor, requestee, eAddFriendResponseType::YOURFRIENDSLISTFULL, false);
} else {
// Do not send this if we are requesting to be a best friend.
SendFriendRequest(requestee, requestor);
}
}
}
void ChatPacketHandler::HandleFriendResponse(const ChatPackets::AddFriendResponse& response, const SystemAddress& sysAddr) {
const LWOOBJID playerID = response.playerID;
const eAddFriendResponseCode clientResponseCode = response.responseCode;
const LUWString& friendName = response.friendName;
//Now to try and find both of these:
auto& requestor = Game::playerContainer.GetPlayerDataMutable(playerID);
auto& requestee = Game::playerContainer.GetPlayerDataMutable(friendName.GetAsString());
if (!requestor || !requestee) return;
eAddFriendResponseType serverResponseCode{};
uint8_t isAlreadyBestFriends = 0U;
// We need to convert this response code to one we can actually send back to the client.
switch (clientResponseCode) {
case eAddFriendResponseCode::ACCEPTED:
serverResponseCode = eAddFriendResponseType::ACCEPTED;
break;
case eAddFriendResponseCode::BUSY:
serverResponseCode = eAddFriendResponseType::BUSY;
break;
case eAddFriendResponseCode::CANCELLED:
serverResponseCode = eAddFriendResponseType::CANCELLED;
break;
case eAddFriendResponseCode::REJECTED:
serverResponseCode = eAddFriendResponseType::DECLINED;
break;
}
// Now that we have handled the base cases, we need to check the other cases.
if (serverResponseCode == eAddFriendResponseType::ACCEPTED) {
for (const auto& friendData : requestor.friends) {
if (friendData.friendID == requestee.playerID) {
serverResponseCode = eAddFriendResponseType::ALREADYFRIEND;
if (friendData.isBestFriend) {
isAlreadyBestFriends = 1U;
}
}
}
}
// This message is NOT sent for best friends and is handled differently for those requests.
if (serverResponseCode == eAddFriendResponseType::ACCEPTED) {
// Add the each player to the others friend list.
auto& requestorData = requestee.friends.emplace_back();
requestorData.zoneID = requestor.zoneID;
requestorData.friendID = requestor.playerID;
requestorData.friendName = requestor.playerName;
requestorData.isBestFriend = false;
requestorData.isFTP = false;
requestorData.isOnline = true;
auto& requesteeData = requestor.friends.emplace_back();
requesteeData.zoneID = requestee.zoneID;
requesteeData.friendID = requestee.playerID;
requesteeData.friendName = requestee.playerName;
requesteeData.isBestFriend = false;
requesteeData.isFTP = false;
requesteeData.isOnline = true;
Database::Get()->AddFriend(requestor.playerID, requestee.playerID);
}
if (serverResponseCode != eAddFriendResponseType::DECLINED) SendFriendResponse(requestor, requestee, serverResponseCode, isAlreadyBestFriends);
if (serverResponseCode != eAddFriendResponseType::ALREADYFRIEND) SendFriendResponse(requestee, requestor, serverResponseCode, isAlreadyBestFriends);
}
void ChatPacketHandler::HandleRemoveFriend(const ChatPackets::RemoveFriend& request, const SystemAddress& sysAddr) {
const LWOOBJID playerID = request.playerID;
auto friendName = request.friendName.GetAsString();
//we'll have to query the db here to find the user, since you can delete them while they're offline.
//First, we need to find their ID:
LWOOBJID friendID = 0;
auto friendIdResult = Database::Get()->GetCharacterInfo(friendName);
if (friendIdResult) {
friendID = friendIdResult->id;
}
// Convert friendID to LWOOBJID
GeneralUtils::SetBit(friendID, eObjectBits::CHARACTER);
Database::Get()->RemoveFriend(playerID, friendID);
//Now, we need to send an update to notify the sender (and possibly, receiver) that their friendship has been ended:
auto& goonA = Game::playerContainer.GetPlayerDataMutable(playerID);
if (goonA) {
// Remove the friend from our list of friends
for (auto friendData = goonA.friends.cbegin(); friendData != goonA.friends.cend(); friendData++) {
if (friendData->friendID == friendID) {
if (friendData->isBestFriend) --goonA.countOfBestFriends;
goonA.friends.erase(friendData);
break;
}
}
SendRemoveFriend(goonA, friendName, true);
}
auto& goonB = Game::playerContainer.GetPlayerDataMutable(friendID);
if (!goonB) return;
// Do it again for other person
for (auto friendData = goonB.friends.cbegin(); friendData != goonB.friends.cend(); friendData++) {
if (friendData->friendID == playerID) {
if (friendData->isBestFriend) --goonB.countOfBestFriends;
goonB.friends.erase(friendData);
break;
}
}
std::string goonAName = GeneralUtils::UTF16ToWTF8(Game::playerContainer.GetName(playerID));
SendRemoveFriend(goonB, goonAName, true);
}
void ChatPacketHandler::HandleGMLevelUpdate(const ChatPackets::GMLevelUpdate& update, const SystemAddress& sysAddr) {
auto& player = Game::playerContainer.GetPlayerDataMutable(update.playerID);
if (!player) return;
player.gmLevel = update.gmLevel;
}
void ChatPacketHandler::HandleWho(const ChatPackets::FindPlayerRequest& request, const SystemAddress& sysAddr) {
const auto& sender = Game::playerContainer.GetPlayerData(request.requestor);
if (!sender) return;
const auto& player = Game::playerContainer.GetPlayerData(request.playerName.GetAsString());
bool online = player;
ClientPackets::WhoResponse response;
response.isOnline = online;
response.zoneID = player.zoneID;
response.playerName = request.playerName;
SendRouted(request.requestor, sender.worldServerSysAddr, response);
}
void ChatPacketHandler::HandleShowAll(const ChatPackets::ShowAllRequest& request, const SystemAddress& sysAddr) {
const auto& sender = Game::playerContainer.GetPlayerData(request.requestor);
if (!sender) return;
ClientPackets::ShowAllResponse response;
response.playerCount = Game::playerContainer.GetPlayerCount();
response.simCount = Game::playerContainer.GetSimCount();
response.displayIndividualPlayers = request.displayIndividualPlayers;
response.displayZoneData = request.displayZoneData;
if (request.displayZoneData || request.displayIndividualPlayers) {
for (auto& [playerID, playerData] : Game::playerContainer.GetAllPlayers()) {
if (!playerData) continue;
response.players.push_back({ playerData.playerName, playerData.zoneID });
}
}
SendRouted(request.requestor, sender.worldServerSysAddr, response);
}
// the structure the client uses to send this packet is shared in many chat messages
// that are sent to the server. Because of this, there are large gaps of unused data in chat messages
namespace {
// Whispers and team chat, for moderation (log_chat=0 turns chat logging off, log_private_chat=0 just these)
void LogChat(const PlayerData& sender, const std::string& channel, const std::string& message, LWOOBJID recipientId = LWOOBJID_EMPTY, const std::string& recipientName = "") {
if (Game::config->GetValue("log_chat") == "0" || Game::config->GetValue("log_private_chat") == "0") return;
IChatLog::ChatMessage entry;
entry.time = static_cast<int64_t>(std::time(nullptr));
entry.channel = channel;
entry.senderId = sender.playerID;
entry.senderName = sender.playerName;
entry.recipientId = recipientId == LWOOBJID_EMPTY ? 0 : recipientId;
entry.recipientName = recipientName;
entry.zoneId = sender.zoneID.GetMapID();
entry.instanceId = sender.zoneID.GetInstanceID();
entry.cloneId = sender.zoneID.GetCloneID();
entry.message = message;
try {
if (const auto info = Database::Get()->GetCharacterInfo(sender.playerID)) entry.accountId = info->accountId;
Database::Get()->InsertChatMessage(entry);
} catch (const std::exception& ex) {
LOG("Couldn't log a chat message: %s", ex.what());
}
}
}
void ChatPacketHandler::HandleChatMessage(const ChatPackets::GeneralChatMessage& chatMessage, const SystemAddress& sysAddr) {
const LWOOBJID playerID = chatMessage.playerID;
const auto& sender = Game::playerContainer.GetPlayerData(playerID);
if (!sender || sender.GetIsMuted()) return;
// Messages longer than MAX_MESSAGE_LENGTH were dropped when read
const eChatChannel channel = chatMessage.chatChannel;
const LUWString& message = chatMessage.message;
LOG("Got a message from (%s) via [%s]: %s", sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str());
switch (channel) {
case eChatChannel::TEAM: {
auto* team = TeamContainer::GetTeam(playerID);
if (team == nullptr) return;
LogChat(sender, "team", message.GetAsString());
for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (!otherMember) return;
SendPrivateChatMessage(sender, otherMember, otherMember, message, eChatChannel::TEAM, eChatMessageResponseCode::SENT);
}
break;
}
default:
LOG("Unhandled Chat channel [%s]", StringifiedEnum::ToString(channel).data());
break;
}
}
// the structure the client uses to send this packet is shared in many chat messages
// that are sent to the server. Because of this, there are large gaps of unused data in chat messages
void ChatPacketHandler::HandlePrivateChatMessage(const ChatPackets::PrivateChatMessage& chatMessage, const SystemAddress& sysAddr) {
const LWOOBJID playerID = chatMessage.playerID;
const auto& sender = Game::playerContainer.GetPlayerData(playerID);
if (!sender || sender.GetIsMuted()) return;
const eChatChannel channel = chatMessage.chatChannel;
if (channel != eChatChannel::PRIVATE_CHAT) LOG("WARNING: Received Private chat with the wrong channel!");
// Messages longer than MAX_MESSAGE_LENGTH were dropped when read
auto receiverName = chatMessage.receiverName.GetAsString();
const LUWString& message = chatMessage.message;
LOG("Got a message from (%s) via [%s]: %s to %s", sender.playerName.c_str(), StringifiedEnum::ToString(channel).data(), message.GetAsString().c_str(), receiverName.c_str());
const auto& receiver = Game::playerContainer.GetPlayerData(receiverName);
if (!receiver) {
PlayerData otherPlayer;
otherPlayer.playerName = receiverName;
auto responseType = Database::Get()->GetCharacterInfo(receiverName)
? eChatMessageResponseCode::NOTONLINE
: eChatMessageResponseCode::GENERALERROR;
SendPrivateChatMessage(sender, otherPlayer, sender, message, eChatChannel::GENERAL, responseType);
return;
}
// Check to see if they are friends
// only freinds can whispr each other
for (const auto& fr : receiver.friends) {
if (fr.friendID == sender.playerID) {
LogChat(sender, "whisper", message.GetAsString(), receiver.playerID, receiver.playerName);
//To the sender:
SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::PRIVATE_CHAT, eChatMessageResponseCode::SENT);
//To the receiver:
SendPrivateChatMessage(sender, receiver, receiver, message, eChatChannel::PRIVATE_CHAT, eChatMessageResponseCode::RECEIVEDNEWWHISPER);
return;
}
}
SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::GENERAL, eChatMessageResponseCode::NOTFRIENDS);
}
void ChatPacketHandler::OnAchievementNotify(ChatPackets::AchievementNotify& notify, const SystemAddress& sysAddr) {
const auto& playerData = Game::playerContainer.GetPlayerData(notify.earnerName.GetAsString());
if (!playerData) return;
for (const auto& myFriend : playerData.friends) {
auto& friendData = Game::playerContainer.GetPlayerData(myFriend.friendID);
if (friendData) {
notify.targetPlayerName.string = GeneralUtils::ASCIIToUTF16(friendData.playerName);
LOG_DEBUG("Sending achievement notify to %s", notify.targetPlayerName.GetAsString().c_str());
SendRouted(friendData.playerID, friendData.worldServerSysAddr, notify);
}
}
}
void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) {
ChatPackets::Client::PrivateChatMessage chatMessage;
chatMessage.playerID = sender.playerID;
chatMessage.chatChannel = channel;
chatMessage.senderName = LUWString(sender.playerName);
chatMessage.senderID = sender.playerID;
chatMessage.senderGMLevel = sender.gmLevel;
chatMessage.receiverName = LUWString(receiver.playerName);
chatMessage.receiverGMLevel = receiver.gmLevel;
chatMessage.responseCode = responseCode;
chatMessage.message = message;
SendRouted(routeTo.playerID, routeTo.worldServerSysAddr, chatMessage);
}
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
Update types
0 - friend logged out
1 - friend logged in
2 - friend changed world / updated
[wstring] - Name of friend
[u16] - World ID
[u16] - World Instance
[u32] - World Clone
[bool] - is best friend
[bool] - is FTP*/
ClientPackets::UpdateFriendNotify notify;
notify.notifyType = notifyType;
std::string playerName = playerData.playerName.c_str();
notify.friendName = LUWString(playerName);
const auto& zoneID = playerData.zoneID;
const LWOCLONEID cloneID = zoneID.GetCloneID() == friendData.zoneID.GetCloneID() ? 0 : zoneID.GetCloneID();
notify.zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), cloneID);
notify.isBestFriend = isBestFriend; //isBFF
notify.isFreeTrial = 0; //isFTP
SendRouted(friendData.playerID, friendData.worldServerSysAddr, notify);
}
void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const PlayerData& sender) {
//Make sure people aren't requesting people that they're already friends with:
for (const auto& fr : receiver.friends) {
if (fr.friendID == sender.playerID) {
SendFriendResponse(sender, receiver, eAddFriendResponseType::ALREADYFRIEND, fr.isBestFriend);
return; //we have this player as a friend, yeet this function so it doesn't send another request.
}
}
ClientPackets::AddFriendRequest request;
request.requestorName = LUWString(sender.playerName);
request.isBestFriendRequest = 0; // This is a BFF flag however this is unused in live and does not have an implementation client side.
SendRouted(receiver.playerID, receiver.worldServerSysAddr, request);
}
void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready, uint8_t isBestFriendRequest) {
ClientPackets::AddFriendResponse response;
response.responseCode = responseCode;
// For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver.
response.isOnlineOrBestFriend = responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS;
// Then write the player name
response.friendName = LUWString(sender.playerName);
// Then if this is an acceptance code, write the following extra info.
response.friendID = sender.playerID;
response.zoneID = sender.zoneID;
response.isBestFriend = isBestFriendRequest; //isBFF
response.isFreeTrial = 0; //isFTP
SendRouted(receiver.playerID, receiver.worldServerSysAddr, response);
}
void ChatPacketHandler::SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful) {
ClientPackets::RemoveFriendResponse response;
response.isSuccessful = isSuccessful; //isOnline
response.friendName = LUWString(personToRemove);
SendRouted(receiver.playerID, receiver.worldServerSysAddr, response);
}