Enhance school creation and map management features by updating the API to support mod packs and map layouts. Introduce a new map snapshot protocol for efficient data handling during school sessions. Revise documentation to reflect these changes, including updates to the protocol and architecture documents. Improve UI components for mod selection and map editing, ensuring a better user experience. Update tests to validate new functionalities and ensure robustness.
ci / server (push) Failing after 3m31s
ci / client (push) Successful in 14s

This commit is contained in:
Leonid Pershin
2026-08-18 15:15:49 +03:00
parent 1bc75244e8
commit 30cc937069
36 changed files with 1876 additions and 171 deletions
+133
View File
@@ -0,0 +1,133 @@
namespace HSchool.Content;
/// <summary>Kind of a map-tree node, matching the snapshot wire values.</summary>
public enum MapNodeKind : byte
{
Territory = 0,
Building = 1,
Floor = 2,
Room = 3,
}
/// <summary>One labelled node of a school's map, ready to put on the wire or in a test.</summary>
public sealed class MapViewNode
{
public required MapNodeKind Kind { get; init; }
public required string Id { get; init; }
/// <summary>Empty for the yard.</summary>
public required string ParentId { get; init; }
public required string Name { get; init; }
public IReadOnlyList<string> Items { get; init; } = [];
public IReadOnlyList<string> Positions { get; init; } = [];
}
/// <summary>
/// Builds the location tree the client draws. Labels come from the frozen catalog in the
/// requested locale; people and in-place activities are not part of this view.
/// </summary>
public static class MapView
{
public static IReadOnlyList<MapViewNode> Build(DefCatalog catalog, MapLayout map, string locale)
{
if (map.Territory is not { } territory)
{
return [];
}
var nodes = new List<MapViewNode>
{
Node(
MapNodeKind.Territory,
territory.Id,
parentId: string.Empty,
LabelOf(catalog, locale, DefKind.Territory, territory.Def),
items: [],
PositionsOf(catalog, locale, DefKind.Territory, territory.Def)),
};
foreach (var building in map.Buildings)
{
nodes.Add(Node(
MapNodeKind.Building,
building.Id,
territory.Id,
LabelOf(catalog, locale, DefKind.Building, building.Def),
[],
PositionsOf(catalog, locale, DefKind.Building, building.Def)));
}
foreach (var floor in map.Floors)
{
var defLabel = LabelOf(catalog, locale, DefKind.Floor, floor.Def);
var name = string.IsNullOrWhiteSpace(floor.Label) ? defLabel : floor.Label;
nodes.Add(Node(
MapNodeKind.Floor,
floor.Id,
floor.Building,
name,
[],
PositionsOf(catalog, locale, DefKind.Floor, floor.Def)));
}
foreach (var room in map.Rooms)
{
var items = new List<string>(room.Slots.Count);
foreach (var fill in room.Slots)
{
items.Add(LabelOf(catalog, locale, DefKind.Thing, fill.Thing));
}
nodes.Add(Node(
MapNodeKind.Room,
room.Id,
room.Floor,
LabelOf(catalog, locale, DefKind.Room, room.Def),
items,
PositionsOf(catalog, locale, DefKind.Room, room.Def)));
}
return nodes;
}
private static MapViewNode Node(
MapNodeKind kind,
string id,
string parentId,
string name,
IReadOnlyList<string> items,
IReadOnlyList<string> positions) =>
new()
{
Kind = kind,
Id = id,
ParentId = parentId,
Name = name,
Items = items,
Positions = positions,
};
private static string LabelOf(DefCatalog catalog, string locale, DefKind kind, string defName) =>
catalog.TryGet(kind, defName, out var def) ? catalog.Label(locale, def) : defName;
private static IReadOnlyList<string> PositionsOf(DefCatalog catalog, string locale, DefKind kind, string defName)
{
var names = catalog.PositionsFor(kind, defName);
if (names.Count == 0)
{
return [];
}
var labels = new string[names.Count];
for (var i = 0; i < names.Count; i++)
{
labels[i] = LabelOf(catalog, locale, DefKind.Position, names[i]);
}
return labels;
}
}