Files
h-school/src/HSchool.Server/Game/PresenceFrame.cs
T
Leonid Pershin 9e429d0cd7 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.
2026-08-21 13:07:47 +03:00

104 lines
3.6 KiB
C#

using HSchool.Content;
using HSchool.People;
using HSchool.Protocol;
using HSchool.Schedule;
using HSchool.Simulation;
namespace HSchool.Server.Game;
/// <summary>
/// Builds a presence frame from stored places plus the current lesson. Occupancy is state;
/// the lesson labels are still derived from the timetable so an empty classroom during a period
/// still shows what should be happening there.
/// </summary>
internal static class PresenceFrame
{
public static ServerPresenceMessage Build(School school, int weekDays, string locale)
{
var people = new List<PresencePerson>();
var counts = new Dictionary<string, int>(StringComparer.Ordinal);
foreach (var row in school.CapturePresence())
{
if (row.NodeId is null)
{
continue;
}
var walking = row.Path.Count > 0 || row.RemainingMinutes > 0;
var circle = school.TalkCircleOf(row.PersonId);
var summonPhase = school.DirectorSummonPresencePhase(row.PersonId);
people.Add(new PresencePerson(
row.PersonId,
row.NodeId,
walking ? PresenceState.Walking : PresenceState.Here,
circle?.MemberIds ?? [],
circle?.TopicId ?? "",
summonPhase));
counts[row.NodeId] = counts.GetValueOrDefault(row.NodeId) + 1;
}
people.Sort((left, right) => StringComparer.Ordinal.Compare(left.Id, right.Id));
var activity = ActivityByRoom(school, weekDays, locale);
var nodeIds = counts.Keys
.Concat(activity.Keys)
.Distinct(StringComparer.Ordinal)
.OrderBy(id => id, StringComparer.Ordinal);
var nodes = new List<PresenceNode>();
foreach (var id in nodeIds)
{
var count = (ushort)Math.Min(counts.GetValueOrDefault(id), ushort.MaxValue);
var subject = "";
var classLabel = "";
if (activity.TryGetValue(id, out var labels))
{
subject = labels.Subject;
classLabel = labels.Class;
}
nodes.Add(new PresenceNode(id, count, subject, classLabel));
}
return new ServerPresenceMessage(school.Id, nodes, people);
}
private static Dictionary<string, (string Subject, string Class)> ActivityByRoom(
School school,
int weekDays,
string locale)
{
var labels = new Dictionary<string, (string Subject, string Class)>(StringComparer.Ordinal);
if (school.Timetable is null || school.Roster is null || school.Catalog is null)
{
return labels;
}
var occurring = TimetableClock.OccurringAt(
school.Timetable,
school.Catalog,
school.Clock.Time,
weekDays);
if (occurring.Count == 0)
{
return labels;
}
var catalog = school.Catalog;
var classes = school.Roster.Classes.ToDictionary(item => item.Id, StringComparer.Ordinal);
foreach (var lesson in occurring)
{
classes.TryGetValue(lesson.ClassId, out var schoolClass);
var subjectLabel = catalog.Subjects.TryGetValue(lesson.Subject, out var subject)
? catalog.Label(locale, subject)
: lesson.Subject;
var classLabel = schoolClass is null
? lesson.ClassId
: $"{schoolClass.Year}{schoolClass.Letter}";
labels[lesson.RoomId] = (subjectLabel, classLabel);
}
return labels;
}
}