- Updated `ai.md` to clarify the mechanics of hunger restoration and the importance of lunch breaks in the school schedule. - Revised `schedule.md` to detail the new lunch break structure, allowing for separate sittings for different grade levels. - Enhanced `Decision.cs` and `DecisionPlanner.cs` to incorporate logic for lunch breaks, ensuring that students only leave lessons during their designated lunch windows. - Updated `DayFrameDef` and related classes to support multiple lunch breaks and validate their configurations. - Adjusted tests to validate the new decision-making logic regarding lunch breaks and hunger management, ensuring robust functionality. - Improved localization strings to reflect changes in the school day structure and lunch functionalities.
112 lines
3.6 KiB
C#
112 lines
3.6 KiB
C#
using Arch.Core;
|
|
using HSchool.Ai;
|
|
using HSchool.Content;
|
|
|
|
namespace HSchool.Simulation.Tests;
|
|
|
|
public class NeedDecayTests
|
|
{
|
|
[Fact]
|
|
public void Hunger_DropsByDecayPerHourOnCampus()
|
|
{
|
|
var catalog = VanillaCatalog();
|
|
var hunger = catalog.Needs["Hunger"];
|
|
var world = World.Create();
|
|
try
|
|
{
|
|
world.Create(
|
|
new PersonNeeds(new Dictionary<string, float>(StringComparer.Ordinal) { ["Hunger"] = hunger.Initial }),
|
|
new Presence("cafeteria", 0f, "cafeteria", false, []));
|
|
NeedDecay.Apply(world, catalog, gameMinutes: 60);
|
|
|
|
var query = new QueryDescription().WithAll<PersonNeeds>();
|
|
world.Query(
|
|
in query,
|
|
(ref PersonNeeds needs) =>
|
|
Assert.Equal(hunger.Initial - hunger.DecayPerHour, needs.Values["Hunger"], precision: 4));
|
|
}
|
|
finally
|
|
{
|
|
World.Destroy(world);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void SleepAndHunger_ReturnToMaxOffCampus()
|
|
{
|
|
var catalog = VanillaCatalog();
|
|
var world = World.Create();
|
|
try
|
|
{
|
|
world.Create(
|
|
new PersonNeeds(new Dictionary<string, float>(StringComparer.Ordinal)
|
|
{
|
|
["Sleep"] = 0.2f,
|
|
["Hunger"] = 0.4f,
|
|
}),
|
|
Presence.OffCampus);
|
|
NeedDecay.Apply(world, catalog, gameMinutes: 60);
|
|
|
|
var query = new QueryDescription().WithAll<PersonNeeds>();
|
|
world.Query(in query, (ref PersonNeeds needs) =>
|
|
{
|
|
// Both are restored за кадром: the day at home covers a night and meals alike.
|
|
Assert.Equal(catalog.Needs["Sleep"].Max, needs.Values["Sleep"]);
|
|
Assert.Equal(catalog.Needs["Hunger"].Max, needs.Values["Hunger"]);
|
|
});
|
|
}
|
|
finally
|
|
{
|
|
World.Destroy(world);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void SameInputs_LeaveTheSameNeedValues()
|
|
{
|
|
var catalog = VanillaCatalog();
|
|
var first = HungerAfterHour(catalog);
|
|
var second = HungerAfterHour(catalog);
|
|
Assert.Equal(first, second);
|
|
}
|
|
|
|
private static float HungerAfterHour(DefCatalog catalog)
|
|
{
|
|
var world = World.Create();
|
|
try
|
|
{
|
|
world.Create(
|
|
new PersonNeeds(new Dictionary<string, float>(StringComparer.Ordinal) { ["Hunger"] = 1f }),
|
|
new Presence("cafeteria", 0f, "cafeteria", false, []));
|
|
NeedDecay.Apply(world, catalog, gameMinutes: 60);
|
|
var value = 0f;
|
|
var query = new QueryDescription().WithAll<PersonNeeds>();
|
|
world.Query(in query, (ref PersonNeeds needs) => value = needs.Values["Hunger"]);
|
|
return value;
|
|
}
|
|
finally
|
|
{
|
|
World.Destroy(world);
|
|
}
|
|
}
|
|
|
|
private static DefCatalog VanillaCatalog()
|
|
{
|
|
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)));
|
|
}
|
|
|
|
return new CatalogLoader().Load([CatalogLoader.CorePackId], documents);
|
|
}
|
|
}
|