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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
The reader the message structs share made room for the length it read,
up to the message limit, before it found that the stream held less.
The length now has to fit in the data the stream has left first.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Several game messages read a 32 bit length and then read that many
characters without checking it, so the loop ran for the whole length
even after the message had ended. The length now has to fit in the
data the message has left. This covers the activity state, cinematic
path, death type, removed item info and inventory group messages and
the shared wide string reader.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
An sd0 buffer could hold any number of chunks, and each chunk grew the
inflated data by a full chunk whatever it held, so the data of a chunk
that was not full was followed by a gap. A buffer now holds at most
4096 chunks and inflates to at most 64 MiB, and every chunk is inflated
right after the one before it.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The AMF3 reader read an array inside an array to any depth and kept
reading values after the data had ended. It now refuses arrays nested
more than 64 deep and throws when the data ends before a value does.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
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>
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>
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>
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>
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>
A world server passed every routed packet on to the chat server under
whatever message ID it carried. It now passes on the messages a client
sends, and SHOW_ALL only for the level the showall command needs. The
message ID is read as the full 32 bit number, the packet has to be long
enough to hold a chat packet and the sender needs a character.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A world server connects to a chat server that was started again while
it runs. That connect now uses the password the servers share too, as
the chat server listens with it.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Only world servers connect to the chat server. It now starts as an
internal server, the same way the master server does, and the world
servers connect to it with the password they already use for the
master server.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The key came from the shared game random engine, which is seeded from
the start time. It now comes from std::random_device through a small
helper. The key stays a 32 bit number, so the packets do not change.
The master and world servers no longer write the key or its hash to
the log.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>