Wear worn clothes on campus and replace rags at the work morning.
Condition drops from catalog hours only while the person is at school; skip through the night issues the same fresh set as a lived night so the school does not walk in in rags. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
using HSchool.Content;
|
||||
using HSchool.People;
|
||||
using HSchool.Schedule;
|
||||
|
||||
namespace HSchool.Simulation.Tests;
|
||||
|
||||
public class ApparelWearTests
|
||||
{
|
||||
private static readonly DateTime TuesdayMorning = new(2012, 4, 3, 6, 0, 0, DateTimeKind.Utc);
|
||||
private static readonly DateTime TuesdayLesson = new(2012, 4, 3, 8, 30, 0, DateTimeKind.Utc);
|
||||
private static readonly DateTime TuesdayEvening = new(2012, 4, 3, 22, 0, 0, DateTimeKind.Utc);
|
||||
private static readonly DateTime WednesdayMorning = new(2012, 4, 4, 6, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
[Fact]
|
||||
public void HourOnCampus_DropsWornShirtByWearPerHour()
|
||||
{
|
||||
using var school = OpenStaffed();
|
||||
AdvanceTo(school, TuesdayLesson);
|
||||
var (person, index) = WornShirt(school, onCampus: true);
|
||||
var rate = school.Catalog!.BehaviorRules!.ApparelWearPerHour;
|
||||
var before = person.Items[index].Condition;
|
||||
|
||||
TickMinutes(school, 60);
|
||||
|
||||
var row = school.CapturePresence().Single(item => item.PersonId == person.Id);
|
||||
Assert.NotNull(row.NodeId);
|
||||
Assert.Equal(before - rate, person.Items[index].Condition, precision: 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HourOnCampus_LeavesBagAndLockerShirtsAlone()
|
||||
{
|
||||
using var school = OpenStaffed();
|
||||
AdvanceTo(school, TuesdayLesson);
|
||||
var (person, wornIndex) = WornShirt(school, onCampus: true);
|
||||
var bag = person.Items[wornIndex] with { Location = ItemLocations.Bag, Condition = 1f };
|
||||
var locker = person.Items[wornIndex] with { Location = ItemLocations.Locker, Condition = 1f };
|
||||
AddItem(person, bag);
|
||||
AddItem(person, locker);
|
||||
|
||||
TickMinutes(school, 60);
|
||||
|
||||
Assert.True(person.Items[wornIndex].Condition < 1f);
|
||||
Assert.Equal(1f, person.Items.Single(item => item.Location == ItemLocations.Bag && item.Def == bag.Def).Condition);
|
||||
Assert.Equal(1f, person.Items.Single(item => item.Location == ItemLocations.Locker && item.Def == locker.Def).Condition);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WearWithoutCrossingABand_DoesNotMarkPeopleChanged()
|
||||
{
|
||||
using var school = OpenStaffed();
|
||||
AdvanceTo(school, TuesdayLesson);
|
||||
var changed = false;
|
||||
for (var i = 0; i < 60; i++)
|
||||
{
|
||||
changed |= school.Tick(0.2d, 5d);
|
||||
}
|
||||
|
||||
Assert.False(changed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CrossingAConditionBand_MarksPeopleChanged()
|
||||
{
|
||||
using var school = OpenStaffed();
|
||||
AdvanceTo(school, TuesdayLesson);
|
||||
var (person, index) = WornShirt(school, onCampus: true);
|
||||
SetItem(person, index, person.Items[index] with { Condition = 0.755f });
|
||||
|
||||
var changed = false;
|
||||
for (var i = 0; i < 60; i++)
|
||||
{
|
||||
changed |= school.Tick(0.2d, 5d);
|
||||
}
|
||||
|
||||
Assert.True(person.Items[index].Condition < 0.75f);
|
||||
Assert.True(changed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RaggedWornShirt_IsReplacedByWorkMorning()
|
||||
{
|
||||
using var school = OpenEmpty(TuesdayEvening);
|
||||
var (person, index) = WornShirt(school);
|
||||
var def = person.Items[index].Def;
|
||||
var color = person.Items[index].Color;
|
||||
SetItem(person, index, person.Items[index] with { Condition = 0.05f });
|
||||
|
||||
var result = school.TrySkipEmpty();
|
||||
|
||||
Assert.True(result.Succeeded);
|
||||
Assert.True(result.PeopleChanged);
|
||||
Assert.Equal(WednesdayMorning, school.Clock.Time);
|
||||
Assert.Equal(def, person.Items[index].Def);
|
||||
Assert.Equal(color, person.Items[index].Color);
|
||||
Assert.Equal(1f, person.Items[index].Condition);
|
||||
Assert.Contains(
|
||||
school.DayLog,
|
||||
row => row.PersonId == person.Id
|
||||
&& row.Type == PersonLogTypes.ApparelReplaced
|
||||
&& row.ThingDef == def);
|
||||
var caption = school.DayLog.First(row => row.PersonId == person.Id).Caption(school.Catalog!, "ru");
|
||||
Assert.StartsWith("получил новую ", caption, StringComparison.Ordinal);
|
||||
Assert.Contains(school.Catalog!.Label("ru", school.Catalog.Things[def]), caption, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SkipThroughTheNight_MatchesALivedNight()
|
||||
{
|
||||
using var skipped = OpenEmpty(TuesdayEvening);
|
||||
using var lived = OpenEmpty(TuesdayEvening);
|
||||
var skippedShirt = WornShirt(skipped);
|
||||
var livedShirt = WornShirt(lived);
|
||||
SetItem(skippedShirt.Person, skippedShirt.Index, skippedShirt.Person.Items[skippedShirt.Index] with { Condition = 0.05f });
|
||||
SetItem(livedShirt.Person, livedShirt.Index, livedShirt.Person.Items[livedShirt.Index] with { Condition = 0.05f });
|
||||
|
||||
Assert.True(skipped.TrySkipEmpty().Succeeded);
|
||||
while (lived.Clock.Time < WednesdayMorning)
|
||||
{
|
||||
lived.Tick(0.2d, 5d);
|
||||
}
|
||||
|
||||
Assert.Equal(skipped.Clock.Time, lived.Clock.Time);
|
||||
Assert.Equal(1f, skippedShirt.Person.Items[skippedShirt.Index].Condition);
|
||||
Assert.Equal(1f, livedShirt.Person.Items[livedShirt.Index].Condition);
|
||||
Assert.Equal(skippedShirt.Person.Items[skippedShirt.Index].Def, livedShirt.Person.Items[livedShirt.Index].Def);
|
||||
Assert.Equal(
|
||||
skipped.DayLog.Select(row => (row.Type, row.ThingDef)).OrderBy(row => row.ThingDef, StringComparer.Ordinal),
|
||||
lived.DayLog.Select(row => (row.Type, row.ThingDef)).OrderBy(row => row.ThingDef, StringComparer.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConditionCaption_FollowsTheCatalogBands()
|
||||
{
|
||||
var catalog = Vanilla().Catalog;
|
||||
Assert.Equal("ApparelConditionIntact", ApparelCondition.BandId(catalog.BehaviorRules, 1f));
|
||||
Assert.Equal("ApparelConditionWorn", ApparelCondition.BandId(catalog.BehaviorRules, 0.5f));
|
||||
Assert.Equal("ApparelConditionTorn", ApparelCondition.BandId(catalog.BehaviorRules, 0.2f));
|
||||
Assert.Equal("ApparelConditionRags", ApparelCondition.BandId(catalog.BehaviorRules, 0.1f));
|
||||
Assert.Equal("целая", ApparelCondition.Label(catalog, "ru", 1f));
|
||||
Assert.Equal("висит лохмотьями", ApparelCondition.Label(catalog, "ru", 0.1f));
|
||||
Assert.Equal("intact", ApparelCondition.Label(catalog, "en", 1f));
|
||||
}
|
||||
|
||||
private static (Person Person, int Index) WornShirt(School school, bool onCampus = false)
|
||||
{
|
||||
var here = onCampus
|
||||
? school.CapturePresence()
|
||||
.Where(row => row.NodeId is not null)
|
||||
.Select(row => row.PersonId)
|
||||
.ToHashSet(StringComparer.Ordinal)
|
||||
: null;
|
||||
foreach (var person in school.Roster!.People)
|
||||
{
|
||||
if (here is not null && !here.Contains(person.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var i = 0; i < person.Items.Count; i++)
|
||||
{
|
||||
if (person.Items[i].Location.Equals(ItemLocations.Worn, StringComparison.Ordinal)
|
||||
&& (person.Items[i].Def.Equals("Shirt", StringComparison.Ordinal)
|
||||
|| person.Items[i].Def.Equals("TShirt", StringComparison.Ordinal)))
|
||||
{
|
||||
return (person, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("No worn Shirt in the roster.");
|
||||
}
|
||||
|
||||
private static void SetItem(Person person, int index, InventoryItem item)
|
||||
{
|
||||
if (person.Items is not IList<InventoryItem> list || list.IsReadOnly)
|
||||
{
|
||||
throw new InvalidOperationException("Items is not a mutable list.");
|
||||
}
|
||||
|
||||
list[index] = item;
|
||||
}
|
||||
|
||||
private static void AddItem(Person person, InventoryItem item)
|
||||
{
|
||||
if (person.Items is not IList<InventoryItem> list || list.IsReadOnly)
|
||||
{
|
||||
throw new InvalidOperationException("Items is not a mutable list.");
|
||||
}
|
||||
|
||||
list.Add(item);
|
||||
}
|
||||
|
||||
private static void TickMinutes(School school, int minutes)
|
||||
{
|
||||
for (var i = 0; i < minutes; i++)
|
||||
{
|
||||
school.Tick(0.2d, 5d);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AdvanceTo(School school, DateTime until)
|
||||
{
|
||||
while (school.Clock.Time < until)
|
||||
{
|
||||
school.Tick(0.2d, 5d);
|
||||
}
|
||||
}
|
||||
|
||||
private static School OpenStaffed()
|
||||
{
|
||||
var (catalog, map) = Vanilla();
|
||||
var roster = RosterGenerator.Generate(catalog, map, schoolSeed: 1, "Russia", TuesdayMorning);
|
||||
var pool = ApplicantPool.Create(catalog, roster, schoolSeed: 1, "Russia", TuesdayMorning);
|
||||
var schoolClass = roster.Classes.First(row =>
|
||||
row.RoomId is "classroom-101" or "classroom-102" or "classroom-103" or "classroom-104");
|
||||
var school = School.Create(1, "Износ", TuesdayMorning, catalog, map);
|
||||
school.InstallPeople(roster, seed: 1, "Russia", pool);
|
||||
school.SetTimetable(new Timetable(
|
||||
[
|
||||
new LessonPlacement(schoolClass.Id, "Mathematics", "t1", schoolClass.RoomId, Day: 1, Period: 1),
|
||||
new LessonPlacement(schoolClass.Id, "PhysicalEducation", "t2", "gym-hall", Day: 1, Period: 2),
|
||||
],
|
||||
[]));
|
||||
school.ConfigurePresence(weekDays: 5, maxDecisionsPerTick: 10_000);
|
||||
return school;
|
||||
}
|
||||
|
||||
private static School OpenEmpty(DateTime start)
|
||||
{
|
||||
var (catalog, map) = Vanilla();
|
||||
var roster = RosterGenerator.Generate(catalog, map, schoolSeed: 1, "Russia", start);
|
||||
var pool = ApplicantPool.Create(catalog, roster, schoolSeed: 1, "Russia", start);
|
||||
var school = School.Create(1, "Износ ночь", start, catalog, map);
|
||||
school.InstallPeople(roster, seed: 1, "Russia", pool);
|
||||
school.ConfigurePresence(weekDays: 5, maxDecisionsPerTick: 10_000);
|
||||
return school;
|
||||
}
|
||||
|
||||
private static (DefCatalog Catalog, MapLayout Map) Vanilla()
|
||||
{
|
||||
var root = Path.Combine(AppContext.BaseDirectory, "vanilla");
|
||||
var documents = new List<ContentDocument>();
|
||||
foreach (var path in Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (!path.EndsWith(".jsonc", StringComparison.OrdinalIgnoreCase)
|
||||
&& !path.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var relative = Path.GetRelativePath(root, path).Replace('\\', '/');
|
||||
documents.Add(new ContentDocument(CatalogLoader.CorePackId, relative, File.ReadAllText(path)));
|
||||
}
|
||||
|
||||
var catalog = new CatalogLoader().Load([CatalogLoader.CorePackId], documents);
|
||||
var map = CatalogLoader.LastDefaultMap([CatalogLoader.CorePackId], documents);
|
||||
Assert.NotNull(map);
|
||||
return (catalog, map);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user