Merge phase 30 apparel defs.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,28 +10,28 @@
|
|||||||
|
|
||||||
## Задачи
|
## Задачи
|
||||||
|
|
||||||
- [ ] Поля `ThingDef`: `layers`, `portable`, `mass`, `insulation`, `formality`, `age`, `sex`,
|
- [x] Поля `ThingDef`: `layers`, `portable`, `mass`, `insulation`, `formality`, `age`, `sex`,
|
||||||
`colors`, `skirtLength`, `pe`. Пустые `layers` — прежняя мебель, карта не ломается
|
`colors`, `skirtLength`, `pe`. Пустые `layers` — прежняя мебель, карта не ломается
|
||||||
- [ ] Слои — известный набор в коде (`Underwear` … `Accessory`), как роли. Неизвестный слой —
|
- [x] Слои — известный набор в коде (`Underwear` … `Accessory`), как роли. Неизвестный слой —
|
||||||
ошибка загрузки
|
ошибка загрузки
|
||||||
- [ ] `ColorDef`: id и теги (`neutral`, `bright`, `school`). Ссылка вещи на неизвестный цвет —
|
- [x] `ColorDef`: id и теги (`neutral`, `bright`, `school`). Ссылка вещи на неизвестный цвет —
|
||||||
ошибка
|
ошибка
|
||||||
- [ ] Вещь с несколькими `layers` допустима (платье). Пересечение слоёв у двух надетых сразу
|
- [x] Вещь с несколькими `layers` допустима (платье). Пересечение слоёв у двух надетых сразу
|
||||||
валидатор не проверяет — это состояние человека, не каталога
|
валидатор не проверяет — это состояние человека, не каталога
|
||||||
- [ ] Ваниль по [`../design/inventory.md`](../design/inventory.md): бельё, носки, низы, верх,
|
- [x] Ваниль по [`../design/inventory.md`](../design/inventory.md): бельё, носки, низы, верх,
|
||||||
свитер, куртка/пальто/шуба, обувь, шапка, аксессуары, платье, форма физкультуры,
|
свитер, куртка/пальто/шуба, обувь, шапка, аксессуары, платье, форма физкультуры,
|
||||||
портфель, учебник, телефон, бутылка
|
портфель, учебник, телефон, бутылка
|
||||||
- [ ] Палитра цветов и локали ru/en на все новые def
|
- [x] Палитра цветов и локали ru/en на все новые def
|
||||||
- [ ] Учебник — один `ThingDef` без зашитого предмета; предмет живёт на экземпляре в фазе 31
|
- [x] Учебник — один `ThingDef` без зашитого предмета; предмет живёт на экземпляре в фазе 31
|
||||||
- [ ] Мебель на карте по-прежнему без `layers`; редактор не предлагает надеть стул
|
- [x] Мебель на карте по-прежнему без `layers`; редактор не предлагает надеть стул
|
||||||
|
|
||||||
## Тесты, без которых фаза не закрыта
|
## Тесты, без которых фаза не закрыта
|
||||||
|
|
||||||
- [ ] `Chair` без новых полей грузится как раньше
|
- [x] `Chair` без новых полей грузится как раньше
|
||||||
- [ ] Платье с `layers: ["Bottom", "Top"]` проходит; неизвестный слой — ошибка
|
- [x] Платье с `layers: ["Bottom", "Top"]` проходит; неизвестный слой — ошибка
|
||||||
- [ ] Футболка ссылается на `Red`; отсутствие `Red` в палитре — ошибка
|
- [x] Футболка ссылается на `Red`; отсутствие `Red` в палитре — ошибка
|
||||||
- [ ] Тест полноты локалей зелёный на новый набор
|
- [x] Тест полноты локалей зелёный на новый набор
|
||||||
- [ ] Ванильная карта и каталог предметов (мебель) не изменились по вместимости
|
- [x] Ванильная карта и каталог предметов (мебель) не изменились по вместимости
|
||||||
|
|
||||||
## Критерий готовности
|
## Критерий готовности
|
||||||
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@
|
|||||||
| Фаза | Статус | Зачем |
|
| Фаза | Статус | Зачем |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| [29. Страна](29-country.md) | 🔄 | `CountryDef`, create выбирает страну, климат-пресет от сида |
|
| [29. Страна](29-country.md) | 🔄 | `CountryDef`, create выбирает страну, климат-пресет от сида |
|
||||||
| [30. Слои и палитра](30-apparel-defs.md) | 🔄 | Поля `ThingDef`, `ColorDef`, ванильная одежда и ноша |
|
| [30. Слои и палитра](30-apparel-defs.md) | ✅ | Поля `ThingDef`, `ColorDef`, ванильная одежда и ноша |
|
||||||
| [31. Генератор одевает](31-dress-generator.md) | ⬜ | Гардероб на человеке, ноша-число, `people.json` |
|
| [31. Генератор одевает](31-dress-generator.md) | ⬜ | Гардероб на человеке, ноша-число, `people.json` |
|
||||||
|
|
||||||
29 и 30 независимы; 31 стоит на обеих.
|
29 и 30 независимы; 31 стоит на обеих.
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
namespace HSchool.Content;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Layers apparel occupies. Code, not a def — new layers are a rebuild, like
|
||||||
|
/// <see cref="PersonRoles"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class ApparelLayers
|
||||||
|
{
|
||||||
|
public const string Underwear = "Underwear";
|
||||||
|
public const string Socks = "Socks";
|
||||||
|
public const string Bottom = "Bottom";
|
||||||
|
public const string Top = "Top";
|
||||||
|
public const string OverTop = "OverTop";
|
||||||
|
public const string Outer = "Outer";
|
||||||
|
public const string Shoes = "Shoes";
|
||||||
|
public const string Head = "Head";
|
||||||
|
public const string Accessory = "Accessory";
|
||||||
|
|
||||||
|
public static readonly IReadOnlyList<string> All =
|
||||||
|
[Underwear, Socks, Bottom, Top, OverTop, Outer, Shoes, Head, Accessory];
|
||||||
|
|
||||||
|
public static bool IsKnown(string layer) =>
|
||||||
|
All.Any(candidate => candidate.Equals(layer, StringComparison.Ordinal));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ColorDef appropriateness tags. School rules filter these, not color names.
|
||||||
|
/// </summary>
|
||||||
|
public static class ColorTags
|
||||||
|
{
|
||||||
|
public const string Neutral = "neutral";
|
||||||
|
public const string Bright = "bright";
|
||||||
|
public const string School = "school";
|
||||||
|
|
||||||
|
public static readonly IReadOnlyList<string> All = [Neutral, Bright, School];
|
||||||
|
|
||||||
|
public static bool IsKnown(string tag) =>
|
||||||
|
All.Any(candidate => candidate.Equals(tag, StringComparison.Ordinal));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Bottom length for the short-form dress code. Omit on things that have no hem.</summary>
|
||||||
|
public static class SkirtLengths
|
||||||
|
{
|
||||||
|
public const string Regular = "regular";
|
||||||
|
public const string Short = "short";
|
||||||
|
|
||||||
|
public static readonly IReadOnlyList<string> All = [Regular, Short];
|
||||||
|
|
||||||
|
public static bool IsKnown(string length) =>
|
||||||
|
All.Any(candidate => candidate.Equals(length, StringComparison.Ordinal));
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ColorDef : Def
|
||||||
|
{
|
||||||
|
public IReadOnlyList<string> Tags { get; init; } = [];
|
||||||
|
}
|
||||||
@@ -313,6 +313,7 @@ public sealed class CatalogLoader
|
|||||||
var dayFrames = new Dictionary<string, DayFrameDef>(StringComparer.Ordinal);
|
var dayFrames = new Dictionary<string, DayFrameDef>(StringComparer.Ordinal);
|
||||||
var holidays = new Dictionary<string, HolidayDef>(StringComparer.Ordinal);
|
var holidays = new Dictionary<string, HolidayDef>(StringComparer.Ordinal);
|
||||||
var behavior = new Dictionary<string, BehaviorDef>(StringComparer.Ordinal);
|
var behavior = new Dictionary<string, BehaviorDef>(StringComparer.Ordinal);
|
||||||
|
var colors = new Dictionary<string, ColorDef>(StringComparer.Ordinal);
|
||||||
|
|
||||||
foreach (var (key, json) in resolved)
|
foreach (var (key, json) in resolved)
|
||||||
{
|
{
|
||||||
@@ -372,6 +373,9 @@ public sealed class CatalogLoader
|
|||||||
case DefKind.Behavior:
|
case DefKind.Behavior:
|
||||||
behavior[key.Name] = Jsonc.Deserialize<BehaviorDef>(json);
|
behavior[key.Name] = Jsonc.Deserialize<BehaviorDef>(json);
|
||||||
break;
|
break;
|
||||||
|
case DefKind.Color:
|
||||||
|
colors[key.Name] = Jsonc.Deserialize<ColorDef>(json);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,6 +399,7 @@ public sealed class CatalogLoader
|
|||||||
dayFrames,
|
dayFrames,
|
||||||
holidays,
|
holidays,
|
||||||
behavior,
|
behavior,
|
||||||
|
colors,
|
||||||
ru,
|
ru,
|
||||||
en);
|
en);
|
||||||
}
|
}
|
||||||
@@ -415,6 +420,13 @@ public sealed class CatalogLoader
|
|||||||
throw new ContentLoadException($"ThingDef '{thing.DefName}' references unknown ActionDef '{action}'.");
|
throw new ContentLoadException($"ThingDef '{thing.DefName}' references unknown ActionDef '{action}'.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ValidateApparelFields(thing, catalog);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var color in catalog.Colors.Values)
|
||||||
|
{
|
||||||
|
ValidateColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var room in catalog.Rooms.Values)
|
foreach (var room in catalog.Rooms.Values)
|
||||||
@@ -426,6 +438,13 @@ public sealed class CatalogLoader
|
|||||||
throw new ContentLoadException($"RoomDef '{room.DefName}' slot '{slot.Key}' references unknown ThingDef '{slot.Thing}'.");
|
throw new ContentLoadException($"RoomDef '{room.DefName}' slot '{slot.Key}' references unknown ThingDef '{slot.Thing}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var slotted = catalog.Things[slot.Thing];
|
||||||
|
if (slotted.Layers.Count > 0 || slotted.Portable)
|
||||||
|
{
|
||||||
|
throw new ContentLoadException(
|
||||||
|
$"RoomDef '{room.DefName}' slot '{slot.Key}' cannot place '{slot.Thing}' on the map.");
|
||||||
|
}
|
||||||
|
|
||||||
if (slot.Count < 0 || slot.Count > byte.MaxValue)
|
if (slot.Count < 0 || slot.Count > byte.MaxValue)
|
||||||
{
|
{
|
||||||
throw new ContentLoadException($"RoomDef '{room.DefName}' slot '{slot.Key}' count must be 0–{byte.MaxValue}.");
|
throw new ContentLoadException($"RoomDef '{room.DefName}' slot '{slot.Key}' count must be 0–{byte.MaxValue}.");
|
||||||
@@ -465,6 +484,11 @@ public sealed class CatalogLoader
|
|||||||
throw new ContentLoadException($"RoomDef '{room.DefName}' seatThing '{room.SeatThing}' must have pupilSlots.");
|
throw new ContentLoadException($"RoomDef '{room.DefName}' seatThing '{room.SeatThing}' must have pupilSlots.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (seat.Layers.Count > 0 || seat.Portable)
|
||||||
|
{
|
||||||
|
throw new ContentLoadException($"RoomDef '{room.DefName}' seatThing '{room.SeatThing}' cannot be apparel or carried.");
|
||||||
|
}
|
||||||
|
|
||||||
if (room.DefaultSeats < 1 || room.DefaultSeats > byte.MaxValue)
|
if (room.DefaultSeats < 1 || room.DefaultSeats > byte.MaxValue)
|
||||||
{
|
{
|
||||||
throw new ContentLoadException($"RoomDef '{room.DefName}' defaultSeats must be 1–{byte.MaxValue}.");
|
throw new ContentLoadException($"RoomDef '{room.DefName}' defaultSeats must be 1–{byte.MaxValue}.");
|
||||||
@@ -522,10 +546,91 @@ public sealed class CatalogLoader
|
|||||||
.Concat(Enumerate(catalog.Staffing.Values))
|
.Concat(Enumerate(catalog.Staffing.Values))
|
||||||
.Concat(Enumerate(catalog.DayFrames.Values))
|
.Concat(Enumerate(catalog.DayFrames.Values))
|
||||||
.Concat(Enumerate(catalog.Holidays.Values))
|
.Concat(Enumerate(catalog.Holidays.Values))
|
||||||
.Concat(Enumerate(catalog.Behavior.Values));
|
.Concat(Enumerate(catalog.Behavior.Values))
|
||||||
|
.Concat(Enumerate(catalog.Colors.Values));
|
||||||
|
|
||||||
static IEnumerable<Def> Enumerate(IEnumerable<Def> defs) => defs.Where(def => !def.Abstract);
|
static IEnumerable<Def> Enumerate(IEnumerable<Def> defs) => defs.Where(def => !def.Abstract);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ValidateColor(ColorDef color)
|
||||||
|
{
|
||||||
|
var seen = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
foreach (var tag in color.Tags)
|
||||||
|
{
|
||||||
|
if (!ColorTags.IsKnown(tag))
|
||||||
|
{
|
||||||
|
throw new ContentLoadException($"ColorDef '{color.DefName}' has unknown tag '{tag}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!seen.Add(tag))
|
||||||
|
{
|
||||||
|
throw new ContentLoadException($"ColorDef '{color.DefName}' repeats tag '{tag}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateApparelFields(ThingDef thing, DefCatalog catalog)
|
||||||
|
{
|
||||||
|
if (thing.Mass < 0)
|
||||||
|
{
|
||||||
|
throw new ContentLoadException($"ThingDef '{thing.DefName}' mass cannot be negative.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thing.Insulation < 0)
|
||||||
|
{
|
||||||
|
throw new ContentLoadException($"ThingDef '{thing.DefName}' insulation cannot be negative.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thing.Formality is < 0 or > 100)
|
||||||
|
{
|
||||||
|
throw new ContentLoadException($"ThingDef '{thing.DefName}' formality must be 0–100.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thing.Age is { } age && age.Min > age.Max)
|
||||||
|
{
|
||||||
|
throw new ContentLoadException($"ThingDef '{thing.DefName}' age min is above max.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thing.Sex is not null
|
||||||
|
&& !thing.Sex.Equals("male", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& !thing.Sex.Equals("female", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new ContentLoadException($"ThingDef '{thing.DefName}' has unknown sex '{thing.Sex}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thing.SkirtLength is not null && !SkirtLengths.IsKnown(thing.SkirtLength))
|
||||||
|
{
|
||||||
|
throw new ContentLoadException($"ThingDef '{thing.DefName}' has unknown skirtLength '{thing.SkirtLength}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var layers = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
foreach (var layer in thing.Layers)
|
||||||
|
{
|
||||||
|
if (!ApparelLayers.IsKnown(layer))
|
||||||
|
{
|
||||||
|
throw new ContentLoadException($"ThingDef '{thing.DefName}' has unknown layer '{layer}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!layers.Add(layer))
|
||||||
|
{
|
||||||
|
throw new ContentLoadException($"ThingDef '{thing.DefName}' repeats layer '{layer}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var colors = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
foreach (var color in thing.Colors)
|
||||||
|
{
|
||||||
|
if (!colors.Add(color))
|
||||||
|
{
|
||||||
|
throw new ContentLoadException($"ThingDef '{thing.DefName}' repeats color '{color}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!catalog.Colors.TryGetValue(color, out var def) || def.Abstract)
|
||||||
|
{
|
||||||
|
throw new ContentLoadException($"ThingDef '{thing.DefName}' references unknown ColorDef '{color}'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed record RawDef(string PackId, DefKind Kind, string DefName, JsonObject Json, string Source);
|
private sealed record RawDef(string PackId, DefKind Kind, string DefName, JsonObject Json, string Source);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ public sealed class DefCatalog
|
|||||||
IReadOnlyDictionary<string, DayFrameDef> dayFrames,
|
IReadOnlyDictionary<string, DayFrameDef> dayFrames,
|
||||||
IReadOnlyDictionary<string, HolidayDef> holidays,
|
IReadOnlyDictionary<string, HolidayDef> holidays,
|
||||||
IReadOnlyDictionary<string, BehaviorDef> behavior,
|
IReadOnlyDictionary<string, BehaviorDef> behavior,
|
||||||
|
IReadOnlyDictionary<string, ColorDef> colors,
|
||||||
IReadOnlyDictionary<string, string> ru,
|
IReadOnlyDictionary<string, string> ru,
|
||||||
IReadOnlyDictionary<string, string> en)
|
IReadOnlyDictionary<string, string> en)
|
||||||
{
|
{
|
||||||
@@ -48,6 +49,7 @@ public sealed class DefCatalog
|
|||||||
DayFrames = dayFrames;
|
DayFrames = dayFrames;
|
||||||
Holidays = holidays;
|
Holidays = holidays;
|
||||||
Behavior = behavior;
|
Behavior = behavior;
|
||||||
|
Colors = colors;
|
||||||
_ru = ru;
|
_ru = ru;
|
||||||
_en = en;
|
_en = en;
|
||||||
AnyNeedDecays = needs.Values.Any(need => !need.Abstract && need.DecayPerHour > 0f);
|
AnyNeedDecays = needs.Values.Any(need => !need.Abstract && need.DecayPerHour > 0f);
|
||||||
@@ -101,6 +103,8 @@ public sealed class DefCatalog
|
|||||||
|
|
||||||
public IReadOnlyDictionary<string, BehaviorDef> Behavior { get; }
|
public IReadOnlyDictionary<string, BehaviorDef> Behavior { get; }
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<string, ColorDef> Colors { get; }
|
||||||
|
|
||||||
/// <summary>The one concrete staffing ruleset, or null when a pack has not defined it.</summary>
|
/// <summary>The one concrete staffing ruleset, or null when a pack has not defined it.</summary>
|
||||||
public StaffingDef? StaffingRules => Staffing.Values.FirstOrDefault(def => !def.Abstract);
|
public StaffingDef? StaffingRules => Staffing.Values.FirstOrDefault(def => !def.Abstract);
|
||||||
|
|
||||||
@@ -135,6 +139,7 @@ public sealed class DefCatalog
|
|||||||
DefKind.DayFrame => DayFrames.GetValueOrDefault(defName),
|
DefKind.DayFrame => DayFrames.GetValueOrDefault(defName),
|
||||||
DefKind.Holiday => Holidays.GetValueOrDefault(defName),
|
DefKind.Holiday => Holidays.GetValueOrDefault(defName),
|
||||||
DefKind.Behavior => Behavior.GetValueOrDefault(defName),
|
DefKind.Behavior => Behavior.GetValueOrDefault(defName),
|
||||||
|
DefKind.Color => Colors.GetValueOrDefault(defName),
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -213,6 +218,7 @@ public sealed class DefCatalog
|
|||||||
DayFrameDef => DefKind.DayFrame,
|
DayFrameDef => DefKind.DayFrame,
|
||||||
HolidayDef => DefKind.Holiday,
|
HolidayDef => DefKind.Holiday,
|
||||||
BehaviorDef => DefKind.Behavior,
|
BehaviorDef => DefKind.Behavior,
|
||||||
|
ColorDef => DefKind.Color,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(def)),
|
_ => throw new ArgumentOutOfRangeException(nameof(def)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public enum DefKind
|
|||||||
DayFrame,
|
DayFrame,
|
||||||
Holiday,
|
Holiday,
|
||||||
Behavior,
|
Behavior,
|
||||||
|
Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Shared JSONC fields. Kind comes from the folder under <c>defs/</c>, not from the file.</summary>
|
/// <summary>Shared JSONC fields. Kind comes from the folder under <c>defs/</c>, not from the file.</summary>
|
||||||
@@ -72,6 +73,36 @@ public sealed class ThingDef : Def
|
|||||||
/// must not inflate class size just because someone sits in it.
|
/// must not inflate class size just because someone sits in it.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int PupilSlots { get; init; }
|
public int PupilSlots { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Layers this occupies when worn. Empty — not apparel, so it can sit on the map.</summary>
|
||||||
|
public IReadOnlyList<string> Layers { get; init; } = [];
|
||||||
|
|
||||||
|
/// <summary>Can live in a bag, locker or at home. Furniture stays false.</summary>
|
||||||
|
public bool Portable { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Kilograms. Carried mass sums; worn mass does not in this slice.</summary>
|
||||||
|
public float Mass { get; init; }
|
||||||
|
|
||||||
|
/// <summary>How much it warms — and how much it steams in heat.</summary>
|
||||||
|
public float Insulation { get; init; }
|
||||||
|
|
||||||
|
/// <summary>0–100. Dress-code formality, not a fashion score.</summary>
|
||||||
|
public int Formality { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Allowed age window. Omit for any age.</summary>
|
||||||
|
public IntRange? Age { get; init; }
|
||||||
|
|
||||||
|
/// <summary><c>male</c>, <c>female</c>, or omit for any.</summary>
|
||||||
|
public string? Sex { get; init; }
|
||||||
|
|
||||||
|
/// <summary>ColorDef ids this thing may be. Empty — the instance has no colour.</summary>
|
||||||
|
public IReadOnlyList<string> Colors { get; init; } = [];
|
||||||
|
|
||||||
|
/// <summary><see cref="SkirtLengths"/> id, or omit when there is no hem to measure.</summary>
|
||||||
|
public string? SkirtLength { get; init; }
|
||||||
|
|
||||||
|
/// <summary>PE kit. Worn for that lesson; not a second wardrobe of everyday clothes.</summary>
|
||||||
|
public bool Pe { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class PositionDef : Def;
|
public sealed class PositionDef : Def;
|
||||||
|
|||||||
@@ -161,6 +161,11 @@ public static class MapValidator
|
|||||||
throw new MapValidationException($"Room '{room.Id}' slot '{fill.Key}' uses unknown or abstract ThingDef '{fill.Thing}'.");
|
throw new MapValidationException($"Room '{room.Id}' slot '{fill.Key}' uses unknown or abstract ThingDef '{fill.Thing}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (thing.Layers.Count > 0 || thing.Portable)
|
||||||
|
{
|
||||||
|
throw new MapValidationException($"Room '{room.Id}' slot '{fill.Key}' cannot place '{fill.Thing}' on the map.");
|
||||||
|
}
|
||||||
|
|
||||||
if (fill.Count > byte.MaxValue)
|
if (fill.Count > byte.MaxValue)
|
||||||
{
|
{
|
||||||
throw new MapValidationException($"Room '{room.Id}' slot '{fill.Key}' count {fill.Count} is above {byte.MaxValue}.");
|
throw new MapValidationException($"Room '{room.Id}' slot '{fill.Key}' count {fill.Count} is above {byte.MaxValue}.");
|
||||||
|
|||||||
@@ -123,6 +123,9 @@ internal static class PackPaths
|
|||||||
case "behavior":
|
case "behavior":
|
||||||
kind = DefKind.Behavior;
|
kind = DefKind.Behavior;
|
||||||
return true;
|
return true;
|
||||||
|
case "colors":
|
||||||
|
kind = DefKind.Color;
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
kind = default;
|
kind = default;
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ internal sealed record CatalogResponse(
|
|||||||
|
|
||||||
private static IReadOnlyList<DefInfoResponse> PlaceableThings(DefCatalog catalog, string locale) =>
|
private static IReadOnlyList<DefInfoResponse> PlaceableThings(DefCatalog catalog, string locale) =>
|
||||||
catalog.Things.Values
|
catalog.Things.Values
|
||||||
.Where(def => !def.Abstract)
|
.Where(def => !def.Abstract && def.Layers.Count == 0 && !def.Portable)
|
||||||
.OrderBy(def => def.DefName, StringComparer.Ordinal)
|
.OrderBy(def => def.DefName, StringComparer.Ordinal)
|
||||||
.Select(def => new DefInfoResponse(def.DefName, catalog.Label(locale, def), def.PupilSlots))
|
.Select(def => new DefInfoResponse(def.DefName, catalog.Label(locale, def), def.PupilSlots))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[
|
||||||
|
{ "defName": "White", "tags": ["neutral", "school"] },
|
||||||
|
{ "defName": "Black", "tags": ["neutral", "school"] },
|
||||||
|
{ "defName": "Gray", "tags": ["neutral", "school"] },
|
||||||
|
{ "defName": "Blue", "tags": ["neutral", "school"] },
|
||||||
|
{ "defName": "LightBlue" },
|
||||||
|
{ "defName": "Red", "tags": ["bright"] },
|
||||||
|
{ "defName": "Green", "tags": ["bright"] },
|
||||||
|
{ "defName": "Beige" },
|
||||||
|
]
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"defName": "Underwear",
|
||||||
|
"layers": ["Underwear"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.05,
|
||||||
|
"insulation": 1,
|
||||||
|
"colors": ["White", "Black", "Gray"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Socks",
|
||||||
|
"layers": ["Socks"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.04,
|
||||||
|
"insulation": 1,
|
||||||
|
"colors": ["White", "Black", "Gray"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Skirt",
|
||||||
|
"layers": ["Bottom"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.25,
|
||||||
|
"insulation": 2,
|
||||||
|
"formality": 40,
|
||||||
|
"sex": "female",
|
||||||
|
"skirtLength": "regular",
|
||||||
|
"colors": ["Black", "Gray", "Blue", "Beige"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "ShortSkirt",
|
||||||
|
"layers": ["Bottom"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.2,
|
||||||
|
"insulation": 1,
|
||||||
|
"formality": 30,
|
||||||
|
"sex": "female",
|
||||||
|
"skirtLength": "short",
|
||||||
|
"colors": ["Black", "Gray", "Blue", "Beige"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Pants",
|
||||||
|
"layers": ["Bottom"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.4,
|
||||||
|
"insulation": 3,
|
||||||
|
"formality": 30,
|
||||||
|
"skirtLength": "regular",
|
||||||
|
"colors": ["Black", "Gray", "Blue", "Beige"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Shorts",
|
||||||
|
"layers": ["Bottom"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.25,
|
||||||
|
"insulation": 1,
|
||||||
|
"formality": 20,
|
||||||
|
"skirtLength": "short",
|
||||||
|
"colors": ["Black", "Gray", "Blue", "Beige"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Jeans",
|
||||||
|
"layers": ["Bottom"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.5,
|
||||||
|
"insulation": 3,
|
||||||
|
"formality": 20,
|
||||||
|
"skirtLength": "regular",
|
||||||
|
"colors": ["Blue", "Black", "Gray"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Trousers",
|
||||||
|
"layers": ["Bottom"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.4,
|
||||||
|
"insulation": 3,
|
||||||
|
"formality": 70,
|
||||||
|
"skirtLength": "regular",
|
||||||
|
"colors": ["Black", "Gray", "Blue"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "TShirt",
|
||||||
|
"layers": ["Top"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.15,
|
||||||
|
"insulation": 2,
|
||||||
|
"formality": 20,
|
||||||
|
"colors": ["White", "Black", "Gray", "Blue", "LightBlue", "Red", "Green", "Beige"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Shirt",
|
||||||
|
"layers": ["Top"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.2,
|
||||||
|
"insulation": 2,
|
||||||
|
"formality": 70,
|
||||||
|
"colors": ["White", "Black", "Blue", "Gray"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Sweater",
|
||||||
|
"layers": ["OverTop"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.4,
|
||||||
|
"insulation": 8,
|
||||||
|
"formality": 30,
|
||||||
|
"colors": ["Black", "Gray", "Blue", "Red", "Green", "Beige"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Jacket",
|
||||||
|
"layers": ["Outer"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 1,
|
||||||
|
"insulation": 12,
|
||||||
|
"formality": 40,
|
||||||
|
"colors": ["Black", "Gray", "Blue", "Beige", "Red", "Green"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Coat",
|
||||||
|
"layers": ["Outer"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 1.5,
|
||||||
|
"insulation": 18,
|
||||||
|
"formality": 70,
|
||||||
|
"colors": ["Black", "Gray", "Beige"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "FurCoat",
|
||||||
|
"layers": ["Outer"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 2.5,
|
||||||
|
"insulation": 28,
|
||||||
|
"formality": 50,
|
||||||
|
"colors": ["Black", "Beige", "Gray"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Shoes",
|
||||||
|
"layers": ["Shoes"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.6,
|
||||||
|
"insulation": 2,
|
||||||
|
"formality": 40,
|
||||||
|
"colors": ["Black", "Gray", "Beige", "White"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "WinterBoots",
|
||||||
|
"layers": ["Shoes"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 1,
|
||||||
|
"insulation": 8,
|
||||||
|
"formality": 30,
|
||||||
|
"colors": ["Black", "Gray", "Beige"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Hat",
|
||||||
|
"layers": ["Head"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.15,
|
||||||
|
"insulation": 6,
|
||||||
|
"formality": 20,
|
||||||
|
"colors": ["Black", "Gray", "Beige", "Red", "Blue"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Belt",
|
||||||
|
"layers": ["Accessory"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.1,
|
||||||
|
"formality": 50,
|
||||||
|
"colors": ["Black", "Gray", "Beige"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Scarf",
|
||||||
|
"layers": ["Accessory"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.1,
|
||||||
|
"insulation": 4,
|
||||||
|
"formality": 30,
|
||||||
|
"colors": ["Black", "Gray", "Blue", "Red", "Beige"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Dress",
|
||||||
|
"layers": ["Bottom", "Top"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.4,
|
||||||
|
"insulation": 3,
|
||||||
|
"formality": 50,
|
||||||
|
"sex": "female",
|
||||||
|
"skirtLength": "regular",
|
||||||
|
"colors": ["Black", "Blue", "Red", "Beige", "Gray"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "PeShirt",
|
||||||
|
"layers": ["Top"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.15,
|
||||||
|
"insulation": 2,
|
||||||
|
"formality": 10,
|
||||||
|
"pe": true,
|
||||||
|
"colors": ["Gray", "Blue", "Black", "Green", "Red"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "PeShorts",
|
||||||
|
"layers": ["Bottom"],
|
||||||
|
"portable": true,
|
||||||
|
"mass": 0.2,
|
||||||
|
"insulation": 1,
|
||||||
|
"formality": 10,
|
||||||
|
"pe": true,
|
||||||
|
"skirtLength": "short",
|
||||||
|
"colors": ["Gray", "Blue", "Black", "Green", "Red"],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{ "defName": "SchoolBag", "portable": true, "mass": 0.8 },
|
||||||
|
{ "defName": "Textbook", "portable": true, "mass": 0.4 },
|
||||||
|
{ "defName": "Phone", "portable": true, "mass": 0.15, "colors": ["Black", "White", "Gray"] },
|
||||||
|
{ "defName": "Bottle", "portable": true, "mass": 0.3, "colors": ["Blue", "Green", "White"] },
|
||||||
|
]
|
||||||
@@ -18,6 +18,46 @@
|
|||||||
"Computer": "Computer",
|
"Computer": "Computer",
|
||||||
"MedicalCouch": "Exam couch",
|
"MedicalCouch": "Exam couch",
|
||||||
"Locker": "Locker",
|
"Locker": "Locker",
|
||||||
|
"Underwear": "Underwear",
|
||||||
|
"Socks": "Socks",
|
||||||
|
"Skirt": "Skirt",
|
||||||
|
"ShortSkirt": "Short skirt",
|
||||||
|
"Pants": "Pants",
|
||||||
|
"Shorts": "Shorts",
|
||||||
|
"Jeans": "Jeans",
|
||||||
|
"Trousers": "Trousers",
|
||||||
|
"TShirt": "T-shirt",
|
||||||
|
"Shirt": "Shirt",
|
||||||
|
"Sweater": "Sweater",
|
||||||
|
"Jacket": "Jacket",
|
||||||
|
"Coat": "Coat",
|
||||||
|
"FurCoat": "Fur coat",
|
||||||
|
"Shoes": "Shoes",
|
||||||
|
"WinterBoots": "Winter boots",
|
||||||
|
"Hat": "Hat",
|
||||||
|
"Belt": "Belt",
|
||||||
|
"Scarf": "Scarf",
|
||||||
|
"Dress": "Dress",
|
||||||
|
"PeShirt": "PE shirt",
|
||||||
|
"PeShorts": "PE shorts",
|
||||||
|
"SchoolBag": "School bag",
|
||||||
|
"Textbook": "Textbook",
|
||||||
|
"Phone": "Phone",
|
||||||
|
"Bottle": "Bottle",
|
||||||
|
"White": "White",
|
||||||
|
"Black": "Black",
|
||||||
|
"Gray": "Gray",
|
||||||
|
"Blue": "Blue",
|
||||||
|
"LightBlue": "Light blue",
|
||||||
|
"Red": "Red",
|
||||||
|
"Green": "Green",
|
||||||
|
"Beige": "Beige",
|
||||||
|
"Bottom": "Bottom",
|
||||||
|
"Top": "Top",
|
||||||
|
"OverTop": "Over top",
|
||||||
|
"Outer": "Outerwear",
|
||||||
|
"Head": "Head",
|
||||||
|
"Accessory": "Accessory",
|
||||||
"Principal": "Principal",
|
"Principal": "Principal",
|
||||||
"Secretary": "Secretary",
|
"Secretary": "Secretary",
|
||||||
"Teacher": "Teacher",
|
"Teacher": "Teacher",
|
||||||
|
|||||||
@@ -18,6 +18,46 @@
|
|||||||
"Computer": "Компьютер",
|
"Computer": "Компьютер",
|
||||||
"MedicalCouch": "Кушетка",
|
"MedicalCouch": "Кушетка",
|
||||||
"Locker": "Шкафчик",
|
"Locker": "Шкафчик",
|
||||||
|
"Underwear": "Бельё",
|
||||||
|
"Socks": "Носки",
|
||||||
|
"Skirt": "Юбка",
|
||||||
|
"ShortSkirt": "Короткая юбка",
|
||||||
|
"Pants": "Штаны",
|
||||||
|
"Shorts": "Шорты",
|
||||||
|
"Jeans": "Джинсы",
|
||||||
|
"Trousers": "Брюки",
|
||||||
|
"TShirt": "Футболка",
|
||||||
|
"Shirt": "Рубашка",
|
||||||
|
"Sweater": "Свитер",
|
||||||
|
"Jacket": "Куртка",
|
||||||
|
"Coat": "Пальто",
|
||||||
|
"FurCoat": "Шуба",
|
||||||
|
"Shoes": "Обувь",
|
||||||
|
"WinterBoots": "Зимняя обувь",
|
||||||
|
"Hat": "Шапка",
|
||||||
|
"Belt": "Ремень",
|
||||||
|
"Scarf": "Шарф",
|
||||||
|
"Dress": "Платье",
|
||||||
|
"PeShirt": "Футболка для физкультуры",
|
||||||
|
"PeShorts": "Шорты для физкультуры",
|
||||||
|
"SchoolBag": "Портфель",
|
||||||
|
"Textbook": "Учебник",
|
||||||
|
"Phone": "Телефон",
|
||||||
|
"Bottle": "Бутылка",
|
||||||
|
"White": "Белый",
|
||||||
|
"Black": "Чёрный",
|
||||||
|
"Gray": "Серый",
|
||||||
|
"Blue": "Синий",
|
||||||
|
"LightBlue": "Голубой",
|
||||||
|
"Red": "Красный",
|
||||||
|
"Green": "Зелёный",
|
||||||
|
"Beige": "Бежевый",
|
||||||
|
"Bottom": "Низ",
|
||||||
|
"Top": "Верх",
|
||||||
|
"OverTop": "Поверх",
|
||||||
|
"Outer": "Верхняя одежда",
|
||||||
|
"Head": "Голова",
|
||||||
|
"Accessory": "Аксессуар",
|
||||||
"Principal": "Директор",
|
"Principal": "Директор",
|
||||||
"Secretary": "Секретарь",
|
"Secretary": "Секретарь",
|
||||||
"Teacher": "Учитель",
|
"Teacher": "Учитель",
|
||||||
|
|||||||
@@ -303,6 +303,9 @@ public class SchoolApiTests(AppHostFixture fixture)
|
|||||||
Assert.Equal("GymHall", Assert.Single(ru.Subjects, subject => subject.DefName == "PhysicalEducation").Room);
|
Assert.Equal("GymHall", Assert.Single(ru.Subjects, subject => subject.DefName == "PhysicalEducation").Room);
|
||||||
Assert.Null(Assert.Single(ru.Subjects, subject => subject.DefName == "Mathematics").Room);
|
Assert.Null(Assert.Single(ru.Subjects, subject => subject.DefName == "Mathematics").Room);
|
||||||
Assert.Contains(ru.Holidays, holiday => holiday.DefName == "SpringBreak");
|
Assert.Contains(ru.Holidays, holiday => holiday.DefName == "SpringBreak");
|
||||||
|
Assert.Contains(ru.Things, thing => thing.DefName == "Chair");
|
||||||
|
Assert.DoesNotContain(ru.Things, thing => thing.DefName == "TShirt");
|
||||||
|
Assert.DoesNotContain(ru.Things, thing => thing.DefName == "Textbook");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
namespace HSchool.Content.Tests;
|
||||||
|
|
||||||
|
public class ApparelDefTests
|
||||||
|
{
|
||||||
|
private readonly CatalogLoader _loader = new();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ChairWithoutApparelFields_LoadsAsFurniture()
|
||||||
|
{
|
||||||
|
var catalog = _loader.Load(
|
||||||
|
[CatalogLoader.CorePackId],
|
||||||
|
[
|
||||||
|
PackDocuments.Def(
|
||||||
|
CatalogLoader.CorePackId,
|
||||||
|
"actions",
|
||||||
|
"sit",
|
||||||
|
"""{ "defName": "Sit", "abstract": true }"""),
|
||||||
|
PackDocuments.Def(
|
||||||
|
CatalogLoader.CorePackId,
|
||||||
|
"things",
|
||||||
|
"chair",
|
||||||
|
"""{ "defName": "Chair", "actions": ["Sit"] }"""),
|
||||||
|
]);
|
||||||
|
|
||||||
|
var chair = catalog.Things["Chair"];
|
||||||
|
Assert.Equal(["Sit"], chair.Actions);
|
||||||
|
Assert.Empty(chair.Layers);
|
||||||
|
Assert.False(chair.Portable);
|
||||||
|
Assert.Equal(0, chair.Mass);
|
||||||
|
Assert.Equal(0, chair.Insulation);
|
||||||
|
Assert.Equal(0, chair.Formality);
|
||||||
|
Assert.Null(chair.Age);
|
||||||
|
Assert.Null(chair.Sex);
|
||||||
|
Assert.Empty(chair.Colors);
|
||||||
|
Assert.Null(chair.SkirtLength);
|
||||||
|
Assert.False(chair.Pe);
|
||||||
|
Assert.Equal(0, chair.PupilSlots);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DressOccupyingTwoLayers_Loads()
|
||||||
|
{
|
||||||
|
var catalog = _loader.Load(
|
||||||
|
[CatalogLoader.CorePackId],
|
||||||
|
[
|
||||||
|
PackDocuments.Def(
|
||||||
|
CatalogLoader.CorePackId,
|
||||||
|
"things",
|
||||||
|
"dress",
|
||||||
|
"""{ "defName": "Dress", "layers": ["Bottom", "Top"], "portable": true }"""),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Assert.Equal([ApparelLayers.Bottom, ApparelLayers.Top], catalog.Things["Dress"].Layers);
|
||||||
|
Assert.True(catalog.Things["Dress"].Portable);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UnknownLayer_FailsTheCatalog()
|
||||||
|
{
|
||||||
|
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
|
||||||
|
[CatalogLoader.CorePackId],
|
||||||
|
[
|
||||||
|
PackDocuments.Def(
|
||||||
|
CatalogLoader.CorePackId,
|
||||||
|
"things",
|
||||||
|
"cape",
|
||||||
|
"""{ "defName": "Cape", "layers": ["Cloak"] }"""),
|
||||||
|
]));
|
||||||
|
|
||||||
|
Assert.Contains("Cloak", ex.Message, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TwoTopsInTheCatalog_DoNotFailLoad()
|
||||||
|
{
|
||||||
|
var catalog = _loader.Load(
|
||||||
|
[CatalogLoader.CorePackId],
|
||||||
|
[
|
||||||
|
PackDocuments.Def(
|
||||||
|
CatalogLoader.CorePackId,
|
||||||
|
"things",
|
||||||
|
"shirt",
|
||||||
|
"""{ "defName": "Shirt", "layers": ["Top"] }"""),
|
||||||
|
PackDocuments.Def(
|
||||||
|
CatalogLoader.CorePackId,
|
||||||
|
"things",
|
||||||
|
"tshirt",
|
||||||
|
"""{ "defName": "TShirt", "layers": ["Top"] }"""),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Assert.Equal([ApparelLayers.Top], catalog.Things["Shirt"].Layers);
|
||||||
|
Assert.Equal([ApparelLayers.Top], catalog.Things["TShirt"].Layers);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TShirtReferencingRed_LoadsWhenThePaletteHasRed()
|
||||||
|
{
|
||||||
|
var catalog = _loader.Load(
|
||||||
|
[CatalogLoader.CorePackId],
|
||||||
|
[
|
||||||
|
PackDocuments.Def(
|
||||||
|
CatalogLoader.CorePackId,
|
||||||
|
"colors",
|
||||||
|
"red",
|
||||||
|
"""{ "defName": "Red", "tags": ["bright"] }"""),
|
||||||
|
PackDocuments.Def(
|
||||||
|
CatalogLoader.CorePackId,
|
||||||
|
"things",
|
||||||
|
"tshirt",
|
||||||
|
"""{ "defName": "TShirt", "layers": ["Top"], "colors": ["Red"] }"""),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Assert.Equal(["Red"], catalog.Things["TShirt"].Colors);
|
||||||
|
Assert.Equal([ColorTags.Bright], catalog.Colors["Red"].Tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TShirtReferencingMissingRed_FailsTheCatalog()
|
||||||
|
{
|
||||||
|
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
|
||||||
|
[CatalogLoader.CorePackId],
|
||||||
|
[
|
||||||
|
PackDocuments.Def(
|
||||||
|
CatalogLoader.CorePackId,
|
||||||
|
"things",
|
||||||
|
"tshirt",
|
||||||
|
"""{ "defName": "TShirt", "layers": ["Top"], "colors": ["Red"] }"""),
|
||||||
|
]));
|
||||||
|
|
||||||
|
Assert.Contains("Red", ex.Message, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void UnknownColorTag_FailsTheCatalog()
|
||||||
|
{
|
||||||
|
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
|
||||||
|
[CatalogLoader.CorePackId],
|
||||||
|
[
|
||||||
|
PackDocuments.Def(
|
||||||
|
CatalogLoader.CorePackId,
|
||||||
|
"colors",
|
||||||
|
"neon",
|
||||||
|
"""{ "defName": "Neon", "tags": ["glow"] }"""),
|
||||||
|
]));
|
||||||
|
|
||||||
|
Assert.Contains("glow", ex.Message, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RoomSlotWithApparel_FailsTheCatalog()
|
||||||
|
{
|
||||||
|
var ex = Assert.Throws<ContentLoadException>(() => _loader.Load(
|
||||||
|
[CatalogLoader.CorePackId],
|
||||||
|
[
|
||||||
|
PackDocuments.Def(
|
||||||
|
CatalogLoader.CorePackId,
|
||||||
|
"things",
|
||||||
|
"tshirt",
|
||||||
|
"""{ "defName": "TShirt", "layers": ["Top"] }"""),
|
||||||
|
PackDocuments.Def(
|
||||||
|
CatalogLoader.CorePackId,
|
||||||
|
"rooms",
|
||||||
|
"office",
|
||||||
|
"""{ "defName": "Office", "slots": [{ "key": "seat", "thing": "TShirt" }], "travelMinutes": 1 }"""),
|
||||||
|
]));
|
||||||
|
|
||||||
|
Assert.Contains("TShirt", ex.Message, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,18 @@ public class VanillaCoreTests
|
|||||||
Assert.Equal(1, catalog.Things["Computer"].PupilSlots);
|
Assert.Equal(1, catalog.Things["Computer"].PupilSlots);
|
||||||
Assert.Equal(0, catalog.Things["Desk"].PupilSlots);
|
Assert.Equal(0, catalog.Things["Desk"].PupilSlots);
|
||||||
Assert.Equal(0, catalog.Things["Chair"].PupilSlots);
|
Assert.Equal(0, catalog.Things["Chair"].PupilSlots);
|
||||||
|
Assert.Empty(catalog.Things["Chair"].Layers);
|
||||||
|
Assert.False(catalog.Things["Chair"].Portable);
|
||||||
|
Assert.Empty(catalog.Things["StudentDesk"].Layers);
|
||||||
|
Assert.Equal([ApparelLayers.Bottom, ApparelLayers.Top], catalog.Things["Dress"].Layers);
|
||||||
|
Assert.Contains("Red", catalog.Things["TShirt"].Colors);
|
||||||
|
Assert.True(catalog.Things["PeShirt"].Pe);
|
||||||
|
Assert.True(catalog.Things["Textbook"].Portable);
|
||||||
|
Assert.Empty(catalog.Things["Textbook"].Layers);
|
||||||
|
Assert.Equal(1, catalog.Things.Values.Count(thing => thing.DefName == "Textbook"));
|
||||||
|
Assert.Equal([ColorTags.Bright], catalog.Colors["Red"].Tags);
|
||||||
|
Assert.Equal([ColorTags.Neutral, ColorTags.School], catalog.Colors["White"].Tags);
|
||||||
|
Assert.Empty(catalog.Colors["Beige"].Tags);
|
||||||
Assert.True(catalog.Subjects.ContainsKey("PrimarySchool"));
|
Assert.True(catalog.Subjects.ContainsKey("PrimarySchool"));
|
||||||
Assert.Equal(1, catalog.Subjects["PrimarySchool"].Grades.Min);
|
Assert.Equal(1, catalog.Subjects["PrimarySchool"].Grades.Min);
|
||||||
Assert.Equal(4, catalog.Subjects["PrimarySchool"].Grades.Max);
|
Assert.Equal(4, catalog.Subjects["PrimarySchool"].Grades.Max);
|
||||||
@@ -57,6 +69,9 @@ public class VanillaCoreTests
|
|||||||
Assert.DoesNotContain(map.Rooms, room => room.Label is "1A" or "1B" or "2A" or "2B");
|
Assert.DoesNotContain(map.Rooms, room => room.Label is "1A" or "1B" or "2A" or "2B");
|
||||||
Assert.Equal(16, map.Rooms.Single(room => room.Id == "classroom-101").Seats);
|
Assert.Equal(16, map.Rooms.Single(room => room.Id == "classroom-101").Seats);
|
||||||
Assert.Empty(map.Rooms.Single(room => room.Id == "classroom-101").Slots);
|
Assert.Empty(map.Rooms.Single(room => room.Id == "classroom-101").Slots);
|
||||||
|
Assert.Equal(
|
||||||
|
2,
|
||||||
|
catalog.Things.Values.Count(thing => !thing.Abstract && thing.PupilSlots > 0));
|
||||||
Assert.Equal(0.5f, catalog.Rooms["Classroom"].TravelMinutes);
|
Assert.Equal(0.5f, catalog.Rooms["Classroom"].TravelMinutes);
|
||||||
Assert.Equal(1.5f, catalog.Rooms["Corridor"].TravelMinutes);
|
Assert.Equal(1.5f, catalog.Rooms["Corridor"].TravelMinutes);
|
||||||
Assert.Equal(1.5f, catalog.Rooms["Stairwell"].TravelMinutes);
|
Assert.Equal(1.5f, catalog.Rooms["Stairwell"].TravelMinutes);
|
||||||
@@ -119,11 +134,13 @@ public class VanillaCoreTests
|
|||||||
keys.AddRange(Names(catalog.DayFrames.Values));
|
keys.AddRange(Names(catalog.DayFrames.Values));
|
||||||
keys.AddRange(Names(catalog.Holidays.Values));
|
keys.AddRange(Names(catalog.Holidays.Values));
|
||||||
keys.AddRange(Names(catalog.Behavior.Values));
|
keys.AddRange(Names(catalog.Behavior.Values));
|
||||||
|
keys.AddRange(Names(catalog.Colors.Values));
|
||||||
keys.AddRange(catalog.PackIds);
|
keys.AddRange(catalog.PackIds);
|
||||||
|
|
||||||
// Derived in code, so no def carries them.
|
// Derived in code, so no def carries them.
|
||||||
keys.Add(BodyBuilds.Attribute);
|
keys.Add(BodyBuilds.Attribute);
|
||||||
keys.AddRange(BodyBuilds.Values);
|
keys.AddRange(BodyBuilds.Values);
|
||||||
|
keys.AddRange(ApparelLayers.All);
|
||||||
|
|
||||||
foreach (var value in catalog.BodyAttributes.Values.SelectMany(def => def.Options))
|
foreach (var value in catalog.BodyAttributes.Values.SelectMany(def => def.Options))
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user