Dress people at generation from a separate apparel stream.
Wardrobes live on the person in people.json: everyday layers, textbooks per parallel, and chance-based bags from catalog data. Hauling and the carry cap are defs, not constants. The golden roster gains an items column; later family members shift because Hauling consumes extra appearance rolls. Old saves without items are dressed on load. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
namespace HSchool.People;
|
||||
|
||||
/// <summary>
|
||||
/// How much a person may carry in the bag. The curve lives on <see cref="BehaviorDef"/>;
|
||||
/// these fallbacks match vanilla so a pack without the new fields still dresses people.
|
||||
/// Worn mass does not count in this slice, and overload does not slow walking.
|
||||
/// </summary>
|
||||
public static class CarryMass
|
||||
{
|
||||
public const string Strength = "Strength";
|
||||
public const string Endurance = "Endurance";
|
||||
public const string Hauling = "Hauling";
|
||||
|
||||
public const float DefaultBase = 5f;
|
||||
public const float DefaultPerStrength = 0.08f;
|
||||
public const float DefaultPerEndurance = 0.04f;
|
||||
public const float DefaultPerHauling = 0.08f;
|
||||
|
||||
public static float Capacity(DefCatalog catalog, IReadOnlyDictionary<string, int> skills) =>
|
||||
Capacity(catalog, Skill(skills, Strength), Skill(skills, Endurance), Skill(skills, Hauling));
|
||||
|
||||
public static float Capacity(DefCatalog catalog, IReadOnlyDictionary<string, float> skills) =>
|
||||
Capacity(
|
||||
catalog,
|
||||
Skill(skills, Strength),
|
||||
Skill(skills, Endurance),
|
||||
Skill(skills, Hauling));
|
||||
|
||||
public static float Capacity(DefCatalog catalog, float strength, float endurance, float hauling)
|
||||
{
|
||||
var rules = catalog.BehaviorRules;
|
||||
var value = (rules?.CarryMassBase ?? DefaultBase)
|
||||
+ (strength * (rules?.CarryMassPerStrength ?? DefaultPerStrength))
|
||||
+ (endurance * (rules?.CarryMassPerEndurance ?? DefaultPerEndurance))
|
||||
+ (hauling * (rules?.CarryMassPerHauling ?? DefaultPerHauling));
|
||||
return MathF.Round(MathF.Max(value, 0f), 2);
|
||||
}
|
||||
|
||||
public static float Held(DefCatalog catalog, IEnumerable<InventoryItem> items)
|
||||
{
|
||||
var sum = 0f;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (!item.Location.Equals(ItemLocations.Bag, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (catalog.Things.TryGetValue(item.Def, out var def))
|
||||
{
|
||||
sum += def.Mass;
|
||||
}
|
||||
}
|
||||
|
||||
return MathF.Round(sum, 2);
|
||||
}
|
||||
|
||||
private static float Skill(IReadOnlyDictionary<string, int> skills, string name) =>
|
||||
skills.TryGetValue(name, out var value) ? value : 0f;
|
||||
|
||||
private static float Skill(IReadOnlyDictionary<string, float> skills, string name) =>
|
||||
skills.TryGetValue(name, out var value) ? value : 0f;
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
namespace HSchool.People;
|
||||
|
||||
/// <summary>
|
||||
/// Fills a person's wardrobe from a stream that does not touch looks or skills. Same person id
|
||||
/// and school seed always yield the same items, colours and places.
|
||||
/// </summary>
|
||||
public static class DressGenerator
|
||||
{
|
||||
public static Person Dress(DefCatalog catalog, Person person, int schoolSeed, int? pupilYear)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(catalog);
|
||||
ArgumentNullException.ThrowIfNull(person);
|
||||
|
||||
var age = person.AgeOn(RosterGenerator.DefaultAsOf);
|
||||
return Dress(catalog, person, schoolSeed, pupilYear, age);
|
||||
}
|
||||
|
||||
public static Person Dress(
|
||||
DefCatalog catalog,
|
||||
Person person,
|
||||
int schoolSeed,
|
||||
int? pupilYear,
|
||||
int age)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(catalog);
|
||||
ArgumentNullException.ThrowIfNull(person);
|
||||
|
||||
var rng = new Random(Seed.Mix(schoolSeed, person.Id, dayNumber: 0, Seed.ApparelSalt));
|
||||
var items = new List<InventoryItem>();
|
||||
var occupied = new HashSet<string>(StringComparer.Ordinal);
|
||||
var chance = catalog.BehaviorRules?.OptionalApparelChance ?? 0.4f;
|
||||
|
||||
foreach (var layer in RequiredLayers)
|
||||
{
|
||||
TryWear(catalog, person, age, rng, layer, occupied, items);
|
||||
}
|
||||
|
||||
foreach (var layer in OptionalLayers)
|
||||
{
|
||||
if (rng.NextDouble() < chance)
|
||||
{
|
||||
TryWear(catalog, person, age, rng, layer, occupied, items);
|
||||
}
|
||||
}
|
||||
|
||||
if (!occupied.Contains(ApparelLayers.Outer))
|
||||
{
|
||||
TryStashOuter(catalog, person, age, rng, items);
|
||||
}
|
||||
|
||||
if (person.IsStudent && HasPhysicalEducation(catalog, pupilYear))
|
||||
{
|
||||
GrantPeKit(catalog, person, age, rng, items);
|
||||
}
|
||||
|
||||
var capacity = CarryMass.Capacity(catalog, person.Skills);
|
||||
var held = 0f;
|
||||
GrantChanceCarry(catalog, person, age, rng, items, ref held, capacity);
|
||||
if (person.IsStudent && pupilYear is { } year)
|
||||
{
|
||||
GrantTextbooks(catalog, year, rng, items, ref held, capacity);
|
||||
}
|
||||
|
||||
return person with { Items = items };
|
||||
}
|
||||
|
||||
public static Person EnsureDressed(
|
||||
DefCatalog catalog,
|
||||
Person person,
|
||||
int schoolSeed,
|
||||
int? pupilYear,
|
||||
int age)
|
||||
{
|
||||
if (person.Items.Count > 0)
|
||||
{
|
||||
return person;
|
||||
}
|
||||
|
||||
return Dress(catalog, person, schoolSeed, pupilYear, age);
|
||||
}
|
||||
|
||||
public static Roster EnsureRoster(DefCatalog catalog, Roster roster, int schoolSeed, DateTime asOf)
|
||||
{
|
||||
var years = roster.Classes.ToDictionary(
|
||||
schoolClass => schoolClass.Id,
|
||||
schoolClass => schoolClass.Year,
|
||||
StringComparer.Ordinal);
|
||||
var people = new Person[roster.People.Count];
|
||||
for (var i = 0; i < roster.People.Count; i++)
|
||||
{
|
||||
var person = roster.People[i];
|
||||
int? year = person.ClassId is { } classId && years.TryGetValue(classId, out var value)
|
||||
? value
|
||||
: null;
|
||||
people[i] = EnsureDressed(catalog, person, schoolSeed, year, person.AgeOn(asOf));
|
||||
}
|
||||
|
||||
return new Roster(people, roster.Families, roster.Classes);
|
||||
}
|
||||
|
||||
public static ApplicantPool EnsurePool(
|
||||
DefCatalog catalog,
|
||||
ApplicantPool pool,
|
||||
Roster roster,
|
||||
int schoolSeed,
|
||||
DateTime asOf)
|
||||
{
|
||||
var rosterPeople = roster.People.ToDictionary(person => person.Id, StringComparer.Ordinal);
|
||||
var applicants = new Applicant[pool.Applicants.Count];
|
||||
for (var i = 0; i < pool.Applicants.Count; i++)
|
||||
{
|
||||
var applicant = pool.Applicants[i];
|
||||
if (rosterPeople.TryGetValue(applicant.Person.Id, out var member))
|
||||
{
|
||||
applicants[i] = applicant with { Person = member };
|
||||
continue;
|
||||
}
|
||||
|
||||
applicants[i] = applicant with
|
||||
{
|
||||
Person = EnsureDressed(
|
||||
catalog,
|
||||
applicant.Person,
|
||||
schoolSeed,
|
||||
pupilYear: null,
|
||||
applicant.Person.AgeOn(asOf)),
|
||||
};
|
||||
}
|
||||
|
||||
return pool with { Applicants = applicants };
|
||||
}
|
||||
|
||||
public static bool NeedsDressing(Roster roster, ApplicantPool? pool)
|
||||
{
|
||||
foreach (var person in roster.People)
|
||||
{
|
||||
if (person.Items.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (pool is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var applicant in pool.Applicants)
|
||||
{
|
||||
if (applicant.Person.Items.Count == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds textbooks for subjects the pupil just became old enough for. Existing clothes stay.
|
||||
/// </summary>
|
||||
public static Person EnsureYearTextbooks(DefCatalog catalog, Person person, int year)
|
||||
{
|
||||
if (!person.IsStudent)
|
||||
{
|
||||
return person;
|
||||
}
|
||||
|
||||
var items = person.Items.ToList();
|
||||
var held = CarryMass.Held(catalog, items);
|
||||
var capacity = CarryMass.Capacity(catalog, person.Skills);
|
||||
var rng = new Random(0);
|
||||
GrantTextbooks(catalog, year, rng, items, ref held, capacity);
|
||||
return person with { Items = items };
|
||||
}
|
||||
|
||||
private static readonly string[] RequiredLayers =
|
||||
[ApparelLayers.Underwear, ApparelLayers.Socks, ApparelLayers.Bottom, ApparelLayers.Top, ApparelLayers.Shoes];
|
||||
|
||||
private static readonly string[] OptionalLayers =
|
||||
[ApparelLayers.OverTop, ApparelLayers.Outer, ApparelLayers.Head, ApparelLayers.Accessory];
|
||||
|
||||
private static void TryWear(
|
||||
DefCatalog catalog,
|
||||
Person person,
|
||||
int age,
|
||||
Random rng,
|
||||
string layer,
|
||||
HashSet<string> occupied,
|
||||
List<InventoryItem> items)
|
||||
{
|
||||
if (occupied.Contains(layer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var candidates = Apparel(catalog, person.Female, age, everyday: true)
|
||||
.Where(def => def.Layers.Contains(layer, StringComparer.Ordinal)
|
||||
&& def.Layers.All(candidate => !occupied.Contains(candidate)))
|
||||
.ToList();
|
||||
if (candidates.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pick = candidates[rng.Next(candidates.Count)];
|
||||
var color = PickColor(catalog, pick, rng, worn: true);
|
||||
items.Add(new InventoryItem(pick.DefName, color, Condition: 1, ItemLocations.Worn));
|
||||
foreach (var taken in pick.Layers)
|
||||
{
|
||||
occupied.Add(taken);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryStashOuter(
|
||||
DefCatalog catalog,
|
||||
Person person,
|
||||
int age,
|
||||
Random rng,
|
||||
List<InventoryItem> items)
|
||||
{
|
||||
var candidates = Apparel(catalog, person.Female, age, everyday: true)
|
||||
.Where(def => def.Layers.Contains(ApparelLayers.Outer, StringComparer.Ordinal))
|
||||
.ToList();
|
||||
if (candidates.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pick = candidates[rng.Next(candidates.Count)];
|
||||
var color = PickColor(catalog, pick, rng, worn: false);
|
||||
items.Add(new InventoryItem(pick.DefName, color, Condition: 1, ItemLocations.Home));
|
||||
}
|
||||
|
||||
private static void GrantPeKit(
|
||||
DefCatalog catalog,
|
||||
Person person,
|
||||
int age,
|
||||
Random rng,
|
||||
List<InventoryItem> items)
|
||||
{
|
||||
foreach (var def in Apparel(catalog, person.Female, age, everyday: false).Where(candidate => candidate.Pe))
|
||||
{
|
||||
var color = PickColor(catalog, def, rng, worn: false);
|
||||
items.Add(new InventoryItem(def.DefName, color, Condition: 1, ItemLocations.Home));
|
||||
}
|
||||
}
|
||||
|
||||
private static void GrantChanceCarry(
|
||||
DefCatalog catalog,
|
||||
Person person,
|
||||
int age,
|
||||
Random rng,
|
||||
List<InventoryItem> items,
|
||||
ref float held,
|
||||
float capacity)
|
||||
{
|
||||
foreach (var def in catalog.Things.Values
|
||||
.Where(thing => !thing.Abstract
|
||||
&& thing.Portable
|
||||
&& thing.Layers.Count == 0
|
||||
&& thing.CarryChance > 0)
|
||||
.OrderBy(thing => thing.DefName, StringComparer.Ordinal))
|
||||
{
|
||||
if (!AgeFits(def, age) || !SexFits(def, person.Female))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rng.NextDouble() >= def.CarryChance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var color = PickColor(catalog, def, rng, worn: false);
|
||||
Place(catalog, items, def, color, subject: null, ref held, capacity);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GrantTextbooks(
|
||||
DefCatalog catalog,
|
||||
int year,
|
||||
Random rng,
|
||||
List<InventoryItem> items,
|
||||
ref float held,
|
||||
float capacity)
|
||||
{
|
||||
var textbook = catalog.Things.Values
|
||||
.Where(thing => !thing.Abstract
|
||||
&& thing.Portable
|
||||
&& thing.Layers.Count == 0
|
||||
&& thing.CarryChance <= 0)
|
||||
.OrderBy(thing => thing.DefName, StringComparer.Ordinal)
|
||||
.FirstOrDefault();
|
||||
if (textbook is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var have = items
|
||||
.Where(item => item.Def.Equals(textbook.DefName, StringComparison.Ordinal) && item.Subject is not null)
|
||||
.Select(item => item.Subject!)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
foreach (var subject in catalog.Subjects.Values
|
||||
.Where(def => !def.Abstract && year >= def.Grades.Min && year <= def.Grades.Max)
|
||||
.OrderBy(def => def.DefName, StringComparer.Ordinal))
|
||||
{
|
||||
if (!have.Add(subject.DefName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var color = PickColor(catalog, textbook, rng, worn: false);
|
||||
Place(catalog, items, textbook, color, subject.DefName, ref held, capacity);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Place(
|
||||
DefCatalog catalog,
|
||||
List<InventoryItem> items,
|
||||
ThingDef def,
|
||||
string? color,
|
||||
string? subject,
|
||||
ref float held,
|
||||
float capacity)
|
||||
{
|
||||
var location = held + def.Mass <= capacity ? ItemLocations.Bag : ItemLocations.Home;
|
||||
items.Add(new InventoryItem(def.DefName, color, Condition: 1, location, subject));
|
||||
if (location.Equals(ItemLocations.Bag, StringComparison.Ordinal))
|
||||
{
|
||||
held = MathF.Round(held + def.Mass, 2);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<ThingDef> Apparel(DefCatalog catalog, bool female, int age, bool everyday)
|
||||
{
|
||||
foreach (var def in catalog.Things.Values.OrderBy(thing => thing.DefName, StringComparer.Ordinal))
|
||||
{
|
||||
if (def.Abstract || def.Layers.Count == 0 || !AgeFits(def, age) || !SexFits(def, female))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (everyday)
|
||||
{
|
||||
if (def.Pe)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (def.SkirtLength is not null
|
||||
&& def.SkirtLength.Equals(SkirtLengths.Short, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
yield return def;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? PickColor(DefCatalog catalog, ThingDef def, Random rng, bool worn)
|
||||
{
|
||||
if (def.Colors.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var pool = def.Colors.ToList();
|
||||
if (worn)
|
||||
{
|
||||
var quiet = def.Colors
|
||||
.Where(id => catalog.Colors.TryGetValue(id, out var color)
|
||||
&& !color.Tags.Contains(ColorTags.Bright, StringComparer.Ordinal))
|
||||
.ToList();
|
||||
if (quiet.Count > 0)
|
||||
{
|
||||
pool = quiet;
|
||||
}
|
||||
}
|
||||
|
||||
pool.Sort(StringComparer.Ordinal);
|
||||
return pool[rng.Next(pool.Count)];
|
||||
}
|
||||
|
||||
private static bool AgeFits(ThingDef def, int age) =>
|
||||
def.Age is not { } range || (age >= range.Min && age <= range.Max);
|
||||
|
||||
private static bool SexFits(ThingDef def, bool female)
|
||||
{
|
||||
if (def.Sex is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var want = female ? "female" : "male";
|
||||
return def.Sex.Equals(want, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool HasPhysicalEducation(DefCatalog catalog, int? pupilYear)
|
||||
{
|
||||
if (pupilYear is not { } year)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return catalog.Subjects.Values.Any(subject =>
|
||||
!subject.Abstract
|
||||
&& subject.DefName.Equals("PhysicalEducation", StringComparison.Ordinal)
|
||||
&& year >= subject.Grades.Min
|
||||
&& year <= subject.Grades.Max);
|
||||
}
|
||||
}
|
||||
@@ -78,40 +78,50 @@ internal static class FamilyFactory
|
||||
{
|
||||
parentIds.Add(fatherId);
|
||||
members.Add(
|
||||
RollAdult(
|
||||
DressGenerator.Dress(
|
||||
catalog,
|
||||
names,
|
||||
rng,
|
||||
fatherId,
|
||||
familyId,
|
||||
female: false,
|
||||
fatherBirth,
|
||||
asOf,
|
||||
surname,
|
||||
fatherGiven,
|
||||
NameGrammar.Patronymic(fatherPatronymicSource.Form, female: false, names.PatronymicRule),
|
||||
isParent: childDrafts.Length > 0,
|
||||
nativeLanguage));
|
||||
RollAdult(
|
||||
catalog,
|
||||
names,
|
||||
rng,
|
||||
fatherId,
|
||||
familyId,
|
||||
female: false,
|
||||
fatherBirth,
|
||||
asOf,
|
||||
surname,
|
||||
fatherGiven,
|
||||
NameGrammar.Patronymic(fatherPatronymicSource.Form, female: false, names.PatronymicRule),
|
||||
isParent: childDrafts.Length > 0,
|
||||
nativeLanguage),
|
||||
schoolSeed,
|
||||
pupilYear: null,
|
||||
SchoolYears.AgeYears(fatherBirth, asOf)));
|
||||
}
|
||||
|
||||
if (hasMother)
|
||||
{
|
||||
parentIds.Add(motherId);
|
||||
members.Add(
|
||||
RollAdult(
|
||||
DressGenerator.Dress(
|
||||
catalog,
|
||||
names,
|
||||
rng,
|
||||
motherId,
|
||||
familyId,
|
||||
female: true,
|
||||
motherBirth,
|
||||
asOf,
|
||||
surname,
|
||||
motherGiven,
|
||||
NameGrammar.Patronymic(motherPatronymicSource.Form, female: true, names.PatronymicRule),
|
||||
isParent: childDrafts.Length > 0,
|
||||
nativeLanguage));
|
||||
RollAdult(
|
||||
catalog,
|
||||
names,
|
||||
rng,
|
||||
motherId,
|
||||
familyId,
|
||||
female: true,
|
||||
motherBirth,
|
||||
asOf,
|
||||
surname,
|
||||
motherGiven,
|
||||
NameGrammar.Patronymic(motherPatronymicSource.Form, female: true, names.PatronymicRule),
|
||||
isParent: childDrafts.Length > 0,
|
||||
nativeLanguage),
|
||||
schoolSeed,
|
||||
pupilYear: null,
|
||||
SchoolYears.AgeYears(motherBirth, asOf)));
|
||||
}
|
||||
|
||||
var childIds = new List<string>(childDrafts.Length);
|
||||
@@ -121,17 +131,22 @@ internal static class FamilyFactory
|
||||
var id = $"{familyId}.c{i}";
|
||||
childIds.Add(id);
|
||||
members.Add(
|
||||
RollChild(
|
||||
DressGenerator.Dress(
|
||||
catalog,
|
||||
names,
|
||||
rng,
|
||||
id,
|
||||
familyId,
|
||||
draft,
|
||||
asOf,
|
||||
surname,
|
||||
fatherGiven.Form,
|
||||
nativeLanguage));
|
||||
RollChild(
|
||||
catalog,
|
||||
names,
|
||||
rng,
|
||||
id,
|
||||
familyId,
|
||||
draft,
|
||||
asOf,
|
||||
surname,
|
||||
fatherGiven.Form,
|
||||
nativeLanguage),
|
||||
schoolSeed,
|
||||
draft.Seat.Year,
|
||||
SchoolYears.AgeYears(draft.Birth, asOf)));
|
||||
}
|
||||
|
||||
var family = new Family(familyId, parentIds, childIds, childIds.Count, fatherGiven.Form, surname.Male);
|
||||
@@ -151,6 +166,7 @@ internal static class FamilyFactory
|
||||
DateTime yearStart,
|
||||
DateTime asOf,
|
||||
int childIndex,
|
||||
int schoolSeed,
|
||||
string? nativeLanguage = null)
|
||||
{
|
||||
var fatherGiven = FatherGivenOf(family, members);
|
||||
@@ -184,23 +200,28 @@ internal static class FamilyFactory
|
||||
surnameCases,
|
||||
PatronymicTable(patronymic, female));
|
||||
|
||||
return new Person
|
||||
{
|
||||
Id = $"{family.Id}.c{childIndex}",
|
||||
FamilyId = family.Id,
|
||||
Female = female,
|
||||
BirthDate = DateTime.SpecifyKind(birth, DateTimeKind.Utc),
|
||||
Name = name,
|
||||
IsStudent = true,
|
||||
IsStaff = false,
|
||||
IsParent = false,
|
||||
ClassId = seat.ClassId,
|
||||
Numbers = numbers,
|
||||
Choices = choices,
|
||||
Skills = skills,
|
||||
Traits = traits,
|
||||
Needs = needs,
|
||||
};
|
||||
return DressGenerator.Dress(
|
||||
catalog,
|
||||
new Person
|
||||
{
|
||||
Id = $"{family.Id}.c{childIndex}",
|
||||
FamilyId = family.Id,
|
||||
Female = female,
|
||||
BirthDate = DateTime.SpecifyKind(birth, DateTimeKind.Utc),
|
||||
Name = name,
|
||||
IsStudent = true,
|
||||
IsStaff = false,
|
||||
IsParent = false,
|
||||
ClassId = seat.ClassId,
|
||||
Numbers = numbers,
|
||||
Choices = choices,
|
||||
Skills = skills,
|
||||
Traits = traits,
|
||||
Needs = needs,
|
||||
},
|
||||
schoolSeed,
|
||||
seat.Year,
|
||||
age);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -226,20 +247,25 @@ internal static class FamilyFactory
|
||||
var birth = asOf.AddYears(-(24 + rng.Next(38))).AddDays(-rng.Next(365));
|
||||
|
||||
var id = $"{familyId}.p0";
|
||||
var person = RollAdult(
|
||||
var person = DressGenerator.Dress(
|
||||
catalog,
|
||||
names,
|
||||
rng,
|
||||
id,
|
||||
familyId,
|
||||
female,
|
||||
birth,
|
||||
asOf,
|
||||
surname,
|
||||
given,
|
||||
NameGrammar.Patronymic(patronymicSource.Form, female, names.PatronymicRule),
|
||||
isParent: false,
|
||||
nativeLanguage);
|
||||
RollAdult(
|
||||
catalog,
|
||||
names,
|
||||
rng,
|
||||
id,
|
||||
familyId,
|
||||
female,
|
||||
birth,
|
||||
asOf,
|
||||
surname,
|
||||
given,
|
||||
NameGrammar.Patronymic(patronymicSource.Form, female, names.PatronymicRule),
|
||||
isParent: false,
|
||||
nativeLanguage),
|
||||
schoolSeed,
|
||||
pupilYear: null,
|
||||
SchoolYears.AgeYears(birth, asOf));
|
||||
|
||||
return (new Family(familyId, [id], [], NextChild: 0, FatherGiven: string.Empty, Surname: surname.Male), person);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace HSchool.People;
|
||||
|
||||
/// <summary>Where an instance lives. One place at a time — worn, bag, locker or home.</summary>
|
||||
public static class ItemLocations
|
||||
{
|
||||
public const string Worn = "worn";
|
||||
public const string Bag = "bag";
|
||||
public const string Locker = "locker";
|
||||
public const string Home = "home";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One thing on a person. <see cref="Subject"/> is set only on a textbook instance — the def is
|
||||
/// shared. Condition is 1 at birth; wear is a later phase.
|
||||
/// </summary>
|
||||
public sealed record InventoryItem(
|
||||
string Def,
|
||||
string? Color,
|
||||
float Condition,
|
||||
string Location,
|
||||
string? Subject = null);
|
||||
@@ -46,6 +46,9 @@ public sealed record Person
|
||||
/// <summary>Hourly rate frozen at hire. Null until the person is staff.</summary>
|
||||
public float? HourlyWageAsk { get; init; }
|
||||
|
||||
/// <summary>Worn, bag, locker and home. Empty on rosters written before clothes existed.</summary>
|
||||
public IReadOnlyList<InventoryItem> Items { get; init; } = [];
|
||||
|
||||
public int AgeOn(DateTime asOf) => SchoolYears.AgeYears(BirthDate, asOf);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ public static class Seed
|
||||
public const int SkillGrantSalt = 8;
|
||||
public const int NativeLanguageSalt = 9;
|
||||
public const int ClimatePresetSalt = 10;
|
||||
public const int ApparelSalt = 11;
|
||||
|
||||
/// <summary>A stream that belongs to the school rather than to one family.</summary>
|
||||
public static int ForSchool(int schoolSeed, int salt) => Mix(schoolSeed, familyIndex: -1, salt);
|
||||
|
||||
@@ -175,19 +175,22 @@ public static class YearlyIntake
|
||||
}
|
||||
|
||||
var rng = new Random(Seed.Mix(schoolSeed, person.Id, when.Year, Seed.SkillGrantSalt));
|
||||
people[id] = person with
|
||||
{
|
||||
Skills = PersonSampler.EnsurePupilYear(
|
||||
catalog,
|
||||
names,
|
||||
rng,
|
||||
person.AgeOn(when),
|
||||
person.Choices,
|
||||
person.Traits,
|
||||
person.Skills,
|
||||
year,
|
||||
nativeLanguage),
|
||||
};
|
||||
people[id] = DressGenerator.EnsureYearTextbooks(
|
||||
catalog,
|
||||
person with
|
||||
{
|
||||
Skills = PersonSampler.EnsurePupilYear(
|
||||
catalog,
|
||||
names,
|
||||
rng,
|
||||
person.AgeOn(when),
|
||||
person.Choices,
|
||||
person.Traits,
|
||||
person.Skills,
|
||||
year,
|
||||
nativeLanguage),
|
||||
},
|
||||
year);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +239,7 @@ public static class YearlyIntake
|
||||
|
||||
var childIndex = family.NextChildIndex;
|
||||
var rng = new Random(Seed.Mix(schoolSeed, index, Seed.IntakeSalt + yearStart.Year * 10 + childIndex));
|
||||
var child = FamilyFactory.AddChild(catalog, names, rng, family, members, seats[cursor], yearStart, asOf, childIndex, nativeLanguage);
|
||||
var child = FamilyFactory.AddChild(catalog, names, rng, family, members, seats[cursor], yearStart, asOf, childIndex, schoolSeed, nativeLanguage);
|
||||
people[child.Id] = child;
|
||||
var familyAt = families.FindIndex(candidate => candidate.Id.Equals(family.Id, StringComparison.Ordinal));
|
||||
families[familyAt] = family with
|
||||
|
||||
Reference in New Issue
Block a user