Files
TheriapolisV3/Theriapolis.Core/Persistence/PlayerCharacterState.cs
T
Christopher Wiebe b451f83174 Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:40:51 -07:00

111 lines
4.8 KiB
C#

namespace Theriapolis.Core.Persistence;
/// <summary>
/// Plain-data snapshot of a <see cref="Rules.Character.Character"/> for
/// the save layer. Kept as primitive fields + arrays so the persistence
/// codec doesn't pull MessagePack attributes onto the live model.
///
/// Items / equipped slots reference content by id (string); the loader
/// re-resolves them against the current <see cref="Data.ItemDef"/> set
/// after world content reloads.
/// </summary>
public sealed class PlayerCharacterState
{
public string CladeId { get; set; } = "";
public string SpeciesId { get; set; } = "";
public string ClassId { get; set; } = "";
public string BackgroundId { get; set; } = "";
// Final ability scores (post clade + species mods).
public byte STR { get; set; }
public byte DEX { get; set; }
public byte CON { get; set; }
public byte INT { get; set; }
public byte WIS { get; set; }
public byte CHA { get; set; }
public int Level { get; set; } = 1;
public int Xp { get; set; } = 0;
public int MaxHp { get; set; }
public int CurrentHp { get; set; }
public int ExhaustionLevel { get; set; } = 0;
// Phase 5 M6 additions — backward-compat handled by EndOfStream check in SaveCodec.ReadCharacter.
public string FightingStyle { get; set; } = "";
public int RageUsesRemaining { get; set; } = 2;
// Phase 6 M3 — coin balance (Fangs are Theriapolis's universal currency).
public int CurrencyFang { get; set; } = 0;
// Phase 6.5 M0 — levelling state.
/// <summary>
/// Subclass id chosen at level 3 (or whatever
/// <see cref="C.SUBCLASS_SELECTION_LEVEL"/> is). Empty pre-L3.
/// </summary>
public string SubclassId { get; set; } = "";
/// <summary>Feature ids learned across all level-ups, in unlock order.</summary>
public string[] LearnedFeatureIds { get; set; } = Array.Empty<string>();
/// <summary>Append-only per-level-up history (deltas, not post-state).</summary>
public LevelUpRecordState[] LevelUpHistory { get; set; } = Array.Empty<LevelUpRecordState>();
// Phase 6.5 M4 — hybrid-character genealogy. Null for purebred PCs.
public HybridStateSnapshot? Hybrid { get; set; }
/// <summary>Skill ids stored as enum byte values for compactness.</summary>
public byte[] SkillProficiencies { get; set; } = Array.Empty<byte>();
/// <summary>Active conditions stored as enum byte values.</summary>
public byte[] Conditions { get; set; } = Array.Empty<byte>();
/// <summary>Inventory stacks — references to ItemDef.Id with quantity + condition + optional equip slot.</summary>
public InventoryItemState[] Inventory { get; set; } = Array.Empty<InventoryItemState>();
}
/// <summary>
/// Phase 6.5 M0 — one entry in <see cref="PlayerCharacterState.LevelUpHistory"/>.
/// Plain-data round-trip of <see cref="Rules.Character.LevelUpRecord"/>.
/// </summary>
public sealed class LevelUpRecordState
{
public int Level { get; set; }
public int HpGained { get; set; }
public bool HpWasAveraged { get; set; }
public int HpHitDieResult { get; set; }
/// <summary>Empty when no subclass was chosen at this level (i.e. anything but L3).</summary>
public string SubclassChosen { get; set; } = "";
/// <summary>ASI ability ids stored as enum byte values; same length as <see cref="AsiValues"/>.</summary>
public byte[] AsiKeys { get; set; } = Array.Empty<byte>();
public int[] AsiValues { get; set; } = Array.Empty<int>();
public string[] FeaturesUnlocked { get; set; } = Array.Empty<string>();
}
/// <summary>
/// Phase 6.5 M4 — flat round-trip record for <see cref="Rules.Character.HybridState"/>.
/// </summary>
public sealed class HybridStateSnapshot
{
public string SireClade { get; set; } = "";
public string SireSpecies { get; set; } = "";
public string DamClade { get; set; } = "";
public string DamSpecies { get; set; } = "";
/// <summary>0 = Sire, 1 = Dam (matches <see cref="Rules.Character.ParentLineage"/> byte values).</summary>
public byte DominantParent { get; set; } = 0;
public bool PassingActive { get; set; } = false;
/// <summary>Per-NPC discovery list — populated in Phase 6.5 M5.</summary>
public int[] NpcsWhoKnow { get; set; } = Array.Empty<int>();
/// <summary>Active scent-mask tier (Phase 6.5 M5). 0 = none.</summary>
public byte ActiveMaskTier { get; set; } = 0;
}
/// <summary>One stack in <see cref="PlayerCharacterState.Inventory"/>.</summary>
public sealed class InventoryItemState
{
public string ItemId { get; set; } = "";
public int Qty { get; set; } = 1;
public int Condition { get; set; } = 100;
/// <summary>Null if this stack is bagged. Otherwise the EquipSlot enum byte value.</summary>
public byte? EquippedAt { get; set; }
}