Commit Graph

1384 Commits

Author SHA1 Message Date
Aaron Kimbrell
b5de6cf2c9 feat(world): the game client's level editor packets
A world server answers the client's level editor: it remembers per connection
whether the editor is on (and never sends such a client a zone load), takes and
gives up the zone's lock, says whether files may be sent, takes them, sends the
zone's files, and reloads the zone. Who may is the same as for the dashboard's zone
file API: the account's editor level against zone_publish_gm_level, the zone's
editors or an admin, and the lock's holder to send files. Files are unpacked,
checked, written and packed on a worker thread, and every request is in the audit
log.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 19:34:43 -06:00
Aaron Kimbrell
dbe0722095 feat(character): the editor level is the account's GM level, and the client is told it
A character's editor level is its account's GM level for accounts at editor_gm_level
or above, and 0 for everyone else (it was 1 or 0). It is written as the save's edit
attribute, sent with the character after the GM level (the editor flag, then the
level) and given in answer to the GetEditorLevel game message.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 19:34:43 -06:00
Aaron Kimbrell
3526ee2eb0 revert(world): the edit mode world
This reverts commit f6914940. The client's level editor builds a zone's objects
itself from its own files, so a world server that constructs none of them has no use.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 19:34:43 -06:00
Aaron Kimbrell
f6914940c1 feat(world): an edit mode world constructs none of the zone's objects
With -editmode 1 the world loads the zone file, terrain scene map, navmesh and
level revisions, but makes no level objects, carver blockers, spawners, spawner
networks, triggers, vanity objects or live event objects. The zone control
object stays with the zone's own template, as the client makes the same one
itself (LevelManager::LoadZoneControlObject, 0x01082660); its server script
does nothing and a race track's does not take the player into a race. Such a
world lets in only accounts at editor_gm_level.

Plumbing only: no command starts an edit mode instance yet (docs/EditMode.md).

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 17:34:13 -06:00
Aaron Kimbrell
78b2c0f406 feat(character): the save's edit attribute is 1 for accounts at editor_gm_level
The client takes a non-zero edit as the editor being enabled. A character whose
account GM level is at least editor_gm_level (worldconfig.ini, default 9) is
saved with edit="1"; everyone else keeps 0. A civilian never gets it, whatever
the setting. The setting is in the dashboard's settings catalog.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 17:34:13 -06:00
Aaron Kimbrell
98edeb0c72 Merge exp/platforms-3: moving platforms rounds 3-4
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 17:11:47 -06:00
Aaron Kimbrell
beed2651e5 feat(scripts): L_SIMPLE_MOVER_SWITCH, the platforms of the NJ attic switches
The script (with L_BASE_SWITCH_ACTIVATED_SERVER) asks the switches in the
platform's switchGroup to tell it when they are activated and
deactivated, and sends the platform to waypoint 1 on the one and back to
waypoint 0 on the other. The NJ monastery's ice and lightning attic
platforms (LOTs 14539 and 14376) run it.

A switch can now be asked for that (SwitchComponent::
AddActivationCallback). Where live's switch tells its group that it has
loaded, the platform here looks for its switches again each second until
it has found one.

The script was on the list of scripts left out, with a note that its
platform does not exist; it is taken off the list.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:57:15 -06:00
Aaron Kimbrell
42d02fc619 fix(moving platform): a rotator reads its angles and rate whatever type the level gave them
LWOPlatformRotator::LoadConfigData (0x00c33e10) reads rotX, rotY, rotZ and
rate as floats from the config; a value the level stored as another
number type read as 0 here.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:57:15 -06:00
Aaron Kimbrell
8a238ab1d0 fix(shooting gallery): a target at the end of its path respawns and despawns, without a score
SG_TARGET.lua, the targets' server script: at the last waypoint of its
path (onArrived with isLastPoint, which the client's PushToLua
0x00b1a910 sets for a moving platform's Arrived as for a waypoint
follower's) a target notifies the cannon, FinishedPath, and sg_cannon.lua
spawns the next one as after a hit (checkSpawn). A target set to despawn
there then dies silently after 1 s, a moving platform after 2 s.

Here ships and subs sat at the end of their path until the game ended,
and nothing came after them. The script cannot load (the targets have a
script row of their own), so the cannon does it:
- the respawn is split out of RegisterHit (CheckSpawn), and the die
  callback only registers a hit when the death is not the silent one;
- a moving platform target tells through PlatformAtLastWaypoint, a
  waypoint follower through a new MovementAIComponent waypoint callback.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:50:32 -06:00
Aaron Kimbrell
22c3168587 fix(moving platform): a simple mover without a quick build sets off when it is loaded
A simple mover is a LWOPlatform like a mover: made stopped at its
waypoint (12) and, unless startPathingOnLoad is false, waiting there (9)
for the shortest wait before it leaves for its other end
(ResumeTravelling 0x00c86b90, SetWaitingTime 0x00bdc860, RunPlatform
0x00c86e10). Live showed it on the NJ counterweights, which wait for
their quick build first; one without a quick build was left waiting for
a script here.

The client only reads startPathingOnLoad (LWOMovingPlatformComponent::
LoadConfigData 0x00d0f690) and never starts a platform by itself, so the
rule is the server's, taken from what live did with movers (12 with the
setting false, else 9 and off) and from the scripts: L_NJ_SPINNER_SERVER
stops its platform when it starts up, simple movers included, and the
simple movers that wait for an event without that script all have
startPathingOnLoad 0 in the maps.

The wait now runs in the component for both, in place of the timer a
built counterweight got, and a simple mover with startPathingOnLoad
false is constructed stopped (12).

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:49:24 -06:00
Aaron Kimbrell
c5bd9bbe52 Merge exp/seamless-transfer-2: seamless instance moves keep the player in place
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:49:22 -06:00
Aaron Kimbrell
65d0151ba8 Merge exp/component-audit-2: component audit rounds 2-4
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:49:08 -06:00
Aaron Kimbrell
4e8e0e64ef fix(moving platform): a mover keeps its velocity through a wait between the ends, as live
Live serialized a mover leaving a waypoint not where the waypoint is but
past it: the GF shooting gallery subs 2 units on (speed 15), the FV
dragons 5.33 (speed 40), each along the stretch they came by, and a
dragon met 2.2 s into its 5 s wait was 88 units past its waypoint (1.219
of the next stretch). At either end of a path they left from the
waypoint itself.

It is the client's LWOPlatform at work, which the server ran too:
- SetWaitingTime (0x00bdc860) takes the object's velocities away only
  when the platform has stopped or is at an end of its path, so through a
  wait in between the object goes on as it came;
- the percent is how far the object is from the waypoint
  (CalculatePercentTravelledToWaypoint 0x00be9050);
- UpdateVelocities (0x00bd4330) on leaving turns it the next stretch's
  way from where it is, and it arrives when it passes the next waypoint
  (CloseToNextWaypoint 0x00bd4140), where ProcessStateChange (0x00cce380)
  puts it at the waypoint;
- a platform that has not stopped looks whether it arrived while it waits
  too (HandleStoppedLogic 0x00bd16a0): the dragons were at waypoint 5
  without having left 4.

The server's mover now moves by its velocity in the same way, so the
state it sends on leaving carries the place and percent live sent.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:46:31 -06:00
Aaron Kimbrell
2ed70710cf feat(moving platform): a rotator turns its object as the client's does
LWOPlatformRotator on a path does not move: while it travels it turns at
the angular velocity SetAngularVelocity (0x00bfa380) works out, never
waits (IncrementWaitingTime 0x004a0390) and never arrives
(HandleStoppedLogic 0x00bdc660). The velocity is the one that turns from
no rotation to NiMatrix3::FromEulerAnglesXYZ (0x0116c3a0) of rotX, rotY
and rotZ (half turns in the level config, LoadConfigData 0x00c33e10) in
half the rate, as hkpKeyFrameUtility's
setUsingPositionOrientationPair (0x012a4050) gives it: the shortest
way, the axis times the angle over the time, none for whole turns. In
reverse it is the other way round.

The server's object now turns with it, without a serialization of its
physics (each client turns its own once the rotator is sent travelling),
and is put at its waypoint as SetupPath does. A quick build's rotator
waits for the build like a mover.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:45:01 -06:00
Aaron Kimbrell
b3606a0e02 fix: adapt the trade's model check to the moderation status sent for a blueprint
The blueprint data a world server sends for a stored brick built model
says that the model is approved, and the client goes by that over the
status the item itself carries. A model item that names a blueprint is
taken into a trade for that reason, and the item's own status only
decides for a model without one.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
ba0c5756ad fix: a trade offer takes no undeletable item and no unapproved model
The client's trade window also refuses an item that can not be
deleted and a brick built model whose moderation status is not
approved. An offer with such an item is now refused like the other
offers the window can not make.

Whether an item can be deleted is what the inventory already works
out for a delete: not the item of the mount the player rides, and not
an item its row of the DeletionRestrictions table restricts.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
d5723696f4 fix: adapt the transfers of a match, a rescue and a property reload to a failed transfer
The chat server passes the master server's answer for a match on to
the world servers as it is, and a world server tells each player of
the match that the map change failed. A player moved from the
dashboard and the players of a property that loads again are told the
same, and their characters stay in the zone they are in.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
ebcfd54915 fix: tell the player when the zone they go to gets no instance
The master server dropped a transfer request it started no instance
for, so the world server never heard back and the player waited. It
now answers the request with an empty address and the reason in place
of the port: no room for another instance (max_instances), a zone that
is not in the ZoneTable, or another reason. A request for a private
zone that does not exist is answered the same way.

The world server passes this on as a TRANSFER_TO_WORLD without an
address, which the client shows as a failed map change, and leaves the
character where it is. The auth server fails the login when there is
no character select to send the player to.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
24294561ae fix: a changed trade offer takes both accepts back
A player could change their offer after the other player gave the final
accept, and the trade completed with the new offer on their own accept.
Both clients drop their accepts when an offer changes, the one that
changed it and the one that receives the update, so the trade now does
the same and both players have to accept the offers as they are.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
6f7fd5f84e fix: a trade offer takes what the trade window takes
The client's trade window has 8 slots for each player and takes neither
a bound item nor an equipped one. An offer with more than 8 items, a
bound item or an equipped item is now refused like the other offers the
window can not make, when it arrives and again when the trade
completes.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
b3f5cd74df fix: a vendor sells up to 999 of an item at once
The limit was 1000. The client's buy dialog counts up to 999 for an
item from a vendor, whatever the item's stack size is, so that is the
most a purchase can ask for.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
66e6137a9f fix: a launchpad sends its rockets to its own zone
A zone transfer from a launchpad took the zone from the message when
the message asked for that. The client never does, so the zone is now
always the launchpad's default zone or the zone the player picked on
it. The log line no longer uses the sender named in the message.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
718bd78e8c fix: check for the player before handling the messages a player sends
Several messages used the player, the character or the user they named
without checking that there is one. A match request and a trade request
are now only handled for the player that sent them, a linked mission
request and a build mode change only for the sending player, and the
remaining handlers return when the player, character or user is
missing.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
0f1bce32fb fix: only the owner of a property changes it
The messages that set a property's privacy option and name, start and
finish building, place, move and remove models and change a model's
behaviors were handled for any player on the property, and outside of
a property they used a property that is not there. They are now only
handled for the owner of the property the player is on. Only an item
from a model inventory is placed as a model.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
081e26ded8 fix: a player's own messages have to be sent for that player
The die request, the smash request, the loaded notice, chat commands
and the racing pickup notice were handled for whichever object they
named. They are now only handled for the player that sent them, or for
the vehicle that player drives where the client sends them for it. A
racing pickup also has to be an object of a race track that is not a
player, a vehicle or the zone's own object.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
f170b46a5e fix: check the player's score before a level up
A level up was applied every time the client reported one. The next
level now has to exist, the player needs the score that level asks
for, and the message has to come from the player it is for.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
1056ca0025 fix: a character's mount save is written as 0, as live did
All 450 live saves looked at had <mnt a="0"/>. The server wrote the item
of the mount being ridden, a value live never showed and the client
would start with although nobody rides after a load.

<mnt a="0"/> is now always written, riding or not. A save that holds
another value, or one that is not a number, is still read without
failing and gets 0 at the next save.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:13:42 -06:00
Aaron Kimbrell
9ffed42772 fix: limit the count of a vendor purchase and work its price out in 64 bits
A purchase took any count, and the coin, alternate currency and
commendation prices were worked out as 32 bit numbers that wrap for a
large count. A purchase is now for 1 to 1000 items and the prices are
64 bit numbers.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:04:52 -06:00
Aaron Kimbrell
2d24608186 fix: pay a vendor sale by the items that left the inventory
Selling paid for the count in the message while the move stopped at
what the stack held. The count now has to be between 1 and the stack's
count, the item can not be one the vendor already holds for buy back,
and the coins and alternate currency are worked out from the number of
items that left the inventory.

Buying back needs an item in the buy back inventory and a count the
stack holds. Both prices are worked out as 64 bit numbers.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:04:30 -06:00
Aaron Kimbrell
fe4a1e5e98 fix: check the mission state before completing it from a dialogue
MissionDialogueOK accepted or completed the mission for whichever
object the message named as the responder. The responder now has to be
the player that sent the message, the object the dialogue belongs to
has to give or take the mission (on its own or through a random
mission's pool), a mission is only accepted when the player can accept
it and only completed when it is ready to complete. The object's
script runs after these checks.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:04:01 -06:00
Aaron Kimbrell
c0b7a8f643 fix: check a trade offer against the items the player holds
A trade used the LOT and the count from the offer as the player sent
it. The offer is now resolved when it arrives and again when the trade
completes: the LOT comes from the held item, the count stops at the
held count, and an offer that names an unknown item, the same item
twice or a count of 0 is refused. The other player only receives an
item that was taken from the offering player.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:03:21 -06:00
Aaron Kimbrell
9b386caf1e fix: a launch pad that does not use its table row is ruled by its level
LWORocketLaunchComponentCommon::LoadConfigData (0x00c55150) reads the
level's targetZone, targetScene, GMLevel, propertyDefaultTargetZone,
rocketLaunchPreCondition, rocketUseLaunchPrecondition and the alternative
landing keys, and the table row replaces them only while
rocketLaunchUseDBDefaults is true, its default (0x00c12ea0). The server
used the row for every pad.

31 pads in the 1.10.64 maps turn the key off. Their zones and scenes are
their rows'; what only the level has are the launch preconditions in use
on the Venture Explorer's four pads ("237;8;127|128;6|55;107;282") and
on Avant Gardens' pads to Nimbus Station, its two properties and the
Venture Explorer. Those are now checked before a launch, as the row's are.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 15:55:07 -06:00
Aaron Kimbrell
02623be7fb feat: save the mount item as live did
Every live save had <mnt a="..."/>, which
LWOMountControlComponent::LoadFromSaveData (0x00bf99c0) reads as the
character's mount item (0 on the 450 saves looked at). The server wrote
no such element.

MountControlComponent now writes it: the item of the mount being ridden,
0 on foot. The saved value is read and kept; nothing is mounted from it,
as riding does not go on over a world change. A save from before gains
<mnt a="0"/> and nothing else in it changes.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 15:54:54 -06:00
Aaron Kimbrell
c5c90f73a2 fix: write what the client reads for a chest, a choice build and a roller
The three components were made from the registry and wrote nothing, so
the client read the next component's bits as theirs.

- LWOChestComponent::Deserialize (0x00c22010), construction only: the
  player (u64) and the loot offer count (u32).
- LWOChoiceBuildComponent::Deserialize (0x00bf26d0): a flag, then the
  state (u32, 0 waiting, 1 building), the built object and the user
  (u64 each). Always in a construction.
- LWORollerComponent::Deserialize (0x00c15920), construction: the level's
  activity_camera_pos and activity_camera_lookat_pos (3 floats each) and
  the projectile skill (u32, the client starts at 1348); then the flag of
  the block about its user, which is never set.

LWOGameObject::Deserialize (0x00f99690) reads the components in the order
of the object's component list, which ObjectLoader2::DoObjectComponentLoad
(0x0105ebb0) fills in the order of FillComponentOrder (0x0101f8e0): the
roller after the shooting gallery and before the moving platform, the
choice build after the moving platform and before the LUP exhibit and the
model, the chest after the zone control and before render. They are
placed against the components their objects have.

The gate rush control (103) is left out: its two registry entries have no
object, and there is no component for it.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 15:54:54 -06:00
Aaron Kimbrell
b2fba9d35b feat: save play time as live did: time before the session, stime in it
Live kept the total before the running session in <char time> and the
session's seconds in <char stime>; both changed at a login, together with
llog (time 262486 and stime 7344 became time 269830 at the next login).
The client reads both (LWOCharacterComponent::LoadFromSaveData 0x00ca4910)
and shows their sum (SendMessage 0x00d34330, GetTimePlayed). The server
wrote everything into time and no stime.

Now time + stime is read (a missing stime is 0, so a save from before
keeps its total) and time and stime are written as live had them. A
session starts when the character is picked on the character list:
charinfo.last_login is loaded with the character, a save whose llog is
another login has its stime moved into time, and llog is written as the
session's login. Without a known login the session goes on.

GetTotalTimePlayed is still every second played. The dashboard's save
check knows stime as a number.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 15:54:54 -06:00
Aaron Kimbrell
baf8b24ad1 fix: a property is rented only with the level's required mission
LWOPropertyVendorComponent::onCheckUseRequirements (0x00c2e210) lets a
player use the property guard only when the level's
property_required_mission (LoadConfigData 0x00c2e550) is finished. The
server opened the vendor and rented the property on any request.

The guard is now not opened without the mission, and a rent request
without it is answered with PropertyRentalResponse code 9, which
onPropertyRentalResponse (0x00c56d70) shows as "you failed to rent" with
UGG_PROPRENT_Dont_have_achievement. A mission that was finished and is
being repeated counts, as in the client's list of finished missions. A
guard without the key asks for nothing.

The six property maps name missions 768, 872, 873, 874, 1293 and 1322.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 15:28:18 -06:00
Aaron Kimbrell
1113775741 fix: a seamless instance move keeps the player in scope, in place and without the white fade
The source no longer destroys the player's own ghost: losing it hid the character, and the target's construction
brought it back with the login effect (LWOGhostComponent::OnGhostReceiveDestruction, 0x00c99410,
LWOBaseRenderComponent::OnEnterScope, 0x00be2150). The target constructs the player to its own client without a
position and with world transition state 3. The client sends PlayerLoaded itself after that construction, so the
server only runs it when none came within 5 s, and PlayerReady (camera pitch reset) is not sent. A seamless arrival is
only expected for 30 s, so a later login gets LOAD_STATIC_ZONE.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 15:15:16 -06:00
Aaron Kimbrell
f081dafb94 feat: a player's construction can leave out its position and world transition
For the one construction a client gets of its own player after a seamless instance move. The client applies a
construction's frame to its player at once (LWOBasePhysComponent::Deserialize, 0x00bf5a10), and world transition
state 0 makes it fade the screen in from white (LWOCharacterComponent::PostCharacterLoaded, 0x00c71660). State 3 is
read but nothing acts on it. Nothing sets either flag yet.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 15:15:16 -06:00
Aaron Kimbrell
7695445c4c feat: taking every object from one client can leave one out
DestructAllEntities takes an object to skip, for the seamless instance move that keeps the player's own ghost.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 15:15:16 -06:00
Aaron Kimbrell
53968599d2 fix: a launch pad checks its GM level and launch precondition
LWORocketLaunchComponentCommon does not launch a player under the row's
gmLevel (msgFireEventClientSide 0x00cb78b0) or, with useLaunchPrecondition,
one that fails the row's launchPrecondition (msgCheckUseRequirements
0x00c7d0d0). The server launched on any request. Rows: the test launcher
asks for GM level 9, the pads to Gnarled Forest and Forbidden Valley for
mission 474, Starbase 3001's for mission 956.

The alternative landing precondition pointer also starts out null for a
pad without a row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 15:14:44 -06:00
Aaron Kimbrell
38fe226e6f fix: a proximity monitor row lists proximity types, not radii
LWOProximityMonitorComponent::LoadDataFromTemplate (0x00c2dfb0) reads the
row's Proximities as ids of ProximityTypes rows, and SendMessage
(0x00cbb930) makes, on AddedToWorld, a proximity under the type's name and
radius (30 when missing) for each type loaded on its side.

The ids were read as two radii: every launch pad ("7,8", the client's
RocketSign and WorldLauncher types) got a rocketSmall sphere of 7 and a
rocketLarge one of 8 that nothing used. Now the row's and the type's
LoadOnServer decide; of the 40 rows only a test object's has one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 15:14:44 -06:00
Aaron Kimbrell
b033655e90 fix: a rail that is off or whose preconditions fail is not ridden
LWORailActivatorComponent::msgCheckUseRequirements (0x00c2c5b0) refuses a
use, after the quick build check, when the row's preconditions fail or
rail_activator_active is off, so a client does not ask. The server started
the rail on any use request. The rows ask for an equipped item (44) or a
completed mission (283 to 286).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 15:14:44 -06:00
Aaron Kimbrell
dc5f13cdbc feat: write the character attributes live saved and the client reads
LWOCharacterComponent::LoadFromSaveData (0x00ca4910) reads them, and live
saves had every one (450 of 450 characters saved at least once):
- cm, the most coins a character can hold: 9223372036854775807 on every
  live save, the client's own value without it. Written when missing, an
  existing one is kept, and nothing caps the coins by it.
- edit (editor level), gid and gn (guild id and name).
- mldt (when the property moderation notice was last shown): 0 when
  missing, as most live saves.
- v and vd: the votes left and the day they are for (GetVotesLeft /
  AdjustVotesLeft). Read back; a new day fills them up to the 3 of
  WorldConfig's character_votes_per_day.
- lrx.. lrrw: the last respawn point reached, once one was reached in the
  world.
- lzcs: the checksum of the zone in lzid.

Saves without them load as before. stime is not written: live kept the
session's seconds in it and the time before the session in time, which
would change what time means in existing saves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:50:43 -06:00
Aaron Kimbrell
95e0746441 fix: a worn jetpack works again after a world change
Jetpack mode is not saved, and the equip skill that turns it on was cast
only when the item was put on, so a player still wearing a jetpack lost
it in the next world. Live sent SetJetPackMode (use, bypass checks, effect
167, the values of the jetpack's equip skill) shortly after the player's
own construction in 12 of 28 world loads of a player with a jetpack, and
constructed the player in jetpack mode in the other 16.

The jetpack behaviors among the equip skills of the equipped items are
run again when the player has loaded. Nothing new is saved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:50:33 -06:00
Aaron Kimbrell
5ac39dca8b fix: an activity's construction always carries its players
LWOActivityComponent::Deserialize (0x00d002b0) reads a flag, the player
count and each player with its 10 values; a construction is the only way
a client coming into range learns who is in the activity. The block was
written only while it was waiting for the next update. Live finish line
constructions (LOT 4967) had the flag and the count in 40 of 40, one of
them with a player in it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:50:33 -06:00
Aaron Kimbrell
9265ab0e10 fix: run speed modifiers stack as the client's do
LWOControllablePhysComponent::msgAddRunspeedModifier (0x00be0af0) and
msgRemoveRunSpeedModifier (0x00be0720): a modifier over 1000 or of exactly
500 counts for nothing, any slow (under 500) wins with the lowest one, and
otherwise the highest boost is the speed. Before, the last one applied won,
so a small boost cast after a big one, or a boost cast while slowed,
replaced it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:50:33 -06:00
Aaron Kimbrell
e786d2b1c5 fix: constructions keep pending health, quick build and equipment changes
The destroyable's health and threat blocks, the quick build's state and
the inventory's equipped items are each read behind a flag bit
(LWODestroyableComponent::Deserialize 0x00d2bbd0 and the others). DLU
cleared their dirty flags when it wrote a construction, which goes only
to the client the object came into range of, so a change waiting for
the next update never reached the clients already watching: a health
bar, a quick build's state, a player's new gear.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:26:46 -06:00
Aaron Kimbrell
d3f5c20323 fix: a LUP exhibit's construction always carries the exhibit
LWOLUPExhibitComponent::Deserialize (0x00c63790) reads a flag and the
exhibit's LOT, and that is the only way the client learns which exhibit
is shown. DLU wrote the block on a construction only while it was dirty,
so a player who came into range after the first update saw no exhibit
until the next one was picked, up to 20 seconds later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:26:46 -06:00
Aaron Kimbrell
1382606bd1 fix: constructions keep pending changes of five more components
The level, forced movement, possessable, possessor and combat AI parts
are each read behind a flag bit (LWOLevelProgressionComponent::
Deserialize 0x00c63bd0 and the others). DLU cleared their dirty flags
when it wrote a construction, which goes only to the client the object
came into range of, so a change waiting for the next update never
reached the clients already watching: a level up, a mount or dismount,
a rail ride.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:26:46 -06:00
Aaron Kimbrell
7893185cad fix: write a trigger object's trigger component as live did
The client makes the trigger component of an object whose header sets
the trigger bit after all the others, and reads it last:
LWOTriggerComponent::Deserialize (0x00bd78c0) takes a flag bit and a
32-bit pick type. Every live construction of a trigger object ended with
the flag and -1 (4,533 of 4,533); DLU wrote nothing, which the client
read as no flag. Nothing changes for the player: -1 is the client's
default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:26:46 -06:00