2026-04-30 20:40:51 -07:00
|
|
|
namespace Theriapolis.Core.Rules.Stats;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-05-09 20:25:56 -07:00
|
|
|
/// Skill list used by Theriapolis — extends the d20 baseline with 12
|
|
|
|
|
/// additional skills (M6.18) so each ability has 5 skills tied to it.
|
|
|
|
|
/// New skills appended at the end of the byte enum to preserve save-game
|
|
|
|
|
/// compatibility with characters created before M6.18. Each skill is
|
|
|
|
|
/// backed by a single ability — see <see cref="SkillIdExtensions.Ability"/>.
|
2026-04-30 20:40:51 -07:00
|
|
|
/// </summary>
|
|
|
|
|
public enum SkillId : byte
|
|
|
|
|
{
|
|
|
|
|
Acrobatics = 0,
|
|
|
|
|
AnimalHandling = 1,
|
|
|
|
|
Arcana = 2, // Theriapolis: "Advanced Engineering"
|
|
|
|
|
Athletics = 3,
|
|
|
|
|
Deception = 4,
|
|
|
|
|
History = 5,
|
|
|
|
|
Insight = 6,
|
|
|
|
|
Intimidation = 7,
|
|
|
|
|
Investigation = 8,
|
|
|
|
|
Medicine = 9,
|
|
|
|
|
Nature = 10,
|
|
|
|
|
Perception = 11,
|
|
|
|
|
Performance = 12,
|
|
|
|
|
Persuasion = 13,
|
|
|
|
|
Religion = 14, // Theriapolis: Covenant lore
|
|
|
|
|
SleightOfHand = 15,
|
|
|
|
|
Stealth = 16,
|
|
|
|
|
Survival = 17,
|
2026-05-09 20:25:56 -07:00
|
|
|
|
|
|
|
|
// M6.18 — Theriapolis-flavored expansions, 5 per ability.
|
|
|
|
|
Brawl = 18, // STR
|
|
|
|
|
BuildRead = 19, // STR
|
|
|
|
|
Driving = 20, // DEX
|
|
|
|
|
Endurance = 21, // CON
|
|
|
|
|
Force = 22, // STR
|
|
|
|
|
Fortitude = 23, // CON
|
|
|
|
|
Hardiness = 24, // CON
|
|
|
|
|
Haulage = 25, // STR
|
|
|
|
|
LungCraft = 26, // CON
|
|
|
|
|
Marksmanship = 27, // DEX
|
|
|
|
|
PainTolerance = 28, // CON
|
|
|
|
|
ScentSpeak = 29, // CHA
|
2026-04-30 20:40:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static class SkillIdExtensions
|
|
|
|
|
{
|
|
|
|
|
public static AbilityId Ability(this SkillId s) => s switch
|
|
|
|
|
{
|
|
|
|
|
SkillId.Acrobatics => AbilityId.DEX,
|
|
|
|
|
SkillId.AnimalHandling => AbilityId.WIS,
|
|
|
|
|
SkillId.Arcana => AbilityId.INT,
|
|
|
|
|
SkillId.Athletics => AbilityId.STR,
|
|
|
|
|
SkillId.Deception => AbilityId.CHA,
|
|
|
|
|
SkillId.History => AbilityId.INT,
|
|
|
|
|
SkillId.Insight => AbilityId.WIS,
|
|
|
|
|
SkillId.Intimidation => AbilityId.CHA,
|
|
|
|
|
SkillId.Investigation => AbilityId.INT,
|
|
|
|
|
SkillId.Medicine => AbilityId.WIS,
|
|
|
|
|
SkillId.Nature => AbilityId.INT,
|
|
|
|
|
SkillId.Perception => AbilityId.WIS,
|
|
|
|
|
SkillId.Performance => AbilityId.CHA,
|
|
|
|
|
SkillId.Persuasion => AbilityId.CHA,
|
|
|
|
|
SkillId.Religion => AbilityId.INT,
|
|
|
|
|
SkillId.SleightOfHand => AbilityId.DEX,
|
|
|
|
|
SkillId.Stealth => AbilityId.DEX,
|
|
|
|
|
SkillId.Survival => AbilityId.WIS,
|
2026-05-09 20:25:56 -07:00
|
|
|
|
|
|
|
|
SkillId.Brawl => AbilityId.STR,
|
|
|
|
|
SkillId.BuildRead => AbilityId.STR,
|
|
|
|
|
SkillId.Driving => AbilityId.DEX,
|
|
|
|
|
SkillId.Endurance => AbilityId.CON,
|
|
|
|
|
SkillId.Force => AbilityId.STR,
|
|
|
|
|
SkillId.Fortitude => AbilityId.CON,
|
|
|
|
|
SkillId.Hardiness => AbilityId.CON,
|
|
|
|
|
SkillId.Haulage => AbilityId.STR,
|
|
|
|
|
SkillId.LungCraft => AbilityId.CON,
|
|
|
|
|
SkillId.Marksmanship => AbilityId.DEX,
|
|
|
|
|
SkillId.PainTolerance => AbilityId.CON,
|
|
|
|
|
SkillId.ScentSpeak => AbilityId.CHA,
|
2026-04-30 20:40:51 -07:00
|
|
|
_ => throw new ArgumentOutOfRangeException(nameof(s)),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// <summary>Parses a snake_case JSON value (e.g. "animal_handling") into a SkillId.</summary>
|
|
|
|
|
public static SkillId FromJson(string raw) => raw.ToLowerInvariant() switch
|
|
|
|
|
{
|
|
|
|
|
"acrobatics" => SkillId.Acrobatics,
|
|
|
|
|
"animal_handling" => SkillId.AnimalHandling,
|
|
|
|
|
"arcana" => SkillId.Arcana,
|
|
|
|
|
"athletics" => SkillId.Athletics,
|
|
|
|
|
"deception" => SkillId.Deception,
|
|
|
|
|
"history" => SkillId.History,
|
|
|
|
|
"insight" => SkillId.Insight,
|
|
|
|
|
"intimidation" => SkillId.Intimidation,
|
|
|
|
|
"investigation" => SkillId.Investigation,
|
|
|
|
|
"medicine" => SkillId.Medicine,
|
|
|
|
|
"nature" => SkillId.Nature,
|
|
|
|
|
"perception" => SkillId.Perception,
|
|
|
|
|
"performance" => SkillId.Performance,
|
|
|
|
|
"persuasion" => SkillId.Persuasion,
|
|
|
|
|
"religion" => SkillId.Religion,
|
|
|
|
|
"sleight_of_hand" => SkillId.SleightOfHand,
|
|
|
|
|
"stealth" => SkillId.Stealth,
|
|
|
|
|
"survival" => SkillId.Survival,
|
2026-05-09 20:25:56 -07:00
|
|
|
|
|
|
|
|
"brawl" => SkillId.Brawl,
|
|
|
|
|
"build_read" => SkillId.BuildRead,
|
|
|
|
|
"driving" => SkillId.Driving,
|
|
|
|
|
"endurance" => SkillId.Endurance,
|
|
|
|
|
"force" => SkillId.Force,
|
|
|
|
|
"fortitude" => SkillId.Fortitude,
|
|
|
|
|
"hardiness" => SkillId.Hardiness,
|
|
|
|
|
"haulage" => SkillId.Haulage,
|
|
|
|
|
"lung_craft" => SkillId.LungCraft,
|
|
|
|
|
"marksmanship" => SkillId.Marksmanship,
|
|
|
|
|
"pain_tolerance" => SkillId.PainTolerance,
|
|
|
|
|
"scent_speak" => SkillId.ScentSpeak,
|
2026-04-30 20:40:51 -07:00
|
|
|
_ => throw new ArgumentException($"Unknown skill: '{raw}'"),
|
|
|
|
|
};
|
|
|
|
|
}
|