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,65 @@
|
||||
namespace MrGameEng.Mods;
|
||||
|
||||
/// <summary>A file contributed by a mod, addressed by its content-relative path.</summary>
|
||||
/// <param name="RelativePath">Path relative to the content folder, forward slashes.</param>
|
||||
/// <param name="FullPath">Absolute path of the winning file on disk.</param>
|
||||
/// <param name="Mod">The mod that contributed the file.</param>
|
||||
public readonly record struct ModFile(string RelativePath, string FullPath, Mod Mod);
|
||||
|
||||
/// <summary>
|
||||
/// Merged view of one content folder (e.g. <c>Textures</c>) across the active mods in load
|
||||
/// order: a later mod shipping a file under the same relative path overrides the earlier
|
||||
/// one. Paths use forward slashes and match case-insensitively (Windows-friendly content).
|
||||
/// </summary>
|
||||
public sealed class ModContentTree
|
||||
{
|
||||
private readonly Dictionary<string, ModFile> _files;
|
||||
|
||||
private ModContentTree(Dictionary<string, ModFile> files, IReadOnlyList<ModFile> ordered)
|
||||
{
|
||||
_files = files;
|
||||
Files = ordered;
|
||||
}
|
||||
|
||||
/// <summary>Winning files, sorted by relative path — deterministic for identical mod sets.</summary>
|
||||
public IReadOnlyList<ModFile> Files { get; }
|
||||
|
||||
/// <summary>Returns the winning file for <paramref name="relativePath"/>.</summary>
|
||||
public bool TryGet(string relativePath, out ModFile file) =>
|
||||
_files.TryGetValue(Normalize(relativePath), out file);
|
||||
|
||||
/// <summary>
|
||||
/// Builds the merged tree of <paramref name="contentFolder"/> over <paramref name="mods"/>
|
||||
/// (in load order). With <paramref name="extensions"/> only matching files are included
|
||||
/// (e.g. <c>".png"</c>); without them, every file.
|
||||
/// </summary>
|
||||
public static ModContentTree Build(IReadOnlyList<Mod> mods, string contentFolder, params string[] extensions)
|
||||
{
|
||||
var files = new Dictionary<string, ModFile>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
var root = mod.ContentPath(contentFolder);
|
||||
if (!Directory.Exists(root))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var fullPath in Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (extensions.Length > 0 &&
|
||||
!extensions.Contains(Path.GetExtension(fullPath), StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var relative = Normalize(Path.GetRelativePath(root, fullPath));
|
||||
files[relative] = new ModFile(relative, fullPath, mod); // поздний мод побеждает
|
||||
}
|
||||
}
|
||||
|
||||
var ordered = files.Values.OrderBy(f => f.RelativePath, StringComparer.Ordinal).ToList();
|
||||
return new ModContentTree(files, ordered);
|
||||
}
|
||||
|
||||
private static string Normalize(string path) => path.Replace('\\', '/');
|
||||
}
|
||||
Reference in New Issue
Block a user