Live filled Die's direction_relative_angle_xz, _angle_y and _force on
11,606 of 19,428 deaths. The values are the dir_angle_xz, dir_angle_y
(degrees, sent as radians) and dir_force parameters of the BasicAttack
that dealt the killing blow: per killing skill the triple is constant
(skill 1210: -25 degrees, force 12 on 540 of 549 kills; skill 10:
force 7 on 1,830 of 1,835), and deaths with no killer (5,948) never
have one. BasicAttack is the only behavior with these parameters apart
from one Grab. The client passes them to PerformSmashableDeath, which
throws the smashable's pieces that way; DLU sent 0.
The radians are rounded the way live did (-25 is 0xbedf66f3, 20 is
0x3eb2b8c3). Not covered: use_caster_velocity (one behavior, racing).
Check: smash crates and enemies with different weapons; the pieces fly
off to the side/up per weapon instead of straight out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live sent client_death=false on every Die, players included (19,428 in
the live captures, 452 of them player deaths); DLU sent true for
players. The client only uses the flag to match a death it already
predicted locally (LWODestroyableComponent::msgDie): with it set, a
Die that arrives while a post-death respawn is pending is dropped.
Check: die to an enemy, to falling and to a quickbuild trap; the death
animation, coin loss and respawn prompt all still appear, and other
players see you die.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CollectibleComponent (79 rows: id, requirement_mission) is only in the
CDClient for the server: the 1.10.64 client has no string for it. DLU
never read it.
What the column holds (1.10.64 CDClient, joined with ComponentsRegistry,
Missions and MissionTasks): for 63 rows it is the mission the collectible
belongs to. Mostly that is the achievement whose collection task
(taskType 3) targets the collectible's LOT (the flags, imagination bricks,
Johnny Thunder collectibles). For a few it is a mission to accept from an
NPC that does not collect the item itself: the four Ninjago dragon relics
(16483-16485) are collected by the hidden achievements 2064-2067 but
their requirement is 2040 (accepted from LOT 13789, "complete 2064-2067").
Other values: -1 and 66666666 (no such mission) on test rows.
So a collectible whose requirement_mission is a mission to accept
(Missions.isMission) now only counts (HasBeenCollected progresses
nothing) while the player has that mission accepted and not handed in
(ACTIVE, READY_TO_COMPLETE or their repeat states). Achievements, missing
missions and rows without one are unchanged. Without this, a player who
had not accepted 2040 could collect the relics early and have 2040
complete as soon as it was accepted. The gate itself is inferred from the
data: the captures show collections but not the live server's check.
The collectible's object report shows the requirement mission.
Check in game: in Ninjago Monastery, before accepting the dragon relic
mission (2040), touch a dragon relic: it does not count; accept 2040 and
collect them: each counts and 2040 completes after the fourth. Flags,
imagination bricks and Johnny Thunder collectibles still count as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client reads <pet a> as its active pet's database ID with
GetInt64Attribute (LWOPetControlComponent::LoadFromSaveData 0x00c18120);
DLU never wrote it. Live wrote a="0" on every character (226 live
charxmls, 45 with pets): the charxml is only read when the player loads,
before any pet is out, and a pet summoned afterwards registers itself
(RegisterPetDBID). So a is written as 0.
The pet taming type p@t stays 0 as DLU already wrote it: it is an int
(IntAttribute), the client sets it to 0 for every pet it is given
(AddPetToPlayer in LWOPetControlComponent::HandleMessage 0x00d0fe00,
which ignores the message's elemental type) and every live pet had t="0".
Commented so it isn't "fixed" later.
Check in game: with a tamed pet, summon it, change zones and log out and
back in: the pet menu and pet naming work as before and no pet is shown
as out until you summon one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client reads the cooldowns still running from the charxml on load
(LWOSkillComponent::LoadFromSaveData 0x00c2a9c0): <skil sc="..."> split
on ';' and ':' (the delimiter string at 0x015b1714 is L";:") into a
cooldown group and the seconds left, each put in its cooldown map under
{hasCooldownGroup = 1, group}, the key it checks when the player casts a
skill of that group (0x00c11400). Live wrote <skil/> with nothing running
and e.g. <skil sc="17:15.6958;"/> otherwise (226 live charxmls; groups
8, 17 and 78 seen, the times below the groups' SkillBehavior cooldowns).
The claude-client-re-docs page described sc as "id;time" pairs; the
live saves and the delimiters show "group:time;".
DLU wrote no <skil>, so relogging or changing zones cleared every
cooldown (e.g. the 90 s imagination and faction skills).
- A player's successful cast (CastPlayerSkill) starts its
SkillBehavior.cooldowngroup's cooldown (groups 0 and up, cooldown > 0),
counted down in Update.
- Saved as live wrote it; loaded back when the player's SkillComponent
is created, so a cooldown survives several zone changes. Old saves
without <skil> load with none running.
Check in game: use a skill with a long cooldown (e.g. a faction kit's
special skill, a consumable with a cooldown), change zones or log out and
back in right away: its cooldown is still shown and counts down from
where it was (minus the loading time on the server).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client's rail activator sends RequestRailActivatorState (1479, no
payload) when it is added to the world (LWORailActivatorComponent::
SendMessage 0x00c00da0) and live answered with
NotifyRailActivatorStateChange (1478, bActive) to that client; the client
stores rail_activator_active and updates the rail's pick type (usable or
not). Live: 152 requests, 413 answers, every one bActive = true. DLU
dropped the request and never answered.
RailActivatorComponent keeps the level key rail_activator_active
(default true; every rail in the live levels sets it to 1) and the
request is answered with it.
Check in game: in Ninjago Monastery and Nimbus Station, rails (spinjitzu
posts, the Nexus Tower rails) show as usable and work as before when
first loading the world and after zone changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client sends SetTooltipFlag (469: bFlag, then the tooltip,
2 live packets, both tooltip 24) when a tooltip has been shown, and
follows it with SetFlag for the same ID. DLU dropped it and never wrote
char@ttip, so the client's tooltip bits reset on every load.
- SetTooltipFlag message struct and handler: the bit is set or cleared on
the CharacterComponent exactly as the client does it
(LWOCharacterComponent::SendMessage 0x00d34330): tooltips above 127 are
ignored, set = 1 << tooltip, clear = mask ~1 << tooltip (which also
clears the lower bits), shifts of 64 or more give 0.
- Saved as char@ttip, a 64-bit value (the client reads it with
GetLongLongValue in LoadFromSaveData 0x00ca4910). Live wrote it on
every character (226 live charxmls: "0" or "16777216" = tooltip 24).
Old saves without it load with 0.
Check in game: on a new character, trigger a first-time tooltip (e.g.
the one about an item or a new area), change zones and log out and back
in: the character loads normally and the same tooltip does not come back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client sends SetMissionTypeState (851) for the journal's mission
types and subtypes (Missions.defined_type / defined_subtype) at load and
after mission updates: 650 live packets, every one with the optional
state left at NEW, the subtype written before the type. DLU
dropped it and never wrote the states, so the client lost them on every
load.
- SetMissionTypeState message struct and handler: the state is stored on
the player's MissionComponent (byte-sized as the client keeps it,
LWOMissionComponent::msgSetMissionTypeState 0x00c90d00).
- Saved as live wrote it (226 live charxmls): after <cur>,
<ts><type v="Build"><st sub="" val="1"/></type>...</ts>, <ts/> when
empty. Read back per <type>; old saves without <ts> load with none.
The client's reader (0x00d171c0) reads v from <ts> rather than from
each <type>, so it files every state under one type; the server keeps
the types apart.
- Mission::SetMissionTypeState (on accept) records NEW for the mission's
type instead of doing nothing.
Check in game: accept missions of a few kinds (a location mission, a
battle achievement), open the passport/journal, then log out and back in
and change zones: the journal tabs keep their "new" markers as before,
nothing errors, and the character still loads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live, on every equip and unequip (captures: 22,422 ChangeObjectWorldState,
4,269 equip effects, 245 UncastSkill):
- ChangeObjectWorldState on the item to everyone: ATTACHED when equipped,
INVENTORY when unequipped, proxies (sub-items) included.
- The equip-<slot> / unequip-<slot> effect on the wearer to everyone
(effect -1, priority 1.07), the slot picked from the item's equip location
as the client's ItemComponent does (0x00cdafb0): special_l left,
special_r right, hair head, clavicle, chest, legs; none for others (e.g. a
rocket's Extra_1); the item's equipEffects with "equip"/"unequip" when it
has one; no equip effect for noEquipAnimation items, no unequip effect for
proxies.
- Order when an item replaces another: the new item's state and effect,
then the old item's, then the old item's proxies taken away
(RemoveItemFromInventory, UnEquipInventory with ignoreCooldown,
ChangeObjectWorldState INVENTORY, to the player), then UncastSkill for
the old item's equip skills (castOnType 1, not its proxies'), then its
skill removed and the new one added, then the new item's proxies.
- An equipped item taken out of the inventory is followed by
UnEquipInventory (ignoreCooldown) to the player.
DLU sent none of these (only the rocket's ChangeObjectWorldState, which
now comes from equipping it, before RocketEquipped as live).
Check in game (with a second player watching): equip and unequip a hat, a
sword, a shield, a shirt, pants and a backpack; both players see the equip
animation/effect. Put a ninja hood on, then another hat over it: the hood's
back piece disappears and its passive effects end (no lingering aura or
stat). Launch a rocket: it is still shown during the launch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StartRailMovement's bDamageImmune, bNoAggro and bShowNameBillboard came
only from the level keys rail_activator_damage_immune, rail_no_aggro and
rail_show_name_billboard, which 23 of the 80 level rails do not have, so
those rails sent false. The client reads the same flags from the
RailActivatorComponent row (DamageImmune, NoAggro, ShowNameBillboard; all
11 rows are 1) in LWOPlayerForcedMovementComponent::LoadRailData
(0x00c85dc0), and the message's values replace the row's unless bUseDB
(msgStartRailMovement 0x00ccd9c0). The server now takes the row's value
and lets a level key replace it when the key is there. Wire format is
unchanged; only the flag values sent for the 23 rails change (to true,
matching the 5 live samples).
In game: ride the 23 rails without the keys, all in the Ninjago earth gauntlet
(earthgauntlet levels: dart spinners, 8 spinners, millstones, boss) near enemies: you are not
damaged or targeted while riding, and name billboards behave as on the
other rails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A level object's smashable_loot_matrix replaces the DestructibleComponent
LootMatrixIndex when smashable_loot_matrix_set is true or absent and the
matrix is not -1, as the client resolves it
(LWODestroyableComponent::LoadConfigData 0x00c44cb0). DLU only read the
key in BaseInteractDropLootServer, so tagged smashables dropped their
template's loot. Resolution in DestroyableComponent::GetLevelLootMatrix,
with tests. No wire change.
In game: smash level smashables that carry smashable_loot_matrix_set=1
(11 level objects in the 1.10.64 levels) and ones with a matrix but no _set key; they drop the
level's loot instead of the template's. Ordinary smashables
(smashable_loot_matrix_set=0) drop what they did before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client (LWODestroyableComponent::LoadConfigData 0x00c44cb0) uses a
level object's set_faction only when override_faction is absent or true;
with override_faction=0 LoadDataFromTemplate (0x00c9f900) puts the
DestructibleComponent factionList back. 2948 level objects have
override_faction=0, and DLU applied their set_faction anyway. The
resolution is now DestroyableComponent::GetLevelFactions, with tests.
No wire change.
In game: enemies and smashables placed in levels with a set_faction but
override_faction=0 (most smashables, many enemies) are targeted and
aggro as before for the common case; check a few enemies in AG/GF/FV
still fight you and friendly objects are not attacked, and that
smashables still smash and give credit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Entering build mode pushes the equipped items (PushEquippedItems); the
thinking hat and a model picked up off the property are then equipped
until PopEquippedItems puts the old equipment back. A character save in
between (the world's periodic save, a disconnect, a zone change) wrote
what was equipped at that moment, so a carried brick built model (LOT
6662) was saved in MODELS with eq="true" and equipped again on every
load. While the equipment is pushed, the save now writes the pushed
equipment as equipped instead. Nothing is dropped from the save.
In game: on a property, pick up a brick built model and carry it, wait
for a save (or log out) while carrying it, log back in: the model is in
the Models bag, not equipped, and your normal gear is worn. Characters
already saved with an equipped model keep it until it is put away or
the save is cleaned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client works out on its own when its racer goes the wrong way and
shows a 6 second countdown, but it only moves the car when the server
sends RacingSetPlayerResetInfo, which DLU never did for this.
The server now follows the reset planes the way the client's
LWORacingControlComponent does (1.10.64): each path waypoint is a plane
facing along its rotation; the racer starts between planes 0 and 1,
moves forward when in front of the upcoming plane and back when behind
the last one (CheckUpcomingResetPlane @ 0x00c7edf0, CheckLastResetPlane @
0x00c7f1a0, CrossResetPlaneBackward @ 0x00cba380). Driving back
through a second plane in a row starts the client's countdown
(UpdateWrongWayCount @ 0x00be5c10, 6 seconds); going forward through a plane ends it. When
it runs out, the racer gets the same reset as an unsmashed reset: reset
info for their furthest point and RacingResetPlayerToLastReset. Resets
sent for smashes keep the planes in step as well.
Fixes issue 764.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The buyback inventory grew by 9 slots whenever it was nearly full, so
the vendor's buyback page kept resizing. Live kept 27 items: a 2014
capture of 29 sales shows that on the 28th, the server sent
RemoveItemFromInventory (buyback inventory) for the first item sold,
then added the new one.
The buyback inventory now keeps its size. Before a sale needs a new
buyback slot and the inventory holds 27 or more items, the oldest items
(lowest object ID: each sale gives a new, higher ID) are removed. Sales
that fit on an existing buyback stack remove nothing. The removal is not
counted again by the economy ledger, which counted the items as gone
when they were sold.
Fixes issue 1129.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deleting an item now follows its ItemComponent delResIndex row in the
DeletionRestrictions table, the way the client's shared inventory code
decides it (LWOInventoryComponent_Common::CanRemoveFromInventory @
0x00ce0d20, CheckDeletionRestrictionIndex @ 0x00c94c20):
- missing, unrestricted, unknown-type or empty rows allow it;
- LOTS_INCLUDED: another item of any listed LOT must remain;
- LOTS_EXCLUDED: other items of every listed LOT must remain;
- ANY_RESTRICTION / ALL_RESTRICTIONS: any / all listed rows allow it;
- ZONE: only in the listed maps; ALWAYS_RESTRICTED: never.
Operators (GM level 9) may delete anything, as in the client. A refused
delete is logged and the item stays.
ItemComponent.minNumRequired is not used: the client never reads it, so
its meaning can't be verified.
Issue 960: the rocket (6416, row 8) and the classic rocket parts (rows 1-3)
have rows that keep at least one rocket or part, so the last rocket can
no longer be deleted and strand the player.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing in the server writes or reads a packet by hand any more, so the
helpers for doing so go:
- CBITSTREAM, CMSGHEADER, CINSTREAM, CINSTREAM_SKIP_HEADER, SEND_PACKET,
SEND_PACKET_BROADCAST and HEADER_SIZE leave dCommonVars.h;
- the free BitStreamUtils::WriteHeader (LUBitStream::WriteHeader writes the
same bytes) and the unused PacketUtils::SavePacket are deleted.
The last raw reads are replaced: WorldServer builds its input stream
directly, the master packet logs read the header with
LUBitStream::ReadHeader instead of peeking at packet->data[1] and [3], and
MessageInspector reads a sent game message's header with the new
NetGameMsg::ReadPacketHeader (the counterpart of WritePacket) instead of
memcmp/memcpy. packet->data[0] is still compared with RakNet's own
connection IDs.
The frozen oracles keep using the macros verbatim through the test-only
tests/dGameTests/LegacyPacketMacros.h; the HeaderSkip tests, which only
tested CINSTREAM_SKIP_HEADER, are removed.
docs/PacketArchitecture.md: "where we are" now describes the final state
and what still touches raw bytes (RakNet IDs, replica headers, behavior bit
streams), and a new section collects the known wire discrepancies found
during the conversion, with client addresses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
eReplicaComponentType had the destroyable component's registry type (7) named
BUFF, the real buff component (98) as BUFF_REAL, and a made-up DESTROYABLE =
1000 that DestroyableComponent was stored under. Now DESTROYABLE = 7 and
BUFF = 98, as in ComponentsRegistry and the client.
Undone with it:
- Destructible stats came from whichever of the "buff" (7), quick build and
collectible registry ids was set, so the few objects without a type 7 entry
read a DestructibleComponent row with an unrelated id (the NJ dragon relics
16482-16485 via their collectible id, 125 quick build LOTs when placed with
is_smashable). The type 7 entry is used now; objects without one keep the
defaults (is_smashable objects: 1 health, smashable, factions -1 and 6;
collectibles: an empty destroyable). The client does the same
(LWODestroyableComponent::AllocateComponents / DoObjectComponentLoad).
- DestroyableComponent::Reinitialize, an unused copy of that pick order.
- WriteComponents' destroyableSerialized flags: the components are written from
a list in the client's order, and where the destroyable goes (its own place
after the buff, right before a quick build that has no registry entry for it,
or after the render component) is one function.
- The dashboard's registry 7 -> DESTROYABLE mapping; the destroyable type also
has a name there now (1000 was outside magic_enum's range).
Component types are not stored or sent as enum numbers anywhere besides the
CDClient's own values, which now match. migrations/cdserver/4 is unrelated (it
restores LOT 12916's registry rows that migration 0 overwrote) and stays.
Verified: dGameTests ReplicaComponentOrderTests serialize players, enemies,
smashables, quick builds and collectibles with and without a registry entry,
NPCs, pets, vehicles, models and an entity with every listed component with
the new code and a frozen copy of the old WriteComponents, and expect the same
bits for construction and serialization (a deliberately wrong destroyable place
fails them). Full ctest passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* saving from a test works
* testing works
* Update SavingTests.cpp
* test dServer stuff
* tests
* use dummy database and add missing pure fns
* add more tests
* add more tests
* add rocket tests
* Update BuffComponent.h
* Update test_xml_data.xml
* Update SavingTests.cpp
* update
* Assorted pet improvements
* remove unecessary include
* updates to address some feedback
* fixed database code for testing
* Removed reference member (for now)
* Removed cmake flag
* Added cooldown handling
* Made most of the logs hidden outside of debug mode
* removed weird submodule
* kill this phantom submodule
* updated to reflect reviewed feedback
* Added IsCooldownImmune() method to DestroyableComponent
* friggin typo
* Implemented non-pending changes and added cooldown immunity functions to DestroyableComponentTests
* add trailing linebreak
* another typo :(
* flipped cooldown test order (not leaving immune)
* Clean up comment and add DestroyableComponent test
* Components: Make ComponentType inline
Prevents the next commits ODR violation
* Components: Add new components
* Entity: Add headers
inline script component ComponentType
* Components: Flip constructor argument order
Entity comes first always
* Entity: Add generic AddComponent
Allows for much easier adding of components and is error proof by not allowing the user to add more than 1 of a specific component type to an Entity.
* Entity: Migrate all component constructors
Move all to the new variadic templates AddComponent function to reduce clutter and ways the component map is modified.
The new function makes no assumptions. Component is assumed to not exist and is checked for with operator[]. This will construct a null component which will then be newed if the component didnt exist, or it will just get the current component if it does already exist. No new component will be allocated or constructed if the component already exists and the already existing pointer is returned instead.
* Entity: Add placement new
For the case where the component may already exist, use a placement new to construct the component again, it would be constructed again, but would not need to go through the allocator.
* Entity: Add comments on likely new code
* Tests: Fix tests
* Update Entity.cpp
* Update SGCannon.cpp
* Entity: call destructor when re-constructing
* Update Entity.cpp
Update Entity.cpp
---------
Co-authored-by: Aaron Kimbrell <aronwk.aaron@gmail.com>
* Make serialize actually virtual
yep
* Abstract to PhysicsComponent
Move shared functionality of all physics related classes to a base class.
Tested that there were no failed to unserialize errors when in main gameplay in Gnarled Forest or in a race.
Tested that 2 players were able to see each other in the above scenarios just fine as well.
* Update PhantomPhysicsComponent.cpp
* Add SimplePhysicsTest
* Add construction test
* Update SimplePhysicsComponentTests.cpp
* remove flags and fix override
* Update VendorComponent.h
* Breakout rest of the enums from dcommonvars
so we don't have to deal with merge conflicts
ePlayerFlags is not a scoped enum, yet, due to it's complexity
* address feedback
* make player flag types consistent
* fix typo
* breakout the component types into a scoped enum
tested that things are the same as they were before
* fix missed rename
* fix brick-by-brick name to be crafting
because that's what it is
Implement GTest as a testing infrastructure.
Make windows output binaries to the build folder instead of the release type folder (potentially issue further down the line)
Add a simple unit test for DestroyableComponent