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>
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 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
LWOPhantomPhysicsComponent::Deserialize (0x00c34db0) reads the effect
amount as a 32-bit float, and trigger commands are run by the server only:
the client has no parser of its own for "Friction,0.1". The amount stays
a float. No live packet of one of the 18 volumes with 0.1 was found, so
what live sent for them is not known.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>
LWOCollectibleComponent::Deserialize (0x00c45ec0) reads the 16-bit id
on construction and nothing on an update. DLU wrote the id on every
serialization, so the client read 16 stray bits as the flag bits of the
components after it. The replica decoder follows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOPhantomPhysicsComponent::Deserialize (0x00c34db0) reads the effect
behind a flag bit. DLU cleared the dirty flag when it wrote a
construction, which goes only to the client the volume came into range
of, so the clients already watching it never got an effect change that
was waiting for the next update.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A trigger's SetPhysicsVolumeEffect is "type,amount,x,y,z,use distance,
min,max". In live phantom physics constructions:
- the direction has the amount as its length: "Push,50,0.91,-0.34,-0.22"
went out as 45.68, -17.07, -11.04. DLU multiplied the arguments as
they were, so a direction that isn't a unit vector pushed harder;
- a zero direction ("Repulse,150,0,0,0,true,25,100") is no direction;
- the distances go out when use distance is "true" ("true,150,175" as 1
and 175, "true,20,50" as 1 and 50) and not when it is "1"
("Push,40.0,0,1,0,1,20,50"). DLU never wrote them.
LWOPhantomPhysicsComponent::Deserialize (0x00c34db0) reads the two
distances as floats and keeps the least of the min and 1, and a max of at
least that.
Seven arguments no longer read an eighth.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOSimplePhysicsComponent::Deserialize (0x00be4b00) reads the velocity
and the motion state each behind its own flag bit. DLU cleared both dirty
flags when it wrote a construction, which goes only to the client the
object came into range of, so the clients already watching it never got
a change that was waiting for the next update.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOControllablePhysComponent::Deserialize (0x00c45770) reads the jetpack
mode, effect, flying and bypass checks bits on construction, and
LWOControllablePhysComponent::SendMessage (0x00d2f020) keeps the same
from SetJetPackMode. DLU sent the message but never kept it, so a player
who came into range of someone flying was told they had no jetpack.
Live player constructions in jetpack mode carried effect 167, flying 0
and bypass checks 1, and a 0 after the pickup radius (the client reads
that bit into the flying flag). DLU wrote the jetpack mode there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOCharacterComponent::Deserialize (0x00ca3a40) reads the GM and PVP
block and the current activity each behind its own flag bit. DLU never
cleared the two dirty flags, so after the first quick build or pet taming
every later serialization of the player carried both blocks again. They
are cleared after an update now, as the guild block's is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only a rotater on a path runs (LWOMovingPlatformComponent::SetupPlatform 0x00ccea50), and the client reads a platform that is not at a waypoint from its path's waypoints (LWOPlatform::Deserialize 0x00c870e0), which a rotater without a path does not have. It was sent travelling (2) whenever startPathingOnLoad was not false. No object in the client's maps is a rotater.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live (the Frakjaw battle captures, LOT 16141): when its quick build completes the simple mover is serialized waiting at waypoint 0 (9) and about 0.1 s later travelling (2); it stops at its end without a serialization. That is the client's LWOPlatform, active once the quick build is complete (SendMessage 0x00bdc930), waiting the shortest wait and setting off (RunPlatform 0x00c86e10). L_CAVE_PRISON_CAGE and L_MONASTERY_BOSS_INSTANCE_SERVER never send it: they ask to be told of its PlatformAtLastWaypoint, and act then.
So the simple mover sets off when it is built, the cave prison's button goes down when the counterweight arrives instead of when it is built, and Frakjaw is hit when it arrives (0.1 s and the row's 1 s after the build) instead of on a 0.5 s timer with a counterweight that never moved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOMovingPlatformComponent::SetCurrentPath (0x00d0f570, the SetCurrentPath game message of 0x00d0f5d0): the path, its starting waypoint and direction, then the subcomponents are made anew (SetupPlatform 0x00ccea50) and the mover takes the path at that waypoint. The path is written to the clients once more, whose component sets it the same way (Deserialize 0x00c88000), and the reverse flag of the path is no longer always written false.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOPlatform moves its object with SetPosition and SetRotation ignoring the dirty flags (ProcessStateChange 0x00cce380), turned between the two waypoints' rotations, and live serialized a moving platform's mover data without its physics. The server's object stayed where it spawned, so anything that asks where a platform is (the lightning orbs' knockback of issue 1352, phantoms riding a platform) had the start of its path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The state bits were named the wrong way round: 1 is waiting and 4 is stopped (LWOPlatform::StartTravelling 0x0058b170, StopTravelling 0x0058b1d0), and the client only stops on 4 (LWOPlatform::Deserialize 0x00c870e0), so a mover stopped on its way kept moving on the client. The mover now runs as LWOPlatform::RunPlatform 0x00c86e10, ArrivedAtWaypoint 0x00c86c30, HandleWaypointArrived 0x00c17bc0 and ResumeTravelling 0x00c86b90, which gives the states of the live captures: 12 until started, 9 waiting at a waypoint (25 at an end), 2 on its way, 28 stopped at an end of a path run once. A GoToWaypoint at a waypoint waits there first with the desired waypoint, as the NJ falling tiles did, and a mover that stops is not serialized for it. A quick build's mover does not run until it is built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LWOCharacterComponent::SendMessage (0x00d34330), IsValidPVPTarget: the
target is never the player itself. LWODestroyableComponent's IsEnemy asks
it for every character. DLU called a player with PVP on an enemy of
itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Zone::LoadZoneIntoMemory takes the paths the zone file reader read (lost in the merge of the raw terrain loading)
- the terrain reader is main's unchanged: files from before version 32 keep their colour and texture maps as the file
has them, and the dashboard's terrain tests expect that (bounds by the span between the first and last point too)
- the model component test counts the model block only: the item block in front of it is the ItemComponent's
- scene file tests: a file from before chunks keeps only its header, editor settings are a count of colours, and the
tests that log have a logger
- the settings catalog lists export_terrain_to_obj
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>
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_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>