- Marked tasks as complete in the decision-making phase documentation, indicating readiness for implementation. - Updated the README to reflect the completion status of the decision-making phase. - Enhanced localization strings to include new presence tracking features, improving user experience. - Revised the game screen logic to display real-time presence status, including walking states for individuals. - Added tests to validate the new localization strings and presence functionalities, ensuring robust performance.
172 lines
5.6 KiB
C#
172 lines
5.6 KiB
C#
using Arch.Core;
|
|
using HSchool.Ai;
|
|
using HSchool.Content;
|
|
|
|
namespace HSchool.Simulation;
|
|
|
|
/// <summary>
|
|
/// Starts and ticks actions on World entities. Rules live in <see cref="ActionStepper"/>; this
|
|
/// adapter copies them onto components and counts occupied things at a node.
|
|
/// </summary>
|
|
internal static class ActivitySystem
|
|
{
|
|
private static readonly QueryDescription People =
|
|
new QueryDescription().WithAll<PersonIdentity, PersonRoles, PersonNeeds, Presence, PersonActivity>();
|
|
|
|
public static bool TryStart(School school, string personId, string actionId)
|
|
{
|
|
if (school.Catalog is null || school.Map is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!school.Catalog.Actions.TryGetValue(actionId, out var action) || action.Abstract)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var started = false;
|
|
var world = school.World;
|
|
world.Query(
|
|
in People,
|
|
(ref PersonIdentity identity, ref PersonRoles roles, ref Presence presence, ref PersonActivity activity) =>
|
|
{
|
|
if (started || !identity.Id.Equals(personId, StringComparison.Ordinal))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (activity.IsActive || presence.NodeId is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!RoleFits(action, roles))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var location = school.Map.NodeDef(presence.NodeId);
|
|
if (location is null || !location.Equals(action.Room, StringComparison.Ordinal))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(action.Thing))
|
|
{
|
|
var available = RoomOccupancy.ThingCount(school.Catalog, school.Map, presence.NodeId, action.Thing);
|
|
var occupied = Occupied(school, presence.NodeId, action.Thing);
|
|
if (!ActionStepper.CanOccupy(available, occupied))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
var next = ActionStepper.Begin(action);
|
|
activity = new PersonActivity(next.ActionId, next.Thing, next.RemainingMinutes);
|
|
started = true;
|
|
});
|
|
return started;
|
|
}
|
|
|
|
public static IReadOnlyList<string> Apply(School school, double gameMinutes)
|
|
{
|
|
if (school.Catalog is null || gameMinutes <= 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
var catalog = school.Catalog;
|
|
var minutes = (float)gameMinutes;
|
|
var completed = new List<string>();
|
|
var world = school.World;
|
|
world.Query(in People, (ref PersonIdentity identity, ref PersonNeeds needs, ref Presence presence, ref PersonActivity activity) =>
|
|
{
|
|
if (!activity.IsActive)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!presence.IsOnCampus || activity.ActionId is null || !catalog.Actions.TryGetValue(activity.ActionId, out var action))
|
|
{
|
|
activity = PersonActivity.Idle;
|
|
return;
|
|
}
|
|
|
|
var location = school.Map?.NodeDef(presence.NodeId ?? "");
|
|
if (location is null || !location.Equals(action.Room, StringComparison.Ordinal))
|
|
{
|
|
activity = PersonActivity.Idle;
|
|
return;
|
|
}
|
|
|
|
var next = ActionStepper.Advance(
|
|
new ActivityProgress(activity.ActionId, activity.Thing, activity.RemainingMinutes),
|
|
minutes,
|
|
out var finished);
|
|
if (!finished)
|
|
{
|
|
activity = new PersonActivity(next.ActionId, next.Thing, next.RemainingMinutes);
|
|
return;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(action.Need)
|
|
&& catalog.Needs.TryGetValue(action.Need, out var need)
|
|
&& needs.Values.TryGetValue(action.Need, out var current))
|
|
{
|
|
needs.Values[action.Need] = ActionStepper.ApplyNeedGain(current, action, need);
|
|
}
|
|
|
|
activity = PersonActivity.Idle;
|
|
completed.Add(identity.Id);
|
|
});
|
|
completed.Sort(StringComparer.Ordinal);
|
|
return completed;
|
|
}
|
|
|
|
private static int Occupied(School school, string nodeId, string thing)
|
|
{
|
|
var occupied = 0;
|
|
var world = school.World;
|
|
world.Query(in People, (ref Presence presence, ref PersonActivity activity) =>
|
|
{
|
|
if (activity.IsActive
|
|
&& presence.NodeId is not null
|
|
&& presence.NodeId.Equals(nodeId, StringComparison.Ordinal)
|
|
&& thing.Equals(activity.Thing, StringComparison.Ordinal))
|
|
{
|
|
occupied++;
|
|
}
|
|
});
|
|
return occupied;
|
|
}
|
|
|
|
private static bool RoleFits(ActionDef action, PersonRoles roles)
|
|
{
|
|
if (action.Roles.Count == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
foreach (var role in action.Roles)
|
|
{
|
|
if (role.Equals(HSchool.Content.PersonRoles.Student, StringComparison.OrdinalIgnoreCase) && roles.IsStudent)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (role.Equals(HSchool.Content.PersonRoles.Staff, StringComparison.OrdinalIgnoreCase) && roles.IsStaff)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (role.Equals(HSchool.Content.PersonRoles.Parent, StringComparison.OrdinalIgnoreCase) && roles.IsParent)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|