using Arch.Core; using HSchool.Ai; using HSchool.Content; namespace HSchool.Simulation; /// /// Starts and ticks actions on World entities. Rules live in ; this /// adapter copies them onto components and counts occupied things at a node. /// internal static class ActivitySystem { private static readonly QueryDescription People = new QueryDescription().WithAll(); 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 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(); 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; } }