using Theriapolis.Core.Data; namespace Theriapolis.Core.Rules.Character; /// /// Phase 6.5 M2 — given a character's + a chosen /// subclassId, look up the subclass-feature ids unlocked at a /// specific level. Used by to populate /// . /// /// The resolver does NOT mutate state — it's a pure lookup. The /// takes the resulting feature ids and /// applies their mechanical effects at combat-resolution time. /// public static class SubclassResolver { /// /// Look up a subclass def by id from a content collection. Returns /// null if the id is empty or unknown — callers should treat that as /// "no subclass picked yet" (pre-L3) or "subclass content missing" /// (data error, log it). /// public static SubclassDef? TryFindSubclass( IReadOnlyDictionary subclasses, string? subclassId) { if (string.IsNullOrEmpty(subclassId)) return null; return subclasses.TryGetValue(subclassId, out var def) ? def : null; } /// /// Feature ids unlocked by the chosen subclass at . /// Returns an empty array if no subclass is picked, the subclass def is /// missing, or the level has no entry in . /// public static string[] UnlockedFeaturesAt( IReadOnlyDictionary subclasses, string? subclassId, int level) { var def = TryFindSubclass(subclasses, subclassId); if (def is null) return Array.Empty(); foreach (var entry in def.LevelFeatures) if (entry.Level == level) return entry.Features ?? Array.Empty(); return Array.Empty(); } /// /// Resolve a feature description (for display in the level-up screen /// and combat HUD tooltips). Looks first in the subclass's /// , then falls through to /// the parent class's /// (in case the feature id is /// shared — e.g. asi, extra_attack). Returns null if neither /// has it. /// public static ClassFeatureDef? ResolveFeatureDef( ClassDef classDef, SubclassDef? subclass, string featureId) { if (subclass is not null && subclass.FeatureDefinitions.TryGetValue(featureId, out var sdef)) return sdef; if (classDef.FeatureDefinitions.TryGetValue(featureId, out var cdef)) return cdef; return null; } }