Commit Graph

2860 Commits

Author SHA1 Message Date
Aaron Kimbrell
b6636de0d9 fix(triggers): the spawner network deactivate command as the game names it
The shipped trigger files name the command DectivateSpawnerNetwork. The server
now reads that name as the deactivate spawner network command, so those triggers
run, and the edited file checks take it. The correctly spelt name stays as an alias.
The shipped files test no longer lets unknown trigger ids through for the
retail clients.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-10 20:09:06 -06:00
Aaron Kimbrell
99b9e580e7 doc: edited zone files and the level editor in the README and docs/ZonePublish.md
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 19:34:43 -06:00
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
e50a2255f8 feat(zone): zone files as the game client's level editor packs them
ZoneFilePack reads and writes the editor's packed files: named entries whose data is
sd0, one entry for a single file or the zone, terrain, scenes and triggers of a whole
map. Every length is checked, data that is not sd0 or has a chunk over 256 KiB is
refused, and only a name's base name is used. ZoneFilePush makes a set of what was
sent (one file replaces that file in the zone's set), checks it and applies it like
any other set.

The override store links a set's files instead of moving them, so a file the zone
already has can be carried into its next set, and one change at a time is made in an
override folder whichever server makes it. The asset manager can read the client's
own file past an override.

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
ca7d166c0b chore(net): the packet field lists have the world reload hold and the reload counts
Regenerated with tools/gen_game_message_fields.py.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 19:34:43 -06:00
Aaron Kimbrell
11a70ad870 fix(master): a cut password skips what it cut, and a starting private instance answers once ready
A capped string now skips the characters it cut, so what follows it is read from the
right place. A request to join a private instance that is still starting is answered
once its world server is ready, instead of at once with an address nothing listens
on yet.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 19:34:43 -06:00
Aaron Kimbrell
37cc844562 revert(master): the edit mode flag on a private instance
This reverts commit 4cf0909d. Nothing starts an edit mode instance any more. Its two
fixes that had nothing to do with edit mode come back in the next commit.

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
a4c4c24a5c feat(dashboard): the zone file API, with zone edit locks
/api/v1/zones/{zoneId}: send a zone's edited files (multipart), follow the reload,
list, restore and clear the zone's sets, and take, release and look at its lock.
Every request needs an API key that names the zones_publish permission, an owner at
zone_publish_gm_level, and HTTPS or the server's own machine; refusals are counted
per address and every use is in the audit log. A publish is checked at its headers
before any of its body is taken, the files are checked and written on a worker
thread, and the zone's instances are replaced through the world reload, held while
players are there unless told to move them.

The Worlds page lists each zone's editors and lock holder for an admin.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 19:34:43 -06:00
Aaron Kimbrell
69476e6546 feat(web): upload routes write large bodies to a file
A route registered as an upload route has its headers run through the middleware
first, so a request that isn't allowed is answered before any of its body is taken.
The body then goes to a file as it arrives, with a size limit, instead of into
memory. MultipartFile splits a multipart/form-data body that is in a file into its
parts without loading it.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 19:11:37 -06:00
Aaron Kimbrell
e714ff2f0a feat(master): the world file watch follows the override folder, and can hold a zone
For a file under maps/ master watches the file a world would read now: the override
when one is there, else the client's. An override that appears, changes or goes away
makes the instances that read another version stale, also for files from the packs.
The dashboard can hold a zone's automatic reloads (WORLD_RELOAD_HOLD, a new message
after the existing ones) until a reload of the zone is asked for. The status sent to
the dashboard counts what reloads did per zone.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 19:11:37 -06:00
Aaron Kimbrell
d124372973 feat(config): zone_override_dir and the limits of edited zone files
World, master and chat servers read files under maps/ through the override folder
named by zone_override_dir (empty: off). The settings for who may send zone files
and how large a set may be are in sharedconfig.ini and the settings catalog.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 19:11:37 -06:00
Aaron Kimbrell
f34577b5a2 feat(zone): checking, keeping and locking a zone's edited files
Three pieces that know nothing of how a set of files arrives:
- ZonePublish reads every file of a set with the server's own readers and says, file
  by file, what is wrong; a set is good only when every file is.
- ZoneOverrideStore keeps a good set in the override folder: built in a staging
  folder, flushed, swapped in with a rename, the set before it kept as a backup.
  Restoring a backup and clearing a zone's set are the same swap.
- ZoneEditLock has the rules for a zone's editors and its one lock holder, and for
  holding a reload while players are in the zone.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 19:11:37 -06:00
Aaron Kimbrell
0e57b95229 feat(db): zone edit locks and zone editors
Tables for the one account holding a zone's lock and for each zone's assigned
editors, on MySQL and SQLite, with the interface and the test database.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 18:22:03 -06:00
Aaron Kimbrell
3072715dc3 feat(assets): an override folder for files under maps/
The asset manager reads a name under maps/ from the override folder when the file is
there, and from the client otherwise. Nothing else is looked for in it and the
client's files are never written. Off until a folder is set.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 18:22:03 -06:00
Aaron Kimbrell
f70433c430 fix(zone): lengths and counts in zone files are checked against what is left of the file
A string length, the path chunk's length and an object's render technique count are
compared with the bytes left before memory is set aside for them. The terrain reader
is built into dCommon, so every server that reads zone files has it.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 17:41:01 -06:00
Aaron Kimbrell
4394497198 doc: README: moving platforms rounds 3-4 merged, the edit attribute and the edit mode plumbing
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 17:34:13 -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
4cf0909d52 feat(master): an edit mode flag on a private instance, down to its world server
CreatePrivateZone carries editMode as one more byte, written only when set, so
a plain private zone is sent as before. Master keeps it on the instance, starts
the world server with -editmode 1, and gives a private instance's replacement
(a reload or a live update) the same flag. Nothing sets it yet.

A capped string now skips the characters it cut, so what follows it is read
from the right place. A request to join a private instance that is still
starting is answered once its world server is ready, instead of at once with
an address nothing listens on yet.

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
b9327fa2b6 doc: README: the component audit and the seamless instance move merged
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 17:01:44 -06:00
Aaron Kimbrell
4e5fb276d6 feat(scripts): L_FIRE_TRANS_ROCKS and its manager, the rocks of the NJ fire transition
L_FIRE_TRANS_ROCKS_MANAGER waits until the rocks of its RockGroup are all
there (NumberOfRocks, 3), sends them to waypoint 1 five seconds later,
and each time all of them have told it they are back down sends them up
again after a second. L_FIRE_TRANS_ROCKS holds a rock that arrived at
waypoint 1 for 3.5 s and sends it back to waypoint 0, where it tells the
manager (PlatformDown). Both act on ArrivedAtDesiredWaypoint and
GoToWaypoint, which the mover has.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:57:15 -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
423b55e0b8 feat(scripts): L_SIMPLE_ONCE_ONLY_MOVER_SPINNER_EVENT
The script of the platforms a spinner moves once: a simple mover (or a
mover on a two point path) in the spinner's group_deactivated_event
group goes to its waypoint 1 when the spinner fires the event named in
the platform's name_deactivated_event. The NJ spinner script already
fires those events at its groups.

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
ead394d991 fix: adapt the login packet tests to a session key that differs every time
The tests compared the login response and SET_SESSION_KEY of the old
hand written code with the structs byte for byte, with the same seed
for both. The key no longer comes from the seeded engine, so the tests
leave its bytes out, and the copy of the old code writes the key the
way main writes it now.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:48:56 -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
83a82355d0 fix: adapt the dashboard's settings to max_instances
The settings list every key of the shipped config files, so the master
server's limit on world instances gets its entry too.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
31743d9fa1 doc: README: the checks on what clients and servers send, merged from the fix branch
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -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
e164f9aaed fix: the master server only starts instances of zones in the ZoneTable
A transfer or private zone request started a world server for whatever
map ID it named, and for as many instances as were asked for. A new
instance now needs a row in the ZoneTable for its zone, and no more
than max_instances (masterconfig.ini, 256 by default, 0 for no limit)
run at once. The character select has no row and is always started.

A request that gets no instance is logged and dropped. Before, the
handlers went on to use the missing instance.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
5f20e0b3d2 fix: the model migrations leave a model they can not read as it is
When the data of a model could not be inflated, the two later position
migrations went on with an empty model and wrote the result back. All
three now log the model and leave its row alone. The first migration's
log line also gets the model ID it formats.

The client's own sd0 reader has no limit on the chunks or on the
inflated size, so the limits of the reader here stay well above any
model: a chunk inflates to at most 256 KiB, and 64 MiB is 256 full
chunks.

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
5ff45d9a90 fix: adapt the session key packets and the packet decoder to the 33 character key
SetSessionKey, SessionKeyResponse and NewSessionAlert carry the key as
a 33 character string where the number was, which the auth, master and
world servers now pass. The packet decoder blanks that string in a
kept packet, and the packet tests write the string.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 16:28:34 -06:00
Aaron Kimbrell
26b188852f fix: session keys are 16 bytes sent as 32 hex characters
The key was a 32 bit number, sent to the client as the MD5 hash of its
decimal text. The client only stores the key and sends it back, so it
is now 16 bytes from the system entropy source written as 32 lowercase
hex characters, which fills the same 33 character field of the login
response.

The auth, master and world servers pass the key itself between them as
a 33 character string in place of the number: SET_SESSION_KEY,
SESSION_KEY_RESPONSE and NEW_SESSION_ALERT keep their IDs and carry the
string where the number was. The master server keeps one key for each
username, and a world server compares the key a client sends with the
one from the master server in constant time. A key stays valid until
the next login of its account replaces it, as before.

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