using Theriapolis.Core.Rules.Stats; using Theriapolis.Game.CodexUI.Core; using Theriapolis.Game.CodexUI.Screens; using Theriapolis.Game.CodexUI.Widgets; using Theriapolis.Game.UI; namespace Theriapolis.Game.CodexUI.Steps; /// /// Step VI — Skills. Two-column grid of , one /// per ability. Each panel lists every skill governed by that ability with /// its current . Background-sealed skills are /// pre-checked and locked; class-pickable skills are toggleable up to the /// class's SkillsChoose count. /// public static class StepSkills { public static CodexWidget Build(CodexCharacterCreationScreen s, CodexAtlas atlas, CodexHoverPopover popover) { var col = new Column { Spacing = 14 }; col.Add(StepCommon.PageIntro( "Folio VI — Of Trained Hands", "Choose your Skills", $"Your background grants {s.Background?.SkillProficiencies.Length ?? 0} skill(s) automatically (sealed). From your calling's offered list, choose {s.Class?.SkillsChoose ?? 0} more.")); // Meta line var meta = new Row { Spacing = 16, VAlignChildren = VAlign.Middle, Padding = new Thickness(14, 12, 14, 12) }; meta.Add(new CodexLabel($"{s.ChosenSkills.Count} / {s.Class?.SkillsChoose ?? 0} CHOSEN", CodexFonts.DisplaySmall, CodexColors.Ink)); meta.Add(new CodexLabel($"+ {s.Background?.SkillProficiencies.Length ?? 0} SEALED BY BACKGROUND", CodexFonts.MonoTagSmall, CodexColors.InkMute)); col.Add(new CodexPanel(atlas, meta)); // Skill groups by ability var bgLocked = new System.Collections.Generic.HashSet( s.Background?.SkillProficiencies ?? System.Array.Empty(), System.StringComparer.OrdinalIgnoreCase); var classOpts = new System.Collections.Generic.HashSet( s.Class?.SkillOptions ?? System.Array.Empty(), System.StringComparer.OrdinalIgnoreCase); var groupedByAbility = new System.Collections.Generic.Dictionary>(); foreach (var ab in CodexCopy.AbilityOrder) groupedByAbility[ab] = new(); foreach (var skillId in CodexCharacterCreationScreen.AllSkillIds()) groupedByAbility[CodexCopy.SkillAbility(skillId)].Add(skillId); var grid = new Grid { Columns = 2 }; foreach (var ab in CodexCopy.AbilityOrder) grid.Add(BuildGroup(s, atlas, popover, ab, groupedByAbility[ab], bgLocked, classOpts)); col.Add(grid); return col; } private static CodexWidget BuildGroup(CodexCharacterCreationScreen s, CodexAtlas atlas, CodexHoverPopover popover, AbilityId ab, System.Collections.Generic.List skillIds, System.Collections.Generic.HashSet bgLocked, System.Collections.Generic.HashSet classOpts) { var inner = new Column { Spacing = 4 }; inner.Add(new CodexLabel(CodexCopy.AbilityLabels[ab].ToUpperInvariant(), CodexFonts.DisplaySmall, CodexColors.Ink)); foreach (var skillId in skillIds) { bool fromBg = bgLocked.Contains(skillId); bool fromClass = classOpts.Contains(skillId); bool checkedNow; try { checkedNow = s.ChosenSkills.Contains(SkillIdExtensions.FromJson(skillId)); } catch { checkedNow = false; } var state = fromBg ? CheckboxState.LockedFromBg : checkedNow ? CheckboxState.Checked : !fromClass ? CheckboxState.Unavailable : CheckboxState.Default; string sourceTag = fromBg ? "BACKGROUND" : (fromClass ? "CLASS" : "—"); var row = new CodexCheckboxRow(CodexCopy.SkillName(skillId), sourceTag, state, atlas); string sid = skillId; row.OnClick = () => { SkillId enumId; try { enumId = SkillIdExtensions.FromJson(sid); } catch { return; } if (s.ChosenSkills.Contains(enumId)) s.ChosenSkills.Remove(enumId); else if (s.ChosenSkills.Count < (s.Class?.SkillsChoose ?? 0)) s.ChosenSkills.Add(enumId); s.InvalidateLayout(); }; row.OnHover = () => popover.Show(row.Bounds, CodexCopy.SkillName(sid), CodexCopy.SkillDescription(sid), CodexCopy.SkillAbility(sid).ToString()); inner.Add(row); } return new CodexPanel(atlas, inner) { Inset = new Thickness(14, 12, 16, 12) }; } }