- 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.
154 lines
5.4 KiB
C#
154 lines
5.4 KiB
C#
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);
|
||
}
|
||
}
|