Add MrGameEng.Mods: mod loading, JSON defs and localization
CI / build-test (push) Failing after 1m8s
CI / build-test (push) Failing after 1m8s
Mods are folders with About/About.json metadata; ModLoader resolves a
deterministic load order (dependencies first, ties alphabetical) and
later mods override earlier ones everywhere.
DefDatabase loads JSON def files ({ "type", "defs": [...] }) into
game-registered Def subclasses, with parent inheritance (own fields on
top of the parent's, nested values replaced whole), abstract parents
and full replacement of same-named defs by later mods.
LanguageManager loads Languages/<code>/*.json flat key-string maps,
switches language at runtime and falls back current -> default -> key.
ModContentTree merges one content folder across mods by relative path;
AtlasBuilder gains an explicit-sources Build overload so a merged
texture tree can be packed incrementally at game start.
The LittleSim game now ships its entire content as the Core mod,
demonstrating the module end to end.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
501d81e19f
commit
5d3c18de40
@@ -0,0 +1,93 @@
|
||||
using Xunit;
|
||||
|
||||
namespace MrGameEng.Mods.Tests;
|
||||
|
||||
public sealed class LanguageManagerTests : IDisposable
|
||||
{
|
||||
private readonly string _root = Directory.CreateTempSubdirectory("mrge-lang-tests-").FullName;
|
||||
private int _modCounter;
|
||||
|
||||
public void Dispose() => Directory.Delete(_root, recursive: true);
|
||||
|
||||
private Mod WriteLanguageMod(string code, string fileName, string json)
|
||||
{
|
||||
var id = $"mod{_modCounter++:D2}";
|
||||
var modDir = Path.Combine(_root, id);
|
||||
var languageDir = Path.Combine(modDir, "Languages", code);
|
||||
Directory.CreateDirectory(languageDir);
|
||||
File.WriteAllText(Path.Combine(languageDir, fileName), json);
|
||||
return new Mod(new ModInfo { Id = id }, modDir);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_UsesCurrentLanguage_FallsBackToDefault_ThenKey()
|
||||
{
|
||||
var mod = WriteLanguageMod("en", "ui.json", """{ "hud.title": "Title", "hud.only-en": "English only" }""");
|
||||
var ruDir = Path.Combine(mod.RootPath, "Languages", "ru");
|
||||
Directory.CreateDirectory(ruDir);
|
||||
File.WriteAllText(Path.Combine(ruDir, "ui.json"), """{ "hud.title": "Заголовок" }""");
|
||||
|
||||
var languages = new LanguageManager(defaultLanguage: "en");
|
||||
languages.Load([mod]);
|
||||
|
||||
Assert.True(languages.SetLanguage("ru"));
|
||||
Assert.Equal("Заголовок", languages.Get("hud.title"));
|
||||
Assert.Equal("English only", languages.Get("hud.only-en")); // fallback на en
|
||||
Assert.Equal("hud.missing", languages.Get("hud.missing")); // ключ виден, не краш
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_LaterMod_OverridesKey()
|
||||
{
|
||||
var core = WriteLanguageMod("en", "ui.json", """{ "hud.title": "Core", "hud.other": "Other" }""");
|
||||
var patch = WriteLanguageMod("en", "ui.json", """{ "hud.title": "Patched" }""");
|
||||
|
||||
var languages = new LanguageManager();
|
||||
languages.Load([core, patch]);
|
||||
|
||||
Assert.Equal("Patched", languages.Get("hud.title"));
|
||||
Assert.Equal("Other", languages.Get("hud.other"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetLanguage_UnknownCode_ReturnsFalse_AndKeepsCurrent()
|
||||
{
|
||||
var languages = new LanguageManager();
|
||||
languages.Load([WriteLanguageMod("en", "ui.json", """{ "k": "v" }""")]);
|
||||
|
||||
Assert.False(languages.SetLanguage("fr"));
|
||||
Assert.Equal("en", languages.CurrentLanguage);
|
||||
Assert.Equal(["en"], languages.AvailableLanguages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Format_SubstitutesArguments()
|
||||
{
|
||||
var languages = new LanguageManager();
|
||||
languages.Load([WriteLanguageMod("en", "ui.json", """{ "hud.pop": "population {0}" }""")]);
|
||||
|
||||
Assert.Equal("population 80", languages.Format("hud.pop", 80));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_NonStringValue_Throws()
|
||||
{
|
||||
var mod = WriteLanguageMod("en", "ui.json", """{ "bad": { "nested": true } }""");
|
||||
|
||||
var languages = new LanguageManager();
|
||||
Assert.Throws<InvalidDataException>(() => languages.Load([mod]));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Revision_Bumps_OnLoadAndLanguageSwitch()
|
||||
{
|
||||
var languages = new LanguageManager();
|
||||
var before = languages.Revision;
|
||||
languages.Load([WriteLanguageMod("ru", "ui.json", """{ "k": "v" }""")]);
|
||||
Assert.NotEqual(before, languages.Revision);
|
||||
|
||||
before = languages.Revision;
|
||||
languages.SetLanguage("ru");
|
||||
Assert.NotEqual(before, languages.Revision);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user