refactor(component): the quickbuild is an activity component, as in the client

LWOQuickBuildComponent uses the activity component's functions; DLU's
QuickBuildComponent now derives ActivityComponent. Its RebuildComponent row
(the activity among it) loads in LoadDataFromTemplate and the level's
overrides in LoadConfigData, out of Entity::Initialize. It still writes its
own replica data (same construction and update bytes) and spawns its
activator as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-10-01 14:19:49 -05:00
parent ab8b944f2f
commit 4bfdeec369
3 changed files with 55 additions and 47 deletions

View File

@@ -625,57 +625,13 @@ void Entity::Initialize() {
case eReplicaComponentType::QUICK_BUILD: {
const auto quickBuildComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::QUICK_BUILD);
if (quickBuildComponentID > 0) {
auto* const quickBuildComponent = AddComponent<QuickBuildComponent>(quickBuildComponentID);
AddComponent<QuickBuildComponent>(quickBuildComponentID);
// A quick build without a destroyable gets one (componentID -1) and is smashable, as the client makes it
// (DoObjectComponentLoad); live: health 1 / 1.0, armor 1 / 0, faction -1, smashable (AG LOT 7804)
if (!HasComponent(eReplicaComponentType::DESTROYABLE)) {
AddComponent<DestroyableComponent>(-1, true);
}
CDRebuildComponentTable* const rebCompTable = CDClientManager::GetTable<CDRebuildComponentTable>();
const std::vector<CDRebuildComponent> rebCompData = rebCompTable->Query([quickBuildComponentID](CDRebuildComponent entry) { return (entry.id == quickBuildComponentID); });
if (!rebCompData.empty()) {
quickBuildComponent->SetResetTime(rebCompData[0].reset_time);
quickBuildComponent->SetCompleteTime(rebCompData[0].complete_time);
quickBuildComponent->SetTakeImagination(rebCompData[0].take_imagination);
quickBuildComponent->SetInterruptible(rebCompData[0].interruptible);
quickBuildComponent->SetSelfActivator(rebCompData[0].self_activator);
quickBuildComponent->SetActivityId(rebCompData[0].activityID);
quickBuildComponent->SetPostImaginationCost(rebCompData[0].post_imagination_cost);
quickBuildComponent->SetTimeBeforeSmash(rebCompData[0].time_before_smash);
const auto rebuildResetTime = GetVar<float>(u"rebuild_reset_time");
if (rebuildResetTime != 0.0f) {
quickBuildComponent->SetResetTime(rebuildResetTime);
}
// The level's activityID replaces the template's. The client (LWOQuickBuildComponent::LoadDataFromTemplate)
// reads RebuildComponent.activityID over it unless the level sets actIDovrd, but live used the level's:
// the AM Center draw bridge (LOT 12047: level activityID 12047 without actIDovrd, template activity 6,
// the quickbuild rewards) dropped no loot in 4 of 4 live completions.
const auto activityID = GetVar<int32_t>(u"activityID");
if (activityID > 0) {
quickBuildComponent->SetActivityId(activityID);
Loot::CacheMatrix(activityID);
}
const auto timeBeforeSmash = GetVar<float>(u"tmeSmsh");
if (timeBeforeSmash > 0) {
quickBuildComponent->SetTimeBeforeSmash(timeBeforeSmash);
}
const auto compTime = GetVar<float>(u"compTime");
if (compTime > 0) {
quickBuildComponent->SetCompleteTime(compTime);
}
}
}
break;
}

View File

@@ -1,4 +1,6 @@
#include "QuickBuildComponent.h"
#include "CDClientManager.h"
#include "CDRebuildComponentTable.h"
#include "Entity.h"
#include "DestroyableComponent.h"
#include "GameMessages.h"
@@ -31,12 +33,30 @@
#include "Amf3.h"
#include "EconomyLedger.h"
QuickBuildComponent::QuickBuildComponent(Entity* const entity, const int32_t componentID) : Component{ entity, componentID } {
QuickBuildComponent::QuickBuildComponent(Entity* const entity, const int32_t componentID) : ActivityComponent{ entity, componentID } {
RegisterMsg(&QuickBuildComponent::OnRequestUse);
RegisterMsg(&QuickBuildComponent::OnGetObjectReportInfo);
}
// The level config: the precondition and the activator's position, then the activator
// The RebuildComponent row, its activity among them (LWOQuickBuildComponent is an activity component in the client)
void QuickBuildComponent::LoadDataFromTemplate() {
const auto componentID = m_ComponentID;
const std::vector<CDRebuildComponent> rebCompData = CDClientManager::GetTable<CDRebuildComponentTable>()->Query([componentID](CDRebuildComponent entry) { return (entry.id == static_cast<uint32_t>(componentID)); });
if (rebCompData.empty()) return;
SetResetTime(rebCompData[0].reset_time);
SetCompleteTime(rebCompData[0].complete_time);
SetTakeImagination(rebCompData[0].take_imagination);
SetInterruptible(rebCompData[0].interruptible);
SetSelfActivator(rebCompData[0].self_activator);
SetActivityId(rebCompData[0].activityID);
SetPostImaginationCost(rebCompData[0].post_imagination_cost);
SetTimeBeforeSmash(rebCompData[0].time_before_smash);
LoadActivityData(rebCompData[0].activityID);
m_HasRow = true;
}
void QuickBuildComponent::LoadConfigData() {
std::u16string checkPreconditions = m_Parent->GetVar<std::u16string>(u"CheckPrecondition");
@@ -55,7 +75,34 @@ void QuickBuildComponent::LoadConfigData() {
m_ActivatorPosition = m_Parent->GetPosition();
}
// The activator is spawned as it was before the row loaded in LoadDataFromTemplate: without looking at
// self_activator (unchanged behaviour; whether live made activators for self-activating quickbuilds is unchecked)
const bool selfActivator = m_SelfActivator;
m_SelfActivator = false;
SpawnActivator();
m_SelfActivator = selfActivator;
// The level's settings over the row's
if (!m_HasRow) return;
const auto rebuildResetTime = m_Parent->GetVar<float>(u"rebuild_reset_time");
if (rebuildResetTime != 0.0f) SetResetTime(rebuildResetTime);
// The level's activityID replaces the template's. The client (LWOQuickBuildComponent::LoadDataFromTemplate)
// reads RebuildComponent.activityID over it unless the level sets actIDovrd, but live used the level's:
// the AM Center draw bridge (LOT 12047: level activityID 12047 without actIDovrd, template activity 6,
// the quickbuild rewards) dropped no loot in 4 of 4 live completions.
const auto activityID = m_Parent->GetVar<int32_t>(u"activityID");
if (activityID > 0) {
SetActivityId(activityID);
Loot::CacheMatrix(activityID);
}
const auto timeBeforeSmash = m_Parent->GetVar<float>(u"tmeSmsh");
if (timeBeforeSmash > 0) SetTimeBeforeSmash(timeBeforeSmash);
const auto compTime = m_Parent->GetVar<float>(u"compTime");
if (compTime > 0) SetCompleteTime(compTime);
}
QuickBuildComponent::~QuickBuildComponent() {

View File

@@ -6,6 +6,7 @@
#include <string>
#include "dCommonVars.h"
#include "NiPoint3.h"
#include "ActivityComponent.h"
#include "ScriptedActivityComponent.h"
#include "Preconditions.h"
#include "Component.h"
@@ -20,12 +21,13 @@ enum class eQuickBuildFailReason : uint32_t;
* consists of an activator that shows a popup and then the actual entity that the bricks are built into. Note
* that quick builds are also scripted activities so this shared some logic with the ScriptedActivityComponent.
*/
class QuickBuildComponent final : public Component {
class QuickBuildComponent final : public ActivityComponent {
public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::QUICK_BUILD;
QuickBuildComponent(Entity* const entity, const int32_t componentID);
void LoadDataFromTemplate() override;
void LoadConfigData() override;
~QuickBuildComponent() override;
@@ -361,6 +363,9 @@ private:
*/
int32_t m_ActivityId = 0;
// Whether the RebuildComponent row was found (the level's settings only apply over a row)
bool m_HasRow = false;
/**
* Currently unused
*/