using HSchool.Content; using HSchool.People; namespace HSchool.Ai; /// /// Pure map / roster helpers for nurse visits. Queue state lives on the school worker. /// Simpler than director summons: no hearing opinion, tend while co-located. /// public static class NurseCare { /// Presence / card label while walking to the wait node or office. public const string GoingAction = "GoingToNurse"; /// Presence / card label while queued in the corridor. public const string WaitingAction = "WaitForNurse"; /// Presence / card label while being tended in the office. public const string CareAction = "NurseCare"; public const byte PresenceNone = 0; public const byte PresenceGoing = 1; public const byte PresenceWaiting = 2; public const byte PresenceCare = 3; public const string OfficeRoomDef = "MedicalOffice"; public const string CorridorRoomDef = "Corridor"; public const string MedicineSkill = "Medicine"; public static string? OfficeNodeId(MapLayout map) { ArgumentNullException.ThrowIfNull(map); return map.Rooms .Where(room => room.Def.Equals(OfficeRoomDef, StringComparison.Ordinal)) .Select(room => room.Id) .OrderBy(id => id, StringComparer.Ordinal) .FirstOrDefault(); } /// /// Corridor (or other neighbour) where the queue waits — never inside the office. /// public static string? WaitNodeId(MapLayout map, string? officeNodeId = null) { ArgumentNullException.ThrowIfNull(map); officeNodeId ??= OfficeNodeId(map); if (officeNodeId is null) { return null; } string? bestCorridor = null; string? bestAny = null; foreach (var link in map.Links) { var other = OtherEnd(link, officeNodeId); if (other is null) { continue; } bestAny = PickOrdinal(bestAny, other); if (map.NodeDef(other) is { } def && def.Equals(CorridorRoomDef, StringComparison.Ordinal)) { bestCorridor = PickOrdinal(bestCorridor, other); } } return bestCorridor ?? bestAny; } public static bool HasHiredNurse(Roster roster) => roster.People.Any(person => person.IsStaff && Staffing.NursePosition.Equals(person.Position, StringComparison.Ordinal)); public static string? NurseId(Roster roster) => roster.People .Where(person => person.IsStaff && Staffing.NursePosition.Equals(person.Position, StringComparison.Ordinal)) .Select(person => person.Id) .OrderBy(id => id, StringComparer.Ordinal) .FirstOrDefault(); public static float MedicineOf(Person? nurse) => nurse is not null && nurse.Skills.TryGetValue(MedicineSkill, out var level) ? level : 0f; public static bool CanStart(Roster roster, MapLayout map) => HasHiredNurse(roster) && OfficeNodeId(map) is not null && WaitNodeId(map) is not null; private static string? OtherEnd(MapLink link, string nodeId) { if (link.A.Equals(nodeId, StringComparison.Ordinal)) { return link.B; } if (link.B.Equals(nodeId, StringComparison.Ordinal)) { return link.A; } return null; } private static string PickOrdinal(string? current, string candidate) { if (current is null) { return candidate; } return string.CompareOrdinal(candidate, current) < 0 ? candidate : current; } }