Show director summons as info toasts and presence queue labels.
Corridor waiters need a summon-phase byte on presence (v11); EventDef DirectorSummoned is info-only so clocks keep running.
This commit is contained in:
@@ -104,9 +104,73 @@ internal static class DirectorSummonSystem
|
||||
|
||||
school.DirectorSummons.Add(new DirectorSummonTicket(personId, school.NextSummonOrder++, admitted: false));
|
||||
PresenceSystem.Enqueue(school, personId);
|
||||
school.RaiseWorldEvent(new WorldEvent(EventTriggers.DirectorSummon, personId));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wire tag for the presence frame: going / waiting / hearing. Node + state alone cannot tell
|
||||
/// a corridor waiter from a passer-by, so this is a dedicated byte (protocol bump).
|
||||
/// </summary>
|
||||
public static byte ResolvePresencePhase(School school, string personId)
|
||||
{
|
||||
var ticket = school.DirectorSummons.FirstOrDefault(row =>
|
||||
row.PersonId.Equals(personId, StringComparison.Ordinal));
|
||||
if (ticket is null)
|
||||
{
|
||||
return DirectorSummons.PresenceNone;
|
||||
}
|
||||
|
||||
var walking = false;
|
||||
var hearing = false;
|
||||
string? nodeId = null;
|
||||
school.World.Query(
|
||||
in PresenceQuery,
|
||||
(ref PersonIdentity identity, ref Presence presence, ref PersonActivity activity, ref Intent _) =>
|
||||
{
|
||||
if (!identity.Id.Equals(personId, StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
nodeId = presence.NodeId;
|
||||
walking = presence.Path.Length > 0 || presence.RemainingMinutes > 0
|
||||
|| (presence.DestinationId is not null
|
||||
&& !presence.DestinationId.Equals(presence.NodeId, StringComparison.Ordinal));
|
||||
hearing = activity.IsActive && ActivitySystem.IsPrincipalHearing(activity.ActionId);
|
||||
});
|
||||
|
||||
if (hearing)
|
||||
{
|
||||
return DirectorSummons.PresenceHearing;
|
||||
}
|
||||
|
||||
if (walking)
|
||||
{
|
||||
return DirectorSummons.PresenceGoing;
|
||||
}
|
||||
|
||||
var office = DirectorSummons.OfficeNodeId(school.Map!);
|
||||
if (ticket.Admitted
|
||||
&& office is not null
|
||||
&& nodeId is not null
|
||||
&& nodeId.Equals(office, StringComparison.Ordinal))
|
||||
{
|
||||
return DirectorSummons.PresenceHearing;
|
||||
}
|
||||
|
||||
return DirectorSummons.PresenceWaiting;
|
||||
}
|
||||
|
||||
/// <summary>ActionDef name for the person card when the live activity is idle but summoned.</summary>
|
||||
public static string? PresenceActionId(byte phase) => phase switch
|
||||
{
|
||||
DirectorSummons.PresenceGoing => DirectorSummons.GoingAction,
|
||||
DirectorSummons.PresenceWaiting => DirectorSummons.WaitingAction,
|
||||
DirectorSummons.PresenceHearing => DirectorSummons.HearingAction,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
public static void ClearAll(School school)
|
||||
{
|
||||
if (school.DirectorSummons.Count == 0)
|
||||
|
||||
@@ -426,6 +426,20 @@ public sealed class School : IDisposable
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// <summary>Queue a fact for the worker's notice board. Simulation never builds UI itself.</summary>
|
||||
internal void RaiseWorldEvent(in WorldEvent fact) => _worldEvents.Add(fact);
|
||||
|
||||
/// <summary>
|
||||
/// Presence wire tag for auto director summons. Values match <c>DirectorSummons.Presence*</c>
|
||||
/// and <see cref="Protocol.PresenceSummonPhase"/>.
|
||||
/// </summary>
|
||||
public byte DirectorSummonPresencePhase(string personId) =>
|
||||
DirectorSummonSystem.ResolvePresencePhase(this, personId);
|
||||
|
||||
/// <summary>ActionDef id for the person card when summoned but the live activity is idle.</summary>
|
||||
public string? DirectorSummonPresenceActionId(string personId) =>
|
||||
DirectorSummonSystem.PresenceActionId(DirectorSummonPresencePhase(personId));
|
||||
|
||||
private void RecordWorldEvents(DateTime before, DateTime after)
|
||||
{
|
||||
_worldEvents.AddRange(EventSystem.Detect(this, before, after));
|
||||
|
||||
@@ -2,5 +2,6 @@ namespace HSchool.Simulation;
|
||||
|
||||
/// <summary>
|
||||
/// A fact the school raised this step. Same seam as <c>AffinityEvent</c>: a list, no UI.
|
||||
/// <see cref="PersonKey"/> is empty when the fact is school-wide (morning, lesson bell).
|
||||
/// </summary>
|
||||
public readonly record struct WorldEvent(string Trigger);
|
||||
public readonly record struct WorldEvent(string Trigger, string PersonKey = "");
|
||||
|
||||
Reference in New Issue
Block a user