Update wire protocol to version 5, introducing pupil slots and item counts in map snapshots. Enhance UI components to display pupil slots and item counts in game screens and map editors. Revise localization strings for improved user guidance. Update tests to validate new functionalities and ensure robustness in handling room and item data.
This commit is contained in:
@@ -352,6 +352,11 @@ public sealed class CatalogLoader
|
||||
{
|
||||
foreach (var thing in catalog.Things.Values)
|
||||
{
|
||||
if (thing.PupilSlots < 0 || thing.PupilSlots > byte.MaxValue)
|
||||
{
|
||||
throw new ContentLoadException($"ThingDef '{thing.DefName}' pupilSlots must be 0–{byte.MaxValue}.");
|
||||
}
|
||||
|
||||
foreach (var action in thing.Actions)
|
||||
{
|
||||
if (!catalog.Actions.ContainsKey(action))
|
||||
@@ -369,6 +374,11 @@ public sealed class CatalogLoader
|
||||
{
|
||||
throw new ContentLoadException($"RoomDef '{room.DefName}' slot '{slot.Key}' references unknown ThingDef '{slot.Thing}'.");
|
||||
}
|
||||
|
||||
if (slot.Count < 0 || slot.Count > byte.MaxValue)
|
||||
{
|
||||
throw new ContentLoadException($"RoomDef '{room.DefName}' slot '{slot.Key}' count must be 0–{byte.MaxValue}.");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var position in room.Positions)
|
||||
|
||||
@@ -27,6 +27,12 @@ public sealed class ActionDef : Def;
|
||||
public sealed class ThingDef : Def
|
||||
{
|
||||
public IReadOnlyList<string> Actions { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// How many pupils this thing hosts for lessons. Zero for teacher's furniture — a chair
|
||||
/// must not inflate class size just because someone sits in it.
|
||||
/// </summary>
|
||||
public int PupilSlots { get; init; }
|
||||
}
|
||||
|
||||
public sealed class PositionDef : Def;
|
||||
|
||||
@@ -63,6 +63,12 @@ public sealed class SlotFill
|
||||
public required string Key { get; init; }
|
||||
|
||||
public required string Thing { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// How many of <see cref="Thing"/> occupy this slot. Missing or non-positive JSON is 1, so
|
||||
/// older maps without a count still round-trip.
|
||||
/// </summary>
|
||||
public int Count { get; init; } = 1;
|
||||
}
|
||||
|
||||
public sealed class MapLink
|
||||
|
||||
@@ -140,6 +140,11 @@ public static class MapValidator
|
||||
{
|
||||
throw new MapValidationException($"Room '{room.Id}' slot '{fill.Key}' uses unknown or abstract ThingDef '{fill.Thing}'.");
|
||||
}
|
||||
|
||||
if (fill.Count > byte.MaxValue)
|
||||
{
|
||||
throw new MapValidationException($"Room '{room.Id}' slot '{fill.Key}' count {fill.Count} is above {byte.MaxValue}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,11 +21,17 @@ public sealed class MapViewNode
|
||||
|
||||
public required string Name { get; init; }
|
||||
|
||||
public IReadOnlyList<string> Items { 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.
|
||||
@@ -47,6 +53,7 @@ public static class MapView
|
||||
parentId: string.Empty,
|
||||
LabelOf(catalog, locale, DefKind.Territory, territory.Def),
|
||||
items: [],
|
||||
pupilSlots: 0,
|
||||
PositionsOf(catalog, locale, DefKind.Territory, territory.Def)),
|
||||
};
|
||||
|
||||
@@ -58,6 +65,7 @@ public static class MapView
|
||||
territory.Id,
|
||||
LabelOf(catalog, locale, DefKind.Building, building.Def),
|
||||
[],
|
||||
pupilSlots: 0,
|
||||
PositionsOf(catalog, locale, DefKind.Building, building.Def)));
|
||||
}
|
||||
|
||||
@@ -69,23 +77,20 @@ public static class MapView
|
||||
floor.Building,
|
||||
FloorName(catalog, locale, floor),
|
||||
[],
|
||||
pupilSlots: 0,
|
||||
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));
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
@@ -97,7 +102,8 @@ public static class MapView
|
||||
string id,
|
||||
string parentId,
|
||||
string name,
|
||||
IReadOnlyList<string> items,
|
||||
IReadOnlyList<MapViewItem> items,
|
||||
int pupilSlots,
|
||||
IReadOnlyList<string> positions) =>
|
||||
new()
|
||||
{
|
||||
@@ -106,9 +112,33 @@ public static class MapView
|
||||
ParentId = parentId,
|
||||
Name = name,
|
||||
Items = items,
|
||||
PupilSlots = pupilSlots,
|
||||
Positions = positions,
|
||||
};
|
||||
|
||||
private static (IReadOnlyList<MapViewItem> Items, int PupilSlots) RoomContents(
|
||||
DefCatalog catalog,
|
||||
string locale,
|
||||
RoomNode room)
|
||||
{
|
||||
var items = new List<MapViewItem>(room.Slots.Count);
|
||||
var pupilSlots = 0L;
|
||||
foreach (var fill in room.Slots)
|
||||
{
|
||||
var count = SlotQuantity(fill.Count);
|
||||
items.Add(new MapViewItem(LabelOf(catalog, locale, DefKind.Thing, fill.Thing), count));
|
||||
if (catalog.Things.TryGetValue(fill.Thing, out var thing) && thing.PupilSlots > 0)
|
||||
{
|
||||
pupilSlots += (long)thing.PupilSlots * count;
|
||||
}
|
||||
}
|
||||
|
||||
return (items, (int)Math.Clamp(pupilSlots, 0, ushort.MaxValue));
|
||||
}
|
||||
|
||||
private static int SlotQuantity(int count) =>
|
||||
count < 1 ? 1 : Math.Min(count, byte.MaxValue);
|
||||
|
||||
/// <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
|
||||
|
||||
Reference in New Issue
Block a user