Enhance school creation functionality by introducing support for name sets in the API and UI. Update the catalog to include skills, traits, body attributes, needs, and name sets, improving character generation capabilities. Revise localization strings for better user guidance and update tests to validate the new name set functionality and ensure robustness in school creation processes.
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
namespace HSchool.Content;
|
||||
|
||||
/// <summary>Roles the generator and traits talk about. Code, not a def — new roles are a rebuild.</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Derived body type, not a <see cref="BodyAttributeDef"/>. Skill limits may name it the same
|
||||
/// way they name hair colour; the generator computes it from height and weight.
|
||||
/// </summary>
|
||||
public static class BodyBuilds
|
||||
{
|
||||
public const string Attribute = "Build";
|
||||
|
||||
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<string> Values =
|
||||
[Skinny, Average, Athletic, Heavy, Obese];
|
||||
|
||||
public static bool IsKnown(string value) =>
|
||||
Values.Any(candidate => candidate.Equals(value, StringComparison.Ordinal));
|
||||
|
||||
/// <summary>
|
||||
/// WHO-ish bands with a fit slice in the healthy range so <c>Athletic</c> is derived, not rolled.
|
||||
/// Height in centimetres, weight in kilograms.
|
||||
/// </summary>
|
||||
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<AgeMeanPoint> AgeMeans { get; init; } = [];
|
||||
|
||||
public IReadOnlyList<BodySkillLimit> BodyLimits { 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<string> Incompatible { get; init; } = [];
|
||||
|
||||
/// <summary>Empty means every role. Values are <see cref="PersonRoles"/> ids.</summary>
|
||||
public IReadOnlyList<string> Roles { get; init; } = [];
|
||||
|
||||
public IntRange? Age { get; init; }
|
||||
|
||||
public IReadOnlyList<TraitSkillModifier> SkillModifiers { get; init; } = [];
|
||||
}
|
||||
|
||||
public enum BodyAttributeKind
|
||||
{
|
||||
Number,
|
||||
Choice,
|
||||
}
|
||||
|
||||
public sealed class SexAgeDistribution
|
||||
{
|
||||
/// <summary><c>male</c>, <c>female</c>, or omit for both.</summary>
|
||||
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<SexAgeDistribution> Distributions { get; init; } = [];
|
||||
|
||||
public IReadOnlyList<WeightedOption> 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<GivenNameEntry> MaleGiven { get; init; } = [];
|
||||
|
||||
public IReadOnlyList<GivenNameEntry> FemaleGiven { get; init; } = [];
|
||||
|
||||
public IReadOnlyList<SurnameEntry> Surnames { get; init; } = [];
|
||||
}
|
||||
Reference in New Issue
Block a user