Enhance apparel definitions: Introduce 'UpperUnderwear' layer and related items (Bra, SportsBra, SoftBra) to support gender-specific clothing. Update inventory and dress generation logic to accommodate new layers and ensure proper coverage. Adjust localization for new apparel terms and refine tests for underwear functionality.
This commit is contained in:
@@ -115,6 +115,45 @@ public class ApparelDefTests
|
||||
Assert.Equal([ColorTags.Bright], catalog.Colors["Red"].Tags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BraInheritsFromUnderwear_ButOccupiesUpperUnderwear()
|
||||
{
|
||||
var catalog = _loader.Load(
|
||||
[CatalogLoader.CorePackId],
|
||||
[
|
||||
PackDocuments.Def(
|
||||
CatalogLoader.CorePackId,
|
||||
"colors",
|
||||
"white",
|
||||
"""{ "defName": "White", "tags": ["neutral"] }"""),
|
||||
PackDocuments.Def(
|
||||
CatalogLoader.CorePackId,
|
||||
"things",
|
||||
"underwear",
|
||||
"""{ "defName": "Underwear", "abstract": true, "layers": ["Underwear"], "portable": true, "mass": 0.05, "colors": ["White"] }"""),
|
||||
PackDocuments.Def(
|
||||
CatalogLoader.CorePackId,
|
||||
"things",
|
||||
"bra",
|
||||
"""{ "defName": "Bra", "parent": "Underwear", "layers": ["UpperUnderwear"], "sex": "female" }"""),
|
||||
PackDocuments.Def(
|
||||
CatalogLoader.CorePackId,
|
||||
"things",
|
||||
"panties",
|
||||
"""{ "defName": "Panties", "parent": "Underwear", "sex": "female" }"""),
|
||||
]);
|
||||
|
||||
Assert.True(catalog.Things["Underwear"].Abstract);
|
||||
Assert.False(catalog.Things["Bra"].Abstract);
|
||||
Assert.Equal("Underwear", catalog.Things["Bra"].Parent);
|
||||
Assert.Equal([ApparelLayers.UpperUnderwear], catalog.Things["Bra"].Layers);
|
||||
Assert.True(catalog.Things["Bra"].Portable);
|
||||
Assert.Equal(0.05f, catalog.Things["Bra"].Mass);
|
||||
Assert.Equal(["White"], catalog.Things["Bra"].Colors);
|
||||
Assert.Equal([ApparelLayers.Underwear], catalog.Things["Panties"].Layers);
|
||||
Assert.Equal("female", catalog.Things["Panties"].Sex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TShirtReferencingMissingRed_FailsTheCatalog()
|
||||
{
|
||||
|
||||
@@ -41,6 +41,15 @@ public class VanillaCoreTests
|
||||
Assert.False(catalog.Things["Chair"].Portable);
|
||||
Assert.Empty(catalog.Things["StudentDesk"].Layers);
|
||||
Assert.Equal([ApparelLayers.Bottom, ApparelLayers.Top], catalog.Things["Dress"].Layers);
|
||||
Assert.True(catalog.Things["Underwear"].Abstract);
|
||||
Assert.Equal("Underwear", catalog.Things["Panties"].Parent);
|
||||
Assert.Equal("Underwear", catalog.Things["Bra"].Parent);
|
||||
Assert.Equal([ApparelLayers.Underwear], catalog.Things["Panties"].Layers);
|
||||
Assert.Equal([ApparelLayers.UpperUnderwear], catalog.Things["Bra"].Layers);
|
||||
Assert.Equal("female", catalog.Things["Bra"].Sex);
|
||||
Assert.Equal(11, catalog.Things["Bra"].Age?.Min);
|
||||
Assert.Contains(ApparelLayers.UpperUnderwear, catalog.Things["Shirt"].FullyCoversLayers);
|
||||
Assert.DoesNotContain(ApparelLayers.UpperUnderwear, catalog.Things["Pants"].FullyCoversLayers);
|
||||
Assert.Contains("Red", catalog.Things["TShirt"].Colors);
|
||||
Assert.True(catalog.Things["PeShirt"].Pe);
|
||||
Assert.True(catalog.Things["Textbook"].Portable);
|
||||
|
||||
@@ -136,6 +136,43 @@ public class DressGeneratorTests
|
||||
Assert.Equal(ItemsSnapshot(roster), ItemsSnapshot(dressed));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Underwear_IsSplitBySex_AndAdultFemalesWearABra()
|
||||
{
|
||||
var roster = Fixtures.Generate(Fixtures.Classrooms(11));
|
||||
var bras = new HashSet<string>(StringComparer.Ordinal) { "Bra", "SportsBra", "SoftBra" };
|
||||
|
||||
foreach (var person in roster.People)
|
||||
{
|
||||
var worn = person.Items
|
||||
.Where(item => item.Location == ItemLocations.Worn)
|
||||
.Select(item => item.Def)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
Assert.DoesNotContain("Underwear", worn);
|
||||
if (person.Female)
|
||||
{
|
||||
Assert.Contains("Panties", worn);
|
||||
Assert.DoesNotContain("Briefs", worn);
|
||||
Assert.DoesNotContain("Boxers", worn);
|
||||
if (person.AgeOn(Fixtures.AsOf) >= 11)
|
||||
{
|
||||
Assert.True(worn.Overlaps(bras), $"{person.Id} age {person.AgeOn(Fixtures.AsOf)} has no bra.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.False(worn.Overlaps(bras), $"{person.Id} is too young for a bra.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.True(worn.Contains("Briefs") || worn.Contains("Boxers"), $"{person.Id} has no male underwear.");
|
||||
Assert.DoesNotContain("Panties", worn);
|
||||
Assert.False(worn.Overlaps(bras), $"{person.Id} wears a bra.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EveryoneHasHauling()
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using HSchool.Content;
|
||||
using HSchool.Server.Api;
|
||||
using HSchool.Server.Game;
|
||||
|
||||
@@ -101,7 +102,7 @@ public class PortraitPromptBuilderTests
|
||||
null,
|
||||
null,
|
||||
new PersonFamilyResponse([], [], [], []),
|
||||
[new WornItemResponse("Shirt", "Shirt", "White", "White", [], 1f, "new")],
|
||||
[new WornItemResponse("Shirt", "Shirt", "White", "White", [new DefLabelResponse(ApparelLayers.Top, "Top")], 1f, "new", [ApparelLayers.Underwear])],
|
||||
[],
|
||||
0f,
|
||||
0f);
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
using HSchool.Content;
|
||||
using HSchool.Server.Api;
|
||||
using HSchool.Server.Game;
|
||||
|
||||
namespace HSchool.Server.Tests;
|
||||
|
||||
public class PortraitVisibleWornTests
|
||||
{
|
||||
[Fact]
|
||||
public void Filter_ExcludesUnderwearUnderIntactPants()
|
||||
{
|
||||
var visible = PortraitVisibleWorn.Filter(
|
||||
[
|
||||
Item("Underwear", ApparelLayers.Underwear, covers: []),
|
||||
Item("Pants", ApparelLayers.Bottom, covers: [ApparelLayers.Underwear]),
|
||||
]).Select(item => item.DefName).ToList();
|
||||
|
||||
Assert.Equal(["Pants"], visible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Filter_KeepsUnderwearWhenPantsAreTorn()
|
||||
{
|
||||
var visible = PortraitVisibleWorn.Filter(
|
||||
[
|
||||
Item("Underwear", ApparelLayers.Underwear, covers: [], condition: 1f),
|
||||
Item("Pants", ApparelLayers.Bottom, covers: [ApparelLayers.Underwear], condition: 0.2f),
|
||||
]).Select(item => item.DefName).ToList();
|
||||
|
||||
Assert.Contains("Underwear", visible);
|
||||
Assert.Contains("Pants", visible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Filter_ExcludesBraUnderIntactShirt()
|
||||
{
|
||||
var visible = PortraitVisibleWorn.Filter(
|
||||
[
|
||||
Item("Bra", ApparelLayers.UpperUnderwear, covers: []),
|
||||
Item("Shirt", ApparelLayers.Top, covers: [ApparelLayers.Underwear, ApparelLayers.UpperUnderwear]),
|
||||
]).Select(item => item.DefName).ToList();
|
||||
|
||||
Assert.Equal(["Shirt"], visible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Filter_KeepsBraWhenShirtIsTorn()
|
||||
{
|
||||
var visible = PortraitVisibleWorn.Filter(
|
||||
[
|
||||
Item("Bra", ApparelLayers.UpperUnderwear, covers: [], condition: 1f),
|
||||
Item("Shirt", ApparelLayers.Top, covers: [ApparelLayers.Underwear, ApparelLayers.UpperUnderwear], condition: 0.2f),
|
||||
]).Select(item => item.DefName).ToList();
|
||||
|
||||
Assert.Contains("Bra", visible);
|
||||
Assert.Contains("Shirt", visible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Filter_KeepsBraWhenOnlyPantsCoverUnderwear()
|
||||
{
|
||||
var visible = PortraitVisibleWorn.Filter(
|
||||
[
|
||||
Item("Panties", ApparelLayers.Underwear, covers: []),
|
||||
Item("Bra", ApparelLayers.UpperUnderwear, covers: []),
|
||||
Item("Pants", ApparelLayers.Bottom, covers: [ApparelLayers.Underwear]),
|
||||
]).Select(item => item.DefName).ToList();
|
||||
|
||||
Assert.DoesNotContain("Panties", visible);
|
||||
Assert.Contains("Bra", visible);
|
||||
Assert.Contains("Pants", visible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Filter_KeepsShirtWithPants()
|
||||
{
|
||||
var visible = PortraitVisibleWorn.Filter(
|
||||
[
|
||||
Item("Shirt", ApparelLayers.Top, covers: [ApparelLayers.Underwear]),
|
||||
Item("Pants", ApparelLayers.Bottom, covers: [ApparelLayers.Underwear]),
|
||||
]).Select(item => item.DefName).ToList();
|
||||
|
||||
Assert.Contains("Shirt", visible);
|
||||
Assert.Contains("Pants", visible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Filter_ExcludesShirtUnderSweater()
|
||||
{
|
||||
var visible = PortraitVisibleWorn.Filter(
|
||||
[
|
||||
Item("Shirt", ApparelLayers.Top, covers: [ApparelLayers.Underwear]),
|
||||
Item("Sweater", ApparelLayers.OverTop, covers: [ApparelLayers.Underwear, ApparelLayers.Top]),
|
||||
]).Select(item => item.DefName).ToList();
|
||||
|
||||
Assert.Equal(["Sweater"], visible);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_OmitsCoveredShirtFromPrompt()
|
||||
{
|
||||
var preset = SwarmUiPresetDefinition.CreateDefault();
|
||||
preset.Positive = "School photo.";
|
||||
var card = SampleCard(
|
||||
[
|
||||
Item("Shirt", ApparelLayers.Top, covers: [ApparelLayers.Underwear], color: "White", colorLabel: "White"),
|
||||
Item("Coat", ApparelLayers.Outer, covers: [ApparelLayers.Underwear, ApparelLayers.Top, ApparelLayers.OverTop, ApparelLayers.Bottom], color: "Black", colorLabel: "Black"),
|
||||
]);
|
||||
|
||||
var (positive, _) = PortraitPromptBuilder.Build(card, preset.ToProfile(PortraitKind.Avatar), PortraitKind.Avatar);
|
||||
|
||||
Assert.Contains("coat", positive, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("shirt", positive, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static WornItemResponse Item(
|
||||
string defName,
|
||||
string layer,
|
||||
IReadOnlyList<string> covers,
|
||||
string? color = null,
|
||||
string? colorLabel = null,
|
||||
float condition = 1f) =>
|
||||
new(
|
||||
defName,
|
||||
defName,
|
||||
color,
|
||||
colorLabel,
|
||||
[new DefLabelResponse(layer, layer)],
|
||||
condition,
|
||||
"new",
|
||||
covers);
|
||||
|
||||
private static PersonCardResponse SampleCard(IReadOnlyList<WornItemResponse> worn) =>
|
||||
new(
|
||||
"f0.c0",
|
||||
"Maria Ivanova",
|
||||
"Ivanova",
|
||||
"Maria",
|
||||
"",
|
||||
true,
|
||||
12,
|
||||
new DateTime(2000, 3, 14, 0, 0, 0, DateTimeKind.Utc),
|
||||
["student"],
|
||||
5,
|
||||
"A",
|
||||
"class-1",
|
||||
null,
|
||||
null,
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
new PersonFamilyResponse([], [], [], []),
|
||||
worn,
|
||||
[],
|
||||
0f,
|
||||
0f);
|
||||
}
|
||||
Reference in New Issue
Block a user