1 #region Copyright & License Information 2 /* 3 * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) 4 * This file is part of OpenRA, which is free software. It is made 5 * available to you under the terms of the GNU General Public License 6 * as published by the Free Software Foundation, either version 3 of 7 * the License, or (at your option) any later version. For more 8 * information, see COPYING. 9 */ 10 #endregion 11 12 using System; 13 using System.Collections.Generic; 14 using System.IO; 15 using System.Linq; 16 using OpenRA.Primitives; 17 18 namespace OpenRA.Mods.D2k.UtilityCommands 19 { 20 public class D2kMapImporter 21 { 22 const int MapCordonWidth = 2; 23 24 public static Dictionary<int, Pair<string, string>> ActorDataByActorCode = new Dictionary<int, Pair<string, string>> 25 { 26 { 20, Pair.New("wormspawner", "Creeps") }, 27 { 23, Pair.New("mpspawn", "Neutral") }, 28 { 41, Pair.New("spicebloom.spawnpoint", "Neutral") }, 29 { 42, Pair.New("spicebloom.spawnpoint", "Neutral") }, 30 { 43, Pair.New("spicebloom.spawnpoint", "Neutral") }, 31 { 44, Pair.New("spicebloom.spawnpoint", "Neutral") }, 32 { 45, Pair.New("spicebloom.spawnpoint", "Neutral") }, 33 34 // Atreides: 35 { 4, Pair.New("wall", "Atreides") }, 36 { 5, Pair.New("wind_trap", "Atreides") }, 37 { 8, Pair.New("construction_yard", "Atreides") }, 38 { 11, Pair.New("barracks", "Atreides") }, 39 { 14, Pair.New("refinery", "Atreides") }, 40 { 17, Pair.New("outpost", "Atreides") }, 41 { 63, Pair.New("light_factory", "Atreides") }, 42 { 69, Pair.New("silo", "Atreides") }, 43 { 72, Pair.New("heavy_factory", "Atreides") }, 44 { 75, Pair.New("repair_pad", "Atreides") }, 45 { 78, Pair.New("medium_gun_turret", "Atreides") }, 46 { 120, Pair.New("high_tech_factory", "Atreides") }, 47 { 123, Pair.New("large_gun_turret", "Atreides") }, 48 { 126, Pair.New("research_centre", "Atreides") }, 49 { 129, Pair.New("starport", "Atreides") }, 50 { 132, Pair.New("palace", "Atreides") }, 51 { 180, Pair.New("light_inf", "Atreides") }, 52 { 181, Pair.New("trooper", "Atreides") }, 53 { 182, Pair.New("fremen", "Atreides") }, 54 { 183, Pair.New("sardaukar", "Atreides") }, 55 { 184, Pair.New("engineer", "Atreides") }, 56 { 185, Pair.New("harvester", "Atreides") }, 57 { 186, Pair.New("mcv", "Atreides") }, 58 { 187, Pair.New("trike", "Atreides") }, 59 { 188, Pair.New("quad", "Atreides") }, 60 { 189, Pair.New("combat_tank_a", "Atreides") }, 61 { 190, Pair.New("missile_tank", "Atreides") }, 62 { 191, Pair.New("siege_tank", "Atreides") }, 63 { 192, Pair.New("carryall", "Atreides") }, 64 { 194, Pair.New("sonic_tank", "Atreides") }, 65 66 // Harkonnen: 67 { 204, Pair.New("wall", "Harkonnen") }, 68 { 205, Pair.New("wind_trap", "Harkonnen") }, 69 { 208, Pair.New("construction_yard", "Harkonnen") }, 70 { 211, Pair.New("barracks", "Harkonnen") }, 71 { 214, Pair.New("refinery", "Harkonnen") }, 72 { 217, Pair.New("outpost", "Harkonnen") }, 73 { 263, Pair.New("light_factory", "Harkonnen") }, 74 { 269, Pair.New("silo", "Harkonnen") }, 75 { 272, Pair.New("heavy_factory", "Harkonnen") }, 76 { 275, Pair.New("repair_pad", "Harkonnen") }, 77 { 278, Pair.New("medium_gun_turret", "Harkonnen") }, 78 { 320, Pair.New("high_tech_factory", "Harkonnen") }, 79 { 323, Pair.New("large_gun_turret", "Harkonnen") }, 80 { 326, Pair.New("research_centre", "Harkonnen") }, 81 { 329, Pair.New("starport", "Harkonnen") }, 82 { 332, Pair.New("palace", "Harkonnen") }, 83 { 360, Pair.New("light_inf", "Harkonnen") }, 84 { 361, Pair.New("trooper", "Harkonnen") }, 85 { 362, Pair.New("fremen", "Harkonnen") }, 86 { 363, Pair.New("mpsardaukar", "Harkonnen") }, 87 { 364, Pair.New("engineer", "Harkonnen") }, 88 { 365, Pair.New("harvester", "Harkonnen") }, 89 { 366, Pair.New("mcv", "Harkonnen") }, 90 { 367, Pair.New("trike", "Harkonnen") }, 91 { 368, Pair.New("quad", "Harkonnen") }, 92 { 369, Pair.New("combat_tank_h", "Harkonnen") }, 93 { 370, Pair.New("missile_tank", "Harkonnen") }, 94 { 371, Pair.New("siege_tank", "Harkonnen") }, 95 { 372, Pair.New("carryall", "Harkonnen") }, 96 { 374, Pair.New("devastator", "Harkonnen") }, 97 98 // Ordos: 99 { 404, Pair.New("wall", "Ordos") }, 100 { 405, Pair.New("wind_trap", "Ordos") }, 101 { 408, Pair.New("construction_yard", "Ordos") }, 102 { 411, Pair.New("barracks", "Ordos") }, 103 { 414, Pair.New("refinery", "Ordos") }, 104 { 417, Pair.New("outpost", "Ordos") }, 105 { 463, Pair.New("light_factory", "Ordos") }, 106 { 469, Pair.New("silo", "Ordos") }, 107 { 472, Pair.New("heavy_factory", "Ordos") }, 108 { 475, Pair.New("repair_pad", "Ordos") }, 109 { 478, Pair.New("medium_gun_turret", "Ordos") }, 110 { 520, Pair.New("high_tech_factory", "Ordos") }, 111 { 523, Pair.New("large_gun_turret", "Ordos") }, 112 { 526, Pair.New("research_centre", "Ordos") }, 113 { 529, Pair.New("starport", "Ordos") }, 114 { 532, Pair.New("palace", "Ordos") }, 115 { 560, Pair.New("light_inf", "Ordos") }, 116 { 561, Pair.New("trooper", "Ordos") }, 117 { 562, Pair.New("saboteur", "Ordos") }, 118 { 563, Pair.New("sardaukar", "Ordos") }, 119 { 564, Pair.New("engineer", "Ordos") }, 120 { 565, Pair.New("harvester", "Ordos") }, 121 { 566, Pair.New("mcv", "Ordos") }, 122 { 567, Pair.New("raider", "Ordos") }, 123 { 568, Pair.New("quad", "Ordos") }, 124 { 569, Pair.New("combat_tank_o", "Ordos") }, 125 { 570, Pair.New("missile_tank", "Ordos") }, 126 { 571, Pair.New("siege_tank", "Ordos") }, 127 { 572, Pair.New("carryall", "Ordos") }, 128 { 574, Pair.New("deviator", "Ordos") }, 129 130 // Corrino: 131 { 580, Pair.New("wall", "Corrino") }, 132 { 581, Pair.New("wind_trap", "Corrino") }, 133 { 582, Pair.New("construction_yard", "Corrino") }, 134 { 583, Pair.New("barracks", "Corrino") }, 135 { 584, Pair.New("refinery", "Corrino") }, 136 { 585, Pair.New("outpost", "Corrino") }, 137 { 587, Pair.New("light_factory", "Corrino") }, 138 { 588, Pair.New("palace", "Corrino") }, 139 { 589, Pair.New("silo", "Corrino") }, 140 { 590, Pair.New("heavy_factory", "Corrino") }, 141 { 591, Pair.New("repair_pad", "Corrino") }, 142 { 592, Pair.New("medium_gun_turret", "Corrino") }, 143 { 593, Pair.New("high_tech_factory", "Corrino") }, 144 { 594, Pair.New("large_gun_turret", "Corrino") }, 145 { 595, Pair.New("research_centre", "Corrino") }, 146 { 596, Pair.New("starport", "Corrino") }, 147 { 597, Pair.New("sietch", "Corrino") }, 148 { 598, Pair.New("light_inf", "Corrino") }, 149 { 599, Pair.New("trooper", "Corrino") }, 150 { 600, Pair.New("sardaukar", "Corrino") }, 151 { 601, Pair.New("fremen", "Corrino") }, 152 { 602, Pair.New("engineer", "Corrino") }, 153 { 603, Pair.New("harvester", "Corrino") }, 154 { 604, Pair.New("mcv", "Corrino") }, 155 { 605, Pair.New("trike", "Corrino") }, 156 { 606, Pair.New("quad", "Corrino") }, 157 { 607, Pair.New("combat_tank_h", "Corrino") }, 158 { 608, Pair.New("missile_tank", "Corrino") }, 159 { 609, Pair.New("siege_tank", "Corrino") }, 160 { 610, Pair.New("carryall", "Corrino") }, 161 162 // Fremen: 163 { 620, Pair.New("wall", "Fremen") }, 164 { 621, Pair.New("wind_trap", "Fremen") }, 165 { 622, Pair.New("construction_yard", "Fremen") }, 166 { 623, Pair.New("barracks", "Fremen") }, 167 { 624, Pair.New("refinery", "Fremen") }, 168 { 625, Pair.New("outpost", "Fremen") }, 169 { 627, Pair.New("light_factory", "Fremen") }, 170 { 628, Pair.New("palace", "Fremen") }, 171 { 629, Pair.New("silo", "Fremen") }, 172 { 630, Pair.New("heavy_factory", "Fremen") }, 173 { 631, Pair.New("repair_pad", "Fremen") }, 174 { 632, Pair.New("medium_gun_turret", "Fremen") }, 175 { 633, Pair.New("high_tech_factory", "Fremen") }, 176 { 634, Pair.New("large_gun_turret", "Fremen") }, 177 { 635, Pair.New("research_centre", "Fremen") }, 178 { 636, Pair.New("starport", "Fremen") }, 179 { 637, Pair.New("sietch", "Fremen") }, 180 { 638, Pair.New("light_inf", "Fremen") }, 181 { 639, Pair.New("trooper", "Fremen") }, 182 { 640, Pair.New("fremen", "Fremen") }, 183 { 641, Pair.New("nsfremen", "Fremen") }, 184 { 642, Pair.New("engineer", "Fremen") }, 185 { 643, Pair.New("harvester", "Fremen") }, 186 { 644, Pair.New("mcv", "Fremen") }, 187 { 645, Pair.New("trike", "Fremen") }, 188 { 646, Pair.New("quad", "Fremen") }, 189 { 647, Pair.New("combat_tank_a", "Fremen") }, 190 { 648, Pair.New("missile_tank", "Fremen") }, 191 { 649, Pair.New("siege_tank", "Fremen") }, 192 { 650, Pair.New("carryall", "Fremen") }, 193 { 652, Pair.New("sonic_tank", "Fremen") }, 194 195 // Smugglers: 196 { 660, Pair.New("wall", "Smugglers") }, 197 { 661, Pair.New("wind_trap", "Smugglers") }, 198 { 662, Pair.New("construction_yard", "Smugglers") }, 199 { 663, Pair.New("barracks", "Smugglers") }, 200 { 664, Pair.New("refinery", "Smugglers") }, 201 { 666, Pair.New("outpost", "Smugglers") }, 202 { 667, Pair.New("light_factory", "Smugglers") }, 203 { 668, Pair.New("silo", "Smugglers") }, 204 { 669, Pair.New("heavy_factory", "Smugglers") }, 205 { 670, Pair.New("repair_pad", "Smugglers") }, 206 { 671, Pair.New("medium_gun_turret", "Smugglers") }, 207 { 672, Pair.New("high_tech_factory", "Smugglers") }, 208 { 673, Pair.New("large_gun_turret", "Smugglers") }, 209 { 674, Pair.New("research_centre", "Smugglers") }, 210 { 675, Pair.New("starport", "Smugglers") }, 211 { 676, Pair.New("palace", "Smugglers") }, 212 { 677, Pair.New("light_inf", "Smugglers") }, 213 { 678, Pair.New("trooper", "Smugglers") }, 214 { 679, Pair.New("saboteur", "Smugglers") }, 215 { 680, Pair.New("engineer", "Smugglers") }, 216 { 681, Pair.New("harvester", "Smugglers") }, 217 { 682, Pair.New("mcv", "Smugglers") }, 218 { 683, Pair.New("trike", "Smugglers") }, 219 { 684, Pair.New("quad", "Smugglers") }, 220 { 685, Pair.New("combat_tank_o", "Smugglers") }, 221 { 686, Pair.New("missile_tank", "Smugglers") }, 222 { 687, Pair.New("siege_tank", "Smugglers") }, 223 { 688, Pair.New("carryall", "Smugglers") }, 224 225 // Mercenaries: 226 { 700, Pair.New("wall", "Mercenaries") }, 227 { 701, Pair.New("wind_trap", "Mercenaries") }, 228 { 702, Pair.New("construction_yard", "Mercenaries") }, 229 { 703, Pair.New("barracks", "Mercenaries") }, 230 { 704, Pair.New("refinery", "Mercenaries") }, 231 { 705, Pair.New("outpost", "Mercenaries") }, 232 { 707, Pair.New("light_factory", "Mercenaries") }, 233 { 708, Pair.New("silo", "Mercenaries") }, 234 { 709, Pair.New("heavy_factory", "Mercenaries") }, 235 { 710, Pair.New("repair_pad", "Mercenaries") }, 236 { 711, Pair.New("medium_gun_turret", "Mercenaries") }, 237 { 712, Pair.New("high_tech_factory", "Mercenaries") }, 238 { 713, Pair.New("large_gun_turret", "Mercenaries") }, 239 { 714, Pair.New("research_centre", "Mercenaries") }, 240 { 715, Pair.New("starport", "Mercenaries") }, 241 { 716, Pair.New("palace", "Mercenaries") }, 242 { 717, Pair.New("light_inf", "Mercenaries") }, 243 { 718, Pair.New("trooper", "Mercenaries") }, 244 { 719, Pair.New("saboteur", "Mercenaries") }, 245 { 720, Pair.New("harvester", "Mercenaries") }, 246 { 721, Pair.New("harvester", "Mercenaries") }, 247 { 722, Pair.New("mcv", "Mercenaries") }, 248 { 723, Pair.New("trike", "Mercenaries") }, 249 { 724, Pair.New("quad", "Mercenaries") }, 250 { 725, Pair.New("combat_tank_o", "Mercenaries") }, 251 { 726, Pair.New("missile_tank", "Mercenaries") }, 252 { 727, Pair.New("siege_tank", "Mercenaries") }, 253 { 728, Pair.New("carryall", "Mercenaries") }, 254 }; 255 256 readonly Ruleset rules; 257 readonly FileStream stream; 258 readonly string tilesetName; 259 readonly TerrainTile clearTile; 260 261 Map map; 262 Size mapSize; 263 TileSet tileSet; 264 List<TerrainTemplateInfo> tileSetsFromYaml; 265 int playerCount; 266 D2kMapImporter(string filename, string tileset, Ruleset rules)267 D2kMapImporter(string filename, string tileset, Ruleset rules) 268 { 269 tilesetName = tileset; 270 this.rules = rules; 271 272 try 273 { 274 clearTile = new TerrainTile(0, 0); 275 stream = File.OpenRead(filename); 276 277 if (stream.Length == 0 || stream.Length % 4 != 0) 278 throw new ArgumentException("The map is in an unrecognized format!", "filename"); 279 280 Initialize(filename); 281 FillMap(); 282 } 283 catch (Exception e) 284 { 285 Console.WriteLine(e); 286 map = null; 287 } 288 finally 289 { 290 stream.Close(); 291 } 292 } 293 Import(string filename, string mod, string tileset, Ruleset rules)294 public static Map Import(string filename, string mod, string tileset, Ruleset rules) 295 { 296 var importer = new D2kMapImporter(filename, tileset, rules); 297 var map = importer.map; 298 if (map == null) 299 return null; 300 301 map.RequiresMod = mod; 302 303 return map; 304 } 305 Initialize(string mapFile)306 void Initialize(string mapFile) 307 { 308 mapSize = new Size(stream.ReadUInt16(), stream.ReadUInt16()); 309 310 tileSet = Game.ModData.DefaultTileSets["ARRAKIS"]; 311 312 map = new Map(Game.ModData, tileSet, mapSize.Width + 2 * MapCordonWidth, mapSize.Height + 2 * MapCordonWidth) 313 { 314 Title = Path.GetFileNameWithoutExtension(mapFile), 315 Author = "Westwood Studios" 316 }; 317 318 var tl = new PPos(MapCordonWidth, MapCordonWidth); 319 var br = new PPos(MapCordonWidth + mapSize.Width - 1, MapCordonWidth + mapSize.Height - 1); 320 map.SetBounds(tl, br); 321 322 // Get all templates from the tileset YAML file that have at least one frame and an Image property corresponding to the requested tileset 323 // 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 324 tileSetsFromYaml = tileSet.Templates.Where(t => t.Value.Frames != null 325 && t.Value.Images[0].ToLower() == tilesetName.ToLower()).Select(ts => ts.Value).ToList(); 326 327 var players = new MapPlayers(map.Rules, playerCount); 328 map.PlayerDefinitions = players.ToMiniYaml(); 329 } 330 FillMap()331 void FillMap() 332 { 333 while (stream.Position < stream.Length) 334 { 335 var tileInfo = stream.ReadUInt16(); 336 var tileSpecialInfo = stream.ReadUInt16(); 337 var tile = GetTile(tileInfo); 338 339 var locationOnMap = GetCurrentTilePositionOnMap(); 340 341 map.Tiles[locationOnMap] = tile; 342 343 // Spice 344 if (tileSpecialInfo == 1) 345 map.Resources[locationOnMap] = new ResourceTile(1, 1); 346 if (tileSpecialInfo == 2) 347 map.Resources[locationOnMap] = new ResourceTile(1, 2); 348 349 // Actors 350 if (ActorDataByActorCode.ContainsKey(tileSpecialInfo)) 351 { 352 var kvp = ActorDataByActorCode[tileSpecialInfo]; 353 if (!rules.Actors.ContainsKey(kvp.First.ToLower())) 354 throw new InvalidOperationException("Actor with name {0} could not be found in the rules YAML file!".F(kvp.First)); 355 356 var a = new ActorReference(kvp.First) 357 { 358 new LocationInit(locationOnMap), 359 new OwnerInit(kvp.Second) 360 }; 361 362 map.ActorDefinitions.Add(new MiniYamlNode("Actor" + map.ActorDefinitions.Count, a.Save())); 363 364 if (kvp.First == "mpspawn") 365 playerCount++; 366 } 367 } 368 } 369 GetCurrentTilePositionOnMap()370 CPos GetCurrentTilePositionOnMap() 371 { 372 var tileIndex = (int)stream.Position / 4 - 2; 373 374 var x = (tileIndex % mapSize.Width) + MapCordonWidth; 375 var y = (tileIndex / mapSize.Width) + MapCordonWidth; 376 377 return new CPos(x, y); 378 } 379 GetTile(int tileIndex)380 TerrainTile GetTile(int tileIndex) 381 { 382 // Some tiles are duplicates of other tiles, just on a different tileset 383 if (tilesetName.ToLower() == "bloxbgbs.r8") 384 { 385 if (tileIndex == 355) 386 return new TerrainTile(441, 0); 387 388 if (tileIndex == 375) 389 return new TerrainTile(442, 0); 390 } 391 392 if (tilesetName.ToLower() == "bloxtree.r8") 393 { 394 var indices = new[] { 683, 684, 685, 706, 703, 704, 705, 726, 723, 724, 725, 746, 743, 744, 745, 747 }; 395 for (var i = 0; i < 16; i++) 396 if (tileIndex == indices[i]) 397 return new TerrainTile(474, (byte)i); 398 399 indices = new[] { 369, 370, 389, 390 }; 400 for (var i = 0; i < 4; i++) 401 if (tileIndex == indices[i]) 402 return new TerrainTile(117, (byte)i); 403 404 indices = new[] { 661, 662, 681, 682 }; 405 for (var i = 0; i < 4; i++) 406 if (tileIndex == indices[i]) 407 return new TerrainTile(251, (byte)i); 408 409 if (tileIndex == 322) 410 return new TerrainTile(215, 0); 411 } 412 413 if (tilesetName.ToLower() == "bloxwast.r8") 414 { 415 if (tileIndex == 342) 416 return new TerrainTile(250, 0); 417 418 if (tileIndex == 383) 419 return new TerrainTile(121, 1); 420 421 if (tileIndex == 384) 422 return new TerrainTile(1046, 0); 423 424 if (tileIndex == 579) 425 return new TerrainTile(80, 0); 426 427 if (tileIndex == 597) 428 return new TerrainTile(80, 0); 429 430 if (tileIndex == 598) 431 return new TerrainTile(470, 0); 432 433 if (tileIndex == 599) 434 return new TerrainTile(470, 1); 435 436 if (tileIndex == 608) 437 return new TerrainTile(58, 0); 438 439 if (tileIndex == 627) 440 return new TerrainTile(248, 0); 441 442 if (tileIndex == 628) 443 return new TerrainTile(248, 1); 444 445 if (tileIndex == 719) 446 return new TerrainTile(275, 0); 447 448 var indices = new[] { 340, 341, 360, 361 }; 449 for (var i = 0; i < 4; i++) 450 if (tileIndex == indices[i]) 451 return new TerrainTile(308, (byte)i); 452 453 indices = new[] { 660, 661, 662, 680, 681, 682 }; 454 for (var i = 0; i < 6; i++) 455 if (tileIndex == indices[i]) 456 return new TerrainTile(443, (byte)i); 457 458 indices = new[] { 609, 610, 629, 630 }; 459 for (var i = 0; i < 4; i++) 460 if (tileIndex == indices[i]) 461 return new TerrainTile(251, (byte)i); 462 } 463 464 // Get the first tileset template that contains the Frame ID of the original map's tile with the requested index 465 var template = tileSetsFromYaml.FirstOrDefault(x => x.Frames.Contains(tileIndex)); 466 467 // HACK: The arrakis.yaml tileset file seems to be missing some tiles, so just get a replacement for them 468 // Also used for duplicate tiles that are taken from only tileset 469 if (template == null) 470 { 471 // Just get a template that contains a tile with the same ID as requested 472 var templates = tileSet.Templates.Where(t => t.Value.Frames != null && t.Value.Frames.Contains(tileIndex)); 473 if (templates.Any()) 474 template = templates.First().Value; 475 } 476 477 if (template == null) 478 { 479 var pos = GetCurrentTilePositionOnMap(); 480 Console.WriteLine("Tile with index {0} could not be found in the tileset YAML file!".F(tileIndex)); 481 Console.WriteLine("Defaulting to a \"clear\" tile for coordinates ({0}, {1})!".F(pos.X, pos.Y)); 482 return clearTile; 483 } 484 485 var templateIndex = template.Id; 486 var frameIndex = Array.IndexOf(template.Frames, tileIndex); 487 488 return new TerrainTile(templateIndex, (byte)((frameIndex == -1) ? 0 : frameIndex)); 489 } 490 } 491 } 492