#region Copyright & License Information /* * Copyright 2007-2010 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 LICENSE. */ #endregion using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; using OpenRA.FileFormats; using System.Text; namespace OpenRA { public class Map : MapStub { // Yaml map data public Dictionary Players = new Dictionary(); public Dictionary Actors = new Dictionary(); public List Smudges = new List(); // Rules overrides public List Rules = new List(); // Sequences overrides public List Sequences = new List(); // Weapon overrides public List Weapons = new List(); // Voices overrides public List Voices = new List(); // Binary map data public byte TileFormat = 1; [FieldLoader.Load] public int2 MapSize; public TileReference[,] MapTiles; public TileReference[,] MapResources; public string [,] CustomTerrain; public Map() { // Do nothing; not a valid map (editor hack) } public static Map FromTileset(string tileset) { var tile = OpenRA.Rules.TileSets[tileset].Templates.First(); Map map = new Map() { Title = "Name your map here", Description = "Describe your map here", Author = "Your name here", MapSize = new int2(1, 1), Tileset = tileset, MapResources = new TileReference[1, 1], MapTiles = new TileReference[1, 1] { { new TileReference { type = tile.Key, image = (byte)(tile.Value.PickAny ? 0xffu : 0), index = (byte)(tile.Value.PickAny ? 0xffu : 0) } } }, }; return map; } class Format2ActorReference { public string Id = null; public string Type = null; public int2 Location = int2.Zero; public string Owner = null; } public Map(string path) : base(path) { var yaml = new MiniYaml( null, MiniYaml.FromStream( Container.GetContent( "map.yaml" ) ) ); // 'Simple' metadata FieldLoader.Load( this, yaml ); // Players & Actors -- this has changed several times. // - Be backwards compatible wherever possible. // - Loading a map then saving it out upgrades to latest. // Minimum criteria for dropping a format: // - There are no maps of this format left in tree switch (MapFormat) { case 1: { Players.Add("Neutral", new PlayerReference("Neutral", "allies", true, true)); int actors = 0; foreach (var kv in yaml.NodesDict["Actors"].NodesDict) { string[] vals = kv.Value.Value.Split(' '); string[] loc = vals[2].Split(','); Actors.Add("Actor" + actors++, new ActorReference(vals[0]) { new LocationInit( new int2( int.Parse( loc[ 0 ] ), int.Parse( loc[ 1 ] ) ) ), new OwnerInit( "Neutral" ), }); } } break; case 2: { foreach (var kv in yaml.NodesDict["Players"].NodesDict) { var player = new PlayerReference(kv.Value); Players.Add(player.Name, player); } foreach (var kv in yaml.NodesDict["Actors"].NodesDict) { var oldActorReference = FieldLoader.Load(kv.Value); Actors.Add(oldActorReference.Id, new ActorReference(oldActorReference.Type) { new LocationInit( oldActorReference.Location ), new OwnerInit( oldActorReference.Owner ) }); } } break; case 3: case 4: case 5: { foreach (var kv in yaml.NodesDict["Players"].NodesDict) { var player = new PlayerReference(kv.Value); Players.Add(player.Name, player); } foreach (var kv in yaml.NodesDict["Actors"].NodesDict) Actors.Add(kv.Key, new ActorReference(kv.Value.Value, kv.Value.NodesDict)); } break; default: throw new InvalidDataException("Map format {0} is not supported.".F(MapFormat)); } /* hack: make some slots. */ if (!Players.Any(p => p.Value.Playable)) { for (int index = 0; index < Waypoints.Count; index++) { var p = new PlayerReference { Name = "Multi{0}".F(index), Race = "Random", Playable = true, DefaultStartingUnits = true, Enemies = new[]{"Creeps"} }; Players.Add(p.Name, p); } } // Color1/Color2 -> ColorRamp if (MapFormat < 4) foreach (var mp in Players) mp.Value.ColorRamp = new ColorRamp( (byte)((mp.Value.Color.GetHue() / 360.0f) * 255), (byte)(mp.Value.Color.GetSaturation() * 255), (byte)(mp.Value.Color.GetBrightness() * 255), (byte)(mp.Value.Color2.GetBrightness() * 255)); // Creep player / Required Mod if (MapFormat < 5) { RequiresMod = Game.CurrentMods.Keys.First(); foreach (var mp in Players.Where(p => !p.Value.NonCombatant && !p.Value.Enemies.Contains("Creeps"))) mp.Value.Enemies = mp.Value.Enemies.Concat(new[] {"Creeps"}).ToArray(); Players.Add("Creeps", new PlayerReference { Name = "Creeps", Race = "Random", NonCombatant = true, Enemies = Players.Keys.Where(k => k != "Neutral").ToArray() }); } // Smudges foreach (var kv in yaml.NodesDict["Smudges"].NodesDict) { string[] vals = kv.Key.Split(' '); string[] loc = vals[1].Split(','); Smudges.Add(new SmudgeReference(vals[0], new int2(int.Parse(loc[0]), int.Parse(loc[1])), int.Parse(vals[2]))); } // Rules Rules = yaml.NodesDict["Rules"].Nodes; // Sequences Sequences = (yaml.NodesDict.ContainsKey("Sequences")) ? yaml.NodesDict["Sequences"].Nodes : new List(); // Weapons Weapons = (yaml.NodesDict.ContainsKey("Weapons")) ? yaml.NodesDict["Weapons"].Nodes : new List(); // Voices Voices = (yaml.NodesDict.ContainsKey("Voices")) ? yaml.NodesDict["Voices"].Nodes : new List(); CustomTerrain = new string[MapSize.X, MapSize.Y]; LoadBinaryData(); } public void Save(string toPath) { MapFormat = 5; var root = new List(); var fields = new string[] { "Selectable", "MapFormat", "RequiresMod", "Title", "Description", "Author", "Tileset", "MapSize", "TopLeft", "BottomRight", "UseAsShellmap", "Type", "StartPoints" }; foreach (var field in fields) { var f = this.GetType().GetField(field); if (f.GetValue(this) == null) continue; root.Add( new MiniYamlNode( field, FieldSaver.FormatValue( this, f ) ) ); } root.Add( new MiniYamlNode( "Players", null, Players.Select( p => new MiniYamlNode( "PlayerReference@{0}".F( p.Key ), FieldSaver.Save( p.Value ) ) ).ToList() ) ); root.Add( new MiniYamlNode( "Actors", null, Actors.Select( x => new MiniYamlNode( x.Key, x.Value.Save() ) ).ToList() ) ); root.Add(new MiniYamlNode("Waypoints", MiniYaml.FromDictionary( Waypoints ))); root.Add(new MiniYamlNode("Smudges", MiniYaml.FromList( Smudges ))); root.Add(new MiniYamlNode("Rules", null, Rules)); root.Add(new MiniYamlNode("Sequences", null, Sequences)); root.Add(new MiniYamlNode("Weapons", null, Weapons)); root.Add(new MiniYamlNode("Voices", null, Voices)); Dictionary entries = new Dictionary(); entries.Add("map.bin", SaveBinaryData()); var s = root.WriteToString(); entries.Add("map.yaml", Encoding.UTF8.GetBytes(s)); // Saving the map to a new location if (toPath != Path) { Path = toPath; // Create a new map package // TODO: Add other files (resources, rules) to the entries list Container = FileSystem.CreatePackage(Path, int.MaxValue, entries); } // Update existing package Container.Write(entries); } static byte ReadByte(Stream s) { int ret = s.ReadByte(); if (ret == -1) throw new NotImplementedException(); return (byte)ret; } static ushort ReadWord(Stream s) { ushort ret = ReadByte(s); ret |= (ushort)(ReadByte(s) << 8); return ret; } public void LoadBinaryData() { using (var dataStream = Container.GetContent("map.bin")) { if (ReadByte(dataStream) != 1) throw new InvalidDataException("Unknown binary map format"); // Load header info var width = ReadWord(dataStream); var height = ReadWord(dataStream); if (width != MapSize.X || height != MapSize.Y) throw new InvalidDataException("Invalid tile data"); MapTiles = new TileReference[MapSize.X, MapSize.Y]; MapResources = new TileReference[MapSize.X, MapSize.Y]; // Load tile data for (int i = 0; i < MapSize.X; i++) for (int j = 0; j < MapSize.Y; j++) { ushort tile = ReadWord(dataStream); byte index = ReadByte(dataStream); byte image = (index == byte.MaxValue) ? (byte)(i % 4 + (j % 4) * 4) : index; MapTiles[i, j] = new TileReference(tile, index, image); } // Load resource data for (int i = 0; i < MapSize.X; i++) for (int j = 0; j < MapSize.Y; j++) MapResources[i, j] = new TileReference(ReadByte(dataStream), ReadByte(dataStream)); } } public byte[] SaveBinaryData() { MemoryStream dataStream = new MemoryStream(); using (var writer = new BinaryWriter(dataStream)) { // File header consists of a version byte, followed by 2 ushorts for width and height writer.Write(TileFormat); writer.Write((ushort)MapSize.X); writer.Write((ushort)MapSize.Y); // Tile data for (int i = 0; i < MapSize.X; i++) for (int j = 0; j < MapSize.Y; j++) { writer.Write(MapTiles[i, j].type); writer.Write(MapTiles[i, j].index); } // Resource data for (int i = 0; i < MapSize.X; i++) for (int j = 0; j < MapSize.Y; j++) { writer.Write(MapResources[i, j].type); writer.Write(MapResources[i, j].index); } } return dataStream.ToArray(); } public bool IsInMap(int2 xy) { return IsInMap(xy.X, xy.Y); } public bool IsInMap(int x, int y) { return Bounds.Contains(x,y); } static T[,] ResizeArray(T[,] ts, T t, int width, int height) { var result = new T[width, height]; for (var i = 0; i < width; i++) for (var j = 0; j < height; j++) result[i, j] = i <= ts.GetUpperBound(0) && j <= ts.GetUpperBound(1) ? ts[i, j] : t; return result; } public void Resize(int width, int height) // editor magic. { MapTiles = ResizeArray(MapTiles, MapTiles[0, 0], width, height); MapResources = ResizeArray(MapResources, MapResources[0, 0], width, height); MapSize = new int2(width, height); } public void ResizeCordon(int left, int top, int right, int bottom) { TopLeft = new int2(left, top); BottomRight = new int2(right, bottom); Bounds = Rectangle.FromLTRB(TopLeft.X, TopLeft.Y, BottomRight.X, BottomRight.Y); } } }