Map zones follow load order and mod reloads; mod unit tests

This commit is contained in:
cthangover committed 2026-10-21 12:31:57 +02:00
1 parent fa734b7edc
commit fda254a698
5 files changed
+92 -47

No files matched your search

+37 -37
View File
@@ -1,37 +1,37 @@
# Godot 4.x # Godot 4.x
.godot/ .godot/
.idea/ .idea/
sdk/ sdk/
bin/ bin/
obj/ obj/
#ai #ai
.omo/ .omo/
.opencode/ .opencode/
gen/ gen/
obj/ obj/
bin/ bin/
*.tres~ *.tres~
*.tscn~ *.tscn~
# GDScript LSP # GDScript LSP
.godot/ .godot/
# Export # Export
*.exe *.exe
*.dmg *.dmg
*.apk *.apk
*.aab *.aab
*.pck *.pck
*.zip *.zip
*.log *.log
# OS # OS
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Editor # Editor
*.import *.import
*.godot.uid *.godot.uid
+1
View File
@@ -8,6 +8,7 @@
"sources": [ "sources": [
"src/**/*.cs" "src/**/*.cs"
], ],
"test_sources": ["tests/*.cs"],
"depends": ["ui_base>=1"], "depends": ["ui_base>=1"],
"ui": [ "ui": [
{ {
+2
View File
@@ -22,6 +22,8 @@ namespace Cthangover.Mods.Map
public void OnModResourcesReady() public void OnModResourcesReady()
{ {
var service = MapZoneService.Instance; var service = MapZoneService.Instance;
// Runs again after every mod reload; the service outlives it when map_base stays loaded.
service.MarkStale();
MapStateBridge.Register(service.ExportState, service.ImportState, service.ResetOverrides); MapStateBridge.Register(service.ExportState, service.ImportState, service.ResetOverrides);
} }
} }
+16 -10
View File
@@ -67,8 +67,10 @@ namespace Cthangover.Mods.Map
/// <summary> /// <summary>
/// Loads the zone list from the given data, replacing the previous list /// Loads the zone list from the given data, replacing the previous list
/// but preserving runtime overrides. This is the testable entry point; /// but preserving runtime overrides. Zones come in mod load order: a
/// runtime code uses <see cref="ReloadFromMods"/>. /// zone with an id seen before replaces it in place, so a dependent mod
/// overrides a zone the way it overrides any other resource. This is the
/// testable entry point; runtime code uses <see cref="ReloadFromMods"/>.
/// </summary> /// </summary>
public void LoadZones(IEnumerable<MapZone> newZones) public void LoadZones(IEnumerable<MapZone> newZones)
{ {
@@ -82,13 +84,13 @@ namespace Cthangover.Mods.Map
if (zone == null || string.IsNullOrEmpty(zone.ID)) if (zone == null || string.IsNullOrEmpty(zone.ID))
continue; continue;
if (byId.ContainsKey(zone.ID)) if (byId.TryGetValue(zone.ID, out var replaced))
{ {
GameLogger.Log("MAP", $"duplicate zone id '{zone.ID}' ignored", LogLevel.Warning); GameLogger.Log("MAP", $"zone '{zone.ID}' replaced by a later definition", LogLevel.Warning);
continue; zones[zones.IndexOf(replaced)] = zone;
} }
else
zones.Add(zone); zones.Add(zone);
byId[zone.ID] = zone; byId[zone.ID] = zone;
} }
} }
@@ -114,10 +116,11 @@ namespace Cthangover.Mods.Map
var result = new List<MapZone>(); var result = new List<MapZone>();
var options = JsonOptionsProvider.ReadOptions; var options = JsonOptionsProvider.ReadOptions;
foreach (var kvp in ModManager.Instance.Mods) foreach (var modId in ModRegistry.Instance.GetOrderedModIds())
{ {
var modId = kvp.Key; var provider = ModManager.Instance.GetMod(modId)?.FileProvider;
var provider = kvp.Value.FileProvider; if (provider == null)
continue;
foreach (var entry in provider.ListFiles("map")) foreach (var entry in provider.ListFiles("map"))
{ {
@@ -157,6 +160,9 @@ namespace Cthangover.Mods.Map
/// Loads zones on first use and re-reads nothing afterwards; call /// Loads zones on first use and re-reads nothing afterwards; call
/// <see cref="Reload"/> when the mod set or the map files changed. /// <see cref="Reload"/> when the mod set or the map files changed.
/// </summary> /// </summary>
/// <summary>Drops the loaded zones so the next <see cref="EnsureLoaded"/> reads the current mod set (called after every mod set load).</summary>
public void MarkStale() => loaded = false;
public void EnsureLoaded() public void EnsureLoaded()
{ {
if (!loaded) if (!loaded)
+36
View File
@@ -0,0 +1,36 @@
namespace Cthangover.Mods.Map.Tests
{
using System.Collections.Generic;
using Cthangover.Core.Mods.Testing;
/// <summary>
/// Zone lists arrive in mod load order, and a mod that ships a zone with an
/// id already defined replaces it — the same rule as every other mod
/// resource, so a dependent mod can reshape a zone of the mod it builds on.
/// </summary>
public class MapZoneServiceTests
{
[ModTest("zones/later_definition_replaces_earlier")]
static void LaterDefinitionReplacesEarlier(ModTestContext ctx)
{
var service = MapZoneService.Instance;
try
{
service.LoadZones(new List<MapZone>
{
new() { ID = "test_zone", Title = "owner" },
new() { ID = "test_other", Title = "other" },
new() { ID = "TEST_ZONE", Title = "dependent" },
});
ModAssert.Equal(2, service.Zones.Count, "zone count");
ModAssert.Equal("dependent", service.Get("test_zone").Title, "the later definition wins");
ModAssert.Equal("dependent", service.Zones[0].Title, "it keeps the replaced zone's place");
}
finally
{
service.ReloadFromMods();
}
}
}
}