using HSchool.Content; using HSchool.People; namespace HSchool.Ai; /// /// Pure map / roster helpers for auto director summons. Queue state lives on the school worker. /// public static class DirectorSummons { public const string HearingAction = "PrincipalHearing"; public const string OfficeRoomDef = "PrincipalsOffice"; public const string CorridorRoomDef = "Corridor"; 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. /// Prefer a link; tie-break by ordinal id. /// 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 HasHiredPrincipal(Roster roster) => roster.People.Any(person => person.IsStaff && Staffing.PrincipalPosition.Equals(person.Position, StringComparison.Ordinal)); public static string? PrincipalId(Roster roster) => roster.People .Where(person => person.IsStaff && Staffing.PrincipalPosition.Equals(person.Position, StringComparison.Ordinal)) .Select(person => person.Id) .OrderBy(id => id, StringComparer.Ordinal) .FirstOrDefault(); public static bool CanStart(Roster roster, MapLayout map) => HasHiredPrincipal(roster) && OfficeNodeId(map) is not null && WaitNodeId(map) is not null; public static bool RollSummon(float chance, int seed) => chance >= 1f || (chance > 0f && TalkCircles.Roll01(seed) < chance); 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; } }