class_name Races extends RefCounted ## The 4 races (races-and-classes spec §3). FEATURES ONLY — no race touches an ## attribute score, which is what keeps the roll + spend pure. ## ## nightsight / claws / keen_scent are FLAGS, not numbers. Combat (M5) and the DM's ## exploration checks consume them; creation only stores them. ## ## The dwarf's +2 is SITUATIONAL (vs poison and the affliction track), so it is NOT ## a flat save bonus — baking a conditional into a flat number is how a sheet starts ## lying. It is exposed separately as poison_save_bonus(). const IDS := ["human", "elf", "dwarf", "beastfolk"] const TABLE := { "human": { "save_bonus": 1, "granted_skills": [], "bonus_skill_choice": true, "poison_save_bonus": 0, "flags": {"nightsight": false, "claws": false, "keen_scent": false}, }, "elf": { "save_bonus": 0, "granted_skills": ["perception"], "bonus_skill_choice": false, "poison_save_bonus": 0, "flags": {"nightsight": true, "claws": false, "keen_scent": false}, }, "dwarf": { "save_bonus": 0, "granted_skills": [], "bonus_skill_choice": false, "poison_save_bonus": 2, "flags": {"nightsight": true, "claws": false, "keen_scent": false}, }, "beastfolk": { "save_bonus": 0, "granted_skills": [], "bonus_skill_choice": false, "poison_save_bonus": 0, "flags": {"nightsight": false, "claws": true, "keen_scent": true}, }, } static func exists(id: String) -> bool: return TABLE.has(id) static func save_bonus(id: String) -> int: return int(TABLE.get(id, {}).get("save_bonus", 0)) static func granted_skills(id: String) -> Array: return TABLE.get(id, {}).get("granted_skills", []) static func wants_bonus_skill(id: String) -> bool: return bool(TABLE.get(id, {}).get("bonus_skill_choice", false)) static func poison_save_bonus(id: String) -> int: return int(TABLE.get(id, {}).get("poison_save_bonus", 0)) static func flag(id: String, name: String) -> bool: return bool(TABLE.get(id, {}).get("flags", {}).get(name, false))