feat(entity): movement AI for scripted objects and ones with a path, as the client

ApplyMovementAiIfNeeded gives an object with a script (its registry's or a
custom script) or an attached_path a movement AI component (id 0); 1843 LOTs
gain one. It follows its attached path only when that is a movement path (a
moving platform's path stays the platform's). Not on spawner objects, which
the client never loads. Nothing sent changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-10-01 17:07:47 -05:00
parent f3ae06cee6
commit 1481bb7b32
3 changed files with 21 additions and 1 deletions

View File

@@ -283,6 +283,12 @@ Entity::ObjectLoadState Entity::DoObjectPreLoad() {
// physics from the model case, which takes the registry's simple physics id
state.hasModelBehaviors = !GetVar<bool>(u"inInventory") && HasVar(u"modelType") && HasVar(u"modelBehaviors");
// ApplyMovementAiIfNeeded (0x01031350): an object with a script (its registry's, or a custom script in the config,
// ApplyScriptComponentIfHasScript) or an attached path gets movement AI. Not a spawner object: the client never
// loads one, the server keeps thousands
const bool hasScript = state.GetComponentID(eReplicaComponentType::SCRIPT, -1) > -1 || HasVar(u"custom_script_server") || HasVar(u"custom_script_client");
state.addsMovementAI = m_TemplateID != 176 && (hasScript || HasVar(u"attached_path"));
// ObjectLoader2::ApplyComponentWhitelist (0x0103e300): the config's componentWhitelist keeps only these registry
// components (the lists ObjectLoader2's constructor builds; property models use 1)
static constexpr std::array<std::initializer_list<eReplicaComponentType>, 5> COMPONENT_WHITELISTS{ {
@@ -464,6 +470,9 @@ void Entity::DoObjectComponentLoad(ObjectLoadState& state) {
};
AddComponent<MovementAIComponent>(-1, moveInfo);
} else if (state.addsMovementAI) {
// ApplyMovementAiIfNeeded: a scripted object or one with an attached path has one (id 0: no row)
AddComponent<MovementAIComponent>(0, MovementAIInfo{});
}
break;
}

View File

@@ -406,6 +406,8 @@ private:
const std::initializer_list<eReplicaComponentType>* whitelist = nullptr;
bool hasModelBehaviors = false;
bool markedAsPhantom = false;
// ApplyMovementAiIfNeeded: movement AI for a scripted object or one with an attached path
bool addsMovementAI = false;
// A model with behaviors' physics id, which the physics cases leave to the model case
int32_t modelPhysicsComponentID = -1;

View File

@@ -14,6 +14,7 @@
#include "CDClientManager.h"
#include "Game.h"
#include "dZoneManager.h"
#include "Zone.h"
#include "CDComponentsRegistryTable.h"
#include "QuickBuildComponent.h"
@@ -97,7 +98,15 @@ void MovementAIComponent::LoadConfigData() {
if (m_Info.wanderRadius > 8.0f) m_Info.wanderRadius = 8.0f;
if (m_Info.wanderSpeed > 0.5f) m_Info.wanderSpeed *= 0.5f;
SetPath(m_Parent->GetVarAsString(u"attached_path"));
// A movement AI the object has only because it is scripted or has a path (id 0) follows only a movement path: a
// moving platform's path belongs to its moving platform
const auto pathName = m_Parent->GetVarAsString(u"attached_path");
if (m_ComponentID == 0) {
if (pathName.empty()) return;
const auto* const path = Game::zoneManager->GetZone()->GetPath(pathName);
if (!path || path->pathType != PathType::Movement) return;
}
SetPath(pathName);
}
void MovementAIComponent::SetPath(const std::string pathName) {