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,115 @@
using Xunit;
namespace MrGameEng.Mods.Tests;
public sealed class ModLoaderTests : IDisposable
{
private readonly string _root = Directory.CreateTempSubdirectory("mrge-mods-tests-").FullName;
private string ModsRoot => Path.Combine(_root, "Mods");
public void Dispose() => Directory.Delete(_root, recursive: true);
/// <summary>Создаёт мод с About.json; зависимости — id модов, которые должны грузиться раньше.</summary>
private void WriteMod(string id, params string[] dependencies)
{
var aboutDir = Path.Combine(ModsRoot, id, "About");
Directory.CreateDirectory(aboutDir);
var deps = string.Join(", ", dependencies.Select(d => $"\"{d}\""));
File.WriteAllText(
Path.Combine(aboutDir, "About.json"),
$$"""{ "id": "{{id}}", "name": "{{id}} mod", "version": "1.0", "dependencies": [{{deps}}] }"""
);
}
[Fact]
public void Load_OrdersDependenciesFirst_ThenAlphabetically()
{
WriteMod("zebra", "Core");
WriteMod("apple", "Core");
WriteMod("Core");
var mods = ModLoader.Load(ModsRoot);
Assert.Equal(["Core", "apple", "zebra"], mods.Select(m => m.Id));
}
[Fact]
public void Load_MissingDependency_Throws()
{
WriteMod("orphan", "NoSuchMod");
var exception = Assert.Throws<InvalidDataException>(() => ModLoader.Load(ModsRoot));
Assert.Contains("NoSuchMod", exception.Message);
}
[Fact]
public void Load_CyclicDependency_Throws()
{
WriteMod("a", "b");
WriteMod("b", "a");
var exception = Assert.Throws<InvalidDataException>(() => ModLoader.Load(ModsRoot));
Assert.Contains("Cyclic", exception.Message);
}
[Fact]
public void Load_ActiveSubset_LoadsOnlyRequested()
{
WriteMod("Core");
WriteMod("extra", "Core");
WriteMod("unused");
var mods = ModLoader.Load(ModsRoot, ["extra", "Core"]);
Assert.Equal(["Core", "extra"], mods.Select(m => m.Id));
}
[Fact]
public void Load_IgnoresDirectoriesWithoutAbout()
{
WriteMod("Core");
Directory.CreateDirectory(Path.Combine(ModsRoot, "not-a-mod"));
var mod = Assert.Single(ModLoader.Load(ModsRoot));
Assert.Equal("Core", mod.Id);
Assert.Equal("Core mod", mod.Info.Name);
}
[Fact]
public void FindModsRoot_WalksUpFromNestedDirectory()
{
WriteMod("Core");
var nested = Path.Combine(_root, "bin", "Debug", "net8.0");
Directory.CreateDirectory(nested);
Assert.Equal(ModsRoot, ModLoader.FindModsRoot(nested));
}
[Fact]
public void ContentTree_LaterMod_OverridesSameRelativePath()
{
WriteMod("Core");
WriteMod("patch", "Core");
File.WriteAllText(CreateContentFile("Core", "Textures", "things/rock.png"), "core");
File.WriteAllText(CreateContentFile("Core", "Textures", "things/tree.png"), "core");
File.WriteAllText(CreateContentFile("patch", "Textures", "things/rock.png"), "patched");
var mods = ModLoader.Load(ModsRoot);
var tree = ModContentTree.Build(mods, "Textures", ".png");
Assert.Equal(2, tree.Files.Count);
Assert.True(tree.TryGet("things/rock.png", out var rock));
Assert.Equal("patch", rock.Mod.Id);
Assert.Equal("patched", File.ReadAllText(rock.FullPath));
Assert.True(tree.TryGet("things/tree.png", out var oak));
Assert.Equal("Core", oak.Mod.Id);
}
private string CreateContentFile(string modId, string folder, string relativePath)
{
var fullPath = Path.Combine(ModsRoot, modId, folder, relativePath);
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
return fullPath;
}
}