namespace HSchool.Content; /// Roles the generator and traits talk about. Code, not a def — new roles are a rebuild. public static class PersonRoles { public const string Student = "student"; public const string Staff = "staff"; public const string Parent = "parent"; public static bool IsKnown(string role) => role.Equals(Student, StringComparison.OrdinalIgnoreCase) || role.Equals(Staff, StringComparison.OrdinalIgnoreCase) || role.Equals(Parent, StringComparison.OrdinalIgnoreCase); } /// /// Derived body type, not a . Skill limits may name it the same /// way they name hair colour; the generator computes it from height and weight. /// public static class BodyBuilds { public const string Attribute = "Build"; /// /// The two numeric attributes the build is computed from. They are named in code, so a pack /// that renames or drops them is rejected at load rather than quietly producing an average /// build for everybody. /// public const string HeightAttribute = "Height"; public const string WeightAttribute = "Weight"; public const string Skinny = "Skinny"; public const string Average = "Average"; public const string Athletic = "Athletic"; public const string Heavy = "Heavy"; public const string Obese = "Obese"; public static readonly IReadOnlyList Values = [Skinny, Average, Athletic, Heavy, Obese]; public static bool IsKnown(string value) => Values.Any(candidate => candidate.Equals(value, StringComparison.Ordinal)); /// /// WHO-ish bands with a fit slice in the healthy range so Athletic is derived, not rolled. /// Height in centimetres, weight in kilograms. /// public static string FromHeightAndWeight(int heightCm, int weightKg) { if (heightCm <= 0) { return Average; } var metres = heightCm / 100d; var bmi = weightKg / (metres * metres); if (bmi < 18.5) { return Skinny; } if (bmi < 22.0) { return Average; } if (bmi < 25.0) { return Athletic; } if (bmi < 30.0) { return Heavy; } return Obese; } } public sealed class IntRange { public int Min { get; init; } public int Max { get; init; } = 100; } public sealed class StatDistribution { public float Mean { get; init; } public float StdDev { get; init; } = 1; } public sealed class AgeMeanPoint { public int Age { get; init; } public float Mean { get; init; } } public sealed class BodySkillLimit { public required string Attribute { get; init; } public string? Value { get; init; } public int? Min { get; init; } public int? Max { get; init; } } public sealed class SkillDef : Def { public IntRange Range { get; init; } = new(); public StatDistribution? Distribution { get; init; } public IReadOnlyList AgeMeans { get; init; } = []; public IReadOnlyList BodyLimits { get; init; } = []; } public sealed class SubjectSkillShare { public required string Skill { get; init; } public float Share { get; init; } } public sealed class SubjectDef : Def { public IntRange Grades { get; init; } = new() { Min = 1, Max = 11 }; public int HoursPerWeek { get; init; } public IReadOnlyList Skills { get; init; } = []; /// /// RoomDef the lesson needs. Null means the class's homeroom. PE uses a gym, informatics a lab. /// public string? Room { get; init; } } public sealed class TraitSkillModifier { public required string Skill { get; init; } public int Offset { get; init; } } public sealed class TraitDef : Def { public int Weight { get; init; } = 1; public IReadOnlyList Incompatible { get; init; } = []; /// Empty means every role. Values are ids. public IReadOnlyList Roles { get; init; } = []; public IntRange? Age { get; init; } public IReadOnlyList SkillModifiers { get; init; } = []; /// /// Added to the hourly wage ask. Positive means the person wants more at the same skills. /// public float WageAsk { get; init; } } public sealed class StaffingDef : Def { public int PoolSize { get; init; } public float StayChance { get; init; } public float ParentChance { get; init; } public float HourlyWageBase { get; init; } public float HourlyWagePerSkill { get; init; } /// /// One full rate. A hired person is paid for at least this many hours even with nothing /// assigned; hours beyond it are paid on top. /// public float BaseWeeklyHours { get; init; } /// /// Most hours one person can carry. A subject whose curriculum needs more than this needs a /// second teacher — the hours are then shared between them. /// public float MaxWeeklyHours { get; init; } public float WeeksPerMonth { get; init; } } public enum BodyAttributeKind { Number, Choice, } public sealed class SexAgeDistribution { /// male, female, or omit for both. public string? Sex { get; init; } public int? AgeMin { get; init; } public int? AgeMax { get; init; } public required StatDistribution Distribution { get; init; } public IntRange? Range { get; init; } } public sealed class WeightedOption { public required string Value { get; init; } public int Weight { get; init; } = 1; public string? Sex { get; init; } public int? AgeMin { get; init; } public int? AgeMax { get; init; } } public sealed class BodyAttributeDef : Def { public BodyAttributeKind Kind { get; init; } public IReadOnlyList Distributions { get; init; } = []; public IReadOnlyList Options { get; init; } = []; } public sealed class NeedDef : Def { public float Initial { get; init; } = 1; public float DecayPerHour { get; init; } public float Min { get; init; } public float Max { get; init; } = 1; } public sealed class CaseTable { public required string Nom { get; init; } public required string Gen { get; init; } public required string Dat { get; init; } public required string Acc { get; init; } public required string Ins { get; init; } public required string Pre { get; init; } public string this[GrammaticalCase grammaticalCase] => grammaticalCase switch { GrammaticalCase.Nominative => Nom, GrammaticalCase.Genitive => Gen, GrammaticalCase.Dative => Dat, GrammaticalCase.Accusative => Acc, GrammaticalCase.Instrumental => Ins, GrammaticalCase.Prepositional => Pre, _ => Nom, }; } public sealed class GivenNameEntry { public required string Form { get; init; } public string? Declension { get; init; } public CaseTable? Cases { get; init; } } public sealed class SurnameEntry { public required string Male { get; init; } public required string Female { get; init; } public string? Declension { get; init; } public CaseTable? MaleCases { get; init; } public CaseTable? FemaleCases { get; init; } } public sealed class NameSetDef : Def { public string PatronymicRule { get; init; } = NameGrammar.SlavicPatronymic; public string DefaultGivenDeclension { get; init; } = NameGrammar.Hard; public string DefaultSurnameDeclension { get; init; } = NameGrammar.Ov; public IReadOnlyList MaleGiven { get; init; } = []; public IReadOnlyList FemaleGiven { get; init; } = []; public IReadOnlyList Surnames { get; init; } = []; }