Files
TheriapolisV3/Theriapolis.Game/CodexUI/Steps/StepSkills.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

95 lines
4.8 KiB
C#

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;
/// <summary>
/// Step VI — Skills. Two-column grid of <see cref="SkillGroupPanel"/>, one
/// per ability. Each panel lists every skill governed by that ability with
/// its current <see cref="CheckboxState"/>. Background-sealed skills are
/// pre-checked and locked; class-pickable skills are toggleable up to the
/// class's <c>SkillsChoose</c> count.
/// </summary>
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<string>(
s.Background?.SkillProficiencies ?? System.Array.Empty<string>(),
System.StringComparer.OrdinalIgnoreCase);
var classOpts = new System.Collections.Generic.HashSet<string>(
s.Class?.SkillOptions ?? System.Array.Empty<string>(),
System.StringComparer.OrdinalIgnoreCase);
var groupedByAbility = new System.Collections.Generic.Dictionary<AbilityId, System.Collections.Generic.List<string>>();
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<string> skillIds,
System.Collections.Generic.HashSet<string> bgLocked,
System.Collections.Generic.HashSet<string> 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) };
}
}