using Arch.Core; using HSchool.Ai; using HSchool.Content; using HSchool.People; namespace HSchool.Simulation.Tests; public class WarmthTests { private static readonly DateTime JanuaryMorning = new(2012, 1, 15, 6, 0, 0, DateTimeKind.Utc); [Fact] public void NakedOnFrost_LosesWarmth_AndHomeRestoresItOvernight() { using var school = Open(JanuaryMorning); PutFirstPerson(school, "yard", PersonInsulation.Naked, warmth: 1f); school.SyncWeather(force: true); Assert.True(school.Weather.TemperatureC < 0f); WarmthDecay.Apply(school, gameMinutes: 60); var afterHour = WarmthOf(school); Assert.True(afterHour < 1f, $"warmth stayed {afterHour} on the frost"); PutFirstPerson(school, nodeId: null, PersonInsulation.Naked, afterHour); NeedDecay.Apply(school.World, school.Catalog!, gameMinutes: 60); Assert.Equal(school.Catalog!.Needs["Warmth"].Max, WarmthOf(school)); } [Fact] public void JacketOnFrost_LosesLessWarmthThanNaked() { using var school = Open(JanuaryMorning); var catalog = school.Catalog!; school.SyncWeather(force: true); PutFirstPerson(school, "yard", PersonInsulation.Naked, warmth: 1f); WarmthDecay.Apply(school, gameMinutes: 60); var naked = WarmthOf(school); PutFirstPerson(school, "yard", PersonInsulation.FromWorn(catalog, "Jacket"), warmth: 1f); WarmthDecay.Apply(school, gameMinutes: 60); var coated = WarmthOf(school); Assert.True(naked < 1f); Assert.True(coated > naked, $"jacket {coated} should beat naked {naked}"); } [Fact] public void FromPerson_SumsWornInsulation_AndIgnoresHome() { using var school = Open(JanuaryMorning); var catalog = school.Catalog!; var person = school.Roster!.People.First(row => row.Items.Any(item => item.Location.Equals(ItemLocations.Worn, StringComparison.Ordinal))); Assert.True(PersonInsulation.FromPerson(person, catalog).Value > 0); var atHome = person with { Items = [.. person.Items.Select(item => item with { Location = ItemLocations.Home })], }; Assert.Equal(0, PersonInsulation.FromPerson(atHome, catalog).Value); } private static void PutFirstPerson(School school, string? nodeId, PersonInsulation insulation, float warmth) { var query = new QueryDescription().WithAll(); var first = true; school.World.Query( in query, (ref PersonNeeds needs, ref PersonInsulation worn, ref Presence presence) => { if (!first) { return; } first = false; worn = insulation; needs.Values["Warmth"] = warmth; presence = nodeId is null ? Presence.OffCampus : new Presence(nodeId, 0f, nodeId, false, []); }); } private static float WarmthOf(School school) { var value = float.NaN; var query = new QueryDescription().WithAll(); school.World.Query(in query, (ref PersonNeeds needs) => { if (float.IsNaN(value) && needs.Values.TryGetValue("Warmth", out var warmth)) { value = warmth; } }); return value; } private static School Open(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, climatePresetId: "TemperateContinental"); school.ConfigurePresence(); return school; } private static (DefCatalog Catalog, MapLayout Map) Vanilla() { var root = Path.Combine(AppContext.BaseDirectory, "vanilla"); var documents = new List(); 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); } }