diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportGen2MapCommand.cs similarity index 54% rename from OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs rename to OpenRA.Mods.Cnc/UtilityCommands/ImportGen2MapCommand.cs index af8ed60398..656191ba64 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportGen2MapCommand.cs @@ -24,237 +24,23 @@ using OpenRA.Traits; namespace OpenRA.Mods.Cnc.UtilityCommands { - class ImportTSMapCommand : IUtilityCommand + public abstract class ImportGen2MapCommand { - string IUtilityCommand.Name => "--import-ts-map"; - bool IUtilityCommand.ValidateArguments(string[] args) { return args.Length >= 2; } + protected abstract Dictionary OverlayToActor { get; } - static readonly Dictionary OverlayToActor = new() - { - { 0x00, "gasand" }, - { 0x01, "gasand" }, - { 0x02, "gawall" }, - { 0x03, "gawall" }, - { 0x18, "bridge1" }, - { 0x19, "bridge2" }, - { 0x1A, "nawall" }, - { 0x27, "tracks01" }, - { 0x28, "tracks02" }, - { 0x29, "tracks03" }, - { 0x2A, "tracks04" }, - { 0x2B, "tracks05" }, - { 0x2C, "tracks06" }, - { 0x2D, "tracks07" }, - { 0x2E, "tracks08" }, - { 0x2F, "tracks09" }, - { 0x30, "tracks10" }, - { 0x31, "tracks11" }, - { 0x32, "tracks12" }, - { 0x33, "tracks13" }, - { 0x34, "tracks14" }, - { 0x35, "tracks15" }, - { 0x36, "tracks16" }, - { 0x37, "tracktunnel01" }, - { 0x38, "tracktunnel02" }, - { 0x39, "tracktunnel03" }, - { 0x3A, "tracktunnel04" }, - { 0x3B, "railbrdg1" }, - { 0x3C, "railbrdg2" }, - { 0x3D, "crat01" }, - { 0x3E, "crat02" }, - { 0x3F, "crat03" }, - { 0x40, "crat04" }, - { 0x41, "crat0A" }, - { 0x42, "crat0B" }, - { 0x43, "crat0C" }, - { 0x44, "drum01" }, - { 0x45, "drum02" }, - { 0x46, "palet01" }, - { 0x47, "palet02" }, - { 0x48, "palet03" }, - { 0x49, "palet04" }, + protected abstract Dictionary OverlayShapes { get; } - // Bridges - { 0x4A, "lobrdg_b" }, // lobrdg01 - { 0x4B, "lobrdg_b" }, // lobrdg02 - { 0x4C, "lobrdg_b" }, // lobrdg03 - { 0x4D, "lobrdg_b" }, // lobrdg04 - { 0x4E, "lobrdg_b" }, // lobrdg05 - { 0x4F, "lobrdg_b" }, // lobrdg06 - { 0x50, "lobrdg_b" }, // lobrdg07 - { 0x51, "lobrdg_b" }, // lobrdg08 - { 0x52, "lobrdg_b" }, // lobrdg09 - { 0x53, "lobrdg_a" }, // lobrdg10 - { 0x54, "lobrdg_a" }, // lobrdg11 - { 0x55, "lobrdg_a" }, // lobrdg12 - { 0x56, "lobrdg_a" }, // lobrdg13 - { 0x57, "lobrdg_a" }, // lobrdg14 - { 0x58, "lobrdg_a" }, // lobrdg15 - { 0x59, "lobrdg_a" }, // lobrdg16 - { 0x5A, "lobrdg_a" }, // lobrdg17 - { 0x5B, "lobrdg_a" }, // lobrdg18 - { 0x5C, "lobrdg_r_se" }, // lobrdg19 - { 0x5D, "lobrdg_r_se" }, // lobrdg20 - { 0x5E, "lobrdg_r_nw" }, // lobrdg21 - { 0x5F, "lobrdg_r_nw" }, // lobrdg22 - { 0x60, "lobrdg_r_ne" }, // lobrdg23 - { 0x61, "lobrdg_r_ne" }, // lobrdg24 - { 0x62, "lobrdg_r_sw" }, // lobrdg25 - { 0x63, "lobrdg_r_sw" }, // lobrdg26 - { 0x64, "lobrdg_b_d" }, // lobrdg27 - { 0x65, "lobrdg_a_d" }, // lobrdg28 + protected abstract Dictionary OverlayToHealth { get; } - // Ramps - { 0x7A, "lobrdg_r_se" }, // lobrdg1 - { 0x7B, "lobrdg_r_nw" }, // lobrdg2 - { 0x7C, "lobrdg_r_ne" }, // lobrdg3 - { 0x7D, "lobrdg_r_sw" }, // lobrdg4 + protected abstract Dictionary ResourceFromOverlay { get; } - // Other - { 0x1B, "bigblue" }, - { 0xA7, "veinhole" }, - { 0xA8, "srock01" }, - { 0xA9, "srock02" }, - { 0xAA, "srock03" }, - { 0xAB, "srock04" }, - { 0xAC, "srock05" }, - { 0xAD, "trock01" }, - { 0xAE, "trock02" }, - { 0xAF, "trock03" }, - { 0xB0, "trock04" }, - { 0xB1, "trock05" }, - { 0xB2, null }, // veinholedummy - { 0xB3, "crate" } - }; + protected abstract Dictionary DeployableActors { get; } - static readonly Dictionary OverlayShapes = new() - { - { 0x4A, new Size(1, 3) }, - { 0x4B, new Size(1, 3) }, - { 0x4C, new Size(1, 3) }, - { 0x4D, new Size(1, 3) }, - { 0x4E, new Size(1, 3) }, - { 0x4F, new Size(1, 3) }, - { 0x50, new Size(1, 3) }, - { 0x51, new Size(1, 3) }, - { 0x52, new Size(1, 3) }, - { 0x53, new Size(3, 1) }, - { 0x54, new Size(3, 1) }, - { 0x55, new Size(3, 1) }, - { 0x56, new Size(3, 1) }, - { 0x57, new Size(3, 1) }, - { 0x58, new Size(3, 1) }, - { 0x59, new Size(3, 1) }, - { 0x5A, new Size(3, 1) }, - { 0x5B, new Size(3, 1) }, - { 0x5C, new Size(1, 3) }, - { 0x5D, new Size(1, 3) }, - { 0x5E, new Size(1, 3) }, - { 0x5F, new Size(1, 3) }, - { 0x60, new Size(3, 1) }, - { 0x61, new Size(3, 1) }, - { 0x62, new Size(3, 1) }, - { 0x63, new Size(3, 1) }, - { 0x64, new Size(1, 3) }, - { 0x65, new Size(3, 1) }, - { 0x7A, new Size(1, 3) }, - { 0x7B, new Size(1, 3) }, - { 0x7C, new Size(3, 1) }, - { 0x7D, new Size(3, 1) }, - }; + protected abstract string[] LampActors { get; } - static readonly Dictionary OverlayToHealth = new() - { - // 1,3 bridge tiles - { 0x4A, DamageState.Undamaged }, - { 0x4B, DamageState.Undamaged }, - { 0x4C, DamageState.Undamaged }, - { 0x4D, DamageState.Undamaged }, - { 0x4E, DamageState.Heavy }, - { 0x4F, DamageState.Heavy }, - { 0x50, DamageState.Heavy }, - { 0x51, DamageState.Critical }, - { 0x52, DamageState.Critical }, + protected abstract string[] CreepActors { get; } - // 3,1 bridge tiles - { 0x53, DamageState.Undamaged }, - { 0x54, DamageState.Undamaged }, - { 0x55, DamageState.Undamaged }, - { 0x56, DamageState.Undamaged }, - { 0x57, DamageState.Heavy }, - { 0x58, DamageState.Heavy }, - { 0x59, DamageState.Heavy }, - { 0x5A, DamageState.Critical }, - { 0x5B, DamageState.Critical }, - - // Ramps - { 0x5C, DamageState.Undamaged }, - { 0x5D, DamageState.Heavy }, - { 0x5E, DamageState.Undamaged }, - { 0x5F, DamageState.Heavy }, - { 0x60, DamageState.Undamaged }, - { 0x61, DamageState.Heavy }, - { 0x62, DamageState.Undamaged }, - { 0x63, DamageState.Heavy }, - - // Ramp duplicates - { 0x7A, DamageState.Undamaged }, - { 0x7B, DamageState.Undamaged }, - { 0x7C, DamageState.Undamaged }, - { 0x7D, DamageState.Undamaged }, - - // Actually dead, placeholders for resurrection - { 0x64, DamageState.Undamaged }, - { 0x65, DamageState.Undamaged }, - }; - - static readonly Dictionary ResourceFromOverlay = new() - { - // "tib" - Regular Tiberium - { - 0x01, new byte[] - { - 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79 - } - }, - - // "btib" - Blue Tiberium - { - 0x02, new byte[] - { - 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, - - // Should be "tib2" - 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, - 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, - - // Should be "tib3" - 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, - 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6 - } - }, - - // Veins - { 0x03, new byte[] { 0x7E } } - }; - - static readonly Dictionary DeployableActors = new() - { - { "gadpsa", "lpst" }, - { "gatick", "ttnk" } - }; - - static readonly string[] LampActors = - { - "GALITE", "INGALITE", "NEGLAMP", "REDLAMP", "NEGRED", "GRENLAMP", "BLUELAMP", "YELWLAMP", - "INYELWLAMP", "PURPLAMP", "INPURPLAMP", "INORANLAMP", "INGRNLMP", "INREDLMP", "INBLULMP" - }; - - static readonly string[] CreepActors = { "DOGGIE", "VISC_SML", "VISC_LRG", "JFISH" }; - - [Desc("FILENAME", "Convert a Tiberian Sun map to the OpenRA format.")] - void IUtilityCommand.Run(Utility utility, string[] args) + protected void Run(Utility utility, string[] args) { // HACK: The engine code assumes that Game.modData is set. Game.ModData = utility.ModData; @@ -299,47 +85,14 @@ namespace OpenRA.Mods.Cnc.UtilityCommands Console.WriteLine(dest + " saved."); } - static void UnpackLZO(byte[] src, byte[] dest) - { - var srcOffset = 0U; - var destOffset = 0U; - - while (destOffset < dest.Length && srcOffset < src.Length) - { - var srcLength = BitConverter.ToUInt16(src, (int)srcOffset); - var destLength = (uint)BitConverter.ToUInt16(src, (int)srcOffset + 2); - srcOffset += 4; - LZOCompression.DecodeInto(src, srcOffset, srcLength, dest, destOffset, ref destLength); - srcOffset += srcLength; - destOffset += destLength; - } - } - - static void UnpackLCW(byte[] src, byte[] dest, byte[] temp) - { - var srcOffset = 0; - var destOffset = 0; - - while (destOffset < dest.Length) - { - var srcLength = BitConverter.ToUInt16(src, srcOffset); - var destLength = BitConverter.ToUInt16(src, srcOffset + 2); - srcOffset += 4; - LCWCompression.DecodeInto(src, temp, srcOffset); - Array.Copy(temp, 0, dest, destOffset, destLength); - srcOffset += srcLength; - destOffset += destLength; - } - } - - static void ReadTiles(Map map, IniFile file, int2 fullSize) + protected virtual void ReadTiles(Map map, IniFile file, int2 fullSize) { var terrainInfo = (ITemplatedTerrainInfo)Game.ModData.DefaultTerrainInfo[map.Tileset]; var mapSection = file.GetSection("IsoMapPack5"); var data = Convert.FromBase64String(string.Concat(mapSection.Select(kvp => kvp.Value))); var cells = (fullSize.X * 2 - 1) * fullSize.Y; - var lzoPackSize = cells * 11 + 4; // last 4 bytes contains a lzo pack header saying no more data is left + var lzoPackSize = cells * 11 + 4; // The last 4 bytes contain a LZO pack header saying no more data is left. var isoMapPack = new byte[lzoPackSize]; UnpackLZO(data, isoMapPack); @@ -370,7 +123,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands } } - static void ReadOverlay(Map map, IniFile file, int2 fullSize) + protected virtual void ReadOverlay(Map map, IniFile file, int2 fullSize) { var overlaySection = file.GetSection("OverlayPack"); var overlayCompressed = Convert.FromBase64String(string.Concat(overlaySection.Select(kvp => kvp.Value))); @@ -410,85 +163,28 @@ namespace OpenRA.Mods.Cnc.UtilityCommands if (overlayType == 0xFF) continue; - if (OverlayToActor.TryGetValue(overlayType, out var actorType)) + if (TryHandleOverlayToActorInner(cell, overlayPack, overlayIndex, overlayType, out var ar)) { - if (string.IsNullOrEmpty(actorType)) - continue; - - var shape = new Size(1, 1); - if (OverlayShapes.TryGetValue(overlayType, out shape)) - { - // Only import the top-left cell of multi-celled overlays - var aboveType = overlayPack[overlayIndex[cell - new CVec(1, 0)]]; - if (shape.Width > 1 && aboveType != 0xFF) - if (OverlayToActor.TryGetValue(aboveType, out var a) && a == actorType) - continue; - - var leftType = overlayPack[overlayIndex[cell - new CVec(0, 1)]]; - if (shape.Height > 1 && leftType != 0xFF) - if (OverlayToActor.TryGetValue(leftType, out var a) && a == actorType) - continue; - } - - // Fix position of vein hole actors - var location = cell; - if (actorType == "veinhole") - location -= new CVec(1, 1); - - var ar = new ActorReference(actorType) - { - new LocationInit(location), - new OwnerInit("Neutral") - }; - - if (OverlayToHealth.TryGetValue(overlayType, out var damageState)) - { - var health = 100; - if (damageState == DamageState.Critical) - health = 25; - else if (damageState == DamageState.Heavy) - health = 50; - else if (damageState == DamageState.Medium) - health = 75; - - if (health != 100) - ar.Add(new HealthInit(health)); - } - - map.ActorDefinitions.Add(new MiniYamlNode("Actor" + map.ActorDefinitions.Count, ar.Save())); + if (ar != null) + map.ActorDefinitions.Add(new MiniYamlNode("Actor" + map.ActorDefinitions.Count, ar.Save())); continue; } - // TS maps encode the non-harvestable border tiles as overlay - // Only convert to vein resources if the overlay data specifies non-border frames - if (overlayType == 0x7E) + if (TryHandleResourceFromOverlayInner(overlayType, overlayDataPack[overlayIndex[cell]], out var resourceTile)) { - var frame = overlayDataPack[overlayIndex[cell]]; - if (frame < 48 || frame > 60) - continue; - - // Pick half or full density based on the frame - map.Resources[cell] = new ResourceTile(3, (byte)(frame == 52 ? 1 : 2)); + map.Resources[cell] = resourceTile; continue; } - var resourceType = ResourceFromOverlay - .Where(kv => kv.Value.Contains(overlayType)) - .Select(kv => kv.Key) - .FirstOrDefault(); - - if (resourceType != 0) - { - map.Resources[cell] = new ResourceTile(resourceType, overlayDataPack[overlayIndex[cell]]); + if (TryHandleOtherOverlayInner(map, cell, overlayDataPack, overlayIndex, overlayType)) continue; - } - Console.WriteLine("{0} unknown overlay {1}", cell, overlayType); + Console.WriteLine($"Cell {cell}: unknown overlay {overlayType}"); } } - static void ReadWaypoints(Map map, IniFile file, int2 fullSize) + protected virtual void ReadWaypoints(Map map, IniFile file, int2 fullSize) { var waypointsSection = file.GetSection("Waypoints", true); foreach (var kv in waypointsSection) @@ -510,7 +206,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands } } - static void ReadTerrainActors(Map map, IniFile file, int2 fullSize) + protected virtual void ReadTerrainActors(Map map, IniFile file, int2 fullSize) { var terrainSection = file.GetSection("Terrain", true); foreach (var kv in terrainSection) @@ -536,7 +232,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands } } - static void ReadActors(Map map, IniFile file, string type, int2 fullSize) + protected virtual void ReadActors(Map map, IniFile file, string type, int2 fullSize) { var structuresSection = file.GetSection(type, true); foreach (var kv in structuresSection) @@ -596,7 +292,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands } } - static void ReadLighting(Map map, IniFile file) + protected virtual void ReadLighting(Map map, IniFile file) { var lightingTypes = new Dictionary() { @@ -643,7 +339,7 @@ namespace OpenRA.Mods.Cnc.UtilityCommands } } - static void ReadLamps(Map map, IniFile file) + protected virtual void ReadLamps(Map map, IniFile file) { var lightingTypes = new Dictionary() { @@ -683,5 +379,113 @@ namespace OpenRA.Mods.Cnc.UtilityCommands } } } + + protected virtual bool TryHandleOverlayToActorInner(CPos cell, byte[] overlayPack, CellLayer overlayIndex, byte overlayType, out ActorReference actorReference) + { + actorReference = null; + if (!OverlayToActor.TryGetValue(overlayType, out var actorType)) + return false; + + // This could be just a dummy handler that we want to ignore. + if (string.IsNullOrEmpty(actorType)) + return true; + + if (OverlayShapes.TryGetValue(overlayType, out var shape)) + { + // Only import the top-left cell of multi-celled overlays + // Returning true here means this is a part of a bigger overlay that has already been handled. + var aboveType = overlayPack[overlayIndex[cell - new CVec(1, 0)]]; + if (shape.Width > 1 && aboveType != 0xFF) + if (OverlayToActor.TryGetValue(aboveType, out var a) && a == actorType) + return true; + + var leftType = overlayPack[overlayIndex[cell - new CVec(0, 1)]]; + if (shape.Height > 1 && leftType != 0xFF) + if (OverlayToActor.TryGetValue(leftType, out var a) && a == actorType) + return true; + } + + actorReference = new ActorReference(actorType) + { + new LocationInit(cell), + new OwnerInit("Neutral") + }; + + TryHandleOverlayToHealthInner(overlayType, actorReference); + + return true; + } + + protected virtual bool TryHandleOverlayToHealthInner(byte overlayType, ActorReference actorReference) + { + if (!OverlayToHealth.TryGetValue(overlayType, out var damageState)) + return false; + + var health = 100; + if (damageState == DamageState.Critical) + health = 25; + else if (damageState == DamageState.Heavy) + health = 50; + else if (damageState == DamageState.Medium) + health = 75; + + if (health != 100) + actorReference.Add(new HealthInit(health)); + + return true; + } + + protected virtual bool TryHandleResourceFromOverlayInner(byte overlayType, byte densityIndex, out ResourceTile resourceTile) + { + var resourceType = ResourceFromOverlay + .Where(kv => kv.Value.Contains(overlayType)) + .Select(kv => kv.Key) + .FirstOrDefault(); + + resourceTile = new ResourceTile(resourceType, densityIndex); + return resourceType != 0; + } + + protected virtual bool TryHandleOtherOverlayInner(Map map, CPos cell, byte[] overlayDataPack, CellLayer overlayIndex, byte overlayType) + { + return false; + } + + #region Helper methods + + protected static void UnpackLZO(byte[] src, byte[] dest) + { + var srcOffset = 0U; + var destOffset = 0U; + + while (destOffset < dest.Length && srcOffset < src.Length) + { + var srcLength = BitConverter.ToUInt16(src, (int)srcOffset); + var destLength = (uint)BitConverter.ToUInt16(src, (int)srcOffset + 2); + srcOffset += 4; + LZOCompression.DecodeInto(src, srcOffset, srcLength, dest, destOffset, ref destLength); + srcOffset += srcLength; + destOffset += destLength; + } + } + + protected static void UnpackLCW(byte[] src, byte[] dest, byte[] temp) + { + var srcOffset = 0; + var destOffset = 0; + + while (destOffset < dest.Length) + { + var srcLength = BitConverter.ToUInt16(src, srcOffset); + var destLength = BitConverter.ToUInt16(src, srcOffset + 2); + srcOffset += 4; + LCWCompression.DecodeInto(src, temp, srcOffset); + Array.Copy(temp, 0, dest, destOffset, destLength); + srcOffset += srcLength; + destOffset += destLength; + } + } + + #endregion } } diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportTiberianSunMapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportTiberianSunMapCommand.cs new file mode 100644 index 0000000000..06f8c487ea --- /dev/null +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportTiberianSunMapCommand.cs @@ -0,0 +1,331 @@ +#region Copyright & License Information +/* + * Copyright (c) The OpenRA Developers and Contributors + * 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.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Cnc.UtilityCommands +{ + class ImportTiberianSunMapCommand : ImportGen2MapCommand, IUtilityCommand + { + string IUtilityCommand.Name => "--import-ts-map"; + + bool IUtilityCommand.ValidateArguments(string[] args) { return args.Length >= 2; } + + [Desc("FILENAME", "Convert a Tiberian Sun map to the OpenRA format.")] + void IUtilityCommand.Run(Utility utility, string[] args) + { + Run(utility, args); + } + + #region Mod-specific data + + protected override Dictionary OverlayToActor { get; } = new() + { + { 0x00, "gasand" }, + { 0x01, "gasand" }, + { 0x02, "gawall" }, + { 0x03, "gawall" }, + { 0x18, "bridge1" }, + { 0x19, "bridge2" }, + { 0x1A, "nawall" }, + { 0x27, "tracks01" }, + { 0x28, "tracks02" }, + { 0x29, "tracks03" }, + { 0x2A, "tracks04" }, + { 0x2B, "tracks05" }, + { 0x2C, "tracks06" }, + { 0x2D, "tracks07" }, + { 0x2E, "tracks08" }, + { 0x2F, "tracks09" }, + { 0x30, "tracks10" }, + { 0x31, "tracks11" }, + { 0x32, "tracks12" }, + { 0x33, "tracks13" }, + { 0x34, "tracks14" }, + { 0x35, "tracks15" }, + { 0x36, "tracks16" }, + { 0x37, "tracktunnel01" }, + { 0x38, "tracktunnel02" }, + { 0x39, "tracktunnel03" }, + { 0x3A, "tracktunnel04" }, + { 0x3B, "railbrdg1" }, + { 0x3C, "railbrdg2" }, + { 0x3D, "crat01" }, + { 0x3E, "crat02" }, + { 0x3F, "crat03" }, + { 0x40, "crat04" }, + { 0x41, "crat0A" }, + { 0x42, "crat0B" }, + { 0x43, "crat0C" }, + { 0x44, "drum01" }, + { 0x45, "drum02" }, + { 0x46, "palet01" }, + { 0x47, "palet02" }, + { 0x48, "palet03" }, + { 0x49, "palet04" }, + + // Bridges + { 0x4A, "lobrdg_b" }, // lobrdg01 + { 0x4B, "lobrdg_b" }, // lobrdg02 + { 0x4C, "lobrdg_b" }, // lobrdg03 + { 0x4D, "lobrdg_b" }, // lobrdg04 + { 0x4E, "lobrdg_b" }, // lobrdg05 + { 0x4F, "lobrdg_b" }, // lobrdg06 + { 0x50, "lobrdg_b" }, // lobrdg07 + { 0x51, "lobrdg_b" }, // lobrdg08 + { 0x52, "lobrdg_b" }, // lobrdg09 + { 0x53, "lobrdg_a" }, // lobrdg10 + { 0x54, "lobrdg_a" }, // lobrdg11 + { 0x55, "lobrdg_a" }, // lobrdg12 + { 0x56, "lobrdg_a" }, // lobrdg13 + { 0x57, "lobrdg_a" }, // lobrdg14 + { 0x58, "lobrdg_a" }, // lobrdg15 + { 0x59, "lobrdg_a" }, // lobrdg16 + { 0x5A, "lobrdg_a" }, // lobrdg17 + { 0x5B, "lobrdg_a" }, // lobrdg18 + { 0x5C, "lobrdg_r_se" }, // lobrdg19 + { 0x5D, "lobrdg_r_se" }, // lobrdg20 + { 0x5E, "lobrdg_r_nw" }, // lobrdg21 + { 0x5F, "lobrdg_r_nw" }, // lobrdg22 + { 0x60, "lobrdg_r_ne" }, // lobrdg23 + { 0x61, "lobrdg_r_ne" }, // lobrdg24 + { 0x62, "lobrdg_r_sw" }, // lobrdg25 + { 0x63, "lobrdg_r_sw" }, // lobrdg26 + { 0x64, "lobrdg_b_d" }, // lobrdg27 + { 0x65, "lobrdg_a_d" }, // lobrdg28 + + // Ramps + { 0x7A, "lobrdg_r_se" }, // lobrdg1 + { 0x7B, "lobrdg_r_nw" }, // lobrdg2 + { 0x7C, "lobrdg_r_ne" }, // lobrdg3 + { 0x7D, "lobrdg_r_sw" }, // lobrdg4 + + // Other + { 0x1B, "bigblue" }, + { 0xA7, "veinhole" }, + { 0xA8, "srock01" }, + { 0xA9, "srock02" }, + { 0xAA, "srock03" }, + { 0xAB, "srock04" }, + { 0xAC, "srock05" }, + { 0xAD, "trock01" }, + { 0xAE, "trock02" }, + { 0xAF, "trock03" }, + { 0xB0, "trock04" }, + { 0xB1, "trock05" }, + { 0xB2, null }, // veinholedummy + { 0xB3, "crate" } + }; + + protected override Dictionary OverlayShapes { get; } = new() + { + { 0x4A, new Size(1, 3) }, + { 0x4B, new Size(1, 3) }, + { 0x4C, new Size(1, 3) }, + { 0x4D, new Size(1, 3) }, + { 0x4E, new Size(1, 3) }, + { 0x4F, new Size(1, 3) }, + { 0x50, new Size(1, 3) }, + { 0x51, new Size(1, 3) }, + { 0x52, new Size(1, 3) }, + { 0x53, new Size(3, 1) }, + { 0x54, new Size(3, 1) }, + { 0x55, new Size(3, 1) }, + { 0x56, new Size(3, 1) }, + { 0x57, new Size(3, 1) }, + { 0x58, new Size(3, 1) }, + { 0x59, new Size(3, 1) }, + { 0x5A, new Size(3, 1) }, + { 0x5B, new Size(3, 1) }, + { 0x5C, new Size(1, 3) }, + { 0x5D, new Size(1, 3) }, + { 0x5E, new Size(1, 3) }, + { 0x5F, new Size(1, 3) }, + { 0x60, new Size(3, 1) }, + { 0x61, new Size(3, 1) }, + { 0x62, new Size(3, 1) }, + { 0x63, new Size(3, 1) }, + { 0x64, new Size(1, 3) }, + { 0x65, new Size(3, 1) }, + { 0x7A, new Size(1, 3) }, + { 0x7B, new Size(1, 3) }, + { 0x7C, new Size(3, 1) }, + { 0x7D, new Size(3, 1) }, + }; + + protected override Dictionary OverlayToHealth { get; } = new() + { + // 1,3 bridge tiles + { 0x4A, DamageState.Undamaged }, + { 0x4B, DamageState.Undamaged }, + { 0x4C, DamageState.Undamaged }, + { 0x4D, DamageState.Undamaged }, + { 0x4E, DamageState.Heavy }, + { 0x4F, DamageState.Heavy }, + { 0x50, DamageState.Heavy }, + { 0x51, DamageState.Critical }, + { 0x52, DamageState.Critical }, + + // 3,1 bridge tiles + { 0x53, DamageState.Undamaged }, + { 0x54, DamageState.Undamaged }, + { 0x55, DamageState.Undamaged }, + { 0x56, DamageState.Undamaged }, + { 0x57, DamageState.Heavy }, + { 0x58, DamageState.Heavy }, + { 0x59, DamageState.Heavy }, + { 0x5A, DamageState.Critical }, + { 0x5B, DamageState.Critical }, + + // Ramps + { 0x5C, DamageState.Undamaged }, + { 0x5D, DamageState.Heavy }, + { 0x5E, DamageState.Undamaged }, + { 0x5F, DamageState.Heavy }, + { 0x60, DamageState.Undamaged }, + { 0x61, DamageState.Heavy }, + { 0x62, DamageState.Undamaged }, + { 0x63, DamageState.Heavy }, + + // Ramp duplicates + { 0x7A, DamageState.Undamaged }, + { 0x7B, DamageState.Undamaged }, + { 0x7C, DamageState.Undamaged }, + { 0x7D, DamageState.Undamaged }, + + // Actually dead, placeholders for resurrection + { 0x64, DamageState.Undamaged }, + { 0x65, DamageState.Undamaged }, + }; + + protected override Dictionary ResourceFromOverlay { get; } = new() + { + // "tib" - Regular Tiberium + { + 0x01, new byte[] + { + 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79 + } + }, + + // "btib" - Blue Tiberium + { + 0x02, new byte[] + { + 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, + + // Should be "tib2" + 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, + + // Should be "tib3" + 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, + 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6 + } + }, + + // Veins + { 0x03, new byte[] { 0x7E } } + }; + + protected override Dictionary DeployableActors { get; } = new() + { + { "gadpsa", "lpst" }, + { "gatick", "ttnk" }, + { "gaarty", "art2" }, + { "djugg", "jugg" }, + + // Not yet implemented actors: + // { "gaicbm", "icbm" }, + // { "dlimpet", "limpet" }, + // { "dgweap", "mobwarg" }, + // { "dnweap", "mobwarn" }, + // { "mstl", "sgen" }, + // { "ddefd", "defender" }, + }; + + protected override string[] LampActors { get; } = + { + "GALITE", "INGALITE", "NEGLAMP", "REDLAMP", "NEGRED", "GRENLAMP", "BLUELAMP", "YELWLAMP", + "INYELWLAMP", "PURPLAMP", "INPURPLAMP", "INORANLAMP", "INGRNLMP", "INREDLMP", "INBLULMP" + }; + + protected override string[] CreepActors { get; } = { "DOGGIE", "VISC_SML", "VISC_LRG", "JFISH" }; + + #endregion + + #region Method overrides + + protected override bool TryHandleOverlayToActorInner(CPos cell, byte[] overlayPack, CellLayer overlayIndex, byte overlayType, out ActorReference actorReference) + { + actorReference = null; + if (!OverlayToActor.TryGetValue(overlayType, out var actorType)) + return false; + + // Just a dummy handler apparently (see "veinholedummy"). + if (string.IsNullOrEmpty(actorType)) + return true; + + if (OverlayShapes.TryGetValue(overlayType, out var shape)) + { + // Only import the top-left cell of multi-celled overlays + // Returning true here means this is a part of a bigger overlay that has already been handled. + var aboveType = overlayPack[overlayIndex[cell - new CVec(1, 0)]]; + if (shape.Width > 1 && aboveType != 0xFF) + if (OverlayToActor.TryGetValue(aboveType, out var a) && a == actorType) + return true; + + var leftType = overlayPack[overlayIndex[cell - new CVec(0, 1)]]; + if (shape.Height > 1 && leftType != 0xFF) + if (OverlayToActor.TryGetValue(leftType, out var a) && a == actorType) + return true; + } + + // Fix position of vein hole actors. + var location = cell; + if (actorType == "veinhole") + location -= new CVec(1, 1); + + actorReference = new ActorReference(actorType) + { + new LocationInit(location), + new OwnerInit("Neutral") + }; + + TryHandleOverlayToHealthInner(overlayType, actorReference); + + return true; + } + + protected override bool TryHandleOtherOverlayInner(Map map, CPos cell, byte[] overlayDataPack, CellLayer overlayIndex, byte overlayType) + { + // TS maps encode the non-harvestable border tiles as overlay + // Only convert to vein resources if the overlay data specifies non-border frames + if (overlayType == 0x7E) + { + var frame = overlayDataPack[overlayIndex[cell]]; + if (frame < 48 || frame > 60) + return true; + + // Pick half or full density based on the frame + map.Resources[cell] = new ResourceTile(3, (byte)(frame == 52 ? 1 : 2)); + return true; + } + + return false; + } + + #endregion + } +}