2026-04-30 20:40:51 -07:00
|
|
|
|
namespace Theriapolis.Core.Rules.Stats;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Body-size category from clades.md. Determines tactical-tile footprint,
|
|
|
|
|
|
/// reach, equipment fit, and grappling rules.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public enum SizeCategory : byte
|
|
|
|
|
|
{
|
|
|
|
|
|
Tiny = 0, // reserved; no Phase 5 species uses this
|
|
|
|
|
|
Small = 1, // rabbit-folk, housecat-folk, ferret-folk
|
2026-05-07 22:23:47 -07:00
|
|
|
|
Medium = 2, // fox-folk, deer-folk, sheep-folk, goat-folk, leopard-folk, badger-folk, hare-folk
|
2026-04-30 20:40:51 -07:00
|
|
|
|
MediumLarge = 3, // wolf-folk, elk-folk, lion-folk, wolverine-folk, coyote-folk
|
|
|
|
|
|
Large = 4, // brown bear-folk, polar bear-folk, moose-folk, bull-folk, bison-folk
|
|
|
|
|
|
Huge = 5, // reserved; no Phase 5 species uses this
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static class SizeExtensions
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>Tactical-tile footprint per side (1 = 1×1, 2 = 2×2).</summary>
|
|
|
|
|
|
public static int FootprintTiles(this SizeCategory s) => s switch
|
|
|
|
|
|
{
|
|
|
|
|
|
SizeCategory.Tiny => 1,
|
|
|
|
|
|
SizeCategory.Small => 1,
|
|
|
|
|
|
SizeCategory.Medium => 1,
|
|
|
|
|
|
SizeCategory.MediumLarge => 1, // counts as Large for grappling/carrying only
|
|
|
|
|
|
SizeCategory.Large => 2,
|
|
|
|
|
|
SizeCategory.Huge => 3,
|
|
|
|
|
|
_ => throw new ArgumentOutOfRangeException(nameof(s)),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Default melee reach in tactical tiles (weapon-modifiable).</summary>
|
|
|
|
|
|
public static int DefaultReachTiles(this SizeCategory s) => s switch
|
|
|
|
|
|
{
|
|
|
|
|
|
SizeCategory.Tiny => 1,
|
|
|
|
|
|
SizeCategory.Small => 1,
|
|
|
|
|
|
SizeCategory.Medium => 1,
|
|
|
|
|
|
SizeCategory.MediumLarge => 1,
|
|
|
|
|
|
SizeCategory.Large => 2,
|
|
|
|
|
|
SizeCategory.Huge => 3,
|
|
|
|
|
|
_ => throw new ArgumentOutOfRangeException(nameof(s)),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Carrying-capacity multiplier per equipment.md (Small ½×, Large 2×).</summary>
|
|
|
|
|
|
public static float CarryCapacityMult(this SizeCategory s) => s switch
|
|
|
|
|
|
{
|
|
|
|
|
|
SizeCategory.Tiny => 0.25f,
|
|
|
|
|
|
SizeCategory.Small => 0.5f,
|
|
|
|
|
|
SizeCategory.Medium => 1.0f,
|
|
|
|
|
|
SizeCategory.MediumLarge => 1.0f, // Medium frame, Large for grappling
|
|
|
|
|
|
SizeCategory.Large => 2.0f,
|
|
|
|
|
|
SizeCategory.Huge => 4.0f,
|
|
|
|
|
|
_ => throw new ArgumentOutOfRangeException(nameof(s)),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Parses a snake_case JSON value (e.g. "medium_large") into a SizeCategory.</summary>
|
|
|
|
|
|
public static SizeCategory FromJson(string? raw) => raw switch
|
|
|
|
|
|
{
|
|
|
|
|
|
"tiny" => SizeCategory.Tiny,
|
|
|
|
|
|
"small" => SizeCategory.Small,
|
|
|
|
|
|
"medium" => SizeCategory.Medium,
|
|
|
|
|
|
"medium_large" => SizeCategory.MediumLarge,
|
|
|
|
|
|
"large" => SizeCategory.Large,
|
|
|
|
|
|
"huge" => SizeCategory.Huge,
|
|
|
|
|
|
_ => throw new ArgumentException($"Unknown size category: '{raw}'"),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|