Files
OpenRA/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs
Paul Chote e8794032e0 Introduce initial PPos plumbing.
PPos is best thought of as a cell grid applied in
screen space.  Multiple cells with different
terrain heights may be projected to the same PPos,
or to multiple PPos if they do not align with the
screen grid.

PPos coordinates are used primarily for map edge
checks and shroud / visibility queries.
2015-07-27 19:34:49 +01:00

403 lines
15 KiB
C#

#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<int, Pair<string, string>> actorDataByActorCode = new Dictionary<int, Pair<string, string>>
{
{ 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("wall", "Atreides") },
{ 5, Pair.New("power", "Atreides") },
{ 8, Pair.New("conyard", "Atreides") },
{ 11, Pair.New("barracks", "Atreides") },
{ 14, Pair.New("refinery", "Atreides") },
{ 17, Pair.New("radar", "Atreides") },
{ 63, Pair.New("light", "Atreides") },
{ 69, Pair.New("silo", "Atreides") },
{ 72, Pair.New("heavy", "Atreides") },
{ 75, Pair.New("repair", "Atreides") },
{ 78, Pair.New("guntower", "Atreides") },
{ 120, Pair.New("hightech", "Atreides") },
{ 123, Pair.New("rockettower", "Atreides") },
{ 126, Pair.New("research", "Atreides") },
{ 129, Pair.New("starport", "Atreides") },
{ 132, Pair.New("palace", "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("mcv", "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("carryall", "Atreides") },
{ 194, Pair.New("sonictank", "Atreides") },
// Harkonnen:
{ 204, Pair.New("wall", "Harkonnen") },
{ 205, Pair.New("power", "Harkonnen") },
{ 208, Pair.New("conyard", "Harkonnen") },
{ 211, Pair.New("barracks", "Harkonnen") },
{ 214, Pair.New("refinery", "Harkonnen") },
{ 217, Pair.New("radar", "Harkonnen") },
{ 263, Pair.New("light", "Harkonnen") },
{ 269, Pair.New("silo", "Harkonnen") },
{ 272, Pair.New("heavy", "Harkonnen") },
{ 275, Pair.New("repair", "Harkonnen") },
{ 278, Pair.New("guntower", "Harkonnen") },
{ 320, Pair.New("hightech", "Harkonnen") },
{ 323, Pair.New("rockettower", "Harkonnen") },
{ 326, Pair.New("research", "Harkonnen") },
{ 329, Pair.New("starport", "Harkonnen") },
{ 332, Pair.New("palace", "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("mcv", "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("carryall", "Harkonnen") },
{ 374, Pair.New("devast", "Harkonnen") },
// Ordos:
{ 404, Pair.New("wall", "Ordos") },
{ 405, Pair.New("power", "Ordos") },
{ 408, Pair.New("conyard", "Ordos") },
{ 411, Pair.New("barracks", "Ordos") },
{ 414, Pair.New("refinery", "Ordos") },
{ 417, Pair.New("radar", "Ordos") },
{ 463, Pair.New("light", "Ordos") },
{ 469, Pair.New("silo", "Ordos") },
{ 472, Pair.New("heavy", "Ordos") },
{ 475, Pair.New("repair", "Ordos") },
{ 478, Pair.New("guntower", "Ordos") },
{ 520, Pair.New("hightech", "Ordos") },
{ 523, Pair.New("rockettower", "Ordos") },
{ 526, Pair.New("research", "Ordos") },
{ 529, Pair.New("starport", "Ordos") },
{ 532, Pair.New("palace", "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("mcv", "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("carryall", "Ordos") },
{ 574, Pair.New("deviatortank", "Ordos") },
// Corrino:
{ 580, Pair.New("wall", "Corrino") },
{ 581, Pair.New("power", "Corrino") },
{ 582, Pair.New("conyard", "Corrino") },
{ 583, Pair.New("barracks", "Corrino") },
{ 584, Pair.New("refinery", "Corrino") },
{ 585, Pair.New("radar", "Corrino") },
{ 587, Pair.New("light", "Corrino") },
{ 588, Pair.New("palace", "Corrino") },
{ 589, Pair.New("silo", "Corrino") },
{ 590, Pair.New("heavy", "Corrino") },
{ 591, Pair.New("repair", "Corrino") },
{ 592, Pair.New("guntower", "Corrino") },
{ 593, Pair.New("hightech", "Corrino") },
{ 594, Pair.New("rockettower", "Corrino") },
{ 595, Pair.New("research", "Corrino") },
{ 596, Pair.New("starport", "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("mcv", "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("carryall", "Corrino") },
// Fremen:
{ 620, Pair.New("wall", "Fremen") },
{ 621, Pair.New("power", "Fremen") },
{ 622, Pair.New("conyard", "Fremen") },
{ 623, Pair.New("barracks", "Fremen") },
{ 624, Pair.New("refinery", "Fremen") },
{ 625, Pair.New("radar", "Fremen") },
{ 627, Pair.New("light", "Fremen") },
{ 628, Pair.New("palacec", "Fremen") },
{ 629, Pair.New("silo", "Fremen") },
{ 630, Pair.New("heavy", "Fremen") },
{ 631, Pair.New("repair", "Fremen") },
{ 632, Pair.New("guntower", "Fremen") },
{ 633, Pair.New("hightech", "Fremen") },
{ 634, Pair.New("rockettower", "Fremen") },
{ 635, Pair.New("research", "Fremen") },
{ 636, Pair.New("starport", "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("mcv", "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("carryall", "Fremen") },
{ 652, Pair.New("sonictank", "Fremen") },
// Smugglers:
{ 660, Pair.New("wall", "Smugglers") },
{ 661, Pair.New("power", "Smugglers") },
{ 662, Pair.New("conyard", "Smugglers") },
{ 663, Pair.New("barracks", "Smugglers") },
{ 664, Pair.New("refinery", "Smugglers") },
{ 666, Pair.New("radar", "Smugglers") },
{ 667, Pair.New("light", "Smugglers") },
{ 668, Pair.New("silo", "Smugglers") },
{ 669, Pair.New("heavy", "Smugglers") },
{ 670, Pair.New("repair", "Smugglers") },
{ 671, Pair.New("guntower", "Smugglers") },
{ 672, Pair.New("hightech", "Smugglers") },
{ 673, Pair.New("rockettower", "Smugglers") },
{ 674, Pair.New("research", "Smugglers") },
{ 675, Pair.New("starport", "Smugglers") },
{ 676, Pair.New("palace", "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("mcv", "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("carryall", "Smugglers") },
// Mercenaries:
{ 700, Pair.New("wall", "Mercenaries") },
{ 701, Pair.New("power", "Mercenaries") },
{ 702, Pair.New("conyard", "Mercenaries") },
{ 703, Pair.New("barracks", "Mercenaries") },
{ 704, Pair.New("refinery", "Mercenaries") },
{ 705, Pair.New("radar", "Mercenaries") },
{ 707, Pair.New("light", "Mercenaries") },
{ 708, Pair.New("silo", "Mercenaries") },
{ 709, Pair.New("heavy", "Mercenaries") },
{ 710, Pair.New("repair", "Mercenaries") },
{ 711, Pair.New("guntower", "Mercenaries") },
{ 712, Pair.New("hightech", "Mercenaries") },
{ 713, Pair.New("rockettower", "Mercenaries") },
{ 714, Pair.New("research", "Mercenaries") },
{ 715, Pair.New("starport", "Mercenaries") },
{ 716, Pair.New("palace", "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("mcv", "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("carryall", "Mercenaries") },
};
readonly Ruleset rules;
readonly FileStream stream;
readonly string tilesetName;
readonly TerrainTile clearTile;
Map map;
Size mapSize;
TileSet tileSet;
List<TerrainTemplateInfo> 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;
var players = new MapPlayers(map.Rules, map.SpawnPoints.Value.Length);
map.PlayerDefinitions = players.ToMiniYaml();
return map;
}
void Initialize(string mapFile)
{
mapSize = new Size(stream.ReadUInt16(), stream.ReadUInt16());
tileSet = rules.TileSets["ARRAKIS"];
map = new Map(tileSet, mapSize.Width + 2 * MapCordonWidth, mapSize.Height + 2 * MapCordonWidth)
{
Title = Path.GetFileNameWithoutExtension(mapFile),
Author = "Westwood Studios"
};
var tl = new PPos(MapCordonWidth, MapCordonWidth);
var br = new PPos(MapCordonWidth + mapSize.Width - 1, MapCordonWidth + mapSize.Height - 1);
map.SetBounds(tl, br);
// 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.Images[0].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.ActorDefinitions.Add(new MiniYamlNode("Actor" + map.ActorDefinitions.Count, a.Save()));
}
}
}
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));
}
}
}