Commit Graph

1285 Commits

Author SHA1 Message Date
Aaron Kimbrell
8564e293fa fix(mission): GetMissionState answers the client's state bit mask
LWOMissionComponent::getMissionState (0x00cd7400) returns a mask: COMPLETE
once the mission was completed, ORed with the state of the mission in
progress; a mission not in progress gets AVAILABLE when it was never
completed or is repeatable and its prerequisites are met, else 0. DLU's
handler answered UNKNOWN (-1, every bit set) for a mission that cannot be
taken and COMPLETE for a completed repeatable one, and ignored
cooldownInfoRequested. With cooldown info requested (0x00cd7770) the
client only checks the cooldown of a repeat in progress (state 9 or 10):
fewer whole minutes since the last completion than Missions.cooldownTime.

MissionComponent::GetMissionState, used across the server, is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:43:08 -05:00
Aaron Kimbrell
0253fea006 feat(destroyable): enemy preconditions from the level, as the client
An object's EnemyPrecondition (level config) decides who it is an enemy of:
whoever asks and doesn't meet the preconditions, whatever the factions
(LWODestroyableComponent::msgIsEnemy 0x00c9e930; ',' ';' ':' join groups
that must all pass, '|' alternatives). Forbidden Valley Siege's objects use
it today; any level object can.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:31:35 -05:00
Aaron Kimbrell
4b84677cba fix(destroyable): resurrect only a dead player that isn't already resurrecting
RequestResurrect resurrected whoever sent it, so a living player could reset
their health and imagination, and repeated requests stacked respawns. The
client's destroyable resurrects only when smashed and not resurrecting
(LWODestroyableComponent::SendMessage 0x00d29e40); live, all 17 requests in
the captures came after the player's Die and before its Resurrect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:17:28 -05:00
Aaron Kimbrell
cf1e8a54c6 fix(destroyable): damage reductions add up, as the client
The damage reduction behavior set the reduction to its amount and back to 0
when it ended, so a second reduction replaced the first and ending either
removed both. The client's behavior sends AddDamageReduction
(DamageReductionBehavior::Cast) and the destroyable adds it to the current
reduction; removing takes off that amount, never below 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:11:20 -05:00
Aaron Kimbrell
3236d9f08a fix(entity): children once each in id order, and the count matches the ids sent
The client keeps an object's children as a set of ids
(LWOGameObject::childObjects). Every multi-child list in the live captures
(57) was in ascending id order. A child added twice is now kept once. The
child count also counted entries the loop then skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:55:04 -05:00
Aaron Kimbrell
6f4fd4054a feat(entity): the object's interaction distance, preconditions and world state, as the client
LWOGameObject loads these itself: interactionDistance from its Objects row
(16 without one) and the level's interaction_distance over it,
CheckPrecondition and objectWorldState from the level config, before its
components load theirs. The quickbuild, the pet and the race instancer read
them from the object; the race instancer now uses the Objects row's
distance like the client does, instead of a fixed 16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:44:53 -05:00
Aaron Kimbrell
0c215de446 fix(entity): send updatesWithParentPosition from the spawn behavior, as live
The parent info always said the object doesn't move with its parent. Live
sent true for objects a spawn behavior made with updatePositionWithParent
(all 70 Engineer Personal Fortress constructions in the captures, LOT 9479,
and the Summoner's Scratch, 9644); the client then moves them with the
player. Every other construction in the captures sent false, and none sent
an object world state, which stays unsent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:30:23 -05:00
Aaron Kimbrell
9aa06f349c fix(entity): message handlers run in the order they registered, as the client
Handlers sat in an unordered_multimap, whose order for one message is up to
the standard library: GCC ran them newest first, so the components loaded
last answered a RequestUse first, and Windows builds could differ. They are
now kept per message in registration order: the object's own handlers, then
its components' in load order (LWOGameObject::PassMessageToComponents).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:19:18 -05:00
Aaron Kimbrell
260e6cb1b1 refactor(entity): each component type has an allocator, as the client
DoObjectComponentLoad walks the client's load order and calls the type's
allocator from a table, like ObjectLoader2, instead of one 76-case switch.
Components with rules of their own have a static Allocator (the client's
LWOxComponent::Allocator); the rest share a registry allocator. The load
state moves to ObjectLoadState.h so the allocators can take it.

Construction bytes are identical for all 15,962 LOTs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:11:58 -05:00
Aaron Kimbrell
3a79286666 fix(combat-ai): wander only as often as the movement row's WanderChance
An enemy moved every time its wander timer ran out. The MovementAIComponent
rows give a percent chance (90 or 100) to move each time; enemies with no
row (survival and instance enemies, bosses, behavior spawns) get 0 and now
only move to fight and to return.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:01:11 -05:00
Aaron Kimbrell
1068fe904d fix(entity): no mission offer on characters and no racing stats, as the client
The client gives a character no mission offer component (LOT 1's registry
has none; DLU's had no missions) and never makes racing stats (74 is not in
its component load order). Same bytes for every LOT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 17:56:50 -05:00
Aaron Kimbrell
0fccea55b8 fix(entity): proximity and pathfinding from a script only when the client makes the script
The client's script allocator adds them only for a script component it makes
(a registry script with a non-zero id, or a custom script in the config it is
sent). Characters and the registry-id-0 script LOTs no longer get them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 17:51:44 -05:00
Aaron Kimbrell
b81ffb2d28 refactor(entity): components load their config and save after all exist, as the client
LWOGameObject::MakeReadyForSerialization runs each component's
LoadConfigData and then a character's LoadFromSaveData, in the order the
components were made, once DoObjectComponentLoad has made them all; then
their Startup. Entity::Initialize now does the same with a component list in
creation order (the client's componentList); the walk's own save calls
(mission, physics, destroyable, level, character, skill, ghost) and the
destroyable's call into the buff go. A model's behaviors load in its
LoadConfigData; controllable physics places a non-character at
construction, as the client's physics takes the spawn position. A component
added later still loads at once. Same bytes for every LOT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 17:47:30 -05:00
Aaron Kimbrell
818d584c76 feat(entity): a model with behaviors gets no physics from the physics cases
As LWOControllablePhysComponent's and LWOPhantomPhysicsComponent's
allocators do (like the simple physics one already): the registry's physics
id is left to the model case, which makes the model's physics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 17:40:29 -05:00
Aaron Kimbrell
6627b022d4 feat(component): the BBB component only where the client has one
The client makes a BBB component (76) for a character (DoObjectPreLoad, LOT 1)
and for registry entries (none in the CDClient), and reads it last
(LWOBBBComponent::Deserialize: a metadata-source-item bit and the item).
DLU wrote a placeholder 0 bit at the end of every construction; now only a
character's BBBComponent writes it. Every other object sends one bit less,
which the client never read (live non-player constructions decode without
it; the zone control object stays byte for byte equal to live).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 17:18:52 -05:00
Aaron Kimbrell
243b2106bc refactor(entity): destroyables made where the client makes them
The destroyable slot makes only the registry's (with its buff); a
collectible's destroyable comes right after the collectible when there is
none, and a smashable object's (is_smashable) after the walk
(LWODestroyableComponent::Allocator), as in DoObjectComponentLoad. A
quickbuild or model with is_smashable now gets its own defaults first, as
the client's order gives it. Same bytes for every LOT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 17:13:37 -05:00
Aaron Kimbrell
1481bb7b32 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>
2026-10-01 17:07:47 -05:00
Aaron Kimbrell
f3ae06cee6 feat(component): the exhibit component (32) the client's loader leaves dead
1.10.64's DoObjectComponentLoad has an EXHIBIT case that makes nothing;
DLU makes ExhibitComponent there, built like LWOExhibitComponent on the
spawner component: its ExhibitComponent row (model offset and size) and the
exhibit_* level keys. It sends nothing in a construction (the client's
Deserialize is empty), so the 12 exhibit LOTs keep their bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:56:25 -05:00
Aaron Kimbrell
477dd9f3e3 refactor(entity): load an object in the client's two steps
Entity::Initialize now runs DoObjectPreLoad (what the components will be:
the player's own components, markedAsPhantom, model behaviors, the
component whitelist) and DoObjectComponentLoad (the walk in the client's
order and what the client adds after it), as ObjectLoader2 does, with an
ObjectLoadState that answers the registry through the whitelist. Same
components and bytes for every LOT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:52:15 -05:00
Aaron Kimbrell
432f4f6ff9 fix(component): send a script component only when the client makes one
The client makes a script component for a registry script with a non-zero
id, or for a custom script in the config it is sent (LOT 176 never):
ApplyScriptComponentIfHasScript and LWOScriptComponent's allocator. A
character's script and a server-only custom_script_server stay on the
server and send nothing. The 5 LOTs whose registry script id is 0 (e.g. the
AG concert fan, LOT 3002: live sent no config and no script) lose the bit
the client never read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:38:56 -05:00
Aaron Kimbrell
c69b26b661 feat(entity): more of the client's object loading rules
From ObjectLoader2 (DoObjectPreLoad / DoObjectComponentLoad):
- componentWhitelist N keeps only that whitelist's registry components
  (ApplyComponentWhitelist; property models use 1).
- No pet component for an object in an inventory (LWOPetComponent::Factory).
- Havok vehicle physics: use_simple_physics makes simple physics instead,
  objectWorldState 2 none.
- markedAsPhantom (any value) turns the registry's simple physics into
  phantom physics with that row, and doesn't count when the registry has
  phantom physics.
- Vendors get a proximity monitor first when they have none; a mission
  offer's object gets one after the walk (113 LOTs gain one).
Same bytes for every LOT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:32:07 -05:00
Aaron Kimbrell
b24661710b refactor(component): controllable physics places its object itself
Where an object starts (where the level put it) moves into
ControllablePhysicsComponent::LoadConfigData, and where a character comes
into the world (its save, then its respawn point, the target scene or the
zone's spawn when it came from elsewhere) into LoadFromSaveData, out of
Entity::Initialize. Same placement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:05:04 -05:00
Aaron Kimbrell
4ea1762e0a refactor(component): the script component picks its own script
The ScriptComponent row (server and client script) loads in
LoadDataFromTemplate and custom_script_server / custom_script_client in
LoadConfigData, out of Entity::Initialize, which only decides whether there
is one (a registry entry, a character or a custom script), as the client's
allocator does. Same scripts and bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:03:00 -05:00
Aaron Kimbrell
bff3fe6363 refactor(component): moving platform subcomponents like the client's
PlatformSubComponent (LWOPlatform: the state between waypoints and its
serialization) is the base of MoverSubComponent (4), SimpleMoverSubComponent
(5) and the new RotatorSubComponent (6), as LWOPlatformMover,
LWOPlatformSimpleMover and LWOPlatformRotator are built on LWOPlatform. The
platform keeps one subcomponent per type the client makes
(LWOMovingPlatformComponent::SetupPlatform: one per platformIs* setting that
is true) and sends them all; the first is the one the server runs. Same bytes
for every platform with one subcomponent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:59:45 -05:00
Aaron Kimbrell
083c071853 refactor(entity): each vendor type in its own place in the client's load order
Vendor (16), donation vendor (100) and achievement vendor (102) are separate
entries in the client's component walk; no LOT has two of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:49:05 -05:00
Aaron Kimbrell
54adbcdce8 refactor(component): proximity monitor and movement AI load their own rows
The ProximityMonitorComponent row's radii and the MovementAIComponent row
(with the level's wanderRadius over it) move out of Entity::Initialize into
each component's LoadDataFromTemplate / LoadConfigData. Same components and
bytes for every LOT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:47:10 -05:00
Aaron Kimbrell
c710e1d3a9 refactor(component): vendors share the client's base vendor component
LWOBaseVendorComponent is the base of the client's vendor, donation vendor and
achievement vendor; DLU's vendor logic becomes BaseVendorComponent with
VendorComponent, DonationVendorComponent and AchievementVendorComponent all
built on it (the last two derived from VendorComponent before). Nothing sent
changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:43:13 -05:00
Aaron Kimbrell
9a464ac35f refactor(component): group the component files by the client's families
Physics/, Activity/ (activity, scripted activity, shooting gallery,
quickbuild, minigame control, racing control base, racing control, gate rush),
Vendor/, Character/ (what the client makes with a character, plus ghost,
minifig and mission), Sound/ and Property/; the rest stays where it was.
File moves only; DLU_COMPONENT_INCLUDE_DIRS gives every target the folders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:37:45 -05:00
Aaron Kimbrell
8f09121ad4 feat(entity): a character's components as the client makes them
LWOCharacterComponent::Factory makes possession control, mount control, pet
creator, level progression and forced movement, then the character, all with
componentID -1; DLU now makes the same set in the same order (mount control
and pet creator are new, empty). The ghost is added after the component walk,
where the client adds it, for a character with physics. Nothing sent changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:30:20 -05:00
Aaron Kimbrell
b0dd69cf0d feat(entity): player forced movement (106) from the registry, written where the client reads it
The client makes 106 from the registry; the only non-character LOT with it
(16684, a test skateboard mount) now has one and writes it between the pet
and the character, the client's place for it. A character still writes it
with its other parts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:49:13 -05:00
Aaron Kimbrell
a7c8556cb5 feat(entity): the client's HF light direction gadget component (66)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:46:45 -05:00
Aaron Kimbrell
0138d45c58 fix(component): the zone control object constructs as on live
Minigame control (50) is an activity component in the client
(LWOMinigameComponent) and is made from the registry, so the zone control
object (LOT 2365, registry id 0) has one; it sends what an activity sends
(dirty, no players), not a fixed 0x40000000 (which nothing sent: the old
id > 0 check never made it). The zone's script on that object is server
only: the client makes no script component for it, so it sends nothing.
DLU's zone control construction now equals live's byte for byte (209
captures, e.g. GruntMonkey world2); the decoder reads 50 as an activity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:29:02 -05:00
Aaron Kimbrell
0e611bc76c refactor(component): racing control and gate rush share the client's racing control base
LWOBaseRacingControlComponent (an activity component) is the base of the
client's racing control (71) and gate rush control (103); DLU gets the same
BaseRacingControlComponent. Nothing sent changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:23:38 -05:00
Aaron Kimbrell
4bfdeec369 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>
2026-10-01 14:19:49 -05:00
Aaron Kimbrell
ab8b944f2f refactor(component): vehicle physics (46) and physics system (47) are physics components
As in the client, where both use LWOBasePhysComponent's functions (projectile
physics, 18, does not and stays a plain component). They now answer the
object's position like the other physics components (their LOTs have no other
physics); nothing they send changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:56:49 -05:00
Aaron Kimbrell
d786290803 refactor(entity): models get their physics in the client's model case
ObjectLoader2::LoadModelBehaviorsComponent: a model without physics gets simple
physics 4247 (with behaviors: controllable 4246 for modelType 0, else simple,
or the registry's id, which the simple physics case leaves to it), and
modelType 0 gets pathfinding control. This replaces the BBB model (LOT 14)
special case, which now loads through the registry like any object (same
bytes as live LOT 14 constructions); the 17 other model LOTs without
physics in the registry now get the simple physics the client makes for them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:49:38 -05:00
Aaron Kimbrell
2d7b5cca13 refactor(spawner): the spawner object's SpawnerComponent owns its spawner
The zone manager keeps an index of spawners by object ID; the spawner now
belongs to the SpawnerComponent of its LOT 176 object and goes with it (the
component tells the zone manager to stop indexing it). Spawning, timers and
the update order are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:31:18 -05:00
Aaron Kimbrell
9da7b11456 feat(entity): the client's spawner component (10) on spawner objects
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:19:18 -05:00
Aaron Kimbrell
2f01b85d8a feat(entity): combat AI objects get the client's proximity monitor and pathfinding control
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:39:52 -05:00
Aaron Kimbrell
aea4949cf3 feat(entity): script objects get the client's proximity monitor and pathfinding control
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:38:46 -05:00
Aaron Kimbrell
4a546607f7 feat(entity): the client's pathfinding control component (86) on pets
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:37:17 -05:00
Aaron Kimbrell
c65f559b39 feat(entity): the client's physics system component (47)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:01:25 -05:00
Aaron Kimbrell
5f77dca7e7 feat(entity): the client's vehicle physics component (46)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:01:25 -05:00
Aaron Kimbrell
589a91a1f0 feat(entity): the client's projectile physics component (18)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:01:25 -05:00
Aaron Kimbrell
029e601e07 refactor(component): the destroyable loads itself in the client's phases
The DestructibleComponent row moves from Entity::Initialize into
LoadDataFromTemplate and the level keys into LoadConfigData; a character's
save loads after both. Like the client (LWODestroyableComponent::LoadConfigData
reads them for every destroyable), the destroyables added for models and
quickbuilds now also read set_faction, is_smashable and the level loot matrix;
their live defaults become their template. Nothing changes for objects without
those keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:52:10 -05:00
Aaron Kimbrell
e1c3c19c9a refactor(component): the quickbuild reads its level config and spawns its activator in LoadConfigData
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:37:42 -05:00
Aaron Kimbrell
db99190c31 refactor(component): moving platforms load in the client's phases
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:36:08 -05:00
Aaron Kimbrell
43b99b730f refactor(component): physics components load in the client's phases
The physics row's collision group in LoadDataFromTemplate and the level's
CollisionGroupID in LoadConfigData; each physics type then reads its own level
keys and makes its physics after that, in the same order as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:34:28 -05:00
Aaron Kimbrell
a4f1b974c8 refactor(component): combat AI loads its row, skills and level radii in the client's phases
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:21:32 -05:00
Aaron Kimbrell
1cdac362ff refactor(component): an object's starting inventory loads in LoadDataFromTemplate
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:19:07 -05:00