Merge FileFormats dll into Game and reorganise namespaces.

This commit is contained in:
Paul Chote
2014-04-16 23:19:24 +12:00
parent 5a698d612f
commit 4935266945
246 changed files with 423 additions and 922 deletions

View File

@@ -0,0 +1,105 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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.Linq;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA
{
public class ActorInitializer
{
public readonly Actor self;
public World world { get { return self.World; } }
internal TypeDictionary dict;
public ActorInitializer(Actor actor, TypeDictionary dict)
{
this.self = actor;
this.dict = dict;
}
public T Get<T>() where T : IActorInit { return dict.Get<T>(); }
public U Get<T, U>() where T : IActorInit<U> { return dict.Get<T>().Value(world); }
public bool Contains<T>() where T : IActorInit { return dict.Contains<T>(); }
}
public interface IActorInit { }
public interface IActorInit<T> : IActorInit
{
T Value(World world);
}
public class FacingInit : IActorInit<int>
{
[FieldFromYamlKey] public readonly int value = 128;
public FacingInit() { }
public FacingInit(int init) { value = init; }
public int Value(World world) { return value; }
}
public class TurretFacingInit : IActorInit<int>
{
[FieldFromYamlKey] public readonly int value = 128;
public TurretFacingInit() { }
public TurretFacingInit(int init) { value = init; }
public int Value(World world) { return value; }
}
public class LocationInit : IActorInit<CPos>
{
[FieldFromYamlKey] public readonly int2 value = int2.Zero;
public LocationInit() { }
public LocationInit(CPos init) { value = init.ToInt2(); }
public CPos Value(World world) { return (CPos)value; }
}
public class SubCellInit : IActorInit<SubCell>
{
[FieldFromYamlKey] public readonly int value = 0;
public SubCellInit() { }
public SubCellInit(int init) { value = init; }
public SubCellInit(SubCell init) { value = (int)init; }
public SubCell Value(World world) { return (SubCell)value; }
}
public class CenterPositionInit : IActorInit<WPos>
{
[FieldFromYamlKey] public readonly WPos value = WPos.Zero;
public CenterPositionInit() { }
public CenterPositionInit(WPos init) { value = init; }
public WPos Value(World world) { return value; }
}
public class OwnerInit : IActorInit<Player>
{
[FieldFromYamlKey] public readonly string PlayerName = "Neutral";
Player player;
public OwnerInit() { }
public OwnerInit(string playerName) { this.PlayerName = playerName; }
public OwnerInit(Player player)
{
this.player = player;
this.PlayerName = player.InternalName;
}
public Player Value(World world)
{
if (player != null)
return player;
return world.Players.First(x => x.InternalName == PlayerName);
}
}
}

View File

@@ -0,0 +1,54 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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.Collections;
using System.Collections.Generic;
using OpenRA.Primitives;
namespace OpenRA
{
public class ActorReference : IEnumerable
{
public string Type;
public TypeDictionary InitDict;
public ActorReference( string type ) : this( type, new Dictionary<string, MiniYaml>() ) { }
public ActorReference( string type, Dictionary<string, MiniYaml> inits )
{
Type = type;
InitDict = new TypeDictionary();
foreach( var i in inits )
InitDict.Add( LoadInit( i.Key, i.Value ) );
}
static IActorInit LoadInit(string traitName, MiniYaml my)
{
var info = Game.CreateObject<IActorInit>(traitName + "Init");
FieldLoader.Load(info, my);
return info;
}
public MiniYaml Save()
{
var ret = new MiniYaml( Type );
foreach( var init in InitDict )
{
var initName = init.GetType().Name;
ret.Nodes.Add( new MiniYamlNode( initName.Substring( 0, initName.Length - 4 ), FieldSaver.Save( init ) ) );
}
return ret;
}
// for initialization syntax
public void Add( object o ) { InitDict.Add( o ); }
public IEnumerator GetEnumerator() { return InitDict.GetEnumerator(); }
}
}

506
OpenRA.Game/Map/Map.cs Normal file
View File

@@ -0,0 +1,506 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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 System.Security.Cryptography;
using System.Text;
using OpenRA.FileSystem;
using OpenRA.Network;
using OpenRA.Traits;
namespace OpenRA
{
public class MapOptions
{
public bool? Cheats;
public bool? Crates;
public bool? Fog;
public bool? Shroud;
public bool? AllyBuildRadius;
public bool? FragileAlliances;
public int? StartingCash;
public bool ConfigurableStartingUnits = true;
public string[] Difficulties = { };
public void UpdateServerSettings(Session.Global settings)
{
if (Cheats.HasValue)
settings.AllowCheats = Cheats.Value;
if (Crates.HasValue)
settings.Crates = Crates.Value;
if (Fog.HasValue)
settings.Fog = Fog.Value;
if (Shroud.HasValue)
settings.Shroud = Shroud.Value;
if (AllyBuildRadius.HasValue)
settings.AllyBuildRadius = AllyBuildRadius.Value;
if (StartingCash.HasValue)
settings.StartingCash = StartingCash.Value;
if (FragileAlliances.HasValue)
settings.FragileAlliances = FragileAlliances.Value;
}
}
public class Map
{
[FieldLoader.Ignore] public IFolder Container;
public string Path { get; private set; }
// Yaml map data
public string Uid { get; private set; }
public int MapFormat;
public bool Selectable = true;
public bool UseAsShellmap;
public string RequiresMod;
public string Title;
public string Type = "Conquest";
public string Description;
public string Author;
public string Tileset;
public bool AllowStartUnitConfig = true;
public Bitmap CustomPreview;
[FieldLoader.LoadUsing("LoadOptions")]
public MapOptions Options;
static object LoadOptions(MiniYaml y)
{
var options = new MapOptions();
if (y.NodesDict.ContainsKey("Options"))
FieldLoader.Load(options, y.NodesDict["Options"]);
return options;
}
[FieldLoader.Ignore] public Lazy<Dictionary<string, ActorReference>> Actors;
public int PlayerCount { get { return Players.Count(p => p.Value.Playable); } }
public Rectangle Bounds;
// Yaml map data
[FieldLoader.Ignore] public Dictionary<string, PlayerReference> Players = new Dictionary<string, PlayerReference>();
[FieldLoader.Ignore] public Lazy<List<SmudgeReference>> Smudges;
[FieldLoader.Ignore] public List<MiniYamlNode> Rules = new List<MiniYamlNode>();
[FieldLoader.Ignore] public List<MiniYamlNode> Sequences = new List<MiniYamlNode>();
[FieldLoader.Ignore] public List<MiniYamlNode> VoxelSequences = new List<MiniYamlNode>();
[FieldLoader.Ignore] public List<MiniYamlNode> Weapons = new List<MiniYamlNode>();
[FieldLoader.Ignore] public List<MiniYamlNode> Voices = new List<MiniYamlNode>();
[FieldLoader.Ignore] public List<MiniYamlNode> Notifications = new List<MiniYamlNode>();
[FieldLoader.Ignore] public List<MiniYamlNode> Translations = new List<MiniYamlNode>();
// Binary map data
[FieldLoader.Ignore] public byte TileFormat = 1;
public int2 MapSize;
[FieldLoader.Ignore] public Lazy<TileReference<ushort, byte>[,]> MapTiles;
[FieldLoader.Ignore] public Lazy<TileReference<byte, byte>[,]> MapResources;
[FieldLoader.Ignore] public string[,] CustomTerrain;
public static Map FromTileset(TileSet tileset)
{
var tile = tileset.Templates.First();
var tileRef = new TileReference<ushort, byte> { Type = tile.Key, Index = (byte)0 };
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.Id,
Options = new MapOptions(),
MapResources = Exts.Lazy(() => new TileReference<byte, byte>[1, 1]),
MapTiles = Exts.Lazy(() => new TileReference<ushort, byte>[1, 1] { { tileRef } }),
Actors = Exts.Lazy(() => new Dictionary<string, ActorReference>()),
Smudges = Exts.Lazy(() => new List<SmudgeReference>())
};
return map;
}
void AssertExists(string filename)
{
using (var s = Container.GetContent(filename))
if (s == null)
throw new InvalidOperationException("Required file {0} not present in this map".F(filename));
}
// Stub constructor that doesn't produce a valid map, but is
// sufficient for loading a mod to the content-install panel
public Map() { }
// The standard constructor for most purposes
public Map(string path) : this(path, null) { }
// Support upgrading format 5 maps to a more
// recent version by defining upgradeForMod.
public Map(string path, string upgradeForMod)
{
Path = path;
Container = GlobalFileSystem.OpenPackage(path, null, int.MaxValue);
AssertExists("map.yaml");
AssertExists("map.bin");
var yaml = new MiniYaml(null, MiniYaml.FromStream(Container.GetContent("map.yaml")));
FieldLoader.Load(this, yaml);
// Support for formats 1-3 dropped 2011-02-11.
// Use release-20110207 to convert older maps to format 4
// Use release-20110511 to convert older maps to format 5
if (MapFormat < 5)
throw new InvalidDataException("Map format {0} is not supported.\n File: {1}".F(MapFormat, path));
// Format 5 -> 6 enforces the use of RequiresMod
if (MapFormat == 5)
{
if (upgradeForMod == null)
throw new InvalidDataException("Map format {0} is not supported, but can be upgraded.\n File: {1}".F(MapFormat, path));
Console.WriteLine("Upgrading {0} from Format 5 to Format 6", path);
// TODO: This isn't very nice, but there is no other consistent way
// of finding the mod early during the engine initialization.
RequiresMod = upgradeForMod;
}
// Load players
foreach (var kv in yaml.NodesDict["Players"].NodesDict)
{
var player = new PlayerReference(kv.Value);
Players.Add(player.Name, player);
}
Actors = Exts.Lazy(() =>
{
var ret = new Dictionary<string, ActorReference>();
foreach (var kv in yaml.NodesDict["Actors"].NodesDict)
ret.Add(kv.Key, new ActorReference(kv.Value.Value, kv.Value.NodesDict));
return ret;
});
// Smudges
Smudges = Exts.Lazy(() =>
{
var ret = new List<SmudgeReference>();
foreach (var kv in yaml.NodesDict["Smudges"].NodesDict)
{
var vals = kv.Key.Split(' ');
var loc = vals[1].Split(',');
ret.Add(new SmudgeReference(vals[0], new int2(int.Parse(loc[0]), int.Parse(loc[1])), int.Parse(vals[2])));
}
return ret;
});
Rules = MiniYaml.NodesOrEmpty(yaml, "Rules");
Sequences = MiniYaml.NodesOrEmpty(yaml, "Sequences");
VoxelSequences = MiniYaml.NodesOrEmpty(yaml, "VoxelSequences");
Weapons = MiniYaml.NodesOrEmpty(yaml, "Weapons");
Voices = MiniYaml.NodesOrEmpty(yaml, "Voices");
Notifications = MiniYaml.NodesOrEmpty(yaml, "Notifications");
Translations = MiniYaml.NodesOrEmpty(yaml, "Translations");
CustomTerrain = new string[MapSize.X, MapSize.Y];
MapTiles = Exts.Lazy(() => LoadMapTiles());
MapResources = Exts.Lazy(() => LoadResourceTiles());
// The Uid is calculated from the data on-disk, so
// format changes must be flushed to disk.
// TODO: this isn't very nice
if (MapFormat < 6)
Save(path);
Uid = ComputeHash();
if (Container.Exists("map.png"))
CustomPreview = new Bitmap(Container.GetContent("map.png"));
}
public CPos[] GetSpawnPoints()
{
return Actors.Value.Values
.Where(a => a.Type == "mpspawn")
.Select(a => (CPos)a.InitDict.Get<LocationInit>().value)
.ToArray();
}
public void Save(string toPath)
{
MapFormat = 6;
var root = new List<MiniYamlNode>();
var fields = new[]
{
"Selectable",
"MapFormat",
"RequiresMod",
"Title",
"Description",
"Author",
"Tileset",
"MapSize",
"Bounds",
"UseAsShellmap",
"Type",
};
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("Options", FieldSaver.SaveDifferences(Options, new MapOptions())));
root.Add(new MiniYamlNode("Players", null,
Players.Select(p => new MiniYamlNode("PlayerReference@{0}".F(p.Key), FieldSaver.SaveDifferences(p.Value, new PlayerReference()))).ToList())
);
root.Add(new MiniYamlNode("Actors", null,
Actors.Value.Select(x => new MiniYamlNode(x.Key, x.Value.Save())).ToList())
);
root.Add(new MiniYamlNode("Smudges", MiniYaml.FromList<SmudgeReference>(Smudges.Value)));
root.Add(new MiniYamlNode("Rules", null, Rules));
root.Add(new MiniYamlNode("Sequences", null, Sequences));
root.Add(new MiniYamlNode("VoxelSequences", null, VoxelSequences));
root.Add(new MiniYamlNode("Weapons", null, Weapons));
root.Add(new MiniYamlNode("Voices", null, Voices));
root.Add(new MiniYamlNode("Notifications", null, Notifications));
root.Add(new MiniYamlNode("Translations", null, Translations));
var entries = new Dictionary<string, byte[]>();
entries.Add("map.bin", SaveBinaryData());
var s = root.WriteToString();
entries.Add("map.yaml", Encoding.UTF8.GetBytes(s));
// Add any custom assets
if (Container != null)
{
foreach (var file in Container.AllFileNames())
{
if (file == "map.bin" || file == "map.yaml")
continue;
entries.Add(file, Container.GetContent(file).ReadAllBytes());
}
}
// Saving the map to a new location
if (toPath != Path)
{
Path = toPath;
// Create a new map package
Container = GlobalFileSystem.CreatePackage(Path, int.MaxValue, entries);
}
// Update existing package
Container.Write(entries);
}
public TileReference<ushort, byte>[,] LoadMapTiles()
{
var tiles = new TileReference<ushort, byte>[MapSize.X, MapSize.Y];
using (var dataStream = Container.GetContent("map.bin"))
{
if (dataStream.ReadUInt8() != 1)
throw new InvalidDataException("Unknown binary map format");
// Load header info
var width = dataStream.ReadUInt16();
var height = dataStream.ReadUInt16();
if (width != MapSize.X || height != MapSize.Y)
throw new InvalidDataException("Invalid tile data");
// Load tile data
for (int i = 0; i < MapSize.X; i++)
for (int j = 0; j < MapSize.Y; j++)
{
var tile = dataStream.ReadUInt16();
var index = dataStream.ReadUInt8();
if (index == byte.MaxValue)
index = (byte)(i % 4 + (j % 4) * 4);
tiles[i, j] = new TileReference<ushort, byte>(tile, index);
}
}
return tiles;
}
public TileReference<byte, byte>[,] LoadResourceTiles()
{
var resources = new TileReference<byte, byte>[MapSize.X, MapSize.Y];
using (var dataStream = Container.GetContent("map.bin"))
{
if (dataStream.ReadUInt8() != 1)
throw new InvalidDataException("Unknown binary map format");
// Load header info
var width = dataStream.ReadUInt16();
var height = dataStream.ReadUInt16();
if (width != MapSize.X || height != MapSize.Y)
throw new InvalidDataException("Invalid tile data");
// Skip past tile data
dataStream.Seek(3 * MapSize.X * MapSize.Y, SeekOrigin.Current);
// Load resource data
for (var i = 0; i < MapSize.X; i++)
for (var j = 0; j < MapSize.Y; j++)
{
var type = dataStream.ReadUInt8();
var index = dataStream.ReadUInt8();
resources[i, j] = new TileReference<byte, byte>(type, index);
}
}
return resources;
}
public byte[] SaveBinaryData()
{
var 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 (var i = 0; i < MapSize.X; i++)
for (var j = 0; j < MapSize.Y; j++)
{
writer.Write(MapTiles.Value[i, j].Type);
writer.Write(MapTiles.Value[i, j].Index);
}
// Resource data
for (var i = 0; i < MapSize.X; i++)
for (var j = 0; j < MapSize.Y; j++)
{
writer.Write(MapResources.Value[i, j].Type);
writer.Write(MapResources.Value[i, j].Index);
}
}
return dataStream.ToArray();
}
public bool IsInMap(CPos xy) { return IsInMap(xy.X, xy.Y); }
public bool IsInMap(int x, int y) { return Bounds.Contains(x, y); }
public void Resize(int width, int height) // editor magic.
{
var oldMapTiles = MapTiles.Value;
var oldMapResources = MapResources.Value;
MapTiles = Exts.Lazy(() => Exts.ResizeArray(oldMapTiles, oldMapTiles[0, 0], width, height));
MapResources = Exts.Lazy(() => Exts.ResizeArray(oldMapResources, oldMapResources[0, 0], width, height));
MapSize = new int2(width, height);
}
public void ResizeCordon(int left, int top, int right, int bottom)
{
Bounds = Rectangle.FromLTRB(left, top, right, bottom);
}
string ComputeHash()
{
// UID is calculated by taking an SHA1 of the yaml and binary data
// Read the relevant data into a buffer
var data = Container.GetContent("map.yaml").ReadAllBytes()
.Concat(Container.GetContent("map.bin").ReadAllBytes()).ToArray();
// Take the SHA1
using (var csp = SHA1.Create())
return new string(csp.ComputeHash(data).SelectMany(a => a.ToString("x2")).ToArray());
}
public void MakeDefaultPlayers()
{
var firstRace = OpenRA.Rules.Info["world"].Traits
.WithInterface<CountryInfo>().First(c => c.Selectable).Race;
if (!Players.ContainsKey("Neutral"))
Players.Add("Neutral", new PlayerReference
{
Name = "Neutral",
Race = firstRace,
OwnsWorld = true,
NonCombatant = true
});
var numSpawns = GetSpawnPoints().Length;
for (var index = 0; index < numSpawns; index++)
{
if (Players.ContainsKey("Multi{0}".F(index)))
continue;
var p = new PlayerReference
{
Name = "Multi{0}".F(index),
Race = "Random",
Playable = true,
Enemies = new[] { "Creeps" }
};
Players.Add(p.Name, p);
}
Players.Add("Creeps", new PlayerReference
{
Name = "Creeps",
Race = firstRace,
NonCombatant = true,
Enemies = Players.Where(p => p.Value.Playable).Select(p => p.Key).ToArray()
});
}
public void FixOpenAreas()
{
var r = new Random();
var tileset = OpenRA.Rules.TileSets[Tileset];
for (var j = Bounds.Top; j < Bounds.Bottom; j++)
{
for (var i = Bounds.Left; i < Bounds.Right; i++)
{
var tr = MapTiles.Value[i, j];
if (!tileset.Templates.ContainsKey(tr.Type))
{
Console.WriteLine("Unknown Tile ID {0}".F(tr.Type));
continue;
}
var template = tileset.Templates[tr.Type];
if (!template.PickAny)
continue;
tr.Index = (byte)r.Next(0, template.Tiles.Count);
MapTiles.Value[i, j] = tr;
}
}
}
}
}

187
OpenRA.Game/Map/MapCache.cs Executable file
View File

@@ -0,0 +1,187 @@
#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;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Primitives;
namespace OpenRA
{
public class MapCache : IEnumerable<MapPreview>
{
public static readonly MapPreview UnknownMap = new MapPreview(null, null);
readonly Cache<string, MapPreview> previews;
readonly Manifest manifest;
readonly SheetBuilder sheetBuilder;
Thread previewLoaderThread;
object syncRoot = new object();
Queue<MapPreview> generateMinimap = new Queue<MapPreview>();
public MapCache(Manifest m)
{
manifest = m;
previews = new Cache<string, MapPreview>(uid => new MapPreview(uid, this));
sheetBuilder = new SheetBuilder(SheetType.BGRA);
}
public void LoadMaps()
{
var paths = manifest.MapFolders.SelectMany(f => FindMapsIn(f));
foreach (var path in paths)
{
try
{
var map = new Map(path, manifest.Mod.Id);
if (manifest.MapCompatibility.Contains(map.RequiresMod))
previews[map.Uid].UpdateFromMap(map);
}
catch (Exception e)
{
Console.WriteLine("Failed to load map: {0}", path);
Console.WriteLine("Details: {0}", e);
}
}
}
public void QueryRemoteMapDetails(IEnumerable<string> uids)
{
var maps = uids.Distinct()
.Select(uid => previews[uid])
.Where(p => p.Status == MapStatus.Unavailable)
.ToDictionary(p => p.Uid, p => p);
if (!maps.Any())
return;
foreach (var p in maps.Values)
p.UpdateRemoteSearch(MapStatus.Searching, null);
var url = Game.Settings.Game.MapRepository + "hash/" + string.Join(",", maps.Keys.ToArray()) + "/yaml";
Action<DownloadDataCompletedEventArgs, bool> onInfoComplete = (i, cancelled) =>
{
if (cancelled || i.Error != null)
{
Log.Write("debug", "Remote map query failed with error: {0}", i.Error != null ? i.Error.Message : "cancelled");
Log.Write("debug", "URL was: {0}", url);
foreach (var p in maps.Values)
p.UpdateRemoteSearch(MapStatus.Unavailable, null);
return;
}
var data = Encoding.UTF8.GetString(i.Result);
var yaml = MiniYaml.FromString(data);
foreach (var kv in yaml)
maps[kv.Key].UpdateRemoteSearch(MapStatus.DownloadAvailable, kv.Value);
};
new Download(url, _ => { }, onInfoComplete);
}
public static IEnumerable<string> FindMapsIn(string dir)
{
string[] noMaps = { };
// Ignore optional flag
if (dir.StartsWith("~"))
dir = dir.Substring(1);
// Paths starting with ^ are relative to the user directory
if (dir.StartsWith("^"))
dir = Platform.SupportDir + dir.Substring(1);
if (!Directory.Exists(dir))
return noMaps;
var dirsWithMaps = Directory.GetDirectories(dir)
.Where(d => Directory.GetFiles(d, "map.yaml").Any() && Directory.GetFiles(d, "map.bin").Any());
return dirsWithMaps.Concat(Directory.GetFiles(dir, "*.oramap"));
}
void LoadAsyncInternal()
{
for (;;)
{
MapPreview p;
lock (syncRoot)
{
if (generateMinimap.Count == 0)
break;
p = generateMinimap.Peek();
// Preview already exists
if (p.Minimap != null)
{
generateMinimap.Dequeue();
continue;
}
}
// Render the minimap into the shared sheet
// Note: this is not generally thread-safe, but it works here because:
// (a) This worker is the only thread writing to this sheet
// (b) The main thread is the only thread reading this sheet
// (c) The sheet is marked dirty after the write is completed,
// which causes the main thread to copy this to the texture during
// the next render cycle.
// (d) Any partially written bytes from the next minimap is in an
// unallocated area, and will be committed in the next cycle.
var bitmap = p.CustomPreview ?? Minimap.RenderMapPreview(p.Map, true);
p.Minimap = sheetBuilder.Add(bitmap);
lock (syncRoot)
generateMinimap.Dequeue();
// Yuck... But this helps the UI Jank when opening the map selector significantly.
Thread.Sleep(50);
}
}
public void CacheMinimap(MapPreview preview)
{
lock (syncRoot)
generateMinimap.Enqueue(preview);
if (previewLoaderThread == null || !previewLoaderThread.IsAlive)
{
previewLoaderThread = new Thread(LoadAsyncInternal);
previewLoaderThread.Start();
}
}
public MapPreview this[string key]
{
get { return previews[key]; }
}
public IEnumerator<MapPreview> GetEnumerator()
{
return previews.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

224
OpenRA.Game/Map/MapPreview.cs Executable file
View File

@@ -0,0 +1,224 @@
#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;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading;
using OpenRA.Graphics;
using OpenRA.Widgets;
namespace OpenRA
{
public enum MapStatus { Available, Unavailable, Searching, DownloadAvailable, Downloading, DownloadError }
// Fields names must match the with the remote API
public class RemoteMapData
{
public readonly string title;
public readonly string author;
public readonly string map_type;
public readonly int players;
public readonly Rectangle bounds;
public readonly int[] spawnpoints = {};
public readonly string minimap;
public readonly bool downloading;
public readonly bool requires_upgrade;
}
public class MapPreview
{
static readonly List<CPos> NoSpawns = new List<CPos>();
MapCache cache;
public readonly string Uid;
public string Title { get; private set; }
public string Type { get; private set; }
public string Author { get; private set; }
public int PlayerCount { get; private set; }
public List<CPos> SpawnPoints { get; private set; }
public Rectangle Bounds { get; private set; }
public Bitmap CustomPreview { get; private set; }
public Map Map { get; private set; }
public MapStatus Status { get; private set; }
Download download;
public long DownloadBytes { get; private set; }
public int DownloadPercentage { get; private set; }
Sprite minimap;
bool generatingMinimap;
public Sprite Minimap
{
get
{
if (minimap != null)
return minimap;
if (!generatingMinimap && Status == MapStatus.Available)
{
generatingMinimap = true;
cache.CacheMinimap(this);
}
return null;
}
set
{
minimap = value;
generatingMinimap = false;
}
}
public MapPreview(string uid, MapCache cache)
{
this.cache = cache;
Uid = uid;
Title = "Unknown Map";
Type = "Unknown";
Author = "Unknown Author";
PlayerCount = 0;
Bounds = Rectangle.Empty;
SpawnPoints = NoSpawns;
Status = MapStatus.Unavailable;
}
public void UpdateFromMap(Map m)
{
Map = m;
Title = m.Title;
Type = m.Type;
Type = m.Type;
Author = m.Author;
PlayerCount = m.Players.Count(x => x.Value.Playable);
Bounds = m.Bounds;
SpawnPoints = m.GetSpawnPoints().ToList();
CustomPreview = m.CustomPreview;
Status = MapStatus.Available;
}
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml)
{
// Update on the main thread to ensure consistency
Game.RunAfterTick(() =>
{
if (status == MapStatus.DownloadAvailable)
{
try
{
var r = FieldLoader.Load<RemoteMapData>(yaml);
// Map is not useable by the current version
if (!r.downloading || r.requires_upgrade)
{
Status = MapStatus.Unavailable;
return;
}
Title = r.title;
Type = r.map_type;
Author = r.author;
PlayerCount = r.players;
Bounds = r.bounds;
var spawns = new List<CPos>();
for (var j = 0; j < r.spawnpoints.Length; j += 2)
spawns.Add(new CPos(r.spawnpoints[j], r.spawnpoints[j+1]));
SpawnPoints = spawns;
CustomPreview = new Bitmap(new MemoryStream(Convert.FromBase64String(r.minimap)));
}
catch (Exception) {}
if (CustomPreview != null)
cache.CacheMinimap(this);
}
Status = status;
});
}
public void Install()
{
if (Status != MapStatus.DownloadAvailable || !Game.Settings.Game.AllowDownloading)
return;
Status = MapStatus.Downloading;
var baseMapPath = new[] { Platform.SupportDir, "maps", Game.modData.Manifest.Mod.Id }.Aggregate(Path.Combine);
// Create the map directory if it doesn't exist
if (!Directory.Exists(baseMapPath))
Directory.CreateDirectory(baseMapPath);
new Thread(() =>
{
// Request the filename from the server
// Run in a worker thread to avoid network delays
var mapUrl = Game.Settings.Game.MapRepository + Uid;
try
{
var request = WebRequest.Create(mapUrl);
request.Method = "HEAD";
var res = request.GetResponse();
// Map not found
if (res.Headers["Content-Disposition"] == null)
{
Status = MapStatus.DownloadError;
return;
}
var mapPath = Path.Combine(baseMapPath, res.Headers["Content-Disposition"].Replace("attachment; filename = ", ""));
Action<DownloadProgressChangedEventArgs> onDownloadProgress = i => { DownloadBytes = i.BytesReceived; DownloadPercentage = i.ProgressPercentage; };
Action<AsyncCompletedEventArgs, bool> onDownloadComplete = (i, cancelled) =>
{
download = null;
if (cancelled || i.Error != null)
{
Log.Write("debug", "Remote map download failed with error: {0}", i.Error != null ? i.Error.Message : "cancelled");
Log.Write("debug", "URL was: {0}", mapUrl);
Status = MapStatus.DownloadError;
return;
}
Log.Write("debug", "Downloaded map to '{0}'", mapPath);
Game.RunAfterTick(() => UpdateFromMap(new Map(mapPath)));
};
download = new Download(mapUrl, mapPath, onDownloadProgress, onDownloadComplete);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Status = MapStatus.DownloadError;
}
}).Start();
}
public void CancelInstall()
{
if (download == null)
return;
download.Cancel();
download = null;
}
}
}

View File

@@ -0,0 +1,50 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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 OpenRA.Graphics;
namespace OpenRA
{
public class PlayerReference
{
public string Name;
public string Palette;
public bool OwnsWorld = false;
public bool NonCombatant = false;
public bool Playable = false;
public bool Spectating = false;
public string Bot = null;
public string StartingUnitsClass = null;
public bool AllowBots = true;
public bool Required = false;
public bool LockRace = false;
public string Race;
// ColorRamp naming retained for backward compatibility
public bool LockColor = false;
public HSLColor ColorRamp = new HSLColor(0, 0, 238);
public HSLColor Color { get { return ColorRamp; } set { ColorRamp = value; } }
public bool LockSpawn = false;
public int Spawn = 0;
public bool LockTeam = false;
public int Team = 0;
public string[] Allies = { };
public string[] Enemies = { };
public PlayerReference() { }
public PlayerReference(MiniYaml my) { FieldLoader.Load(this, my); }
public override string ToString() { return Name; }
}
}

View File

@@ -0,0 +1,31 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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
namespace OpenRA
{
public struct SmudgeReference
{
public readonly string Type;
public readonly int2 Location;
public readonly int Depth;
public SmudgeReference(string type, int2 location, int depth)
{
Type = type;
Location = location;
Depth = depth;
}
public override string ToString()
{
return "{0} {1},{2} {3}".F(Type, Location.X, Location.Y, Depth);
}
}
}

View File

@@ -0,0 +1,26 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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
namespace OpenRA
{
public struct TileReference<T, U>
{
public T Type;
public U Index;
public TileReference(T t, U i)
{
Type = t;
Index = i;
}
public override int GetHashCode() { return Type.GetHashCode() ^ Index.GetHashCode(); }
}
}

143
OpenRA.Game/Map/TileSet.cs Normal file
View File

@@ -0,0 +1,143 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 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.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Reflection;
namespace OpenRA
{
public class TerrainTypeInfo
{
public string Type;
public string[] TargetTypes = { };
public string[] AcceptsSmudgeType = { };
public bool IsWater = false; // TODO: Remove this
public Color Color;
public string CustomCursor;
public TerrainTypeInfo() { }
public TerrainTypeInfo(MiniYaml my) { FieldLoader.Load(this, my); }
public MiniYaml Save() { return FieldSaver.Save(this); }
}
public class TileTemplate
{
public ushort Id;
public string Image;
public int[] Frames;
public int2 Size;
public bool PickAny;
public string Category;
[FieldLoader.LoadUsing("LoadTiles")]
public Dictionary<byte, string> Tiles = new Dictionary<byte, string>();
public TileTemplate() { }
public TileTemplate(MiniYaml my) { FieldLoader.Load(this, my); }
static object LoadTiles(MiniYaml y)
{
return y.NodesDict["Tiles"].NodesDict.ToDictionary(
t => byte.Parse(t.Key),
t => t.Value.Value);
}
static readonly string[] Fields = { "Id", "Image", "Frames", "Size", "PickAny" };
public MiniYaml Save()
{
var root = new List<MiniYamlNode>();
foreach (var field in Fields)
{
FieldInfo f = this.GetType().GetField(field);
if (f.GetValue(this) == null)
continue;
root.Add(new MiniYamlNode(field, FieldSaver.FormatValue(this, f)));
}
root.Add(new MiniYamlNode("Tiles", null,
Tiles.Select(x => new MiniYamlNode(x.Key.ToString(), x.Value)).ToList()));
return new MiniYaml(null, root);
}
}
public class TileSet
{
public string Name;
public string Id;
public int SheetSize = 512;
public string Palette;
public string PlayerPalette;
public string[] Extensions;
public int WaterPaletteRotationBase = 0x60;
public Dictionary<string, TerrainTypeInfo> Terrain = new Dictionary<string, TerrainTypeInfo>();
public Dictionary<ushort, TileTemplate> Templates = new Dictionary<ushort, TileTemplate>();
public string[] EditorTemplateOrder;
static readonly string[] Fields = { "Name", "Id", "SheetSize", "Palette", "Extensions" };
public TileSet() { }
public TileSet(string filepath)
{
var yaml = MiniYaml.DictFromFile(filepath);
// General info
FieldLoader.Load(this, yaml["General"]);
// TerrainTypes
Terrain = yaml["Terrain"].NodesDict.Values
.Select(y => new TerrainTypeInfo(y)).ToDictionary(t => t.Type);
// Templates
Templates = yaml["Templates"].NodesDict.Values
.Select(y => new TileTemplate(y)).ToDictionary(t => t.Id);
}
public void Save(string filepath)
{
var root = new List<MiniYamlNode>();
var gen = new List<MiniYamlNode>();
foreach (var field in Fields)
{
var f = this.GetType().GetField(field);
if (f.GetValue(this) == null)
continue;
gen.Add(new MiniYamlNode(field, FieldSaver.FormatValue(this, f)));
}
root.Add(new MiniYamlNode("General", null, gen));
root.Add(new MiniYamlNode("Terrain", null,
Terrain.Select(t => new MiniYamlNode("TerrainType@{0}".F(t.Value.Type), t.Value.Save())).ToList()));
root.Add(new MiniYamlNode("Templates", null,
Templates.Select(t => new MiniYamlNode("Template@{0}".F(t.Value.Id), t.Value.Save())).ToList()));
root.WriteToFile(filepath);
}
public string GetTerrainType(TileReference<ushort, byte> r)
{
var tt = Templates[r.Type].Tiles;
string ret;
if (!tt.TryGetValue(r.Index, out ret))
return "Clear"; // Default walkable
return ret;
}
}
}