Add plumbing for custom movement layers.

This commit is contained in:
Paul Chote
2017-01-04 18:10:46 +00:00
parent 695a572dc3
commit 2bd5a392d1
12 changed files with 207 additions and 56 deletions

View File

@@ -19,23 +19,25 @@ namespace OpenRA
public struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable<CPos>
{
public readonly int X, Y;
public readonly byte Layer;
public CPos(int x, int y) { X = x; Y = y; }
public static readonly CPos Zero = new CPos(0, 0);
public CPos(int x, int y) { X = x; Y = y; Layer = 0; }
public CPos(int x, int y, byte layer) { X = x; Y = y; Layer = layer; }
public static readonly CPos Zero = new CPos(0, 0, 0);
public static explicit operator CPos(int2 a) { return new CPos(a.X, a.Y); }
public static CPos operator +(CVec a, CPos b) { return new CPos(a.X + b.X, a.Y + b.Y); }
public static CPos operator +(CPos a, CVec b) { return new CPos(a.X + b.X, a.Y + b.Y); }
public static CPos operator -(CPos a, CVec b) { return new CPos(a.X - b.X, a.Y - b.Y); }
public static CPos operator +(CVec a, CPos b) { return new CPos(a.X + b.X, a.Y + b.Y, b.Layer); }
public static CPos operator +(CPos a, CVec b) { return new CPos(a.X + b.X, a.Y + b.Y, a.Layer); }
public static CPos operator -(CPos a, CVec b) { return new CPos(a.X - b.X, a.Y - b.Y, a.Layer); }
public static CVec operator -(CPos a, CPos b) { return new CVec(a.X - b.X, a.Y - b.Y); }
public static bool operator ==(CPos me, CPos other) { return me.X == other.X && me.Y == other.Y; }
public static bool operator ==(CPos me, CPos other) { return me.X == other.X && me.Y == other.Y && me.Layer == other.Layer; }
public static bool operator !=(CPos me, CPos other) { return !(me == other); }
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode(); }
public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode() ^ Layer.GetHashCode(); }
public bool Equals(CPos other) { return X == other.X && Y == other.Y; }
public bool Equals(CPos other) { return X == other.X && Y == other.Y && Layer == other.Layer; }
public override bool Equals(object obj) { return obj is CPos && Equals((CPos)obj); }
public override string ToString() { return X + "," + Y; }
@@ -117,6 +119,7 @@ namespace OpenRA
{
case "X": return X;
case "Y": return Y;
case "Layer": return Layer;
default: throw new LuaException("CPos does not define a member '{0}'".F(key));
}
}

View File

@@ -100,7 +100,9 @@ namespace OpenRA.Mods.Common.Activities
minRange = armaments.Max(a => a.Weapon.MinRange);
maxRange = armaments.Min(a => a.MaxRange());
if (!Target.IsInRange(self.CenterPosition, maxRange) || Target.IsInRange(self.CenterPosition, minRange))
var mobile = move as Mobile;
if (!Target.IsInRange(self.CenterPosition, maxRange) || Target.IsInRange(self.CenterPosition, minRange)
|| (mobile != null && !mobile.CanInteractWithGroundLayer(self)))
{
// Try to move within range, drop the target otherwise
if (move == null)

View File

@@ -184,10 +184,15 @@ namespace OpenRA.Mods.Common.Activities
else
{
mobile.SetLocation(mobile.FromCell, mobile.FromSubCell, nextCell.Value.First, nextCell.Value.Second);
var from = self.World.Map.CenterOfSubCell(mobile.FromCell, mobile.FromSubCell);
var map = self.World.Map;
var from = (mobile.FromCell.Layer == 0 ? map.CenterOfCell(mobile.FromCell) :
self.World.GetCustomMovementLayers()[mobile.FromCell.Layer].CenterOfCell(mobile.FromCell)) +
map.Grid.OffsetOfSubCell(mobile.FromSubCell);
var to = Util.BetweenCells(self.World, mobile.FromCell, mobile.ToCell) +
(self.World.Map.Grid.OffsetOfSubCell(mobile.FromSubCell) +
self.World.Map.Grid.OffsetOfSubCell(mobile.ToSubCell)) / 2;
(map.Grid.OffsetOfSubCell(mobile.FromSubCell) + map.Grid.OffsetOfSubCell(mobile.ToSubCell)) / 2;
var move = new MoveFirstHalf(
this,
from,
@@ -415,21 +420,22 @@ namespace OpenRA.Mods.Common.Activities
protected override MovePart OnComplete(Actor self, Mobile mobile, Move parent)
{
var fromSubcellOffset = self.World.Map.Grid.OffsetOfSubCell(mobile.FromSubCell);
var toSubcellOffset = self.World.Map.Grid.OffsetOfSubCell(mobile.ToSubCell);
var map = self.World.Map;
var fromSubcellOffset = map.Grid.OffsetOfSubCell(mobile.FromSubCell);
var toSubcellOffset = map.Grid.OffsetOfSubCell(mobile.ToSubCell);
var nextCell = parent.PopPath(self);
if (nextCell != null)
{
if (IsTurn(mobile, nextCell.Value.First))
{
var nextSubcellOffset = self.World.Map.Grid.OffsetOfSubCell(nextCell.Value.Second);
var nextSubcellOffset = map.Grid.OffsetOfSubCell(nextCell.Value.Second);
var ret = new MoveFirstHalf(
Move,
Util.BetweenCells(self.World, mobile.FromCell, mobile.ToCell) + (fromSubcellOffset + toSubcellOffset) / 2,
Util.BetweenCells(self.World, mobile.ToCell, nextCell.Value.First) + (toSubcellOffset + nextSubcellOffset) / 2,
mobile.Facing,
Util.GetNearestFacing(mobile.Facing, self.World.Map.FacingBetween(mobile.ToCell, nextCell.Value.First, mobile.Facing)),
Util.GetNearestFacing(mobile.Facing, map.FacingBetween(mobile.ToCell, nextCell.Value.First, mobile.Facing)),
moveFraction - MoveFractionTotal);
mobile.FinishedMoving(self);
@@ -440,10 +446,13 @@ namespace OpenRA.Mods.Common.Activities
parent.path.Add(nextCell.Value.First);
}
var toPos = mobile.ToCell.Layer == 0 ? map.CenterOfCell(mobile.ToCell) :
self.World.GetCustomMovementLayers()[mobile.ToCell.Layer].CenterOfCell(mobile.ToCell);
var ret2 = new MoveSecondHalf(
Move,
Util.BetweenCells(self.World, mobile.FromCell, mobile.ToCell) + (fromSubcellOffset + toSubcellOffset) / 2,
self.World.Map.CenterOfCell(mobile.ToCell) + toSubcellOffset,
toPos + toSubcellOffset,
mobile.Facing,
mobile.Facing,
moveFraction - MoveFractionTotal);

View File

@@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Activities
{
static readonly List<CPos> NoPath = new List<CPos>();
readonly Mobile mobile;
protected readonly Mobile Mobile;
readonly IPathFinder pathFinder;
readonly DomainIndex domainIndex;
readonly uint movementClass;
@@ -53,10 +53,10 @@ namespace OpenRA.Mods.Common.Activities
{
Target = target;
mobile = self.Trait<Mobile>();
Mobile = self.Trait<Mobile>();
pathFinder = self.World.WorldActor.Trait<IPathFinder>();
domainIndex = self.World.WorldActor.Trait<DomainIndex>();
movementClass = (uint)mobile.Info.GetMovementClass(self.World.Map.Rules.TileSet);
movementClass = (uint)Mobile.Info.GetMovementClass(self.World.Map.Rules.TileSet);
if (target.IsValidFor(self))
targetPosition = self.World.Map.CellContaining(target.CenterPosition);
@@ -91,7 +91,7 @@ namespace OpenRA.Mods.Common.Activities
inner.Cancel(self);
self.SetTargetLine(Target.FromCell(self.World, targetPosition), Color.Green);
return ActivityUtils.RunActivity(self, new AttackMoveActivity(self, mobile.MoveTo(targetPosition, 0)));
return ActivityUtils.RunActivity(self, new AttackMoveActivity(self, Mobile.MoveTo(targetPosition, 0)));
}
// Inner move order has completed.
@@ -103,7 +103,7 @@ namespace OpenRA.Mods.Common.Activities
return NextActivity;
// Target has moved, and MoveAdjacentTo is still valid.
inner = mobile.MoveTo(() => CalculatePathToTarget(self));
inner = Mobile.MoveTo(() => CalculatePathToTarget(self));
repath = false;
}
@@ -142,14 +142,14 @@ namespace OpenRA.Mods.Common.Activities
var loc = self.Location;
foreach (var cell in targetCells)
if (domainIndex.IsPassable(loc, cell, movementClass) && mobile.CanEnterCell(cell))
if (domainIndex.IsPassable(loc, cell, movementClass) && Mobile.CanEnterCell(cell))
searchCells.Add(cell);
if (!searchCells.Any())
return NoPath;
using (var fromSrc = PathSearch.FromPoints(self.World, mobile.Info, self, searchCells, loc, true))
using (var fromDest = PathSearch.FromPoint(self.World, mobile.Info, self, loc, targetPosition, true).Reverse())
using (var fromSrc = PathSearch.FromPoints(self.World, Mobile.Info, self, searchCells, loc, true))
using (var fromDest = PathSearch.FromPoint(self.World, Mobile.Info, self, loc, targetPosition, true).Reverse())
return pathFinder.FindBidiPath(fromSrc, fromDest);
}

View File

@@ -11,6 +11,7 @@
using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Activities
@@ -31,12 +32,13 @@ namespace OpenRA.Mods.Common.Activities
{
// We are now in range. Don't move any further!
// HACK: This works around the pathfinder not returning the shortest path
return AtCorrectRange(self.CenterPosition);
return AtCorrectRange(self.CenterPosition) && Mobile.CanInteractWithGroundLayer(self);
}
protected override bool ShouldRepath(Actor self, CPos oldTargetPosition)
{
return targetPosition != oldTargetPosition && !AtCorrectRange(self.CenterPosition);
return targetPosition != oldTargetPosition && (!AtCorrectRange(self.CenterPosition)
|| !Mobile.CanInteractWithGroundLayer(self));
}
protected override IEnumerable<CPos> CandidateMovementCells(Actor self)

View File

@@ -57,21 +57,28 @@ namespace OpenRA.Mods.Common.Pathfinder
public class PooledCellInfoLayer : IDisposable
{
public CellLayer<CellInfo> Layer { get; private set; }
CellInfoLayerPool layerPool;
List<CellLayer<CellInfo>> layers = new List<CellLayer<CellInfo>>();
public PooledCellInfoLayer(CellInfoLayerPool layerPool)
{
this.layerPool = layerPool;
Layer = layerPool.GetLayer();
}
public CellLayer<CellInfo> GetLayer()
{
var layer = layerPool.GetLayer();
layers.Add(layer);
return layer;
}
public void Dispose()
{
if (Layer == null)
return;
layerPool.ReturnLayer(Layer);
Layer = null;
if (layerPool != null)
foreach (var layer in layers)
layerPool.ReturnLayer(layer);
layers = null;
layerPool = null;
}
}

View File

@@ -11,7 +11,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Pathfinder
{
@@ -84,12 +87,21 @@ namespace OpenRA.Mods.Common.Pathfinder
readonly MobileInfo mobileInfo;
readonly MobileInfo.WorldMovementInfo worldMovementInfo;
readonly CellInfoLayerPool.PooledCellInfoLayer pooledLayer;
CellLayer<CellInfo> cellInfo;
CellLayer<CellInfo> groundInfo;
readonly Dictionary<byte, Pair<ICustomMovementLayer, CellLayer<CellInfo>>> customLayerInfo =
new Dictionary<byte, Pair<ICustomMovementLayer, CellLayer<CellInfo>>>();
public PathGraph(CellInfoLayerPool layerPool, MobileInfo mobileInfo, Actor actor, World world, bool checkForBlocked)
{
pooledLayer = layerPool.Get();
cellInfo = pooledLayer.Layer;
groundInfo = pooledLayer.GetLayer();
var layers = world.GetCustomMovementLayers().Values
.Where(cml => cml.EnabledForActor(actor.Info, mobileInfo));
foreach (var cml in layers)
customLayerInfo[cml.Index] = Pair.New(cml, pooledLayer.GetLayer());
World = world;
this.mobileInfo = mobileInfo;
worldMovementInfo = mobileInfo.GetWorldMovementInfo(world);
@@ -117,7 +129,8 @@ namespace OpenRA.Mods.Common.Pathfinder
public List<GraphConnection> GetConnections(CPos position)
{
var previousPos = cellInfo[position].PreviousPos;
var info = position.Layer == 0 ? groundInfo : customLayerInfo[position.Layer].Second;
var previousPos = info[position].PreviousPos;
var dx = position.X - previousPos.X;
var dy = position.Y - previousPos.Y;
@@ -133,6 +146,24 @@ namespace OpenRA.Mods.Common.Pathfinder
validNeighbors.Add(new GraphConnection(neighbor, movementCost));
}
if (position.Layer == 0)
{
foreach (var cli in customLayerInfo.Values)
{
var layerPosition = new CPos(position.X, position.Y, cli.First.Index);
var entryCost = cli.First.EntryMovementCost(Actor.Info, mobileInfo, layerPosition);
if (entryCost != Constants.InvalidNode)
validNeighbors.Add(new GraphConnection(layerPosition, entryCost));
}
}
else
{
var layerPosition = new CPos(position.X, position.Y, 0);
var exitCost = customLayerInfo[position.Layer].First.ExitMovementCost(Actor.Info, mobileInfo, layerPosition);
if (exitCost != Constants.InvalidNode)
validNeighbors.Add(new GraphConnection(layerPosition, exitCost));
}
return validNeighbors;
}
@@ -179,14 +210,15 @@ namespace OpenRA.Mods.Common.Pathfinder
public CellInfo this[CPos pos]
{
get { return cellInfo[pos]; }
set { cellInfo[pos] = value; }
get { return (pos.Layer == 0 ? groundInfo : customLayerInfo[pos.Layer].Second)[pos]; }
set { (pos.Layer == 0 ? groundInfo : customLayerInfo[pos.Layer].Second)[pos] = value; }
}
public void Dispose()
{
groundInfo = null;
customLayerInfo.Clear();
pooledLayer.Dispose();
cellInfo = null;
}
}
}

View File

@@ -151,15 +151,17 @@ namespace OpenRA.Mods.Common.Traits
public int MovementCostForCell(World world, CPos cell)
{
return MovementCostForCell(world.Map, TilesetTerrainInfo[world.Map.Rules.TileSet], cell);
return MovementCostForCell(world, TilesetTerrainInfo[world.Map.Rules.TileSet], cell);
}
int MovementCostForCell(Map map, TerrainInfo[] terrainInfos, CPos cell)
int MovementCostForCell(World world, TerrainInfo[] terrainInfos, CPos cell)
{
if (!map.Contains(cell))
if (!world.Map.Contains(cell))
return int.MaxValue;
var index = map.GetTerrainIndex(cell);
var index = cell.Layer == 0 ? world.Map.GetTerrainIndex(cell) :
world.GetCustomMovementLayers()[cell.Layer].GetTerrainIndex(cell);
if (index == byte.MaxValue)
return int.MaxValue;
@@ -279,7 +281,7 @@ namespace OpenRA.Mods.Common.Traits
public int MovementCostToEnterCell(WorldMovementInfo worldMovementInfo, Actor self, CPos cell, Actor ignoreActor = null, CellConditions check = CellConditions.All)
{
var cost = MovementCostForCell(worldMovementInfo.World.Map, worldMovementInfo.TerrainInfos, cell);
var cost = MovementCostForCell(worldMovementInfo.World, worldMovementInfo.TerrainInfos, cell);
if (cost == int.MaxValue || !CanMoveFreelyInto(worldMovementInfo.World, self, cell, ignoreActor, check))
return int.MaxValue;
return cost;
@@ -413,7 +415,12 @@ namespace OpenRA.Mods.Common.Traits
{
subCell = GetValidSubCell(subCell);
SetLocation(cell, subCell, cell, subCell);
SetVisualPosition(self, self.World.Map.CenterOfSubCell(cell, subCell));
var position = cell.Layer == 0 ? self.World.Map.CenterOfCell(cell) :
self.World.GetCustomMovementLayers()[cell.Layer].CenterOfCell(cell);
var subcellOffset = self.World.Map.Grid.OffsetOfSubCell(subCell);
SetVisualPosition(self, position + subcellOffset);
FinishedMoving(self);
}
@@ -626,7 +633,9 @@ namespace OpenRA.Mods.Common.Traits
public int MovementSpeedForCell(Actor self, CPos cell)
{
var index = self.World.Map.GetTerrainIndex(cell);
var index = cell.Layer == 0 ? self.World.Map.GetTerrainIndex(cell) :
self.World.GetCustomMovementLayers()[cell.Layer].GetTerrainIndex(cell);
if (index == byte.MaxValue)
return 0;
@@ -716,6 +725,21 @@ namespace OpenRA.Mods.Common.Traits
}
}
public bool CanInteractWithGroundLayer(Actor self)
{
// TODO: Think about extending this to support arbitrary layer-layer checks
// in a way that is compatible with the other IMove types.
// This would then allow us to e.g. have units attack other units inside tunnels.
if (ToCell.Layer == 0)
return true;
ICustomMovementLayer layer;
if (self.World.GetCustomMovementLayers().TryGetValue(ToCell.Layer, out layer))
return layer.InteractsWithDefaultLayer;
return true;
}
void IActorPreviewInitModifier.ModifyActorPreviewInit(Actor self, TypeDictionary inits)
{
if (!inits.Contains<DynamicFacingInit>() && !inits.Contains<FacingInit>())

View File

@@ -26,7 +26,7 @@ namespace OpenRA.Mods.Common.Traits
public object Create(ActorInitializer init) { return new ActorMap(init.World, this); }
}
public class ActorMap : IActorMap, ITick
public class ActorMap : IActorMap, ITick, INotifyCreated
{
class InfluenceNode
{
@@ -165,6 +165,8 @@ namespace OpenRA.Mods.Common.Traits
int nextTriggerId;
readonly CellLayer<InfluenceNode> influence;
readonly Dictionary<int, CellLayer<InfluenceNode>> customInfluence = new Dictionary<int, CellLayer<InfluenceNode>>();
public readonly Dictionary<int, ICustomMovementLayer> CustomMovementLayers = new Dictionary<int, ICustomMovementLayer>();
readonly Bin[] bins;
readonly int rows, cols;
@@ -192,6 +194,15 @@ namespace OpenRA.Mods.Common.Traits
actorShouldBeRemoved = removeActorPosition.Contains;
}
void INotifyCreated.Created(Actor self)
{
foreach (var cml in self.TraitsImplementing<ICustomMovementLayer>())
{
CustomMovementLayers[cml.Index] = cml;
customInfluence.Add(cml.Index, new CellLayer<InfluenceNode>(self.World.Map));
}
}
sealed class ActorsAtEnumerator : IEnumerator<Actor>
{
InfluenceNode node;
@@ -228,7 +239,9 @@ namespace OpenRA.Mods.Common.Traits
var uv = a.ToMPos(map);
if (!influence.Contains(uv))
return Enumerable.Empty<Actor>();
return new ActorsAtEnumerable(influence[uv]);
var layer = a.Layer == 0 ? influence : customInfluence[a.Layer];
return new ActorsAtEnumerable(layer[uv]);
}
public IEnumerable<Actor> GetActorsAt(CPos a, SubCell sub)
@@ -237,7 +250,8 @@ namespace OpenRA.Mods.Common.Traits
if (!influence.Contains(uv))
yield break;
for (var i = influence[uv]; i != null; i = i.Next)
var layer = a.Layer == 0 ? influence : customInfluence[a.Layer];
for (var i = layer[uv]; i != null; i = i.Next)
if (!i.Actor.Disposed && (i.SubCell == sub || i.SubCell == SubCell.FullCell))
yield return i.Actor;
}
@@ -283,7 +297,8 @@ namespace OpenRA.Mods.Common.Traits
if (!influence.Contains(uv))
return false;
return influence[uv] != null;
var layer = a.Layer == 0 ? influence : customInfluence[a.Layer];
return layer[uv] != null;
}
// NOTE: can not check aircraft
@@ -294,7 +309,8 @@ namespace OpenRA.Mods.Common.Traits
return false;
var always = sub == SubCell.FullCell || sub == SubCell.Any;
for (var i = influence[uv]; i != null; i = i.Next)
var layer = a.Layer == 0 ? influence : customInfluence[a.Layer];
for (var i = layer[uv]; i != null; i = i.Next)
{
if (always || i.SubCell == sub || i.SubCell == SubCell.FullCell)
{
@@ -318,7 +334,8 @@ namespace OpenRA.Mods.Common.Traits
return false;
var always = sub == SubCell.FullCell || sub == SubCell.Any;
for (var i = influence[uv]; i != null; i = i.Next)
var layer = a.Layer == 0 ? influence : customInfluence[a.Layer];
for (var i = layer[uv]; i != null; i = i.Next)
if ((always || i.SubCell == sub || i.SubCell == SubCell.FullCell) && !i.Actor.Disposed && withCondition(i.Actor))
return true;
@@ -333,7 +350,8 @@ namespace OpenRA.Mods.Common.Traits
if (!influence.Contains(uv))
continue;
influence[uv] = new InfluenceNode { Next = influence[uv], SubCell = c.Second, Actor = self };
var layer = c.First.Layer == 0 ? influence : customInfluence[c.First.Layer];
layer[uv] = new InfluenceNode { Next = layer[uv], SubCell = c.Second, Actor = self };
List<CellTrigger> triggers;
if (cellTriggerInfluence.TryGetValue(c.First, out triggers))
@@ -350,9 +368,10 @@ namespace OpenRA.Mods.Common.Traits
if (!influence.Contains(uv))
continue;
var temp = influence[uv];
var layer = c.First.Layer == 0 ? influence : customInfluence[c.First.Layer];
var temp = layer[uv];
RemoveInfluenceInner(ref temp, self);
influence[uv] = temp;
layer[uv] = temp;
List<CellTrigger> triggers;
if (cellTriggerInfluence.TryGetValue(c.First, out triggers))
@@ -567,4 +586,12 @@ namespace OpenRA.Mods.Common.Traits
}
}
}
public static class ActorMapWorldExts
{
public static Dictionary<int, ICustomMovementLayer> GetCustomMovementLayers(this World world)
{
return ((ActorMap)world.ActorMap).CustomMovementLayers;
}
}
}

View File

@@ -39,6 +39,11 @@ namespace OpenRA.Mods.Common.Traits
public bool IsPassable(CPos p1, CPos p2, uint movementClass)
{
// HACK: Work around units in other movement layers from being blocked
// when the point in the main layer is not pathable
if (p1.Layer != 0 || p2.Layer != 0)
return true;
return domainIndexes[movementClass].IsPassable(p1, p2);
}
@@ -49,6 +54,12 @@ namespace OpenRA.Mods.Common.Traits
foreach (var index in domainIndexes)
index.Value.UpdateCells(world, dirty);
}
public void AddFixedConnection(IEnumerable<CPos> cells)
{
foreach (var index in domainIndexes)
index.Value.AddFixedConnection(cells);
}
}
class MovementClassDomainIndex
@@ -119,6 +130,19 @@ namespace OpenRA.Mods.Common.Traits
CreateConnection(c1, c2);
}
public void AddFixedConnection(IEnumerable<CPos> cells)
{
// HACK: this is a temporary workaround to add a permanent connection between the domains of the listed cells.
// This is sufficient for fixed point-to-point tunnels, but not for dynamically updating custom layers
// such as destroyable elevated bridges.
// To support those the domain index will need to learn about custom movement layers, but that then requires
// a complete refactor of the domain code to deal with MobileInfo or better a shared pathfinder class type.
var cellDomains = cells.Select(c => domains[c]).ToHashSet();
foreach (var c1 in cellDomains)
foreach (var c2 in cellDomains.Where(c => c != c1))
CreateConnection(c1, c2);
}
bool HasConnection(int d1, int d2)
{
// Search our connections graph for a possible route

View File

@@ -233,4 +233,18 @@ namespace OpenRA.Mods.Common.Traits
[RequireExplicitImplementation]
public interface IGainsExperienceModifier { int GetGainsExperienceModifier(); }
[RequireExplicitImplementation]
public interface ICustomMovementLayer
{
byte Index { get; }
bool InteractsWithDefaultLayer { get; }
bool EnabledForActor(ActorInfo a, MobileInfo mi);
int EntryMovementCost(ActorInfo a, MobileInfo mi, CPos cell);
int ExitMovementCost(ActorInfo a, MobileInfo mi, CPos cell);
byte GetTerrainIndex(CPos cell);
WPos CenterOfCell(CPos cell);
}
}

View File

@@ -12,6 +12,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Support;
using OpenRA.Traits;
@@ -85,7 +86,13 @@ namespace OpenRA.Mods.Common
public static WPos BetweenCells(World w, CPos from, CPos to)
{
return WPos.Lerp(w.Map.CenterOfCell(from), w.Map.CenterOfCell(to), 1, 2);
var fromPos = from.Layer == 0 ? w.Map.CenterOfCell(from) :
w.GetCustomMovementLayers()[from.Layer].CenterOfCell(from);
var toPos = to.Layer == 0 ? w.Map.CenterOfCell(to) :
w.GetCustomMovementLayers()[to.Layer].CenterOfCell(to);
return WPos.Lerp(fromPos, toPos, 1, 2);
}
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> ts, MersenneTwister random)