From c47ebfbb52d70312c127c3c7bd3f5736b1fab164 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Wed, 31 Dec 2025 21:46:05 +0000 Subject: [PATCH] Fix tileset templates to appear in the map editor in their order in YAML. Fixes regression from 649e7e8c280269f0ef4f30486d808eb2cb219b10. When the Templates was changed from a Dictionary to a FrozenDictionary, this caused the items to be reordered. Some code was relying on the previous behaviour where the Dictionary ordering would happen to remain the same as the YAML input. Fix this by creating a TemplatesInDefinitionOrder which can be used to iterate in the same ordering as given in YAML. Code that depends on the ordering can use this, and other code that does lookups can use the FrozenDictionary. --- OpenRA.Mods.Common/MapGenerator/MultiBrush.cs | 2 +- OpenRA.Mods.Common/Terrain/DefaultTerrain.cs | 14 +++++++---- OpenRA.Mods.Common/Terrain/TerrainInfo.cs | 1 + .../Traits/World/TilingPathTool.cs | 2 +- .../Widgets/Logic/Editor/TileSelectorLogic.cs | 4 ++-- .../UtilityCommands/D2kMapImporter.cs | 23 ++++++++----------- 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/OpenRA.Mods.Common/MapGenerator/MultiBrush.cs b/OpenRA.Mods.Common/MapGenerator/MultiBrush.cs index b4be1beb9d..7376f22a3a 100644 --- a/OpenRA.Mods.Common/MapGenerator/MultiBrush.cs +++ b/OpenRA.Mods.Common/MapGenerator/MultiBrush.cs @@ -434,7 +434,7 @@ namespace OpenRA.Mods.Common.MapGenerator /// Load a named MultiBrush collection from a map's tileset. public static ImmutableArray LoadCollection(Map map, string name) { - var templatedTerrainInfo = map.Rules.TerrainInfo as ITemplatedTerrainInfo; + var templatedTerrainInfo = (ITemplatedTerrainInfo)map.Rules.TerrainInfo; return templatedTerrainInfo.MultiBrushCollections[name] .Select(info => new MultiBrush(map, info)) .ToImmutableArray(); diff --git a/OpenRA.Mods.Common/Terrain/DefaultTerrain.cs b/OpenRA.Mods.Common/Terrain/DefaultTerrain.cs index 5142be3eba..92b093e70a 100644 --- a/OpenRA.Mods.Common/Terrain/DefaultTerrain.cs +++ b/OpenRA.Mods.Common/Terrain/DefaultTerrain.cs @@ -87,6 +87,8 @@ namespace OpenRA.Mods.Common.Terrain [FieldLoader.Ignore] public readonly FrozenDictionary Templates; [FieldLoader.Ignore] + public readonly ImmutableArray TemplatesInDefinitionOrder; + [FieldLoader.Ignore] public readonly FrozenDictionary> MultiBrushCollections; [FieldLoader.Ignore] @@ -125,10 +127,11 @@ namespace OpenRA.Mods.Common.Terrain defaultWalkableTerrainIndex = GetTerrainIndex("Clear"); // Templates - Templates = yaml["Templates"].ToDictionary().Values - .Select(y => (TerrainTemplateInfo)new DefaultTerrainTemplateInfo(this, y)) - .ToDictionary(t => t.Id) - .ToFrozenDictionary(); + TemplatesInDefinitionOrder = yaml["Templates"].Nodes + .Select(n => (TerrainTemplateInfo)new DefaultTerrainTemplateInfo(this, n.Value)) + .ToImmutableArray(); + Templates = TemplatesInDefinitionOrder + .ToFrozenDictionary(t => t.Id); MultiBrushCollections = yaml.TryGetValue("MultiBrushCollections", out var collectionDefinitions) @@ -187,10 +190,11 @@ namespace OpenRA.Mods.Common.Terrain float ITerrainInfo.MinHeightColorBrightness => MinHeightColorBrightness; float ITerrainInfo.MaxHeightColorBrightness => MaxHeightColorBrightness; - TerrainTile ITerrainInfo.DefaultTerrainTile => new(Templates.First().Key, 0); + TerrainTile ITerrainInfo.DefaultTerrainTile => new(TemplatesInDefinitionOrder[0].Id, 0); ImmutableArray ITemplatedTerrainInfo.EditorTemplateOrder => EditorTemplateOrder; FrozenDictionary ITemplatedTerrainInfo.Templates => Templates; + ImmutableArray ITemplatedTerrainInfo.TemplatesInDefinitionOrder => TemplatesInDefinitionOrder; FrozenDictionary> ITemplatedTerrainInfo.MultiBrushCollections => MultiBrushCollections; void IDumpSheetsTerrainInfo.DumpSheets(string terrainName, ImmutablePalette palette, ref int sheetCount) diff --git a/OpenRA.Mods.Common/Terrain/TerrainInfo.cs b/OpenRA.Mods.Common/Terrain/TerrainInfo.cs index fb969668d0..3d57a02b03 100644 --- a/OpenRA.Mods.Common/Terrain/TerrainInfo.cs +++ b/OpenRA.Mods.Common/Terrain/TerrainInfo.cs @@ -19,6 +19,7 @@ namespace OpenRA.Mods.Common.Terrain { ImmutableArray EditorTemplateOrder { get; } FrozenDictionary Templates { get; } + ImmutableArray TemplatesInDefinitionOrder { get; } FrozenDictionary> MultiBrushCollections { get; } } diff --git a/OpenRA.Mods.Common/Traits/World/TilingPathTool.cs b/OpenRA.Mods.Common/Traits/World/TilingPathTool.cs index 4c986a6b3e..3f2726fb0b 100644 --- a/OpenRA.Mods.Common/Traits/World/TilingPathTool.cs +++ b/OpenRA.Mods.Common/Traits/World/TilingPathTool.cs @@ -341,7 +341,7 @@ namespace OpenRA.Mods.Common.Traits World = self.World; TraitInfo = info; - var templatedTerrainInfo = World.Map.Rules.TerrainInfo as ITemplatedTerrainInfo; + var templatedTerrainInfo = (ITemplatedTerrainInfo)World.Map.Rules.TerrainInfo; SegmentedBrushes = templatedTerrainInfo.MultiBrushCollections.Keys .Order() diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs index 1a5ab98701..67cf6790b0 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs @@ -43,7 +43,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic } readonly ITemplatedTerrainInfo terrainInfo; - readonly TileSelectorTemplate[] allTemplates; + readonly ImmutableArray allTemplates; [ObjectCreator.UseCtor] public TileSelectorLogic(Widget widget, ModData modData, World world, WorldRenderer worldRenderer) @@ -53,7 +53,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic if (terrainInfo == null) throw new InvalidDataException("TileSelectorLogic requires a template-based tileset."); - allTemplates = terrainInfo.Templates.Values.Select(t => new TileSelectorTemplate(t)).ToArray(); + allTemplates = terrainInfo.TemplatesInDefinitionOrder.Select(t => new TileSelectorTemplate(t)).ToImmutableArray(); allCategories = allTemplates.SelectMany(t => t.Categories) .Distinct() diff --git a/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs index a285249f53..189b2d37a9 100644 --- a/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs +++ b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs @@ -276,7 +276,7 @@ namespace OpenRA.Mods.D2k.UtilityCommands Map map; Size mapSize; DefaultTerrain terrainInfo; - List tileSetsFromYaml; + List tileSetsFromYaml; int playerCount; D2kMapImporter(string filename, string tileset, Ruleset rules) @@ -338,11 +338,10 @@ namespace OpenRA.Mods.D2k.UtilityCommands // Get all templates from the tileset YAML file that have at least one frame and an Image property corresponding to the requested tileset // Each frame is a tile from the Dune 2000 tileset files, with the Frame ID being the index of the tile in the original file - tileSetsFromYaml = terrainInfo.Templates.Where(t => - { - var templateInfo = (DefaultTerrainTemplateInfo)t.Value; - return templateInfo.Frames != null && string.Equals(templateInfo.Images[0], tilesetName, StringComparison.InvariantCultureIgnoreCase); - }).Select(ts => ts.Value).ToList(); + tileSetsFromYaml = terrainInfo.TemplatesInDefinitionOrder + .Cast() + .Where(templateInfo => templateInfo.Frames != null && string.Equals(templateInfo.Images[0], tilesetName, StringComparison.InvariantCultureIgnoreCase)) + .ToList(); var players = new MapPlayers(map.Rules, playerCount); map.PlayerDefinitions = players.ToMiniYaml(); @@ -505,16 +504,14 @@ namespace OpenRA.Mods.D2k.UtilityCommands } // Get the first tileset template that contains the Frame ID of the original map's tile with the requested index - var template = tileSetsFromYaml.FirstOrDefault(x => ((DefaultTerrainTemplateInfo)x).Frames.Contains(tileIndex)); + var template = tileSetsFromYaml.FirstOrDefault(x => x.Frames.Contains(tileIndex)); // HACK: The arrakis.yaml tileset file seems to be missing some tiles, so just get a replacement for them // Also used for duplicate tiles that are taken from only tileset // Just get a template that contains a tile with the same ID as requested - template ??= terrainInfo.Templates.FirstOrDefault(t => - { - var templateInfo = (DefaultTerrainTemplateInfo)t.Value; - return templateInfo.Frames != null && templateInfo.Frames.Contains(tileIndex); - }).Value; + template ??= terrainInfo.TemplatesInDefinitionOrder + .Cast() + .FirstOrDefault(templateInfo => templateInfo.Frames != null && templateInfo.Frames.Contains(tileIndex)); if (template == null) { @@ -525,7 +522,7 @@ namespace OpenRA.Mods.D2k.UtilityCommands } var templateIndex = template.Id; - var frameIndex = ((DefaultTerrainTemplateInfo)template).Frames.IndexOf(tileIndex); + var frameIndex = template.Frames.IndexOf(tileIndex); return new TerrainTile(templateIndex, (byte)((frameIndex == -1) ? 0 : frameIndex)); }