diff --git a/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs index cb680c057e..10b33452cf 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs @@ -24,8 +24,10 @@ namespace OpenRA.Mods.Common.UtilityCommands Game.ModData = modData; var rules = Game.ModData.RulesetCache.LoadDefaultRules(); - var map = LegacyMapImporter.Import(args[1], modData.Manifest.Mod.Id, rules, e => Console.WriteLine(e)); - var dest = Path.ChangeExtension(args[1], "oramap"); + var map = LegacyMapImporter.Import(args[1], modData.Manifest.Mod.Id, rules, Console.WriteLine); + + var fileName = Path.GetFileNameWithoutExtension(args[1]); + var dest = fileName + ".oramap"; map.Save(dest); Console.WriteLine(dest + " saved."); } diff --git a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj index 74756d80ee..da83a159d9 100644 --- a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj +++ b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj @@ -95,6 +95,8 @@ + + diff --git a/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs new file mode 100644 index 0000000000..61089da3a6 --- /dev/null +++ b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs @@ -0,0 +1,404 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using OpenRA.Primitives; + +namespace OpenRA.Mods.D2k.UtilityCommands +{ + class D2kMapImporter + { + const int MapCordonWidth = 2; + + readonly Dictionary> actorDataByActorCode = new Dictionary> + { + { 20, Pair.New("wormspawner", "Creeps") }, + { 23, Pair.New("mpspawn", "Neutral") }, + { 41, Pair.New("spicebloom", "Neutral") }, + { 42, Pair.New("spicebloom", "Neutral") }, + { 43, Pair.New("spicebloom", "Neutral") }, + { 44, Pair.New("spicebloom", "Neutral") }, + { 45, Pair.New("spicebloom", "Neutral") }, + + // Atreides: + { 4, Pair.New("WALLA", "Atreides") }, + { 5, Pair.New("PWRA", "Atreides") }, + { 8, Pair.New("CONYARDA", "Atreides") }, + { 11, Pair.New("BARRA", "Atreides") }, + { 14, Pair.New("REFA", "Atreides") }, + { 17, Pair.New("RADARA", "Atreides") }, + { 63, Pair.New("LIGHTA", "Atreides") }, + { 69, Pair.New("SILOA", "Atreides") }, + { 72, Pair.New("HEAVYA", "Atreides") }, + { 75, Pair.New("REPAIRA", "Atreides") }, + { 78, Pair.New("GUNTOWERA", "Atreides") }, + { 120, Pair.New("HIGHTECHA", "Atreides") }, + { 123, Pair.New("ROCKETTOWERA", "Atreides") }, + { 126, Pair.New("RESEARCHA", "Atreides") }, + { 129, Pair.New("STARPORTA", "Atreides") }, + { 132, Pair.New("PALACEA", "Atreides") }, + { 180, Pair.New("RIFLE", "Atreides") }, + { 181, Pair.New("BAZOOKA", "Atreides") }, + { 182, Pair.New("FREMEN", "Atreides") }, + { 183, Pair.New("SARDAUKAR", "Atreides") }, + { 184, Pair.New("ENGINEER", "Atreides") }, + { 185, Pair.New("HARVESTER", "Atreides") }, + { 186, Pair.New("MCVA", "Atreides") }, + { 187, Pair.New("TRIKE", "Atreides") }, + { 188, Pair.New("QUAD", "Atreides") }, + { 189, Pair.New("COMBATA", "Atreides") }, + { 190, Pair.New("MISSILETANK", "Atreides") }, + { 191, Pair.New("SIEGETANK", "Atreides") }, + { 192, Pair.New("CARRYALLA", "Atreides") }, + { 194, Pair.New("SONICTANK", "Atreides") }, + + // Harkonnen: + { 204, Pair.New("WALLH", "Harkonnen") }, + { 205, Pair.New("PWRH", "Harkonnen") }, + { 208, Pair.New("CONYARDH", "Harkonnen") }, + { 211, Pair.New("BARRH", "Harkonnen") }, + { 214, Pair.New("REFH", "Harkonnen") }, + { 217, Pair.New("RADARH", "Harkonnen") }, + { 263, Pair.New("LIGHTH", "Harkonnen") }, + { 269, Pair.New("SILOH", "Harkonnen") }, + { 272, Pair.New("HEAVYH", "Harkonnen") }, + { 275, Pair.New("REPAIRH", "Harkonnen") }, + { 278, Pair.New("GUNTOWERH", "Harkonnen") }, + { 320, Pair.New("HIGHTECHH", "Harkonnen") }, + { 323, Pair.New("ROCKETTOWERH", "Harkonnen") }, + { 326, Pair.New("RESEARCHH", "Harkonnen") }, + { 329, Pair.New("STARPORTH", "Harkonnen") }, + { 332, Pair.New("PALACEH", "Harkonnen") }, + { 360, Pair.New("RIFLE", "Harkonnen") }, + { 361, Pair.New("BAZOOKA", "Harkonnen") }, + { 362, Pair.New("FREMEN", "Harkonnen") }, + { 363, Pair.New("SARDAUKAR", "Harkonnen") }, + { 364, Pair.New("ENGINEER", "Harkonnen") }, + { 365, Pair.New("HARVESTER", "Harkonnen") }, + { 366, Pair.New("MCVH", "Harkonnen") }, + { 367, Pair.New("TRIKE", "Harkonnen") }, + { 368, Pair.New("QUAD", "Harkonnen") }, + { 369, Pair.New("COMBATH", "Harkonnen") }, + { 370, Pair.New("MISSILETANK", "Harkonnen") }, + { 371, Pair.New("SIEGETANK", "Harkonnen") }, + { 372, Pair.New("CARRYALLH", "Harkonnen") }, + { 374, Pair.New("DEVAST", "Harkonnen") }, + + // Ordos: + { 404, Pair.New("WALLO", "Ordos") }, + { 405, Pair.New("PWRO", "Ordos") }, + { 408, Pair.New("CONYARDO", "Ordos") }, + { 411, Pair.New("BARRO", "Ordos") }, + { 414, Pair.New("REFO", "Ordos") }, + { 417, Pair.New("RADARO", "Ordos") }, + { 463, Pair.New("LIGHTO", "Ordos") }, + { 469, Pair.New("SILOO", "Ordos") }, + { 472, Pair.New("HEAVYO", "Ordos") }, + { 475, Pair.New("REPAIRO", "Ordos") }, + { 478, Pair.New("GUNTOWERO", "Ordos") }, + { 520, Pair.New("HIGHTECHO", "Ordos") }, + { 523, Pair.New("ROCKETTOWERO", "Ordos") }, + { 526, Pair.New("RESEARCHO", "Ordos") }, + { 529, Pair.New("STARPORTO", "Ordos") }, + { 532, Pair.New("PALACEO", "Ordos") }, + { 560, Pair.New("RIFLE", "Ordos") }, + { 561, Pair.New("BAZOOKA", "Ordos") }, + { 562, Pair.New("SABOTEUR", "Ordos") }, + { 563, Pair.New("SARDAUKAR", "Ordos") }, + { 564, Pair.New("ENGINEER", "Ordos") }, + { 565, Pair.New("HARVESTER", "Ordos") }, + { 566, Pair.New("MCVO", "Ordos") }, + { 567, Pair.New("RAIDER", "Ordos") }, + { 568, Pair.New("QUAD", "Ordos") }, + { 569, Pair.New("COMBATO", "Ordos") }, + { 570, Pair.New("MISSILETANK", "Ordos") }, + { 571, Pair.New("SIEGETANK", "Ordos") }, + { 572, Pair.New("CARRYALLO", "Ordos") }, + { 574, Pair.New("DEVIATORTANK", "Ordos") }, + + // Corrino: + { 580, Pair.New("WALLH", "Corrino") }, + { 581, Pair.New("PWRH", "Corrino") }, + { 582, Pair.New("CONYARDC", "Corrino") }, + { 583, Pair.New("BARRH", "Corrino") }, + { 584, Pair.New("REFH", "Corrino") }, + { 585, Pair.New("RADARH", "Corrino") }, + { 587, Pair.New("LIGHTH", "Corrino") }, + { 588, Pair.New("PALACEC", "Corrino") }, + { 589, Pair.New("SILOH", "Corrino") }, + { 590, Pair.New("HEAVYC", "Corrino") }, + { 591, Pair.New("REPAIRH", "Corrino") }, + { 592, Pair.New("GUNTOWERH", "Corrino") }, + { 593, Pair.New("HIGHTECHH", "Corrino") }, + { 594, Pair.New("ROCKETTOWERH", "Corrino") }, + { 595, Pair.New("RESEARCHH", "Corrino") }, + { 596, Pair.New("STARPORTC", "Corrino") }, + { 597, Pair.New("SIETCH", "Corrino") }, + { 598, Pair.New("RIFLE", "Corrino") }, + { 599, Pair.New("BAZOOKA", "Corrino") }, + { 600, Pair.New("SARDAUKAR", "Corrino") }, + { 601, Pair.New("FREMEN", "Corrino") }, + { 602, Pair.New("ENGINEER", "Corrino") }, + { 603, Pair.New("HARVESTER", "Corrino") }, + { 604, Pair.New("MCVH", "Corrino") }, + { 605, Pair.New("TRIKE", "Corrino") }, + { 606, Pair.New("QUAD", "Corrino") }, + { 607, Pair.New("COMBATH", "Corrino") }, + { 608, Pair.New("MISSILETANK", "Corrino") }, + { 609, Pair.New("SIEGETANK", "Corrino") }, + { 610, Pair.New("CARRYALLH", "Corrino") }, + + // Fremen: + { 620, Pair.New("WALLA", "Fremen") }, + { 621, Pair.New("PWRA", "Fremen") }, + { 622, Pair.New("CONYARDA", "Fremen") }, + { 623, Pair.New("BARRA", "Fremen") }, + { 624, Pair.New("REFA", "Fremen") }, + { 625, Pair.New("RADARA", "Fremen") }, + { 627, Pair.New("LIGHTA", "Fremen") }, + { 628, Pair.New("PALACEC", "Fremen") }, + { 629, Pair.New("SILOA", "Fremen") }, + { 630, Pair.New("HEAVYA", "Fremen") }, + { 631, Pair.New("REPAIRA", "Fremen") }, + { 632, Pair.New("GUNTOWERA", "Fremen") }, + { 633, Pair.New("HIGHTECHA", "Fremen") }, + { 634, Pair.New("ROCKETTOWERA", "Fremen") }, + { 635, Pair.New("RESEARCHA", "Fremen") }, + { 636, Pair.New("STARPORTA", "Fremen") }, + { 637, Pair.New("SIETCH", "Fremen") }, + { 638, Pair.New("RIFLE", "Fremen") }, + { 639, Pair.New("BAZOOKA", "Fremen") }, + { 640, Pair.New("FREMEN", "Fremen") }, + ////{ 641, Pair.New("", "Fremen") },// Fremen fremen non-stealth + { 642, Pair.New("ENGINEER", "Fremen") }, + { 643, Pair.New("HARVESTER", "Fremen") }, + { 644, Pair.New("MCVA", "Fremen") }, + { 645, Pair.New("TRIKE", "Fremen") }, + { 646, Pair.New("QUAD", "Fremen") }, + { 647, Pair.New("COMBATA", "Fremen") }, + { 648, Pair.New("MISSILETANK", "Fremen") }, + { 649, Pair.New("SIEGETANK", "Fremen") }, + { 650, Pair.New("CARRYALLA", "Fremen") }, + { 652, Pair.New("SONICTANK", "Fremen") }, + + // Smugglers: + { 660, Pair.New("WALLO", "Smugglers") }, + { 661, Pair.New("PWRO", "Smugglers") }, + { 662, Pair.New("CONYARDO", "Smugglers") }, + { 663, Pair.New("BARRO", "Smugglers") }, + { 664, Pair.New("REFO", "Smugglers") }, + { 666, Pair.New("RADARO", "Smugglers") }, + { 667, Pair.New("LIGHTO", "Smugglers") }, + { 668, Pair.New("SILOO", "Smugglers") }, + { 669, Pair.New("HEAVYO", "Smugglers") }, + { 670, Pair.New("REPAIRO", "Smugglers") }, + { 671, Pair.New("GUNTOWERO", "Smugglers") }, + { 672, Pair.New("HIGHTECHO", "Smugglers") }, + { 673, Pair.New("ROCKETTOWERO", "Smugglers") }, + { 674, Pair.New("RESEARCHO", "Smugglers") }, + { 675, Pair.New("STARPORTO", "Smugglers") }, + { 676, Pair.New("PALACEO", "Smugglers") }, + { 677, Pair.New("RIFLE", "Smugglers") }, + { 678, Pair.New("BAZOOKA", "Smugglers") }, + { 679, Pair.New("SABOTEUR", "Smugglers") }, + { 680, Pair.New("ENGINEER", "Smugglers") }, + { 681, Pair.New("HARVESTER", "Smugglers") }, + { 682, Pair.New("MCVO", "Smugglers") }, + { 683, Pair.New("TRIKE", "Smugglers") }, + { 684, Pair.New("QUAD", "Smugglers") }, + { 685, Pair.New("COMBATO", "Smugglers") }, + { 686, Pair.New("MISSILETANK", "Smugglers") }, + { 687, Pair.New("SIEGETANK", "Smugglers") }, + { 688, Pair.New("CARRYALLO", "Smugglers") }, + + // Mercenaries: + { 700, Pair.New("WALLO", "Mercenaries") }, + { 701, Pair.New("PWRO", "Mercenaries") }, + { 702, Pair.New("CONYARDO", "Mercenaries") }, + { 703, Pair.New("BARRO", "Mercenaries") }, + { 704, Pair.New("REFO", "Mercenaries") }, + { 705, Pair.New("RADARO", "Mercenaries") }, + { 707, Pair.New("LIGHTO", "Mercenaries") }, + { 708, Pair.New("SILOO", "Mercenaries") }, + { 709, Pair.New("HEAVYO", "Mercenaries") }, + { 710, Pair.New("REPAIRO", "Mercenaries") }, + { 711, Pair.New("GUNTOWERO", "Mercenaries") }, + { 712, Pair.New("HIGHTECHO", "Mercenaries") }, + { 713, Pair.New("ROCKETTOWERO", "Mercenaries") }, + { 714, Pair.New("RESEARCHO", "Mercenaries") }, + { 715, Pair.New("STARPORTO", "Mercenaries") }, + { 716, Pair.New("PALACEO", "Mercenaries") }, + { 717, Pair.New("RIFLE", "Mercenaries") }, + { 718, Pair.New("BAZOOKA", "Mercenaries") }, + { 719, Pair.New("SABOTEUR", "Mercenaries") }, + { 720, Pair.New("HARVESTER", "Mercenaries") }, + { 721, Pair.New("HARVESTER", "Mercenaries") }, + { 722, Pair.New("MCVO", "Mercenaries") }, + { 723, Pair.New("TRIKE", "Mercenaries") }, + { 724, Pair.New("QUAD", "Mercenaries") }, + { 725, Pair.New("COMBATO", "Mercenaries") }, + { 726, Pair.New("MISSILETANK", "Mercenaries") }, + { 727, Pair.New("SIEGETANK", "Mercenaries") }, + { 728, Pair.New("CARRYALLO", "Mercenaries") }, + }; + + readonly Ruleset rules; + readonly FileStream stream; + readonly string tilesetName; + readonly TerrainTile clearTile; + + Map map; + Size mapSize; + TileSet tileSet; + List tileSetsFromYaml; + + D2kMapImporter(string filename, string tileset, Ruleset rules) + { + tilesetName = tileset; + this.rules = rules; + + try + { + clearTile = new TerrainTile(0, 0); + stream = File.OpenRead(filename); + + if (stream.Length == 0 || stream.Length % 4 != 0) + throw new Exception("The map is in an unrecognized format!"); + + Initialize(filename); + FillMap(); + } + catch (Exception e) + { + Console.WriteLine(e); + map = null; + } + finally + { + stream.Close(); + } + } + + public static Map Import(string filename, string mod, string tileset, Ruleset rules) + { + var map = new D2kMapImporter(filename, tileset, rules).map; + if (map == null) + return null; + + map.RequiresMod = mod; + map.MakeDefaultPlayers(); + + return map; + } + + void Initialize(string mapFile) + { + mapSize = new Size(stream.ReadUInt16(), stream.ReadUInt16()); + + tileSet = rules.TileSets["ARRAKIS"]; + map = Map.FromTileset(tileSet); + map.Title = Path.GetFileNameWithoutExtension(mapFile); + map.Author = "Westwood Studios"; + map.MapSize.X = mapSize.Width + 2 * MapCordonWidth; + map.MapSize.Y = mapSize.Height + 2 * MapCordonWidth; + map.Bounds = new Rectangle(MapCordonWidth, MapCordonWidth, mapSize.Width, mapSize.Height); + + map.Smudges = Exts.Lazy(() => new List()); + map.Actors = Exts.Lazy(() => new Dictionary()); + map.MapResources = Exts.Lazy(() => new CellLayer(TileShape.Rectangle, new Size(map.MapSize.X, map.MapSize.Y))); + map.MapTiles = Exts.Lazy(() => new CellLayer(TileShape.Rectangle, new Size(map.MapSize.X, map.MapSize.Y))); + + map.Options = new MapOptions(); + + // 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 = tileSet.Templates.Where(t => t.Value.Frames != null + && t.Value.Image.ToLower() == tilesetName.ToLower()).Select(ts => ts.Value).ToList(); + } + + void FillMap() + { + while (stream.Position < stream.Length) + { + var tileInfo = stream.ReadUInt16(); + var tileSpecialInfo = stream.ReadUInt16(); + var tile = GetTile(tileInfo); + + var locationOnMap = GetCurrentTilePositionOnMap(); + + map.MapTiles.Value[locationOnMap] = tile; + + // Spice + if (tileSpecialInfo == 1) + map.MapResources.Value[locationOnMap] = new ResourceTile(1, 1); + if (tileSpecialInfo == 2) + map.MapResources.Value[locationOnMap] = new ResourceTile(1, 2); + + // Actors + if (actorDataByActorCode.ContainsKey(tileSpecialInfo)) + { + var kvp = actorDataByActorCode[tileSpecialInfo]; + if (!rules.Actors.ContainsKey(kvp.First.ToLower())) + throw new InvalidOperationException("Actor with name {0} could not be found in the rules YAML file!".F(kvp.First)); + + var a = new ActorReference(kvp.First) + { + new LocationInit(locationOnMap), + new OwnerInit(kvp.Second) + }; + map.Actors.Value.Add("Actor" + map.Actors.Value.Count, a); + } + } + } + + CPos GetCurrentTilePositionOnMap() + { + var tileIndex = (int)stream.Position / 4 - 2; + + var x = (tileIndex % mapSize.Width) + MapCordonWidth; + var y = (tileIndex / mapSize.Width) + MapCordonWidth; + + return new CPos(x, y); + } + + TerrainTile GetTile(int tileIndex) + { + // 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)); + + // 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 = tileSet.Templates.Where(t => t.Value.Frames != null && t.Value.Frames.Contains(tileIndex)); + if (templates.Any()) + template = templates.First().Value; + } + + if (template == null) + { + var pos = GetCurrentTilePositionOnMap(); + Console.WriteLine("Tile with index {0} could not be found in the tileset YAML file!".F(tileIndex)); + Console.WriteLine("Defaulting to a \"clear\" tile for coordinates ({0}, {1})!".F(pos.X, pos.Y)); + return clearTile; + } + + var templateIndex = template.Id; + var frameIndex = Array.IndexOf(template.Frames, tileIndex); + + return new TerrainTile(templateIndex, (byte)((frameIndex == -1) ? 0 : frameIndex)); + } + } +} diff --git a/OpenRA.Mods.D2k/UtilityCommands/ImportD2kMapCommand.cs b/OpenRA.Mods.D2k/UtilityCommands/ImportD2kMapCommand.cs new file mode 100644 index 0000000000..d6046dd9f4 --- /dev/null +++ b/OpenRA.Mods.D2k/UtilityCommands/ImportD2kMapCommand.cs @@ -0,0 +1,40 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.IO; +using System.Linq; + +namespace OpenRA.Mods.D2k.UtilityCommands +{ + class ImportD2kMapCommand : IUtilityCommand + { + public string Name { get { return "--import-d2k-map"; } } + + [Desc("FILENAME", "TILESET", "Convert a legacy Dune 2000 MAP file to the OpenRA format.")] + public void Run(ModData modData, string[] args) + { + // HACK: The engine code assumes that Game.modData is set. + Game.ModData = modData; + + var rules = Game.ModData.RulesetCache.LoadDefaultRules(); + + var map = D2kMapImporter.Import(args[1], modData.Manifest.Mod.Id, args[2], rules); + + if (map == null) + return; + + var fileName = Path.GetFileNameWithoutExtension(args[1]); + var dest = fileName + ".oramap"; + map.Save(dest); + Console.WriteLine(dest + " saved."); + } + } +}