Corridor waiters need a summon-phase byte on presence (v11); EventDef DirectorSummoned is info-only so clocks keep running.
104 lines
3.6 KiB
C#
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;
|
|
}
|
|
}
|