From a0cd008da65ba6895c5f9bd4d137e81c40ad2e2b Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 29 Mar 2023 00:48:58 +0300 Subject: [PATCH] Fixed gen2 map importer Bounds calculations --- .../UtilityCommands/ImportGen2MapCommand.cs | 86 ++++++++++++++----- 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportGen2MapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportGen2MapCommand.cs index 656191ba64..8bef579550 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/ImportGen2MapCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportGen2MapCommand.cs @@ -26,6 +26,19 @@ namespace OpenRA.Mods.Cnc.UtilityCommands { public abstract class ImportGen2MapCommand { + // The top few map rows on the original games are visible but not part of the interactable map area. + // These are imported as being outside the map bounds but within the shroud visible margin, + // which requires some extra padding to make the PPos calculations work. + // The first value is added to the left, right, and bottom edges. The second value is added to the top. + // There are 6 tiles in the top uninteractable region (3 in og TS, double for OpenRA). + // So far testing hasn't shown the need for a left/right/bottom uninteractable margin except in cases of odd elevation, which affects us but not the original games. + protected virtual int2 UninteractableMargin { get; } = new int2(0, 6); + + // Having the top cordon equal the maximum possible elevation for the mod is important! + // It needs to be equal to the top row's maximum elevation because there may be an actor there that will later crash the game. + // But since we need to create the map's canvas before importing tile data (like elevation), we opt for the maximum possible elevation to be safe. + protected virtual (int Left, int Top, int Right, int Bottom) Cordon { get; } = (1, 16, 1, 4); + protected abstract Dictionary OverlayToActor { get; } protected abstract Dictionary OverlayShapes { get; } @@ -52,16 +65,17 @@ namespace OpenRA.Mods.Cnc.UtilityCommands var tileset = mapSection.GetValue("Theater", ""); var iniSize = mapSection.GetValue("Size", "0, 0, 0, 0").Split(',').Select(int.Parse).ToArray(); var iniBounds = mapSection.GetValue("LocalSize", "0, 0, 0, 0").Split(',').Select(int.Parse).ToArray(); - var size = new Size(iniSize[2], 2 * iniSize[3]); if (!utility.ModData.DefaultTerrainInfo.TryGetValue(tileset, out var terrainInfo)) throw new InvalidDataException($"Unknown tileset {tileset}"); - var map = new Map(Game.ModData, terrainInfo, size.Width, size.Height) + var usedAreaSize = new Size(iniSize[2], 2 * iniSize[3]); + var mapCanvasSize = new Size(usedAreaSize.Width + Cordon.Left + Cordon.Right, usedAreaSize.Height + Cordon.Top + Cordon.Bottom); + + var map = new Map(Game.ModData, terrainInfo, mapCanvasSize.Width, mapCanvasSize.Height) { Title = basic.GetValue("Name", Path.GetFileNameWithoutExtension(filename)), Author = "Westwood Studios", - Bounds = new Rectangle(iniBounds[0], iniBounds[1], iniBounds[2], 2 * iniBounds[3] + 2 * iniBounds[1]), RequiresMod = utility.ModData.Manifest.Id }; @@ -80,6 +94,9 @@ namespace OpenRA.Mods.Cnc.UtilityCommands var mapPlayers = new MapPlayers(map.Rules, spawnCount); map.PlayerDefinitions = mapPlayers.ToMiniYaml(); + // This needs to be called after ReadTiles because it depends on terrain elevation. + SetInteractableBounds(map, iniBounds); + var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; map.Save(ZipFileLoader.Create(dest)); Console.WriteLine(dest + " saved."); @@ -107,18 +124,15 @@ namespace OpenRA.Mods.Cnc.UtilityCommands var z = mf.ReadUInt8(); /*var zero2 = */mf.ReadUInt8(); - var dx = rx - ry + fullSize.X - 1; - var dy = rx + ry - fullSize.X - 1; - var mapCell = new MPos(dx / 2, dy); - var cell = mapCell.ToCPos(map); + var uv = ToMPos(rx, ry, fullSize.X); - if (map.Tiles.Contains(cell)) + if (map.Tiles.Contains(uv)) { if (!terrainInfo.Templates.ContainsKey(tilenum)) tilenum = subtile = 0; - map.Tiles[cell] = new TerrainTile(tilenum, subtile); - map.Height[cell] = z; + map.Tiles[uv] = new TerrainTile(tilenum, subtile); + map.Height[uv] = z; } } } @@ -146,7 +160,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands var dx = (ushort)x; var dy = (ushort)(y * 2 + x % 2); - var uv = new MPos(dx / 2, dy); + var uv = ToMPos(dx, dy); var rx = (ushort)((dx + dy) / 2 + 1); var ry = (ushort)(dy - rx + fullSize.X + 1); @@ -192,9 +206,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands var pos = int.Parse(kv.Value); var ry = pos / 1000; var rx = pos - ry * 1000; - var dx = rx - ry + fullSize.X - 1; - var dy = rx + ry - fullSize.X - 1; - var cell = new MPos(dx / 2, dy).ToCPos(map); + var cell = ToMPos(rx, ry, fullSize.X).ToCPos(map); var ar = new ActorReference((!int.TryParse(kv.Key, out var wpindex) || wpindex > 7) ? "waypoint" : "mpspawn") { @@ -214,9 +226,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands var pos = int.Parse(kv.Key); var ry = pos / 1000; var rx = pos - ry * 1000; - var dx = rx - ry + fullSize.X - 1; - var dy = rx + ry - fullSize.X - 1; - var cell = new MPos(dx / 2, dy).ToCPos(map); + var cell = ToMPos(rx, ry, fullSize.X).ToCPos(map); var name = kv.Value.ToLowerInvariant(); var ar = new ActorReference(name) @@ -253,9 +263,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands var ry = int.Parse(entries[4]); var facing = (byte)(224 - byte.Parse(entries[type == "Infantry" ? 7 : 5])); - var dx = rx - ry + fullSize.X - 1; - var dy = rx + ry - fullSize.X - 1; - var cell = new MPos(dx / 2, dy).ToCPos(map); + var cell = ToMPos(rx, ry, fullSize.X).ToCPos(map); var ar = new ActorReference(name) { @@ -380,6 +388,26 @@ namespace OpenRA.Mods.Cnc.UtilityCommands } } + protected virtual void SetInteractableBounds(Map map, int[] iniBounds) + { + // Apply cordon cells + the ini-specified map margin. + var left = Cordon.Left + iniBounds[0]; + var top = Cordon.Top + 2 * iniBounds[1]; + + var playableAreaWidth = iniBounds[2]; + var playableAreaHeight = 2 * iniBounds[3] - UninteractableMargin.Y; + + // Black magic, don't ask. Non-flat maps seen to require additional height padding. + var unknownHeightPadding = map.Height.Max() == 0 ? 0 : 8; + + // Calculate Bounds edge tile coordinates. + // Reduce bottom and right by 1 because map.SetBounds() increases them. + var topLeft = new PPos(left, top); + var bottomRight = new PPos(playableAreaWidth + left - 1, playableAreaHeight + unknownHeightPadding + top - 1); + + map.SetBounds(topLeft, bottomRight); + } + protected virtual bool TryHandleOverlayToActorInner(CPos cell, byte[] overlayPack, CellLayer overlayIndex, byte overlayType, out ActorReference actorReference) { actorReference = null; @@ -486,6 +514,24 @@ namespace OpenRA.Mods.Cnc.UtilityCommands } } + /// + /// Convert TS relative position to OpenRA MPos, accounting for map cordons. + /// + protected MPos ToMPos(int dx, int dy) + { + return new MPos(Cordon.Left + (dx + Cordon.Top % 2) / 2, Cordon.Top + dy); + } + + /// + /// Convert TS relative position to OpenRA MPos, accounting for map cordons. + /// + protected MPos ToMPos(int rx, int ry, int mapWidthWithoutCordon) + { + var dx = rx - ry + mapWidthWithoutCordon - 1; + var dy = rx + ry - mapWidthWithoutCordon - 1; + return ToMPos(dx, dy); + } + #endregion } }