Add HSchool.Content project for JSONC definitions, catalog, and map validation. Update solution structure to include new content and tests projects. Enhance school management to support mod packs and map instances, ensuring proper loading and validation. Revise documentation to reflect these changes and update tests for new functionality.
ci / server (push) Failing after 3m36s
ci / client (push) Successful in 13s

This commit is contained in:
Leonid Pershin
2026-08-18 14:35:09 +03:00
parent 37c39a3beb
commit 1bc75244e8
55 changed files with 2289 additions and 59 deletions
+120
View File
@@ -0,0 +1,120 @@
namespace HSchool.Content;
/// <summary>
/// Frozen set of defs and locale strings for one school. Built once at create/load; the worker
/// never re-reads pack files afterwards.
/// </summary>
public sealed class DefCatalog
{
internal DefCatalog(
IReadOnlyList<string> packIds,
IReadOnlyDictionary<string, ActionDef> actions,
IReadOnlyDictionary<string, ThingDef> things,
IReadOnlyDictionary<string, PositionDef> positions,
IReadOnlyDictionary<string, WorkDef> works,
IReadOnlyDictionary<string, RoomDef> rooms,
IReadOnlyDictionary<string, BuildingDef> buildings,
IReadOnlyDictionary<string, FloorDef> floors,
IReadOnlyDictionary<string, TerritoryDef> territories,
IReadOnlyDictionary<string, string> ru,
IReadOnlyDictionary<string, string> en)
{
PackIds = packIds;
Actions = actions;
Things = things;
Positions = positions;
Works = works;
Rooms = rooms;
Buildings = buildings;
Floors = floors;
Territories = territories;
_ru = ru;
_en = en;
}
public IReadOnlyList<string> PackIds { get; }
public IReadOnlyDictionary<string, ActionDef> Actions { get; }
public IReadOnlyDictionary<string, ThingDef> Things { get; }
public IReadOnlyDictionary<string, PositionDef> Positions { get; }
public IReadOnlyDictionary<string, WorkDef> Works { get; }
public IReadOnlyDictionary<string, RoomDef> Rooms { get; }
public IReadOnlyDictionary<string, BuildingDef> Buildings { get; }
public IReadOnlyDictionary<string, FloorDef> Floors { get; }
public IReadOnlyDictionary<string, TerritoryDef> Territories { get; }
private readonly IReadOnlyDictionary<string, string> _ru;
private readonly IReadOnlyDictionary<string, string> _en;
public bool TryGet(DefKind kind, string defName, out Def def)
{
Def? found = kind switch
{
DefKind.Action => Actions.GetValueOrDefault(defName),
DefKind.Thing => Things.GetValueOrDefault(defName),
DefKind.Position => Positions.GetValueOrDefault(defName),
DefKind.Work => Works.GetValueOrDefault(defName),
DefKind.Room => Rooms.GetValueOrDefault(defName),
DefKind.Building => Buildings.GetValueOrDefault(defName),
DefKind.Floor => Floors.GetValueOrDefault(defName),
DefKind.Territory => Territories.GetValueOrDefault(defName),
_ => null,
};
if (found is null)
{
def = null!;
return false;
}
def = found;
return true;
}
/// <summary>
/// Positions the location panel should list for this node type. Only RoomDefs carry them today.
/// </summary>
public IReadOnlyList<string> PositionsFor(DefKind kind, string defName) =>
kind == DefKind.Room && Rooms.TryGetValue(defName, out var room) ? room.Positions : [];
/// <summary>Locale string for a def, walking <c>parent</c> when the key is missing. Falls back to <c>defName</c>.</summary>
public string Label(string locale, Def def)
{
var table = locale.Equals("en", StringComparison.OrdinalIgnoreCase) ? _en : _ru;
var current = def;
while (true)
{
if (table.TryGetValue(current.DefName, out var label))
{
return label;
}
if (current.Parent is null || !TryGet(KindOf(current), current.Parent, out var parent) || parent.DefName == current.DefName)
{
return def.DefName;
}
current = parent;
}
}
internal static DefKind KindOf(Def def) => def switch
{
ActionDef => DefKind.Action,
ThingDef => DefKind.Thing,
PositionDef => DefKind.Position,
WorkDef => DefKind.Work,
RoomDef => DefKind.Room,
BuildingDef => DefKind.Building,
FloorDef => DefKind.Floor,
TerritoryDef => DefKind.Territory,
_ => throw new ArgumentOutOfRangeException(nameof(def)),
};
}