Remove obsolete project files for MrGameEng.AI, MrGameEng.Assets, MrGameEng.Atlases, and MrGameEng.Collisions modules. Introduce new AssetManager and related classes for asset loading and management, including support for texture atlases and mod definitions. Enhance mod loading capabilities with DefDatabase and LanguageManager for JSON-based definitions and localization. Implement a shelf packing algorithm for efficient texture atlas creation.

This commit is contained in:
Leonid Pershin
2026-06-12 07:47:04 +03:00
parent 1f87fb0b74
commit c30e2ce764
70 changed files with 0 additions and 233 deletions
@@ -0,0 +1,146 @@
using Xunit;
namespace MrGameEng.Mods.Tests;
public sealed class DefDatabaseTests : IDisposable
{
private sealed class AnimalDef : Def
{
public float Speed { get; init; }
public int Legs { get; init; } = 4;
public List<string> Tags { get; init; } = [];
}
private readonly string _root = Directory.CreateTempSubdirectory("mrge-defs-tests-").FullName;
private int _modCounter;
public void Dispose() => Directory.Delete(_root, recursive: true);
/// <summary>Создаёт мод с одним файлом дефов и возвращает его (порядок загрузки = порядок создания).</summary>
private Mod WriteDefsMod(string defsJson)
{
var id = $"mod{_modCounter++:D2}";
var modDir = Path.Combine(_root, id);
Directory.CreateDirectory(Path.Combine(modDir, "Defs"));
File.WriteAllText(Path.Combine(modDir, "Defs", "animals.json"), defsJson);
return new Mod(new ModInfo { Id = id }, modDir);
}
private DefDatabase LoadAnimals(params Mod[] mods)
{
var database = new DefDatabase();
database.RegisterType<AnimalDef>("Animal");
database.Load(mods);
return database;
}
[Fact]
public void Load_ParsesDefs_AndAllIsSortedByName()
{
var mod = WriteDefsMod(
"""
{ "type": "Animal", "defs": [
{ "defName": "Wolf", "label": "волк", "speed": 9 },
{ "defName": "Bear", "speed": 6 }
]}
"""
);
var database = LoadAnimals(mod);
Assert.Equal(["Bear", "Wolf"], database.All<AnimalDef>().Select(d => d.DefName));
Assert.Equal(9f, database.Get<AnimalDef>("Wolf").Speed);
Assert.Equal("волк", database.Get<AnimalDef>("Wolf").Label);
Assert.Equal(4, database.Get<AnimalDef>("Bear").Legs); // значение по умолчанию
}
[Fact]
public void Load_ParentChain_MergesFields_ChildWins()
{
var mod = WriteDefsMod(
"""
{ "type": "Animal", "defs": [
{ "defName": "BaseAnimal", "abstract": true, "speed": 5, "tags": ["wild"] },
{ "defName": "Hare", "parent": "BaseAnimal", "speed": 12 },
{ "defName": "Snail", "parent": "BaseAnimal", "legs": 0, "tags": ["slow", "slimy"] }
]}
"""
);
var database = LoadAnimals(mod);
var hare = database.Get<AnimalDef>("Hare");
Assert.Equal(12f, hare.Speed); // своё поле победило
Assert.Equal(["wild"], hare.Tags); // унаследовано
var snail = database.Get<AnimalDef>("Snail");
Assert.Equal(5f, snail.Speed); // унаследовано
Assert.Equal(0, snail.Legs);
Assert.Equal(["slow", "slimy"], snail.Tags); // массив заменён целиком
Assert.False(database.TryGet<AnimalDef>("BaseAnimal", out _)); // абстрактный не эмитится
}
[Fact]
public void Load_LaterMod_ReplacesSameDefName()
{
var core = WriteDefsMod(
"""{ "type": "Animal", "defs": [ { "defName": "Wolf", "speed": 9, "tags": ["wild"] } ] }"""
);
var patch = WriteDefsMod(
"""{ "type": "Animal", "defs": [ { "defName": "Wolf", "speed": 20 } ] }"""
);
var database = LoadAnimals(core, patch);
var wolf = database.Get<AnimalDef>("Wolf");
Assert.Equal(20f, wolf.Speed);
Assert.Empty(wolf.Tags); // полная замена, не слияние с дефом раннего мода
}
[Fact]
public void Load_CyclicParents_Throws()
{
var mod = WriteDefsMod(
"""
{ "type": "Animal", "defs": [
{ "defName": "A", "parent": "B" },
{ "defName": "B", "parent": "A" }
]}
"""
);
var exception = Assert.Throws<InvalidDataException>(() => LoadAnimals(mod));
Assert.Contains("Cyclic", exception.Message);
}
[Fact]
public void Load_UnknownParent_Throws()
{
var mod = WriteDefsMod(
"""{ "type": "Animal", "defs": [ { "defName": "X", "parent": "Ghost" } ] }"""
);
var exception = Assert.Throws<InvalidDataException>(() => LoadAnimals(mod));
Assert.Contains("Ghost", exception.Message);
}
[Fact]
public void Load_UnknownDefType_ThrowsWithFileAndMod()
{
var mod = WriteDefsMod("""{ "type": "Vehicle", "defs": [] }""");
var exception = Assert.Throws<InvalidDataException>(() => LoadAnimals(mod));
Assert.Contains("Vehicle", exception.Message);
Assert.Contains(mod.Id, exception.Message);
}
[Fact]
public void Get_UnknownDef_Throws_TryGetReturnsFalse()
{
var database = LoadAnimals(WriteDefsMod("""{ "type": "Animal", "defs": [] }"""));
Assert.Throws<KeyNotFoundException>(() => database.Get<AnimalDef>("Dodo"));
Assert.False(database.TryGet<AnimalDef>("Dodo", out _));
}
}