Files
Leonid Pershin b135a9caad Enhance protocol and simulation features with activity tracking and behavior definitions
- Updated protocol documentation to include new `activity` and `activityLabel` fields in the person card response, reflecting real-time activity status.
- Introduced `BehaviorDef` to define behavior rules, including need thresholds and lesson skill gains, enhancing AI decision-making.
- Revised the `DefCatalog` to incorporate behavior definitions and updated validation logic to ensure proper behavior handling.
- Enhanced the simulation to manage presence and activity states, allowing for more dynamic interactions within the school environment.
- Updated tests to validate the new activity tracking and behavior functionalities, ensuring robust performance and reliability.
- Improved localization strings to support new activity and behavior features, enhancing user experience.
2026-08-19 19:49:44 +03:00

154 lines
5.4 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Arch.Core;
using HSchool.Ai;
using HSchool.Content;
using HSchool.People;
namespace HSchool.Simulation.Tests;
public class ActivityTests
{
private static readonly DateTime Start = new(2012, 4, 3, 12, 0, 0, DateTimeKind.Utc);
[Fact]
public void EatLunch_FillsHungerAfterTheStatedMinutes()
{
using var school = OpenCafeteria(chairs: 8);
Spawn(school, "a", hunger: 0.3f);
Assert.True(school.TryStartAction("a", "EatLunch"));
school.Tick(2.8d, 5d);
Assert.Equal("EatLunch", ActivityOf(school, "a"));
Assert.Equal(0.3f, HungerOf(school, "a"));
school.Tick(0.2d, 5d);
Assert.Null(ActivityOf(school, "a"));
Assert.Equal(0.8f, HungerOf(school, "a"));
}
[Fact]
public void FewerChairsThanPeople_ExtrasDoNotStart()
{
using var school = OpenCafeteria(chairs: 1);
Spawn(school, "a", hunger: 0.3f);
Spawn(school, "b", hunger: 0.3f);
Assert.True(school.TryStartAction("a", "EatLunch"));
Assert.False(school.TryStartAction("b", "EatLunch"));
Assert.Equal("EatLunch", ActivityOf(school, "a"));
Assert.Null(ActivityOf(school, "b"));
}
private static School OpenCafeteria(int chairs)
{
var catalog = new CatalogLoader().Load(
[CatalogLoader.CorePackId],
[
new ContentDocument("core", "defs/actions/sit.jsonc", """{ "defName": "Sit", "abstract": true }"""),
new ContentDocument(
"core",
"defs/actions/eat.jsonc",
"""
{
"defName": "EatLunch",
"room": "Cafeteria",
"thing": "Chair",
"minutes": 15,
"need": "Hunger",
"needGain": 0.5,
"roles": ["student"]
}
"""),
new ContentDocument(
"core",
"defs/needs/hunger.jsonc",
"""{ "defName": "Hunger", "initial": 1, "decayPerHour": 0, "min": 0, "max": 1 }"""),
new ContentDocument("core", "defs/things/chair.jsonc", """{ "defName": "Chair", "actions": ["Sit"] }"""),
new ContentDocument("core", "defs/territories/yard.jsonc", """{ "defName": "Yard", "travelMinutes": 1 }"""),
new ContentDocument("core", "defs/buildings/main.jsonc", """{ "defName": "Main" }"""),
new ContentDocument("core", "defs/floors/floor.jsonc", """{ "defName": "Floor" }"""),
new ContentDocument(
"core",
"defs/rooms/cafeteria.jsonc",
"""
{
"defName": "Cafeteria",
"slots": [{ "key": "seats", "thing": "Chair" }],
"travelMinutes": 1
}
"""),
]);
var map = new MapLayout
{
Territory = new TerritoryNode { Id = "yard", Def = "Yard" },
Buildings = [new BuildingNode { Id = "main", Def = "Main" }],
Floors = [new FloorNode { Id = "floor-1", Def = "Floor", Building = "main" }],
Rooms =
[
new RoomNode
{
Id = "cafe",
Def = "Cafeteria",
Building = "main",
Floor = "floor-1",
Slots = [new SlotFill { Key = "seats", Thing = "Chair", Count = chairs }],
},
],
Links = [new MapLink { A = "yard", B = "cafe" }],
};
MapValidator.Validate(map, catalog);
return School.Create(1, "Столовая", Start, catalog, map);
}
private static void Spawn(School school, string id, float hunger)
{
school.World.Create(
new PersonIdentity(id, "f", false, Start, DummyName()),
new PersonRoles(true, false, false, null, null, null),
new PersonNeeds(new Dictionary<string, float>(StringComparer.Ordinal) { ["Hunger"] = hunger }),
new Presence("cafe", 0f, "cafe", false, []),
PersonActivity.Idle);
}
private static string? ActivityOf(School school, string id)
{
string? found = null;
var query = new QueryDescription().WithAll<PersonIdentity, PersonActivity>();
school.World.Query(in query, (ref PersonIdentity identity, ref PersonActivity activity) =>
{
if (identity.Id.Equals(id, StringComparison.Ordinal))
{
found = activity.ActionId;
}
});
return found;
}
private static float HungerOf(School school, string id)
{
var value = float.NaN;
var query = new QueryDescription().WithAll<PersonIdentity, PersonNeeds>();
school.World.Query(in query, (ref PersonIdentity identity, ref PersonNeeds needs) =>
{
if (identity.Id.Equals(id, StringComparison.Ordinal))
{
value = needs.Values["Hunger"];
}
});
return value;
}
private static PersonName DummyName()
{
var cases = new CaseTable
{
Nom = "А",
Gen = "А",
Dat = "А",
Acc = "А",
Ins = "А",
Pre = "А",
};
return new PersonName("А", "А", "А", cases, cases, cases);
}
}