From 6f94043b3342c0e19badeafc1a21969f3b355b0f Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 19 Apr 2025 05:36:53 -0700 Subject: [PATCH] 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. --- dChatServer/ChatPacketHandler.cpp | 62 +++++++++++++++++++++---------- dChatServer/ChatPacketHandler.h | 1 + dChatServer/ChatServer.cpp | 8 +++- dChatServer/PlayerContainer.cpp | 4 +- dChatServer/PlayerContainer.h | 2 +- dCommon/dEnums/dCommonVars.h | 1 + dGame/dMission/Mission.cpp | 17 ++++++++- dNet/ChatPackets.cpp | 25 +++++++++++++ dNet/ChatPackets.h | 12 ++++++ 9 files changed, 107 insertions(+), 25 deletions(-) diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index d01d65fd..de0b395f 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -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(isBestFriend); //isBFF bitStream.Write(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(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(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.sysAddr != UNASSIGNED_SYSTEM_ADDRESS); + bitStream.Write(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(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(isSuccessful); //isOnline bitStream.Write(LUWString(personToRemove)); - SystemAddress sysAddr = receiver.sysAddr; + SystemAddress sysAddr = receiver.worldServerSysAddr; SEND_PACKET; } diff --git a/dChatServer/ChatPacketHandler.h b/dChatServer/ChatPacketHandler.h index def9c9b9..519dfeb6 100644 --- a/dChatServer/ChatPacketHandler.h +++ b/dChatServer/ChatPacketHandler.h @@ -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); diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index 2aceb5a7..d6728a9a 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -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: diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index 5aa3bc14..5392fe54 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -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; } diff --git a/dChatServer/PlayerContainer.h b/dChatServer/PlayerContainer.h index 167a73b1..4e9f3933 100644 --- a/dChatServer/PlayerContainer.h +++ b/dChatServer/PlayerContainer.h @@ -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; diff --git a/dCommon/dEnums/dCommonVars.h b/dCommon/dEnums/dCommonVars.h index 70eb1dc9..085fed2e 100644 --- a/dCommon/dEnums/dCommonVars.h +++ b/dCommon/dEnums/dCommonVars.h @@ -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... diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index f1f97eab..559193b7 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -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); } } } diff --git a/dNet/ChatPackets.cpp b/dNet/ChatPackets.cpp index 8f4015e9..19375426 100644 --- a/dNet/ChatPackets.cpp +++ b/dNet/ChatPackets.cpp @@ -107,3 +107,28 @@ void ChatPackets::Announcement::Send() { bitStream.Write(message); SEND_PACKET_BROADCAST; } + +void ChatPackets::AchievementNotify::Serialize(RakNet::BitStream& bitstream) const { + bitstream.Write(0); // Packing + bitstream.Write(0); // Packing + bitstream.Write(0); // Packing + bitstream.Write(targetPlayerName); + bitstream.Write(0); // Packing / No way to know meaning because of not enough data. + bitstream.Write(0); // Packing / No way to know meaning because of not enough data. + bitstream.Write(0); // Packing / No way to know meaning because of not enough data. + bitstream.Write(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; +} diff --git a/dNet/ChatPackets.h b/dNet/ChatPackets.h index 0f70c8e2..b435ecab 100644 --- a/dNet/ChatPackets.h +++ b/dNet/ChatPackets.h @@ -10,6 +10,8 @@ struct SystemAddress; #include #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);