Pause the school on warning notices until the owner dismisses them.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-20 22:33:38 +03:00
co-authored by Cursor
parent e22d9bae33
commit f36af269bb
27 changed files with 993 additions and 51 deletions
@@ -0,0 +1,93 @@
using HSchool.Content;
using HSchool.Protocol;
using HSchool.Server.Game;
namespace HSchool.Server.Tests;
public class NoticeBoardTests
{
[Fact]
public void PausingPost_IsSticky_InfoIsNot()
{
var board = new NoticeBoard(maxSticky: 8);
Assert.True(board.TryPost(Info(), personId: 0, out var info));
Assert.False(info.Pause);
Assert.False(board.HasPausing);
Assert.Empty(board.Sticky);
Assert.True(board.TryPost(Pausing(), personId: 0, out var warning));
Assert.True(warning.Pause);
Assert.True(board.HasPausing);
Assert.Equal("GenerationFailed", Assert.Single(board.Sticky).DefName);
Assert.DoesNotContain(board.ToSave(), row => row.DefName == "DayStarted");
}
[Fact]
public void NinthSticky_IsNotPosted()
{
var board = new NoticeBoard(maxSticky: 8);
for (var i = 0; i < 8; i++)
{
Assert.True(board.TryPost(Pausing(), personId: 0, out _));
}
Assert.False(board.TryPost(Pausing(), personId: 0, out var ninth));
Assert.Equal(default, ninth);
Assert.Equal(8, board.Sticky.Count);
}
[Fact]
public void Dismiss_RemovesSticky_DoesNotClearTheBoardUntilTheLast()
{
var board = new NoticeBoard(maxSticky: 8);
Assert.True(board.TryPost(Pausing(), personId: 0, out var first));
Assert.True(board.TryPost(Pausing(), personId: 0, out var second));
Assert.True(board.TryDismiss(first.Id));
Assert.True(board.HasPausing);
Assert.Equal(second.Id, Assert.Single(board.Sticky).Id);
Assert.True(board.TryDismiss(second.Id));
Assert.False(board.HasPausing);
Assert.False(board.TryDismiss(second.Id));
}
[Fact]
public void Load_RestoresSticky_SkipsInfoRows()
{
var saved = new StickyNoticeSave[]
{
new() { Id = 3, DefName = "DayStarted", Severity = NoticeSeverity.Info, Pause = false, TtlMs = 8000 },
new() { Id = 4, DefName = "GenerationFailed", Severity = NoticeSeverity.Error, Pause = true, TtlMs = 0 },
};
var board = new NoticeBoard(maxSticky: 8, saved);
Assert.True(board.HasPausing);
var sticky = Assert.Single(board.Sticky);
Assert.Equal(4u, sticky.Id);
Assert.Equal("GenerationFailed", sticky.DefName);
Assert.Equal(NoticeSeverity.Error, sticky.Severity);
}
private static EventDef Info() => new()
{
DefName = "DayStarted",
Severity = EventSeverities.Info,
Pause = false,
TtlMs = 8000,
Trigger = EventTriggers.DayStart,
Action = EventActions.None,
};
private static EventDef Pausing() => new()
{
DefName = "GenerationFailed",
Severity = EventSeverities.Error,
Pause = true,
TtlMs = 0,
Trigger = EventTriggers.GenerationFailed,
Action = EventActions.None,
};
}
@@ -44,6 +44,62 @@ public class SchoolStoreTests
}
}
[Fact]
public void Save_RoundTripsStickyNotices_AndOmitsThemWhenMissing()
{
var directory = Directory.CreateTempSubdirectory("h-school-store-");
try
{
var store = CreateStore(directory.FullName);
store.Save(new SchoolSave
{
Format = 3,
Id = 2,
Name = "Sticky",
GameTime = new DateTime(2012, 3, 31, 6, 0, 0, DateTimeKind.Utc),
Running = false,
SpeedIndex = 1,
Notices =
[
new StickyNoticeSave
{
Id = 9,
DefName = "GenerationFailed",
Severity = 2,
Pause = true,
TtlMs = 0,
},
],
});
var withNotices = Assert.Single(store.LoadAll());
var sticky = Assert.Single(withNotices.Notices!);
Assert.Equal(9u, sticky.Id);
Assert.Equal("GenerationFailed", sticky.DefName);
Assert.True(sticky.Pause);
File.WriteAllText(
Path.Combine(directory.FullName, "3.json"),
"""
{
"format": 3,
"id": 3,
"name": "Plain",
"gameTime": "2012-03-31T06:00:00Z",
"running": false,
"speedIndex": 0
}
""");
var plain = store.LoadAll().Single(save => save.Id == 3);
Assert.True(plain.Notices is null || plain.Notices.Count == 0);
}
finally
{
directory.Delete(recursive: true);
}
}
private static SchoolStore CreateStore(string directory)
{
var options = Options.Create(new SimulationOptions { SavesDirectory = directory });