- Bumped the wire protocol version to 6, reflecting changes in the communication structure.
- Expanded the timetable API with new endpoints for fetching and managing lesson schedules, including `GET /api/schools/{id}/timetable` and `POST /api/schools/{id}/timetable/pin`.
- Updated the protocol documentation to include detailed descriptions of the new timetable features and message structures.
- Enhanced the client-side implementation to support the new timetable functionalities, including lesson pinning and unpinning.
- Revised server-side logic to handle timetable operations and ensure proper integration with existing school management features.
- Added tests to validate the new timetable functionalities and ensure robustness in handling lesson data.
94 lines
2.8 KiB
C#
94 lines
2.8 KiB
C#
using HSchool.Content;
|
|
using HSchool.People;
|
|
using HSchool.Protocol;
|
|
using HSchool.Schedule;
|
|
using HSchool.Simulation;
|
|
|
|
namespace HSchool.Server.Game;
|
|
|
|
/// <summary>
|
|
/// Overlays the current lesson onto map-tree nodes. Occupancy is derived from the timetable and
|
|
/// the clock; it is not stored on the map.
|
|
/// </summary>
|
|
internal static class MapOccupancy
|
|
{
|
|
public static void Apply(
|
|
MapSnapshotNode[] nodes,
|
|
School school,
|
|
int weekDays,
|
|
string locale)
|
|
{
|
|
if (school.Timetable is null || school.Roster is null || school.Catalog is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var occurring = TimetableClock.OccurringAt(
|
|
school.Timetable,
|
|
school.Catalog,
|
|
school.Clock.Time,
|
|
weekDays);
|
|
if (occurring.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var catalog = school.Catalog;
|
|
var classes = school.Roster.Classes.ToDictionary(item => item.Id, StringComparer.Ordinal);
|
|
var people = school.Roster.People.ToDictionary(person => person.Id, StringComparer.Ordinal);
|
|
var byRoom = occurring.ToDictionary(lesson => lesson.RoomId, StringComparer.Ordinal);
|
|
|
|
for (var i = 0; i < nodes.Length; i++)
|
|
{
|
|
if (!byRoom.TryGetValue(nodes[i].Id, out var lesson))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
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}";
|
|
|
|
nodes[i] = nodes[i] with
|
|
{
|
|
ActivitySubject = subjectLabel,
|
|
ActivityClass = classLabel,
|
|
Characters = NamesOf(lesson, schoolClass, people),
|
|
};
|
|
}
|
|
}
|
|
|
|
private static string[] NamesOf(
|
|
LessonPlacement lesson,
|
|
SchoolClass? schoolClass,
|
|
IReadOnlyDictionary<string, Person> people)
|
|
{
|
|
var names = new List<string>();
|
|
if (people.TryGetValue(lesson.TeacherId, out var teacher))
|
|
{
|
|
names.Add(teacher.Name.Full);
|
|
}
|
|
else if (lesson.TeacherId.Length > 0)
|
|
{
|
|
names.Add(lesson.TeacherId);
|
|
}
|
|
|
|
if (schoolClass is not null)
|
|
{
|
|
foreach (var pupilId in schoolClass.PupilIds)
|
|
{
|
|
if (people.TryGetValue(pupilId, out var pupil))
|
|
{
|
|
names.Add(pupil.Name.Full);
|
|
}
|
|
}
|
|
}
|
|
|
|
return names.Count > byte.MaxValue ? [.. names.Take(byte.MaxValue)] : [.. names];
|
|
}
|
|
}
|