From 53db1230abed0994d42ba24cae5a26b703ee5319 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 30 Dec 2020 18:44:09 +0000 Subject: [PATCH] Move default tileset parsing to Mods.Common. --- OpenRA.Game/Map/TerrainInfo.cs | 68 ++++ OpenRA.Game/Map/TileSet.cs | 311 ------------------ .../UtilityCommands/ImportTSMapCommand.cs | 1 + .../EditorBrushes/EditorTileBrush.cs | 1 + OpenRA.Mods.Common/Terrain/DefaultTerrain.cs | 167 +++++++++- .../Terrain/DefaultTileCache.cs | 36 +- OpenRA.Mods.Common/Terrain/TerrainInfo.cs | 103 ++++++ OpenRA.Mods.Common/Traits/Buildings/Bridge.cs | 1 + .../Traits/World/EditorCursorLayer.cs | 1 + .../Traits/World/LegacyBridgeLayer.cs | 1 + .../Traits/World/TerrainRenderer.cs | 33 +- OpenRA.Mods.Common/TraitsInterfaces.cs | 1 + .../UtilityCommands/CheckMissingSprites.cs | 3 +- .../UtilityCommands/ImportLegacyMapCommand.cs | 1 + .../Widgets/Logic/Editor/NewMapLogic.cs | 1 + .../Widgets/Logic/Editor/TileSelectorLogic.cs | 2 +- .../Widgets/TerrainTemplatePreviewWidget.cs | 2 +- .../Traits/Buildings/D2kBuilding.cs | 1 + .../UtilityCommands/D2kMapImporter.cs | 25 +- 19 files changed, 404 insertions(+), 355 deletions(-) create mode 100644 OpenRA.Game/Map/TerrainInfo.cs delete mode 100644 OpenRA.Game/Map/TileSet.cs rename OpenRA.Game/Graphics/Theater.cs => OpenRA.Mods.Common/Terrain/DefaultTileCache.cs (80%) create mode 100644 OpenRA.Mods.Common/Terrain/TerrainInfo.cs diff --git a/OpenRA.Game/Map/TerrainInfo.cs b/OpenRA.Game/Map/TerrainInfo.cs new file mode 100644 index 0000000000..9c865ce148 --- /dev/null +++ b/OpenRA.Game/Map/TerrainInfo.cs @@ -0,0 +1,68 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Collections.Generic; +using OpenRA.FileSystem; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA +{ + public interface ITerrainLoader + { + ITerrainInfo ParseTerrain(IReadOnlyFileSystem fileSystem, string path); + } + + public interface ITerrainInfo + { + string Id { get; } + TerrainTypeInfo[] TerrainTypes { get; } + TerrainTileInfo GetTerrainInfo(TerrainTile r); + bool TryGetTerrainInfo(TerrainTile r, out TerrainTileInfo info); + byte GetTerrainIndex(string type); + byte GetTerrainIndex(TerrainTile r); + TerrainTile DefaultTerrainTile { get; } + + Color[] HeightDebugColors { get; } + IEnumerable RestrictedPlayerColors { get; } + float MinHeightColorBrightness { get; } + float MaxHeightColorBrightness { get; } + } + + public class TerrainTileInfo + { + [FieldLoader.Ignore] + public readonly byte TerrainType = byte.MaxValue; + public readonly byte Height; + public readonly byte RampType; + public readonly Color MinColor; + public readonly Color MaxColor; + } + + public class TerrainTypeInfo + { + public readonly string Type; + public readonly BitSet TargetTypes; + public readonly HashSet AcceptsSmudgeType = new HashSet(); + public readonly Color Color; + public readonly bool RestrictPlayerColor = false; + public readonly string CustomCursor; + + public TerrainTypeInfo(MiniYaml my) { FieldLoader.Load(this, my); } + } + + // HACK: Temporary placeholder to avoid having to change all the traits that reference this constant. + // This can be removed after the palette references have been moved from traits to sequences. + public class TileSet + { + public const string TerrainPaletteInternalName = "terrain"; + } +} diff --git a/OpenRA.Game/Map/TileSet.cs b/OpenRA.Game/Map/TileSet.cs deleted file mode 100644 index 0bd864a350..0000000000 --- a/OpenRA.Game/Map/TileSet.cs +++ /dev/null @@ -1,311 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. For more - * information, see COPYING. - */ -#endregion - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using OpenRA.FileSystem; -using OpenRA.Primitives; -using OpenRA.Support; -using OpenRA.Traits; - -namespace OpenRA -{ - public interface ITerrainLoader - { - ITerrainInfo ParseTerrain(IReadOnlyFileSystem fileSystem, string path); - } - - public interface ITerrainInfo - { - string Id { get; } - TerrainTypeInfo[] TerrainTypes { get; } - TerrainTileInfo GetTerrainInfo(TerrainTile r); - bool TryGetTerrainInfo(TerrainTile r, out TerrainTileInfo info); - byte GetTerrainIndex(string type); - byte GetTerrainIndex(TerrainTile r); - TerrainTile DefaultTerrainTile { get; } - - Color[] HeightDebugColors { get; } - IEnumerable RestrictedPlayerColors { get; } - float MinHeightColorBrightness { get; } - float MaxHeightColorBrightness { get; } - } - - public interface ITemplatedTerrainInfo : ITerrainInfo - { - string[] EditorTemplateOrder { get; } - IReadOnlyDictionary Templates { get; } - } - - public interface ITerrainInfoNotifyMapCreated : ITerrainInfo - { - void MapCreated(Map map); - } - - public class TerrainTileInfo - { - [FieldLoader.Ignore] - public readonly byte TerrainType = byte.MaxValue; - public readonly byte Height; - public readonly byte RampType; - public readonly Color MinColor; - public readonly Color MaxColor; - public readonly float ZOffset = 0.0f; - public readonly float ZRamp = 1.0f; - } - - public class TerrainTypeInfo - { - public readonly string Type; - public readonly BitSet TargetTypes; - public readonly HashSet AcceptsSmudgeType = new HashSet(); - public readonly Color Color; - public readonly bool RestrictPlayerColor = false; - public readonly string CustomCursor; - - public TerrainTypeInfo(MiniYaml my) { FieldLoader.Load(this, my); } - } - - public class TerrainTemplateInfo - { - public readonly ushort Id; - public readonly string[] Images; - public readonly int[] Frames; - public readonly int2 Size; - public readonly bool PickAny; - public readonly string[] Categories; - public readonly string Palette; - - readonly TerrainTileInfo[] tileInfo; - - public TerrainTemplateInfo(ITerrainInfo terrainInfo, MiniYaml my) - { - FieldLoader.Load(this, my); - - var nodes = my.ToDictionary()["Tiles"].Nodes; - - if (!PickAny) - { - tileInfo = new TerrainTileInfo[Size.X * Size.Y]; - foreach (var node in nodes) - { - if (!int.TryParse(node.Key, out var key)) - throw new YamlException("Tileset `{0}` template `{1}` defines a frame `{2}` that is not a valid integer.".F(terrainInfo.Id, Id, node.Key)); - - if (key < 0 || key >= tileInfo.Length) - throw new YamlException("Tileset `{0}` template `{1}` references frame {2}, but only [0..{3}] are valid for a {4}x{5} Size template.".F(terrainInfo.Id, Id, key, tileInfo.Length - 1, Size.X, Size.Y)); - - tileInfo[key] = LoadTileInfo(terrainInfo, node.Value); - } - } - else - { - tileInfo = new TerrainTileInfo[nodes.Count]; - - var i = 0; - foreach (var node in nodes) - { - if (!int.TryParse(node.Key, out var key)) - throw new YamlException("Tileset `{0}` template `{1}` defines a frame `{2}` that is not a valid integer.".F(terrainInfo.Id, Id, node.Key)); - - if (key != i++) - throw new YamlException("Tileset `{0}` template `{1}` is missing a definition for frame {2}.".F(terrainInfo.Id, Id, i - 1)); - - tileInfo[key] = LoadTileInfo(terrainInfo, node.Value); - } - } - } - - static TerrainTileInfo LoadTileInfo(ITerrainInfo terrainInfo, MiniYaml my) - { - var tile = new TerrainTileInfo(); - FieldLoader.Load(tile, my); - - // Terrain type must be converted from a string to an index - tile.GetType().GetField("TerrainType").SetValue(tile, terrainInfo.GetTerrainIndex(my.Value)); - - // Fall back to the terrain-type color if necessary - var overrideColor = terrainInfo.TerrainTypes[tile.TerrainType].Color; - if (tile.MinColor == default) - tile.GetType().GetField("MinColor").SetValue(tile, overrideColor); - - if (tile.MaxColor == default) - tile.GetType().GetField("MaxColor").SetValue(tile, overrideColor); - - return tile; - } - - public TerrainTileInfo this[int index] { get { return tileInfo[index]; } } - - public bool Contains(int index) - { - return index >= 0 && index < tileInfo.Length; - } - - public int TilesCount - { - get { return tileInfo.Length; } - } - } - - public class TileSet : ITemplatedTerrainInfo, ITerrainInfoNotifyMapCreated - { - public const string TerrainPaletteInternalName = "terrain"; - - public readonly string Name; - public readonly string Id; - public readonly int SheetSize = 512; - public readonly Color[] HeightDebugColors = new[] { Color.Red }; - public readonly string[] EditorTemplateOrder; - public readonly bool IgnoreTileSpriteOffsets; - public readonly bool EnableDepth = false; - public readonly float MinHeightColorBrightness = 1.0f; - public readonly float MaxHeightColorBrightness = 1.0f; - - [FieldLoader.Ignore] - public readonly IReadOnlyDictionary Templates; - - [FieldLoader.Ignore] - public readonly TerrainTypeInfo[] TerrainInfo; - readonly Dictionary terrainIndexByType = new Dictionary(); - readonly byte defaultWalkableTerrainIndex; - - public TileSet(IReadOnlyFileSystem fileSystem, string filepath) - { - var yaml = MiniYaml.FromStream(fileSystem.Open(filepath), filepath) - .ToDictionary(x => x.Key, x => x.Value); - - // General info - FieldLoader.Load(this, yaml["General"]); - - // TerrainTypes - TerrainInfo = yaml["Terrain"].ToDictionary().Values - .Select(y => new TerrainTypeInfo(y)) - .OrderBy(tt => tt.Type) - .ToArray(); - - if (TerrainInfo.Length >= byte.MaxValue) - throw new YamlException("Too many terrain types."); - - for (byte i = 0; i < TerrainInfo.Length; i++) - { - var tt = TerrainInfo[i].Type; - - if (terrainIndexByType.ContainsKey(tt)) - throw new YamlException("Duplicate terrain type '{0}' in '{1}'.".F(tt, filepath)); - - terrainIndexByType.Add(tt, i); - } - - defaultWalkableTerrainIndex = GetTerrainIndex("Clear"); - - // Templates - Templates = yaml["Templates"].ToDictionary().Values - .Select(y => new TerrainTemplateInfo(this, y)).ToDictionary(t => t.Id).AsReadOnly(); - } - - public TileSet(string name, string id, TerrainTypeInfo[] terrainInfo) - { - Name = name; - Id = id; - TerrainInfo = terrainInfo; - - if (TerrainInfo.Length >= byte.MaxValue) - throw new InvalidDataException("Too many terrain types."); - - for (byte i = 0; i < terrainInfo.Length; i++) - { - var tt = terrainInfo[i].Type; - if (terrainIndexByType.ContainsKey(tt)) - throw new InvalidDataException("Duplicate terrain type '{0}'.".F(tt)); - - terrainIndexByType.Add(tt, i); - } - - defaultWalkableTerrainIndex = GetTerrainIndex("Clear"); - } - - public TerrainTypeInfo this[byte index] - { - get { return TerrainInfo[index]; } - } - - public bool TryGetTerrainIndex(string type, out byte index) - { - return terrainIndexByType.TryGetValue(type, out index); - } - - public byte GetTerrainIndex(string type) - { - if (terrainIndexByType.TryGetValue(type, out var index)) - return index; - - throw new InvalidDataException("Tileset '{0}' lacks terrain type '{1}'".F(Id, type)); - } - - public byte GetTerrainIndex(TerrainTile r) - { - var tile = Templates[r.Type][r.Index]; - if (tile.TerrainType != byte.MaxValue) - return tile.TerrainType; - - return defaultWalkableTerrainIndex; - } - - public TerrainTileInfo GetTileInfo(TerrainTile r) - { - return Templates[r.Type][r.Index]; - } - - public bool TryGetTileInfo(TerrainTile r, out TerrainTileInfo info) - { - if (!Templates.TryGetValue(r.Type, out var tpl) || !tpl.Contains(r.Index)) - { - info = null; - return false; - } - - info = tpl[r.Index]; - return info != null; - } - - string ITerrainInfo.Id { get { return Id; } } - TerrainTypeInfo[] ITerrainInfo.TerrainTypes { get { return TerrainInfo; } } - TerrainTileInfo ITerrainInfo.GetTerrainInfo(TerrainTile r) { return GetTileInfo(r); } - bool ITerrainInfo.TryGetTerrainInfo(TerrainTile r, out TerrainTileInfo info) { return TryGetTileInfo(r, out info); } - Color[] ITerrainInfo.HeightDebugColors { get { return HeightDebugColors; } } - IEnumerable ITerrainInfo.RestrictedPlayerColors { get { return TerrainInfo.Where(ti => ti.RestrictPlayerColor).Select(ti => ti.Color); } } - float ITerrainInfo.MinHeightColorBrightness { get { return MinHeightColorBrightness; } } - float ITerrainInfo.MaxHeightColorBrightness { get { return MaxHeightColorBrightness; } } - TerrainTile ITerrainInfo.DefaultTerrainTile { get { return new TerrainTile(Templates.First().Key, 0); } } - - string[] ITemplatedTerrainInfo.EditorTemplateOrder { get { return EditorTemplateOrder; } } - IReadOnlyDictionary ITemplatedTerrainInfo.Templates { get { return Templates; } } - - void ITerrainInfoNotifyMapCreated.MapCreated(Map map) - { - // Randomize PickAny tile variants - var r = new MersenneTwister(); - for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++) - { - for (var i = map.Bounds.Left; i < map.Bounds.Right; i++) - { - var type = map.Tiles[new MPos(i, j)].Type; - if (!Templates.TryGetValue(type, out var template) || !template.PickAny) - continue; - - map.Tiles[new MPos(i, j)] = new TerrainTile(type, (byte)r.Next(0, template.TilesCount)); - } - } - } - } -} diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs index 43c46e3997..4eea50fb53 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs @@ -17,6 +17,7 @@ using OpenRA.FileSystem; using OpenRA.Mods.Cnc.FileFormats; using OpenRA.Mods.Common; using OpenRA.Mods.Common.FileFormats; +using OpenRA.Mods.Common.Terrain; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; using OpenRA.Traits; diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs index ac1e860754..babf4e3cc6 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using OpenRA.Graphics; +using OpenRA.Mods.Common.Terrain; using OpenRA.Mods.Common.Traits; namespace OpenRA.Mods.Common.Widgets diff --git a/OpenRA.Mods.Common/Terrain/DefaultTerrain.cs b/OpenRA.Mods.Common/Terrain/DefaultTerrain.cs index bf1244ad3c..4d700c15cb 100644 --- a/OpenRA.Mods.Common/Terrain/DefaultTerrain.cs +++ b/OpenRA.Mods.Common/Terrain/DefaultTerrain.cs @@ -9,8 +9,12 @@ */ #endregion +using System.Collections.Generic; +using System.IO; +using System.Linq; using OpenRA.FileSystem; -using OpenRA.Graphics; +using OpenRA.Primitives; +using OpenRA.Support; namespace OpenRA.Mods.Common.Terrain { @@ -20,7 +24,166 @@ namespace OpenRA.Mods.Common.Terrain public ITerrainInfo ParseTerrain(IReadOnlyFileSystem fileSystem, string path) { - return new TileSet(fileSystem, path); + return new DefaultTerrain(fileSystem, path); + } + } + + public class DefaultTerrainTileInfo : TerrainTileInfo + { + public readonly float ZOffset = 0.0f; + public readonly float ZRamp = 1.0f; + } + + public class DefaultTerrainTemplateInfo : TerrainTemplateInfo + { + public readonly string[] Images; + public readonly int[] Frames; + public readonly string Palette; + + public DefaultTerrainTemplateInfo(ITerrainInfo terrainInfo, MiniYaml my) + : base(terrainInfo, my) { } + + protected override TerrainTileInfo LoadTileInfo(ITerrainInfo terrainInfo, MiniYaml my) + { + var tile = new DefaultTerrainTileInfo(); + FieldLoader.Load(tile, my); + + // Terrain type must be converted from a string to an index + tile.GetType().GetField("TerrainType").SetValue(tile, terrainInfo.GetTerrainIndex(my.Value)); + + // Fall back to the terrain-type color if necessary + var overrideColor = terrainInfo.TerrainTypes[tile.TerrainType].Color; + if (tile.MinColor == default) + tile.GetType().GetField("MinColor").SetValue(tile, overrideColor); + + if (tile.MaxColor == default) + tile.GetType().GetField("MaxColor").SetValue(tile, overrideColor); + + return tile; + } + } + + public class DefaultTerrain : ITemplatedTerrainInfo, ITerrainInfoNotifyMapCreated + { + public readonly string Name; + public readonly string Id; + public readonly int SheetSize = 512; + public readonly Color[] HeightDebugColors = { Color.Red }; + public readonly string[] EditorTemplateOrder; + public readonly bool IgnoreTileSpriteOffsets; + public readonly bool EnableDepth = false; + public readonly float MinHeightColorBrightness = 1.0f; + public readonly float MaxHeightColorBrightness = 1.0f; + + [FieldLoader.Ignore] + public readonly IReadOnlyDictionary Templates; + + [FieldLoader.Ignore] + public readonly TerrainTypeInfo[] TerrainInfo; + readonly Dictionary terrainIndexByType = new Dictionary(); + readonly byte defaultWalkableTerrainIndex; + + public DefaultTerrain(IReadOnlyFileSystem fileSystem, string filepath) + { + var yaml = MiniYaml.FromStream(fileSystem.Open(filepath), filepath) + .ToDictionary(x => x.Key, x => x.Value); + + // General info + FieldLoader.Load(this, yaml["General"]); + + // TerrainTypes + TerrainInfo = yaml["Terrain"].ToDictionary().Values + .Select(y => new TerrainTypeInfo(y)) + .OrderBy(tt => tt.Type) + .ToArray(); + + if (TerrainInfo.Length >= byte.MaxValue) + throw new YamlException("Too many terrain types."); + + for (byte i = 0; i < TerrainInfo.Length; i++) + { + var tt = TerrainInfo[i].Type; + + if (terrainIndexByType.ContainsKey(tt)) + throw new YamlException("Duplicate terrain type '{0}' in '{1}'.".F(tt, filepath)); + + terrainIndexByType.Add(tt, i); + } + + defaultWalkableTerrainIndex = GetTerrainIndex("Clear"); + + // Templates + Templates = yaml["Templates"].ToDictionary().Values + .Select(y => (TerrainTemplateInfo)new DefaultTerrainTemplateInfo(this, y)).ToDictionary(t => t.Id).AsReadOnly(); + } + + public TerrainTypeInfo this[byte index] + { + get { return TerrainInfo[index]; } + } + + public byte GetTerrainIndex(string type) + { + if (terrainIndexByType.TryGetValue(type, out var index)) + return index; + + throw new InvalidDataException("Tileset '{0}' lacks terrain type '{1}'".F(Id, type)); + } + + public byte GetTerrainIndex(TerrainTile r) + { + var tile = Templates[r.Type][r.Index]; + if (tile.TerrainType != byte.MaxValue) + return tile.TerrainType; + + return defaultWalkableTerrainIndex; + } + + public TerrainTileInfo GetTileInfo(TerrainTile r) + { + return Templates[r.Type][r.Index]; + } + + public bool TryGetTileInfo(TerrainTile r, out TerrainTileInfo info) + { + if (!Templates.TryGetValue(r.Type, out var tpl) || !tpl.Contains(r.Index)) + { + info = null; + return false; + } + + info = tpl[r.Index]; + return info != null; + } + + string ITerrainInfo.Id { get { return Id; } } + TerrainTypeInfo[] ITerrainInfo.TerrainTypes { get { return TerrainInfo; } } + TerrainTileInfo ITerrainInfo.GetTerrainInfo(TerrainTile r) { return GetTileInfo(r); } + bool ITerrainInfo.TryGetTerrainInfo(TerrainTile r, out TerrainTileInfo info) { return TryGetTileInfo(r, out info); } + Color[] ITerrainInfo.HeightDebugColors { get { return HeightDebugColors; } } + IEnumerable ITerrainInfo.RestrictedPlayerColors { get { return TerrainInfo.Where(ti => ti.RestrictPlayerColor).Select(ti => ti.Color); } } + float ITerrainInfo.MinHeightColorBrightness { get { return MinHeightColorBrightness; } } + float ITerrainInfo.MaxHeightColorBrightness { get { return MaxHeightColorBrightness; } } + TerrainTile ITerrainInfo.DefaultTerrainTile { get { return new TerrainTile(Templates.First().Key, 0); } } + + string[] ITemplatedTerrainInfo.EditorTemplateOrder { get { return EditorTemplateOrder; } } + IReadOnlyDictionary ITemplatedTerrainInfo.Templates { get { return Templates; } } + + void ITerrainInfoNotifyMapCreated.MapCreated(Map map) + { + // Randomize PickAny tile variants + var r = new MersenneTwister(); + for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++) + { + for (var i = map.Bounds.Left; i < map.Bounds.Right; i++) + { + var type = map.Tiles[new MPos(i, j)].Type; + if (!Templates.TryGetValue(type, out var template) || !template.PickAny) + continue; + + map.Tiles[new MPos(i, j)] = new TerrainTile(type, (byte)r.Next(0, template.TilesCount)); + } + } } } } diff --git a/OpenRA.Game/Graphics/Theater.cs b/OpenRA.Mods.Common/Terrain/DefaultTileCache.cs similarity index 80% rename from OpenRA.Game/Graphics/Theater.cs rename to OpenRA.Mods.Common/Terrain/DefaultTileCache.cs index 3a6a6f6c96..9f07456973 100644 --- a/OpenRA.Game/Graphics/Theater.cs +++ b/OpenRA.Mods.Common/Terrain/DefaultTileCache.cs @@ -13,12 +13,13 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using OpenRA.Graphics; using OpenRA.Primitives; using OpenRA.Support; -namespace OpenRA.Graphics +namespace OpenRA.Mods.Common.Terrain { - class TheaterTemplate + public class TheaterTemplate { public readonly Sprite[] Sprites; public readonly int Stride; @@ -32,17 +33,15 @@ namespace OpenRA.Graphics } } - public sealed class Theater : IDisposable + public sealed class DefaultTileCache : IDisposable { readonly Dictionary templates = new Dictionary(); SheetBuilder sheetBuilder; readonly Sprite missingTile; readonly MersenneTwister random; - TileSet tileset; - public Theater(TileSet tileset, Action onMissingImage = null) + public DefaultTileCache(DefaultTerrain terrainInfo, Action onMissingImage = null) { - this.tileset = tileset; var allocated = false; Func allocate = () => @@ -51,17 +50,18 @@ namespace OpenRA.Graphics throw new SheetOverflowException("Terrain sheet overflow. Try increasing the tileset SheetSize parameter."); allocated = true; - return new Sheet(SheetType.Indexed, new Size(tileset.SheetSize, tileset.SheetSize)); + return new Sheet(SheetType.Indexed, new Size(terrainInfo.SheetSize, terrainInfo.SheetSize)); }; random = new MersenneTwister(); var frameCache = new FrameCache(Game.ModData.DefaultFileSystem, Game.ModData.SpriteLoaders); - foreach (var t in tileset.Templates) + foreach (var t in terrainInfo.Templates) { var variants = new List(); + var templateInfo = (DefaultTerrainTemplateInfo)t.Value; - foreach (var i in t.Value.Images) + foreach (var i in templateInfo.Images) { ISpriteFrame[] allFrames; if (onMissingImage != null) @@ -79,8 +79,8 @@ namespace OpenRA.Graphics else allFrames = frameCache[i]; - var frameCount = tileset.EnableDepth ? allFrames.Length / 2 : allFrames.Length; - var indices = t.Value.Frames != null ? t.Value.Frames : Exts.MakeArray(t.Value.TilesCount, j => j); + var frameCount = terrainInfo.EnableDepth ? allFrames.Length / 2 : allFrames.Length; + var indices = templateInfo.Frames != null ? templateInfo.Frames : Exts.MakeArray(t.Value.TilesCount, j => j); var start = indices.Min(); var end = indices.Max(); @@ -91,7 +91,7 @@ namespace OpenRA.Graphics variants.Add(indices.Select(j => { var f = allFrames[j]; - var tile = t.Value.Contains(j) ? t.Value[j] : null; + var tile = t.Value.Contains(j) ? (DefaultTerrainTileInfo)t.Value[j] : null; // The internal z axis is inverted from expectation (negative is closer) var zOffset = tile != null ? -tile.ZOffset : 0; @@ -107,13 +107,13 @@ namespace OpenRA.Graphics throw new YamlException("Sprite type mismatch. Terrain sprites must all be either Indexed or RGBA."); var s = sheetBuilder.Allocate(f.Size, zRamp, offset); - Util.FastCopyIntoChannel(s, f.Data, f.Type); + OpenRA.Graphics.Util.FastCopyIntoChannel(s, f.Data, f.Type); - if (tileset.EnableDepth) + if (terrainInfo.EnableDepth) { - var ss = sheetBuilder.Allocate(f.Size, zRamp, offset); var depthFrame = allFrames[j + frameCount]; - Util.FastCopyIntoChannel(ss, depthFrame.Data, depthFrame.Type); + var ss = sheetBuilder.Allocate(f.Size, zRamp, offset); + OpenRA.Graphics.Util.FastCopyIntoChannel(ss, depthFrame.Data, depthFrame.Type); // s and ss are guaranteed to use the same sheet // because of the custom terrain sheet allocation @@ -127,13 +127,13 @@ namespace OpenRA.Graphics var allSprites = variants.SelectMany(s => s); // Ignore the offsets baked into R8 sprites - if (tileset.IgnoreTileSpriteOffsets) + if (terrainInfo.IgnoreTileSpriteOffsets) allSprites = allSprites.Select(s => new Sprite(s.Sheet, s.Bounds, s.ZRamp, new float3(float2.Zero, s.Offset.Z), s.Channel, s.BlendMode)); if (onMissingImage != null && !variants.Any()) continue; - templates.Add(t.Value.Id, new TheaterTemplate(allSprites.ToArray(), variants.First().Count(), t.Value.Images.Length)); + templates.Add(t.Value.Id, new TheaterTemplate(allSprites.ToArray(), variants.First().Count(), templateInfo.Images.Length)); } // 1x1px transparent tile diff --git a/OpenRA.Mods.Common/Terrain/TerrainInfo.cs b/OpenRA.Mods.Common/Terrain/TerrainInfo.cs new file mode 100644 index 0000000000..e4089dde5d --- /dev/null +++ b/OpenRA.Mods.Common/Terrain/TerrainInfo.cs @@ -0,0 +1,103 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +namespace OpenRA.Mods.Common.Terrain +{ + public interface ITemplatedTerrainInfo : ITerrainInfo + { + string[] EditorTemplateOrder { get; } + IReadOnlyDictionary Templates { get; } + } + + public interface ITerrainInfoNotifyMapCreated : ITerrainInfo + { + void MapCreated(Map map); + } + + public class TerrainTemplateInfo + { + public readonly ushort Id; + public readonly int2 Size; + public readonly bool PickAny; + public readonly string[] Categories; + + readonly TerrainTileInfo[] tileInfo; + + public TerrainTemplateInfo(ITerrainInfo terrainInfo, MiniYaml my) + { + FieldLoader.Load(this, my); + + var nodes = my.ToDictionary()["Tiles"].Nodes; + + if (!PickAny) + { + tileInfo = new TerrainTileInfo[Size.X * Size.Y]; + foreach (var node in nodes) + { + if (!int.TryParse(node.Key, out var key)) + throw new YamlException("Tileset `{0}` template `{1}` defines a frame `{2}` that is not a valid integer.".F(terrainInfo.Id, Id, node.Key)); + + if (key < 0 || key >= tileInfo.Length) + throw new YamlException("Tileset `{0}` template `{1}` references frame {2}, but only [0..{3}] are valid for a {4}x{5} Size template.".F(terrainInfo.Id, Id, key, tileInfo.Length - 1, Size.X, Size.Y)); + + tileInfo[key] = LoadTileInfo(terrainInfo, node.Value); + } + } + else + { + tileInfo = new TerrainTileInfo[nodes.Count]; + + var i = 0; + foreach (var node in nodes) + { + if (!int.TryParse(node.Key, out var key)) + throw new YamlException("Tileset `{0}` template `{1}` defines a frame `{2}` that is not a valid integer.".F(terrainInfo.Id, Id, node.Key)); + + if (key != i++) + throw new YamlException("Tileset `{0}` template `{1}` is missing a definition for frame {2}.".F(terrainInfo.Id, Id, i - 1)); + + tileInfo[key] = LoadTileInfo(terrainInfo, node.Value); + } + } + } + + protected virtual TerrainTileInfo LoadTileInfo(ITerrainInfo terrainInfo, MiniYaml my) + { + var tile = new TerrainTileInfo(); + FieldLoader.Load(tile, my); + + // Terrain type must be converted from a string to an index + tile.GetType().GetField("TerrainType").SetValue(tile, terrainInfo.GetTerrainIndex(my.Value)); + + // Fall back to the terrain-type color if necessary + var overrideColor = terrainInfo.TerrainTypes[tile.TerrainType].Color; + if (tile.MinColor == default) + tile.GetType().GetField("MinColor").SetValue(tile, overrideColor); + + if (tile.MaxColor == default) + tile.GetType().GetField("MaxColor").SetValue(tile, overrideColor); + + return tile; + } + + public TerrainTileInfo this[int index] { get { return tileInfo[index]; } } + + public bool Contains(int index) + { + return index >= 0 && index < tileInfo.Length; + } + + public int TilesCount + { + get { return tileInfo.Length; } + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Buildings/Bridge.cs b/OpenRA.Mods.Common/Traits/Buildings/Bridge.cs index 1d4355fc2c..72eca21bef 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/Bridge.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/Bridge.cs @@ -16,6 +16,7 @@ using System.Linq; using OpenRA.Effects; using OpenRA.GameRules; using OpenRA.Graphics; +using OpenRA.Mods.Common.Terrain; using OpenRA.Primitives; using OpenRA.Traits; diff --git a/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs index a67e457462..df2f3fa7a1 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; +using OpenRA.Mods.Common.Terrain; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits diff --git a/OpenRA.Mods.Common/Traits/World/LegacyBridgeLayer.cs b/OpenRA.Mods.Common/Traits/World/LegacyBridgeLayer.cs index 4beee9851a..490bc1b30f 100644 --- a/OpenRA.Mods.Common/Traits/World/LegacyBridgeLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/LegacyBridgeLayer.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using OpenRA.Graphics; +using OpenRA.Mods.Common.Terrain; using OpenRA.Primitives; using OpenRA.Traits; diff --git a/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs b/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs index 6c3d5f6442..2e9ec17eb4 100644 --- a/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.IO; using OpenRA.Graphics; +using OpenRA.Mods.Common.Terrain; using OpenRA.Primitives; using OpenRA.Traits; @@ -31,19 +32,20 @@ namespace OpenRA.Mods.Common.Traits failed = true; }; - var tileCache = new Theater((TileSet)terrainInfo, onMissingImage); + var tileCache = new DefaultTileCache((DefaultTerrain)terrainInfo, onMissingImage); foreach (var t in terrainInfo.Templates) { - for (var v = 0; v < t.Value.Images.Length; v++) + var templateInfo = (DefaultTerrainTemplateInfo)t.Value; + for (var v = 0; v < templateInfo.Images.Length; v++) { - if (!missingImages.Contains(t.Value.Images[v])) + if (!missingImages.Contains(templateInfo.Images[v])) { for (var i = 0; i < t.Value.TilesCount; i++) { if (t.Value[i] == null || tileCache.HasTileSprite(new TerrainTile(t.Key, (byte)i), v)) continue; - onError("\tTemplate `{0}` references frame {1} that does not exist in sprite `{2}`.".F(t.Key, i, t.Value.Images[v])); + onError("\tTemplate `{0}` references frame {1} that does not exist in sprite `{2}`.".F(t.Key, i, templateInfo.Images[v])); failed = true; } } @@ -60,25 +62,26 @@ namespace OpenRA.Mods.Common.Traits { readonly Map map; readonly Dictionary spriteLayers = new Dictionary(); - readonly TileSet terrainInfo; - readonly Theater tileCache; + readonly DefaultTerrain terrainInfo; + readonly DefaultTileCache tileCache; bool disposed; public TerrainRenderer(World world) { map = world.Map; - terrainInfo = map.Rules.TerrainInfo as TileSet; + terrainInfo = map.Rules.TerrainInfo as DefaultTerrain; if (terrainInfo == null) - throw new InvalidDataException("TerrainRenderer can only be used with the default TileSet"); + throw new InvalidDataException("TerrainRenderer can only be used with the DefaultTerrain parser"); - tileCache = new Theater(terrainInfo); + tileCache = new DefaultTileCache(terrainInfo); } void IWorldLoaded.WorldLoaded(World world, WorldRenderer wr) { foreach (var template in terrainInfo.Templates) { - var palette = template.Value.Palette ?? TileSet.TerrainPaletteInternalName; + var templateInfo = (DefaultTerrainTemplateInfo)template.Value; + var palette = templateInfo.Palette ?? TileSet.TerrainPaletteInternalName; spriteLayers.GetOrAdd(palette, pal => new TerrainSpriteLayer(world, wr, tileCache.Sheet, BlendMode.Alpha, wr.Palette(palette), world.Type != WorldType.Editor)); } @@ -95,7 +98,7 @@ namespace OpenRA.Mods.Common.Traits var tile = map.Tiles[cell]; var palette = TileSet.TerrainPaletteInternalName; if (terrainInfo.Templates.TryGetValue(tile.Type, out var template)) - palette = template.Palette ?? palette; + palette = ((DefaultTerrainTemplateInfo)template).Palette ?? palette; foreach (var kv in spriteLayers) kv.Value.Update(cell, palette == kv.Key ? tileCache.TileSprite(tile) : null, false); @@ -159,8 +162,9 @@ namespace OpenRA.Mods.Common.Traits return templateRect ?? Rectangle.Empty; } - IEnumerable ITiledTerrainRenderer.RenderUIPreview(WorldRenderer wr, TerrainTemplateInfo template, int2 origin, float scale) + IEnumerable ITiledTerrainRenderer.RenderUIPreview(WorldRenderer wr, TerrainTemplateInfo t, int2 origin, float scale) { + var template = t as DefaultTerrainTemplateInfo; if (template == null) yield break; @@ -187,8 +191,11 @@ namespace OpenRA.Mods.Common.Traits } } - IEnumerable ITiledTerrainRenderer.RenderPreview(WorldRenderer wr, TerrainTemplateInfo template, WPos origin) + IEnumerable ITiledTerrainRenderer.RenderPreview(WorldRenderer wr, TerrainTemplateInfo t, WPos origin) { + if (!(t is DefaultTerrainTemplateInfo template)) + yield break; + var i = 0; for (var y = 0; y < template.Size.Y; y++) { diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index 1c3ed3ca7a..51c483ffc1 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -15,6 +15,7 @@ using OpenRA.Activities; using OpenRA.Graphics; using OpenRA.Mods.Common.Activities; using OpenRA.Mods.Common.Graphics; +using OpenRA.Mods.Common.Terrain; using OpenRA.Primitives; using OpenRA.Traits; diff --git a/OpenRA.Mods.Common/UtilityCommands/CheckMissingSprites.cs b/OpenRA.Mods.Common/UtilityCommands/CheckMissingSprites.cs index ce8570e58d..5435ce6749 100644 --- a/OpenRA.Mods.Common/UtilityCommands/CheckMissingSprites.cs +++ b/OpenRA.Mods.Common/UtilityCommands/CheckMissingSprites.cs @@ -10,9 +10,8 @@ #endregion using System; -using System.Collections.Generic; -using OpenRA.Graphics; using OpenRA.Mods.Common.Graphics; +using OpenRA.Mods.Common.Terrain; using OpenRA.Mods.Common.Traits; namespace OpenRA.Mods.Common.UtilityCommands diff --git a/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs index 29f5e1bbae..a14691fd9c 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs @@ -16,6 +16,7 @@ using System.Linq; using System.Text; using OpenRA.FileSystem; using OpenRA.Mods.Common.FileFormats; +using OpenRA.Mods.Common.Terrain; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; using OpenRA.Traits; diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs index c52b8adc56..2afe1c6f66 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs @@ -11,6 +11,7 @@ using System; using System.Linq; +using OpenRA.Mods.Common.Terrain; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Widgets.Logic diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs index b54d0a5019..fed88a7530 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/TileSelectorLogic.cs @@ -13,8 +13,8 @@ using System; using System.IO; using System.Linq; using OpenRA.Graphics; +using OpenRA.Mods.Common.Terrain; using OpenRA.Mods.Common.Traits; -using OpenRA.Traits; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Widgets.Logic diff --git a/OpenRA.Mods.Common/Widgets/TerrainTemplatePreviewWidget.cs b/OpenRA.Mods.Common/Widgets/TerrainTemplatePreviewWidget.cs index 0210e70026..e0d80067ac 100644 --- a/OpenRA.Mods.Common/Widgets/TerrainTemplatePreviewWidget.cs +++ b/OpenRA.Mods.Common/Widgets/TerrainTemplatePreviewWidget.cs @@ -12,9 +12,9 @@ using System; using System.IO; using OpenRA.Graphics; +using OpenRA.Mods.Common.Terrain; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; -using OpenRA.Traits; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Widgets diff --git a/OpenRA.Mods.D2k/Traits/Buildings/D2kBuilding.cs b/OpenRA.Mods.D2k/Traits/Buildings/D2kBuilding.cs index e007967d7f..e571a73083 100644 --- a/OpenRA.Mods.D2k/Traits/Buildings/D2kBuilding.cs +++ b/OpenRA.Mods.D2k/Traits/Buildings/D2kBuilding.cs @@ -11,6 +11,7 @@ using System.IO; using System.Linq; +using OpenRA.Mods.Common.Terrain; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; using OpenRA.Traits; diff --git a/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs index d218de2a17..66af4f6ab8 100644 --- a/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs +++ b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using OpenRA.Mods.Common.Terrain; using OpenRA.Primitives; namespace OpenRA.Mods.D2k.UtilityCommands @@ -260,7 +261,7 @@ namespace OpenRA.Mods.D2k.UtilityCommands Map map; Size mapSize; - ITemplatedTerrainInfo terrainInfo; + DefaultTerrain terrainInfo; List tileSetsFromYaml; int playerCount; @@ -306,7 +307,10 @@ namespace OpenRA.Mods.D2k.UtilityCommands void Initialize(string mapFile) { mapSize = new Size(stream.ReadUInt16(), stream.ReadUInt16()); - terrainInfo = (ITemplatedTerrainInfo)Game.ModData.DefaultTerrainInfo["ARRAKIS"]; + terrainInfo = Game.ModData.DefaultTerrainInfo["ARRAKIS"] as DefaultTerrain; + + if (terrainInfo == null) + throw new InvalidDataException("The D2k map importer requires the DefaultTerrain parser."); map = new Map(Game.ModData, terrainInfo, mapSize.Width + 2 * MapCordonWidth, mapSize.Height + 2 * MapCordonWidth) { @@ -320,8 +324,11 @@ 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 => t.Value.Frames != null - && t.Value.Images[0].ToLowerInvariant() == tilesetName.ToLowerInvariant()).Select(ts => ts.Value).ToList(); + tileSetsFromYaml = terrainInfo.Templates.Where(t => + { + var templateInfo = (DefaultTerrainTemplateInfo)t.Value; + return templateInfo.Frames != null && templateInfo.Images[0].ToLowerInvariant() == tilesetName.ToLowerInvariant(); + }).Select(ts => ts.Value).ToList(); var players = new MapPlayers(map.Rules, playerCount); map.PlayerDefinitions = players.ToMiniYaml(); @@ -461,14 +468,18 @@ 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 => x.Frames.Contains(tileIndex)); + var template = tileSetsFromYaml.FirstOrDefault(x => ((DefaultTerrainTemplateInfo)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 if (template == null) { // Just get a template that contains a tile with the same ID as requested - var templates = terrainInfo.Templates.Where(t => t.Value.Frames != null && t.Value.Frames.Contains(tileIndex)); + var templates = terrainInfo.Templates.Where(t => + { + var templateInfo = (DefaultTerrainTemplateInfo)t.Value; + return templateInfo.Frames != null && templateInfo.Frames.Contains(tileIndex); + }); if (templates.Any()) template = templates.First().Value; } @@ -482,7 +493,7 @@ namespace OpenRA.Mods.D2k.UtilityCommands } var templateIndex = template.Id; - var frameIndex = Array.IndexOf(template.Frames, tileIndex); + var frameIndex = Array.IndexOf(((DefaultTerrainTemplateInfo)template).Frames, tileIndex); return new TerrainTile(templateIndex, (byte)((frameIndex == -1) ? 0 : frameIndex)); }