diff --git a/OpenRA.Game/CPos.cs b/OpenRA.Game/CPos.cs index cfd2cbe47d..7dcef3c928 100644 --- a/OpenRA.Game/CPos.cs +++ b/OpenRA.Game/CPos.cs @@ -19,23 +19,25 @@ namespace OpenRA public struct CPos : IScriptBindable, ILuaAdditionBinding, ILuaSubtractionBinding, ILuaEqualityBinding, ILuaTableBinding, IEquatable { 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)); } } diff --git a/OpenRA.Mods.Common/Activities/Attack.cs b/OpenRA.Mods.Common/Activities/Attack.cs index 09c4109161..8b450dda13 100644 --- a/OpenRA.Mods.Common/Activities/Attack.cs +++ b/OpenRA.Mods.Common/Activities/Attack.cs @@ -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) diff --git a/OpenRA.Mods.Common/Activities/Move/Move.cs b/OpenRA.Mods.Common/Activities/Move/Move.cs index d17bcf9dac..a1360d76d8 100644 --- a/OpenRA.Mods.Common/Activities/Move/Move.cs +++ b/OpenRA.Mods.Common/Activities/Move/Move.cs @@ -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); diff --git a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs index 658c43ae13..c4d05401a1 100644 --- a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs +++ b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs @@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Activities { static readonly List NoPath = new List(); - 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 = self.Trait(); pathFinder = self.World.WorldActor.Trait(); domainIndex = self.World.WorldActor.Trait(); - 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); } diff --git a/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs b/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs index 2642425392..0f68d0aa1b 100644 --- a/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs +++ b/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs @@ -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 CandidateMovementCells(Actor self) diff --git a/OpenRA.Mods.Common/Pathfinder/CellInfoLayerPool.cs b/OpenRA.Mods.Common/Pathfinder/CellInfoLayerPool.cs index 949e7a6541..34174b6845 100644 --- a/OpenRA.Mods.Common/Pathfinder/CellInfoLayerPool.cs +++ b/OpenRA.Mods.Common/Pathfinder/CellInfoLayerPool.cs @@ -57,21 +57,28 @@ namespace OpenRA.Mods.Common.Pathfinder public class PooledCellInfoLayer : IDisposable { - public CellLayer Layer { get; private set; } CellInfoLayerPool layerPool; + List> layers = new List>(); public PooledCellInfoLayer(CellInfoLayerPool layerPool) { this.layerPool = layerPool; - Layer = layerPool.GetLayer(); + } + + public CellLayer 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; } } diff --git a/OpenRA.Mods.Common/Pathfinder/PathGraph.cs b/OpenRA.Mods.Common/Pathfinder/PathGraph.cs index 2950aeb075..05bd4eda0b 100644 --- a/OpenRA.Mods.Common/Pathfinder/PathGraph.cs +++ b/OpenRA.Mods.Common/Pathfinder/PathGraph.cs @@ -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; + CellLayer groundInfo; + + readonly Dictionary>> customLayerInfo = + new Dictionary>>(); 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 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; } } } diff --git a/OpenRA.Mods.Common/Traits/Mobile.cs b/OpenRA.Mods.Common/Traits/Mobile.cs index 04c6b9f081..4f2393b6e4 100644 --- a/OpenRA.Mods.Common/Traits/Mobile.cs +++ b/OpenRA.Mods.Common/Traits/Mobile.cs @@ -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() && !inits.Contains()) diff --git a/OpenRA.Mods.Common/Traits/World/ActorMap.cs b/OpenRA.Mods.Common/Traits/World/ActorMap.cs index 5a942a480d..d3ffcf87e7 100644 --- a/OpenRA.Mods.Common/Traits/World/ActorMap.cs +++ b/OpenRA.Mods.Common/Traits/World/ActorMap.cs @@ -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 influence; + readonly Dictionary> customInfluence = new Dictionary>(); + public readonly Dictionary CustomMovementLayers = new Dictionary(); 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()) + { + CustomMovementLayers[cml.Index] = cml; + customInfluence.Add(cml.Index, new CellLayer(self.World.Map)); + } + } + sealed class ActorsAtEnumerator : IEnumerator { InfluenceNode node; @@ -228,7 +239,9 @@ namespace OpenRA.Mods.Common.Traits var uv = a.ToMPos(map); if (!influence.Contains(uv)) return Enumerable.Empty(); - return new ActorsAtEnumerable(influence[uv]); + + var layer = a.Layer == 0 ? influence : customInfluence[a.Layer]; + return new ActorsAtEnumerable(layer[uv]); } public IEnumerable 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 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 triggers; if (cellTriggerInfluence.TryGetValue(c.First, out triggers)) @@ -567,4 +586,12 @@ namespace OpenRA.Mods.Common.Traits } } } + + public static class ActorMapWorldExts + { + public static Dictionary GetCustomMovementLayers(this World world) + { + return ((ActorMap)world.ActorMap).CustomMovementLayers; + } + } } diff --git a/OpenRA.Mods.Common/Traits/World/DomainIndex.cs b/OpenRA.Mods.Common/Traits/World/DomainIndex.cs index cf23ef6a28..73201f3b42 100644 --- a/OpenRA.Mods.Common/Traits/World/DomainIndex.cs +++ b/OpenRA.Mods.Common/Traits/World/DomainIndex.cs @@ -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 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 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 diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index 67fea93cc4..168598488b 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -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); + } } diff --git a/OpenRA.Mods.Common/Util.cs b/OpenRA.Mods.Common/Util.cs index ddbcb8a478..cbd10b5b0a 100644 --- a/OpenRA.Mods.Common/Util.cs +++ b/OpenRA.Mods.Common/Util.cs @@ -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 Shuffle(this IEnumerable ts, MersenneTwister random)