b451f83174
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>
95 lines
4.8 KiB
C#
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) };
|
|
}
|
|
}
|