using System; using System.Collections.Generic; namespace AcDream.App.UI.Layout; /// /// Snapshot of all character data the Character window report uses. /// Passed to as a provider delegate /// so the window can be rebound to live data in GameWindow or a static /// fixture in the UI Studio. /// /// Field names and retail property ids confirmed from /// gmCharacterInfoUI::UpdatePlayerBirthAgeDeaths (0x004b8cb0), /// UpdateEnduranceInfo (0x004b8eb0), /// UpdateInnateAttributeInfo (0x004b87e0), /// UpdateFakeSkills (0x004b8930), /// UpdateAugmentations (0x004b9000), and /// UpdateLoad (0x004b8a20). /// public sealed class CharacterSheet { // ── Identity ────────────────────────────────────────────────────────────── /// Character name (first line of the report). public string Name { get; init; } = string.Empty; /// Character level. public int Level { get; init; } /// Gender display string, e.g. "Female". Null = omit. public string? Gender { get; init; } /// Race string, e.g. "Aluvian". Null = omit. public string? Race { get; init; } /// Heritage group display string, e.g. "Aluvian". Null = omit. public string? Heritage { get; init; } /// Title string, e.g. "the Adventurer". Null = omit. public string? Title { get; init; } // ── Experience / PK (gmStatManagementUI::UpdateExperience 0x004f0a70, // UpdatePKStatus 0x004f00a0) — the Attributes-tab header strip ────────── /// Total accrued experience (retail PropertyInt64 1). Header value /// element 0x10000235 (m_pTotalXPText). public long TotalXp { get; init; } /// Experience remaining to the next level. Header value element /// 0x10000238 (m_pXPToLevelText). public long XpToNextLevel { get; init; } /// XP-to-next-level meter fill, 0..1 (retail (cur−base)/(cap−base)). /// Drives the header meter 0x10000236 (m_pXPToLevelMeter). public float XpFraction { get; init; } /// PK status display string, e.g. "Non-Player Killer". Header element /// 0x10000233 (m_pPKStatusText). Null = omit. public string? PkStatus { get; init; } // ── Birth / age / deaths (UpdatePlayerBirthAgeDeaths 0x004b8cb0) ───────── /// Formatted birth date string (retail InqInt(0x62) → strftime). /// Null = omit the birth line. public string? BirthDate { get; init; } /// Formatted play-time duration (retail InqInt(0x7d) → QueryDuration). /// Null = omit the age line. public string? PlayTime { get; init; } /// Total deaths (retail InqInt(0x2b) = NumDeaths). public int Deaths { get; init; } /// Raw retail PropertyInt 0x62. Null means the quality was absent. public int? BirthTimestamp { get; init; } /// Raw retail PropertyInt 0x7D seconds. Null means the quality was absent. public int? TotalPlayTimeSeconds { get; init; } // ── Vitals (UpdateEnduranceInfo 0x004b8eb0) ───────────────────────────── public int HealthCurrent { get; init; } public int HealthMax { get; init; } public int StaminaCurrent { get; init; } public int StaminaMax { get; init; } public int ManaCurrent { get; init; } public int ManaMax { get; init; } // ── Innate attributes (UpdateInnateAttributeInfo 0x004b87e0) ──────────── // InqAttribute order: 1,2,4,3,5,6 = Strength, Endurance, Quickness, Coordination, Focus, Self. public int Strength { get; init; } public int Endurance { get; init; } public int Quickness { get; init; } public int Coordination { get; init; } public int Focus { get; init; } public int Self { get; init; } // ── Skills (UpdateFakeSkills 0x004b8930) ──────────────────────────────── // Character Information uses 0xB5/0xC0 for Chess/Fishing; skill credits use 0x18. // InqInt(0x18) = available skill credits — footer 0x10000245 in the Attributes tab. public int UnspentSkillCredits { get; init; } public int SpecializedSkillCredits { get; init; } /// Retail Character Information PropertyInt 0xB5. public int ChessRank { get; init; } /// Retail Character Information PropertyInt 0xC0. public int FishingSkill { get; init; } /// /// Available (unspent) skill credits shown in the Attributes tab footer State-A. /// Retail InqInt(0x18) — gmStatManagementUI::DisplayDefaultFooter (0x0049cde0). /// Element 0x10000243 (footer line-1 value in the studio's 3-line layout). /// public int SkillCredits { get; init; } /// /// Unassigned (banked) experience points. /// Retail InqInt64(2) — shown in footer line-2 in State-A display. /// Element 0x10000245 (footer line-2 value). /// public long UnassignedXp { get; init; } // ── Attribute raise costs (ExperienceToAttributeLevel, gmAttributeUI::PostInit) ── // Retail formula for x1: ExperienceToAttributeLevel(value + 1) − xpSpent. // Retail formula for x10: ExperienceToAttributeLevel(value + min(10, remaining)) − xpSpent. // Cost 0 means the attribute is at max or not trainable. Ordered to match AttrRows: // Strength, Endurance, Coordination, Quickness, Focus, Self, Health, Stamina, Mana. // Source: gmAttributeUI::GetCostToRaise/GetCostToRaise10 (0x0049cb80/0x0049cc70). /// /// XP cost to raise each of the 9 attributes/vitals by 1, in retail display order: /// [0]=Strength, [1]=Endurance, [2]=Coordination, [3]=Quickness, [4]=Focus, [5]=Self, /// [6]=Health, [7]=Stamina, [8]=Mana. /// Cost 0 means the attribute is at max (raise button ghosted). /// public long[] AttributeRaiseCosts { get; init; } = Array.Empty(); /// /// XP cost to raise each of the 9 attributes/vitals by up to 10 retail steps, /// in the same display order as . /// public long[] AttributeRaise10Costs { get; init; } = Array.Empty(); /// /// Skills shown on the Character window Skills tab. Retail gmSkillUI groups these by /// advancement class, then sorts each group alphabetically by skill name. /// public IReadOnlyList Skills { get; init; } = Array.Empty(); // ── Augmentations (UpdateAugmentations 0x004b9000) ───────────────────── // Retail InqInt(0x162) = AugmentationStat; string-switch 1..0xb. /// Augmentation name from the switch in UpdateAugmentations (0x004b9000), /// e.g. "Swords", "Two Handed Weapons". Null = "None". public string? AugmentationName { get; init; } /// /// Raw PropertyInt snapshot used by retail's data-driven mastery, /// luminance, and augmentation report. The controller reads only the /// quality ids cited by gmCharacterInfoUI::UpdateAugmentations. /// public IReadOnlyDictionary CharacterInfoProperties { get; init; } = new Dictionary(); // ── Burden / load (UpdateLoad 0x004b8a20) ─────────────────────────────── // Retail InqLoad + EncumbranceCapacity(Strength, AugEncumbrance). public int BurdenCurrent { get; init; } public int BurdenMax { get; init; } public int EncumbranceAugmentations { get; init; } } public enum CharacterSkillAdvancementClass { Inactive = 0, Untrained = 1, Trained = 2, Specialized = 3, } public sealed record CharacterSkill( uint Id, string Name, uint IconDid, CharacterSkillAdvancementClass AdvancementClass, int BaseLevel, int CurrentLevel, bool UsableUntrained, int TrainedCost, int SpecializedCost, long RaiseCost, long Raise10Cost = 0L);