107 lines
4.9 KiB
C#
107 lines
4.9 KiB
C#
|
|
using Theriapolis.Core.Rules.Character;
|
|||
|
|
using Theriapolis.Core.Rules.Stats;
|
|||
|
|
|
|||
|
|
namespace Theriapolis.Core.Dungeons;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Phase 7 M2 — clade-responsive dungeon movement cost. Per <c>procgen.md</c>
|
|||
|
|
/// Layer 5 final paragraph and Phase 7 plan §5.4: a Large PC squeezing
|
|||
|
|
/// through a Mustelid tunnel takes 2× movement points per tile; a Small
|
|||
|
|
/// PC in an Ursid hall is exposed (×1.5); etc.
|
|||
|
|
///
|
|||
|
|
/// Cost multiplier applies to tactical-tile movement budget per turn —
|
|||
|
|
/// combat reach + LOS unchanged; <em>only</em> movement budget. The
|
|||
|
|
/// caller (<see cref="Rules.Combat.TacticalMovementRules"/> or equivalent)
|
|||
|
|
/// looks up the room the actor is currently in and consults
|
|||
|
|
/// <see cref="GetCostMultiplier"/>.
|
|||
|
|
///
|
|||
|
|
/// Hybrid PCs use their <em>dominant lineage</em>'s clade-implied size
|
|||
|
|
/// for the lookup — matches the Phase 6.5 hybrid passing / presenting-clade
|
|||
|
|
/// contract. A Wolf-Folk × Hare-Folk hybrid with <c>DominantParent: Sire</c>
|
|||
|
|
/// reads as Wolf-Folk (MediumLarge); with <c>DominantParent: Dam</c> reads
|
|||
|
|
/// as Hare-Folk (Medium). Outside dungeons the multiplier is always 1.0.
|
|||
|
|
///
|
|||
|
|
/// Reference table (Phase 7 plan §5.4):
|
|||
|
|
/// Player size | Built by Mustelid | Ursid | Cervid | Bovid | Imperium/None
|
|||
|
|
/// Small | 1.0 | 1.5 | 1.0 | 1.2 | 1.0
|
|||
|
|
/// Medium | 1.2 | 1.0 | 1.0 | 1.0 | 1.0
|
|||
|
|
/// MediumLarge | 1.5 | 1.0 | 1.0 | 1.0 | 1.0
|
|||
|
|
/// Large | 2.0 | 1.0 | 1.2 | 1.0 | 1.0
|
|||
|
|
/// </summary>
|
|||
|
|
public static class ClademorphicMovement
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// Multiplier on movement-cost-per-tile for a player of the given size
|
|||
|
|
/// in a room built by the given clade. Returns 1.0 when no mismatch
|
|||
|
|
/// applies. Unknown <paramref name="builtBy"/> values default to 1.0
|
|||
|
|
/// (no penalty).
|
|||
|
|
/// </summary>
|
|||
|
|
public static float GetCostMultiplier(SizeCategory playerSize, string builtBy)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrEmpty(builtBy)) return 1.0f;
|
|||
|
|
// Normalise — JSON ships lowercase tags.
|
|||
|
|
return builtBy.ToLowerInvariant() switch
|
|||
|
|
{
|
|||
|
|
"mustelid" => playerSize switch
|
|||
|
|
{
|
|||
|
|
SizeCategory.Small => 1.0f,
|
|||
|
|
SizeCategory.Medium => C.MOVE_COST_MISMATCH_LIGHT, // 1.2 — slight squeeze
|
|||
|
|
SizeCategory.MediumLarge => C.MOVE_COST_MISMATCH_MED, // 1.5
|
|||
|
|
SizeCategory.Large => C.MOVE_COST_MISMATCH_HEAVY, // 2.0 — squeezing
|
|||
|
|
_ => 1.0f,
|
|||
|
|
},
|
|||
|
|
"ursid" => playerSize switch
|
|||
|
|
{
|
|||
|
|
SizeCategory.Small => C.MOVE_COST_MISMATCH_MED, // exposed in cavernous halls
|
|||
|
|
_ => 1.0f,
|
|||
|
|
},
|
|||
|
|
"cervid" => playerSize switch
|
|||
|
|
{
|
|||
|
|
SizeCategory.Large => C.MOVE_COST_MISMATCH_LIGHT, // antler clearance
|
|||
|
|
_ => 1.0f,
|
|||
|
|
},
|
|||
|
|
"bovid" => playerSize switch
|
|||
|
|
{
|
|||
|
|
SizeCategory.Small => C.MOVE_COST_MISMATCH_LIGHT,
|
|||
|
|
_ => 1.0f,
|
|||
|
|
},
|
|||
|
|
// Canid / Felid / Leporid / Imperium / "none" / unknown:
|
|||
|
|
_ => 1.0f,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Convenience wrapper that resolves a character's effective size for
|
|||
|
|
/// the lookup (handles Phase 6.5 hybrid dominant-lineage rules).
|
|||
|
|
/// </summary>
|
|||
|
|
public static float GetCostMultiplier(Character character, string builtBy)
|
|||
|
|
{
|
|||
|
|
if (character is null) return 1.0f;
|
|||
|
|
var effectiveSize = EffectiveSize(character);
|
|||
|
|
return GetCostMultiplier(effectiveSize, builtBy);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Resolve the size category that drives the clade-responsive lookup.
|
|||
|
|
/// For purebred PCs, this is just <see cref="Character.Size"/>. For
|
|||
|
|
/// hybrid PCs, it's the size implied by the dominant-lineage species
|
|||
|
|
/// — and we expose this as a separate helper so callers (e.g. NPC
|
|||
|
|
/// mechanics that *also* need the presenting size) can reuse it.
|
|||
|
|
/// </summary>
|
|||
|
|
public static SizeCategory EffectiveSize(Character character)
|
|||
|
|
{
|
|||
|
|
if (character is null) throw new System.ArgumentNullException(nameof(character));
|
|||
|
|
if (!character.IsHybrid) return character.Size;
|
|||
|
|
// Hybrid: pick the size implied by the dominant parent's species.
|
|||
|
|
// The Hybrid record carries the species name only (string); the
|
|||
|
|
// species-to-size mapping lives on Character.Species (the
|
|||
|
|
// *presenting* species set at character creation per the dominant
|
|||
|
|
// lineage). So for a hybrid PC the simplest (and load-bearing-
|
|||
|
|
// correct) answer is the presenting species — which is exactly
|
|||
|
|
// what <c>character.Size</c> already returns. Documented here so
|
|||
|
|
// future agents don't replace this with a parent-species lookup
|
|||
|
|
// and break the contract.
|
|||
|
|
return character.Size;
|
|||
|
|
}
|
|||
|
|
}
|