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.
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
using HSchool.Content;
|
||||
|
||||
namespace HSchool.Ai.Tests;
|
||||
|
||||
public class ActionStepperTests
|
||||
{
|
||||
private static readonly ActionDef Lunch = new()
|
||||
{
|
||||
DefName = "EatLunch",
|
||||
Room = "Cafeteria",
|
||||
Thing = "Chair",
|
||||
Minutes = 15,
|
||||
Need = "Hunger",
|
||||
NeedGain = 0.5f,
|
||||
};
|
||||
|
||||
private static readonly NeedDef Hunger = new()
|
||||
{
|
||||
DefName = "Hunger",
|
||||
Initial = 1,
|
||||
Min = 0,
|
||||
Max = 1,
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void Begin_TakesTheStatedMinutes()
|
||||
{
|
||||
var started = ActionStepper.Begin(Lunch);
|
||||
|
||||
Assert.Equal("EatLunch", started.ActionId);
|
||||
Assert.Equal("Chair", started.Thing);
|
||||
Assert.Equal(15f, started.RemainingMinutes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Advance_CompletesAfterTheStatedMinutes()
|
||||
{
|
||||
var started = ActionStepper.Begin(Lunch);
|
||||
|
||||
var mid = ActionStepper.Advance(started, 14f, out var doneEarly);
|
||||
var done = ActionStepper.Advance(mid, 1f, out var completed);
|
||||
|
||||
Assert.False(doneEarly);
|
||||
Assert.Equal(1f, mid.RemainingMinutes);
|
||||
Assert.True(completed);
|
||||
Assert.False(done.IsActive);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyNeedGain_RaisesHungerByTheStatedAmount()
|
||||
{
|
||||
Assert.Equal(0.8f, ActionStepper.ApplyNeedGain(0.3f, Lunch, Hunger));
|
||||
Assert.Equal(1f, ActionStepper.ApplyNeedGain(0.8f, Lunch, Hunger));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanOccupy_RefusesWhenEveryThingIsTaken()
|
||||
{
|
||||
Assert.True(ActionStepper.CanOccupy(available: 1, occupied: 0));
|
||||
Assert.False(ActionStepper.CanOccupy(available: 1, occupied: 1));
|
||||
Assert.False(ActionStepper.CanOccupy(available: 0, occupied: 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SameAdvances_LeaveTheSameRemainingMinutes()
|
||||
{
|
||||
var first = ActionStepper.Advance(ActionStepper.Begin(Lunch), 7.5f, out _);
|
||||
var second = ActionStepper.Advance(ActionStepper.Begin(Lunch), 7.5f, out _);
|
||||
|
||||
Assert.Equal(first.RemainingMinutes, second.RemainingMinutes);
|
||||
Assert.Equal(first.ActionId, second.ActionId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
namespace HSchool.Content.Tests;
|
||||
|
||||
public class ActionDefTests
|
||||
{
|
||||
private readonly CatalogLoader _loader = new();
|
||||
|
||||
[Fact]
|
||||
public void ConcreteAction_WithoutRoom_FailsLoad()
|
||||
{
|
||||
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
|
||||
[CatalogLoader.CorePackId],
|
||||
[
|
||||
PackDocuments.Def(
|
||||
CatalogLoader.CorePackId,
|
||||
"actions",
|
||||
"eat",
|
||||
"""{ "defName": "Eat", "minutes": 15, "need": "Hunger", "needGain": 0.5 }"""),
|
||||
PackDocuments.Def(
|
||||
CatalogLoader.CorePackId,
|
||||
"needs",
|
||||
"hunger",
|
||||
"""{ "defName": "Hunger", "initial": 1, "decayPerHour": 0, "min": 0, "max": 1 }"""),
|
||||
]));
|
||||
|
||||
Assert.Contains("Eat", ex.Message, StringComparison.Ordinal);
|
||||
Assert.Contains("room", ex.Message, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConcreteAction_UnknownNeed_FailsLoad()
|
||||
{
|
||||
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
|
||||
[CatalogLoader.CorePackId],
|
||||
[
|
||||
PackDocuments.Def(
|
||||
CatalogLoader.CorePackId,
|
||||
"rooms",
|
||||
"cafeteria",
|
||||
"""{ "defName": "Cafeteria", "travelMinutes": 1 }"""),
|
||||
PackDocuments.Def(
|
||||
CatalogLoader.CorePackId,
|
||||
"actions",
|
||||
"eat",
|
||||
"""
|
||||
{ "defName": "EatLunch", "room": "Cafeteria", "minutes": 15, "need": "Snacks", "needGain": 0.5 }
|
||||
"""),
|
||||
]));
|
||||
|
||||
Assert.Contains("EatLunch", ex.Message, StringComparison.Ordinal);
|
||||
Assert.Contains("Snacks", ex.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConcreteAction_UnknownThing_FailsLoad()
|
||||
{
|
||||
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
|
||||
[CatalogLoader.CorePackId],
|
||||
[
|
||||
PackDocuments.Def(
|
||||
CatalogLoader.CorePackId,
|
||||
"rooms",
|
||||
"cafeteria",
|
||||
"""{ "defName": "Cafeteria", "travelMinutes": 1 }"""),
|
||||
PackDocuments.Def(
|
||||
CatalogLoader.CorePackId,
|
||||
"actions",
|
||||
"eat",
|
||||
"""
|
||||
{ "defName": "EatLunch", "room": "Cafeteria", "thing": "Stool", "minutes": 15 }
|
||||
"""),
|
||||
]));
|
||||
|
||||
Assert.Contains("EatLunch", ex.Message, StringComparison.Ordinal);
|
||||
Assert.Contains("Stool", ex.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AbstractAction_DoesNotNeedARoom()
|
||||
{
|
||||
var catalog = _loader.Load(
|
||||
[CatalogLoader.CorePackId],
|
||||
[PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit", "abstract": true }""")]);
|
||||
|
||||
Assert.True(catalog.Actions["Sit"].Abstract);
|
||||
Assert.Null(catalog.Actions["Sit"].Room);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ public class CatalogLoaderTests
|
||||
"sit",
|
||||
"""
|
||||
// a verb the Sit system already knows
|
||||
{ "defName": "Sit", }
|
||||
{ "defName": "Sit", "abstract": true, }
|
||||
"""),
|
||||
]);
|
||||
|
||||
@@ -32,7 +32,7 @@ public class CatalogLoaderTests
|
||||
[
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "things", "chair", """{ "defName": "Chair", "actions": [] }"""),
|
||||
PackDocuments.Def("addon", "things", "chair", """{ "defName": "Chair", "actions": ["Sit"] }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit" }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit", "abstract": true }"""),
|
||||
],
|
||||
log);
|
||||
|
||||
@@ -47,7 +47,7 @@ public class CatalogLoaderTests
|
||||
var catalog = _loader.Load(
|
||||
[CatalogLoader.CorePackId, "addon"],
|
||||
[
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit" }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit", "abstract": true }"""),
|
||||
PackDocuments.Locale(CatalogLoader.CorePackId, "ru", """{ "Sit": "Сесть" }"""),
|
||||
PackDocuments.Locale("addon", "ru", """{ "Sit": "Присесть" }"""),
|
||||
],
|
||||
@@ -63,7 +63,7 @@ public class CatalogLoaderTests
|
||||
var catalog = _loader.Load(
|
||||
[CatalogLoader.CorePackId],
|
||||
[
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit" }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit", "abstract": true }"""),
|
||||
PackDocuments.Locale(CatalogLoader.CorePackId, "ru", """{ "Sit": "Сесть" }"""),
|
||||
]);
|
||||
|
||||
@@ -77,8 +77,8 @@ public class CatalogLoaderTests
|
||||
var catalog = _loader.Load(
|
||||
["addon"],
|
||||
[
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit" }"""),
|
||||
PackDocuments.Def("addon", "actions", "wave", """{ "defName": "Wave" }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit", "abstract": true }"""),
|
||||
PackDocuments.Def("addon", "actions", "wave", """{ "defName": "Wave", "abstract": true }"""),
|
||||
]);
|
||||
|
||||
Assert.Equal([CatalogLoader.CorePackId, "addon"], catalog.PackIds);
|
||||
|
||||
@@ -10,8 +10,8 @@ public class InheritanceTests
|
||||
var catalog = _loader.Load(
|
||||
[CatalogLoader.CorePackId],
|
||||
[
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit" }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "inspect", """{ "defName": "Inspect" }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit", "abstract": true }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "inspect", """{ "defName": "Inspect", "abstract": true }"""),
|
||||
PackDocuments.Def(
|
||||
CatalogLoader.CorePackId,
|
||||
"things",
|
||||
|
||||
@@ -118,7 +118,7 @@ public class MapValidationTests
|
||||
|
||||
private static List<ContentDocument> MiniDefs(bool abstractYard = false) =>
|
||||
[
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit" }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit", "abstract": true }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "things", "chair", """{ "defName": "Chair", "actions": ["Sit"] }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "positions", "principal", """{ "defName": "Principal" }"""),
|
||||
PackDocuments.Def(
|
||||
|
||||
@@ -10,8 +10,8 @@ public class PatchTests
|
||||
var catalog = _loader.Load(
|
||||
[CatalogLoader.CorePackId, "addon"],
|
||||
[
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit" }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "inspect", """{ "defName": "Inspect" }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "sit", """{ "defName": "Sit", "abstract": true }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "actions", "inspect", """{ "defName": "Inspect", "abstract": true }"""),
|
||||
PackDocuments.Def(CatalogLoader.CorePackId, "things", "chair", """{ "defName": "Chair", "actions": ["Sit"] }"""),
|
||||
PackDocuments.Patch(
|
||||
"addon",
|
||||
|
||||
@@ -16,7 +16,9 @@ public class PeopleDefTests
|
||||
Assert.True(catalog.BodyAttributes.ContainsKey("Height"));
|
||||
Assert.Equal(BodyAttributeKind.Number, catalog.BodyAttributes["Height"].Kind);
|
||||
Assert.Equal(BodyAttributeKind.Choice, catalog.BodyAttributes["HairColor"].Kind);
|
||||
Assert.All(catalog.Needs.Values, need => Assert.Equal(0, need.DecayPerHour));
|
||||
Assert.All(catalog.Needs.Values, need => Assert.True(need.DecayPerHour > 0));
|
||||
Assert.True(catalog.Needs["Sleep"].RestoredOffCampus);
|
||||
Assert.Equal(0.1f, catalog.Needs["Hunger"].DecayPerHour);
|
||||
Assert.Contains(catalog.Skills["Agility"].BodyLimits, limit => limit.Attribute == "Build" && limit.Value == "Obese");
|
||||
Assert.True(catalog.NameSets.ContainsKey("Slavic"));
|
||||
Assert.True(catalog.NameSets["Slavic"].MaleGiven.Count >= 20);
|
||||
|
||||
@@ -64,6 +64,19 @@ public class VanillaCoreTests
|
||||
Assert.Equal(3f, catalog.Territories["SchoolYard"].TravelMinutes);
|
||||
Assert.Equal(4, catalog.Traits["Diligent"].CommuteMinutes);
|
||||
Assert.Equal(-4, catalog.Traits["Lazy"].CommuteMinutes);
|
||||
Assert.True(catalog.Actions["Sit"].Abstract);
|
||||
Assert.Equal("Cafeteria", catalog.Actions["EatLunch"].Room);
|
||||
Assert.Equal("Chair", catalog.Actions["EatLunch"].Thing);
|
||||
Assert.Equal(15f, catalog.Actions["EatLunch"].Minutes);
|
||||
Assert.Equal("Hunger", catalog.Actions["EatLunch"].Need);
|
||||
Assert.Equal(0.5f, catalog.Actions["EatLunch"].NeedGain);
|
||||
Assert.Equal("SchoolYard", catalog.Actions["WalkYard"].Room);
|
||||
Assert.Equal(0.1f, catalog.Needs["Hunger"].DecayPerHour);
|
||||
Assert.True(catalog.Needs["Sleep"].RestoredOffCampus);
|
||||
Assert.NotNull(catalog.BehaviorRules);
|
||||
Assert.Equal(0, catalog.BehaviorRules.CommuteSlackMin);
|
||||
Assert.Equal(6, catalog.BehaviorRules.CommuteSlackMax);
|
||||
Assert.Equal(0.35f, catalog.BehaviorRules.NeedThreshold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -97,6 +110,7 @@ public class VanillaCoreTests
|
||||
keys.AddRange(Names(catalog.Staffing.Values));
|
||||
keys.AddRange(Names(catalog.DayFrames.Values));
|
||||
keys.AddRange(Names(catalog.Holidays.Values));
|
||||
keys.AddRange(Names(catalog.Behavior.Values));
|
||||
|
||||
// Derived in code, so no def carries them.
|
||||
keys.Add(BodyBuilds.Attribute);
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
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 Sleep_ReturnsToMaxOffCampus()
|
||||
{
|
||||
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) =>
|
||||
{
|
||||
Assert.Equal(catalog.Needs["Sleep"].Max, needs.Values["Sleep"]);
|
||||
Assert.Equal(0.4f, 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);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Arch.Core;
|
||||
using HSchool.Ai;
|
||||
using HSchool.Content;
|
||||
using HSchool.People;
|
||||
|
||||
@@ -56,7 +57,7 @@ public class PeopleInSchoolTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CoreNeeds_DoNotMoveOnTick()
|
||||
public void OffCampusNeeds_DoNotMoveOnTick()
|
||||
{
|
||||
var (catalog, map) = Vanilla();
|
||||
var roster = RosterGenerator.Generate(catalog, map, schoolSeed: 2, "Slavic", Start);
|
||||
@@ -90,7 +91,9 @@ public class PeopleInSchoolTests
|
||||
var world = World.Create();
|
||||
try
|
||||
{
|
||||
world.Create(new PersonNeeds(new Dictionary<string, float>(StringComparer.Ordinal) { ["Hunger"] = 1f }));
|
||||
world.Create(
|
||||
new PersonNeeds(new Dictionary<string, float>(StringComparer.Ordinal) { ["Hunger"] = 1f }),
|
||||
new Presence("yard", 0f, "yard", false, []));
|
||||
NeedDecay.Apply(world, catalog, gameMinutes: 60);
|
||||
var query = new QueryDescription().WithAll<PersonNeeds>();
|
||||
world.Query(in query, (ref PersonNeeds needs) => Assert.Equal(0f, needs.Values["Hunger"]));
|
||||
|
||||
@@ -48,7 +48,7 @@ public class SchoolTests
|
||||
var loader = new CatalogLoader();
|
||||
var documents = new[]
|
||||
{
|
||||
new ContentDocument("core", "defs/actions/sit.jsonc", """{ "defName": "Sit" }"""),
|
||||
new ContentDocument("core", "defs/actions/sit.jsonc", """{ "defName": "Sit", "abstract": true }"""),
|
||||
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" }"""),
|
||||
|
||||
Reference in New Issue
Block a user