using Godot; using System.Linq; using Theriapolis.Core.Data; using Theriapolis.GodotHost.Scenes.Widgets; using Theriapolis.GodotHost.UI; namespace Theriapolis.GodotHost.Scenes.Steps; /// /// Step IV — Subclass. New vs the React prototype (which is pre-Phase /// 6.5); per port plan §10, dedicated step rather than inline picker. /// Cards filtered by the chosen class's SubclassIds. Selecting /// commits via Patch(subclass_id). /// public partial class StepSubclass : VBoxContainer, IStep { private CharacterDraft _draft = null!; private GridContainer _grid = null!; public void Bind(CharacterDraft draft) { _draft = draft; _draft.Changed += () => Callable.From(Refresh).CallDeferred(); Build(); } public string? Validate() { if (string.IsNullOrEmpty(_draft?.SubclassId)) return "Pick a subclass."; var sub = System.Array.Find(CodexContent.Subclasses, s => s.Id == _draft.SubclassId); if (sub is null || !string.Equals(sub.ClassId, _draft.ClassId, System.StringComparison.OrdinalIgnoreCase)) return "Selected subclass doesn't belong to the current calling."; return null; } private void Build() { AddThemeConstantOverride("separation", 16); var intro = new VBoxContainer(); intro.AddThemeConstantOverride("separation", 6); AddChild(intro); intro.AddChild(new Label { Text = "FOLIO IV · SUBCLASS", ThemeTypeVariation = "Eyebrow" }); intro.AddChild(new Label { Text = "Choose a Subclass", ThemeTypeVariation = "H2" }); intro.AddChild(new Label { Text = "Specialization within your calling. Subclass features unlock at " + "level 3 and beyond, but the choice is locked in now — only subclasses " + "available to your chosen calling are shown.", AutowrapMode = TextServer.AutowrapMode.WordSmart, }); _grid = new GridContainer { Columns = 1, SizeFlagsHorizontal = SizeFlags.ExpandFill }; _grid.AddThemeConstantOverride("v_separation", 12); AddChild(_grid); Refresh(); } private void Refresh() { if (_grid is null) return; foreach (var c in _grid.GetChildren()) c.QueueFree(); var cls = CodexContent.Class(_draft.ClassId); if (cls is null) return; foreach (var subId in cls.SubclassIds) { var sub = System.Array.Find(CodexContent.Subclasses, s => s.Id == subId); if (sub is not null) _grid.AddChild(BuildCard(sub)); } } private Control BuildCard(SubclassDef sub) { bool selected = _draft.SubclassId == sub.Id; var card = CodexCard.Make(); card.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill; CodexCard.SetSelected(card, selected); card.GuiInput += (InputEvent e) => { if (e is InputEventMouseButton mb && mb.Pressed && mb.ButtonIndex == MouseButton.Left) _draft.Patch(new Godot.Collections.Dictionary { { "subclass_id", sub.Id } }); }; var box = new VBoxContainer { MouseFilter = MouseFilterEnum.Pass }; box.AddThemeConstantOverride("separation", 6); card.AddChild(box); box.AddChild(new Label { Text = sub.Name, ThemeTypeVariation = "CardName" }); if (!string.IsNullOrEmpty(sub.Flavor)) { box.AddChild(new Label { Text = sub.Flavor, AutowrapMode = TextServer.AutowrapMode.WordSmart, ThemeTypeVariation = "CardBody", }); } // Level-3 features (the first unlock for any subclass). var l3 = sub.LevelFeatures.FirstOrDefault(e => e.Level == 3); if (l3?.Features.Length > 0) { var featChips = new HFlowContainer { MouseFilter = MouseFilterEnum.Pass }; featChips.AddThemeConstantOverride("h_separation", 6); featChips.AddThemeConstantOverride("v_separation", 4); box.AddChild(featChips); foreach (var fid in l3.Features) { if (!sub.FeatureDefinitions.TryGetValue(fid, out var fd)) continue; if (fd.Kind == "stub") continue; featChips.AddChild(new TraitChip { TraitName = fd.Name, Description = fd.Description, Tag = "L3 · " + fd.Kind, }); } } return card; } }