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>
118 lines
4.6 KiB
C#
118 lines
4.6 KiB
C#
using Theriapolis.Core.Data;
|
|
|
|
namespace Theriapolis.Core.Rules.Character;
|
|
|
|
/// <summary>
|
|
/// Phase 6.5 M4 — runtime state for a hybrid character.
|
|
///
|
|
/// Hybrids are blended from two parent lineages: a <b>Sire</b> (paternal
|
|
/// lineage) and a <b>Dam</b> (maternal lineage), of two different clades.
|
|
/// One parent is <see cref="DominantParent"/> — the lineage whose physical
|
|
/// expression is more visible; this drives Passing eligibility and which
|
|
/// clade the PC presents as for casual scent reads.
|
|
///
|
|
/// The character's own gender is independent of which parent is sire or
|
|
/// dam — a male hybrid PC can have a wolf-folk dam and a coyote-folk
|
|
/// sire just as readily as the reverse.
|
|
///
|
|
/// Per <c>theriapolis-rpg-clades.md</c> HYBRID ORIGIN: blend ability mods
|
|
/// (one from each parent clade), traits (2 from dominant + 1 from
|
|
/// secondary), and inherit *all* universal hybrid detriments — Scent
|
|
/// Dysphoria, Illegible Body Language, Social Stigma, Medical
|
|
/// Incompatibility (handled by <see cref="HybridDetriments"/>).
|
|
///
|
|
/// <see cref="PassingActive"/> is toggle-able mid-game; when set, the PC
|
|
/// presents as the dominant lineage's clade. Detection by NPCs (Phase 6.5
|
|
/// M5) is per-NPC and permanent once revealed.
|
|
/// </summary>
|
|
public sealed class HybridState
|
|
{
|
|
/// <summary>Sire (paternal-lineage) clade id, e.g. "canidae".</summary>
|
|
public string SireClade { get; init; } = "";
|
|
|
|
/// <summary>Sire (paternal-lineage) species id, e.g. "wolf".</summary>
|
|
public string SireSpecies { get; init; } = "";
|
|
|
|
/// <summary>Dam (maternal-lineage) clade id, e.g. "leporidae".</summary>
|
|
public string DamClade { get; init; } = "";
|
|
|
|
/// <summary>Dam (maternal-lineage) species id, e.g. "rabbit".</summary>
|
|
public string DamSpecies { get; init; } = "";
|
|
|
|
/// <summary>
|
|
/// Which parent's expression is dominant. Drives Passing presentation
|
|
/// (the PC scent-reads as this parent's clade) and the trait-split:
|
|
/// 2 Clade traits from the dominant parent, 1 from the secondary.
|
|
/// </summary>
|
|
public ParentLineage DominantParent { get; init; }
|
|
|
|
/// <summary>
|
|
/// True when the PC is actively trying to pass as their dominant
|
|
/// parent's clade. Toggles on the character sheet; consulted by
|
|
/// Phase 6.5 M5 passing-detection rolls.
|
|
/// </summary>
|
|
public bool PassingActive { get; set; }
|
|
|
|
/// <summary>
|
|
/// Phase 6.5 M5 — currently-active scent mask tier (if any). The mask
|
|
/// suppresses scent-based detection per its tier. Phase 6.5 M5 ships
|
|
/// the static tier flag; Phase 8's clock model adds time-based
|
|
/// expiry alongside daily wear.
|
|
/// </summary>
|
|
public ScentMaskTier ActiveMaskTier { get; set; } = ScentMaskTier.None;
|
|
|
|
/// <summary>
|
|
/// NPC ids who have personally detected this PC is hybrid. Permanent
|
|
/// once added — disabling Passing later doesn't undo the discovery
|
|
/// for that specific NPC. Phase 6.5 M5 populates this; M4 reserves it
|
|
/// in the schema so save round-trip works pre- and post-M5.
|
|
/// </summary>
|
|
public HashSet<int> NpcsWhoKnow { get; } = new();
|
|
|
|
/// <summary>
|
|
/// Convenience: which clade the PC presents as for casual scent reads
|
|
/// (used by Phase 6.5 M5 passing logic and Phase 7 dialogue gates).
|
|
/// Returns the dominant parent's clade id.
|
|
/// </summary>
|
|
public string PresentingCladeId =>
|
|
DominantParent == ParentLineage.Sire ? SireClade : DamClade;
|
|
|
|
/// <summary>
|
|
/// Convenience: which species the PC presents as. Same logic as
|
|
/// <see cref="PresentingCladeId"/>.
|
|
/// </summary>
|
|
public string PresentingSpeciesId =>
|
|
DominantParent == ParentLineage.Sire ? SireSpecies : DamSpecies;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Phase 6.5 M4 — which parent in a hybrid PC's lineage is the
|
|
/// dominant/secondary expression. Sire = paternal lineage, Dam = maternal
|
|
/// lineage; the choice is OOC (no gender semantics for the character
|
|
/// themselves, just for the parents' lineages).
|
|
/// </summary>
|
|
public enum ParentLineage : byte
|
|
{
|
|
Sire = 0,
|
|
Dam = 1,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Phase 6.5 M5 — scent-mask tier suppressing hybrid detection. Maps to
|
|
/// the consumable items in <c>items.json</c>:
|
|
/// None — no mask active
|
|
/// Basic — scent_mask_basic; advantage on PC Deception roll
|
|
/// Military — scent_mask_military; auto-suppresses scent detection
|
|
/// DeepCover — scent_mask_deep_cover; auto-suppresses, even Superior Scent
|
|
///
|
|
/// Per the Phase 6.5 plan §4.7. Phase 8's clock + rest model adds
|
|
/// time-based expiry; M5 carries the tier as static state.
|
|
/// </summary>
|
|
public enum ScentMaskTier : byte
|
|
{
|
|
None = 0,
|
|
Basic = 1,
|
|
Military = 2,
|
|
DeepCover = 3,
|
|
}
|