Merge pull request #5629 from pchote/celllayers

Introduce CellLayer and CellRegion abstractions.
This commit is contained in:
Matthias Mailänder
2014-06-27 12:27:30 +02:00
49 changed files with 875 additions and 659 deletions

View File

@@ -37,16 +37,15 @@ namespace OpenRA.Editor
for (var u = 0; u < template.Size.X; u++)
for (var v = 0; v < template.Size.Y; v++)
{
if (surface.Map.IsInMap(new CVec(u, v) + pos))
var cell = pos + new CVec(u, v);
if (surface.Map.Contains(cell))
{
var z = u + v * template.Size.X;
if (tile[z].Length > 0)
surface.Map.MapTiles.Value[u + pos.X, v + pos.Y] =
new TileReference<ushort, byte>
{
Type = brushTemplate.N,
Index = template.PickAny ? (byte)((u + pos.X) % 4 + ((v + pos.Y) % 4) * 4) : (byte)z,
};
{
var index = template.PickAny ? (byte)((u + pos.X) % 4 + ((v + pos.Y) % 4) * 4) : (byte)z;
surface.Map.MapTiles.Value[cell] = new TerrainTile(brushTemplate.N, index);
}
var ch = new int2((pos.X + u) / Surface.ChunkSize, (pos.Y + v) / Surface.ChunkSize);
if (surface.Chunks.ContainsKey(ch))
@@ -70,14 +69,15 @@ namespace OpenRA.Editor
void FloodFillWithBrush(Surface s, CPos pos)
{
var queue = new Queue<CPos>();
var replace = s.Map.MapTiles.Value[pos.X, pos.Y];
var replace = s.Map.MapTiles.Value[pos];
var touched = new bool[s.Map.MapSize.X, s.Map.MapSize.Y];
Action<int, int> maybeEnqueue = (x, y) =>
{
if (s.Map.IsInMap(x, y) && !touched[x, y])
var c = new CPos(x, y);
if (s.Map.Contains(c) && !touched[x, y])
{
queue.Enqueue(new CPos(x, y));
queue.Enqueue(c);
touched[x, y] = true;
}
};
@@ -86,7 +86,7 @@ namespace OpenRA.Editor
while (queue.Count > 0)
{
var p = queue.Dequeue();
if (s.Map.MapTiles.Value[p.X, p.Y].Type != replace.Type)
if (s.Map.MapTiles.Value[p].Type != replace.Type)
continue;
var a = FindEdge(s, p, new CVec(-1, 0), replace);
@@ -94,10 +94,10 @@ namespace OpenRA.Editor
for (var x = a.X; x <= b.X; x++)
{
s.Map.MapTiles.Value[x, p.Y] = new TileReference<ushort, byte> { Type = brushTemplate.N, Index = (byte)0 };
if (s.Map.MapTiles.Value[x, p.Y - 1].Type == replace.Type)
s.Map.MapTiles.Value[new CPos(x, p.Y)] = new TerrainTile(brushTemplate.N, (byte)0);
if (s.Map.MapTiles.Value[new CPos(x, p.Y - 1)].Type == replace.Type)
maybeEnqueue(x, p.Y - 1);
if (s.Map.MapTiles.Value[x, p.Y + 1].Type == replace.Type)
if (s.Map.MapTiles.Value[new CPos(x, p.Y + 1)].Type == replace.Type)
maybeEnqueue(x, p.Y + 1);
}
}
@@ -107,13 +107,13 @@ namespace OpenRA.Editor
s.Chunks.Clear();
}
static CPos FindEdge(Surface s, CPos p, CVec d, TileReference<ushort, byte> replace)
static CPos FindEdge(Surface s, CPos p, CVec d, TerrainTile replace)
{
for (;;)
{
var q = p + d;
if (!s.Map.IsInMap(q)) return p;
if (s.Map.MapTiles.Value[q.X, q.Y].Type != replace.Type) return p;
if (!s.Map.Contains(q)) return p;
if (s.Map.MapTiles.Value[q].Type != replace.Type) return p;
p = q;
}
}

View File

@@ -641,7 +641,8 @@ namespace OpenRA.Editor
for (var i = 0; i < surface1.Map.MapSize.X; i++)
for (var j = 0; j < surface1.Map.MapSize.Y; j++)
{
if (surface1.Map.MapResources.Value[i, j].Type != 0)
var cell = new CPos(i, j);
if (surface1.Map.MapResources.Value[cell].Type != 0)
totalResource += GetResourceValue(i, j);
}
@@ -654,9 +655,12 @@ namespace OpenRA.Editor
for (var u = -1; u < 2; u++)
for (var v = -1; v < 2; v++)
{
if (!surface1.Map.IsInMap(new CPos(x + u, y + v)))
var cell = new CPos(x + u, y + v);
if (!surface1.Map.Contains(cell))
continue;
if (surface1.Map.MapResources.Value[x + u, y + v].Type == resourceType)
if (surface1.Map.MapResources.Value[cell].Type == resourceType)
++sum;
}
@@ -666,7 +670,7 @@ namespace OpenRA.Editor
int GetResourceValue(int x, int y)
{
var imageLength = 0;
int type = surface1.Map.MapResources.Value[x, y].Type;
var type = surface1.Map.MapResources.Value[new CPos(x, y)].Type;
var template = surface1.ResourceTemplates.FirstOrDefault(a => a.Value.Info.ResourceType == type).Value;
if (type == 1)
imageLength = 12;

View File

@@ -21,12 +21,9 @@ namespace OpenRA.Editor
public void Apply(Surface surface)
{
surface.Map.MapResources.Value[surface.GetBrushLocation().X, surface.GetBrushLocation().Y]
= new TileReference<byte, byte>
{
Type = (byte)resourceTemplate.Info.ResourceType,
Index = (byte)random.Next(resourceTemplate.Info.MaxDensity)
};
var type = (byte)resourceTemplate.Info.ResourceType;
var index = (byte)random.Next(resourceTemplate.Info.MaxDensity);
surface.Map.MapResources.Value[surface.GetBrushLocation()] = new ResourceTile(type, index);
var ch = new int2(surface.GetBrushLocation().X / Surface.ChunkSize,
surface.GetBrushLocation().Y / Surface.ChunkSize);

View File

@@ -62,8 +62,8 @@ namespace OpenRA.Editor
public bool ShowRuler;
public bool IsPaste { get { return TileSelection != null && ResourceSelection != null; } }
public TileReference<ushort, byte>[,] TileSelection;
public TileReference<byte, byte>[,] ResourceSelection;
public TerrainTile[,] TileSelection;
public ResourceTile[,] ResourceSelection;
public CPos SelectionStart;
public CPos SelectionEnd;
@@ -206,9 +206,9 @@ namespace OpenRA.Editor
var key = Map.Actors.Value.FirstOrDefault(a => a.Value.Location() == brushLocation);
if (key.Key != null) Map.Actors.Value.Remove(key.Key);
if (Map.MapResources.Value[brushLocation.X, brushLocation.Y].Type != 0)
if (Map.MapResources.Value[brushLocation].Type != 0)
{
Map.MapResources.Value[brushLocation.X, brushLocation.Y] = new TileReference<byte, byte>();
Map.MapResources.Value[brushLocation] = new ResourceTile(0, 0);
var ch = new int2(brushLocation.X / ChunkSize, brushLocation.Y / ChunkSize);
if (Chunks.ContainsKey(ch))
{
@@ -271,7 +271,8 @@ namespace OpenRA.Editor
for (var i = 0; i < ChunkSize; i++)
for (var j = 0; j < ChunkSize; j++)
{
var tr = Map.MapTiles.Value[u * ChunkSize + i, v * ChunkSize + j];
var cell = new CPos(u * ChunkSize + i, v * ChunkSize + j);
var tr = Map.MapTiles.Value[cell];
var tile = TileSetRenderer.Data(tr.Type);
var index = (tr.Index < tile.Count) ? tr.Index : (byte)0;
var rawImage = tile[index];
@@ -279,9 +280,9 @@ namespace OpenRA.Editor
for (var y = 0; y < TileSetRenderer.TileSize; y++)
p[(j * TileSetRenderer.TileSize + y) * stride + i * TileSetRenderer.TileSize + x] = Palette.GetColor(rawImage[x + TileSetRenderer.TileSize * y]).ToArgb();
if (Map.MapResources.Value[u * ChunkSize + i, v * ChunkSize + j].Type != 0)
if (Map.MapResources.Value[cell].Type != 0)
{
var resourceImage = ResourceTemplates[Map.MapResources.Value[u * ChunkSize + i, v * ChunkSize + j].Type].Bitmap;
var resourceImage = ResourceTemplates[Map.MapResources.Value[cell].Type].Bitmap;
var srcdata = resourceImage.LockBits(resourceImage.Bounds(),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
@@ -508,16 +509,17 @@ namespace OpenRA.Editor
var width = Math.Abs((start - end).X);
var height = Math.Abs((start - end).Y);
TileSelection = new TileReference<ushort, byte>[width, height];
ResourceSelection = new TileReference<byte, byte>[width, height];
TileSelection = new TerrainTile[width, height];
ResourceSelection = new ResourceTile[width, height];
for (var x = 0; x < width; x++)
{
for (var y = 0; y < height; y++)
{
// TODO: crash prevention
TileSelection[x, y] = Map.MapTiles.Value[start.X + x, start.Y + y];
ResourceSelection[x, y] = Map.MapResources.Value[start.X + x, start.Y + y];
var cell = new CPos(start.X + x, start.Y + y);
TileSelection[x, y] = Map.MapTiles.Value[cell];
ResourceSelection[x, y] = Map.MapResources.Value[cell];
}
}
}
@@ -534,10 +536,11 @@ namespace OpenRA.Editor
{
var mapX = loc.X + x;
var mapY = loc.Y + y;
var cell = new CPos(mapX, mapY);
// TODO: crash prevention for outside of bounds
Map.MapTiles.Value[mapX, mapY] = TileSelection[x, y];
Map.MapResources.Value[mapX, mapY] = ResourceSelection[x, y];
Map.MapTiles.Value[cell] = TileSelection[x, y];
Map.MapResources.Value[cell] = ResourceSelection[x, y];
var ch = new int2(mapX / ChunkSize, mapY / ChunkSize);
if (Chunks.ContainsKey(ch))

View File

@@ -180,7 +180,7 @@ namespace OpenRA.GameRules
if (target.Type == TargetType.Terrain)
{
var cell = target.CenterPosition.ToCPos();
if (!world.Map.IsInMap(cell))
if (!world.Map.Contains(cell))
return false;
var cellInfo = world.Map.GetTerrainInfo(cell);

View File

@@ -74,8 +74,9 @@ namespace OpenRA.Graphics
continue;
var res = resourceRules.Actors["world"].Traits.WithInterface<ResourceTypeInfo>()
.Where(t => t.ResourceType == map.MapResources.Value[mapX, mapY].Type)
.Where(t => t.ResourceType == map.MapResources.Value[mapX, mapY].Type)
.Select(t => t.TerrainType).FirstOrDefault();
if (res == null)
continue;
@@ -135,7 +136,7 @@ namespace OpenRA.Graphics
var color = t.Trait.RadarSignatureColor(t.Actor);
foreach (var cell in t.Trait.RadarSignatureCells(t.Actor))
if (world.Map.IsInMap(cell))
if (world.Map.Contains(cell))
*(c + ((cell.Y - world.Map.Bounds.Top) * bitmapData.Stride >> 2) + cell.X - world.Map.Bounds.Left) = color.ToArgb();
}
}

View File

@@ -28,15 +28,12 @@ namespace OpenRA.Graphics
var vertices = new Vertex[4 * map.Bounds.Height * map.Bounds.Width];
var nv = 0;
for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
foreach (var cell in map.Cells)
{
for (var i = map.Bounds.Left; i < map.Bounds.Right; i++)
{
var tile = wr.Theater.TileSprite(map.MapTiles.Value[i, j]);
var pos = wr.ScreenPosition(new CPos(i, j).CenterPosition) - 0.5f * tile.size;
Util.FastCreateQuad(vertices, pos, tile, terrainPalette, nv, tile.size);
nv += 4;
}
var tile = wr.Theater.TileSprite(map.MapTiles.Value[cell]);
var pos = wr.ScreenPosition(cell.CenterPosition) - 0.5f * tile.size;
Util.FastCreateQuad(vertices, pos, tile, terrainPalette, nv, tile.size);
nv += 4;
}
vertexBuffer = Game.Renderer.Device.CreateVertexBuffer(vertices.Length);
@@ -46,9 +43,9 @@ namespace OpenRA.Graphics
public void Draw(WorldRenderer wr, Viewport viewport)
{
var verticesPerRow = 4*map.Bounds.Width;
var bounds = viewport.CellBounds;
var firstRow = bounds.Top - map.Bounds.Top;
var lastRow = bounds.Bottom - map.Bounds.Top;
var cells = viewport.VisibleCells;
var firstRow = cells.TopLeft.Y - map.Bounds.Top;
var lastRow = cells.BottomRight.Y - map.Bounds.Top + 1;
if (lastRow < 0 || firstRow > map.Bounds.Height)
return;

View File

@@ -71,7 +71,7 @@ namespace OpenRA.Graphics
missingTile = sheetBuilder.Add(new byte[1], new Size(1, 1));
}
public Sprite TileSprite(TileReference<ushort, byte> r)
public Sprite TileSprite(TerrainTile r)
{
Sprite[] template;
if (!templates.TryGetValue(r.Type, out template))

View File

@@ -9,6 +9,7 @@
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
@@ -46,7 +47,8 @@ namespace OpenRA.Graphics
public int2 TopLeft { get { return CenterLocation - viewportSize / 2; } }
public int2 BottomRight { get { return CenterLocation + viewportSize / 2; } }
int2 viewportSize;
bool cellBoundsDirty = true;
CellRegion cells;
bool cellsDirty = true;
float zoom = 1f;
public float Zoom
@@ -60,7 +62,7 @@ namespace OpenRA.Graphics
{
zoom = value;
viewportSize = (1f / zoom * new float2(Game.Renderer.Resolution)).ToInt2();
cellBoundsDirty = true;
cellsDirty = true;
}
}
@@ -110,14 +112,14 @@ namespace OpenRA.Graphics
public void Center(WPos pos)
{
CenterLocation = worldRenderer.ScreenPxPosition(pos).Clamp(mapBounds);
cellBoundsDirty = true;
cellsDirty = true;
}
public void Scroll(float2 delta, bool ignoreBorders)
{
// Convert scroll delta from world-px to viewport-px
CenterLocation += (1f / Zoom * delta).ToInt2();
cellBoundsDirty = true;
cellsDirty = true;
if (!ignoreBorders)
CenterLocation = CenterLocation.Clamp(mapBounds);
@@ -129,33 +131,30 @@ namespace OpenRA.Graphics
{
get
{
var r = CellBounds;
var ctl = new CPos(r.Left, r.Top).TopLeft;
var cbr = new CPos(r.Right, r.Bottom).TopLeft;
var ctl = VisibleCells.TopLeft.TopLeft;
var cbr = VisibleCells.BottomRight.BottomRight;
var tl = WorldToViewPx(worldRenderer.ScreenPxPosition(ctl)).Clamp(ScreenClip);
var br = WorldToViewPx(worldRenderer.ScreenPxPosition(cbr)).Clamp(ScreenClip);
return Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y);
}
}
// Rectangle (in cell coords) of cells that are currently visible on the screen
Rectangle cachedRect;
public Rectangle CellBounds
public CellRegion VisibleCells
{
get
{
if (cellBoundsDirty)
if (cellsDirty)
{
var boundary = new CVec(1, 1);
var tl = worldRenderer.Position(TopLeft).ToCPos() - boundary;
var br = worldRenderer.Position(BottomRight).ToCPos() + boundary;
// Calculate the intersection of the visible rectangle and the map.
var map = worldRenderer.world.Map;
var tl = map.Clamp(worldRenderer.Position(TopLeft).ToCPos() - new CVec(1, 1));
var br = map.Clamp(worldRenderer.Position(BottomRight).ToCPos());
cachedRect = Rectangle.Intersect(Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y), worldRenderer.world.Map.Bounds);
cellBoundsDirty = false;
cells = new CellRegion(tl, br);
cellsDirty = false;
}
var b = worldRenderer.world.VisibleBounds;
return b.HasValue ? Rectangle.Intersect(cachedRect, b.Value) : cachedRect;
return cells;
}
}
}

View File

@@ -0,0 +1,105 @@
#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 OpenRA.Graphics;
namespace OpenRA
{
// Represents a layer of "something" that covers the map
public class CellLayer<T> : IEnumerable<T>
{
public readonly Size Size;
T[] entries;
public CellLayer(Map map)
: this(new Size(map.MapSize.X, map.MapSize.Y)) { }
public CellLayer(Size size)
{
Size = size;
entries = new T[size.Width * size.Height];
}
// Resolve an array index from cell coordinates
int Index(CPos cell)
{
// This will eventually define a distinct case for diagonal cell grids
return cell.Y * Size.Width + cell.X;
}
/// <summary>Gets or sets the <see cref="OpenRA.CellLayer"/> using cell coordinates</summary>
public T this[CPos cell]
{
get
{
return entries[Index(cell)];
}
set
{
entries[Index(cell)] = value;
}
}
/// <summary>Gets or sets the layer contents using raw map coordinates (not CPos!)</summary>
public T this[int u, int v]
{
get
{
return entries[v * Size.Width + u];
}
set
{
entries[v * Size.Width + u] = value;
}
}
/// <summary>Clears the layer contents with a known value</summary>
public void Clear(T clearValue)
{
for (var i = 0; i < entries.Length; i++)
entries[i] = clearValue;
}
public IEnumerator<T> GetEnumerator()
{
return (IEnumerator<T>)entries.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
// Helper functions
public static class CellLayer
{
/// <summary>Create a new layer by resizing another layer. New cells are filled with defaultValue.</summary>
public static CellLayer<T> Resize<T>(CellLayer<T> layer, Size newSize, T defaultValue)
{
var result = new CellLayer<T>(newSize);
var width = Math.Min(layer.Size.Width, newSize.Width);
var height = Math.Min(layer.Size.Height, newSize.Height);
result.Clear(defaultValue);
for (var j = 0; j < height; j++)
for (var i = 0; i < width; i++)
result[i, j] = layer[i, j];
return result;
}
}
}

View File

@@ -0,0 +1,101 @@
#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 OpenRA.Graphics;
namespace OpenRA
{
// Represents a (on-screen) rectangular collection of tiles.
// TopLeft and BottomRight are inclusive
public class CellRegion : IEnumerable<CPos>
{
// Corners of the region
public readonly CPos TopLeft;
public readonly CPos BottomRight;
// Corners in map coordinates
// Defined for forward compatibility with diagonal cell grids
readonly CPos mapTopLeft;
readonly CPos mapBottomRight;
public CellRegion(CPos topLeft, CPos bottomRight)
{
TopLeft = topLeft;
BottomRight = bottomRight;
mapTopLeft = TopLeft;
mapBottomRight = BottomRight;
}
public bool Contains(CPos cell)
{
// Defined for forward compatibility with diagonal cell grids
var uv = cell;
return uv.X >= mapTopLeft.X && uv.X <= mapBottomRight.X && uv.Y >= mapTopLeft.Y && uv.Y <= mapBottomRight.Y;
}
public IEnumerator<CPos> GetEnumerator()
{
return new CellRegionEnumerator(this);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
class CellRegionEnumerator : IEnumerator<CPos>
{
readonly CellRegion r;
// Current position, in map coordinates
int u, v;
public CellRegionEnumerator(CellRegion region)
{
r = region;
Reset();
}
public bool MoveNext()
{
u += 1;
// Check for column overflow
if (u > r.mapBottomRight.X)
{
v += 1;
u = r.mapTopLeft.X;
// Check for row overflow
if (v > r.mapBottomRight.Y)
return false;
}
return true;
}
public void Reset()
{
// Enumerator starts *before* the first element in the sequence.
u = r.mapTopLeft.X - 1;
v = r.mapTopLeft.Y;
}
public CPos Current { get { return new CPos(u, v); } }
object IEnumerator.Current { get { return Current; } }
public void Dispose() { }
}
}
}

View File

@@ -9,6 +9,7 @@
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
@@ -110,29 +111,38 @@ namespace OpenRA
[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 int[,] CustomTerrain;
[FieldLoader.Ignore] public Lazy<CellLayer<TerrainTile>> MapTiles;
[FieldLoader.Ignore] public Lazy<CellLayer<ResourceTile>> MapResources;
[FieldLoader.Ignore] public CellLayer<int> CustomTerrain;
[FieldLoader.Ignore] Lazy<Ruleset> rules;
public Ruleset Rules { get { return rules != null ? rules.Value : null; } }
public SequenceProvider SequenceProvider { get { return Rules.Sequences[Tileset]; } }
[FieldLoader.Ignore] public CellRegion Cells;
public static Map FromTileset(TileSet tileset)
{
var tile = tileset.Templates.First();
var tileRef = new TileReference<ushort, byte> { Type = tile.Key, Index = (byte)0 };
var size = new Size(1, 1);
var tileRef = new TerrainTile(tileset.Templates.First().Key, (byte)0);
var makeMapTiles = Exts.Lazy(() =>
{
var ret = new CellLayer<TerrainTile>(size);
ret.Clear(tileRef);
return ret;
});
var map = new Map()
{
Title = "Name your map here",
Description = "Describe your map here",
Author = "Your name here",
MapSize = new int2(1, 1),
MapSize = new int2(size),
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 } }),
MapResources = Exts.Lazy(() => new CellLayer<ResourceTile>(size)),
MapTiles = makeMapTiles,
Actors = Exts.Lazy(() => new Dictionary<string, ActorReference>()),
Smudges = Exts.Lazy(() => new List<SmudgeReference>())
};
@@ -229,11 +239,6 @@ namespace OpenRA
NotificationDefinitions = MiniYaml.NodesOrEmpty(yaml, "Notifications");
TranslationDefinitions = MiniYaml.NodesOrEmpty(yaml, "Translations");
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());
@@ -254,6 +259,14 @@ namespace OpenRA
void PostInit()
{
rules = Exts.Lazy(() => Game.modData.RulesetCache.LoadMapRules(this));
var tl = new CPos(Bounds.Left, Bounds.Top);
var br = new CPos(Bounds.Right - 1, Bounds.Bottom - 1);
Cells = new CellRegion(tl, br);
CustomTerrain = new CellLayer<int>(this);
foreach (var cell in Cells)
CustomTerrain[cell] = -1;
}
public Ruleset PreloadRules()
@@ -346,9 +359,9 @@ namespace OpenRA
Container.Write(entries);
}
public TileReference<ushort, byte>[,] LoadMapTiles()
public CellLayer<TerrainTile> LoadMapTiles()
{
var tiles = new TileReference<ushort, byte>[MapSize.X, MapSize.Y];
var tiles = new CellLayer<TerrainTile>(this);
using (var dataStream = Container.GetContent("map.bin"))
{
if (dataStream.ReadUInt8() != 1)
@@ -365,23 +378,27 @@ namespace OpenRA
var data = dataStream.ReadBytes(MapSize.X * MapSize.Y * 3);
var d = 0;
for (var i = 0; i < MapSize.X; i++)
{
for (var j = 0; j < MapSize.Y; j++)
{
var tile = BitConverter.ToUInt16(data, d);
d += 2;
var index = data[d++];
if (index == byte.MaxValue)
index = (byte)(i % 4 + (j % 4) * 4);
tiles[i, j] = new TileReference<ushort, byte>(tile, index);
tiles[i, j] = new TerrainTile(tile, index);
}
}
}
return tiles;
}
public TileReference<byte, byte>[,] LoadResourceTiles()
public CellLayer<ResourceTile> LoadResourceTiles()
{
var resources = new TileReference<byte, byte>[MapSize.X, MapSize.Y];
var resources = new CellLayer<ResourceTile>(this);
using (var dataStream = Container.GetContent("map.bin"))
{
@@ -400,10 +417,11 @@ namespace OpenRA
var data = dataStream.ReadBytes(MapSize.X * MapSize.Y * 2);
var d = 0;
// Load resource data
for (var i = 0; i < MapSize.X; i++)
for (var j = 0; j < MapSize.Y; j++)
resources[i, j] = new TileReference<byte, byte>(data[d++], data[d++]);
resources[i, j] = new ResourceTile(data[d++], data[d++]);
}
return resources;
@@ -423,38 +441,43 @@ namespace OpenRA
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);
var tile = MapTiles.Value[new CPos(i, j)];
writer.Write(tile.Type);
writer.Write(tile.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);
var tile = MapResources.Value[new CPos(i, j)];
writer.Write(tile.Type);
writer.Write(tile.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 bool Contains(CPos xy) { return Bounds.Contains(xy.X, xy.Y); }
public void Resize(int width, int height) // editor magic.
{
var oldMapTiles = MapTiles.Value;
var oldMapResources = MapResources.Value;
var newSize = new Size(width, height);
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);
MapTiles = Exts.Lazy(() => CellLayer.Resize(oldMapTiles, newSize, oldMapTiles[0, 0]));
MapResources = Exts.Lazy(() => CellLayer.Resize(oldMapResources, newSize, oldMapResources[0, 0]));
MapSize = new int2(newSize);
}
public void ResizeCordon(int left, int top, int right, int bottom)
{
Bounds = Rectangle.FromLTRB(left, top, right, bottom);
Cells = new CellRegion(new CPos(Bounds.Left, Bounds.Top), new CPos(Bounds.Right - 1, Bounds.Bottom - 1));
}
string ComputeHash()
@@ -524,26 +547,30 @@ namespace OpenRA
{
for (var i = Bounds.Left; i < Bounds.Right; i++)
{
var tr = MapTiles.Value[i, j];
if (!tileset.Templates.ContainsKey(tr.Type))
var cell = new CPos(i, j);
var type = MapTiles.Value[cell].Type;
var index = MapTiles.Value[cell].Index;
if (!tileset.Templates.ContainsKey(type))
{
Console.WriteLine("Unknown Tile ID {0}".F(tr.Type));
Console.WriteLine("Unknown Tile ID {0}".F(type));
continue;
}
var template = tileset.Templates[tr.Type];
var template = tileset.Templates[type];
if (!template.PickAny)
continue;
tr.Index = (byte)r.Next(0, template.TilesCount);
MapTiles.Value[i, j] = tr;
index = (byte)r.Next(0, template.TilesCount);
MapTiles.Value[cell] = new TerrainTile(type, index);
}
}
}
public int GetTerrainIndex(CPos cell)
{
var custom = CustomTerrain[cell.X, cell.Y];
var custom = CustomTerrain[cell];
var tileSet = Rules.TileSets[Tileset];
return custom != -1 ? custom : tileSet.GetTerrainIndex(MapTiles.Value[cell.X, cell.Y]);
return custom != -1 ? custom : tileSet.GetTerrainIndex(MapTiles.Value[cell]);
}
public TerrainTypeInfo GetTerrainInfo(CPos cell)
@@ -606,12 +633,12 @@ namespace OpenRA
if (range >= TilesByDistance.Length)
throw new InvalidOperationException("FindTilesInCircle supports queries for only <= {0}".F(MaxTilesInCircleRange));
for(var i = 0; i <= range; i++)
for (var i = 0; i <= range; i++)
{
foreach(var offset in TilesByDistance[i])
foreach (var offset in TilesByDistance[i])
{
var t = offset + center;
if (Bounds.Contains(t.X, t.Y))
if (Contains(t))
yield return t;
}
}

View File

@@ -10,15 +10,29 @@
namespace OpenRA
{
public struct TileReference<T, U>
public struct TerrainTile
{
public T Type;
public U Index;
public readonly ushort Type;
public readonly byte Index;
public TileReference(T t, U i)
public TerrainTile(ushort type, byte index)
{
Type = t;
Index = i;
Type = type;
Index = index;
}
public override int GetHashCode() { return Type.GetHashCode() ^ Index.GetHashCode(); }
}
public struct ResourceTile
{
public readonly byte Type;
public readonly byte Index;
public ResourceTile(byte type, byte index)
{
Type = type;
Index = index;
}
public override int GetHashCode() { return Type.GetHashCode() ^ Index.GetHashCode(); }

View File

@@ -223,7 +223,7 @@ namespace OpenRA
throw new InvalidDataException("Tileset '{0}' lacks terrain type '{1}'".F(Id, type));
}
public int GetTerrainIndex(TileReference<ushort, byte> r)
public int GetTerrainIndex(TerrainTile r)
{
var tpl = Templates[r.Type];
@@ -261,7 +261,7 @@ namespace OpenRA
root.WriteToFile(filepath);
}
public TerrainTypeInfo GetTerrainInfo(TileReference<ushort, byte> r)
public TerrainTypeInfo GetTerrainInfo(TerrainTile r)
{
return terrainInfo[GetTerrainIndex(r)];
}

View File

@@ -235,6 +235,8 @@
<Compile Include="Support\MersenneTwister.cs" />
<Compile Include="GameInformation.cs" />
<Compile Include="Widgets\RootWidget.cs" />
<Compile Include="Map\CellLayer.cs" />
<Compile Include="Map\CellRegion.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="FileSystem\D2kSoundResources.cs" />
@@ -358,4 +360,4 @@
</Target>
-->
<ItemGroup />
</Project>
</Project>

View File

@@ -55,7 +55,7 @@ namespace OpenRA.Orders
IEnumerable<Order> OrderInner(World world, CPos xy, MouseInput mi)
{
if (mi.Button == expectedButton && world.Map.IsInMap(xy))
if (mi.Button == expectedButton && world.Map.Contains(xy))
{
world.CancelInputMode();
foreach (var subject in subjects)
@@ -66,6 +66,6 @@ namespace OpenRA.Orders
public virtual void Tick(World world) { }
public IEnumerable<IRenderable> Render(WorldRenderer wr, World world) { yield break; }
public void RenderAfterWorld(WorldRenderer wr, World world) { }
public string GetCursor(World world, CPos xy, MouseInput mi) { return world.Map.IsInMap(xy) ? cursor : "generic-blocked"; }
public string GetCursor(World world, CPos xy, MouseInput mi) { return world.Map.Contains(xy) ? cursor : "generic-blocked"; }
}
}

View File

@@ -41,7 +41,7 @@ namespace OpenRA.Traits
readonly ActorMapInfo info;
readonly Map map;
InfluenceNode[,] influence;
readonly CellLayer<InfluenceNode> influence;
List<Actor>[] actors;
int rows, cols;
@@ -56,7 +56,7 @@ namespace OpenRA.Traits
{
this.info = info;
map = world.Map;
influence = new InfluenceNode[world.Map.MapSize.X, world.Map.MapSize.Y];
influence = new CellLayer<InfluenceNode>(world.Map);
cols = world.Map.MapSize.X / info.BinSize + 1;
rows = world.Map.MapSize.Y / info.BinSize + 1;
@@ -71,20 +71,20 @@ namespace OpenRA.Traits
public IEnumerable<Actor> GetUnitsAt(CPos a)
{
if (!map.IsInMap(a))
if (!map.Contains(a))
yield break;
for (var i = influence[a.X, a.Y]; i != null; i = i.Next)
for (var i = influence[a]; i != null; i = i.Next)
if (!i.Actor.Destroyed)
yield return i.Actor;
}
public IEnumerable<Actor> GetUnitsAt(CPos a, SubCell sub)
{
if (!map.IsInMap(a))
if (!map.Contains(a))
yield break;
for (var i = influence[a.X, a.Y]; i != null; i = i.Next)
for (var i = influence[a]; i != null; i = i.Next)
if (!i.Actor.Destroyed && (i.SubCell == sub || i.SubCell == SubCell.FullCell))
yield return i.Actor;
}
@@ -107,12 +107,12 @@ namespace OpenRA.Traits
public bool AnyUnitsAt(CPos a)
{
return influence[a.X, a.Y] != null;
return influence[a] != null;
}
public bool AnyUnitsAt(CPos a, SubCell sub)
{
for (var i = influence[a.X, a.Y]; i != null; i = i.Next)
for (var i = influence[a]; i != null; i = i.Next)
if (i.SubCell == sub || i.SubCell == SubCell.FullCell)
return true;
@@ -122,20 +122,25 @@ namespace OpenRA.Traits
public void AddInfluence(Actor self, IOccupySpace ios)
{
foreach (var c in ios.OccupiedCells())
influence[c.First.X, c.First.Y] = new InfluenceNode { Next = influence[c.First.X, c.First.Y], SubCell = c.Second, Actor = self };
influence[c.First] = new InfluenceNode { Next = influence[c.First], SubCell = c.Second, Actor = self };
}
public void RemoveInfluence(Actor self, IOccupySpace ios)
{
foreach (var c in ios.OccupiedCells())
RemoveInfluenceInner(ref influence[c.First.X, c.First.Y], self);
{
var temp = influence[c.First];
RemoveInfluenceInner(ref temp, self);
influence[c.First] = temp;
}
}
void RemoveInfluenceInner(ref InfluenceNode influenceNode, Actor toRemove)
{
if (influenceNode == null)
return;
else if (influenceNode.Actor == toRemove)
if (influenceNode.Actor == toRemove)
influenceNode = influenceNode.Next;
if (influenceNode != null)
@@ -206,5 +211,11 @@ namespace OpenRA.Traits
}
}
}
public IEnumerable<Actor> ActorsInWorld()
{
return actors.SelectMany(a => a.Where(b => b.IsInWorld))
.Distinct();
}
}
}

View File

@@ -22,89 +22,78 @@ namespace OpenRA.Traits
static readonly CellContents EmptyCell = new CellContents();
World world;
protected CellContents[,] content;
protected CellContents[,] render;
protected CellLayer<CellContents> content;
protected CellLayer<CellContents> render;
List<CPos> dirty;
public void Render(WorldRenderer wr)
{
var clip = wr.Viewport.CellBounds;
for (var x = clip.Left; x < clip.Right; x++)
foreach (var cell in wr.Viewport.VisibleCells)
{
for (var y = clip.Top; y < clip.Bottom; y++)
{
var pos = new CPos(x, y);
if (world.ShroudObscures(pos))
continue;
if (world.ShroudObscures(cell))
continue;
var c = render[x, y];
if (c.Sprite != null)
new SpriteRenderable(c.Sprite, pos.CenterPosition,
WVec.Zero, -511, c.Type.Palette, 1f, true).Render(wr);
}
var c = render[cell];
if (c.Sprite != null)
new SpriteRenderable(c.Sprite, cell.CenterPosition,
WVec.Zero, -511, c.Type.Palette, 1f, true).Render(wr);
}
}
int GetAdjacentCellsWith(ResourceType t, int i, int j)
int GetAdjacentCellsWith(ResourceType t, CPos cell)
{
var sum = 0;
for (var u = -1; u < 2; u++)
for (var v = -1; v < 2; v++)
if (content[i + u, j + v].Type == t)
if (content[cell + new CVec(u, v)].Type == t)
++sum;
return sum;
}
public void WorldLoaded(World w, WorldRenderer wr)
{
this.world = w;
content = new CellContents[w.Map.MapSize.X, w.Map.MapSize.Y];
render = new CellContents[w.Map.MapSize.X, w.Map.MapSize.Y];
content = new CellLayer<CellContents>(w.Map);
render = new CellLayer<CellContents>(w.Map);
dirty = new List<CPos>();
var resources = w.WorldActor.TraitsImplementing<ResourceType>()
.ToDictionary(r => r.Info.ResourceType, r => r);
var map = w.Map;
for (var x = map.Bounds.Left; x < map.Bounds.Right; x++)
foreach (var cell in w.Map.Cells)
{
for (var y = map.Bounds.Top; y < map.Bounds.Bottom; y++)
{
var cell = new CPos(x, y);
ResourceType t;
if (!resources.TryGetValue(w.Map.MapResources.Value[x, y].Type, out t))
continue;
ResourceType t;
if (!resources.TryGetValue(w.Map.MapResources.Value[cell].Type, out t))
continue;
if (!AllowResourceAt(t, cell))
continue;
if (!AllowResourceAt(t, cell))
continue;
content[x, y] = CreateResourceCell(t, cell);
}
content[cell] = CreateResourceCell(t, cell);
}
// Set initial density based on the number of neighboring resources
for (var x = map.Bounds.Left; x < map.Bounds.Right; x++)
foreach (var cell in w.Map.Cells)
{
for (var y = map.Bounds.Top; y < map.Bounds.Bottom; y++)
var type = content[cell].Type;
if (type != null)
{
var type = content[x, y].Type;
if (type != null)
{
// Adjacent includes the current cell, so is always >= 1
var adjacent = GetAdjacentCellsWith(type, x, y);
var density = int2.Lerp(0, type.Info.MaxDensity, adjacent, 9);
content[x, y].Density = Math.Max(density, 1);
// Adjacent includes the current cell, so is always >= 1
var adjacent = GetAdjacentCellsWith(type, cell);
var density = int2.Lerp(0, type.Info.MaxDensity, adjacent, 9);
var temp = content[cell];
temp.Density = Math.Max(density, 1);
render[x, y] = content[x, y];
UpdateRenderedSprite(new CPos(x, y));
}
render[cell] = content[cell] = temp;
UpdateRenderedSprite(cell);
}
}
}
protected virtual void UpdateRenderedSprite(CPos p)
protected virtual void UpdateRenderedSprite(CPos cell)
{
var t = render[p.X, p.Y];
var t = render[cell];
if (t.Density > 0)
{
var sprites = t.Type.Variants[t.Variant];
@@ -114,7 +103,7 @@ namespace OpenRA.Traits
else
t.Sprite = null;
render[p.X, p.Y] = t;
render[cell] = t;
}
protected virtual string ChooseRandomVariant(ResourceType t)
@@ -129,7 +118,7 @@ namespace OpenRA.Traits
{
if (!self.World.FogObscures(c))
{
render[c.X, c.Y] = content[c.X, c.Y];
render[c] = content[c];
UpdateRenderedSprite(c);
remove.Add(c);
}
@@ -139,15 +128,15 @@ namespace OpenRA.Traits
dirty.Remove(r);
}
public bool AllowResourceAt(ResourceType rt, CPos a)
public bool AllowResourceAt(ResourceType rt, CPos cell)
{
if (!world.Map.IsInMap(a.X, a.Y))
if (!world.Map.Contains(cell))
return false;
if (!rt.Info.AllowedTerrainTypes.Contains(world.Map.GetTerrainInfo(a).Type))
if (!rt.Info.AllowedTerrainTypes.Contains(world.Map.GetTerrainInfo(cell).Type))
return false;
if (!rt.Info.AllowUnderActors && world.ActorMap.AnyUnitsAt(a))
if (!rt.Info.AllowUnderActors && world.ActorMap.AnyUnitsAt(cell))
return false;
return true;
@@ -160,9 +149,10 @@ namespace OpenRA.Traits
|| (currentResourceType == null && AllowResourceAt(newResourceType, cell));
}
CellContents CreateResourceCell(ResourceType t, CPos p)
CellContents CreateResourceCell(ResourceType t, CPos cell)
{
world.Map.CustomTerrain[p.X, p.Y] = world.TileSet.GetTerrainIndex(t.Info.TerrainType);
world.Map.CustomTerrain[cell] = world.TileSet.GetTerrainIndex(t.Info.TerrainType);
return new CellContents
{
Type = t,
@@ -172,7 +162,7 @@ namespace OpenRA.Traits
public void AddResource(ResourceType t, CPos p, int n)
{
var cell = content[p.X, p.Y];
var cell = content[p];
if (cell.Type == null)
cell = CreateResourceCell(t, p);
@@ -180,58 +170,60 @@ namespace OpenRA.Traits
return;
cell.Density = Math.Min(cell.Type.Info.MaxDensity, cell.Density + n);
content[p.X, p.Y] = cell;
content[p] = cell;
if (!dirty.Contains(p))
dirty.Add(p);
}
public bool IsFull(CPos c)
public bool IsFull(CPos cell)
{
return content[c.X, c.Y].Density == content[c.X, c.Y].Type.Info.MaxDensity;
return content[cell].Density == content[cell].Type.Info.MaxDensity;
}
public ResourceType Harvest(CPos p)
public ResourceType Harvest(CPos cell)
{
var type = content[p.X, p.Y].Type;
if (type == null)
var c = content[cell];
if (c.Type == null)
return null;
if (--content[p.X, p.Y].Density < 0)
if (--c.Density < 0)
{
content[p.X, p.Y] = EmptyCell;
world.Map.CustomTerrain[p.X, p.Y] = -1;
content[cell] = EmptyCell;
world.Map.CustomTerrain[cell] = -1;
}
else
content[cell] = c;
if (!dirty.Contains(p))
dirty.Add(p);
if (!dirty.Contains(cell))
dirty.Add(cell);
return type;
return c.Type;
}
public void Destroy(CPos p)
public void Destroy(CPos cell)
{
// Don't break other users of CustomTerrain if there are no resources
if (content[p.X, p.Y].Type == null)
if (content[cell].Type == null)
return;
// Clear cell
content[p.X, p.Y] = EmptyCell;
world.Map.CustomTerrain[p.X, p.Y] = -1;
content[cell] = EmptyCell;
world.Map.CustomTerrain[cell] = -1;
if (!dirty.Contains(p))
dirty.Add(p);
if (!dirty.Contains(cell))
dirty.Add(cell);
}
public ResourceType GetResource(CPos p) { return content[p.X, p.Y].Type; }
public ResourceType GetRenderedResource(CPos p) { return render[p.X, p.Y].Type; }
public int GetResourceDensity(CPos p) { return content[p.X, p.Y].Density; }
public int GetMaxResourceDensity(CPos p)
public ResourceType GetResource(CPos cell) { return content[cell].Type; }
public ResourceType GetRenderedResource(CPos cell) { return render[cell].Type; }
public int GetResourceDensity(CPos cell) { return content[cell].Density; }
public int GetMaxResourceDensity(CPos cell)
{
if (content[p.X, p.Y].Type == null)
if (content[cell].Type == null)
return 0;
return content[p.X, p.Y].Type.Info.MaxDensity;
return content[cell].Type.Info.MaxDensity;
}
public struct CellContents

View File

@@ -27,9 +27,9 @@ namespace OpenRA.Traits
Actor self;
Map map;
int[,] visibleCount;
int[,] generatedShroudCount;
bool[,] explored;
CellLayer<int> visibleCount;
CellLayer<int> generatedShroudCount;
CellLayer<bool> explored;
readonly Lazy<IFogVisibilityModifier[]> fogVisibilities;
@@ -38,8 +38,6 @@ namespace OpenRA.Traits
Dictionary<Actor, CPos[]> visibility = new Dictionary<Actor, CPos[]>();
Dictionary<Actor, CPos[]> generation = new Dictionary<Actor, CPos[]>();
public Rectangle ExploredBounds { get; private set; }
public int Hash { get; private set; }
public Shroud(Actor self)
@@ -47,9 +45,9 @@ namespace OpenRA.Traits
this.self = self;
map = self.World.Map;
visibleCount = new int[map.MapSize.X, map.MapSize.Y];
generatedShroudCount = new int[map.MapSize.X, map.MapSize.Y];
explored = new bool[map.MapSize.X, map.MapSize.Y];
visibleCount = new CellLayer<int>(map);
generatedShroudCount = new CellLayer<int>(map);
explored = new CellLayer<bool>(map);
self.World.ActorAdded += AddVisibility;
self.World.ActorRemoved += RemoveVisibility;
@@ -57,9 +55,6 @@ namespace OpenRA.Traits
self.World.ActorAdded += AddShroudGeneration;
self.World.ActorRemoved += RemoveShroudGeneration;
if (!self.World.LobbyInfo.GlobalSettings.Shroud)
ExploredBounds = map.Bounds;
fogVisibilities = Exts.Lazy(() => self.TraitsImplementing<IFogVisibilityModifier>().ToArray());
}
@@ -71,15 +66,12 @@ namespace OpenRA.Traits
static IEnumerable<CPos> FindVisibleTiles(World world, CPos position, WRange radius)
{
var r = (radius.Range + 1023) / 1024;
var min = (position - new CVec(r, r)).Clamp(world.Map.Bounds);
var max = (position + new CVec(r, r)).Clamp(world.Map.Bounds);
var circleArea = radius.Range * radius.Range;
var limit = radius.Range * radius.Range;
var pos = position.CenterPosition;
for (var j = min.Y; j <= max.Y; j++)
for (var i = min.X; i <= max.X; i++)
if (circleArea >= (new CPos(i, j).CenterPosition - pos).LengthSquared)
yield return new CPos(i, j);
foreach (var cell in world.Map.FindTilesInCircle(position, r))
if ((cell.CenterPosition - pos).HorizontalLengthSquared <= limit)
yield return cell;
}
void AddVisibility(Actor a)
@@ -92,20 +84,11 @@ namespace OpenRA.Traits
var visible = origins.SelectMany(o => FindVisibleTiles(a.World, o, rs.Range))
.Distinct().ToArray();
// Update bounding rect
var r = (rs.Range.Range + 1023) / 1024;
foreach (var o in origins)
{
var box = new Rectangle(o.X - r, o.Y - r, 2 * r + 1, 2 * r + 1);
ExploredBounds = Rectangle.Union(ExploredBounds, box);
}
// Update visibility
foreach (var c in visible)
{
visibleCount[c.X, c.Y]++;
explored[c.X, c.Y] = true;
visibleCount[c]++;
explored[c] = true;
}
if (visibility.ContainsKey(a))
@@ -122,7 +105,7 @@ namespace OpenRA.Traits
return;
foreach (var c in visible)
visibleCount[c.X, c.Y]--;
visibleCount[c]--;
visibility.Remove(a);
Invalidate();
@@ -147,7 +130,7 @@ namespace OpenRA.Traits
var shrouded = GetVisOrigins(a).SelectMany(o => FindVisibleTiles(a.World, o, cs.Range))
.Distinct().ToArray();
foreach (var c in shrouded)
generatedShroudCount[c.X, c.Y]++;
generatedShroudCount[c]++;
if (generation.ContainsKey(a))
throw new InvalidOperationException("Attempting to add duplicate shroud generation");
@@ -163,7 +146,7 @@ namespace OpenRA.Traits
return;
foreach (var c in shrouded)
generatedShroudCount[c.X, c.Y]--;
generatedShroudCount[c]--;
generation.Remove(a);
Invalidate();
@@ -203,55 +186,44 @@ namespace OpenRA.Traits
public void Explore(World world, CPos center, WRange range)
{
foreach (var q in FindVisibleTiles(world, center, range))
explored[q.X, q.Y] = true;
var r = (range.Range + 1023) / 1024;
var box = new Rectangle(center.X - r, center.Y - r, 2 * r + 1, 2 * r + 1);
ExploredBounds = Rectangle.Union(ExploredBounds, box);
explored[q] = true;
Invalidate();
}
public void Explore(Shroud s)
{
for (var i = map.Bounds.Left; i < map.Bounds.Right; i++)
for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
if (s.explored[i,j] == true)
explored[i, j] = true;
foreach (var cell in map.Cells)
if (s.explored[cell])
explored[cell] = true;
ExploredBounds = Rectangle.Union(ExploredBounds, s.ExploredBounds);
Invalidate();
}
public void ExploreAll(World world)
{
for (var i = map.Bounds.Left; i < map.Bounds.Right; i++)
for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
explored[i, j] = true;
ExploredBounds = world.Map.Bounds;
explored.Clear(true);
Invalidate();
}
public void ResetExploration()
{
for (var i = map.Bounds.Left; i < map.Bounds.Right; i++)
for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++)
explored[i, j] = visibleCount[i, j] > 0;
foreach (var cell in map.Cells)
explored[cell] = visibleCount[cell] > 0;
Invalidate();
}
public bool IsExplored(CPos xy) { return IsExplored(xy.X, xy.Y); }
public bool IsExplored(int x, int y)
public bool IsExplored(CPos cell)
{
if (!map.IsInMap(x, y))
if (!map.Contains(cell))
return false;
if (Disabled || !self.World.LobbyInfo.GlobalSettings.Shroud)
return true;
return explored[x, y] && (generatedShroudCount[x, y] == 0 || visibleCount[x, y] > 0);
return explored[cell] && (generatedShroudCount[cell] == 0 || visibleCount[cell] > 0);
}
public bool IsExplored(Actor a)
@@ -259,16 +231,15 @@ namespace OpenRA.Traits
return GetVisOrigins(a).Any(o => IsExplored(o));
}
public bool IsVisible(CPos xy) { return IsVisible(xy.X, xy.Y); }
public bool IsVisible(int x, int y)
public bool IsVisible(CPos cell)
{
if (!map.IsInMap(x, y))
if (!map.Contains(cell))
return false;
if (Disabled || !self.World.LobbyInfo.GlobalSettings.Fog)
return true;
return visibleCount[x, y] > 0;
return visibleCount[cell] > 0;
}
// Actors are hidden under shroud, but not under fog by default

View File

@@ -93,7 +93,7 @@ namespace OpenRA.Widgets
{
TooltipType = WorldTooltipType.None;
var cell = worldRenderer.Position(worldRenderer.Viewport.ViewToWorldPx(Viewport.LastMousePos)).ToCPos();
if (!world.Map.IsInMap(cell))
if (!world.Map.Contains(cell))
return;
if (world.ShroudObscures(cell))

View File

@@ -209,18 +209,20 @@ namespace OpenRA.Widgets
}
else if (key == Game.Settings.Keys.SelectUnitsByTypeKey)
{
var selectedTypes = World.Selection.Actors.Where(
x => x.Owner == World.RenderPlayer).Select(a => a.Info);
var selectedTypes = World.Selection.Actors
.Where(x => x.Owner == World.RenderPlayer)
.Select(a => a.Info);
Func<Actor, bool> cond = a => a.Owner == World.RenderPlayer && selectedTypes.Contains(a.Info);
var newSelection = SelectActorsInBox(
World, worldRenderer.Viewport.TopLeft, worldRenderer.Viewport.BottomRight, cond);
var tl = worldRenderer.Viewport.TopLeft;
var br = worldRenderer.Viewport.BottomRight;
var newSelection = SelectActorsInBox(World, tl, br, cond);
if (newSelection.Count() > selectedTypes.Count())
Game.Debug("Selected across screen");
else
{
newSelection = World.ActorMap.ActorsInBox(
World.Map.Bounds.TopLeftAsCPos().TopLeft,
World.Map.Bounds.BottomRightAsCPos().BottomRight).Where(cond);
newSelection = World.ActorMap.ActorsInWorld().Where(cond);
Game.Debug("Selected across map");
}

View File

@@ -55,17 +55,6 @@ namespace OpenRA
public bool ShroudObscures(Actor a) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(a); }
public bool ShroudObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(p); }
public Rectangle? VisibleBounds
{
get
{
if (RenderPlayer == null)
return null;
return RenderPlayer.Shroud.ExploredBounds;
}
}
public bool IsReplay
{
get { return orderManager.Connection is ReplayConnection; }

View File

@@ -97,28 +97,28 @@ namespace OpenRA.Traits
ClearSides FindClearSides(ResourceType t, CPos p)
{
var ret = ClearSides.None;
if (render[p.X, p.Y - 1].Type != t)
if (render[p + new CVec(0, -1)].Type != t)
ret |= ClearSides.Top | ClearSides.TopLeft | ClearSides.TopRight;
if (render[p.X - 1, p.Y].Type != t)
if (render[p + new CVec(-1, 0)].Type != t)
ret |= ClearSides.Left | ClearSides.TopLeft | ClearSides.BottomLeft;
if (render[p.X + 1, p.Y].Type != t)
if (render[p + new CVec(1, 0)].Type != t)
ret |= ClearSides.Right | ClearSides.TopRight | ClearSides.BottomRight;
if (render[p.X, p.Y + 1].Type != t)
if (render[p + new CVec(0, 1)].Type != t)
ret |= ClearSides.Bottom | ClearSides.BottomLeft | ClearSides.BottomRight;
if (render[p.X - 1, p.Y - 1].Type != t)
if (render[p + new CVec(-1, -1)].Type != t)
ret |= ClearSides.TopLeft;
if (render[p.X + 1, p.Y - 1].Type != t)
if (render[p + new CVec(1, -1)].Type != t)
ret |= ClearSides.TopRight;
if (render[p.X - 1, p.Y + 1].Type != t)
if (render[p + new CVec(-1, 1)].Type != t)
ret |= ClearSides.BottomLeft;
if (render[p.X + 1, p.Y + 1].Type != t)
if (render[p + new CVec(1, 1)].Type != t)
ret |= ClearSides.BottomRight;
return ret;
@@ -126,7 +126,7 @@ namespace OpenRA.Traits
void UpdateRenderedTileInner(CPos p)
{
var t = render[p.X, p.Y];
var t = render[p];
if (t.Density > 0)
{
var clear = FindClearSides(t.Type, p);
@@ -146,7 +146,7 @@ namespace OpenRA.Traits
else
t.Sprite = null;
render[p.X, p.Y] = t;
render[p] = t;
}
protected override void UpdateRenderedSprite(CPos p)

View File

@@ -119,7 +119,7 @@ namespace OpenRA.Mods.RA.Activities
var path = pathFinder.FindBidiPath(
PathSearch.FromPoints(self.World, mobile.Info, self, searchCells, loc, true),
PathSearch.FromPoint(self.World, mobile.Info, self, loc, targetPosition, true).InReverse()
PathSearch.FromPoint(self.World, mobile.Info, self, loc, targetPosition, true).Reverse()
);
inner = mobile.MoveTo(() => path);

View File

@@ -165,7 +165,7 @@ namespace OpenRA.Mods.RA.Air
public bool CanLand(CPos cell)
{
if (!self.World.Map.IsInMap(cell))
if (!self.World.Map.Contains(cell))
return false;
if (self.World.ActorMap.AnyUnitsAt(cell))
@@ -246,7 +246,7 @@ namespace OpenRA.Mods.RA.Air
return false;
IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
cursor = self.World.Map.IsInMap(target.CenterPosition.ToCPos()) ? "move" : "move-blocked";
cursor = self.World.Map.Contains(target.CenterPosition.ToCPos()) ? "move" : "move-blocked";
return true;
}

View File

@@ -34,7 +34,7 @@ namespace OpenRA.Mods.RA.Air
{
public override Activity Tick(Actor self)
{
if (IsCanceled || !self.World.Map.IsInMap(self.Location))
if (IsCanceled || !self.World.Map.Contains(self.Location))
return NextActivity;
var plane = self.Trait<Plane>();

View File

@@ -207,7 +207,7 @@ namespace OpenRA.Mods.RA
bool CanTargetLocation(Actor self, CPos location, List<Actor> actorsAtLocation, TargetModifiers modifiers, ref string cursor)
{
if (!self.World.Map.IsInMap(location))
if (!self.World.Map.Contains(location))
return false;
IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);

View File

@@ -91,14 +91,14 @@ namespace OpenRA.Mods.RA
// Set the initial custom terrain types
foreach (var c in footprint.Keys)
self.World.Map.CustomTerrain[c.X, c.Y] = GetTerrainType(c);
self.World.Map.CustomTerrain[c] = GetTerrainType(c);
}
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.GetTerrainIndex(new TileReference<ushort, byte>(template, (byte)index));
return self.World.TileSet.GetTerrainIndex(new TerrainTile(template, (byte)index));
}
public void LinkNeighbouringBridges(World world, BridgeLayer bridges)
@@ -121,7 +121,7 @@ namespace OpenRA.Mods.RA
IRenderable[] TemplateRenderables(WorldRenderer wr, PaletteReference palette, ushort template)
{
return footprint.Select(c => (IRenderable)(new SpriteRenderable(
wr.Theater.TileSprite(new TileReference<ushort, byte>(template, c.Value)),
wr.Theater.TileSprite(new TerrainTile(template, c.Value)),
c.Key.CenterPosition, WVec.Zero, -512, palette, 1f, true))).ToArray();
}
@@ -204,7 +204,7 @@ namespace OpenRA.Mods.RA
// Update map
foreach (var c in footprint.Keys)
self.World.Map.CustomTerrain[c.X, c.Y] = GetTerrainType(c);
self.World.Map.CustomTerrain[c] = GetTerrainType(c);
// If this bridge repair operation connects two pathfinding domains,
// update the domain index.

View File

@@ -19,95 +19,100 @@ namespace OpenRA.Mods.RA
class BridgeLayerInfo : ITraitInfo
{
[ActorReference]
public readonly string[] Bridges = {"bridge1", "bridge2"};
public readonly string[] Bridges = { "bridge1", "bridge2" };
public object Create(ActorInitializer init) { return new BridgeLayer(init.self, this); }
}
class BridgeLayer : IWorldLoaded
{
readonly BridgeLayerInfo Info;
readonly BridgeLayerInfo info;
readonly World world;
Dictionary<ushort, Pair<string, float>> BridgeTypes = new Dictionary<ushort, Pair<string,float>>();
Bridge[,] Bridges;
Dictionary<ushort, Pair<string, float>> bridgeTypes = new Dictionary<ushort, Pair<string, float>>();
CellLayer<Bridge> bridges;
public BridgeLayer(Actor self, BridgeLayerInfo Info)
public BridgeLayer(Actor self, BridgeLayerInfo info)
{
this.Info = Info;
this.info = info;
this.world = self.World;
}
public void WorldLoaded(World w, WorldRenderer wr)
{
Bridges = new Bridge[w.Map.MapSize.X, w.Map.MapSize.Y];
bridges = new CellLayer<Bridge>(w.Map);
// Build a list of templates that should be overlayed with bridges
foreach(var bridge in Info.Bridges)
foreach (var bridge in info.Bridges)
{
var bi = w.Map.Rules.Actors[bridge].Traits.Get<BridgeInfo>();
foreach (var template in bi.Templates)
BridgeTypes.Add(template.First, Pair.New(bridge, template.Second));
bridgeTypes.Add(template.First, Pair.New(bridge, template.Second));
}
// Loop through the map looking for templates to overlay
for (var i = w.Map.Bounds.Left; i < w.Map.Bounds.Right; i++)
{
for (var j = w.Map.Bounds.Top; j < w.Map.Bounds.Bottom; j++)
if (BridgeTypes.Keys.Contains(w.Map.MapTiles.Value[i, j].Type))
ConvertBridgeToActor(w, i, j);
{
var cell = new CPos(i, j);
if (bridgeTypes.ContainsKey(w.Map.MapTiles.Value[cell].Type))
ConvertBridgeToActor(w, cell);
}
}
// Link adjacent (long)-bridges so that artwork is updated correctly
foreach (var b in w.Actors.SelectMany(a => a.TraitsImplementing<Bridge>()))
b.LinkNeighbouringBridges(w,this);
b.LinkNeighbouringBridges(w, this);
}
void ConvertBridgeToActor(World w, int i, int j)
void ConvertBridgeToActor(World w, CPos cell)
{
// This cell already has a bridge overlaying it from a previous iteration
if (Bridges[i,j] != null)
if (bridges[cell] != null)
return;
// Correlate the tile "image" aka subtile with its position to find the template origin
var tile = w.Map.MapTiles.Value[i, j].Type;
var index = w.Map.MapTiles.Value[i, j].Index;
var tile = w.Map.MapTiles.Value[cell].Type;
var index = w.Map.MapTiles.Value[cell].Index;
var template = w.TileSet.Templates[tile];
var ni = i - index % template.Size.X;
var nj = j - index / template.Size.X;
var ni = cell.X - index % template.Size.X;
var nj = cell.Y - index / template.Size.X;
// Create a new actor for this bridge and keep track of which subtiles this bridge includes
var bridge = w.CreateActor(BridgeTypes[tile].First, new TypeDictionary
var bridge = w.CreateActor(bridgeTypes[tile].First, new TypeDictionary
{
new LocationInit(new CPos(ni, nj)),
new OwnerInit(w.WorldActor.Owner),
new HealthInit(BridgeTypes[tile].Second),
new HealthInit(bridgeTypes[tile].Second),
}).Trait<Bridge>();
var subTiles = new Dictionary<CPos, byte>();
// For each subtile in the template
for (byte ind = 0; ind < template.Size.X*template.Size.Y; ind++)
for (byte ind = 0; ind < template.Size.X * template.Size.Y; ind++)
{
// Where do we expect to find the subtile
var x = ni + ind % template.Size.X;
var y = nj + ind / template.Size.X;
var subtile = new CPos(ni + ind % template.Size.X, nj + ind / template.Size.X);
// This isn't the bridge you're looking for
if (!w.Map.IsInMap(x, y) || w.Map.MapTiles.Value[x, y].Type != tile ||
w.Map.MapTiles.Value[x, y].Index != ind)
if (!w.Map.Contains(subtile) || w.Map.MapTiles.Value[subtile].Type != tile ||
w.Map.MapTiles.Value[subtile].Index != ind)
continue;
subTiles.Add(new CPos(x, y), ind);
Bridges[x,y] = bridge;
subTiles.Add(subtile, ind);
bridges[subtile] = bridge;
}
bridge.Create(tile, subTiles);
}
// Used to check for neighbouring bridges
public Bridge GetBridge(CPos cell)
{
if (!world.Map.IsInMap(cell))
if (!world.Map.Contains(cell))
return null;
return Bridges[ cell.X, cell.Y ];
return bridges[cell];
}
}
}

View File

@@ -19,14 +19,14 @@ namespace OpenRA.Mods.RA.Buildings
public class BuildingInfluence
{
Actor[,] influence;
CellLayer<Actor> influence;
Map map;
public BuildingInfluence(World world)
{
map = world.Map;
influence = new Actor[map.MapSize.X, map.MapSize.Y];
influence = new CellLayer<Actor>(map);
world.ActorAdded += a =>
{
@@ -35,8 +35,8 @@ namespace OpenRA.Mods.RA.Buildings
return;
foreach (var u in FootprintUtils.Tiles(map.Rules, a.Info.Name, b.Info, a.Location))
if (map.IsInMap(u) && influence[u.X, u.Y] == null)
influence[u.X, u.Y] = a;
if (map.Contains(u) && influence[u] == null)
influence[u] = a;
};
world.ActorRemoved += a =>
@@ -46,17 +46,17 @@ namespace OpenRA.Mods.RA.Buildings
return;
foreach (var u in FootprintUtils.Tiles(map.Rules, a.Info.Name, b.Info, a.Location))
if (map.IsInMap(u) && influence[u.X, u.Y] == a)
influence[u.X, u.Y] = null;
if (map.Contains(u) && influence[u] == a)
influence[u] = null;
};
}
public Actor GetBuildingAt(CPos cell)
{
if (!map.IsInMap(cell))
if (!map.Contains(cell))
return null;
return influence[cell.X, cell.Y];
return influence[cell];
}
}
}

View File

@@ -57,11 +57,11 @@ namespace OpenRA.Mods.RA.Buildings
continue;
// Don't place under other buildings or custom terrain
if (bi.GetBuildingAt(c) != self || map.CustomTerrain[c.X, c.Y] != -1)
if (bi.GetBuildingAt(c) != self || map.CustomTerrain[c] != -1)
continue;
var index = Game.CosmeticRandom.Next(template.TilesCount);
layer.AddTile(c, new TileReference<ushort, byte>(template.Id, (byte)index));
layer.AddTile(c, new TerrainTile(template.Id, (byte)index));
}
return;
@@ -77,10 +77,10 @@ namespace OpenRA.Mods.RA.Buildings
continue;
// Don't place under other buildings or custom terrain
if (bi.GetBuildingAt(c) != self || map.CustomTerrain[c.X, c.Y] != -1)
if (bi.GetBuildingAt(c) != self || map.CustomTerrain[c] != -1)
continue;
layer.AddTile(c, new TileReference<ushort, byte>(template.Id, (byte)i));
layer.AddTile(c, new TerrainTile(template.Id, (byte)i));
}
}
}

View File

@@ -26,7 +26,7 @@ namespace OpenRA.Mods.RA.Buildings
if (world.WorldActor.Trait<BuildingInfluence>().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.Map.GetTerrainInfo(a).Type);
return world.Map.Contains(a) && bi.TerrainTypes.Contains(world.Map.GetTerrainInfo(a).Type);
}
public static bool CanPlaceBuilding(this World world, string name, BuildingInfo building, CPos topLeft, Actor toIgnore)
@@ -36,7 +36,7 @@ namespace OpenRA.Mods.RA.Buildings
var res = world.WorldActor.Trait<ResourceLayer>();
return FootprintUtils.Tiles(world.Map.Rules, name, building, topLeft).All(
t => world.Map.IsInMap(t.X, t.Y) && res.GetResource(t) == null &&
t => world.Map.Contains(t) && res.GetResource(t) == null &&
world.IsCellBuildable(t, building, toIgnore));
}

View File

@@ -37,7 +37,7 @@ namespace OpenRA.Mods.RA
var world = firedBy.World;
var targetTile = pos.ToCPos();
if (!world.Map.IsInMap(targetTile))
if (!world.Map.Contains(targetTile))
return;
var isWater = pos.Z <= 0 && world.Map.GetTerrainInfo(targetTile).IsWater;

View File

@@ -90,7 +90,7 @@ namespace OpenRA.Mods.RA
public bool CanEnterCell(CPos cell, Actor ignoreActor, bool checkTransientActors)
{
if (!self.World.Map.IsInMap(cell.X, cell.Y)) return false;
if (!self.World.Map.Contains(cell)) return false;
var type = self.World.Map.GetTerrainInfo(cell).Type;
if (!info.TerrainTypes.Contains(type))

View File

@@ -160,7 +160,7 @@ namespace OpenRA.Mods.RA.Effects
|| (dist.LengthSquared < MissileCloseEnough.Range * MissileCloseEnough.Range) // Within range
|| (info.RangeLimit != 0 && ticks > info.RangeLimit) // Ran out of fuel
|| (!info.High && world.ActorMap.GetUnitsAt(cell).Any(a => a.HasTrait<IBlocksBullets>())) // Hit a wall
|| !world.Map.IsInMap(cell) // This also avoids an IndexOutOfRangeException in GetTerrainInfo below.
|| !world.Map.Contains(cell) // This also avoids an IndexOutOfRangeException in GetTerrainInfo below.
|| (!string.IsNullOrEmpty(info.BoundToTerrainType) && world.Map.GetTerrainInfo(cell).Type != info.BoundToTerrainType); // Hit incompatible terrain
if (shouldExplode)

View File

@@ -54,7 +54,7 @@ namespace OpenRA.Mods.RA
public IEnumerable<Pair<CPos, SubCell>> OccupiedCells() { yield return Pair.New(TopLeft, SubCell.FullCell); }
public bool CanEnterCell(CPos cell, Actor ignoreActor, bool checkTransientActors)
{
if (!self.World.Map.IsInMap(cell.X, cell.Y))
if (!self.World.Map.Contains(cell))
return false;
if (!info.AllowedTerrain.Contains(self.World.Map.GetTerrainInfo(cell).Type))

View File

@@ -202,7 +202,7 @@ namespace OpenRA.Mods.RA
return false;
var location = target.CenterPosition.ToCPos();
if (!self.World.Map.IsInMap(location))
if (!self.World.Map.Contains(location))
return false;
cursor = "ability";

View File

@@ -101,7 +101,7 @@ namespace OpenRA.Mods.RA.Move
public int MovementCostForCell(World world, CPos cell)
{
if (!world.Map.IsInMap(cell.X, cell.Y))
if (!world.Map.Contains(cell))
return int.MaxValue;
var index = world.Map.GetTerrainIndex(cell);
@@ -580,7 +580,7 @@ namespace OpenRA.Mods.RA.Move
if (self.Owner.Shroud.IsExplored(location))
cursor = self.World.Map.GetTerrainInfo(location).CustomCursor ?? cursor;
if (!self.World.Map.IsInMap(location) || (self.Owner.Shroud.IsExplored(location) &&
if (!self.World.Map.Contains(location) || (self.Owner.Shroud.IsExplored(location) &&
unitType.MovementCostForCell(self.World, location) == int.MaxValue))
cursor = "move-blocked";

56
OpenRA.Mods.RA/Move/PathFinder.cs Executable file → Normal file
View File

@@ -12,6 +12,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using OpenRA;
using OpenRA.Primitives;
using OpenRA.Support;
using OpenRA.Traits;
@@ -68,7 +69,7 @@ namespace OpenRA.Mods.RA.Move
var pb = FindBidiPath(
PathSearch.FromPoint(world, mi, self, target, from, true),
PathSearch.FromPoint(world, mi, self, from, target, true).InReverse()
PathSearch.FromPoint(world, mi, self, from, target, true).Reverse()
);
CheckSanePath2(pb, from, target);
@@ -109,7 +110,7 @@ namespace OpenRA.Mods.RA.Move
var path = FindBidiPath(
PathSearch.FromPoints(world, mi, self, tilesInRange, src, true),
PathSearch.FromPoint(world, mi, self, src, targetCell, true).InReverse()
PathSearch.FromPoint(world, mi, self, src, targetCell, true).Reverse()
);
return path;
@@ -124,21 +125,19 @@ namespace OpenRA.Mods.RA.Move
{
List<CPos> path = null;
while (!search.queue.Empty)
while (!search.Queue.Empty)
{
var p = search.Expand(world);
if (search.heuristic(p) == 0)
if (search.Heuristic(p) == 0)
{
path = MakePath(search.cellInfo, p);
path = MakePath(search.CellInfo, p);
break;
}
}
var dbg = world.WorldActor.TraitOrDefault<PathfinderDebugOverlay>();
if (dbg != null)
{
dbg.AddLayer(search.considered.Select(p => new Pair<CPos, int>(p, search.cellInfo[p.X, p.Y].MinCost)), search.maxCost, search.owner);
}
dbg.AddLayer(search.Considered.Select(p => new Pair<CPos, int>(p, search.CellInfo[p].MinCost)), search.MaxCost, search.Owner);
if (path != null)
return path;
@@ -149,15 +148,15 @@ namespace OpenRA.Mods.RA.Move
}
}
static List<CPos> MakePath(CellInfo[,] cellInfo, CPos destination)
static List<CPos> MakePath(CellLayer<CellInfo> cellInfo, CPos destination)
{
var ret = new List<CPos>();
var pathNode = destination;
while (cellInfo[pathNode.X, pathNode.Y].Path != pathNode)
while (cellInfo[pathNode].Path != pathNode)
{
ret.Add(pathNode);
pathNode = cellInfo[pathNode.X, pathNode.Y].Path;
pathNode = cellInfo[pathNode].Path;
}
ret.Add(pathNode);
@@ -165,9 +164,8 @@ namespace OpenRA.Mods.RA.Move
return ret;
}
public List<CPos> FindBidiPath( /* searches from both ends toward each other */
PathSearch fromSrc,
PathSearch fromDest)
// Searches from both ends toward each other
public List<CPos> FindBidiPath(PathSearch fromSrc, PathSearch fromDest)
{
using (new PerfSample("Pathfinder"))
{
@@ -176,13 +174,13 @@ namespace OpenRA.Mods.RA.Move
{
List<CPos> path = null;
while (!fromSrc.queue.Empty && !fromDest.queue.Empty)
while (!fromSrc.Queue.Empty && !fromDest.Queue.Empty)
{
/* make some progress on the first search */
var p = fromSrc.Expand(world);
if (fromDest.cellInfo[p.X, p.Y].Seen &&
fromDest.cellInfo[p.X, p.Y].MinCost < float.PositiveInfinity)
if (fromDest.CellInfo[p].Seen &&
fromDest.CellInfo[p].MinCost < float.PositiveInfinity)
{
path = MakeBidiPath(fromSrc, fromDest, p);
break;
@@ -191,8 +189,8 @@ namespace OpenRA.Mods.RA.Move
/* make some progress on the second search */
var q = fromDest.Expand(world);
if (fromSrc.cellInfo[q.X, q.Y].Seen &&
fromSrc.cellInfo[q.X, q.Y].MinCost < float.PositiveInfinity)
if (fromSrc.CellInfo[q].Seen &&
fromSrc.CellInfo[q].MinCost < float.PositiveInfinity)
{
path = MakeBidiPath(fromSrc, fromDest, q);
break;
@@ -202,8 +200,8 @@ namespace OpenRA.Mods.RA.Move
var dbg = world.WorldActor.TraitOrDefault<PathfinderDebugOverlay>();
if (dbg != null)
{
dbg.AddLayer(fromSrc.considered.Select(p => new Pair<CPos, int>(p, fromSrc.cellInfo[p.X, p.Y].MinCost)), fromSrc.maxCost, fromSrc.owner);
dbg.AddLayer(fromDest.considered.Select(p => new Pair<CPos, int>(p, fromDest.cellInfo[p.X, p.Y].MinCost)), fromDest.maxCost, fromDest.owner);
dbg.AddLayer(fromSrc.Considered.Select(p => new Pair<CPos, int>(p, fromSrc.CellInfo[p].MinCost)), fromSrc.MaxCost, fromSrc.Owner);
dbg.AddLayer(fromDest.Considered.Select(p => new Pair<CPos, int>(p, fromDest.CellInfo[p].MinCost)), fromDest.MaxCost, fromDest.Owner);
}
if (path != null)
@@ -216,25 +214,25 @@ namespace OpenRA.Mods.RA.Move
static List<CPos> MakeBidiPath(PathSearch a, PathSearch b, CPos p)
{
var ca = a.cellInfo;
var cb = b.cellInfo;
var ca = a.CellInfo;
var cb = b.CellInfo;
var ret = new List<CPos>();
var q = p;
while (ca[q.X, q.Y].Path != q)
while (ca[q].Path != q)
{
ret.Add(q);
q = ca[q.X, q.Y].Path;
q = ca[q].Path;
}
ret.Add(q);
ret.Reverse();
q = p;
while (cb[q.X, q.Y].Path != q)
while (cb[q].Path != q)
{
q = cb[q.X, q.Y].Path;
q = cb[q].Path;
ret.Add(q);
}
@@ -286,8 +284,8 @@ namespace OpenRA.Mods.RA.Move
public struct PathDistance : IComparable<PathDistance>
{
public int EstTotal;
public CPos Location;
public readonly int EstTotal;
public readonly CPos Location;
public PathDistance(int estTotal, CPos location)
{

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
* 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,
@@ -10,45 +10,97 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA;
using OpenRA.Primitives;
namespace OpenRA.Mods.RA.Move
{
public sealed class PathSearch : IDisposable
{
World world;
public CellInfo[,] cellInfo;
public PriorityQueue<PathDistance> queue;
public Func<CPos, int> heuristic;
public CellLayer<CellInfo> CellInfo;
public PriorityQueue<PathDistance> Queue;
public Func<CPos, int> Heuristic;
public bool CheckForBlocked;
public Actor IgnoreBuilding;
public bool InReverse;
public HashSet<CPos> Considered;
public Player Owner { get { return self.Owner; } }
public int MaxCost;
Actor self;
MobileInfo mobileInfo;
Func<CPos, int> customCost;
Func<CPos, bool> customBlock;
public bool checkForBlocked;
public Actor ignoreBuilding;
public bool inReverse;
public HashSet<CPos> considered;
public int maxCost;
Pair<CVec, int>[] nextDirections;
MobileInfo mobileInfo;
Actor self;
public Player owner { get { return self.Owner; } }
int laneBias = 1;
public PathSearch(World world, MobileInfo mobileInfo, Actor self)
{
this.world = world;
cellInfo = InitCellInfo();
this.self = self;
CellInfo = InitCellInfo();
this.mobileInfo = mobileInfo;
this.self = self;
customCost = null;
queue = new PriorityQueue<PathDistance>();
considered = new HashSet<CPos>();
maxCost = 0;
Queue = new PriorityQueue<PathDistance>();
Considered = new HashSet<CPos>();
MaxCost = 0;
nextDirections = CVec.directions.Select(d => new Pair<CVec, int>(d, 0)).ToArray();
}
public PathSearch InReverse()
public static PathSearch Search(World world, MobileInfo mi, Actor self, bool checkForBlocked)
{
inReverse = true;
var search = new PathSearch(world, mi, self)
{
CheckForBlocked = checkForBlocked
};
return search;
}
public static PathSearch FromPoint(World world, MobileInfo mi, Actor self, CPos from, CPos target, bool checkForBlocked)
{
var search = new PathSearch(world, mi, self)
{
Heuristic = DefaultEstimator(target),
CheckForBlocked = checkForBlocked
};
search.AddInitialCell(from);
return search;
}
public static PathSearch FromPoints(World world, MobileInfo mi, Actor self, IEnumerable<CPos> froms, CPos target, bool checkForBlocked)
{
var search = new PathSearch(world, mi, self)
{
Heuristic = DefaultEstimator(target),
CheckForBlocked = checkForBlocked
};
foreach (var sl in froms)
search.AddInitialCell(sl);
return search;
}
public static Func<CPos, int> DefaultEstimator(CPos destination)
{
return here =>
{
var diag = Math.Min(Math.Abs(here.X - destination.X), Math.Abs(here.Y - destination.Y));
var straight = Math.Abs(here.X - destination.X) + Math.Abs(here.Y - destination.Y);
// HACK: this relies on fp and cell-size assumptions.
var h = (3400 * diag / 24) + 100 * (straight - (2 * diag));
return (int)(h * 1.001);
};
}
public PathSearch Reverse()
{
InReverse = true;
return this;
}
@@ -60,13 +112,13 @@ namespace OpenRA.Mods.RA.Move
public PathSearch WithIgnoredBuilding(Actor b)
{
ignoreBuilding = b;
IgnoreBuilding = b;
return this;
}
public PathSearch WithHeuristic(Func<CPos, int> h)
{
heuristic = h;
Heuristic = h;
return this;
}
@@ -78,7 +130,7 @@ namespace OpenRA.Mods.RA.Move
public PathSearch WithoutLaneBias()
{
LaneBias = 0;
laneBias = 0;
return this;
}
@@ -88,18 +140,20 @@ namespace OpenRA.Mods.RA.Move
return this;
}
int LaneBias = 1;
public CPos Expand(World world)
{
var p = queue.Pop();
while (cellInfo[p.Location.X, p.Location.Y].Seen)
if (queue.Empty)
var p = Queue.Pop();
while (CellInfo[p.Location].Seen)
{
if (Queue.Empty)
return p.Location;
else
p = queue.Pop();
cellInfo[p.Location.X, p.Location.Y].Seen = true;
p = Queue.Pop();
}
var pCell = CellInfo[p.Location];
pCell.Seen = true;
CellInfo[p.Location] = pCell;
var thisCost = mobileInfo.MovementCostForCell(world, p.Location);
@@ -114,7 +168,7 @@ namespace OpenRA.Mods.RA.Move
}
// This current cell is ok; check all immediate directions:
considered.Add(p.Location);
Considered.Add(p.Location);
for (var i = 0; i < nextDirections.Length; ++i)
{
@@ -124,9 +178,10 @@ namespace OpenRA.Mods.RA.Move
var newHere = p.Location + d;
// Is this direction flat-out unusable or already seen?
if (!world.Map.IsInMap(newHere.X, newHere.Y))
if (!world.Map.Contains(newHere))
continue;
if (cellInfo[newHere.X, newHere.Y].Seen)
if (CellInfo[newHere].Seen)
continue;
// Now we may seriously consider this direction using heuristics:
@@ -135,18 +190,19 @@ namespace OpenRA.Mods.RA.Move
if (costHere == int.MaxValue)
continue;
if (!mobileInfo.CanEnterCell(world, self, newHere, ignoreBuilding, checkForBlocked, false))
if (!mobileInfo.CanEnterCell(world, self, newHere, IgnoreBuilding, CheckForBlocked, false))
continue;
if (customBlock != null && customBlock(newHere))
continue;
var est = heuristic(newHere);
var est = Heuristic(newHere);
if (est == int.MaxValue)
continue;
var cellCost = costHere;
if (d.X * d.Y != 0) cellCost = (cellCost * 34) / 24;
if (d.X * d.Y != 0)
cellCost = (cellCost * 34) / 24;
var userCost = 0;
if (customCost != null)
@@ -156,105 +212,82 @@ namespace OpenRA.Mods.RA.Move
}
// directional bonuses for smoother flow!
if (LaneBias != 0)
if (laneBias != 0)
{
var ux = (newHere.X + (inReverse ? 1 : 0) & 1);
var uy = (newHere.Y + (inReverse ? 1 : 0) & 1);
var ux = newHere.X + (InReverse ? 1 : 0) & 1;
var uy = newHere.Y + (InReverse ? 1 : 0) & 1;
if (ux == 0 && d.Y < 0) cellCost += LaneBias;
else if (ux == 1 && d.Y > 0) cellCost += LaneBias;
if (uy == 0 && d.X < 0) cellCost += LaneBias;
else if (uy == 1 && d.X > 0) cellCost += LaneBias;
if (ux == 0 && d.Y < 0)
cellCost += laneBias;
else if (ux == 1 && d.Y > 0)
cellCost += laneBias;
if (uy == 0 && d.X < 0)
cellCost += laneBias;
else if (uy == 1 && d.X > 0)
cellCost += laneBias;
}
var newCost = cellInfo[p.Location.X, p.Location.Y].MinCost + cellCost;
var newCost = CellInfo[p.Location].MinCost + cellCost;
// Cost is even higher; next direction:
if (newCost > cellInfo[newHere.X, newHere.Y].MinCost)
if (newCost > CellInfo[newHere].MinCost)
continue;
cellInfo[newHere.X, newHere.Y].Path = p.Location;
cellInfo[newHere.X, newHere.Y].MinCost = newCost;
var hereCell = CellInfo[newHere];
hereCell.Path = p.Location;
hereCell.MinCost = newCost;
CellInfo[newHere] = hereCell;
nextDirections[i].Second = newCost + est;
queue.Add(new PathDistance(newCost + est, newHere));
Queue.Add(new PathDistance(newCost + est, newHere));
if (newCost > maxCost) maxCost = newCost;
considered.Add(newHere);
if (newCost > MaxCost)
MaxCost = newCost;
Considered.Add(newHere);
}
// Sort to prefer the cheaper direction:
//Array.Sort(nextDirections, (a, b) => a.Second.CompareTo(b.Second));
// Array.Sort(nextDirections, (a, b) => a.Second.CompareTo(b.Second));
return p.Location;
}
public void AddInitialCell(CPos location)
{
if (!world.Map.IsInMap(location.X, location.Y))
if (!self.World.Map.Contains(location))
return;
cellInfo[location.X, location.Y] = new CellInfo(0, location, false);
queue.Add(new PathDistance(heuristic(location), location));
CellInfo[location] = new CellInfo(0, location, false);
Queue.Add(new PathDistance(Heuristic(location), location));
}
public static PathSearch Search(World world, MobileInfo mi, Actor self, bool checkForBlocked)
static readonly Queue<CellLayer<CellInfo>> CellInfoPool = new Queue<CellLayer<CellInfo>>();
static CellLayer<CellInfo> GetFromPool()
{
var search = new PathSearch(world, mi, self)
{
checkForBlocked = checkForBlocked
};
return search;
lock (CellInfoPool)
return CellInfoPool.Dequeue();
}
public static PathSearch FromPoint(World world, MobileInfo mi, Actor self, CPos from, CPos target, bool checkForBlocked)
static void PutBackIntoPool(CellLayer<CellInfo> ci)
{
var search = new PathSearch(world, mi, self)
{
heuristic = DefaultEstimator(target),
checkForBlocked = checkForBlocked
};
search.AddInitialCell(from);
return search;
lock (CellInfoPool)
CellInfoPool.Enqueue(ci);
}
public static PathSearch FromPoints(World world, MobileInfo mi, Actor self, IEnumerable<CPos> froms, CPos target, bool checkForBlocked)
CellLayer<CellInfo> InitCellInfo()
{
var search = new PathSearch(world, mi, self)
{
heuristic = DefaultEstimator(target),
checkForBlocked = checkForBlocked
};
CellLayer<CellInfo> result = null;
var mapSize = new Size(self.World.Map.MapSize.X, self.World.Map.MapSize.Y);
foreach (var sl in froms)
search.AddInitialCell(sl);
return search;
}
static readonly Queue<CellInfo[,]> cellInfoPool = new Queue<CellInfo[,]>();
static CellInfo[,] GetFromPool()
{
lock (cellInfoPool)
return cellInfoPool.Dequeue();
}
static void PutBackIntoPool(CellInfo[,] ci)
{
lock (cellInfoPool)
cellInfoPool.Enqueue(ci);
}
CellInfo[,] InitCellInfo()
{
CellInfo[,] result = null;
while (cellInfoPool.Count > 0)
// HACK: Uses a static cache so that double-ended searches (which have two PathSearch instances)
// can implicitly share data. The PathFinder should allocate the CellInfo array and pass it
// explicitly to the things that need to share it.
while (CellInfoPool.Count > 0)
{
var cellInfo = GetFromPool();
if (cellInfo.GetUpperBound(0) != world.Map.MapSize.X - 1 ||
cellInfo.GetUpperBound(1) != world.Map.MapSize.Y - 1)
if (cellInfo.Size != mapSize)
{
Log.Write("debug", "Discarding old pooled CellInfo of wrong size.");
continue;
@@ -265,36 +298,24 @@ namespace OpenRA.Mods.RA.Move
}
if (result == null)
result = new CellInfo[world.Map.MapSize.X, world.Map.MapSize.Y];
result = new CellLayer<CellInfo>(self.World.Map);
for (var x = 0; x < world.Map.MapSize.X; x++)
for (var y = 0; y < world.Map.MapSize.Y; y++)
result[ x, y ] = new CellInfo( int.MaxValue, new CPos( x, y ), false );
foreach (var cell in self.World.Map.Cells)
result[cell] = new CellInfo(int.MaxValue, cell, false);
return result;
}
public static Func<CPos, int> DefaultEstimator(CPos destination)
{
return here =>
{
var diag = Math.Min(Math.Abs(here.X - destination.X), Math.Abs(here.Y - destination.Y));
var straight = (Math.Abs(here.X - destination.X) + Math.Abs(here.Y - destination.Y));
var h = (3400 * diag / 24) + 100 * (straight - (2 * diag));
h = (int)(h * 1.001);
return h;
};
}
bool disposed;
public void Dispose()
{
if (disposed)
return;
disposed = true;
PutBackIntoPool(cellInfo);
cellInfo = null;
PutBackIntoPool(CellInfo);
CellInfo = null;
GC.SuppressFinalize(this);
}

View File

@@ -63,7 +63,7 @@ namespace OpenRA.Mods.RA
return false;
var location = target.CenterPosition.ToCPos();
if (self.World.Map.IsInMap(location))
if (self.World.Map.Contains(location))
{
cursor = "ability";
return true;

View File

@@ -44,7 +44,7 @@ namespace OpenRA.Mods.RA.Render
if (self.CenterPosition.Z > 0 || move.IsMoving)
return false;
return cargo.CurrentAdjacentCells.Any(c => self.World.Map.IsInMap(c)
return cargo.CurrentAdjacentCells.Any(c => self.World.Map.Contains(c)
&& info.OpenTerrainTypes.Contains(self.World.Map.GetTerrainInfo(c).Type));
}

View File

@@ -10,6 +10,7 @@
using System.Drawing;
using System.Linq;
using OpenRA;
using OpenRA.Graphics;
using OpenRA.Traits;
@@ -35,36 +36,41 @@ namespace OpenRA.Mods.RA
public class ShroudRenderer : IRenderShroud, IWorldLoaded
{
struct ShroudTile
class ShroudTile
{
public CPos Position;
public float2 ScreenPosition;
public int Variant;
public readonly CPos Position;
public readonly float2 ScreenPosition;
public readonly int Variant;
public Sprite Fog;
public Sprite Shroud;
public ShroudTile(CPos position, float2 screenPosition, int variant)
{
Position = position;
ScreenPosition = screenPosition;
Variant = variant;
}
}
ShroudRendererInfo info;
Sprite[] sprites;
Sprite unexploredTile;
int[] spriteMap;
ShroudTile[] tiles;
int tileStride, variantStride;
CellLayer<ShroudTile> tiles;
int variantStride;
int shroudHash;
PaletteReference fogPalette, shroudPalette;
Rectangle bounds;
bool useExtendedIndex;
Map map;
public ShroudRenderer(World world, ShroudRendererInfo info)
{
var map = world.Map;
bounds = map.Bounds;
useExtendedIndex = info.UseExtendedIndex;
this.info = info;
map = world.Map;
tiles = new ShroudTile[map.MapSize.X * map.MapSize.Y];
tileStride = map.MapSize.X;
tiles = new CellLayer<ShroudTile>(map);
// Force update on first render
shroudHash = -1;
@@ -80,14 +86,10 @@ namespace OpenRA.Mods.RA
}
// Mapping of shrouded directions -> sprite index
spriteMap = new int[useExtendedIndex ? 256 : 16];
spriteMap = new int[info.UseExtendedIndex ? 256 : 16];
for (var i = 0; i < info.Index.Length; i++)
spriteMap[info.Index[i]] = i;
// Set individual tile variants to reduce tiling
for (var i = 0; i < tiles.Length; i++)
tiles[i].Variant = Game.CosmeticRandom.Next(info.Variants.Length);
// Synthesize unexplored tile if it isn't defined
if (!info.Index.Contains(0))
{
@@ -102,21 +104,21 @@ namespace OpenRA.Mods.RA
static int FoggedEdges(Shroud s, CPos p, bool useExtendedIndex)
{
if (!s.IsVisible(p.X, p.Y))
if (!s.IsVisible(p))
return 15;
// If a side is shrouded then we also count the corners
var u = 0;
if (!s.IsVisible(p.X, p.Y - 1)) u |= 0x13;
if (!s.IsVisible(p.X + 1, p.Y)) u |= 0x26;
if (!s.IsVisible(p.X, p.Y + 1)) u |= 0x4C;
if (!s.IsVisible(p.X - 1, p.Y)) u |= 0x89;
if (!s.IsVisible(p + new CVec(0, -1))) u |= 0x13;
if (!s.IsVisible(p + new CVec(1, 0))) u |= 0x26;
if (!s.IsVisible(p + new CVec(0, 1))) u |= 0x4C;
if (!s.IsVisible(p + new CVec(-1, 0))) u |= 0x89;
var uside = u & 0x0F;
if (!s.IsVisible(p.X - 1, p.Y - 1)) u |= 0x01;
if (!s.IsVisible(p.X + 1, p.Y - 1)) u |= 0x02;
if (!s.IsVisible(p.X + 1, p.Y + 1)) u |= 0x04;
if (!s.IsVisible(p.X - 1, p.Y + 1)) u |= 0x08;
if (!s.IsVisible(p + new CVec(-1, -1))) u |= 0x01;
if (!s.IsVisible(p + new CVec(1, -1))) u |= 0x02;
if (!s.IsVisible(p + new CVec(1, 1))) u |= 0x04;
if (!s.IsVisible(p + new CVec(-1, 1))) u |= 0x08;
// RA provides a set of frames for tiles with shrouded
// corners but unshrouded edges. We want to detect this
@@ -129,21 +131,21 @@ namespace OpenRA.Mods.RA
static int ShroudedEdges(Shroud s, CPos p, bool useExtendedIndex)
{
if (!s.IsExplored(p.X, p.Y))
if (!s.IsExplored(p))
return 15;
// If a side is shrouded then we also count the corners
var u = 0;
if (!s.IsExplored(p.X, p.Y - 1)) u |= 0x13;
if (!s.IsExplored(p.X + 1, p.Y)) u |= 0x26;
if (!s.IsExplored(p.X, p.Y + 1)) u |= 0x4C;
if (!s.IsExplored(p.X - 1, p.Y)) u |= 0x89;
if (!s.IsExplored(p + new CVec(0, -1))) u |= 0x13;
if (!s.IsExplored(p + new CVec(1, 0))) u |= 0x26;
if (!s.IsExplored(p + new CVec(0, 1))) u |= 0x4C;
if (!s.IsExplored(p + new CVec(-1, 0))) u |= 0x89;
var uside = u & 0x0F;
if (!s.IsExplored(p.X - 1, p.Y - 1)) u |= 0x01;
if (!s.IsExplored(p.X + 1, p.Y - 1)) u |= 0x02;
if (!s.IsExplored(p.X + 1, p.Y + 1)) u |= 0x04;
if (!s.IsExplored(p.X - 1, p.Y + 1)) u |= 0x08;
if (!s.IsExplored(p + new CVec(-1, -1))) u |= 0x01;
if (!s.IsExplored(p + new CVec(1, -1))) u |= 0x02;
if (!s.IsExplored(p + new CVec(1, 1))) u |= 0x04;
if (!s.IsExplored(p + new CVec(-1, 1))) u |= 0x08;
// RA provides a set of frames for tiles with shrouded
// corners but unshrouded edges. We want to detect this
@@ -173,15 +175,12 @@ namespace OpenRA.Mods.RA
public void WorldLoaded(World w, WorldRenderer wr)
{
// Cache the tile positions to avoid unnecessary calculations
for (var i = bounds.Left; i < bounds.Right; i++)
// Initialize tile cache
foreach (var cell in map.Cells)
{
for (var j = bounds.Top; j < bounds.Bottom; j++)
{
var k = j * tileStride + i;
tiles[k].Position = new CPos(i, j);
tiles[k].ScreenPosition = wr.ScreenPosition(tiles[k].Position.CenterPosition);
}
var screen = wr.ScreenPosition(cell.CenterPosition);
var variant = Game.CosmeticRandom.Next(info.Variants.Length);
tiles[cell] = new ShroudTile(cell, screen, variant);
}
fogPalette = wr.Palette("fog");
@@ -209,22 +208,25 @@ namespace OpenRA.Mods.RA
if (shroud == null)
{
// Players with no shroud see the whole map so we only need to set the edges
for (var k = 0; k < tiles.Length; k++)
foreach (var cell in map.Cells)
{
var shrouded = ObserverShroudedEdges(tiles[k].Position, bounds, useExtendedIndex);
tiles[k].Shroud = GetTile(shrouded, tiles[k].Variant);
tiles[k].Fog = GetTile(shrouded, tiles[k].Variant);
var t = tiles[cell];
var shrouded = ObserverShroudedEdges(t.Position, map.Bounds, info.UseExtendedIndex);
t.Shroud = GetTile(shrouded, t.Variant);
t.Fog = GetTile(shrouded, t.Variant);
}
}
else
{
for (var k = 0; k < tiles.Length; k++)
foreach (var cell in map.Cells)
{
var shrouded = ShroudedEdges(shroud, tiles[k].Position, useExtendedIndex);
var fogged = FoggedEdges(shroud, tiles[k].Position, useExtendedIndex);
var t = tiles[cell];
var shrouded = ShroudedEdges(shroud, t.Position, info.UseExtendedIndex);
var fogged = FoggedEdges(shroud, t.Position, info.UseExtendedIndex);
tiles[k].Shroud = GetTile(shrouded, tiles[k].Variant);
tiles[k].Fog = GetTile(fogged, tiles[k].Variant);
t.Shroud = GetTile(shrouded, t.Variant);
t.Fog = GetTile(fogged, t.Variant);
}
}
}
@@ -233,27 +235,20 @@ namespace OpenRA.Mods.RA
{
Update(shroud);
var clip = wr.Viewport.CellBounds;
var width = clip.Width;
for (var j = clip.Top; j < clip.Bottom; j++)
foreach (var cell in wr.Viewport.VisibleCells)
{
var start = j * tileStride + clip.Left;
for (var k = 0; k < width; k++)
var t = tiles[cell];
if (t.Shroud != null)
{
var s = tiles[start + k].Shroud;
var f = tiles[start + k].Fog;
var pos = t.ScreenPosition - 0.5f * t.Shroud.size;
Game.Renderer.WorldSpriteRenderer.DrawSprite(t.Shroud, pos, shroudPalette);
}
if (s != null)
{
var pos = tiles[start + k].ScreenPosition - 0.5f * s.size;
Game.Renderer.WorldSpriteRenderer.DrawSprite(s, pos, shroudPalette);
}
if (f != null)
{
var pos = tiles[start + k].ScreenPosition - 0.5f * f.size;
Game.Renderer.WorldSpriteRenderer.DrawSprite(f, pos, fogPalette);
}
if (t.Fog != null)
{
var pos = t.ScreenPosition - 0.5f * t.Fog.size;
Game.Renderer.WorldSpriteRenderer.DrawSprite(t.Fog, pos, fogPalette);
}
}
}

View File

@@ -245,7 +245,7 @@ namespace OpenRA.Mods.RA
public IEnumerable<Order> Order(World world, CPos xy, MouseInput mi)
{
world.CancelInputMode();
if (mi.Button == expectedButton && world.Map.IsInMap(xy))
if (mi.Button == expectedButton && world.Map.Contains(xy))
yield return new Order(order, manager.self, false) { TargetLocation = xy, SuppressVisualFeedback = true };
}
@@ -258,6 +258,6 @@ namespace OpenRA.Mods.RA
public IEnumerable<IRenderable> Render(WorldRenderer wr, World world) { yield break; }
public void RenderAfterWorld(WorldRenderer wr, World world) { }
public string GetCursor(World world, CPos xy, MouseInput mi) { return world.Map.IsInMap(xy) ? cursor : "generic-blocked"; }
public string GetCursor(World world, CPos xy, MouseInput mi) { return world.Map.Contains(xy) ? cursor : "generic-blocked"; }
}
}

View File

@@ -32,9 +32,9 @@ namespace OpenRA.Mods.RA
dirty = new Dictionary<CPos, Sprite>();
}
public void AddTile(CPos cell, TileReference<ushort, byte> tile)
public void AddTile(CPos cell, TerrainTile tile)
{
map.CustomTerrain[cell.X, cell.Y] = tileset.GetTerrainIndex(tile);
map.CustomTerrain[cell] = tileset.GetTerrainIndex(tile);
// Terrain tiles define their origin at the topleft
var s = theater.TileSprite(tile);
@@ -59,12 +59,11 @@ namespace OpenRA.Mods.RA
public void Render(WorldRenderer wr)
{
var cliprect = wr.Viewport.CellBounds;
var pal = wr.Palette("terrain");
foreach (var kv in tiles)
{
if (!cliprect.Contains(kv.Key.X, kv.Key.Y))
if (!wr.Viewport.VisibleCells.Contains(kv.Key))
continue;
if (wr.world.ShroudObscures(kv.Key))

View File

@@ -53,17 +53,17 @@ namespace OpenRA.Mods.RA
class MovementClassDomainIndex
{
Rectangle bounds;
Map map;
uint movementClass;
int[,] domains;
CellLayer<int> domains;
Dictionary<int, HashSet<int>> transientConnections;
public MovementClassDomainIndex(World world, uint movementClass)
{
bounds = world.Map.Bounds;
map = world.Map;
this.movementClass = movementClass;
domains = new int[(bounds.Width + bounds.X), (bounds.Height + bounds.Y)];
domains = new CellLayer<int>(world.Map);
transientConnections = new Dictionary<int, HashSet<int>>();
BuildDomains(world);
@@ -71,15 +71,15 @@ namespace OpenRA.Mods.RA
public bool IsPassable(CPos p1, CPos p2)
{
if (!bounds.Contains(p1.X, p1.Y) || !bounds.Contains(p2.X, p2.Y))
if (!map.Contains(p1) || !map.Contains(p2))
return false;
if (domains[p1.X, p1.Y] == domains[p2.X, p2.Y])
if (domains[p1] == domains[p2])
return true;
// Even though p1 and p2 are in different domains, it's possible
// that some dynamic terrain (i.e. bridges) may connect them.
return HasConnection(GetDomainOf(p1), GetDomainOf(p2));
return HasConnection(domains[p1], domains[p2]);
}
public void UpdateCells(World world, HashSet<CPos> dirtyCells)
@@ -90,22 +90,21 @@ namespace OpenRA.Mods.RA
{
// Select all neighbors inside the map boundries
var neighbors = CVec.directions.Select(d => d + cell)
.Where(c => bounds.Contains(c.X, c.Y));
.Where(c => map.Contains(c));
var found = false;
foreach (var neighbor in neighbors)
foreach (var n in neighbors)
{
if (!dirtyCells.Contains(neighbor))
if (!dirtyCells.Contains(n))
{
var neighborDomain = GetDomainOf(neighbor);
var match = CanTraverseTile(world, neighbor);
if (match) neighborDomains.Add(neighborDomain);
var neighborDomain = domains[n];
if (CanTraverseTile(world, n))
neighborDomains.Add(neighborDomain);
// Set ourselves to the first non-dirty neighbor we find.
if (!found)
{
SetDomain(cell, neighborDomain);
domains[cell] = neighborDomain;
found = true;
}
}
@@ -113,20 +112,8 @@ namespace OpenRA.Mods.RA
}
foreach (var c1 in neighborDomains)
{
foreach (var c2 in neighborDomains)
CreateConnection(c1, c2);
}
}
int GetDomainOf(CPos p)
{
return domains[p.X, p.Y];
}
void SetDomain(CPos p, int domain)
{
domains[p.X, p.Y] = domain;
}
bool HasConnection(int d1, int d2)
@@ -146,6 +133,7 @@ namespace OpenRA.Mods.RA
{
if (neighbor == d2)
return true;
if (!visited.Contains(neighbor))
toProcess.Push(neighbor);
}
@@ -180,7 +168,7 @@ namespace OpenRA.Mods.RA
var domain = 1;
var visited = new bool[(bounds.Width + bounds.X), (bounds.Height + bounds.Y)];
var visited = new CellLayer<bool>(map);
var toProcess = new Queue<CPos>();
toProcess.Enqueue(new CPos(map.Bounds.Left, map.Bounds.Top));
@@ -192,7 +180,7 @@ namespace OpenRA.Mods.RA
// Technically redundant with the check in the inner loop, but prevents
// ballooning the domain counter.
if (visited[start.X, start.Y])
if (visited[start])
continue;
var domainQueue = new Queue<CPos>();
@@ -205,7 +193,7 @@ namespace OpenRA.Mods.RA
while (domainQueue.Count != 0)
{
var n = domainQueue.Dequeue();
if (visited[n.X, n.Y])
if (visited[n])
continue;
var candidatePassable = CanTraverseTile(world, n);
@@ -215,12 +203,12 @@ namespace OpenRA.Mods.RA
continue;
}
visited[n.X, n.Y] = true;
SetDomain(n, domain);
visited[n] = true;
domains[n] = domain;
// Don't crawl off the map, or add already-visited cells
var neighbors = CVec.directions.Select(d => n + d)
.Where(p => bounds.Contains(p.X, p.Y) && !visited[p.X, p.Y]);
.Where(p => map.Contains(p) && !visited[p]);
foreach (var neighbor in neighbors)
domainQueue.Enqueue(neighbor);

View File

@@ -11,6 +11,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using OpenRA;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Traits;
@@ -20,7 +21,7 @@ namespace OpenRA.Mods.RA
class PathfinderDebugOverlayInfo : TraitInfo<PathfinderDebugOverlay> { }
class PathfinderDebugOverlay : IRenderOverlay, IWorldLoaded
{
Dictionary<Player, int[,]> layers;
Dictionary<Player, CellLayer<int>> layers;
int refreshTick;
World world;
public bool Visible;
@@ -29,7 +30,7 @@ namespace OpenRA.Mods.RA
{
world = w;
refreshTick = 0;
layers = new Dictionary<Player, int[,]>(8);
layers = new Dictionary<Player, CellLayer<int>>(8);
// Enabled via Cheats menu
Visible = false;
@@ -39,15 +40,15 @@ namespace OpenRA.Mods.RA
{
if (maxWeight == 0) return;
int[,] layer;
CellLayer<int> layer;
if (!layers.TryGetValue(pl, out layer))
{
layer = new int[world.Map.MapSize.X, world.Map.MapSize.Y];
layer = new CellLayer<int>(world.Map);
layers.Add(pl, layer);
}
foreach (var p in cellWeights)
layer[p.First.X, p.First.Y] = Math.Min(128, layer[p.First.X, p.First.Y] + (maxWeight - p.Second) * 64 / maxWeight);
layer[p.First] = Math.Min(128, layer[p.First] + (maxWeight - p.Second) * 64 / maxWeight);
}
public void Render(WorldRenderer wr)
@@ -59,29 +60,25 @@ namespace OpenRA.Mods.RA
var doDim = refreshTick - world.WorldTick <= 0;
if (doDim) refreshTick = world.WorldTick + 20;
var viewBounds = wr.Viewport.CellBounds;
foreach (var pair in layers)
{
var c = (pair.Key != null) ? pair.Key.Color.RGB : Color.PaleTurquoise;
var layer = pair.Value;
// Only render quads in viewing range:
for (var j = viewBounds.Top; j <= viewBounds.Bottom; ++j)
foreach (var cell in wr.Viewport.VisibleCells)
{
for (var i = viewBounds.Left; i <= viewBounds.Right; ++i)
{
if (layer[i, j] <= 0)
continue;
if (layer[cell] <= 0)
continue;
var w = Math.Max(0, Math.Min(layer[i, j], 128));
if (doDim)
layer[i, j] = layer[i, j] * 5 / 6;
var w = Math.Max(0, Math.Min(layer[cell], 128));
if (doDim)
layer[cell] = layer[cell] * 5 / 6;
// TODO: This doesn't make sense for isometric terrain
var tl = wr.ScreenPxPosition(new CPos(i, j).TopLeft);
var br = wr.ScreenPxPosition(new CPos(i, j).BottomRight);
qr.FillRect(RectangleF.FromLTRB(tl.X, tl.Y, br.X, br.Y), Color.FromArgb(w, c));
}
// TODO: This doesn't make sense for isometric terrain
var tl = wr.ScreenPxPosition(cell.TopLeft);
var br = wr.ScreenPxPosition(cell.BottomRight);
qr.FillRect(RectangleF.FromLTRB(tl.X, tl.Y, br.X, br.Y), Color.FromArgb(w, c));
}
}
}

View File

@@ -120,12 +120,11 @@ namespace OpenRA.Mods.RA
public void Render(WorldRenderer wr)
{
var cliprect = wr.Viewport.CellBounds;
var pal = wr.Palette("terrain");
foreach (var kv in tiles)
{
if (!cliprect.Contains(kv.Key.X, kv.Key.Y))
if (!wr.Viewport.VisibleCells.Contains(kv.Key))
continue;
if (world.ShroudObscures(kv.Key))

View File

@@ -137,6 +137,7 @@ namespace OpenRA.Utility
var width = Exts.ParseIntegerInvariant(mapSection.GetValue("Width", "0"));
var height = Exts.ParseIntegerInvariant(mapSection.GetValue("Height", "0"));
mapSize = (legacyMapFormat == IniMapFormat.RedAlert) ? 128 : 64;
var size = new Size(mapSize, mapSize);
map.Title = basic.GetValue("Name", Path.GetFileNameWithoutExtension(iniFile));
map.Author = "Westwood Studios";
@@ -148,8 +149,8 @@ namespace OpenRA.Utility
map.Smudges = Exts.Lazy(() => new List<SmudgeReference>());
map.Actors = Exts.Lazy(() => new Dictionary<string, ActorReference>());
map.MapResources = Exts.Lazy(() => new TileReference<byte, byte>[mapSize, mapSize]);
map.MapTiles = Exts.Lazy(() => new TileReference<ushort, byte>[mapSize, mapSize]);
map.MapResources = Exts.Lazy(() => new CellLayer<ResourceTile>(size));
map.MapTiles = Exts.Lazy(() => new CellLayer<TerrainTile>(size));
map.Options = new MapOptions();
@@ -250,20 +251,20 @@ namespace OpenRA.Utility
void UnpackRATileData(MemoryStream ms)
{
for (var i = 0; i < mapSize; i++)
for (var j = 0; j < mapSize; j++)
map.MapTiles.Value[i, j] = new TileReference<ushort, byte>();
var types = new ushort[mapSize, mapSize];
for (var j = 0; j < mapSize; j++)
{
for (var i = 0; i < mapSize; i++)
{
var tileID = ms.ReadUInt16();
map.MapTiles.Value[i, j].Type = tileID == (ushort)0 ? (ushort)255 : tileID; // RAED weirdness
types[i, j] = tileID == (ushort)0 ? (ushort)255 : tileID; // RAED weirdness
}
}
for (var j = 0; j < mapSize; j++)
for (var i = 0; i < mapSize; i++)
map.MapTiles.Value[i, j].Index = ms.ReadUInt8();
map.MapTiles.Value[new CPos(i, j)] = new TerrainTile(types[i, j], ms.ReadUInt8());
}
void UnpackRAOverlayData(MemoryStream ms)
@@ -277,15 +278,16 @@ namespace OpenRA.Utility
if (o != 255 && overlayResourceMapping.ContainsKey(redAlertOverlayNames[o]))
res = overlayResourceMapping[redAlertOverlayNames[o]];
map.MapResources.Value[i, j] = new TileReference<byte, byte>(res.First, res.Second);
var cell = new CPos(i, j);
map.MapResources.Value[cell] = new ResourceTile(res.First, res.Second);
if (o != 255 && overlayActorMapping.ContainsKey(redAlertOverlayNames[o]))
{
map.Actors.Value.Add("Actor" + actorCount++,
new ActorReference(overlayActorMapping[redAlertOverlayNames[o]])
{
new LocationInit(new CPos(i, j)),
new LocationInit(cell),
new OwnerInit("Neutral")
});
}
@@ -313,16 +315,13 @@ namespace OpenRA.Utility
void UnpackCncTileData(Stream ms)
{
for (var i = 0; i < mapSize; i++)
for (var j = 0; j < mapSize; j++)
map.MapTiles.Value[i, j] = new TileReference<ushort, byte>();
for (var j = 0; j < mapSize; j++)
{
for (var i = 0; i < mapSize; i++)
{
map.MapTiles.Value[i, j].Type = ms.ReadUInt8();
map.MapTiles.Value[i, j].Index = ms.ReadUInt8();
var type = ms.ReadUInt8();
var index = ms.ReadUInt8();
map.MapTiles.Value[new CPos(i, j)] = new TerrainTile(type, index);
}
}
}
@@ -342,7 +341,7 @@ namespace OpenRA.Utility
if (overlayResourceMapping.ContainsKey(kv.Value.ToLower()))
res = overlayResourceMapping[kv.Value.ToLower()];
map.MapResources.Value[cell.X, cell.Y] = new TileReference<byte, byte>(res.First, res.Second);
map.MapResources.Value[cell] = new ResourceTile(res.First, res.Second);
if (overlayActorMapping.ContainsKey(kv.Value.ToLower()))
map.Actors.Value.Add("Actor" + actorCount++,