Update wire protocol to version 6 and enhance timetable functionality
- 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.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user