Add EventDef notices, morning and lesson bells, and info toasts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-20 20:46:33 +03:00
co-authored by Cursor
parent ea3f5c7bf1
commit a554738084
38 changed files with 952 additions and 22 deletions
+2
View File
@@ -42,6 +42,8 @@ internal abstract record GameCommand
internal sealed record SkipEmpty(uint PlayerId, string NormalizedUserName) : GameCommand;
internal sealed record DismissNotice(uint PlayerId, uint NoticeId) : GameCommand;
/// <summary>Stops every worker, re-reads the save directory, starts workers from those files.</summary>
internal sealed record ReloadSaves(TaskCompletionSource Result) : GameCommand;
@@ -225,6 +225,10 @@ internal sealed class GameLoopService(
HandleClockCommand(skipEmpty.PlayerId, skipEmpty.NormalizedUserName, new WorkerCommand.SkipEmpty());
break;
case GameCommand.DismissNotice dismiss:
RouteOpenSchool(dismiss.PlayerId, new WorkerCommand.DismissNotice(dismiss.NoticeId));
break;
case GameCommand.ReloadSaves reload:
await HandleReloadAsync(reload).ConfigureAwait(false);
break;
@@ -61,6 +61,10 @@ internal sealed partial class SchoolWorker
ApplySkip(school);
break;
case WorkerCommand.DismissNotice:
// Info is not stored; pausing dismiss is phase 65.
break;
case WorkerCommand.Dump dump:
dump.Result.TrySetResult(SchoolDumpReader.Read(school, _options.SchoolWeekDays));
break;
@@ -165,6 +165,7 @@ internal sealed partial class SchoolWorker
{
PublishSnapshot();
BroadcastClock();
EmitWorldEvents(school);
MaybeBroadcastPresence(school);
}
@@ -244,6 +245,7 @@ internal sealed partial class SchoolWorker
PublishSnapshot();
Persist();
BroadcastClock();
EmitWorldEvents(school);
BroadcastPresence();
_presenceAge = 0;
}
@@ -489,4 +491,48 @@ internal sealed partial class SchoolWorker
var length = ProtocolCodec.WritePresence(frame, message);
client.TrySendReliable(frame.AsMemory(0, length));
}
private void EmitWorldEvents(School school)
{
var catalog = school.Catalog;
if (catalog is null)
{
school.DrainWorldEvents();
return;
}
foreach (var fact in school.DrainWorldEvents())
{
foreach (var def in catalog.Events.Values)
{
if (def.Abstract
|| !def.Trigger.Equals(fact.Trigger, StringComparison.Ordinal)
|| !def.Severity.Equals(EventSeverities.Info, StringComparison.Ordinal))
{
continue;
}
BroadcastNotice(new ServerNoticeMessage(
++_nextNoticeId,
def.DefName,
NoticeSeverity.Info,
def.Pause,
(uint)Math.Max(0, def.TtlMs)));
}
}
}
private void BroadcastNotice(in ServerNoticeMessage message)
{
var frame = new byte[ProtocolCodec.NoticeSize(message)];
var length = ProtocolCodec.WriteNotice(frame, message);
var payload = frame.AsMemory(0, length);
foreach (var client in _clients.All)
{
if (client.IsReady && client.OpenSchoolId == _id)
{
client.TrySendReliable(payload);
}
}
}
}
+1
View File
@@ -53,6 +53,7 @@ internal sealed partial class SchoolWorker
private Timetable? _timetableSnapshot;
private MapLayout? _mapSnapshot;
private int _presenceAge;
private uint _nextNoticeId;
private School? _school;
private Task? _run;
private bool _persistOnStop = true;
+2
View File
@@ -22,6 +22,8 @@ internal abstract record WorkerCommand
internal sealed record SkipEmpty : WorkerCommand;
internal sealed record DismissNotice(uint Id) : WorkerCommand;
internal sealed record Dump(TaskCompletionSource<SchoolLiveDump?> Result) : WorkerCommand;
internal sealed record GetPerson(