Components get the client's per-component functions: LoadDataFromTemplate,
PrepareConfigData, LoadConfigData, LoadFromSaveData, Startup, Run,
OnEnterScope, OnLeaveScope, Preshutdown and Shutdown, called by the entity in
the client's order. Update is renamed Run, LoadFromXml LoadFromSaveData and
UpdateXml WriteSaveData. Nothing sent changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client makes LWOMiniFigComponent (35) from the registry; DLU had nothing for it. It sends nothing (the client reads the MinifigComponent row itself), so constructions are unchanged; 488 LOTs gain it (LoaderSnapshotTest).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live constructions of models on a Raven Bluff property (LOT 14, LOT 8069) carry the destroyable with health 1 / 1.0, armor 1 / 0, faction -1, smashable; DLU's had armor 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DoObjectComponentLoad adds a destroyable (componentID -1) to a quick build that has none and marks it smashable. DLU's quick build wrote three empty flags instead. The construction's destroyable now matches live field for field (AG LOT 7804: health 1 / 1.0, armor 1 / 0, faction -1, smashable). 117 LOTs gain it; nothing else changes (LoaderSnapshotTest).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client makes the registry's item component for models too (only a pet takes it away); DLU skipped it and the model component wrote the item block itself, for the 30 model LOTs without an item row as well, and with the object id as ug_id. Live (Raven Bluff property capture): the item block carries the userModelID for brick built models (LOT 14) and 0 for reward models (LOT 8069). 1,455 LOTs gain the item component; nothing else changes (LoaderSnapshotTest).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entity::Initialize walks ComponentOrderVector::Initialize's list (0x0101f8e0) as ObjectLoader2::DoObjectComponentLoad does; ids a component needs from a later one come from the registry; the trigger is added after the walk. All 15,962 LOTs get the same components as before (LoaderSnapshotTest).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StopPathing, StartPathing and GoToWaypoint instead of hand-made resyncs; a built platform starts before the scripts hear of it so a script can stop it; the FV tree platform's resync hack is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The subcomponent comes from the platformIsMover, platformIsSimpleMover and
platformIsRotater settings; with none set, a platform without a registry
component is a mover and one with a registry component a simple mover (also
when it has an attached path, so the registry ID is passed then too). All
flags set false means no subcomponent.
A simple mover reads its MovingPlatforms row and the platformMove* settings,
is always constructed with its starting point and state as live did, and
travels between its start and start + platformMove in platformMoveTime.
A rotater is written as type 6 with a mover's data.
On arrival platforms fire OnWaypointReached, then OnArrivedAtDesiredWaypoint
at the waypoint they were sent to and OnPlatformAtLastWaypoint at an end.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live's AddItemToInventoryClientSync for a picked up drop had
_Metric_Souce_LOT_Int in its extra info on all 2,344 live pickups: the LOT
of the DropClientLoot's source object, 1 when a player was the source
(activity rewards and chests, which drop from the player). DLU sent none.
The drop remembers its source object's LOT when it is registered for the
player, and the pickup sends it. A source object already gone when it drops sends
no key. Package contents keep sending none, as live's did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frames and phase scopes in the world, auth, chat and UGC loops (packets per type, entities, physics, ghosting, replica, spawners, log flush, saves, web requests by route). Scopes at LoadPlayer, CreateEntity, each component's construction, InventoryComponent::LoadXml, script timers and a world's zone load; game database queries in the query helpers; CDClient statements timed through sqlite3_trace_v2 as CDClient <table> (CppSQLite3DB gets a handle accessor). Task 96.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DestructibleComponent.attack_priority is loaded onto the DestroyableComponent as a signed value. The client's
LWODestroyableComponent (LoadDataFromTemplate, 0x00c9f900) starts it at 1 and keeps 1 when the column is empty, and
answers GetAttackPriority with it; the server now does the same so TacArcs can order their targets by it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client reads RebuildComponent.activityID over a level's activityID
unless the level sets actIDovrd, but live followed the level's: the AM
Center draw bridge (LOT 12047) has activityID 12047 without actIDovrd and
dropped no loot in 4 of 4 live completions, where the template's activity 6
would have dropped quickbuild rewards. DLU already does what live did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live wrote the item component's UGC info on every construction of an
object with an item component (99 of 99 constructions of LOT 14535, a
possessable with item, skill and controllable physics and no Objects row:
ug_id 0, moderation status NoStatus, no description). DLU wrote a 0 bit.
The item component is read after the skill and base-combat-AI components
(ComponentOrderVector::Initialize 0x0101f8e0; live constructions of LOT 14535
have Skill then Item). DLU wrote it before the inventory, which did not show while
it was a single 0 bit next to the skill's 0 bit, but would misalign every
component after it now that it writes more. It now comes after
BASE_COMBAT_AI. The ReplicaComponentOrderTests oracle moves the item the same
way (intended change).
Test: ReplicaConstructionTest.ItemConstructionWritesEmptyUgcInfo,
ReplicaComponentOrderTest.EveryListedComponent (oracle updated).
Check in game: with a second player watching, use a possessable / mount /
vehicle item (e.g. a racing car in the race lobby); it appears, moves and
is left normally for both. Rocket launches and property models look as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client reads an object's factions only from the DestructibleComponent
factionList (LWODestroyableComponent::LoadDataFromTemplate 0x00c9f900: one
atol per comma-separated token, -1 kept; the faction column is not read),
and live replicated that list: [-1] on 12,829 constructions (vendors,
quickbuilds, bouncers). DLU added the faction column and dropped -1,
so those objects were sent with no factions, and the four rows with faction 6
but factionList "-1" were sent as [6].
-1 has no Factions row, so it adds no friends or enemies.
Test: ReplicaConstructionTest.TemplateFactionMinusOneIsReplicated.
Check in game: vendors, quickbuilds, bouncers and NPCs still can't be
attacked; enemies still fight players and pets; smashables still smash.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sent the milliseconds since the object was created on the server in
every construction header (nonzero in 142,258 of 145,677 captured
constructions; the same object constructed again later carries a larger
value by the time elapsed; the local player's own construction on load is
0). DLU always wrote 0. The client stores it in the object's config
(LWOGameObject::time_since_created_on_server).
Entities now remember when they were created (steady clock) and write
their age.
Test: ReplicaConstructionTest.TimeSinceCreatedOnServerIsTheObjectsAge.
Check in game: zone in and walk around several worlds; objects, NPCs,
enemies and other players appear and animate as before (no objects
popping, stuck or misplaced animations on platforms/spinners).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sends used_mouse false, no caster latency, no clicked position and
a zero originator rotation (present, all four components 0) in every
echo: 78,841 of 78,841 in the live captures, player and server casts
alike. DLU forwarded the casting client's values and wrote the caster's
facing for server casts. The client copies the rotation into its local
StartSkill (LWOSkillComponent::msgEchoStartSkill), which is why a real
rotation made power-up pickups jitter; the per-call override that
worked around that is no longer needed and is removed.
Check: with two players, attack, use a thrown or ranged item and pick
up power-ups; the other player sees the same animations and the
caster no longer snaps or jitters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client reads the cooldowns still running from the charxml on load
(LWOSkillComponent::LoadFromSaveData 0x00c2a9c0): <skil sc="..."> split
on ';' and ':' (the delimiter string at 0x015b1714 is L";:") into a
cooldown group and the seconds left, each put in its cooldown map under
{hasCooldownGroup = 1, group}, the key it checks when the player casts a
skill of that group (0x00c11400). Live wrote <skil/> with nothing running
and e.g. <skil sc="17:15.6958;"/> otherwise (226 live charxmls; groups
8, 17 and 78 seen, the times below the groups' SkillBehavior cooldowns).
The claude-client-re-docs page described sc as "id;time" pairs; the
live saves and the delimiters show "group:time;".
DLU wrote no <skil>, so relogging or changing zones cleared every
cooldown (e.g. the 90 s imagination and faction skills).
- A player's successful cast (CastPlayerSkill) starts its
SkillBehavior.cooldowngroup's cooldown (groups 0 and up, cooldown > 0),
counted down in Update.
- Saved as live wrote it; loaded back when the player's SkillComponent
is created, so a cooldown survives several zone changes. Old saves
without <skil> load with none running.
Check in game: use a skill with a long cooldown (e.g. a faction kit's
special skill, a consumable with a cooldown), change zones or log out and
back in right away: its cooldown is still shown and counts down from
where it was (minus the loading time on the server).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The passport totals for coins, bricks and enemies came from the client's
ModifyPlayerZoneStatistic, which live's client sent for its per-zone counts;
live's server counted the totals itself and told the client
(UpdatePlayerStatistic). Now:
- CurrencyCollected (the amount gained) right after every SetCurrency that
raised the coins (pickups, missions, achievements, selling, activities);
none for losses. Captures: 15,815 pickups + mission/achievement/vendor/
activity gains, all with the stat; no loss had one.
- BricksCollected (the count) after every add to the bricks inventory the
client is told about (captures: 999 of 1,010, pickups and relocations).
- The kill counts go to the killer right after Die, before the loot:
EnemiesSmashed when the DestructibleComponent's isnpc is set, else
SmashablesSmashed for a smashable (captures: 2,766 NPC kills and 538
smashables; faction or AI don't decide it, e.g. the Banana Cluster has AI
and counted as a smashable). Neither while racing. Before, every kill was
a SmashablesSmashed.
- ModifyPlayerZoneStatistic from the client only updates the zone counts,
so nothing is counted twice.
Check in game: pick up coins, sell an item, finish a mission, pick up
bricks, smash a stromling and a crate; each passport number goes up by the
right amount at once and stays the same after relogging (no double counts).
The per-zone statistics still go up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A level object's smashable_loot_matrix replaces the DestructibleComponent
LootMatrixIndex when smashable_loot_matrix_set is true or absent and the
matrix is not -1, as the client resolves it
(LWODestroyableComponent::LoadConfigData 0x00c44cb0). DLU only read the
key in BaseInteractDropLootServer, so tagged smashables dropped their
template's loot. Resolution in DestroyableComponent::GetLevelLootMatrix,
with tests. No wire change.
In game: smash level smashables that carry smashable_loot_matrix_set=1
(11 level objects in the 1.10.64 levels) and ones with a matrix but no _set key; they drop the
level's loot instead of the template's. Ordinary smashables
(smashable_loot_matrix_set=0) drop what they did before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client (LWODestroyableComponent::LoadConfigData 0x00c44cb0) uses a
level object's set_faction only when override_faction is absent or true;
with override_faction=0 LoadDataFromTemplate (0x00c9f900) puts the
DestructibleComponent factionList back. 2948 level objects have
override_faction=0, and DLU applied their set_faction anyway. The
resolution is now DestroyableComponent::GetLevelFactions, with tests.
No wire change.
In game: enemies and smashables placed in levels with a set_faction but
override_faction=0 (most smashables, many enemies) are targeted and
aggro as before for the common case; check a few enemies in AG/GF/FV
still fight you and friendly objects are not attacked, and that
smashables still smash and give credit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
property_bff_build (off by default, as live): while the owner is in build
mode, their best friends can join it and place, move and pick up models,
build brick by brick and edit behaviors, each from their own inventory.
Only the owner starts build mode; a best friend still in it when the owner
leaves keeps building until they leave it.
The client lets only the property's owner edit, so each player now gets
their own DownloadPropertyData, with their own id as the owner while they
can build, sent again whenever that changes. SetBuildModeConfirmed and
GetModelsOnProperty go to each player instead of everyone. The first
builder makes the property private and pauses the models; the last one
restores them. Saving no longer needs the owner in the world.
A model taken off the property goes to whoever placed it; if they aren't
here it stays placed. Privacy and the property's name stay owner only
(neither was checked before).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client keeps the scene under the player loaded, the scenes the zone
file's transitions connect to it, and the global scene
(Zone::StreamScenesAroundPosition 0x0108a3f0, TerrainManager::GetSceneAtPos
0x01069010, the connected scenes at 0x01066500; transitions naming a
missing scene dropped as Zone::FixupInvalidTransitions 0x010842e0 does).
- ZoneScenes (dCommon): the terrain's scene map lookup and the scene graph,
shared by the world server and the dashboard.
- Objects remember the scene they were placed in (spawners pass theirs on).
- ghosting_scenes=1 (world config, off by default): players get the objects
of their loaded scenes instead of the ones within the ghosting distances;
objects from no scene go by the scene under them. Zones without a scene
map keep distance ghosting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The live server scripts (l_fv_panda_server.lua, l_crab_server.lua, in
the client's res/scripts) take a tamed pet out of the groups it was
spawned into: the panda leaves "pandas" and "panda<tamer>", the crab
"crab<tamer>". The panda spawner counts "pandas" (at most five) and
"panda<player>" (one per player), so tamed pandas used to keep counting
against both. Entity gets RemoveFromGroup for it.
The dig and object pet scripts keep their TODO: their live server
scripts are not shipped with the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every game message still written or read by hand is now a NetGameMsg with
Serialize and Deserialize, in per-domain files:
- MovementMessages: teleport, platforms (resync and its request), orient to
angle, node rotation lock, gravity scale, jetpack mode, control scheme,
respawn checkpoint, rails (set/start, ready, cancel, arrived), mount
inventory ID, dismount complete, possession ack, ghost reference override
and position, camera cycling (eCameraTargetCyclingMode moves here).
- ZoneMessages: player loaded (the old PLAYER_LOADED case), ready for
updates, player ready, restore to post load stats, server done loading,
invalid zone transfer list, zone summary display/dismissed, level
processing complete, object world state, and the localized announcement
WorldMigration wrote by hand.
- PlayerMessages: chat mode, GM level, LEGO score, currency, reputation,
GM invis, pickup currency, zone and player statistics, chat commands, bug
reports, verify ack.
- ObjectMessages: fire event client/server side, notify client
(zone) object, notify object, script network vars, failed preconditions,
terminate interaction, set name, request use, request server object info.
- QuickBuildMessages: notify state, enable, cancel.
- ActivityMessages gains match response/update/request, leaderboard request
and data, shooting gallery score/rotation/fire, activity state change.
- MissionMessages gains MissionDialogueCancelled (a no-op, as before).
The wire structs left in GameMessages.h move to their domains (tooltip and
emote to Effects, loot and item use to Inventory, model build to Building,
behavior sound to Property, skill sets to Skill) and gain the missing
direction. The dismount logic moves to PossessorComponent::OnDismountComplete.
Every inbound message is registered in the GameMessageHandler map; the
switch is gone, and GameMessages.cpp only holds the GameMsg/NetGameMsg base
code. Call sites build the structs (NotifyClientObject, TerminateInteraction,
Teleport, PlatformResync, FireEventClientSide, NotifyObject and
NotifyClientZoneObject get convenience constructors like PlayFXEffect).
Dead senders are dropped: SendSetShootingGalleryParams (no callers, field
order was a guess), SendTeamPickupItem (the struct already existed),
SendRequestActivitySummaryLeaderboardData (the struct covers it).
Verified with RemainingMessagesTests: every old Send* function is frozen
verbatim in Legacy/RemainingMessagesLegacy.h and compared byte for byte
(same bits, destination and broadcast flag) over grids of inputs; every old
Handle* read sequence is frozen as a Read* oracle and compared with the
struct's Deserialize; round trips, truncation and a golden packet.
PlayerLoaded (0x00dc36f0), SetGMLevel (0x00dd6230), MissionDialogueCancelled
(0x00d9cc10) and LocalizedAnnouncementServerToSingleClient (0x00f23c50)
were checked against the client. Behaviour notes: an inbound message that
fails to deserialize is dropped, so ParseChatMessage over MAX_MESSAGE_LENGTH
is dropped instead of truncated, and PLAYER_LOADED / READY_FOR_UPDATES /
MISSION_DIALOGUE_CANCELLED now read their (unused) client fields.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>