diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index b6ddf4ce3..25ec9bdf3 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -1565,7 +1565,7 @@ void Entity::OnCollisionPhantom(const LWOOBJID otherEntity) { SwitchComponent* const switchComp = GetComponent(); if (switchComp) { - switchComp->OnUse(other); + switchComp->EnterIfResponding(other); } TriggerEvent(eTriggerEventType::ENTER, other); @@ -1635,16 +1635,21 @@ void Entity::OnEmoteReceived(const int32_t emote, Entity* target) { GetScript()->OnEmoteReceived(this, emote, target); } -void Entity::OnUse(Entity* originator) { - TriggerEvent(eTriggerEventType::INTERACT, originator); - - GetScript()->OnUse(this, originator); - - for (const auto& component : m_Components | std::views::values) { - if (!component) continue; - - component->OnUse(originator); +void Entity::RequestUse(const GameMessages::RequestUse& msg, Entity& user) { + if (!msg.IsMissionChoice()) { + TriggerEvent(eTriggerEventType::INTERACT, &user); + GetScript()->OnUse(this, &user); } + + GameMessages::RequestUseEvent event(msg, user); + HandleMsg(event); +} + +void Entity::OnUse(Entity* originator) { + GameMessages::RequestUse msg; + msg.target = originator->GetObjectID(); + msg.object = m_ObjectID; + RequestUse(msg, *originator); } void Entity::OnHitOrHealResult(Entity* attacker, int32_t damage) { diff --git a/dGame/Entity.h b/dGame/Entity.h index d36789f99..e9618867c 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -28,6 +28,7 @@ namespace GameMessages { struct GetFlag; struct GetFactionTokenType; struct PickupItem; + struct RequestUse; struct ChildRemoved; }; @@ -247,6 +248,10 @@ public: void NotifyObject(Entity* sender, const std::string& name, int32_t param1 = 0, int32_t param2 = 0); void OnEmoteReceived(int32_t emote, Entity* target); + // The client's RequestUse of this object by user: the INTERACT trigger and the script's OnUse (not for the mission + // choice of a multi-interact use), then the components' RequestUse handlers. + void RequestUse(const GameMessages::RequestUse& msg, Entity& user); + // originator uses this object as if their client had sent RequestUse void OnUse(Entity* originator); void OnHitOrHealResult(Entity* attacker, int32_t damage); diff --git a/dGame/dComponents/BuildBorderComponent.cpp b/dGame/dComponents/BuildBorderComponent.cpp index 8af18a2b5..d6dbe3de0 100644 --- a/dGame/dComponents/BuildBorderComponent.cpp +++ b/dGame/dComponents/BuildBorderComponent.cpp @@ -1,4 +1,5 @@ #include "BuildBorderComponent.h" +#include "ObjectMessages.h" #include "EntityManager.h" #include "GameMessages.h" @@ -11,12 +12,15 @@ #include "PropertyManagementComponent.h" BuildBorderComponent::BuildBorderComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + RegisterMsg(&BuildBorderComponent::OnRequestUse); } BuildBorderComponent::~BuildBorderComponent() { } -void BuildBorderComponent::OnUse(Entity* originator) { +bool BuildBorderComponent::OnRequestUse(GameMessages::RequestUseEvent& event) { + if (event.msg.IsMissionChoice()) return false; + auto* const originator = event.user; if (originator->GetCharacter()) { const auto& entities = Game::entityManager->GetEntitiesInGroup("PropertyPlaque"); @@ -31,13 +35,13 @@ void BuildBorderComponent::OnUse(Entity* originator) { auto* inventoryComponent = originator->GetComponent(); if (inventoryComponent == nullptr) { - return; + return true; } auto* thinkingHat = inventoryComponent->FindItemByLot(6086); if (thinkingHat == nullptr) { - return; + return true; } inventoryComponent->PushEquippedItems(); @@ -69,7 +73,8 @@ void BuildBorderComponent::OnUse(Entity* originator) { } InventoryComponent* inv = m_Parent->GetComponent(); - if (!inv) return; + if (!inv) return true; inv->PushEquippedItems(); // technically this is supposed to happen automatically... but it doesnt? so just keep this here } + return true; } diff --git a/dGame/dComponents/BuildBorderComponent.h b/dGame/dComponents/BuildBorderComponent.h index f57313fbc..d24afd520 100644 --- a/dGame/dComponents/BuildBorderComponent.h +++ b/dGame/dComponents/BuildBorderComponent.h @@ -25,7 +25,7 @@ public: * Causes the originator to start build with this entity as a reference point * @param originator the entity (probably a player) that triggered the event */ - void OnUse(Entity* originator) override; + bool OnRequestUse(GameMessages::RequestUseEvent& event); private: }; diff --git a/dGame/dComponents/Component.h b/dGame/dComponents/Component.h index f05248347..374ca87e0 100644 --- a/dGame/dComponents/Component.h +++ b/dGame/dComponents/Component.h @@ -10,6 +10,7 @@ namespace RakNet { namespace GameMessages { struct GameMsg; + struct RequestUseEvent; } class Entity; @@ -61,7 +62,6 @@ public: * Event called when this component is being used, e.g. when some entity interacted with it * @param originator */ - virtual void OnUse(Entity* originator) {} // A character's save (its XML): read on load, written on save virtual void LoadFromSaveData(const tinyxml2::XMLDocument& doc) {} diff --git a/dGame/dComponents/MissionOfferComponent.cpp b/dGame/dComponents/MissionOfferComponent.cpp index 7bc3c2932..f6ff05ad5 100644 --- a/dGame/dComponents/MissionOfferComponent.cpp +++ b/dGame/dComponents/MissionOfferComponent.cpp @@ -5,6 +5,7 @@ #include #include "MissionOfferComponent.h" +#include "ObjectMessages.h" #include "CDClientManager.h" #include "CDMissionsTable.h" #include "CDMissionNPCComponentTable.h" @@ -41,6 +42,7 @@ bool OfferedMission::GetAcceptsMission() const { //------------------------ MissionOfferComponent below ------------------------ MissionOfferComponent::MissionOfferComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + RegisterMsg(&MissionOfferComponent::OnRequestUse); if (componentID != -1) { // Now lookup the missions in the MissionNPCComponent table auto* missionNpcComponentTable = CDClientManager::GetTable(); @@ -73,8 +75,10 @@ namespace { } } -void MissionOfferComponent::OnUse(Entity* originator) { - OfferMissions(originator); +bool MissionOfferComponent::OnRequestUse(GameMessages::RequestUseEvent& event) { + if (event.msg.IsMissionChoice()) OfferMissions(event.user, event.msg.multiInteractID); + else OfferMissions(event.user); + return true; } void MissionOfferComponent::OfferMissions(Entity* entity, const uint32_t specifiedMissionId) { diff --git a/dGame/dComponents/MissionOfferComponent.h b/dGame/dComponents/MissionOfferComponent.h index 74510d831..6dc22ac9e 100644 --- a/dGame/dComponents/MissionOfferComponent.h +++ b/dGame/dComponents/MissionOfferComponent.h @@ -66,11 +66,11 @@ public: MissionOfferComponent(Entity* parent, const int32_t componentID); /** - * Handles the OnUse event triggered by some entity, determines which missions to show based on what they may + * Handles a RequestUse triggered by some entity, determines which missions to show based on what they may * hand in now and what they may start based on their mission history. * @param originator the entity that triggered the event */ - void OnUse(Entity* originator) override; + bool OnRequestUse(GameMessages::RequestUseEvent& event); /** * Offers all the missions an entity can accept to said entity diff --git a/dGame/dComponents/MultiZoneEntranceComponent.cpp b/dGame/dComponents/MultiZoneEntranceComponent.cpp index db716fcb9..b2651f5bd 100644 --- a/dGame/dComponents/MultiZoneEntranceComponent.cpp +++ b/dGame/dComponents/MultiZoneEntranceComponent.cpp @@ -1,10 +1,12 @@ #include "MultiZoneEntranceComponent.h" +#include "ObjectMessages.h" #include "PropertyMessages.h" #include "RocketLaunchpadControlComponent.h" #include "InventoryComponent.h" #include "CharacterComponent.h" MultiZoneEntranceComponent::MultiZoneEntranceComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + RegisterMsg(&MultiZoneEntranceComponent::OnRequestUse); m_Parent = parent; std::string zoneString = GeneralUtils::UTF16ToWTF8(m_Parent->GetVar(u"MultiZoneIDs")); std::stringstream ss(zoneString); @@ -17,17 +19,20 @@ MultiZoneEntranceComponent::MultiZoneEntranceComponent(Entity* parent, const int MultiZoneEntranceComponent::~MultiZoneEntranceComponent() {} -void MultiZoneEntranceComponent::OnUse(Entity* originator) { +bool MultiZoneEntranceComponent::OnRequestUse(GameMessages::RequestUseEvent& event) { + if (event.msg.IsMissionChoice()) return false; + auto* const originator = event.user; auto* const characterComponent = originator->GetComponent(); - if (!characterComponent) return; + if (!characterComponent) return true; auto* rocket = characterComponent->RocketEquip(originator); - if (!rocket) return; + if (!rocket) return true; // the LUP world menu is just the property menu, the client knows how to handle it GameMessages::PropertyEntranceBegin msg; msg.target = m_Parent->GetObjectID(); msg.Send(originator->GetSystemAddress()); + return true; } void MultiZoneEntranceComponent::OnSelectWorld(Entity* originator, uint32_t index) { diff --git a/dGame/dComponents/MultiZoneEntranceComponent.h b/dGame/dComponents/MultiZoneEntranceComponent.h index 1ef184077..5d98162d5 100644 --- a/dGame/dComponents/MultiZoneEntranceComponent.h +++ b/dGame/dComponents/MultiZoneEntranceComponent.h @@ -20,13 +20,13 @@ public: ~MultiZoneEntranceComponent() override; /** - * Handles an OnUse event from some entity, preparing it for launch to some other world + * Handles a RequestUse from some entity, preparing it for launch to some other world * @param originator the entity that triggered the event */ - void OnUse(Entity* originator) override; + bool OnRequestUse(GameMessages::RequestUseEvent& event); /** - * Handles an OnUse event from some entity, preparing it for launch to some other world + * Handles a RequestUse from some entity, preparing it for launch to some other world * @param originator the entity that triggered the event * @param index index of the world that was selected */ diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 4e7b50d33..fedafbd9c 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -53,6 +53,7 @@ std::unordered_map PetComponent::activePets{}; */ PetComponent::PetComponent(Entity* parentEntity, const int32_t componentID) : Component{ parentEntity, componentID } { + RegisterMsg(&PetComponent::OnRequestUse); m_PetInfo = CDClientManager::GetTable()->GetByID(componentID); // TODO: Make reference when safe m_Interaction = LWOOBJID_EMPTY; @@ -120,19 +121,21 @@ void PetComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpd } } -void PetComponent::OnUse(Entity* originator) { +bool PetComponent::OnRequestUse(GameMessages::RequestUseEvent& event) { + if (event.msg.IsMissionChoice()) return false; + auto* const originator = event.user; if (m_Owner != LWOOBJID_EMPTY) { // The owner uses their pet with SHIFT or the pet menu's action button: the client sends RequestUse on the pet // (LWOPetControlComponent::msgPetCommand, "contextAction") if (originator->GetObjectID() == m_Owner) UseAbility(); - return; + return true; } if (m_Tamer != LWOOBJID_EMPTY) { auto* tamer = Game::entityManager->GetEntity(m_Tamer); if (tamer != nullptr) { - return; + return true; } m_Tamer = LWOOBJID_EMPTY; @@ -140,11 +143,11 @@ void PetComponent::OnUse(Entity* originator) { auto* const inventoryComponent = originator->GetComponent(); if (inventoryComponent == nullptr) { - return; + return true; } if (m_Preconditions.has_value()) { - if (!m_Preconditions->Check(originator)) return; + if (!m_Preconditions->Check(originator)) return true; // Taming uses up the items its preconditions ask for for (const auto& cost : m_Preconditions->GetItemCosts(originator)) { inventoryComponent->RemoveItem(cost.lot, cost.count, eInventoryType::ALL); @@ -161,17 +164,17 @@ void PetComponent::OnUse(Entity* originator) { const auto* const entry = CDClientManager::GetTable()->GetByLOT(m_Parent->GetLOT()); if (!entry) { ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to find the puzzle minigame for this pet."); - return; + return true; } const auto* const destroyableComponent = originator->GetComponent(); if (destroyableComponent == nullptr) { - return; + return true; } const auto imagination = destroyableComponent->GetImagination(); if (imagination < entry->imaginationCost) { - return; + return true; } const auto& bricks = BrickDatabase::GetBricks(entry->validPieces); @@ -179,7 +182,7 @@ void PetComponent::OnUse(Entity* originator) { ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to load the puzzle minigame for this pet."); LOG("Couldn't find %s for minigame!", entry->validPieces.c_str()); - return; + return true; } // Where live put the minigame (2014 captures, the same for every try at a pet): the pet goes back to where it @@ -254,6 +257,7 @@ void PetComponent::OnUse(Entity* originator) { characterComponent->SetCurrentActivity(eGameActivity::PET_TAMING); Game::entityManager->SerializeEntity(originator); } + return true; } void PetComponent::Run(float deltaTime) { @@ -1251,7 +1255,7 @@ void PetComponent::UseAbility() { SendHelp(eHelpType::PR_TOOLTIP_1ST_PET_JUMPED_ON_SWITCH); auto* const switchComponent = object->GetComponent(); - if (switchComponent) switchComponent->OnUse(m_Parent); + if (switchComponent) switchComponent->EnterIfResponding(m_Parent); } } diff --git a/dGame/dComponents/PetComponent.h b/dGame/dComponents/PetComponent.h index 5ff6c7f7d..f4ccc0620 100644 --- a/dGame/dComponents/PetComponent.h +++ b/dGame/dComponents/PetComponent.h @@ -43,10 +43,10 @@ public: void Run(float deltaTime) override; /** - * Handles an OnUse event from another entity, initializing the pet taming minigame if this pet is untamed. + * Handles a RequestUse from another entity, initializing the pet taming minigame if this pet is untamed. * @param originator the entity that triggered the event */ - void OnUse(Entity* originator) override; + bool OnRequestUse(GameMessages::RequestUseEvent& event); /** * Attempts to complete the pet minigame by passing a list of bricks to build the minigame model. diff --git a/dGame/dComponents/PossessableComponent.cpp b/dGame/dComponents/PossessableComponent.cpp index a38cf5ba9..aa42c12ce 100644 --- a/dGame/dComponents/PossessableComponent.cpp +++ b/dGame/dComponents/PossessableComponent.cpp @@ -1,10 +1,12 @@ #include "PossessableComponent.h" +#include "ObjectMessages.h" #include "PossessorComponent.h" #include "EntityManager.h" #include "Inventory.h" #include "Item.h" PossessableComponent::PossessableComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + RegisterMsg(&PossessableComponent::OnRequestUse); m_Possessor = LWOOBJID_EMPTY; CDItemComponent item = Inventory::FindItemComponent(m_Parent->GetLOT()); m_AnimationFlag = static_cast(item.animationFlag); @@ -48,9 +50,12 @@ void PossessableComponent::Dismount() { if (m_ItemSpawned) m_Parent->ScheduleKillAfterUpdate(); } -void PossessableComponent::OnUse(Entity* originator) { +bool PossessableComponent::OnRequestUse(GameMessages::RequestUseEvent& event) { + if (event.msg.IsMissionChoice()) return false; + auto* const originator = event.user; auto* possessor = originator->GetComponent(); if (possessor) { possessor->Mount(m_Parent); } + return true; } diff --git a/dGame/dComponents/PossessableComponent.h b/dGame/dComponents/PossessableComponent.h index 776d0fe61..ec4a40e1a 100644 --- a/dGame/dComponents/PossessableComponent.h +++ b/dGame/dComponents/PossessableComponent.h @@ -79,11 +79,11 @@ public: LWOOBJID GetIsItemSpawned() const { return m_ItemSpawned; }; /** - * Handles an OnUsed event by some other entity, if said entity has a Possessor it becomes the possessor + * Handles a RequestUse by some other entity, if said entity has a Possessor it becomes the possessor * of this entity * @param originator the entity that caused the event to trigger */ - void OnUse(Entity* originator) override; + bool OnRequestUse(GameMessages::RequestUseEvent& event); private: diff --git a/dGame/dComponents/PropertyEntranceComponent.cpp b/dGame/dComponents/PropertyEntranceComponent.cpp index 5548b89c6..29cccf24b 100644 --- a/dGame/dComponents/PropertyEntranceComponent.cpp +++ b/dGame/dComponents/PropertyEntranceComponent.cpp @@ -1,4 +1,5 @@ #include "PropertyEntranceComponent.h" +#include "ObjectMessages.h" #include "CDPropertyEntranceComponentTable.h" @@ -19,6 +20,7 @@ #include "User.h" PropertyEntranceComponent::PropertyEntranceComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + RegisterMsg(&PropertyEntranceComponent::OnRequestUse); this->propertyQueries = {}; auto table = CDClientManager::GetTable(); @@ -28,12 +30,14 @@ PropertyEntranceComponent::PropertyEntranceComponent(Entity* parent, const int32 this->m_PropertyName = entry.propertyName; } -void PropertyEntranceComponent::OnUse(Entity* entity) { +bool PropertyEntranceComponent::OnRequestUse(GameMessages::RequestUseEvent& event) { + if (event.msg.IsMissionChoice()) return false; + auto* const entity = event.user; auto* characterComponent = entity->GetComponent(); - if (!characterComponent) return; + if (!characterComponent) return true; auto* rocket = entity->GetComponent()->RocketEquip(entity); - if (!rocket) return; + if (!rocket) return true; GameMessages::PropertyEntranceBegin msg; msg.target = m_Parent->GetObjectID(); @@ -48,6 +52,7 @@ void PropertyEntranceComponent::OnUse(Entity* entity) { uiMessage.strMessageName = "pushGameState"; uiMessage.args = std::move(args); uiMessage.SendToClient(entity->GetSystemAddress()); + return true; } void PropertyEntranceComponent::OnEnterProperty(Entity* entity, uint32_t index, bool returnToZone, const SystemAddress& sysAddr) { diff --git a/dGame/dComponents/PropertyEntranceComponent.h b/dGame/dComponents/PropertyEntranceComponent.h index 14c60aeb9..9674f9ed1 100644 --- a/dGame/dComponents/PropertyEntranceComponent.h +++ b/dGame/dComponents/PropertyEntranceComponent.h @@ -18,10 +18,10 @@ public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PROPERTY_ENTRANCE; /** - * Handles an OnUse request for some other entity, rendering the property browse menu + * Handles a RequestUse for some other entity, rendering the property browse menu * @param entity the entity that triggered the event */ - void OnUse(Entity* entity) override; + bool OnRequestUse(GameMessages::RequestUseEvent& event); /** * Handles the event triggered when the entity selects a property to visit and makes the entity to there diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index baa26f730..798d42793 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -1,4 +1,5 @@ #include "PropertyManagementComponent.h" +#include "ObjectMessages.h" #include "PropertyRent.h" #include "ChatPackets.h" #include "DashboardNotify.h" @@ -41,6 +42,7 @@ PropertyManagementComponent* PropertyManagementComponent::instance = nullptr; PropertyManagementComponent::PropertyManagementComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + RegisterMsg(&PropertyManagementComponent::OnRequestUse); this->owner = LWOOBJID_EMPTY; this->templateId = 0; this->propertyId = LWOOBJID_EMPTY; @@ -978,11 +980,14 @@ void PropertyManagementComponent::SendPropertyData(const Entity& player, const G forPlayer.Send(player.GetSystemAddress()); } -void PropertyManagementComponent::OnUse(Entity* originator) { +bool PropertyManagementComponent::OnRequestUse(GameMessages::RequestUseEvent& event) { + if (event.msg.IsMissionChoice()) return false; + auto* const originator = event.user; OnQueryPropertyData(originator, UNASSIGNED_SYSTEM_ADDRESS); GameMessages::OpenPropertyManagement msg; msg.target = PropertyManagementComponent::Instance()->GetParent()->GetObjectID(); msg.Send(originator->GetSystemAddress()); + return true; } void PropertyManagementComponent::SetOwnerId(const LWOOBJID value) { diff --git a/dGame/dComponents/PropertyManagementComponent.h b/dGame/dComponents/PropertyManagementComponent.h index 9377d094c..5dbd8195f 100644 --- a/dGame/dComponents/PropertyManagementComponent.h +++ b/dGame/dComponents/PropertyManagementComponent.h @@ -49,10 +49,10 @@ public: void OnQueryPropertyData(Entity* originator, const SystemAddress& sysAddr, LWOOBJID author = LWOOBJID_EMPTY); /** - * Handles an OnUse event, telling the client who owns this property, etc. + * Handles a RequestUse, telling the client who owns this property, etc. * @param originator the entity that triggered the event */ - void OnUse(Entity* originator) override; + bool OnRequestUse(GameMessages::RequestUseEvent& event); /** * Sets the owner of this property diff --git a/dGame/dComponents/PropertyVendorComponent.cpp b/dGame/dComponents/PropertyVendorComponent.cpp index bb4f7137e..bf1992894 100644 --- a/dGame/dComponents/PropertyVendorComponent.cpp +++ b/dGame/dComponents/PropertyVendorComponent.cpp @@ -1,4 +1,5 @@ #include "PropertyVendorComponent.h" +#include "ObjectMessages.h" #include "PropertyMessages.h" #include "GameMessages.h" @@ -11,10 +12,13 @@ #include "UserManager.h" PropertyVendorComponent::PropertyVendorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + RegisterMsg(&PropertyVendorComponent::OnRequestUse); } -void PropertyVendorComponent::OnUse(Entity* originator) { - if (PropertyManagementComponent::Instance() == nullptr) return; +bool PropertyVendorComponent::OnRequestUse(GameMessages::RequestUseEvent& event) { + if (event.msg.IsMissionChoice()) return false; + auto* const originator = event.user; + if (PropertyManagementComponent::Instance() == nullptr) return true; OnQueryPropertyData(originator, originator->GetSystemAddress()); @@ -25,8 +29,9 @@ void PropertyVendorComponent::OnUse(Entity* originator) { msg.target = m_Parent->GetObjectID(); msg.Send(originator->GetSystemAddress()); - return; + return true; } + return true; } void PropertyVendorComponent::OnQueryPropertyData(Entity* originator, const SystemAddress& sysAddr) { diff --git a/dGame/dComponents/PropertyVendorComponent.h b/dGame/dComponents/PropertyVendorComponent.h index 4865c9886..4b706a7e8 100644 --- a/dGame/dComponents/PropertyVendorComponent.h +++ b/dGame/dComponents/PropertyVendorComponent.h @@ -16,7 +16,7 @@ public: * Handles a use event from some entity, if the property is cleared this allows the entity to claim it * @param originator the entity that triggered this event */ - void OnUse(Entity* originator) override; + bool OnRequestUse(GameMessages::RequestUseEvent& event); /** * Handles a property data query after the property has been claimed, sending information about the property to the diff --git a/dGame/dComponents/QuickBuildComponent.cpp b/dGame/dComponents/QuickBuildComponent.cpp index a5f74336d..c7c357db3 100644 --- a/dGame/dComponents/QuickBuildComponent.cpp +++ b/dGame/dComponents/QuickBuildComponent.cpp @@ -32,6 +32,7 @@ #include "EconomyLedger.h" QuickBuildComponent::QuickBuildComponent(Entity* const entity, const int32_t componentID) : Component{ entity, componentID } { + RegisterMsg(&QuickBuildComponent::OnRequestUse); std::u16string checkPreconditions = entity->GetVar(u"CheckPrecondition"); if (!checkPreconditions.empty()) { @@ -246,16 +247,19 @@ void QuickBuildComponent::Run(float deltaTime) { } } -void QuickBuildComponent::OnUse(Entity* originator) { +bool QuickBuildComponent::OnRequestUse(GameMessages::RequestUseEvent& event) { + if (event.msg.IsMissionChoice()) return false; + auto* const originator = event.user; if (GetBuilder() != nullptr || m_State == eQuickBuildState::COMPLETED) { - return; + return true; } if (m_Precondition != nullptr && !m_Precondition->Check(originator)) { - return; + return true; } StartQuickBuild(originator); + return true; } void QuickBuildComponent::SpawnActivator() { diff --git a/dGame/dComponents/QuickBuildComponent.h b/dGame/dComponents/QuickBuildComponent.h index 81caa7b16..b7cb7b12b 100644 --- a/dGame/dComponents/QuickBuildComponent.h +++ b/dGame/dComponents/QuickBuildComponent.h @@ -31,10 +31,10 @@ public: void Run(float deltaTime) override; /** - * Handles a OnUse event from some entity, initiating the quick build + * Handles a RequestUse from some entity, initiating the quick build * @param originator the entity that triggered the event */ - void OnUse(Entity* originator) override; + bool OnRequestUse(GameMessages::RequestUseEvent& event); /** * Spawns the activator that can be used to initiate the quickbuild diff --git a/dGame/dComponents/RailActivatorComponent.cpp b/dGame/dComponents/RailActivatorComponent.cpp index 0377a30b5..db0dba12d 100644 --- a/dGame/dComponents/RailActivatorComponent.cpp +++ b/dGame/dComponents/RailActivatorComponent.cpp @@ -1,5 +1,6 @@ #include #include "RailActivatorComponent.h" +#include "ObjectMessages.h" #include "CDClientManager.h" #include "CDRailActivatorComponent.h" #include "Entity.h" @@ -15,6 +16,7 @@ #include "eStateChangeType.h" RailActivatorComponent::RailActivatorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + RegisterMsg(&RailActivatorComponent::OnRequestUse); const auto tableData = CDClientManager::GetTable()->GetEntryByID(componentID); m_Path = parent->GetVar(u"rail_path"); @@ -51,10 +53,12 @@ RailActivatorComponent::RailActivatorComponent(Entity* parent, const int32_t com RailActivatorComponent::~RailActivatorComponent() = default; -void RailActivatorComponent::OnUse(Entity* originator) { +bool RailActivatorComponent::OnRequestUse(GameMessages::RequestUseEvent& event) { + if (event.msg.IsMissionChoice()) return false; + auto* const originator = event.user; auto* quickBuildComponent = m_Parent->GetComponent(); if (quickBuildComponent != nullptr && quickBuildComponent->GetState() != eQuickBuildState::COMPLETED) - return; + return true; if (quickBuildComponent != nullptr) { // Don't want it to be destroyed while a player is using it @@ -101,6 +105,7 @@ void RailActivatorComponent::OnUse(Entity* originator) { startRail.railActivatorObjectID = m_Parent->GetObjectID(); startRail.Send(originator->GetSystemAddress()); }); + return true; } void RailActivatorComponent::OnRailMovementReady(Entity* originator) const { diff --git a/dGame/dComponents/RailActivatorComponent.h b/dGame/dComponents/RailActivatorComponent.h index 6b9b224fe..745bd226e 100644 --- a/dGame/dComponents/RailActivatorComponent.h +++ b/dGame/dComponents/RailActivatorComponent.h @@ -18,10 +18,10 @@ public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RAIL_ACTIVATOR; /** - * Handles the OnUse event from some entity, initiates the rail movement + * Handles a RequestUse from some entity, initiates the rail movement * @param originator the entity that triggered the event */ - void OnUse(Entity* originator) override; + bool OnRequestUse(GameMessages::RequestUseEvent& event); /** * Event handler that's called when some entity has played the start animation for rail movement and now wants to diff --git a/dGame/dComponents/RocketLaunchpadControlComponent.cpp b/dGame/dComponents/RocketLaunchpadControlComponent.cpp index 06982ea4d..542d46c71 100644 --- a/dGame/dComponents/RocketLaunchpadControlComponent.cpp +++ b/dGame/dComponents/RocketLaunchpadControlComponent.cpp @@ -24,6 +24,7 @@ #include "MessageType/Master.h" RocketLaunchpadControlComponent::RocketLaunchpadControlComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + RegisterMsg(&RocketLaunchpadControlComponent::OnRequestUse); auto query = CDClientDatabase::CreatePreppedStmt( "SELECT targetZone, defaultZoneID, targetScene, altLandingPrecondition, altLandingSpawnPointName FROM RocketLaunchpadControlComponent WHERE id = ?;"); query.bind(1, componentID); @@ -92,27 +93,30 @@ void RocketLaunchpadControlComponent::Launch(Entity* originator, LWOMAPID mapId, Game::entityManager->SerializeEntity(originator); } -void RocketLaunchpadControlComponent::OnUse(Entity* originator) { +bool RocketLaunchpadControlComponent::OnRequestUse(GameMessages::RequestUseEvent& event) { + if (event.msg.IsMissionChoice()) return false; + auto* const originator = event.user; // If we are have the property or the LUP component, we don't want to immediately launch // instead we let their OnUse handlers do their things // which components of an Object have their OnUse called when using them // so we don't need to call it here auto* propertyEntrance = m_Parent->GetComponent(); if (propertyEntrance) { - return; + return true; } auto* rocketLaunchLUP = m_Parent->GetComponent(); if (rocketLaunchLUP) { - return; + return true; } // No rocket no launch auto* rocket = originator->GetComponent()->RocketEquip(originator); if (!rocket) { - return; + return true; } Launch(originator); + return true; } void RocketLaunchpadControlComponent::OnProximityUpdate(Entity* entering, std::string name, std::string status) { diff --git a/dGame/dComponents/RocketLaunchpadControlComponent.h b/dGame/dComponents/RocketLaunchpadControlComponent.h index 136c267ce..985fb36b0 100644 --- a/dGame/dComponents/RocketLaunchpadControlComponent.h +++ b/dGame/dComponents/RocketLaunchpadControlComponent.h @@ -45,10 +45,10 @@ public: static GameMessages::FireEventClientSide MakeRocketEquipped(LWOOBJID launchpad, LWOOBJID rocket, LWOOBJID player, LWOCLONEID cloneId, int32_t worldIndex); /** - * Handles an OnUse event from some entity, preparing it for launch to some other world + * Handles a RequestUse from some entity, preparing it for launch to some other world * @param originator the entity that triggered the event */ - void OnUse(Entity* originator) override; + bool OnRequestUse(GameMessages::RequestUseEvent& event); /** * Currently unused diff --git a/dGame/dComponents/SwitchComponent.cpp b/dGame/dComponents/SwitchComponent.cpp index 2e17d95ba..8f9ec044a 100644 --- a/dGame/dComponents/SwitchComponent.cpp +++ b/dGame/dComponents/SwitchComponent.cpp @@ -1,4 +1,5 @@ #include "SwitchComponent.h" +#include "ObjectMessages.h" #include "EntityManager.h" #include "eTriggerEventType.h" #include "RenderComponent.h" @@ -8,6 +9,7 @@ std::vector SwitchComponent::petSwitches; SwitchComponent::SwitchComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + RegisterMsg(&SwitchComponent::OnRequestUse); m_Active = false; m_ResetTime = m_Parent->GetVarAs(u"switch_reset_time"); @@ -34,7 +36,7 @@ void SwitchComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitial outBitStream.Write(m_Active); } -void SwitchComponent::OnUse(Entity* originator) { +void SwitchComponent::EnterIfResponding(Entity* originator) { const auto* const destroyableComponent = originator->GetComponent(); if (!destroyableComponent) return; for (const auto faction : m_FactionsToRespondTo) { @@ -45,6 +47,12 @@ void SwitchComponent::OnUse(Entity* originator) { } } +bool SwitchComponent::OnRequestUse(GameMessages::RequestUseEvent& event) { + if (event.msg.IsMissionChoice()) return false; + EnterIfResponding(event.user); + return true; +} + void SwitchComponent::SetActive(bool active) { m_Active = active; diff --git a/dGame/dComponents/SwitchComponent.h b/dGame/dComponents/SwitchComponent.h index 8fffaabe7..0054c07fd 100644 --- a/dGame/dComponents/SwitchComponent.h +++ b/dGame/dComponents/SwitchComponent.h @@ -22,7 +22,10 @@ public: ~SwitchComponent() override; void Run(float deltaTime) override; - void OnUse(Entity* originator) override; + bool OnRequestUse(GameMessages::RequestUseEvent& event); + + // Enters the switch if entity has one of the factions it responds to (a use, a touch or a pet on it) + void EnterIfResponding(Entity* entity); Entity* GetParentEntity() const; diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index 554e7b2eb..5de9e10cc 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -1,4 +1,5 @@ #include "VendorComponent.h" +#include "ObjectMessages.h" #include "BitStream.h" #include "dServer.h" #include "dZoneManager.h" @@ -21,6 +22,7 @@ #include VendorComponent::VendorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + RegisterMsg(&VendorComponent::OnRequestUse); m_HasStandardCostItems = false; m_HasMultiCostItems = false; SetupConstants(); @@ -36,7 +38,9 @@ void VendorComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitial } } -void VendorComponent::OnUse(Entity* originator) { +bool VendorComponent::OnRequestUse(GameMessages::RequestUseEvent& event) { + if (event.msg.IsMissionChoice()) return false; + auto* const originator = event.user; GameMessages::VendorOpenWindow openWindow; openWindow.target = m_Parent->GetObjectID(); openWindow.SendToClient(originator->GetSystemAddress()); @@ -44,6 +48,7 @@ void VendorComponent::OnUse(Entity* originator) { // Only the entity's VENDOR component sends its stock (achievement and donation vendors never did). auto* vendor = static_cast(m_Parent->GetComponent(eReplicaComponentType::VENDOR)); if (vendor) vendor->SendStatusUpdate(originator->GetSystemAddress()); + return true; } void VendorComponent::RefreshInventory(bool isCreation) { diff --git a/dGame/dComponents/VendorComponent.h b/dGame/dComponents/VendorComponent.h index 08eb8337a..7eea71d76 100644 --- a/dGame/dComponents/VendorComponent.h +++ b/dGame/dComponents/VendorComponent.h @@ -29,7 +29,7 @@ public: void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; - void OnUse(Entity* originator) override; + bool OnRequestUse(GameMessages::RequestUseEvent& event); virtual void RefreshInventory(bool isCreation = false); void SetupConstants(); bool SellsItem(const LOT item) const; diff --git a/dGame/dGameMessages/ObjectMessages.cpp b/dGame/dGameMessages/ObjectMessages.cpp index 824a5d52d..588afbc10 100644 --- a/dGame/dGameMessages/ObjectMessages.cpp +++ b/dGame/dGameMessages/ObjectMessages.cpp @@ -250,22 +250,7 @@ namespace GameMessages { entity.GetCharacter()->SetBuildMode(true); } - if (bIsMultiInteractUse) { - if (multiInteractType == 0) { - auto* missionOfferComponent = static_cast(interactedObject->GetComponent(eReplicaComponentType::MISSION_OFFER)); - - if (missionOfferComponent != nullptr) { - missionOfferComponent->OfferMissions(&entity, multiInteractID); - } - } else { - interactedObject->OnUse(&entity); - } - } else { - interactedObject->OnUse(&entity); - } - - RequestUseEvent event(*this); - interactedObject->HandleMsg(event); + interactedObject->RequestUse(*this, entity); //Perform use task if possible: auto missionComponent = entity.GetComponent(); diff --git a/dGame/dGameMessages/ObjectMessages.h b/dGame/dGameMessages/ObjectMessages.h index 7e0a0475b..e7ce65a89 100644 --- a/dGame/dGameMessages/ObjectMessages.h +++ b/dGame/dGameMessages/ObjectMessages.h @@ -168,8 +168,19 @@ namespace GameMessages { LWOOBJID object{}; bool secondary{ false }; + + // The mission choice of a multi-interact use: only the mission offer handles it. + bool IsMissionChoice() const { return bIsMultiInteractUse && multiInteractType == 0; } + }; + + // A use of an object, delivered to its components' RequestUse handlers. user is who used it: the sender of the + // RequestUse, or whoever the server makes use it. Only dispatch RequestUse locally as this event. + struct RequestUseEvent : public NetGameMsgEvent { + RequestUseEvent() = default; + RequestUseEvent(const RequestUse& message, Entity& user) : NetGameMsgEvent(message), user{ &user } {} + + Entity* user{}; }; - using RequestUseEvent = NetGameMsgEvent; // Client -> server. Developer only. struct RequestServerObjectInfo : public NetGameMsg { diff --git a/tests/dGameTests/dComponentsTests/LootMetricsTests.cpp b/tests/dGameTests/dComponentsTests/LootMetricsTests.cpp index d4c0caa92..904144971 100644 --- a/tests/dGameTests/dComponentsTests/LootMetricsTests.cpp +++ b/tests/dGameTests/dComponentsTests/LootMetricsTests.cpp @@ -12,6 +12,7 @@ #include "InventoryMessages.h" #include "Item.h" #include "LootMetrics.h" +#include "ObjectMessages.h" #include "QuickBuildComponent.h" #include "MissionComponent.h" #include "DestroyableComponent.h" @@ -233,7 +234,7 @@ TEST_F(LootMetricsTests, QuickbuildTakesItsCostAsTheSource) { quickBuild->SetCompleteTime(10.0f); ClearSent(); - quickBuild->OnUse(entity.get()); + { GameMessages::RequestUseEvent use(GameMessages::RequestUse{}, *entity); quickBuild->OnRequestUse(use); } EXPECT_EQ(quickBuild->GetState(), eQuickBuildState::BUILDING); const auto removed = Removed(); ASSERT_EQ(removed.size(), 1u); diff --git a/tests/dGameTests/dComponentsTests/QuickBuildCompleteTests.cpp b/tests/dGameTests/dComponentsTests/QuickBuildCompleteTests.cpp index 064f2c95c..289ab695d 100644 --- a/tests/dGameTests/dComponentsTests/QuickBuildCompleteTests.cpp +++ b/tests/dGameTests/dComponentsTests/QuickBuildCompleteTests.cpp @@ -7,6 +7,7 @@ #include "Entity.h" #include "PlayerMessages.h" #include "dZoneManager.h" +#include "ObjectMessages.h" #include "QuickBuildComponent.h" #include "QuickBuildMessages.h" #include "eQuickBuildState.h" @@ -60,7 +61,7 @@ protected: // Starts the build and runs updates until it completes (the first update only sets up the imagination drain). std::vector Build() { - quickBuild->OnUse(player.get()); + { GameMessages::RequestUseEvent use(GameMessages::RequestUse{}, *player); quickBuild->OnRequestUse(use); } return Capture([&] { for (int i = 0; i < 3 && quickBuild->GetState() != eQuickBuildState::COMPLETED; i++) quickBuild->Run(0.1f); });