Added cache for cell cost and blocking
This commit is contained in:
@@ -58,6 +58,13 @@ namespace OpenRA.Mods.Cnc.Traits
|
|||||||
|
|
||||||
return info.CrushClasses.Overlaps(crushClasses);
|
return info.CrushClasses.Overlaps(crushClasses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ICrushable.TryCalculatePlayerBlocking(Actor self, BitSet<CrushClass> crushClasses, out LongBitSet<PlayerBitMask> blocking)
|
||||||
|
{
|
||||||
|
// Fall back to the slow path
|
||||||
|
blocking = default(LongBitSet<PlayerBitMask>);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Desc("Tag trait for stuff that should not trigger mines.")]
|
[Desc("Tag trait for stuff that should not trigger mines.")]
|
||||||
|
|||||||
@@ -172,7 +172,8 @@ namespace OpenRA.Mods.Common.Pathfinder
|
|||||||
|
|
||||||
int GetCostToNode(CPos destNode, CVec direction)
|
int GetCostToNode(CPos destNode, CVec direction)
|
||||||
{
|
{
|
||||||
var movementCost = locomotorInfo.MovementCostToEnterCell(worldMovementInfo, Actor, destNode, IgnoreActor, checkConditions);
|
var movementCost = locomotor.MovementCostToEnterCell(Actor, destNode, IgnoreActor, checkConditions);
|
||||||
|
|
||||||
if (movementCost != int.MaxValue && !(CustomBlock != null && CustomBlock(destNode)))
|
if (movementCost != int.MaxValue && !(CustomBlock != null && CustomBlock(destNode)))
|
||||||
return CalculateCellCost(destNode, direction, movementCost);
|
return CalculateCellCost(destNode, direction, movementCost);
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
{
|
{
|
||||||
readonly Actor self;
|
readonly Actor self;
|
||||||
Transforms[] transforms;
|
Transforms[] transforms;
|
||||||
|
Locomotor locomotor;
|
||||||
|
|
||||||
public TransformsIntoMobile(ActorInitializer init, TransformsIntoMobileInfo info)
|
public TransformsIntoMobile(ActorInitializer init, TransformsIntoMobileInfo info)
|
||||||
: base(info)
|
: base(info)
|
||||||
@@ -67,6 +68,8 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
protected override void Created(Actor self)
|
protected override void Created(Actor self)
|
||||||
{
|
{
|
||||||
transforms = self.TraitsImplementing<Transforms>().ToArray();
|
transforms = self.TraitsImplementing<Transforms>().ToArray();
|
||||||
|
locomotor = self.World.WorldActor.TraitsImplementing<Locomotor>()
|
||||||
|
.Single(l => l.Info.Name == Info.Locomotor);
|
||||||
base.Created(self);
|
base.Created(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,21 +185,20 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
cursor = self.World.Map.Contains(location) ?
|
cursor = self.World.Map.Contains(location) ?
|
||||||
(self.World.Map.GetTerrainInfo(location).CustomCursor ?? mobile.Info.Cursor) : mobile.Info.BlockedCursor;
|
(self.World.Map.GetTerrainInfo(location).CustomCursor ?? mobile.Info.Cursor) : mobile.Info.BlockedCursor;
|
||||||
|
|
||||||
var locomotor = mobile.Info.LocomotorInfo;
|
|
||||||
if (!(self.CurrentActivity is Transform || mobile.transforms.Any(t => !t.IsTraitDisabled && !t.IsTraitPaused))
|
if (!(self.CurrentActivity is Transform || mobile.transforms.Any(t => !t.IsTraitDisabled && !t.IsTraitPaused))
|
||||||
|| (!explored && !locomotor.MoveIntoShroud)
|
|| (!explored && !mobile.locomotor.Info.MoveIntoShroud)
|
||||||
|| (explored && !CanEnterCell(self.World, self, location)))
|
|| (explored && !CanEnterCell(self, location)))
|
||||||
cursor = mobile.Info.BlockedCursor;
|
cursor = mobile.Info.BlockedCursor;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CanEnterCell(World world, Actor self, CPos cell)
|
bool CanEnterCell(Actor self, CPos cell)
|
||||||
{
|
{
|
||||||
if (mobile.Info.LocomotorInfo.MovementCostForCell(world, cell) == int.MaxValue)
|
if (mobile.locomotor.MovementCostForCell(cell) == int.MaxValue)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return mobile.Info.LocomotorInfo.CanMoveFreelyInto(world, self, cell, null, CellConditions.BlockedByMovers);
|
return mobile.locomotor.CanMoveFreelyInto(self, cell, null, CellConditions.BlockedByMovers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -225,6 +225,16 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
return self.IsAtGroundLevel() && crushClasses.Contains(info.CrushClass);
|
return self.IsAtGroundLevel() && crushClasses.Contains(info.CrushClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ICrushable.TryCalculatePlayerBlocking(Actor self, BitSet<CrushClass> crushClasses, out LongBitSet<PlayerBitMask> blocking)
|
||||||
|
{
|
||||||
|
if (self.IsAtGroundLevel() && crushClasses.Contains(info.CrushClass))
|
||||||
|
blocking = default(LongBitSet<PlayerBitMask>);
|
||||||
|
else
|
||||||
|
blocking = self.World.AllPlayerMask;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void INotifyAddedToWorld.AddedToWorld(Actor self)
|
void INotifyAddedToWorld.AddedToWorld(Actor self)
|
||||||
{
|
{
|
||||||
self.World.AddToMaps(self, this);
|
self.World.AddToMaps(self, this);
|
||||||
|
|||||||
@@ -79,5 +79,15 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
return Info.CrushClasses.Overlaps(crushClasses);
|
return Info.CrushClasses.Overlaps(crushClasses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ICrushable.TryCalculatePlayerBlocking(Actor self, BitSet<CrushClass> crushClasses, out LongBitSet<PlayerBitMask> blocking)
|
||||||
|
{
|
||||||
|
if (IsTraitDisabled || !self.IsAtGroundLevel() || !Info.CrushClasses.Overlaps(crushClasses))
|
||||||
|
blocking = self.World.AllPlayerMask;
|
||||||
|
else
|
||||||
|
blocking = Info.CrushedByFriendlies ? default(LongBitSet<PlayerBitMask>) : self.Owner.AllyMask;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,13 +76,21 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
public int GetInitialFacing() { return InitialFacing; }
|
public int GetInitialFacing() { return InitialFacing; }
|
||||||
|
|
||||||
|
// initialized and used by CanEnterCell
|
||||||
|
Locomotor locomotor;
|
||||||
|
|
||||||
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||||
{
|
{
|
||||||
if (LocomotorInfo.MovementCostForCell(world, cell) == int.MaxValue)
|
// PERF: Avoid repeated trait queries on the hot path
|
||||||
|
if (locomotor == null)
|
||||||
|
locomotor = world.WorldActor.TraitsImplementing<Locomotor>()
|
||||||
|
.SingleOrDefault(l => l.Info.Name == Locomotor);
|
||||||
|
|
||||||
|
if (locomotor.MovementCostForCell(cell) == int.MaxValue)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var check = checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers;
|
var check = checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers;
|
||||||
return LocomotorInfo.CanMoveFreelyInto(world, self, cell, ignoreActor, check);
|
return locomotor.CanMoveFreelyInto(self, cell, ignoreActor, check);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyDictionary<CPos, SubCell> OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any)
|
public IReadOnlyDictionary<CPos, SubCell> OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any)
|
||||||
@@ -425,12 +433,12 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true)
|
public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||||
{
|
{
|
||||||
var cellConditions = checkTransientActors ? CellConditions.All : CellConditions.None;
|
var cellConditions = checkTransientActors ? CellConditions.All : CellConditions.None;
|
||||||
return Info.LocomotorInfo.GetAvailableSubCell(self.World, self, a, preferredSubCell, ignoreActor, cellConditions);
|
return Locomotor.GetAvailableSubCell(self, a, preferredSubCell, ignoreActor, cellConditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanExistInCell(CPos cell)
|
public bool CanExistInCell(CPos cell)
|
||||||
{
|
{
|
||||||
return Info.LocomotorInfo.MovementCostForCell(self.World, cell) != int.MaxValue;
|
return Locomotor.MovementCostForCell(cell) != int.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||||
@@ -664,11 +672,6 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanMoveFreelyInto(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
|
|
||||||
{
|
|
||||||
return Info.LocomotorInfo.CanMoveFreelyInto(self.World, self, cell, ignoreActor, checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EnteringCell(Actor self)
|
public void EnteringCell(Actor self)
|
||||||
{
|
{
|
||||||
// Only make actor crush if it is on the ground
|
// Only make actor crush if it is on the ground
|
||||||
@@ -875,7 +878,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
if (mobile.IsTraitPaused
|
if (mobile.IsTraitPaused
|
||||||
|| (!explored && !locomotorInfo.MoveIntoShroud)
|
|| (!explored && !locomotorInfo.MoveIntoShroud)
|
||||||
|| (explored && locomotorInfo.MovementCostForCell(self.World, location) == int.MaxValue))
|
|| (explored && mobile.Locomotor.MovementCostForCell(location) == int.MaxValue))
|
||||||
cursor = mobile.Info.BlockedCursor;
|
cursor = mobile.Info.BlockedCursor;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using OpenRA.Graphics;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
|
|
||||||
@@ -26,13 +27,34 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
All = TransientActors | BlockedByMovers
|
All = TransientActors | BlockedByMovers
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CellConditionsExts
|
[Flags]
|
||||||
|
public enum CellBlocking : byte
|
||||||
|
{
|
||||||
|
Empty = 0,
|
||||||
|
HasActor = 1,
|
||||||
|
FreeSubCell = 2,
|
||||||
|
Crushable = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LocomoterExts
|
||||||
{
|
{
|
||||||
public static bool HasCellCondition(this CellConditions c, CellConditions cellCondition)
|
public static bool HasCellCondition(this CellConditions c, CellConditions cellCondition)
|
||||||
{
|
{
|
||||||
// PERF: Enum.HasFlag is slower and requires allocations.
|
// PERF: Enum.HasFlag is slower and requires allocations.
|
||||||
return (c & cellCondition) == cellCondition;
|
return (c & cellCondition) == cellCondition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool HasCellBlocking(this CellBlocking c, CellBlocking cellBlocking)
|
||||||
|
{
|
||||||
|
// PERF: Enum.HasFlag is slower and requires allocations.
|
||||||
|
return (c & cellBlocking) == cellBlocking;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool HasMovementType(this MovementType m, MovementType movementType)
|
||||||
|
{
|
||||||
|
// PERF: Enum.HasFlag is slower and requires allocations.
|
||||||
|
return (m & movementType) == movementType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class CustomMovementLayerType
|
public static class CustomMovementLayerType
|
||||||
@@ -78,9 +100,9 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
if (speed > 0)
|
if (speed > 0)
|
||||||
{
|
{
|
||||||
var nodesDict = t.Value.ToDictionary();
|
var nodesDict = t.Value.ToDictionary();
|
||||||
var cost = nodesDict.ContainsKey("PathingCost")
|
var cost = (nodesDict.ContainsKey("PathingCost")
|
||||||
? FieldLoader.GetValue<short>("cost", nodesDict["PathingCost"].Value)
|
? FieldLoader.GetValue<short>("cost", nodesDict["PathingCost"].Value)
|
||||||
: 10000 / speed;
|
: 10000 / speed);
|
||||||
ret.Add(t.Key, new TerrainInfo(speed, (short)cost));
|
ret.Add(t.Key, new TerrainInfo(speed, (short)cost));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,25 +168,6 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
TilesetMovementClass = new Cache<TileSet, int>(CalculateTilesetMovementClass);
|
TilesetMovementClass = new Cache<TileSet, int>(CalculateTilesetMovementClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int MovementCostForCell(World world, CPos cell)
|
|
||||||
{
|
|
||||||
return MovementCostForCell(world, TilesetTerrainInfo[world.Map.Rules.TileSet], cell);
|
|
||||||
}
|
|
||||||
|
|
||||||
int MovementCostForCell(World world, TerrainInfo[] terrainInfos, CPos cell)
|
|
||||||
{
|
|
||||||
if (!world.Map.Contains(cell))
|
|
||||||
return int.MaxValue;
|
|
||||||
|
|
||||||
var index = cell.Layer == 0 ? world.Map.GetTerrainIndex(cell) :
|
|
||||||
world.GetCustomMovementLayers()[cell.Layer].GetTerrainIndex(cell);
|
|
||||||
|
|
||||||
if (index == byte.MaxValue)
|
|
||||||
return int.MaxValue;
|
|
||||||
|
|
||||||
return terrainInfos[index].Cost;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int CalculateTilesetMovementClass(TileSet tileset)
|
public int CalculateTilesetMovementClass(TileSet tileset)
|
||||||
{
|
{
|
||||||
// collect our ability to cross *all* terraintypes, in a bitvector
|
// collect our ability to cross *all* terraintypes, in a bitvector
|
||||||
@@ -190,80 +193,143 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
return new WorldMovementInfo(world, this);
|
return new WorldMovementInfo(world, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int MovementCostToEnterCell(WorldMovementInfo worldMovementInfo, Actor self, CPos cell, Actor ignoreActor = null, CellConditions check = CellConditions.All)
|
public virtual bool DisableDomainPassabilityCheck { get { return false; } }
|
||||||
{
|
|
||||||
var cost = MovementCostForCell(worldMovementInfo.World, worldMovementInfo.TerrainInfos, cell);
|
public virtual object Create(ActorInitializer init) { return new Locomotor(init.Self, this); }
|
||||||
if (cost == int.MaxValue || !CanMoveFreelyInto(worldMovementInfo.World, self, cell, ignoreActor, check))
|
|
||||||
return int.MaxValue;
|
|
||||||
return cost;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SubCell GetAvailableSubCell(
|
public class Locomotor : IWorldLoaded
|
||||||
World world, Actor self, CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, CellConditions check = CellConditions.All)
|
|
||||||
{
|
{
|
||||||
if (MovementCostForCell(world, cell) == int.MaxValue)
|
struct CellCache
|
||||||
|
{
|
||||||
|
public readonly CellBlocking CellBlocking;
|
||||||
|
public readonly short Cost;
|
||||||
|
public readonly LongBitSet<PlayerBitMask> Blocking;
|
||||||
|
|
||||||
|
public CellCache(short cost, LongBitSet<PlayerBitMask> blocking, CellBlocking cellBlocking)
|
||||||
|
{
|
||||||
|
Cost = cost;
|
||||||
|
Blocking = blocking;
|
||||||
|
CellBlocking = cellBlocking;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellCache WithCost(short cost)
|
||||||
|
{
|
||||||
|
return new CellCache(cost, Blocking, CellBlocking);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellCache WithBlocking(LongBitSet<PlayerBitMask> blocking, CellBlocking cellBlocking)
|
||||||
|
{
|
||||||
|
return new CellCache(Cost, blocking, cellBlocking);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly LocomotorInfo Info;
|
||||||
|
CellLayer<CellCache> pathabilityCache;
|
||||||
|
LongBitSet<PlayerBitMask> allPlayerMask = default(LongBitSet<PlayerBitMask>);
|
||||||
|
|
||||||
|
LocomotorInfo.TerrainInfo[] terrainInfos;
|
||||||
|
World world;
|
||||||
|
readonly HashSet<CPos> updatedTerrainCells = new HashSet<CPos>();
|
||||||
|
|
||||||
|
IActorMap actorMap;
|
||||||
|
bool sharesCell;
|
||||||
|
|
||||||
|
public Locomotor(Actor self, LocomotorInfo info)
|
||||||
|
{
|
||||||
|
Info = info;
|
||||||
|
sharesCell = info.SharesCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MovementCostForCell(CPos cell)
|
||||||
|
{
|
||||||
|
if (!world.Map.Contains(cell))
|
||||||
|
return int.MaxValue;
|
||||||
|
|
||||||
|
return pathabilityCache[cell].Cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MovementCostToEnterCell(Actor actor, CPos destNode, Actor ignoreActor, CellConditions check)
|
||||||
|
{
|
||||||
|
if (!world.Map.Contains(destNode))
|
||||||
|
return int.MaxValue;
|
||||||
|
|
||||||
|
var cellCache = pathabilityCache[destNode];
|
||||||
|
|
||||||
|
if (cellCache.Cost == short.MaxValue ||
|
||||||
|
!CanMoveFreelyInto(actor, ignoreActor, destNode, check, cellCache))
|
||||||
|
return int.MaxValue;
|
||||||
|
|
||||||
|
return cellCache.Cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determines whether the actor is blocked by other Actors
|
||||||
|
public bool CanMoveFreelyInto(Actor actor, CPos cell, Actor ignoreActor, CellConditions check)
|
||||||
|
{
|
||||||
|
return CanMoveFreelyInto(actor, ignoreActor, cell, check, pathabilityCache[cell]);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CanMoveFreelyInto(Actor actor, Actor ignoreActor, CPos cell, CellConditions check, CellCache cellCache)
|
||||||
|
{
|
||||||
|
var cellBlocking = cellCache.CellBlocking;
|
||||||
|
var blocking = cellCache.Blocking;
|
||||||
|
|
||||||
|
if (!check.HasCellCondition(CellConditions.TransientActors))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// If actor is null we're just checking what would happen theoretically.
|
||||||
|
// In such a scenario - we'll just assume any other actor in the cell will block us by default.
|
||||||
|
// If we have a real actor, we can then perform the extra checks that allow us to avoid being blocked.
|
||||||
|
if (actor == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// No actor in cell
|
||||||
|
if (cellBlocking == CellBlocking.Empty)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// We are blocked
|
||||||
|
if (ignoreActor == null && blocking.Overlaps(actor.Owner.PlayerMask))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (cellBlocking.HasCellBlocking(CellBlocking.Crushable))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (sharesCell && cellBlocking.HasCellBlocking(CellBlocking.FreeSubCell))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
foreach (var otherActor in world.ActorMap.GetActorsAt(cell))
|
||||||
|
if (IsBlockedBy(actor, otherActor, ignoreActor, check))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SubCell GetAvailableSubCell(Actor self, CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, CellConditions check = CellConditions.All)
|
||||||
|
{
|
||||||
|
if (MovementCostForCell(cell) == int.MaxValue)
|
||||||
return SubCell.Invalid;
|
return SubCell.Invalid;
|
||||||
|
|
||||||
if (check.HasCellCondition(CellConditions.TransientActors))
|
if (check.HasCellCondition(CellConditions.TransientActors))
|
||||||
{
|
{
|
||||||
Func<Actor, bool> checkTransient = otherActor => IsBlockedBy(self, otherActor, ignoreActor, check);
|
Func<Actor, bool> checkTransient = otherActor => IsBlockedBy(self, otherActor, ignoreActor, check);
|
||||||
|
|
||||||
if (!SharesCell)
|
if (!sharesCell)
|
||||||
return world.ActorMap.AnyActorsAt(cell, SubCell.FullCell, checkTransient) ? SubCell.Invalid : SubCell.FullCell;
|
return world.ActorMap.AnyActorsAt(cell, SubCell.FullCell, checkTransient) ? SubCell.Invalid : SubCell.FullCell;
|
||||||
|
|
||||||
return world.ActorMap.FreeSubCell(cell, preferredSubCell, checkTransient);
|
return world.ActorMap.FreeSubCell(cell, preferredSubCell, checkTransient);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!SharesCell)
|
if (!sharesCell)
|
||||||
return world.ActorMap.AnyActorsAt(cell, SubCell.FullCell) ? SubCell.Invalid : SubCell.FullCell;
|
return world.ActorMap.AnyActorsAt(cell, SubCell.FullCell) ? SubCell.Invalid : SubCell.FullCell;
|
||||||
|
|
||||||
return world.ActorMap.FreeSubCell(cell, preferredSubCell);
|
return world.ActorMap.FreeSubCell(cell, preferredSubCell);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool IsMovingInMyDirection(Actor self, Actor other)
|
|
||||||
{
|
|
||||||
var otherMobile = other.TraitOrDefault<Mobile>();
|
|
||||||
if (otherMobile == null || !otherMobile.CurrentMovementTypes.HasFlag(MovementType.Horizontal))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var selfMobile = self.TraitOrDefault<Mobile>();
|
|
||||||
if (selfMobile == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Moving in the same direction if the facing delta is between +/- 90 degrees
|
|
||||||
var delta = Util.NormalizeFacing(otherMobile.Facing - selfMobile.Facing);
|
|
||||||
return delta < 64 || delta > 192;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determines whether the actor is blocked by other Actors
|
|
||||||
public bool CanMoveFreelyInto(World world, Actor self, CPos cell, Actor ignoreActor, CellConditions check)
|
|
||||||
{
|
|
||||||
if (!check.HasCellCondition(CellConditions.TransientActors))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (SharesCell && world.ActorMap.HasFreeSubCell(cell))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// PERF: Avoid LINQ.
|
|
||||||
foreach (var otherActor in world.ActorMap.GetActorsAt(cell))
|
|
||||||
if (IsBlockedBy(self, otherActor, ignoreActor, check))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor, CellConditions check)
|
bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor, CellConditions check)
|
||||||
{
|
{
|
||||||
// We are not blocked by the actor we are ignoring.
|
|
||||||
if (otherActor == ignoreActor)
|
if (otherActor == ignoreActor)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// If self is null, we don't have a real actor - we're just checking what would happen theoretically.
|
|
||||||
// In such a scenario - we'll just assume any other actor in the cell will block us by default.
|
|
||||||
// If we have a real actor, we can then perform the extra checks that allow us to avoid being blocked.
|
|
||||||
if (self == null)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// If the check allows: we are not blocked by allied units moving in our direction.
|
// If the check allows: we are not blocked by allied units moving in our direction.
|
||||||
if (!check.HasCellCondition(CellConditions.BlockedByMovers) &&
|
if (!check.HasCellCondition(CellConditions.BlockedByMovers) &&
|
||||||
self.Owner.Stances[otherActor.Owner] == Stance.Ally &&
|
self.Owner.Stances[otherActor.Owner] == Stance.Ally &&
|
||||||
@@ -280,31 +346,134 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we cannot crush the other actor in our way, we are blocked.
|
// If we cannot crush the other actor in our way, we are blocked.
|
||||||
if (Crushes.IsEmpty)
|
if (Info.Crushes.IsEmpty)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// If the other actor in our way cannot be crushed, we are blocked.
|
// If the other actor in our way cannot be crushed, we are blocked.
|
||||||
// PERF: Avoid LINQ.
|
// PERF: Avoid LINQ.
|
||||||
var crushables = otherActor.TraitsImplementing<ICrushable>();
|
var crushables = otherActor.TraitsImplementing<ICrushable>();
|
||||||
foreach (var crushable in crushables)
|
foreach (var crushable in crushables)
|
||||||
if (crushable.CrushableBy(otherActor, self, Crushes))
|
if (crushable.CrushableBy(otherActor, self, Info.Crushes))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool DisableDomainPassabilityCheck { get { return false; } }
|
static bool IsMovingInMyDirection(Actor self, Actor other)
|
||||||
|
{
|
||||||
|
// PERF: Because we can be sure that OccupiesSpace is Mobile here we can save some performance by avoiding querying for the trait.
|
||||||
|
var otherMobile = other.OccupiesSpace as Mobile;
|
||||||
|
if (otherMobile == null || !otherMobile.CurrentMovementTypes.HasMovementType(MovementType.Horizontal))
|
||||||
|
return false;
|
||||||
|
|
||||||
public virtual object Create(ActorInitializer init) { return new Locomotor(init.Self, this); }
|
// PERF: Same here.
|
||||||
|
var selfMobile = self.OccupiesSpace as Mobile;
|
||||||
|
if (selfMobile == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Moving in the same direction if the facing delta is between +/- 90 degrees
|
||||||
|
var delta = Util.NormalizeFacing(otherMobile.Facing - selfMobile.Facing);
|
||||||
|
return delta < 64 || delta > 192;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Locomotor
|
public void WorldLoaded(World w, WorldRenderer wr)
|
||||||
{
|
{
|
||||||
public readonly LocomotorInfo Info;
|
world = w;
|
||||||
|
var map = w.Map;
|
||||||
|
actorMap = w.ActorMap;
|
||||||
|
actorMap.CellsUpdated += CellsUpdated;
|
||||||
|
pathabilityCache = new CellLayer<CellCache>(map);
|
||||||
|
|
||||||
public Locomotor(Actor self, LocomotorInfo info)
|
terrainInfos = Info.TilesetTerrainInfo[map.Rules.TileSet];
|
||||||
|
|
||||||
|
allPlayerMask = world.AllPlayerMask;
|
||||||
|
|
||||||
|
foreach (var cell in map.AllCells)
|
||||||
|
UpdateCellCost(cell);
|
||||||
|
|
||||||
|
map.CustomTerrain.CellEntryChanged += MapCellEntryChanged;
|
||||||
|
map.Tiles.CellEntryChanged += MapCellEntryChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CellsUpdated(IEnumerable<CPos> cells)
|
||||||
{
|
{
|
||||||
Info = info;
|
foreach (var cell in cells)
|
||||||
|
UpdateCellBlocking(cell);
|
||||||
|
|
||||||
|
foreach (var cell in updatedTerrainCells)
|
||||||
|
UpdateCellCost(cell);
|
||||||
|
|
||||||
|
updatedTerrainCells.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateCellCost(CPos cell)
|
||||||
|
{
|
||||||
|
var index = cell.Layer == 0
|
||||||
|
? world.Map.GetTerrainIndex(cell)
|
||||||
|
: world.GetCustomMovementLayers()[cell.Layer].GetTerrainIndex(cell);
|
||||||
|
|
||||||
|
var cost = short.MaxValue;
|
||||||
|
|
||||||
|
if (index != byte.MaxValue)
|
||||||
|
cost = terrainInfos[index].Cost;
|
||||||
|
|
||||||
|
var current = pathabilityCache[cell];
|
||||||
|
pathabilityCache[cell] = current.WithCost(cost);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateCellBlocking(CPos cell)
|
||||||
|
{
|
||||||
|
var current = pathabilityCache[cell];
|
||||||
|
|
||||||
|
var actors = actorMap.GetActorsAt(cell);
|
||||||
|
|
||||||
|
if (!actors.Any())
|
||||||
|
{
|
||||||
|
pathabilityCache[cell] = current.WithBlocking(default(LongBitSet<PlayerBitMask>), CellBlocking.Empty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cellBlocking = CellBlocking.HasActor;
|
||||||
|
if (sharesCell && actorMap.HasFreeSubCell(cell))
|
||||||
|
{
|
||||||
|
pathabilityCache[cell] = current.WithBlocking(default(LongBitSet<PlayerBitMask>), cellBlocking | CellBlocking.FreeSubCell);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cellBits = default(LongBitSet<PlayerBitMask>);
|
||||||
|
|
||||||
|
foreach (var actor in actors)
|
||||||
|
{
|
||||||
|
var actorBits = default(LongBitSet<PlayerBitMask>);
|
||||||
|
var crushables = actor.TraitsImplementing<ICrushable>();
|
||||||
|
var mobile = actor.OccupiesSpace as Mobile;
|
||||||
|
var isMoving = mobile != null && mobile.CurrentMovementTypes.HasFlag(MovementType.Horizontal);
|
||||||
|
|
||||||
|
if (crushables.Any())
|
||||||
|
{
|
||||||
|
LongBitSet<PlayerBitMask> crushingBits;
|
||||||
|
foreach (var crushable in crushables)
|
||||||
|
if (crushable.TryCalculatePlayerBlocking(actor, Info.Crushes, out crushingBits))
|
||||||
|
{
|
||||||
|
actorBits = actorBits.Union(crushingBits);
|
||||||
|
cellBlocking |= CellBlocking.Crushable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMoving)
|
||||||
|
actorBits = actorBits.Except(actor.Owner.AllyMask);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
actorBits = isMoving ? actor.Owner.EnemyMask : allPlayerMask;
|
||||||
|
|
||||||
|
cellBits = cellBits.Union(actorBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
pathabilityCache[cell] = current.WithBlocking(cellBits, cellBlocking);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MapCellEntryChanged(CPos cell)
|
||||||
|
{
|
||||||
|
updatedTerrainCells.Add(cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
return EmptyPath;
|
return EmptyPath;
|
||||||
|
|
||||||
var distance = source - target;
|
var distance = source - target;
|
||||||
if (source.Layer == target.Layer && distance.LengthSquared < 3 && li.CanMoveFreelyInto(world, self, target, null, CellConditions.All))
|
if (source.Layer == target.Layer && distance.LengthSquared < 3 && locomotor.CanMoveFreelyInto(self, target, null, CellConditions.All))
|
||||||
return new List<CPos> { target };
|
return new List<CPos> { target };
|
||||||
|
|
||||||
List<CPos> pb;
|
List<CPos> pb;
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
public interface ICrushable
|
public interface ICrushable
|
||||||
{
|
{
|
||||||
bool CrushableBy(Actor self, Actor crusher, BitSet<CrushClass> crushClasses);
|
bool CrushableBy(Actor self, Actor crusher, BitSet<CrushClass> crushClasses);
|
||||||
|
bool TryCalculatePlayerBlocking(Actor self, BitSet<CrushClass> crushClasses, out LongBitSet<PlayerBitMask> blocking);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RequireExplicitImplementation]
|
[RequireExplicitImplementation]
|
||||||
|
|||||||
Reference in New Issue
Block a user