refactor(component): use is a RequestUse message handler

Components no longer have a virtual OnUse; each component that reacts to a
use registers a RequestUse handler, like the client's components. The entity
runs the INTERACT trigger and the script, then delivers a RequestUseEvent
(carrying the user) to the handlers. The mission choice of a multi-interact
use goes only to the mission offer, as before. Switches keep a direct
EnterIfResponding for touches and pets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-10-01 07:55:55 -05:00
parent 50bfa623ca
commit 7a0dba22a5
33 changed files with 166 additions and 91 deletions

View File

@@ -1565,7 +1565,7 @@ void Entity::OnCollisionPhantom(const LWOOBJID otherEntity) {
SwitchComponent* const switchComp = GetComponent<SwitchComponent>();
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) {

View File

@@ -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);

View File

@@ -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<InventoryComponent>();
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<InventoryComponent>();
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;
}

View File

@@ -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:
};

View File

@@ -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) {}

View File

@@ -5,6 +5,7 @@
#include <sstream>
#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<CDMissionNPCComponentTable>();
@@ -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) {

View File

@@ -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

View File

@@ -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<std::u16string>(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<CharacterComponent>();
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) {

View File

@@ -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
*/

View File

@@ -53,6 +53,7 @@ std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::activePets{};
*/
PetComponent::PetComponent(Entity* parentEntity, const int32_t componentID) : Component{ parentEntity, componentID } {
RegisterMsg(&PetComponent::OnRequestUse);
m_PetInfo = CDClientManager::GetTable<CDPetComponentTable>()->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<InventoryComponent>();
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<CDTamingBuildPuzzleTable>()->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<DestroyableComponent>();
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<SwitchComponent>();
if (switchComponent) switchComponent->OnUse(m_Parent);
if (switchComponent) switchComponent->EnterIfResponding(m_Parent);
}
}

View File

@@ -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.

View File

@@ -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<eAnimationFlags>(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<PossessorComponent>();
if (possessor) {
possessor->Mount(m_Parent);
}
return true;
}

View File

@@ -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:

View File

@@ -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<CDPropertyEntranceComponentTable>();
@@ -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<CharacterComponent>();
if (!characterComponent) return;
if (!characterComponent) return true;
auto* rocket = entity->GetComponent<CharacterComponent>()->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) {

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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<std::u16string>(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() {

View File

@@ -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

View File

@@ -1,5 +1,6 @@
#include <algorithm>
#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<CDRailActivatorComponentTable>()->GetEntryByID(componentID);
m_Path = parent->GetVar<std::u16string>(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<QuickBuildComponent>();
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 {

View File

@@ -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

View File

@@ -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<PropertyEntranceComponent>();
if (propertyEntrance) {
return;
return true;
}
auto* rocketLaunchLUP = m_Parent->GetComponent<MultiZoneEntranceComponent>();
if (rocketLaunchLUP) {
return;
return true;
}
// No rocket no launch
auto* rocket = originator->GetComponent<CharacterComponent>()->RocketEquip(originator);
if (!rocket) {
return;
return true;
}
Launch(originator);
return true;
}
void RocketLaunchpadControlComponent::OnProximityUpdate(Entity* entering, std::string name, std::string status) {

View File

@@ -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

View File

@@ -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*> SwitchComponent::petSwitches;
SwitchComponent::SwitchComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
RegisterMsg(&SwitchComponent::OnRequestUse);
m_Active = false;
m_ResetTime = m_Parent->GetVarAs<int32_t>(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<DestroyableComponent>();
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;

View File

@@ -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;

View File

@@ -1,4 +1,5 @@
#include "VendorComponent.h"
#include "ObjectMessages.h"
#include "BitStream.h"
#include "dServer.h"
#include "dZoneManager.h"
@@ -21,6 +22,7 @@
#include <ranges>
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<VendorComponent*>(m_Parent->GetComponent(eReplicaComponentType::VENDOR));
if (vendor) vendor->SendStatusUpdate(originator->GetSystemAddress());
return true;
}
void VendorComponent::RefreshInventory(bool isCreation) {

View File

@@ -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;

View File

@@ -250,22 +250,7 @@ namespace GameMessages {
entity.GetCharacter()->SetBuildMode(true);
}
if (bIsMultiInteractUse) {
if (multiInteractType == 0) {
auto* missionOfferComponent = static_cast<MissionOfferComponent*>(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<MissionComponent>();

View File

@@ -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<RequestUse> {
RequestUseEvent() = default;
RequestUseEvent(const RequestUse& message, Entity& user) : NetGameMsgEvent(message), user{ &user } {}
Entity* user{};
};
using RequestUseEvent = NetGameMsgEvent<RequestUse>;
// Client -> server. Developer only.
struct RequestServerObjectInfo : public NetGameMsg {

View File

@@ -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);

View File

@@ -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<CapturedPacket> 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);
});