feat: broadcast achievements in chat as in live (#1771)

* feat: broadcast achievements in chat as in live

Tested that everyone on the receiving players' friends list receives the announcement as it went out in live.  Only works for achievements that have an entry in the MissionEmail table.  This may have been sent out to everyone in your zone as well however we don't really have a way to verify this aside from questioning why the client checks for the receiver being in the ignore list.  This is the only hint to me that this may have been broadcast to more than friends but again, no proof.

* Add initial response msg and sending

* Revert "Add initial response msg and sending"

This reverts commit fb942e4692747ff1debea2e0ad00d22dd0d632f3.
This commit is contained in:
David Markowitz 2025-04-19 05:36:53 -07:00 committed by GitHub
parent 5785764a95
commit 6f94043b33
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 107 additions and 25 deletions

View File

@ -73,7 +73,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
data.Serialize(bitStream);
}
SystemAddress sysAddr = player.sysAddr;
SystemAddress sysAddr = player.worldServerSysAddr;
SEND_PACKET;
}
@ -122,7 +122,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
requesteeFriendData.isOnline = false;
requesteeFriendData.zoneID = requestor.zoneID;
requestee.friends.push_back(requesteeFriendData);
requestee.sysAddr = UNASSIGNED_SYSTEM_ADDRESS;
requestee.worldServerSysAddr = UNASSIGNED_SYSTEM_ADDRESS;
break;
}
}
@ -189,8 +189,8 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee.playerID, bestFriendStatus);
// Sent the best friend update here if the value is 3
if (bestFriendStatus == 3U) {
if (requestee.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
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) {
@ -211,7 +211,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
}
}
} else {
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::WAITINGAPPROVAL, true, true);
if (requestor.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::WAITINGAPPROVAL, true, true);
}
} else {
auto maxFriends = Game::playerContainer.GetMaxNumberOfFriends();
@ -384,7 +384,7 @@ void ChatPacketHandler::HandleWho(Packet* packet) {
bitStream.Write(player.zoneID.GetCloneID());
bitStream.Write(request.playerName);
SystemAddress sysAddr = sender.sysAddr;
SystemAddress sysAddr = sender.worldServerSysAddr;
SEND_PACKET;
}
@ -418,7 +418,7 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
}
}
}
SystemAddress sysAddr = sender.sysAddr;
SystemAddress sysAddr = sender.worldServerSysAddr;
SEND_PACKET;
}
@ -519,6 +519,28 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::GENERAL, eChatMessageResponseCode::NOTFRIENDS);
}
void ChatPacketHandler::OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr) {
ChatPackets::AchievementNotify notify{};
notify.Deserialize(bitstream);
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());
RakNet::BitStream worldStream;
BitStreamUtils::WriteHeader(worldStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
worldStream.Write(friendData.playerID);
notify.WriteHeader(worldStream);
notify.Serialize(worldStream);
Game::server->Send(worldStream, friendData.worldServerSysAddr, false);
}
}
}
void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
@ -537,7 +559,7 @@ void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const P
bitStream.Write(responseCode);
bitStream.Write(message);
SystemAddress sysAddr = routeTo.sysAddr;
SystemAddress sysAddr = routeTo.worldServerSysAddr;
SEND_PACKET;
}
@ -772,7 +794,7 @@ void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerD
bitStream.Write(LUWString(sender.playerName.c_str()));
bitStream.Write(sender.playerID);
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@ -799,7 +821,7 @@ void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool b
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@ -824,7 +846,7 @@ void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64L
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@ -841,7 +863,7 @@ void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i
bitStream.Write(i64PlayerID);
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@ -870,7 +892,7 @@ void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFr
}
bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@ -896,7 +918,7 @@ void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bD
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@ -917,7 +939,7 @@ void ChatPacketHandler::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOO
}
bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@ -959,7 +981,7 @@ void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const Pla
bitStream.Write<uint8_t>(isBestFriend); //isBFF
bitStream.Write<uint8_t>(0); //isFTP
SystemAddress sysAddr = friendData.sysAddr;
SystemAddress sysAddr = friendData.worldServerSysAddr;
SEND_PACKET;
}
@ -981,7 +1003,7 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play
bitStream.Write(LUWString(sender.playerName));
bitStream.Write<uint8_t>(0); // This is a BFF flag however this is unused in live and does not have an implementation client side.
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@ -994,7 +1016,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::ADD_FRIEND_RESPONSE);
bitStream.Write(responseCode);
// For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver.
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.sysAddr != UNASSIGNED_SYSTEM_ADDRESS);
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS);
// Then write the player name
bitStream.Write(LUWString(sender.playerName));
// Then if this is an acceptance code, write the following extra info.
@ -1004,7 +1026,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
bitStream.Write(isBestFriendRequest); //isBFF
bitStream.Write<uint8_t>(0); //isFTP
}
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@ -1018,6 +1040,6 @@ void ChatPacketHandler::SendRemoveFriend(const PlayerData& receiver, std::string
bitStream.Write<uint8_t>(isSuccessful); //isOnline
bitStream.Write(LUWString(personToRemove));
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}

View File

@ -64,6 +64,7 @@ namespace ChatPacketHandler {
void HandleTeamPromote(Packet* packet);
void HandleTeamLootOption(Packet* packet);
void HandleTeamStatusRequest(Packet* packet);
void OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr);
void SendTeamInvite(const PlayerData& receiver, const PlayerData& sender);
void SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName);

View File

@ -224,6 +224,10 @@ void HandlePacket(Packet* packet) {
if (connection != eConnectionType::CHAT) return;
inStream.Read(chatMessageID);
// Our packing byte wasnt there? Probably a false packet
if (inStream.GetNumberOfUnreadBits() < 8) return;
inStream.IgnoreBytes(1);
switch (chatMessageID) {
case MessageType::Chat::GM_MUTE:
Game::playerContainer.MuteUpdate(packet);
@ -322,6 +326,9 @@ void HandlePacket(Packet* packet) {
case MessageType::Chat::SHOW_ALL:
ChatPacketHandler::HandleShowAll(packet);
break;
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
ChatPacketHandler::OnAchievementNotify(inStream, packet->systemAddress);
break;
case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE:
case MessageType::Chat::WORLD_DISCONNECT_REQUEST:
case MessageType::Chat::WORLD_PROXIMITY_RESPONSE:
@ -357,7 +364,6 @@ void HandlePacket(Packet* packet) {
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT:
case MessageType::Chat::UGCC_REQUEST:
case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE:
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW:
case MessageType::Chat::PLAYER_READY:
case MessageType::Chat::GET_DONATION_TOTAL:

View File

@ -52,7 +52,7 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
if (!inStream.Read(data.zoneID)) return;
if (!inStream.Read(data.muteExpire)) return;
if (!inStream.Read(data.gmLevel)) return;
data.sysAddr = packet->systemAddress;
data.worldServerSysAddr = packet->systemAddress;
m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName);
m_PlayerCount++;
@ -241,7 +241,7 @@ void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID) {
LOG("Tried to add player to team that already had 4 players");
const auto& player = GetPlayerData(playerID);
if (!player) return;
ChatPackets::SendSystemMessage(player.sysAddr, u"The teams is full! You have not been added to a team!");
ChatPackets::SendSystemMessage(player.worldServerSysAddr, u"The teams is full! You have not been added to a team!");
return;
}

View File

@ -42,7 +42,7 @@ struct PlayerData {
return muteExpire == 1 || muteExpire > time(NULL);
}
SystemAddress sysAddr{};
SystemAddress worldServerSysAddr{};
LWOZONEID zoneID{};
LWOOBJID playerID = LWOOBJID_EMPTY;
time_t muteExpire = 0;

View File

@ -98,6 +98,7 @@ public:
constexpr LWOZONEID() noexcept = default;
constexpr LWOZONEID(const LWOMAPID& mapID, const LWOINSTANCEID& instanceID, const LWOCLONEID& cloneID) noexcept { m_MapID = mapID; m_InstanceID = instanceID; m_CloneID = cloneID; }
constexpr LWOZONEID(const LWOZONEID& replacement) noexcept { *this = replacement; }
constexpr bool operator==(const LWOZONEID&) const = default;
private:
LWOMAPID m_MapID = LWOMAPID_INVALID; //1000 for VE, 1100 for AG, etc...

View File

@ -27,6 +27,8 @@
#include "Character.h"
#include "CDMissionEmailTable.h"
#include "ChatPackets.h"
#include "PlayerManager.h"
Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) {
m_MissionComponent = missionComponent;
@ -355,12 +357,25 @@ void Mission::Complete(const bool yieldRewards) {
for (const auto& email : missionEmails) {
const auto missionEmailBase = "MissionEmail_" + std::to_string(email.ID) + "_";
if (email.messageType == 1) {
if (email.messageType == 1 /* Send an email to the player */) {
const auto subject = "%[" + missionEmailBase + "subjectText]";
const auto body = "%[" + missionEmailBase + "bodyText]";
const auto sender = "%[" + missionEmailBase + "senderName]";
Mail::SendMail(LWOOBJID_EMPTY, sender, GetAssociate(), subject, body, email.attachmentLOT, 1);
} else if (email.messageType == 2 /* Send an announcement in chat */) {
auto* character = entity->GetCharacter();
ChatPackets::AchievementNotify notify{};
notify.missionEmailID = email.ID;
notify.earningPlayerID = entity->GetObjectID();
notify.earnerName.string = character ? GeneralUtils::ASCIIToUTF16(character->GetName()) : u"";
// Manual write since it's sent to chat server and not a game client
RakNet::BitStream bitstream;
notify.WriteHeader(bitstream);
notify.Serialize(bitstream);
Game::chatServer->Send(&bitstream, HIGH_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
}
}
}

View File

@ -107,3 +107,28 @@ void ChatPackets::Announcement::Send() {
bitStream.Write(message);
SEND_PACKET_BROADCAST;
}
void ChatPackets::AchievementNotify::Serialize(RakNet::BitStream& bitstream) const {
bitstream.Write<uint64_t>(0); // Packing
bitstream.Write<uint32_t>(0); // Packing
bitstream.Write<uint8_t>(0); // Packing
bitstream.Write(targetPlayerName);
bitstream.Write<uint64_t>(0); // Packing / No way to know meaning because of not enough data.
bitstream.Write<uint32_t>(0); // Packing / No way to know meaning because of not enough data.
bitstream.Write<uint16_t>(0); // Packing / No way to know meaning because of not enough data.
bitstream.Write<uint8_t>(0); // Packing / No way to know meaning because of not enough data.
bitstream.Write(missionEmailID);
bitstream.Write(earningPlayerID);
bitstream.Write(earnerName);
}
bool ChatPackets::AchievementNotify::Deserialize(RakNet::BitStream& bitstream) {
bitstream.IgnoreBytes(13);
VALIDATE_READ(bitstream.Read(targetPlayerName));
bitstream.IgnoreBytes(15);
VALIDATE_READ(bitstream.Read(missionEmailID));
VALIDATE_READ(bitstream.Read(earningPlayerID));
VALIDATE_READ(bitstream.Read(earnerName));
return true;
}

View File

@ -10,6 +10,8 @@ struct SystemAddress;
#include <string>
#include "dCommonVars.h"
#include "MessageType/Chat.h"
#include "BitStreamUtils.h"
struct ShowAllRequest{
LWOOBJID requestor = LWOOBJID_EMPTY;
@ -34,6 +36,16 @@ namespace ChatPackets {
void Send();
};
struct AchievementNotify : public LUBitStream {
LUWString targetPlayerName{};
uint32_t missionEmailID{};
LWOOBJID earningPlayerID{};
LUWString earnerName{};
AchievementNotify() : LUBitStream(eConnectionType::CHAT, MessageType::Chat::ACHIEVEMENT_NOTIFY) {}
void Serialize(RakNet::BitStream& bitstream) const override;
bool Deserialize(RakNet::BitStream& bitstream) override;
};
void SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message);
void SendSystemMessage(const SystemAddress& sysAddr, const std::u16string& message, bool broadcast = false);
void SendMessageFail(const SystemAddress& sysAddr);