Let the catalog describe clothes and carryables on ThingDef without dressing anyone.

Empty layers keep furniture on the map; a known layer set and ColorDef palette reject unknown ids at load.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-20 02:47:23 +03:00
co-authored by Cursor
parent 450495f666
commit abcf67eaab
16 changed files with 716 additions and 15 deletions
@@ -303,6 +303,9 @@ public class SchoolApiTests(AppHostFixture fixture)
Assert.Equal("GymHall", Assert.Single(ru.Subjects, subject => subject.DefName == "PhysicalEducation").Room);
Assert.Null(Assert.Single(ru.Subjects, subject => subject.DefName == "Mathematics").Room);
Assert.Contains(ru.Holidays, holiday => holiday.DefName == "SpringBreak");
Assert.Contains(ru.Things, thing => thing.DefName == "Chair");
Assert.DoesNotContain(ru.Things, thing => thing.DefName == "TShirt");
Assert.DoesNotContain(ru.Things, thing => thing.DefName == "Textbook");
}
[Fact]
@@ -0,0 +1,169 @@
namespace HSchool.Content.Tests;
public class ApparelDefTests
{
private readonly CatalogLoader _loader = new();
[Fact]
public void ChairWithoutApparelFields_LoadsAsFurniture()
{
var catalog = _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(
CatalogLoader.CorePackId,
"actions",
"sit",
"""{ "defName": "Sit", "abstract": true }"""),
PackDocuments.Def(
CatalogLoader.CorePackId,
"things",
"chair",
"""{ "defName": "Chair", "actions": ["Sit"] }"""),
]);
var chair = catalog.Things["Chair"];
Assert.Equal(["Sit"], chair.Actions);
Assert.Empty(chair.Layers);
Assert.False(chair.Portable);
Assert.Equal(0, chair.Mass);
Assert.Equal(0, chair.Insulation);
Assert.Equal(0, chair.Formality);
Assert.Null(chair.Age);
Assert.Null(chair.Sex);
Assert.Empty(chair.Colors);
Assert.Null(chair.SkirtLength);
Assert.False(chair.Pe);
Assert.Equal(0, chair.PupilSlots);
}
[Fact]
public void DressOccupyingTwoLayers_Loads()
{
var catalog = _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(
CatalogLoader.CorePackId,
"things",
"dress",
"""{ "defName": "Dress", "layers": ["Bottom", "Top"], "portable": true }"""),
]);
Assert.Equal([ApparelLayers.Bottom, ApparelLayers.Top], catalog.Things["Dress"].Layers);
Assert.True(catalog.Things["Dress"].Portable);
}
[Fact]
public void UnknownLayer_FailsTheCatalog()
{
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(
CatalogLoader.CorePackId,
"things",
"cape",
"""{ "defName": "Cape", "layers": ["Cloak"] }"""),
]));
Assert.Contains("Cloak", ex.Message, StringComparison.Ordinal);
}
[Fact]
public void TwoTopsInTheCatalog_DoNotFailLoad()
{
var catalog = _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(
CatalogLoader.CorePackId,
"things",
"shirt",
"""{ "defName": "Shirt", "layers": ["Top"] }"""),
PackDocuments.Def(
CatalogLoader.CorePackId,
"things",
"tshirt",
"""{ "defName": "TShirt", "layers": ["Top"] }"""),
]);
Assert.Equal([ApparelLayers.Top], catalog.Things["Shirt"].Layers);
Assert.Equal([ApparelLayers.Top], catalog.Things["TShirt"].Layers);
}
[Fact]
public void TShirtReferencingRed_LoadsWhenThePaletteHasRed()
{
var catalog = _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(
CatalogLoader.CorePackId,
"colors",
"red",
"""{ "defName": "Red", "tags": ["bright"] }"""),
PackDocuments.Def(
CatalogLoader.CorePackId,
"things",
"tshirt",
"""{ "defName": "TShirt", "layers": ["Top"], "colors": ["Red"] }"""),
]);
Assert.Equal(["Red"], catalog.Things["TShirt"].Colors);
Assert.Equal([ColorTags.Bright], catalog.Colors["Red"].Tags);
}
[Fact]
public void TShirtReferencingMissingRed_FailsTheCatalog()
{
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(
CatalogLoader.CorePackId,
"things",
"tshirt",
"""{ "defName": "TShirt", "layers": ["Top"], "colors": ["Red"] }"""),
]));
Assert.Contains("Red", ex.Message, StringComparison.Ordinal);
}
[Fact]
public void UnknownColorTag_FailsTheCatalog()
{
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(
CatalogLoader.CorePackId,
"colors",
"neon",
"""{ "defName": "Neon", "tags": ["glow"] }"""),
]));
Assert.Contains("glow", ex.Message, StringComparison.Ordinal);
}
[Fact]
public void RoomSlotWithApparel_FailsTheCatalog()
{
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
[CatalogLoader.CorePackId],
[
PackDocuments.Def(
CatalogLoader.CorePackId,
"things",
"tshirt",
"""{ "defName": "TShirt", "layers": ["Top"] }"""),
PackDocuments.Def(
CatalogLoader.CorePackId,
"rooms",
"office",
"""{ "defName": "Office", "slots": [{ "key": "seat", "thing": "TShirt" }], "travelMinutes": 1 }"""),
]));
Assert.Contains("TShirt", ex.Message, StringComparison.Ordinal);
}
}
@@ -37,6 +37,18 @@ public class VanillaCoreTests
Assert.Equal(1, catalog.Things["Computer"].PupilSlots);
Assert.Equal(0, catalog.Things["Desk"].PupilSlots);
Assert.Equal(0, catalog.Things["Chair"].PupilSlots);
Assert.Empty(catalog.Things["Chair"].Layers);
Assert.False(catalog.Things["Chair"].Portable);
Assert.Empty(catalog.Things["StudentDesk"].Layers);
Assert.Equal([ApparelLayers.Bottom, ApparelLayers.Top], catalog.Things["Dress"].Layers);
Assert.Contains("Red", catalog.Things["TShirt"].Colors);
Assert.True(catalog.Things["PeShirt"].Pe);
Assert.True(catalog.Things["Textbook"].Portable);
Assert.Empty(catalog.Things["Textbook"].Layers);
Assert.Equal(1, catalog.Things.Values.Count(thing => thing.DefName == "Textbook"));
Assert.Equal([ColorTags.Bright], catalog.Colors["Red"].Tags);
Assert.Equal([ColorTags.Neutral, ColorTags.School], catalog.Colors["White"].Tags);
Assert.Empty(catalog.Colors["Beige"].Tags);
Assert.True(catalog.Subjects.ContainsKey("PrimarySchool"));
Assert.Equal(1, catalog.Subjects["PrimarySchool"].Grades.Min);
Assert.Equal(4, catalog.Subjects["PrimarySchool"].Grades.Max);
@@ -57,6 +69,9 @@ public class VanillaCoreTests
Assert.DoesNotContain(map.Rooms, room => room.Label is "1A" or "1B" or "2A" or "2B");
Assert.Equal(16, map.Rooms.Single(room => room.Id == "classroom-101").Seats);
Assert.Empty(map.Rooms.Single(room => room.Id == "classroom-101").Slots);
Assert.Equal(
2,
catalog.Things.Values.Count(thing => !thing.Abstract && thing.PupilSlots > 0));
Assert.Equal(0.5f, catalog.Rooms["Classroom"].TravelMinutes);
Assert.Equal(1.5f, catalog.Rooms["Corridor"].TravelMinutes);
Assert.Equal(1.5f, catalog.Rooms["Stairwell"].TravelMinutes);
@@ -119,11 +134,13 @@ public class VanillaCoreTests
keys.AddRange(Names(catalog.DayFrames.Values));
keys.AddRange(Names(catalog.Holidays.Values));
keys.AddRange(Names(catalog.Behavior.Values));
keys.AddRange(Names(catalog.Colors.Values));
keys.AddRange(catalog.PackIds);
// Derived in code, so no def carries them.
keys.Add(BodyBuilds.Attribute);
keys.AddRange(BodyBuilds.Values);
keys.AddRange(ApparelLayers.All);
foreach (var value in catalog.BodyAttributes.Values.SelectMany(def => def.Options))
{