Auto-summon pupils to the principal after fight breaks, with a corridor FIFO queue.
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
using HSchool.Content;
|
||||
using HSchool.People;
|
||||
|
||||
namespace HSchool.Ai;
|
||||
|
||||
/// <summary>
|
||||
/// Pure map / roster helpers for auto director summons. Queue state lives on the school worker.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Corridor (or other neighbour) where the queue waits — never inside the office.
|
||||
/// Prefer a <see cref="CorridorRoomDef"/> link; tie-break by ordinal id.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user