mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
Before private (7), team (8) and local team (10) chat the client asks the chat server for the minimum chat mode of the channel (LWOChatComponent::RequestMinimumChatMode; it answers every other channel itself) and hands the answer to its chat UI. DLU never answered. Live answered chat mode 0 with the requested channel (74 of 74 team answers in the captures: 53 05 00 39 00 00 00 00 00 08). The chat server now answers with the lowest chat mode among who reads the channel (the sender and their online teammates, or the sender and the recipient), using the GM level as the chat mode as DLU does everywhere else; the private answer also echoes the recipient's name and GM level (0 when offline). Inferred: the minimum for GMs (no live GM sample) and the answer when the recipient is offline. Check: type in team chat and whisper another player: the messages send as before and the chat box shows no error; do the same with a GM in the team. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
619 lines
25 KiB
C++
619 lines
25 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"
|
|
#include "MinimumChatMode.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::HandleRequestMinimumChatMode(const ChatPackets::RequestMinimumChatMode& request, const SystemAddress& sysAddr) {
|
|
const auto& sender = Game::playerContainer.GetPlayerData(request.playerID);
|
|
if (!sender) return;
|
|
|
|
// Everyone on the team who is online reads team chat; without a team only the sender does
|
|
std::vector<eGameMasterLevel> readers{ sender.gmLevel };
|
|
if (const auto* const team = TeamContainer::GetTeam(sender.playerID)) {
|
|
for (const auto memberID : team->memberIDs) {
|
|
const auto& member = Game::playerContainer.GetPlayerData(memberID);
|
|
if (member && member.playerID != sender.playerID) readers.push_back(member.gmLevel);
|
|
}
|
|
}
|
|
|
|
ClientPackets::MinimumChatModeResponse response;
|
|
response.chatMode = MinimumChatMode::Of(readers);
|
|
response.chatChannel = request.chatChannel;
|
|
SendRouted(sender.playerID, sender.worldServerSysAddr, response);
|
|
}
|
|
|
|
void ChatPacketHandler::HandleRequestMinimumChatModePrivate(const ChatPackets::RequestMinimumChatModePrivate& request, const SystemAddress& sysAddr) {
|
|
const auto& sender = Game::playerContainer.GetPlayerData(request.playerID);
|
|
if (!sender) return;
|
|
|
|
const auto& recipient = Game::playerContainer.GetPlayerData(request.recipientName.GetAsString());
|
|
std::vector<eGameMasterLevel> readers{ sender.gmLevel };
|
|
if (recipient) readers.push_back(recipient.gmLevel);
|
|
|
|
ClientPackets::MinimumChatModeResponsePrivate response;
|
|
response.chatMode = MinimumChatMode::Of(readers);
|
|
response.chatChannel = request.chatChannel;
|
|
response.recipientName = request.recipientName;
|
|
response.recipientGMLevel = recipient ? static_cast<uint8_t>(recipient.gmLevel) : 0;
|
|
SendRouted(sender.playerID, sender.worldServerSysAddr, response);
|
|
}
|
|
|
|
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);
|
|
}
|