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:
@@ -61,7 +61,27 @@ internal sealed record PersonCardResponse(
|
||||
IReadOnlyList<NeedStatResponse> Needs,
|
||||
string? Activity,
|
||||
string? ActivityLabel,
|
||||
PersonFamilyResponse Family);
|
||||
PersonFamilyResponse Family,
|
||||
IReadOnlyList<WornItemResponse> Worn,
|
||||
IReadOnlyList<CarriedItemResponse> Carried,
|
||||
float CarryMass,
|
||||
float CarryCapacity);
|
||||
|
||||
internal sealed record WornItemResponse(
|
||||
string DefName,
|
||||
string Label,
|
||||
string? Color,
|
||||
string? ColorLabel,
|
||||
IReadOnlyList<DefLabelResponse> Layers);
|
||||
|
||||
internal sealed record CarriedItemResponse(
|
||||
string DefName,
|
||||
string Label,
|
||||
string? Color,
|
||||
string? ColorLabel,
|
||||
string? Subject,
|
||||
string? SubjectLabel,
|
||||
float Mass);
|
||||
|
||||
internal sealed record LabeledStatResponse(string Id, string Label, string Value);
|
||||
|
||||
|
||||
@@ -81,7 +81,11 @@ internal static class PersonCardReader
|
||||
Needs(needs, catalog, locale),
|
||||
activityId,
|
||||
activityLabel,
|
||||
Family(roster, person));
|
||||
Family(roster, person),
|
||||
Worn(person, catalog, locale),
|
||||
Carried(person, catalog, locale),
|
||||
catalog is null ? 0f : CarryMass.Held(catalog, person.Items),
|
||||
Capacity(person, skills, catalog));
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, float>? LiveNeeds(World world, string personId)
|
||||
@@ -260,6 +264,99 @@ internal static class PersonCardReader
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<WornItemResponse> Worn(Person person, DefCatalog? catalog, string locale)
|
||||
{
|
||||
var rows = new List<WornItemResponse>();
|
||||
foreach (var item in person.Items)
|
||||
{
|
||||
if (!item.Location.Equals(ItemLocations.Worn, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
IReadOnlyList<string> layerIds = [];
|
||||
if (catalog is not null && catalog.Things.TryGetValue(item.Def, out var def))
|
||||
{
|
||||
layerIds = def.Layers;
|
||||
}
|
||||
|
||||
var layers = layerIds
|
||||
.Select(layer => new DefLabelResponse(layer, catalog?.Text(locale, layer) ?? layer))
|
||||
.ToArray();
|
||||
rows.Add(new WornItemResponse(
|
||||
item.Def,
|
||||
ThingLabel(catalog, locale, item.Def),
|
||||
item.Color,
|
||||
ColorLabel(catalog, locale, item.Color),
|
||||
layers));
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<CarriedItemResponse> Carried(Person person, DefCatalog? catalog, string locale)
|
||||
{
|
||||
var rows = new List<CarriedItemResponse>();
|
||||
foreach (var item in person.Items)
|
||||
{
|
||||
if (!item.Location.Equals(ItemLocations.Bag, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var mass = catalog is not null && catalog.Things.TryGetValue(item.Def, out var def) ? def.Mass : 0f;
|
||||
string? subjectLabel = null;
|
||||
if (item.Subject is not null)
|
||||
{
|
||||
subjectLabel = catalog is not null && catalog.Subjects.TryGetValue(item.Subject, out var subject)
|
||||
? catalog.Label(locale, subject)
|
||||
: item.Subject;
|
||||
}
|
||||
|
||||
rows.Add(new CarriedItemResponse(
|
||||
item.Def,
|
||||
ThingLabel(catalog, locale, item.Def),
|
||||
item.Color,
|
||||
ColorLabel(catalog, locale, item.Color),
|
||||
item.Subject,
|
||||
subjectLabel,
|
||||
mass));
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static string ThingLabel(DefCatalog? catalog, string locale, string defName)
|
||||
{
|
||||
if (catalog is not null && catalog.Things.TryGetValue(defName, out var def))
|
||||
{
|
||||
return catalog.Label(locale, def);
|
||||
}
|
||||
|
||||
return defName;
|
||||
}
|
||||
|
||||
private static string? ColorLabel(DefCatalog? catalog, string locale, string? color) =>
|
||||
color is null ? null : catalog?.Text(locale, color) ?? color;
|
||||
|
||||
private static float Capacity(
|
||||
Person person,
|
||||
IReadOnlyDictionary<string, float>? live,
|
||||
DefCatalog? catalog)
|
||||
{
|
||||
if (catalog is null)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
if (live is not null)
|
||||
{
|
||||
return CarryMass.Capacity(catalog, live);
|
||||
}
|
||||
|
||||
return CarryMass.Capacity(catalog, person.Skills);
|
||||
}
|
||||
|
||||
private static PersonFamilyResponse Family(Roster roster, Person person)
|
||||
{
|
||||
var family = roster.Families.FirstOrDefault(candidate => candidate.Id.Equals(person.FamilyId, StringComparison.Ordinal));
|
||||
|
||||
@@ -924,6 +924,13 @@ internal sealed class SchoolWorker
|
||||
}
|
||||
}
|
||||
|
||||
if (DressGenerator.NeedsDressing(roster, applicants))
|
||||
{
|
||||
roster = DressGenerator.EnsureRoster(catalog, roster, seed, school.Clock.Time);
|
||||
applicants = DressGenerator.EnsurePool(catalog, applicants, roster, seed, school.Clock.Time);
|
||||
generated = true;
|
||||
}
|
||||
|
||||
if (!RosterFit.Matches(roster, demand))
|
||||
{
|
||||
throw new SchoolContentUnavailableException(
|
||||
|
||||
@@ -21,4 +21,11 @@
|
||||
// beats walking on to the next room, below the lesson so it never pulls
|
||||
// anybody out of class. Raise it past dutyLessonWeight and they will.
|
||||
"lunchWeight": 6,
|
||||
// Bag limit: base kg + per point of Strength, Endurance and Hauling.
|
||||
"carryMassBase": 5,
|
||||
"carryMassPerStrength": 0.08,
|
||||
"carryMassPerEndurance": 0.04,
|
||||
"carryMassPerHauling": 0.08,
|
||||
// Sweater, coat, hat, accessory — each rolled separately at generation.
|
||||
"optionalApparelChance": 0.4,
|
||||
}
|
||||
|
||||
@@ -30,4 +30,10 @@
|
||||
{ "attribute": "Build", "value": "Athletic", "min": 40 },
|
||||
],
|
||||
},
|
||||
{
|
||||
"defName": "Hauling",
|
||||
"always": true,
|
||||
"range": { "min": 0, "max": 100 },
|
||||
"distribution": { "mean": 50, "stdDev": 12 },
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{ "defName": "SchoolBag", "portable": true, "mass": 0.8 },
|
||||
{ "defName": "SchoolBag", "portable": true, "mass": 0.8, "age": { "min": 6, "max": 18 }, "carryChance": 1 },
|
||||
{ "defName": "Textbook", "portable": true, "mass": 0.4 },
|
||||
{ "defName": "Phone", "portable": true, "mass": 0.15, "colors": ["Black", "White", "Gray"] },
|
||||
{ "defName": "Bottle", "portable": true, "mass": 0.3, "colors": ["Blue", "Green", "White"] },
|
||||
{ "defName": "Phone", "portable": true, "mass": 0.15, "colors": ["Black", "White", "Gray"], "age": { "min": 11 }, "carryChance": 0.45 },
|
||||
{ "defName": "Bottle", "portable": true, "mass": 0.3, "colors": ["Blue", "Green", "White"], "age": { "min": 6 }, "carryChance": 0.7 },
|
||||
]
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
"Agility": "Agility",
|
||||
"Strength": "Strength",
|
||||
"Endurance": "Endurance",
|
||||
"Hauling": "Hauling",
|
||||
"Diligent": "Diligent",
|
||||
"AbsentMinded": "Absent-minded",
|
||||
"Bully": "Bully",
|
||||
|
||||
@@ -115,6 +115,7 @@
|
||||
"Agility": "Ловкость",
|
||||
"Strength": "Сила",
|
||||
"Endurance": "Выносливость",
|
||||
"Hauling": "Переноска",
|
||||
"Diligent": "Усидчивый",
|
||||
"AbsentMinded": "Рассеянный",
|
||||
"Bully": "Задира",
|
||||
|
||||
Reference in New Issue
Block a user