Sick people seek MedicalOffice for tend by Medicine skill; symptoms cut talk weight; no heal API. Co-authored-by: Cursor <cursoragent@cursor.com>
123 lines
3.7 KiB
C#
123 lines
3.7 KiB
C#
using HSchool.Content;
|
|
using HSchool.People;
|
|
|
|
namespace HSchool.Ai;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public static class NurseCare
|
|
{
|
|
/// <summary>Presence / card label while walking to the wait node or office.</summary>
|
|
public const string GoingAction = "GoingToNurse";
|
|
|
|
/// <summary>Presence / card label while queued in the corridor.</summary>
|
|
public const string WaitingAction = "WaitForNurse";
|
|
|
|
/// <summary>Presence / card label while being tended in the office.</summary>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Corridor (or other neighbour) where the queue waits — never inside the office.
|
|
/// </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 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;
|
|
}
|
|
}
|