Commit Graph

9 Commits

Author SHA1 Message Date
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
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
b2fba9d35b feat: save play time as live did: time before the session, stime in it
Live kept the total before the running session in <char time> and the
session's seconds in <char stime>; both changed at a login, together with
llog (time 262486 and stime 7344 became time 269830 at the next login).
The client reads both (LWOCharacterComponent::LoadFromSaveData 0x00ca4910)
and shows their sum (SendMessage 0x00d34330, GetTimePlayed). The server
wrote everything into time and no stime.

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

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

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:50:43 -06:00
Aaron Kimbrell
50bfa623ca refactor(component): the client's component lifecycle
Components get the client's per-component functions: LoadDataFromTemplate,
PrepareConfigData, LoadConfigData, LoadFromSaveData, Startup, Run,
OnEnterScope, OnLeaveScope, Preshutdown and Shutdown, called by the entity in
the client's order. Update is renamed Run, LoadFromXml LoadFromSaveData and
UpdateXml WriteSaveData. Nothing sent changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 07:45:16 -05:00
Aaron Kimbrell
8a8fc263e1 feat(pets): write the active pet in the charxml as live did (pet@a)
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>
2026-09-29 03:44:07 -05:00
Aaron Kimbrell
fd2be18161 feat(skills): save running skill cooldown groups (charxml <skil sc>)
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>
2026-09-29 03:44:07 -05:00
Aaron Kimbrell
921d2c8c2a feat(character): keep and save the tooltip flags (SetTooltipFlag, char@ttip)
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>
2026-09-29 03:44:06 -05:00
Aaron Kimbrell
70df41ae8d feat(missions): keep and save the mission journal type states
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>
2026-09-29 03:44:06 -05:00