From e4c2eecbc7a74182bc910e004f263c257e1debbb Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 19 Jan 2025 22:42:15 -0800 Subject: [PATCH] add msg handling (#1737) --- dCommon/Amf3.h | 29 +++++++++++++- dGame/Entity.cpp | 14 +++++++ dGame/Entity.h | 11 ++++++ dGame/dComponents/CharacterComponent.cpp | 44 +++++++++++++++++++++- dGame/dComponents/CharacterComponent.h | 2 + dGame/dComponents/Component.h | 8 ++++ dGame/dGameMessages/GameMessageHandler.cpp | 39 +++++++++++++++---- dGame/dGameMessages/GameMessages.cpp | 12 ++++++ dGame/dGameMessages/GameMessages.h | 15 +++++++- tests/dCommonTests/Amf3Tests.cpp | 2 +- 10 files changed, 163 insertions(+), 13 deletions(-) diff --git a/dCommon/Amf3.h b/dCommon/Amf3.h index 67d53c28..9a34ad59 100644 --- a/dCommon/Amf3.h +++ b/dCommon/Amf3.h @@ -40,6 +40,7 @@ public: // AMFValue template class instantiations template class AMFValue : public AMFBaseValue { + static_assert(!std::is_same_v, "AMFValue cannot be instantiated with std::string_view"); public: AMFValue() = default; AMFValue(const ValueType value) : m_Data{ value } {} @@ -52,6 +53,15 @@ public: void SetValue(const ValueType value) { m_Data = value; } + AMFValue& operator=(const AMFValue& other) { + return operator=(other.m_Data); + } + + AMFValue& operator=(const ValueType& other) { + m_Data = other; + return *this; + } + protected: ValueType m_Data; }; @@ -211,13 +221,17 @@ public: * @param key The key to associate with the value * @param value The value to insert */ - void Insert(const std::string_view key, std::unique_ptr value) { + template + AmfType& Insert(const std::string_view key, std::unique_ptr value) { const auto element = m_Associative.find(key); + auto& toReturn = *value; if (element != m_Associative.cend() && element->second) { element->second = std::move(value); } else { m_Associative.emplace(key, std::move(value)); } + + return toReturn; } /** @@ -229,11 +243,15 @@ public: * @param key The key to associate with the value * @param value The value to insert */ - void Insert(const size_t index, std::unique_ptr value) { + template + AmfType& Insert(const size_t index, std::unique_ptr value) { + auto& toReturn = *value; if (index >= m_Dense.size()) { m_Dense.resize(index + 1); } + m_Dense.at(index) = std::move(value); + return toReturn; } /** @@ -349,6 +367,13 @@ public: m_Dense.clear(); } + template + AmfType& PushDebug(const std::string_view name) { + auto* value = PushArray(); + value->Insert("name", name.data()); + return value->Insert("value", std::make_unique()); + } + private: /** * The associative portion. These values are key'd with strings to an AMFValue. diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index c11764bd..90bf7e76 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -2216,3 +2216,17 @@ int32_t Entity::GetCollisionGroup() const { return 0; } + +bool Entity::HandleMsg(GameMessages::GameMsg& msg) const { + bool handled = false; + const auto [beg, end] = m_MsgHandlers.equal_range(msg.msgId); + for (auto it = beg; it != end; ++it) { + if (it->second) handled |= it->second(msg); + } + + return handled; +} + +void Entity::RegisterMsg(const MessageType::Game msgId, std::function handler) { + m_MsgHandlers.emplace(msgId, handler); +} diff --git a/dGame/Entity.h b/dGame/Entity.h index 853aa207..700672c3 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -14,12 +14,17 @@ #include "Observable.h" namespace GameMessages { + struct GameMsg; struct ActivityNotify; struct ShootingGalleryFire; struct ChildLoaded; struct PlayerResurrectionFinished; }; +namespace MessageType { + enum class Game : uint16_t; +} + namespace Loot { class Info; }; @@ -316,6 +321,10 @@ public: // Scale will only be communicated to the client when the construction packet is sent void SetScale(const float scale) { m_Scale = scale; }; + void RegisterMsg(const MessageType::Game msgId, std::function handler); + + bool HandleMsg(GameMessages::GameMsg& msg) const; + /** * @brief The observable for player entity position updates. */ @@ -377,6 +386,8 @@ protected: // objectID of receiver and map of notification name to script std::map> m_Subscriptions; + + std::multimap> m_MsgHandlers; }; /** diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index d706af9c..c847b5fb 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -22,6 +22,7 @@ #include "Mail.h" #include "ZoneInstanceManager.h" #include "WorldPackets.h" +#include "MessageType/Game.h" #include CharacterComponent::CharacterComponent(Entity* parent, Character* character, const SystemAddress& systemAddress) : Component(parent) { @@ -47,6 +48,45 @@ CharacterComponent::CharacterComponent(Entity* parent, Character* character, con m_CountryCode = 0; m_LastUpdateTimestamp = std::time(nullptr); m_SystemAddress = systemAddress; + + RegisterMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, this, &CharacterComponent::OnRequestServerObjectInfo); +} + +bool CharacterComponent::OnRequestServerObjectInfo(GameMessages::GameMsg& msg) { + auto& request = static_cast(msg); + AMFArrayValue response; + + response.Insert("visible", true); + response.Insert("objectID", std::to_string(request.targetForReport)); + response.Insert("serverInfo", true); + + auto& data = *response.InsertArray("data"); + auto& cmptType = data.PushDebug("Character"); + + cmptType.PushDebug("Component ID") = GeneralUtils::ToUnderlying(ComponentType); + cmptType.PushDebug("Character's account ID") = m_Character->GetParentUser()->GetAccountID(); + cmptType.PushDebug("Last log out time") = m_Character->GetLastLogin(); + cmptType.PushDebug("Seconds played this session") = 0; + cmptType.PushDebug("Editor enabled") = false; + cmptType.PushDebug("Total number of seconds played") = m_TotalTimePlayed; + cmptType.PushDebug("Total currency") = std::to_string(m_Character->GetCoins()); + cmptType.PushDebug("Currency able to be picked up") = std::to_string(m_DroppedCoins); + cmptType.PushDebug("Tooltip flags value") = "0"; + // visited locations + cmptType.PushDebug("is a GM") = m_GMLevel > eGameMasterLevel::CIVILIAN; + cmptType.PushDebug("Has PVP flag turned on") = m_PvpEnabled; + cmptType.PushDebug("GM Level") = GeneralUtils::ToUnderlying(m_GMLevel); + cmptType.PushDebug("Editor level") = GeneralUtils::ToUnderlying(m_EditorLevel); + cmptType.PushDebug("Guild ID") = "0"; + cmptType.PushDebug("Guild Name") = ""; + cmptType.PushDebug("Reputation") = m_Reputation; + cmptType.PushDebug("Current Activity Type") = GeneralUtils::ToUnderlying(m_CurrentActivity); + cmptType.PushDebug("Property Clone ID") = m_Character->GetPropertyCloneID(); + + GameMessages::SendUIMessageServerToSingleClient("ToggleObjectDebugger", response, m_Parent->GetSystemAddress()); + + LOG("Handled!"); + return true; } bool CharacterComponent::LandingAnimDisabled(int zoneID) { @@ -81,6 +121,8 @@ CharacterComponent::~CharacterComponent() { void CharacterComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { if (bIsInitialUpdate) { + if (!m_Character || !m_Character->GetParentUser()) return; + outBitStream.Write(m_ClaimCodes[0] != 0); if (m_ClaimCodes[0] != 0) outBitStream.Write(m_ClaimCodes[0]); outBitStream.Write(m_ClaimCodes[1] != 0); @@ -100,7 +142,7 @@ void CharacterComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInit outBitStream.Write(m_Character->GetEyebrows()); outBitStream.Write(m_Character->GetEyes()); outBitStream.Write(m_Character->GetMouth()); - outBitStream.Write(0); //AccountID, trying out if 0 works. + outBitStream.Write(m_Character->GetParentUser()->GetAccountID()); outBitStream.Write(m_Character->GetLastLogin()); //Last login outBitStream.Write(0); //"prop mod last display time" outBitStream.Write(m_Uscore); //u-score diff --git a/dGame/dComponents/CharacterComponent.h b/dGame/dComponents/CharacterComponent.h index c8cfa988..cb6027b5 100644 --- a/dGame/dComponents/CharacterComponent.h +++ b/dGame/dComponents/CharacterComponent.h @@ -323,6 +323,8 @@ public: Character* m_Character; private: + bool OnRequestServerObjectInfo(GameMessages::GameMsg& msg); + /** * The map of active venture vision effects */ diff --git a/dGame/dComponents/Component.h b/dGame/dComponents/Component.h index e7fd6028..9379cb15 100644 --- a/dGame/dComponents/Component.h +++ b/dGame/dComponents/Component.h @@ -8,6 +8,10 @@ namespace RakNet { class BitStream; } +namespace GameMessages { + struct GameMsg; +} + class Entity; /** @@ -52,6 +56,10 @@ public: protected: + void RegisterMsg(const MessageType::Game msgId, auto* self, const auto handler) { + m_Parent->RegisterMsg(msgId, std::bind(handler, self, std::placeholders::_1)); + } + /** * The entity that owns this component */ diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 058113d0..ef890401 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -37,8 +37,19 @@ #include "ePlayerFlag.h" #include "dConfig.h" #include "GhostComponent.h" +#include "eGameMasterLevel.h" #include "StringifiedEnum.h" +namespace { + using enum MessageType::Game; + using namespace GameMessages; + using MessageCreator = std::function()>; + std::map g_MessageHandlers = { + { REQUEST_SERVER_OBJECT_INFO, []() { return std::make_unique(); } }, + { SHOOTING_GALLERY_FIRE, []() { return std::make_unique(); } }, + }; +}; + void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const SystemAddress& sysAddr, LWOOBJID objectID, MessageType::Game messageID) { CBITSTREAM; @@ -55,6 +66,24 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System if (messageID != MessageType::Game::READY_FOR_UPDATES) LOG_DEBUG("Received GM with ID and name: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data()); + auto handler = g_MessageHandlers.find(messageID); + if (handler != g_MessageHandlers.end()) { + auto msg = handler->second(); + + // Verify that the system address user is able to use this message. + if (msg->requiredGmLevel > eGameMasterLevel::CIVILIAN) { + auto* usingEntity = Game::entityManager->GetEntity(usr->GetLoggedInChar()); + if (!usingEntity || usingEntity->GetGMLevel() < msg->requiredGmLevel) { + LOG("User %s (%llu) does not have the required GM level to execute this command.", usingEntity->GetCharacter()->GetName().c_str(), usingEntity->GetObjectID()); + return; + } + } + + msg->Deserialize(inStream); + msg->Handle(*entity, sysAddr); + return; + } + switch (messageID) { case MessageType::Game::UN_USE_BBB_MODEL: { @@ -104,13 +133,13 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System break; } - // Currently not actually used for our implementation, however its used right now to get around invisible inventory items in the client. + // Currently not actually used for our implementation, however its used right now to get around invisible inventory items in the client. case MessageType::Game::SELECT_SKILL: { auto var = entity->GetVar(u"dlu_first_time_load"); if (var) { entity->SetVar(u"dlu_first_time_load", false); InventoryComponent* inventoryComponent = entity->GetComponent(); - + if (inventoryComponent) inventoryComponent->FixInvisibleItems(); } break; @@ -704,12 +733,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System case MessageType::Game::UPDATE_INVENTORY_GROUP_CONTENTS: GameMessages::HandleUpdateInventoryGroupContents(inStream, entity, sysAddr); break; - case MessageType::Game::SHOOTING_GALLERY_FIRE: { - GameMessages::ShootingGalleryFire fire{}; - fire.Deserialize(inStream); - fire.Handle(*entity, sysAddr); - break; - } default: LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data()); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 940eb549..6fd7576f 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -6431,4 +6431,16 @@ namespace GameMessages { void ShootingGalleryFire::Handle(Entity& entity, const SystemAddress& sysAddr) { entity.OnShootingGalleryFire(*this); } + + bool RequestServerObjectInfo::Deserialize(RakNet::BitStream& bitStream) { + if (!bitStream.Read(bVerbose)) return false; + if (!bitStream.Read(clientId)) return false; + if (!bitStream.Read(targetForReport)) return false; + return true; + } + + void RequestServerObjectInfo::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* handlingEntity = Game::entityManager->GetEntity(targetForReport); + if (handlingEntity) handlingEntity->HandleMsg(*this); + } } diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 4dde6285..c3889674 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -12,6 +12,7 @@ #include "eLootSourceType.h" #include "Brick.h" #include "MessageType/Game.h" +#include "eGameMasterLevel.h" class AMFBaseValue; class Entity; @@ -50,7 +51,8 @@ enum class eCameraTargetCyclingMode : int32_t { namespace GameMessages { struct GameMsg { - GameMsg(MessageType::Game gmId) : msgId{ gmId } {} + GameMsg(MessageType::Game gmId, eGameMasterLevel lvl) : msgId{ gmId }, requiredGmLevel{ lvl } {} + GameMsg(MessageType::Game gmId) : GameMsg(gmId, eGameMasterLevel::CIVILIAN) {} virtual ~GameMsg() = default; void Send(const SystemAddress& sysAddr) const; virtual void Serialize(RakNet::BitStream& bitStream) const {} @@ -58,6 +60,7 @@ namespace GameMessages { virtual void Handle(Entity& entity, const SystemAddress& sysAddr) {}; MessageType::Game msgId; LWOOBJID target{ LWOOBJID_EMPTY }; + eGameMasterLevel requiredGmLevel; }; class PropertyDataMessage; @@ -769,6 +772,16 @@ namespace GameMessages { struct PlayerResurrectionFinished : public GameMsg { PlayerResurrectionFinished() : GameMsg(MessageType::Game::PLAYER_RESURRECTION_FINISHED) {} }; + + struct RequestServerObjectInfo : public GameMsg { + bool bVerbose{}; + LWOOBJID clientId{}; + LWOOBJID targetForReport{}; + + RequestServerObjectInfo() : GameMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, eGameMasterLevel::DEVELOPER) {} + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + }; }; #endif // GAMEMESSAGES_H diff --git a/tests/dCommonTests/Amf3Tests.cpp b/tests/dCommonTests/Amf3Tests.cpp index 79d0d496..245cfe86 100644 --- a/tests/dCommonTests/Amf3Tests.cpp +++ b/tests/dCommonTests/Amf3Tests.cpp @@ -68,7 +68,7 @@ TEST(dCommonTests, AMF3InsertionAssociativeTest) { array.Insert("Integer", 42U); array.Insert("Double", 42.0); array.InsertArray("Array"); - array.Insert>("Undefined", {}); + array.Insert>("Undefined", std::vector{}); array.Insert("Null", nullptr); ASSERT_EQ(array.Get("CString")->GetValueType(), eAmf::String);