namespace HSchool.Content; /// Kind of a map-tree node, matching the snapshot wire values. public enum MapNodeKind : byte { Territory = 0, Building = 1, Floor = 2, Room = 3, } /// One labelled node of a school's map, ready to put on the wire or in a test. public sealed class MapViewNode { public required MapNodeKind Kind { get; init; } public required string Id { get; init; } /// Empty for the yard. public required string ParentId { get; init; } public required string Name { get; init; } public IReadOnlyList Items { get; init; } = []; /// Sum of ThingDef.PupilSlots × fill count for this node. Zero off rooms. public int PupilSlots { get; init; } public IReadOnlyList Positions { get; init; } = []; } /// One stacked thing in a room. The client formats Парта ×16; this is the data. public sealed record MapViewItem(string Name, int Count); /// /// 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. /// public static class MapView { public static IReadOnlyList Build(DefCatalog catalog, MapLayout map, string locale) { if (map.Territory is not { } territory) { return []; } var nodes = new List { 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 items, int pupilSlots, IReadOnlyList positions) => new() { Kind = kind, Id = id, ParentId = parentId, Name = name, Items = items, PupilSlots = pupilSlots, Positions = positions, }; private static (IReadOnlyList 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(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)); } /// /// A floor's 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 floorName in the editor. /// 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}"; } /// /// Same rule as floors: is "101", not a replacement for "Classroom". /// Do not treat "label equals defName" as "untranslated" — English classrooms are named Classroom. /// 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 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; } }