Lands the codex-worktree D.2b stream plus the extraction the 2026-07-02 UI architecture review mandated before commit: - ItemInteractionController: single owner of double-click use/equip/ container-open, targeted-use mode (health kits), drag-out drop; toolbar shortcut drags don't drop the real item. ItemEquipRules for multi-slot (coat) coverage via equip masks. - Cursor phase: CursorFeedbackController (semantic priority chain: drag > resize > window-move > target-mode > text) + RetailCursorCatalog (enums 0x27/0x28/0x29, hotspot 14,14; ClientUISystem::UpdateCursorState 0x00564630) resolved through the portal EnumIDMap chain by RetailCursorResolver; RetailCursorManager applies dat cursor art to the OS cursor. Register row AP-72 covers the OS standard-cursor fallback. - Character window goes live: CharacterSheetProvider owns sheet assembly, XP-curve/raise-cost math and the raise flow — extracted out of GameWindow per Code Structure Rule 1 instead of committing the ~430-line feature body there. Optimistic XP/credit debits go through eventful store APIs (new ClientObjectTable.UpdateInt64Property + LocalPlayerState.DebitIntProperty/DebitInt64Property) instead of raw property-dictionary writes; register row AP-73 covers the still-missing raise ledger (#163). - RetailWindowFrame: the shared nine-slice window mount recipe; the character window uses it, remaining windows migrate via #164. - Status-bar buttons toggle inventory/character windows; retail row-major backpack ordering; WorldSession.SendUseWithTarget + raise/train sends. GameWindow shrinks 14,214 -> 13,877 lines despite the new features; the sheet/raise logic is unit-tested in CharacterSheetProviderTests instead of trapped in the god object. Build green; full suite 3,286 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 lines
2 KiB
C#
68 lines
2 KiB
C#
namespace AcDream.App.UI.Layout;
|
|
|
|
/// <summary>
|
|
/// Retail character identity display helpers for gmStatManagementUI.
|
|
/// Sources: gmStatManagementUI::UpdateCharacterInfo (0x004f0770) calls
|
|
/// AppraisalSystem::InqGenderHeritageDisplay(gender 0x71, heritage 0xBC, 0),
|
|
/// then appends the current CharacterTitleTable title when one is active.
|
|
/// </summary>
|
|
internal static class CharacterIdentityText
|
|
{
|
|
public const uint GenderPropertyId = 0x71u;
|
|
public const uint HeritageGroupPropertyId = 0xBCu;
|
|
|
|
public static string StatHeaderLine(CharacterSheet sheet)
|
|
{
|
|
string? heritage = !string.IsNullOrWhiteSpace(sheet.Heritage)
|
|
? sheet.Heritage
|
|
: sheet.Race;
|
|
|
|
string? title = StripLeadingArticle(sheet.Title);
|
|
|
|
if (string.IsNullOrWhiteSpace(sheet.Gender))
|
|
return Join(heritage, title);
|
|
return Join(sheet.Gender, heritage, title);
|
|
}
|
|
|
|
public static string? GenderDisplayName(int gender) => gender switch
|
|
{
|
|
1 => "Male",
|
|
2 => "Female",
|
|
_ => null,
|
|
};
|
|
|
|
public static string? HeritageGroupDisplayName(int heritageGroup) => heritageGroup switch
|
|
{
|
|
1 => "Aluvian",
|
|
2 => "Gharu'ndim",
|
|
3 => "Sho",
|
|
4 => "Viamontian",
|
|
5 => "Umbraen",
|
|
6 => "Gearknight",
|
|
7 => "Tumerok",
|
|
8 => "Lugian",
|
|
9 => "Empyrean",
|
|
10 => "Penumbraen",
|
|
11 => "Undead",
|
|
12 => "Olthoi",
|
|
13 => "Olthoi",
|
|
_ => null,
|
|
};
|
|
|
|
private static string Join(params string?[] parts)
|
|
{
|
|
return string.Join(" ", parts
|
|
.Where(p => !string.IsNullOrWhiteSpace(p))
|
|
.Select(p => p!.Trim()));
|
|
}
|
|
|
|
private static string? StripLeadingArticle(string? title)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(title)) return null;
|
|
|
|
string trimmed = title.Trim();
|
|
return trimmed.StartsWith("the ", System.StringComparison.OrdinalIgnoreCase)
|
|
? trimmed[4..]
|
|
: trimmed;
|
|
}
|
|
}
|