The world read only a scene's objects and skipped the rest with
file.ignore. Level now reads a .lvl the way SceneLoader::LoadLvlFile
does (1.10.64) and keeps what it reads.
Chunked files: the file info chunk gives where the environment, object
and particle chunks start (the chunks were walked in order before); the
environment chunk gives where the lighting, skydome and editor settings
start, and the client reads all three only when there is lighting.
Files from before chunks: the no-editor-settings byte only from version
32, the revision from 37, a file newer than 48 is not read, a file
older than 3 has no objects; then lighting, skydome, editor settings
and objects in turn.
Kept, by version:
- lighting: blend time (45 on, default 10), ambient, specular, upper
hemisphere, position, min and max draw distances (31 on; before 39
fog near and far only), cull settings (40 on), fog colour (31 on),
directional colour (36 on), and before 42 the zone's spawn point
(before 33 the fixed one the client uses);
- the skydome and its five other files (34 on; they were skipped from
33);
- the editor's colours (37 on, the first 32);
- each object's node type (38 on), glom ID (32 on) and render
techniques (7 on: a 64 byte name, then per technique a 64 byte name,
u32, u8 and 16 floats); before, a count of techniques was read and
the techniques themselves were not, so an object with any misread
the rest of the file.
Object config is split at line breaks as before. A file that ends early
keeps the objects read before its end.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Zone held a std::function that queried the SceneTable, so tests could swap it out. CDSceneTableTable now loads the table with the others, and Zone looks scenes up in it; tests fill its entries like the zone table's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LuzFile::ReadLUZFile, LuzReader::ReadScenes and ZoneLoader::ReadZoneFile
(1.10.64):
- a version older than 20 is read as 20;
- before version 30 a scene is a bare u32 SceneTable ID: in ID order,
each one with a SceneTable row gets the next index (-1 past 255) as
its ID and the row's sceneName as its file; the others are left out;
- from 30 to 32 the file has no scene IDs: a scene's ID is its index in
the file (they all had ID 0, colliding);
- a later scene with the same ID and layer replaces an earlier one, a
scene ID with no layer 0 scene is left out, and before version 41
only the layer 0 scenes are loaded.
No 1.10.64 zone file has a duplicate scene, a scene without a layer 0
scene or another layer before version 41, so what live zones load is
unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LuzFile::ReadLUZFile copies the path chunk by its size and ReadLUZPaths
reads it with LevelPath::FromBuffer (1.10.64). Reading as they do:
- the paths are read from their chunk, never past its end;
- a chunk version of 2 or more, 10000 paths or more, or a path the
client refuses (version 19 or more, 10000 waypoints or more, more
than 99 name/value pairs on a waypoint) leaves the zone with no
paths, logged; the rest of the zone file still reads;
- before version 3 a path's type is a name ("platform" or a movement
path) and every waypoint has a rotation, the moving platform values
and a name/value list, whatever its type;
- property headers are there from version 3, spawner and camera
headers from version 4;
- a camera waypoint's FOV before version 11 is divided by 1.25;
- a property path's reputation multiplier outside 0-1000 becomes 0.
Every path chunk of every client's zone files on disk (858) reads to
its end.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ZoneBoundary's destinationZone and destinationSceneID and the scene's NiColor, so main's scene tools and tests read
the zone this branch's reader loads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dashboard's copy of the terrain reader in dCommon is gone; main's in dZoneManager reads for everyone, and keeps
what the copy had over it: colour and texture maps from before version 32 become RGBA, cut to the resolution the
client keeps. The scene map (ZoneScenes) and the dCommon tests compile against that reader.
A scene ID takes its layer as the zone file numbers it (0 General, 1 Audio) as well as the enum, so main's scene tools
and tests build; /getscene prints the layer as a number.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's model behaviors (every rotation, DoDamage, OnTimer, OnStartup, MoveBackToStart, moves along the model's
local axes), raw terrain reading with scene lookups and scene tools, property model and brick by brick fixes and the
chat server's player count fix, merged onto this branch's structures:
- game messages stay structs; the angular velocity messages join them and the handler sets a message's target
- strips own their speed again and the block names, moves and rotations stay in the shared action table
(PropertyBehaviorActions, which the dashboard's behavior player reads; it now lists rotations too)
- the zone keeps reading its file through ZoneFile and takes the boundaries from it, then loads the raw terrain as main
does; the old dNavigation terrain reader is gone
- the show all response stays a struct and logs a player count mismatch as main does
- the property management component tells models who sent a chat message
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: the server mock keeps a copy of the last bitstream sent
The bitstreams sent are usually on the sender's stack, so the pointer it kept was dangling once Send returned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: PlaceModelResponse writes the model's rotation
The client's PlaceModelResponse::Deserialize (0x00dc0170) reads the rotation as an optional w, x, y, z quaternion, but the server wrote the 4-byte response after the rotation flag, so with any rotation other than identity the client ran out of data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: naming a model no longer renames the property
The model info window sends UpdatePropertyOrModelForFilterCheck with isProperty false (client 0x00cc42e0); only the property editor sends it true (0x00c2e710). A model's name and description aren't stored yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: keep a model item's blueprint across saves
A brick built model item's config has blueprintid (lowercase), which the saved item XML didn't keep (only blueprintID), so a picked up model lost its blueprint on the next load and could not be placed again. It is now saved as bp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: write model behaviors only for property and inventory models
The client makes the mutable model component, which reads the behaviors and editing info, only when the object's config has propertyObjectID or inInventory (ObjectLoader2::LoadModelBehaviorsComponent); every other model gets the plain one, which reads only the model block. Writing the behaviors for every model made the client read them as the next component's data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: the model vault uses the models' inventory groups, as the client
LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) and msgUpdateInventoryGroupContents (0x00ce1f50) look the inventory type up in m_VaultToPlayerInventory first, which maps VAULT_MODELS to MODELS. The server refused group updates for the model vault.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat: raw terrain parsing for scene data
Replace old dNavigation/dTerrain raw parser with new Raw module in
dZoneManager. Parse heightmaps, color maps, and scene maps from .raw
files to determine which scene a position belongs to. Build scene
adjacency graph from terrain data and scene transitions.
Adds NiColor type, SceneColor lookup table, eSceneType enum, terrain
mesh generation with OBJ export, and debug slash commands for scene
visualization.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: deduplicate terrain grid lookup logic, remove unnecessary cast
Add IsValidForSceneLookup(), GetSceneIDAtGrid(), and GridToWorldPos()
to Raw::Chunk. Use them in GenerateTerrainMesh, GetSceneIDFromPosition,
SpawnScenePoints, and SpawnAllScenePoints instead of duplicated inline
math. Remove unnecessary static_cast in LoadSceneTransitionInfo.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add version < 32 support to raw terrain parser
The parser was only correct for v32 files. For v<32:
- colorMapResolution is width-1, not width
- Color map reads width*width*4 bytes (BGRA per-pixel), not colorMapRes^2*4
- No DDS lightmap
- No texture settings byte or blend DDS
- Scene map: v31 reads (colorMapRes+1)^2 cells with border skip,
v<31 skips 1 byte
- No mesh data
Verified against all 309 .raw files across multiple client versions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: log and reject legacy raw terrain format, clean up ReadRaw API
Log a clear message and throw if the zone uses a legacy raw format
(LUZ version < 30) since no known maps use it. Remove ReadLegacyRaw
and the mapVersion parameter from ReadRaw.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: revert v33 scene field renames to unknown1/unknown2
These fields are skipped by the client in every known build (ptr += 12
and ptr += 4). No client ever reads them into a struct. Keep the
original unknown1/unknown2 names from main.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: load the zone without scene lookups when its raw terrain is missing or unreadable
Log at debug when it parses; build the terrain mesh only for the OBJ export; read the zone name and description only for version 31 and later, as the client does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: look up the scene at a position as the client does, in constant time
Chunk bounds span (width - 1) * scale; the nearest scene map cell is used and the far edge is kept one cell inside. Chunks are indexed by grid cell when they form a grid. Skipping reads instead of seeking, so a cut-off file can't seek past the end. Terrain mesh indices follow the vertex order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: scene point commands take a step and use brick LOTs from the BrickIDTable
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: rename NiColor::ToXYZ to Luminance
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: raw terrain reading, scene lookup, scene graph and scene commands
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: close the OBJ file before removing it so the test passes on Windows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: keep the zone boundary lines and read the luz scene count as the client does
Each boundary line's destination is read as a map ID and an instance ID into a LWOZONEID. The scene count is a byte before version 37 and a u32 from it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: name the fourth flair colour byte padding
The client never sets or reads it, so it holds leftover memory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: keep the scene map of version 31 and older raws
These files have a (resolution + 1)^2 scene map, kept in sceneMapLegacy. Version 31 uses its first resolution rows and columns; older versions are all scene 0, as in the client.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor: simplify the raw heightmap size check
The dimensions are capped at kMaxResolution, so the product can't overflow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: remove the scene point spawn commands and send /getadjacentscenes as one message
The spawn commands were only for testing. /getscene and /getadjacentscenes stay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
IDatabaseUsage: MySQL/MariaDB from information_schema.TABLES (fresh
statistics on MySQL 8), SQLite from its page counts and the dbstat table
(now compiled in) with counted rows. Samples go to database_usage and
database_usage_tables (migrations 105 / 88).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOMissionComponent::MissionWindowOK (0x00d501f0) turns a mission in only
when CanReceiveAllRewards (msgCanReceiveAllRewards 0x00d17e20,
sub_00d17750) passes for the reward the player picked: the reward items
(the repeat ones once completed, each at least 1) must fit after the items
the tasks take (a gather task's target unless it keeps them, one of a
delivery task's target) with the first completion's bag rewards added.
The fit is LWOInventoryComponent_Common::CheckCanAddItems (0x00d53de0):
turned in items leave first (an item of 1 settles the whole count), a
unique item fails while one is held outside the buyback, then each item
tops up stacks of its LOT (stack size 0 is no limit, LookupItemInfo
0x00c1c190) and takes free slots.
MissionDialogueOK now refuses a turn-in that does not pass, which the
client never sends. The client's max currency part is not checked: the
server writes no cm, so the client has no limit either.
InventoryComponent::GetDefaultInventorySize is split out of GetInventory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client's cooldown (LWOMissionComponent sub_00cd7770) counts whole
minutes since the last completion and has none when Missions.cooldownTime
is 0 or below. Accepting used seconds and treated only -1 as no cooldown,
so a cooldown of 0 still refused in the second of completion and other
negative values refused for a while. CanAccept now uses the same check as
the offers (MissionComponent::IsCooldownRunning).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client checks Missions.prereqMissionID in
LWOMissionComponent::getMissionState (0x00cd7400): text without any of
!&^;,|(): is one mission ID (met when 0 or ever completed); anything else
goes through its expression reader (sub_00a32980), left to right with no
precedence, & , ; and, | or, ^ xor, ! ~ not, ( ) groups, and/or skipping
the rest once the result is known. Each operand (sub_00c90a80): "id" is
met once the mission was ever completed; "id:state" when the client's
state mask of that mission equals state.
DLU read the text right to left ignoring groups, so
"(509|229)&(812|813|814|815)" (mission 781) was met by 509 alone; a plain
ID needed the mission's state to be 8, so a repeatable being repeated
blocked the missions after it (1870 after 1869); and id:state compared the
mission's own state. GetClientMissionState moves to MissionPrerequisites
so operands can ask for the mask.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client keeps a viewed flag on each mission in progress, sets it with
SetMissionViewed (LWOMissionComponent::msgSetMissionViewed 0x00bec7a0, only
for missions in progress) and reads it as v on the mission's <cur> entry
(LWOMissionComponent::LoadFromSaveData 0x00d171c0, a number or "true", false
when missing). The server now handles SetMissionViewed, saves v="1" for a
viewed mission in progress and loads it back; saves without v load as not
viewed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOInventoryComponent_Common::msgUseNonEquipmentItem (0x00cdeba0) asks
GetStunned first and uses nothing when canUseItem is false
(LWOControllablePhysComponent::msgGetStunned 0x00be0df0: stunned from
using items, or dead). The server now counts the use-item stun of every
SetStunned it sends for an entity as msgSetStunned (0x00bf1d10) does
(push/pop, never below 0, characters only, unless immune and not
bIgnoreImmunity), and checks it with IsDead before using an item. The
server never checked either.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live charxmls carried <items nn="1"> (225 of 226 captured).
LWOInventoryComponent_Common::LoadFromSaveData (0x00d1ec40) reads it and
then does not add the inventory component's template items to the loaded
character. No effect on players today (the player LOT's inventory
component has no items); the server never wrote it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOInventoryComponent_Client::LoadConfigData (0x00cde200) reads txfring
from the character data; without it the client runs its inventory-full
tips (msgPlayerReady 0x00bde5e0) on every world load. Of 226 captured
live character data packets, 222 had the key, all true; the server never
wrote it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) stores
the ADD's locked flag in the group; locked groups don't count toward the
50 user groups (msgGetInventoryGroupCount 0x00bde4b0 counts only unlocked
ones) and msgUpdateInventoryGroupContents (0x00ce1f50) applies the
1000-LOT cap only to unlocked groups. LoadFromSaveData (0x00d1ec40) reads
it from the grp element's u attribute. The server dropped the flag and
always saved u="0"; saves without u load as unlocked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOInventoryComponent_Common::msgUpdateInventoryGroupContents (0x00ce1f50)
ignores an ADD to an unlocked group that already holds 1000 LOTs. The
server had no cap, so a modified client could grow the charxml without
limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOInventoryComponent_Common::msgUpdateInventoryGroupContents (0x00ce1f50)
takes ADD, REMOVE and CLEAR; CLEAR removes every LOT from the group. The
server logged CLEAR as an invalid action and kept the LOTs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) and
msgUpdateInventoryGroupContents (0x00ce1f50) look the inventory type up in
m_VaultToPlayerInventory first, which Initialize (0x00d55a60) fills with
VAULT_MODELS -> MODELS. The server refused group updates for the model
vault.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) resizes
the group name to 64 characters on ADD and MODIFY. The server kept names
of any length in the charxml.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOInventoryComponent_Common::msgUpdateInventoryGroup (0x00c955b0) ignores
an ADD whose group id is already in the inventory. The server added a
second group with the same id, saved to the charxml.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOInventoryComponent_Common::msgMoveItemInInventory (0x00d20590) looks the
item up in the inventory the message names and moves it only to the
inventory its LOT belongs in (GetInventoryTypeForLOT 0x00c3c4f0), or for
the vaults (VAULT_ITEMS, VAULT_MODELS) the LOT's vault type
(GetValueTypeForLOT 0x00c3c4a0). An item in the target slot swaps into the
moved item's old slot and inventory, and must belong there. The server
moved an item into any inventory destInvType named and left the swapped
item in the destination. Live clients never set destInvType (0 of 61 in
the captures).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOInventoryComponent_Common::DoRemoveItemFromInventory (0x00d1fb50)
passes over equipped items when the loot source is LOOT_SOURCE_DELETION.
The server deleted the item while it was still worn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOInventoryComponent_Common::msgRemoveItemFromInventory (0x00d203b0)
counts the item (GetInvItemCount) and does nothing when it holds fewer
than iStackCount; a request for an item with no count takes the whole
stack. The server cut the request down to the stack and deleted it, and
deleted nothing for a count of 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOInventoryComponent_Common::CanRemoveFromInventory (0x00ce0d20) refuses
the possessed object (GetPossessedObject) and the mount item
(GetMountInventoryID) before the operator bypass and the deletion
restrictions. The server let a player delete the mount they were riding.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live's 28th sale to a full buyback removed the oldest item, then sent
MoveItemInInventory (inventory 11, VENDOR_BUYBACK) for each of the 26 items
left, to slots 0 to 25 in order, before the sold item was added after them.
The server left the hole, so the sold item took the dropped item's slot
and the buyback page was no longer in sale order. The buyback inventory is
not saved and nothing changes hands; only slots move.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOSkillComponent::msgStartSkill (0x00d45cb0) starts no cooldown for cast
types 1 and 2, and inserts the cooldown (std::map insert), so a cooldown
group already running keeps its time left. The server started one for
every cast type and extended a running group to the longer cooldown.
Live client casts used cast types 0, 3 and 4.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOSkillComponent::MsgSyncSkill (0x00d454b0) checks whether the caster is
dead before running a synced behavior; if so it only removes the behavior
handle from the skill's pending ones. The server ran the sync anyway, so a
player's delayed hit still landed after they died.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOSkillComponent::msgStartSkill (0x00d45cb0) logs and ignores a StartSkill
whose skill handle is still waiting on behavior syncs. The server ran it a
second time: the hits, the imagination cost and the mission progress twice.
Live captures hold 22 byte-identical StartSkill resends with the same
handle a few packets apart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Item::UseNonEquip declared a second `success` inside the package branch, so
the UseItemResult it sent (and its log line) was always false, even when the
package opened and its contents were given. The client
(LWOInventoryComponent_Client::sub_00c1bb20 (0x00c1bb20)) plays the item's
audioEventUse on true and a failure sound on false; live sent true for 183
of the 187 UseItemResult in the captures.
Test: UseItemResultTests.AnOpenedPackageReportsSuccess.
Check in game: opening a package (e.g. a starter pack or a reward bag) plays
its open sound rather than the failure sound.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOMissionOfferComponent builds a player's missions for a giver
(0x00c605f0, one entry 0x00bebf30) in priority order: 0 a mission to turn
in here, 5 one in progress, 10 a new one, 15 a repeat. A ready mission is
only listed where MissionNPCComponent.acceptsMission is set, a new one
only where offersMission is set and its cooldown has run out, a mission in
progress by every giver that has it. A random mission (0x00c5ffd0) lists
one of its pool that the giver has: one to turn in first, then one in
progress, else a random one. Missions are kept by mission ID; equal
priorities keep that order.
DLU sent an OfferMission for every mission that qualified, including one
ready to turn in at a giver that does not accept it. In the live captures
every use got one mission's OfferMission pair. A use now offers the head
of the list, and a pick from the multi interaction menu the picked one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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 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>
LWOPlatform::ResumeTravelling: not travelling at a waypoint it waits the waypoint's wait; the AG monument elevator rose at once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>