diff --git a/OpenRA.Game/Graphics/Minimap.cs b/OpenRA.Game/Graphics/Minimap.cs index 24141ac9f2..d0ec1b86fd 100644 --- a/OpenRA.Game/Graphics/Minimap.cs +++ b/OpenRA.Game/Graphics/Minimap.cs @@ -32,6 +32,8 @@ namespace OpenRA.Graphics var bitmapData = terrain.LockBits(terrain.Bounds(), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); + var mapTiles = map.MapTiles.Value; + unsafe { int* c = (int*)bitmapData.Scan0; @@ -41,11 +43,9 @@ namespace OpenRA.Graphics { var mapX = x + map.Bounds.Left; var mapY = y + map.Bounds.Top; - var type = tileset.GetTerrainType(map.MapTiles.Value[mapX, mapY]); - if (!tileset.Terrain.ContainsKey(type)) - throw new InvalidDataException("Tileset {0} lacks terraintype {1}".F(tileset.Id, type)); + var type = tileset.GetTerrainInfo(mapTiles[mapX, mapY]); - *(c + (y * bitmapData.Stride >> 2) + x) = tileset.Terrain[type].Color.ToArgb(); + *(c + (y * bitmapData.Stride >> 2) + x) = type.Color.ToArgb(); } } @@ -80,7 +80,7 @@ namespace OpenRA.Graphics if (res == null) continue; - *(c + (y * bitmapData.Stride >> 2) + x) = tileset.Terrain[res].Color.ToArgb(); + *(c + (y * bitmapData.Stride >> 2) + x) = tileset[tileset.GetTerrainIndex(res)].Color.ToArgb(); } } @@ -107,9 +107,9 @@ namespace OpenRA.Graphics var mapX = x + map.Bounds.Left; var mapY = y + map.Bounds.Top; var custom = map.CustomTerrain[mapX, mapY]; - if (custom == null) + if (custom == -1) continue; - *(c + (y * bitmapData.Stride >> 2) + x) = world.TileSet.Terrain[custom].Color.ToArgb(); + *(c + (y * bitmapData.Stride >> 2) + x) = world.TileSet[custom].Color.ToArgb(); } } diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index c7a23519f5..1d99ab4512 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -111,7 +111,7 @@ namespace OpenRA [FieldLoader.Ignore] public Lazy[,]> MapTiles; [FieldLoader.Ignore] public Lazy[,]> MapResources; - [FieldLoader.Ignore] public string[,] CustomTerrain; + [FieldLoader.Ignore] public int[,] CustomTerrain; [FieldLoader.Ignore] Lazy rules; public Ruleset Rules { get { return rules != null ? rules.Value : null; } } @@ -228,7 +228,10 @@ namespace OpenRA NotificationDefinitions = MiniYaml.NodesOrEmpty(yaml, "Notifications"); TranslationDefinitions = MiniYaml.NodesOrEmpty(yaml, "Translations"); - CustomTerrain = new string[MapSize.X, MapSize.Y]; + CustomTerrain = new int[MapSize.X, MapSize.Y]; + for (var x = 0; x < MapSize.X; x++) + for (var y = 0; y < MapSize.Y; y++) + CustomTerrain[x, y] = -1; MapTiles = Exts.Lazy(() => LoadMapTiles()); MapResources = Exts.Lazy(() => LoadResourceTiles()); @@ -529,7 +532,7 @@ namespace OpenRA var template = tileset.Templates[tr.Type]; if (!template.PickAny) continue; - tr.Index = (byte)r.Next(0, template.Tiles.Count); + tr.Index = (byte)r.Next(0, template.TilesCount); MapTiles.Value[i, j] = tr; } } diff --git a/OpenRA.Game/Map/TileSet.cs b/OpenRA.Game/Map/TileSet.cs index 082cd3d49b..ca82e15e3e 100644 --- a/OpenRA.Game/Map/TileSet.cs +++ b/OpenRA.Game/Map/TileSet.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.IO; using System.Linq; using System.Reflection; using OpenRA.Graphics; @@ -41,29 +42,81 @@ namespace OpenRA public readonly bool PickAny; public readonly string Category; - [FieldLoader.LoadUsing("LoadTiles")] - public readonly Dictionary Tiles = new Dictionary(); + int[] tiles; - public TileTemplate() { } - public TileTemplate(MiniYaml my) { FieldLoader.Load(this, my); } - - public TileTemplate(ushort id, string image, int2 size) + public TileTemplate(ushort id, string image, int2 size, int[] tiles) { this.Id = id; this.Image = image; this.Size = size; + this.tiles = tiles; } - static object LoadTiles(MiniYaml y) + public TileTemplate(TileSet tileSet, MiniYaml my) { - return y.ToDictionary()["Tiles"].ToDictionary( - name => byte.Parse(name), - my => my.Value); + FieldLoader.Load(this, my); + + tiles = LoadTiles(tileSet, my); + } + + int[] LoadTiles(TileSet tileSet, MiniYaml y) + { + var nodes = y.ToDictionary()["Tiles"].Nodes; + + if (!PickAny) + { + var tiles = new int[Size.X * Size.Y]; + + for (var i = 0; i < tiles.Length; i++) + tiles[i] = -1; + + 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 int[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 MiniYaml Save() + public int 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(); foreach (var field in Fields) @@ -76,7 +129,7 @@ namespace OpenRA } root.Add(new MiniYamlNode("Tiles", null, - Tiles.Select(x => new MiniYamlNode(x.Key.ToString(), x.Value)).ToList())); + tiles.Select((terrainTypeIndex, templateIndex) => new MiniYamlNode(templateIndex.ToString(), tileSet[terrainTypeIndex].Type)).ToList())); return new MiniYaml(null, root); } @@ -91,10 +144,13 @@ namespace OpenRA public readonly string PlayerPalette; public readonly string[] Extensions; public readonly int WaterPaletteRotationBase = 0x60; - public readonly Dictionary Terrain = new Dictionary(); public readonly Dictionary Templates = new Dictionary(); public readonly string[] EditorTemplateOrder; + readonly TerrainTypeInfo[] terrainInfo; + readonly Dictionary terrainIndexByType = new Dictionary(); + readonly int defaultWalkableTerrainIndex; + static readonly string[] Fields = { "Name", "Id", "SheetSize", "Palette", "Extensions" }; public TileSet(ModData modData, string filepath) @@ -105,20 +161,83 @@ namespace OpenRA FieldLoader.Load(this, yaml["General"]); // TerrainTypes - Terrain = yaml["Terrain"].ToDictionary().Values - .Select(y => new TerrainTypeInfo(y)).ToDictionary(t => t.Type); + terrainInfo = yaml["Terrain"].ToDictionary().Values + .Select(y => new TerrainTypeInfo(y)) + .OrderBy(tt => tt.Type) + .ToArray(); + for (var 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(y)).ToDictionary(t => t.Id); + .Select(y => new TileTemplate(this, y)).ToDictionary(t => t.Id); } - public TileSet(string name, string id, string palette, string[] extensions) + 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; + + for (var 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[int index] + { + get { return terrainInfo[index]; } + } + + public int TerrainsCount + { + get { return terrainInfo.Length; } + } + + public bool TryGetTerrainIndex(string type, out int index) + { + return terrainIndexByType.TryGetValue(type, out index); + } + + public int GetTerrainIndex(string type) + { + int index; + if (terrainIndexByType.TryGetValue(type, out index)) + return index; + + throw new InvalidDataException("Tileset '{0}' lacks terrain type '{1}'".F(Id, type)); + } + + public int GetTerrainIndex(TileReference r) + { + var tpl = Templates[r.Type]; + + if (tpl.Contains(r.Index)) + { + var ti = tpl[r.Index]; + if (ti != -1) + return ti; + } + + return defaultWalkableTerrainIndex; } public void Save(string filepath) @@ -138,21 +257,16 @@ namespace OpenRA 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())); + 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())).ToList())); + Templates.Select(t => new MiniYamlNode("Template@{0}".F(t.Value.Id), t.Value.Save(this))).ToList())); root.WriteToFile(filepath); } - public string GetTerrainType(TileReference r) + public TerrainTypeInfo GetTerrainInfo(TileReference r) { - var tt = Templates[r.Type].Tiles; - string ret; - if (!tt.TryGetValue(r.Index, out ret)) - return "Clear"; // Default walkable - - return ret; + return terrainInfo[GetTerrainIndex(r)]; } } } diff --git a/OpenRA.Game/Traits/World/ResourceLayer.cs b/OpenRA.Game/Traits/World/ResourceLayer.cs index 1d024de7f8..e9f60a31bb 100644 --- a/OpenRA.Game/Traits/World/ResourceLayer.cs +++ b/OpenRA.Game/Traits/World/ResourceLayer.cs @@ -156,7 +156,7 @@ namespace OpenRA.Traits CellContents CreateResourceCell(ResourceType t, CPos p) { - world.Map.CustomTerrain[p.X, p.Y] = t.Info.TerrainType; + world.Map.CustomTerrain[p.X, p.Y] = world.TileSet.GetTerrainIndex(t.Info.TerrainType); return new CellContents { Type = t, @@ -194,7 +194,7 @@ namespace OpenRA.Traits if (--content[p.X, p.Y].Density < 0) { content[p.X, p.Y] = EmptyCell; - world.Map.CustomTerrain[p.X, p.Y] = null; + world.Map.CustomTerrain[p.X, p.Y] = -1; } if (!dirty.Contains(p)) @@ -211,7 +211,7 @@ namespace OpenRA.Traits // Clear cell content[p.X, p.Y] = EmptyCell; - world.Map.CustomTerrain[p.X, p.Y] = null; + world.Map.CustomTerrain[p.X, p.Y] = -1; if (!dirty.Contains(p)) dirty.Add(p); diff --git a/OpenRA.Game/WorldUtils.cs b/OpenRA.Game/WorldUtils.cs index 8f0fbee3cb..968dd636dc 100644 --- a/OpenRA.Game/WorldUtils.cs +++ b/OpenRA.Game/WorldUtils.cs @@ -88,15 +88,15 @@ namespace OpenRA public const int MaxRange = 50; static List[] TilesByDistance = InitTilesByDistance(MaxRange); - public static string GetTerrainType(this World world, CPos cell) + public static int GetTerrainIndex(this World world, CPos cell) { var custom = world.Map.CustomTerrain[cell.X, cell.Y]; - return custom ?? world.TileSet.GetTerrainType(world.Map.MapTiles.Value[cell.X, cell.Y]); + return custom != -1 ? custom : world.TileSet.GetTerrainIndex(world.Map.MapTiles.Value[cell.X, cell.Y]); } public static TerrainTypeInfo GetTerrainInfo(this World world, CPos cell) { - return world.TileSet.Terrain[world.GetTerrainType(cell)]; + return world.TileSet[world.GetTerrainIndex(cell)]; } public static CPos ClampToWorld(this World world, CPos xy) diff --git a/OpenRA.Mods.D2k/DamagedWithoutFoundation.cs b/OpenRA.Mods.D2k/DamagedWithoutFoundation.cs index a1b7e3c759..6cbf576513 100644 --- a/OpenRA.Mods.D2k/DamagedWithoutFoundation.cs +++ b/OpenRA.Mods.D2k/DamagedWithoutFoundation.cs @@ -49,7 +49,7 @@ namespace OpenRA.Mods.Cnc foreach (var kv in self.OccupiesSpace.OccupiedCells()) { totalTiles++; - if (info.SafeTerrain.Contains(self.World.GetTerrainType(kv.First))) + if (info.SafeTerrain.Contains(self.World.GetTerrainInfo(kv.First).Type)) safeTiles++; } diff --git a/OpenRA.Mods.RA/AI/HackyAI.cs b/OpenRA.Mods.RA/AI/HackyAI.cs index a0a1f1f453..9e4a1a555c 100644 --- a/OpenRA.Mods.RA/AI/HackyAI.cs +++ b/OpenRA.Mods.RA/AI/HackyAI.cs @@ -102,7 +102,7 @@ namespace OpenRA.Mods.RA.AI BuildingInfo rallypointTestBuilding; internal readonly HackyAIInfo Info; - string[] resourceTypes; + HashSet resourceTypeIndices; RushFuzzy rushFuzzy = new RushFuzzy(); @@ -152,8 +152,10 @@ namespace OpenRA.Mods.RA.AI random = new MersenneTwister((int)p.PlayerActor.ActorID); - resourceTypes = Map.Rules.Actors["world"].Traits.WithInterface() - .Select(t => t.TerrainType).ToArray(); + resourceTypeIndices = new HashSet( + Map.Rules.Actors["world"].Traits + .WithInterface() + .Select(t => world.TileSet.GetTerrainIndex(t.TerrainType))); } static int GetPowerProvidedBy(ActorInfo building) @@ -364,7 +366,7 @@ namespace OpenRA.Mods.RA.AI case BuildingType.Refinery: var tilesPos = world.FindTilesInCircle(baseCenter, MaxBaseDistance) - .Where(a => resourceTypes.Contains(world.GetTerrainType(new CPos(a.X, a.Y)))); + .Where(a => resourceTypeIndices.Contains(world.GetTerrainIndex(new CPos(a.X, a.Y)))); if (tilesPos.Any()) { var pos = tilesPos.MinBy(a => (a.CenterPosition - baseCenter.CenterPosition).LengthSquared); diff --git a/OpenRA.Mods.RA/Air/Aircraft.cs b/OpenRA.Mods.RA/Air/Aircraft.cs index e819c219a7..b633bdb6f7 100755 --- a/OpenRA.Mods.RA/Air/Aircraft.cs +++ b/OpenRA.Mods.RA/Air/Aircraft.cs @@ -171,7 +171,7 @@ namespace OpenRA.Mods.RA.Air if (self.World.ActorMap.AnyUnitsAt(cell)) return false; - var type = self.World.GetTerrainType(cell); + var type = self.World.GetTerrainInfo(cell).Type; return info.LandableTerrainTypes.Contains(type); } diff --git a/OpenRA.Mods.RA/Bridge.cs b/OpenRA.Mods.RA/Bridge.cs index 7d78e8e802..aa34689d44 100644 --- a/OpenRA.Mods.RA/Bridge.cs +++ b/OpenRA.Mods.RA/Bridge.cs @@ -94,11 +94,11 @@ namespace OpenRA.Mods.RA self.World.Map.CustomTerrain[c.X, c.Y] = GetTerrainType(c); } - string GetTerrainType(CPos cell) + int GetTerrainType(CPos cell) { var dx = cell - self.Location; var index = dx.X + self.World.TileSet.Templates[template].Size.X * dx.Y; - return self.World.TileSet.GetTerrainType(new TileReference(template, (byte)index)); + return self.World.TileSet.GetTerrainIndex(new TileReference(template, (byte)index)); } public void LinkNeighbouringBridges(World world, BridgeLayer bridges) diff --git a/OpenRA.Mods.RA/Buildings/Bib.cs b/OpenRA.Mods.RA/Buildings/Bib.cs index 95fb2ab413..ec5aeec250 100755 --- a/OpenRA.Mods.RA/Buildings/Bib.cs +++ b/OpenRA.Mods.RA/Buildings/Bib.cs @@ -52,7 +52,7 @@ namespace OpenRA.Mods.RA.Buildings var cellOffset = new CVec(i % width, i / width + bibOffset); // Some mods may define terrain-specific bibs - var terrain = self.World.GetTerrainType(location + cellOffset); + var terrain = self.World.GetTerrainInfo(location + cellOffset).Type; var testSequence = info.Sequence + "-" + terrain; var sequence = anim.HasSequence(testSequence) ? testSequence : info.Sequence; anim.PlayFetchIndex(sequence, () => index); diff --git a/OpenRA.Mods.RA/Buildings/LaysTerrain.cs b/OpenRA.Mods.RA/Buildings/LaysTerrain.cs index c7455458f9..3dbe0b51f6 100755 --- a/OpenRA.Mods.RA/Buildings/LaysTerrain.cs +++ b/OpenRA.Mods.RA/Buildings/LaysTerrain.cs @@ -54,34 +54,34 @@ namespace OpenRA.Mods.RA.Buildings foreach (var c in FootprintUtils.Tiles(self)) { // Only place on allowed terrain types - if (!info.TerrainTypes.Contains(self.World.GetTerrainType(c))) + if (!info.TerrainTypes.Contains(self.World.GetTerrainInfo(c).Type)) continue; // Don't place under other buildings or custom terrain - if (bi.GetBuildingAt(c) != self || self.World.Map.CustomTerrain[c.X, c.Y] != null) + if (bi.GetBuildingAt(c) != self || self.World.Map.CustomTerrain[c.X, c.Y] != -1) continue; - var index = template.Tiles.Keys.Random(Game.CosmeticRandom); - layer.AddTile(c, new TileReference(template.Id, index)); + var index = Game.CosmeticRandom.Next(template.TilesCount); + layer.AddTile(c, new TileReference(template.Id, (byte)index)); } return; } var origin = self.Location + info.Offset; - foreach (var i in template.Tiles.Keys) + for (var i = 0; i < template.TilesCount; i++) { var c = origin + new CVec(i % template.Size.X, i / template.Size.X); // Only place on allowed terrain types - if (!info.TerrainTypes.Contains(self.World.GetTerrainType(c))) + if (!info.TerrainTypes.Contains(self.World.GetTerrainInfo(c).Type)) continue; // Don't place under other buildings or custom terrain - if (bi.GetBuildingAt(c) != self || self.World.Map.CustomTerrain[c.X, c.Y] != null) + if (bi.GetBuildingAt(c) != self || self.World.Map.CustomTerrain[c.X, c.Y] != -1) continue; - layer.AddTile(c, new TileReference(template.Id, i)); + layer.AddTile(c, new TileReference(template.Id, (byte)i)); } } } diff --git a/OpenRA.Mods.RA/Buildings/Util.cs b/OpenRA.Mods.RA/Buildings/Util.cs index 9021765a23..34dc19d896 100644 --- a/OpenRA.Mods.RA/Buildings/Util.cs +++ b/OpenRA.Mods.RA/Buildings/Util.cs @@ -26,7 +26,7 @@ namespace OpenRA.Mods.RA.Buildings if (world.WorldActor.Trait().GetBuildingAt(a) != null) return false; if (world.ActorMap.GetUnitsAt(a).Any(b => b != toIgnore)) return false; - return world.Map.IsInMap(a) && bi.TerrainTypes.Contains(world.GetTerrainType(a)); + return world.Map.IsInMap(a) && bi.TerrainTypes.Contains(world.GetTerrainInfo(a).Type); } public static bool CanPlaceBuilding(this World world, string name, BuildingInfo building, CPos topLeft, Actor toIgnore) diff --git a/OpenRA.Mods.RA/Crate.cs b/OpenRA.Mods.RA/Crate.cs index cbe0b8f7a4..dbb798ce48 100644 --- a/OpenRA.Mods.RA/Crate.cs +++ b/OpenRA.Mods.RA/Crate.cs @@ -93,7 +93,7 @@ namespace OpenRA.Mods.RA { if (!self.World.Map.IsInMap(cell.X, cell.Y)) return false; - var type = self.World.GetTerrainType(cell); + var type = self.World.GetTerrainInfo(cell).Type; if (!info.TerrainTypes.Contains(type)) return false; diff --git a/OpenRA.Mods.RA/CrateSpawner.cs b/OpenRA.Mods.RA/CrateSpawner.cs index c658f20c04..6956863acd 100644 --- a/OpenRA.Mods.RA/CrateSpawner.cs +++ b/OpenRA.Mods.RA/CrateSpawner.cs @@ -117,7 +117,7 @@ namespace OpenRA.Mods.RA var p = self.World.ChooseRandomCell(self.World.SharedRandom); // Is this valid terrain? - var terrainType = self.World.GetTerrainType(p); + var terrainType = self.World.GetTerrainInfo(p).Type; if (!(inWater ? info.ValidWater : info.ValidGround).Contains(terrainType)) continue; diff --git a/OpenRA.Mods.RA/Effects/Missile.cs b/OpenRA.Mods.RA/Effects/Missile.cs index 22f27442c3..994dd98cf0 100755 --- a/OpenRA.Mods.RA/Effects/Missile.cs +++ b/OpenRA.Mods.RA/Effects/Missile.cs @@ -162,7 +162,7 @@ namespace OpenRA.Mods.RA.Effects || (info.RangeLimit != 0 && ticks > info.RangeLimit) // Ran out of fuel || (!info.High && world.ActorMap.GetUnitsAt(cell) .Any(a => a.HasTrait())) // Hit a wall - || (!string.IsNullOrEmpty(info.BoundToTerrainType) && world.GetTerrainType(cell) != info.BoundToTerrainType); // Hit incompatible terrain + || (!string.IsNullOrEmpty(info.BoundToTerrainType) && world.GetTerrainInfo(cell).Type != info.BoundToTerrainType); // Hit incompatible terrain if (shouldExplode) Explode(world); diff --git a/OpenRA.Mods.RA/Husk.cs b/OpenRA.Mods.RA/Husk.cs index ef7065128b..3d2144ed03 100644 --- a/OpenRA.Mods.RA/Husk.cs +++ b/OpenRA.Mods.RA/Husk.cs @@ -58,7 +58,7 @@ namespace OpenRA.Mods.RA if (!self.World.Map.IsInMap(cell.X, cell.Y)) return false; - if (!info.AllowedTerrain.Contains(self.World.GetTerrainType(cell))) + if (!info.AllowedTerrain.Contains(self.World.GetTerrainInfo(cell).Type)) return false; if (!checkTransientActors) diff --git a/OpenRA.Mods.RA/Move/Mobile.cs b/OpenRA.Mods.RA/Move/Mobile.cs index 62d14b1017..f6b4117381 100755 --- a/OpenRA.Mods.RA/Move/Mobile.cs +++ b/OpenRA.Mods.RA/Move/Mobile.cs @@ -48,16 +48,55 @@ namespace OpenRA.Mods.RA.Move var cost = nodesDict.ContainsKey("PathingCost") ? FieldLoader.GetValue("cost", nodesDict["PathingCost"].Value) : (int)(10000 / speed); - ret.Add(t.Key, new TerrainInfo { Speed = speed, Cost = cost }); + ret.Add(t.Key, new TerrainInfo(speed, cost)); } return ret; } + TerrainInfo[] LoadTilesetSpeeds(TileSet tileSet) + { + var info = new TerrainInfo[tileSet.TerrainsCount]; + for (var i = 0; i < info.Length; i++) + info[i] = TerrainInfo.Impassable; + + foreach (var kvp in TerrainSpeeds) + { + int index; + if (tileSet.TryGetTerrainIndex(kvp.Key, out index)) + info[index] = kvp.Value; + } + + return info; + } + public class TerrainInfo { - public int Cost = int.MaxValue; - public decimal Speed = 0; + public static readonly TerrainInfo Impassable = new TerrainInfo(); + + public readonly int Cost; + public readonly decimal Speed; + + public TerrainInfo() + { + Cost = int.MaxValue; + Speed = 0; + } + + public TerrainInfo(decimal speed, int cost) + { + Speed = speed; + Cost = cost; + } + } + + public readonly Cache TilesetTerrainInfo; + public readonly Cache TilesetMovementClass; + + public MobileInfo() + { + TilesetTerrainInfo = new Cache(LoadTilesetSpeeds); + TilesetMovementClass = new Cache(CalculateTilesetMovementClass); } public int MovementCostForCell(World world, CPos cell) @@ -65,20 +104,22 @@ namespace OpenRA.Mods.RA.Move if (!world.Map.IsInMap(cell.X, cell.Y)) return int.MaxValue; - var type = world.GetTerrainType(cell); - if (!TerrainSpeeds.ContainsKey(type)) + var index = world.GetTerrainIndex(cell); + if (index == -1) return int.MaxValue; - return TerrainSpeeds[type].Cost; + return TilesetTerrainInfo[world.TileSet][index].Cost; + } + + public int CalculateTilesetMovementClass(TileSet tileset) + { + /* collect our ability to cross *all* terraintypes, in a bitvector */ + return TilesetTerrainInfo[tileset].Select(ti => ti.Cost < int.MaxValue).ToBits(); } public int GetMovementClass(TileSet tileset) { - /* collect our ability to cross *all* terraintypes, in a bitvector */ - var passability = tileset.Terrain.OrderBy(t => t.Key) - .Select(t => TerrainSpeeds.ContainsKey(t.Key) && TerrainSpeeds[t.Key].Cost < int.MaxValue); - - return passability.ToBits(); + return TilesetMovementClass[tileset]; } public static readonly Dictionary SubCellOffsets = new Dictionary() @@ -440,12 +481,15 @@ namespace OpenRA.Mods.RA.Move public int MovementSpeedForCell(Actor self, CPos cell) { - var type = self.World.GetTerrainType(cell); - - if (!Info.TerrainSpeeds.ContainsKey(type)) + var index = self.World.GetTerrainIndex(cell); + if (index == -1) return 0; - decimal speed = Info.Speed * Info.TerrainSpeeds[type].Speed; + var speed = Info.TilesetTerrainInfo[self.World.TileSet][index].Speed; + if (speed == decimal.Zero) + return 0; + + speed *= Info.Speed; foreach (var t in self.TraitsImplementing()) speed *= t.GetSpeedModifier(); return (int)(speed / 100); diff --git a/OpenRA.Mods.RA/RadarColorFromTerrain.cs b/OpenRA.Mods.RA/RadarColorFromTerrain.cs index 930ec77f94..2c3033ab4d 100644 --- a/OpenRA.Mods.RA/RadarColorFromTerrain.cs +++ b/OpenRA.Mods.RA/RadarColorFromTerrain.cs @@ -25,7 +25,7 @@ namespace OpenRA.Mods.RA public RadarColorFromTerrain(Actor self, string terrain) { - c = self.World.TileSet.Terrain[terrain].Color; + c = self.World.TileSet[self.World.TileSet.GetTerrainIndex(terrain)].Color; } public bool VisibleOnRadar(Actor self) { return true; } diff --git a/OpenRA.Mods.RA/Render/RenderLandingCraft.cs b/OpenRA.Mods.RA/Render/RenderLandingCraft.cs index 37c6b26d2a..eb210b6c44 100644 --- a/OpenRA.Mods.RA/Render/RenderLandingCraft.cs +++ b/OpenRA.Mods.RA/Render/RenderLandingCraft.cs @@ -46,7 +46,7 @@ namespace OpenRA.Mods.RA.Render return false; return cargo.CurrentAdjacentCells - .Any(c => self.World.Map.IsInMap(c) && info.OpenTerrainTypes.Contains(self.World.GetTerrainType(c))); + .Any(c => self.World.Map.IsInMap(c) && info.OpenTerrainTypes.Contains(self.World.GetTerrainInfo(c).Type)); } void Open() diff --git a/OpenRA.Mods.RA/SupportPowers/ChronoshiftPower.cs b/OpenRA.Mods.RA/SupportPowers/ChronoshiftPower.cs index 1221d8e1cf..92cf55cba5 100644 --- a/OpenRA.Mods.RA/SupportPowers/ChronoshiftPower.cs +++ b/OpenRA.Mods.RA/SupportPowers/ChronoshiftPower.cs @@ -71,36 +71,18 @@ namespace OpenRA.Mods.RA var range = ((ChronoshiftPowerInfo)Info).Range; var sourceTiles = self.World.FindTilesInCircle(xy, range); var destTiles = self.World.FindTilesInCircle(sourceLocation, range); - var sourceTerrain = new List(); - var destTerrain = new List(); - int j = 0; - foreach (var t in sourceTiles) + using (var se = sourceTiles.GetEnumerator()) + using (var de = destTiles.GetEnumerator()) + while (se.MoveNext() && de.MoveNext()) { - j = j + 1; - if (!self.Owner.Shroud.IsExplored(t)) - return false; - sourceTerrain.Add(self.World.GetTerrainType(t)); - } + var a = se.Current; + var b = de.Current; - j = 0; - foreach (var t in destTiles) - { - j = j + 1; - if (!self.Owner.Shroud.IsExplored(t)) + if (!self.Owner.Shroud.IsExplored(a) || !self.Owner.Shroud.IsExplored(b)) return false; - self.World.GetTerrainType(t); - destTerrain.Add(self.World.GetTerrainType(t)); - } - - // HACK but I don't want to write a comparison function - if (sourceTerrain.Count != destTerrain.Count) - return false; - - for (int i = 0; i < sourceTerrain.Count; i++) - { - if (!sourceTerrain[i].Equals(destTerrain[i])) + if (self.World.GetTerrainIndex(a) != self.World.GetTerrainIndex(b)) return false; } diff --git a/OpenRA.Mods.RA/World/BuildableTerrainLayer.cs b/OpenRA.Mods.RA/World/BuildableTerrainLayer.cs index b8be9a45d4..a452102d8e 100644 --- a/OpenRA.Mods.RA/World/BuildableTerrainLayer.cs +++ b/OpenRA.Mods.RA/World/BuildableTerrainLayer.cs @@ -38,7 +38,7 @@ namespace OpenRA.Mods.RA public void AddTile(CPos cell, TileReference tile) { - map.CustomTerrain[cell.X, cell.Y] = tileset.GetTerrainType(tile); + map.CustomTerrain[cell.X, cell.Y] = tileset.GetTerrainIndex(tile); // Terrain tiles define their origin at the topleft var s = theater.TileSprite(tile); diff --git a/OpenRA.Mods.RA/World/DomainIndex.cs b/OpenRA.Mods.RA/World/DomainIndex.cs index 6ca131fe07..994505913b 100644 --- a/OpenRA.Mods.RA/World/DomainIndex.cs +++ b/OpenRA.Mods.RA/World/DomainIndex.cs @@ -59,10 +59,6 @@ namespace OpenRA.Mods.RA int[,] domains; Dictionary> transientConnections; - // Each terrain has an offset corresponding to its location in a - // movement class bitmask. This caches each offset. - Dictionary terrainOffsets; - public MovementClassDomainIndex(World world, uint movementClass) { bounds = world.Map.Bounds; @@ -70,14 +66,6 @@ namespace OpenRA.Mods.RA domains = new int[(bounds.Width + bounds.X), (bounds.Height + bounds.Y)]; transientConnections = new Dictionary>(); - terrainOffsets = new Dictionary(); - var terrains = world.TileSet.Terrain.OrderBy(t => t.Key).ToList(); - foreach (var terrain in terrains) - { - var terrainOffset = terrains.FindIndex(x => x.Key == terrain.Key); - terrainOffsets[terrain.Key] = terrainOffset; - } - BuildDomains(world); } @@ -181,8 +169,7 @@ namespace OpenRA.Mods.RA bool CanTraverseTile(World world, CPos p) { - var currentTileType = WorldUtils.GetTerrainType(world, p); - var terrainOffset = terrainOffsets[currentTileType]; + var terrainOffset = world.GetTerrainIndex(p); return (movementClass & (1 << terrainOffset)) > 0; } diff --git a/OpenRA.TilesetBuilder/FormBuilder.cs b/OpenRA.TilesetBuilder/FormBuilder.cs index a58f30d7f0..299c04b02f 100644 --- a/OpenRA.TilesetBuilder/FormBuilder.cs +++ b/OpenRA.TilesetBuilder/FormBuilder.cs @@ -367,7 +367,8 @@ namespace OpenRA.TilesetBuilder name: tilesetName, id: tilesetID.ToUpper(), palette: tilesetPalette.ToLower(), - extensions: new string[] { ext[0], ext[1] } + extensions: new string[] { ext[0], ext[1] }, + terrainInfo: TerrainType ); // List of files to add to the mix file @@ -381,30 +382,25 @@ namespace OpenRA.TilesetBuilder foreach (var t in surface1.Templates) fileList.Add(ExportTemplate(t, surface1.Templates.IndexOf(t), tileset.Extensions.First(), dir)); - // Add the terraintypes - foreach (var tt in TerrainType) - { - tileset.Terrain.Add(tt.Type, tt); - } - // Add the templates ushort cur = 0; foreach (var tp in surface1.Templates) { + var tiles = new int[tp.Width * tp.Height]; + foreach (var t in tp.Cells) + { + string ttype = TerrainType[surface1.TerrainTypes[t.Key.X, t.Key.Y]].Type; + var idx = (t.Key.X - tp.Left) + tp.Width * (t.Key.Y - tp.Top); + tiles[idx] = tileset.GetTerrainIndex(ttype); + } + var template = new TileTemplate( id: cur, image: "{0}{1:00}".F(txtTilesetName.Text, cur), - size: new int2(tp.Width, tp.Height) + size: new int2(tp.Width, tp.Height), + tiles: tiles ); - foreach (var t in tp.Cells) - { - string ttype = "Clear"; - ttype = TerrainType[surface1.TerrainTypes[t.Key.X, t.Key.Y]].Type; - var idx = (t.Key.X - tp.Left) + tp.Width * (t.Key.Y - tp.Top); - template.Tiles.Add((byte)idx, ttype); - } - tileset.Templates.Add(cur, template); cur++; }