Files
h-school/src/HSchool.Content/MapView.cs
T
2026-08-18 23:51:10 +03:00

184 lines
6.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<MapViewItem> Items { get; init; } = [];
/// <summary>Sum of <c>ThingDef.PupilSlots × fill count</c> for this node. Zero off rooms.</summary>
public int PupilSlots { get; init; }
public IReadOnlyList<string> Positions { get; init; } = [];
}
/// <summary>One stacked thing in a room. The client formats <c>Парта ×16</c>; this is the data.</summary>
public sealed record MapViewItem(string Name, int Count);
/// <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: [],
pupilSlots: 0,
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),
[],
pupilSlots: 0,
PositionsOf(catalog, locale, DefKind.Building, building.Def)));
}
foreach (var floor in map.Floors)
{
nodes.Add(Node(
MapNodeKind.Floor,
floor.Id,
floor.Building,
FloorName(catalog, locale, floor),
[],
pupilSlots: 0,
PositionsOf(catalog, locale, DefKind.Floor, floor.Def)));
}
foreach (var room in map.Rooms)
{
var (items, pupilSlots) = RoomContents(catalog, locale, room);
nodes.Add(Node(
MapNodeKind.Room,
room.Id,
room.Floor,
RoomName(catalog, locale, room),
items,
pupilSlots,
PositionsOf(catalog, locale, DefKind.Room, room.Def)));
}
return nodes;
}
private static MapViewNode Node(
MapNodeKind kind,
string id,
string parentId,
string name,
IReadOnlyList<MapViewItem> items,
int pupilSlots,
IReadOnlyList<string> positions) =>
new()
{
Kind = kind,
Id = id,
ParentId = parentId,
Name = name,
Items = items,
PupilSlots = pupilSlots,
Positions = positions,
};
private static (IReadOnlyList<MapViewItem> Items, int PupilSlots) RoomContents(
DefCatalog catalog,
string locale,
RoomNode room)
{
if (catalog.Rooms.TryGetValue(room.Def, out var def) && def.Homeroom && !string.IsNullOrWhiteSpace(def.SeatThing))
{
var seats = RoomOccupancy.Quantity(room.Seats > 0 ? room.Seats : def.DefaultSeats);
return (
[new MapViewItem(LabelOf(catalog, locale, DefKind.Thing, def.SeatThing), seats)],
RoomOccupancy.PupilSlots(catalog, room));
}
var items = new List<MapViewItem>(room.Slots.Count);
foreach (var fill in room.Slots)
{
items.Add(new MapViewItem(
LabelOf(catalog, locale, DefKind.Thing, fill.Thing),
RoomOccupancy.Quantity(fill.Count)));
}
return (items, RoomOccupancy.PupilSlots(catalog, room));
}
/// <summary>
/// A floor's <see cref="FloorNode.Label"/> is its designation, not a replacement name — the
/// vanilla map says "1". On its own that is a stray digit in the client's tree, so it follows
/// the localized def label: "Этаж 1", "Floor 1". Mirrored by <c>floorName</c> in the editor.
/// </summary>
private static string FloorName(DefCatalog catalog, string locale, FloorNode floor)
{
var defLabel = LabelOf(catalog, locale, DefKind.Floor, floor.Def);
return string.IsNullOrWhiteSpace(floor.Label) ? defLabel : $"{defLabel} {floor.Label}";
}
/// <summary>
/// Same rule as floors: <see cref="RoomNode.Label"/> is "101", not a replacement for "Classroom".
/// Do not treat "label equals defName" as "untranslated" — English classrooms are named Classroom.
/// </summary>
private static string RoomName(DefCatalog catalog, string locale, RoomNode room)
{
var defLabel = LabelOf(catalog, locale, DefKind.Room, room.Def);
return string.IsNullOrWhiteSpace(room.Label) ? defLabel : $"{defLabel} {room.Label}";
}
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;
}
}