Files
OpenRA/OpenRA.Game/Map/TileSet.cs
RoosterDragon 59b3cd154d Change indexes into tile arrays to be stored as bytes to save memory.
The index value needs only be big enough to handle all defined terrain types. This is a low number so we can save memory by defining it as a byte. This particularly saves memory for the CustomTerrain field in the Map class, which defines a cell layer for the map using tile indexes, so we can reduce the size of that layer 4x as a result.
2014-10-09 20:03:01 +01:00

273 lines
7.0 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.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
namespace OpenRA
{
public class TerrainTypeInfo
{
public readonly string Type;
public readonly string[] TargetTypes = { };
public readonly string[] AcceptsSmudgeType = { };
public readonly bool IsWater = false; // TODO: Remove this
public readonly Color Color;
public readonly string CustomCursor;
public TerrainTypeInfo() { }
public TerrainTypeInfo(MiniYaml my) { FieldLoader.Load(this, my); }
public MiniYaml Save() { return FieldSaver.Save(this); }
}
public class TileTemplate
{
public readonly ushort Id;
public readonly string Image;
public readonly int[] Frames;
public readonly int2 Size;
public readonly bool PickAny;
public readonly string Category;
byte[] tiles;
public TileTemplate(ushort id, string image, int2 size, byte[] tiles)
{
this.Id = id;
this.Image = image;
this.Size = size;
this.tiles = tiles;
}
public TileTemplate(TileSet tileSet, MiniYaml my)
{
FieldLoader.Load(this, my);
tiles = LoadTiles(tileSet, my);
}
byte[] LoadTiles(TileSet tileSet, MiniYaml y)
{
var nodes = y.ToDictionary()["Tiles"].Nodes;
if (!PickAny)
{
var tiles = new byte[Size.X * Size.Y];
for (var i = 0; i < tiles.Length; i++)
tiles[i] = byte.MaxValue;
foreach (var node in nodes)
{
int key;
if (!int.TryParse(node.Key, out key) || key < 0 || key >= tiles.Length)
throw new InvalidDataException("Invalid tile key '{0}' on template '{1}' of tileset '{2}'.".F(node.Key, Id, tileSet.Id));
tiles[key] = tileSet.GetTerrainIndex(node.Value.Value);
}
return tiles;
}
else
{
var tiles = new byte[nodes.Count];
var i = 0;
foreach (var node in nodes)
{
int key;
if (!int.TryParse(node.Key, out key) || key != i++)
throw new InvalidDataException("Invalid tile key '{0}' on template '{1}' of tileset '{2}'.".F(node.Key, Id, tileSet.Id));
tiles[key] = tileSet.GetTerrainIndex(node.Value.Value);
}
return tiles;
}
}
static readonly string[] Fields = { "Id", "Image", "Frames", "Size", "PickAny" };
public byte this[int index]
{
get { return tiles[index]; }
}
public bool Contains(int index)
{
return index >= 0 && index < tiles.Length;
}
public int TilesCount
{
get { return tiles.Length; }
}
public MiniYaml Save(TileSet tileSet)
{
var root = new List<MiniYamlNode>();
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("Tiles", null,
tiles.Select((terrainTypeIndex, templateIndex) => new MiniYamlNode(templateIndex.ToString(), tileSet[terrainTypeIndex].Type)).ToList()));
return new MiniYaml(null, root);
}
}
public class TileSet
{
public readonly string Name;
public readonly string Id;
public readonly int SheetSize = 512;
public readonly string Palette;
public readonly string PlayerPalette;
public readonly string[] Extensions;
public readonly int WaterPaletteRotationBase = 0x60;
public readonly Dictionary<ushort, TileTemplate> Templates = new Dictionary<ushort, TileTemplate>();
public readonly string[] EditorTemplateOrder;
public readonly TerrainTypeInfo[] TerrainInfo;
readonly Dictionary<string, byte> terrainIndexByType = new Dictionary<string, byte>();
readonly byte defaultWalkableTerrainIndex;
static readonly string[] Fields = { "Name", "Id", "SheetSize", "Palette", "Extensions" };
public TileSet(ModData modData, string filepath)
{
var yaml = MiniYaml.DictFromFile(filepath);
// General info
FieldLoader.Load(this, yaml["General"]);
// TerrainTypes
TerrainInfo = yaml["Terrain"].ToDictionary().Values
.Select(y => new TerrainTypeInfo(y))
.OrderBy(tt => tt.Type)
.ToArray();
if (TerrainInfo.Length >= byte.MaxValue)
throw new InvalidDataException("Too many terrain types.");
for (byte i = 0; i < TerrainInfo.Length; i++)
{
var tt = TerrainInfo[i].Type;
if (terrainIndexByType.ContainsKey(tt))
throw new InvalidDataException("Duplicate terrain type '{0}' in '{1}'.".F(tt, filepath));
terrainIndexByType.Add(tt, i);
}
defaultWalkableTerrainIndex = GetTerrainIndex("Clear");
// Templates
Templates = yaml["Templates"].ToDictionary().Values
.Select(y => new TileTemplate(this, y)).ToDictionary(t => t.Id);
}
public TileSet(string name, string id, string palette, string[] extensions, TerrainTypeInfo[] terrainInfo)
{
this.Name = name;
this.Id = id;
this.Palette = palette;
this.Extensions = extensions;
this.TerrainInfo = terrainInfo;
if (TerrainInfo.Length >= byte.MaxValue)
throw new InvalidDataException("Too many terrain types.");
for (byte i = 0; i < terrainInfo.Length; i++)
{
var tt = terrainInfo[i].Type;
if (terrainIndexByType.ContainsKey(tt))
throw new InvalidDataException("Duplicate terrain type '{0}'.".F(tt));
terrainIndexByType.Add(tt, i);
}
defaultWalkableTerrainIndex = GetTerrainIndex("Clear");
}
public TerrainTypeInfo this[byte index]
{
get { return TerrainInfo[index]; }
}
public int TerrainsCount
{
get { return TerrainInfo.Length; }
}
public bool TryGetTerrainIndex(string type, out byte index)
{
return terrainIndexByType.TryGetValue(type, out index);
}
public byte GetTerrainIndex(string type)
{
byte index;
if (terrainIndexByType.TryGetValue(type, out index))
return index;
throw new InvalidDataException("Tileset '{0}' lacks terrain type '{1}'".F(Id, type));
}
public byte GetTerrainIndex(TerrainTile r)
{
var tpl = Templates[r.Type];
if (tpl.Contains(r.Index))
{
var ti = tpl[r.Index];
if (ti != byte.MaxValue)
return ti;
}
return defaultWalkableTerrainIndex;
}
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,
TerrainInfo.Select(t => new MiniYamlNode("TerrainType@{0}".F(t.Type), t.Save())).ToList()));
root.Add(new MiniYamlNode("Templates", null,
Templates.Select(t => new MiniYamlNode("Template@{0}".F(t.Value.Id), t.Value.Save(this))).ToList()));
root.WriteToFile(filepath);
}
public TerrainTypeInfo GetTerrainInfo(TerrainTile r)
{
return this[GetTerrainIndex(r)];
}
}
}