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);
|
||||
}
|
||||
|
||||
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.")]
|
||||
|
||||
@@ -172,7 +172,8 @@ namespace OpenRA.Mods.Common.Pathfinder
|
||||
|
||||
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)))
|
||||
return CalculateCellCost(destNode, direction, movementCost);
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
readonly Actor self;
|
||||
Transforms[] transforms;
|
||||
Locomotor locomotor;
|
||||
|
||||
public TransformsIntoMobile(ActorInitializer init, TransformsIntoMobileInfo info)
|
||||
: base(info)
|
||||
@@ -67,6 +68,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
protected override void Created(Actor self)
|
||||
{
|
||||
transforms = self.TraitsImplementing<Transforms>().ToArray();
|
||||
locomotor = self.World.WorldActor.TraitsImplementing<Locomotor>()
|
||||
.Single(l => l.Info.Name == Info.Locomotor);
|
||||
base.Created(self);
|
||||
}
|
||||
|
||||
@@ -182,21 +185,20 @@ namespace OpenRA.Mods.Common.Traits
|
||||
cursor = self.World.Map.Contains(location) ?
|
||||
(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))
|
||||
|| (!explored && !locomotor.MoveIntoShroud)
|
||||
|| (explored && !CanEnterCell(self.World, self, location)))
|
||||
|| (!explored && !mobile.locomotor.Info.MoveIntoShroud)
|
||||
|| (explored && !CanEnterCell(self, location)))
|
||||
cursor = mobile.Info.BlockedCursor;
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
self.World.AddToMaps(self, this);
|
||||
|
||||
@@ -79,5 +79,15 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
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; }
|
||||
|
||||
// initialized and used by CanEnterCell
|
||||
Locomotor locomotor;
|
||||
|
||||
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;
|
||||
|
||||
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)
|
||||
@@ -425,12 +433,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
@@ -664,11 +672,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
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)
|
||||
{
|
||||
// Only make actor crush if it is on the ground
|
||||
@@ -875,7 +878,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
if (mobile.IsTraitPaused
|
||||
|| (!explored && !locomotorInfo.MoveIntoShroud)
|
||||
|| (explored && locomotorInfo.MovementCostForCell(self.World, location) == int.MaxValue))
|
||||
|| (explored && mobile.Locomotor.MovementCostForCell(location) == int.MaxValue))
|
||||
cursor = mobile.Info.BlockedCursor;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Traits;
|
||||
|
||||
@@ -26,13 +27,34 @@ namespace OpenRA.Mods.Common.Traits
|
||||
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)
|
||||
{
|
||||
// PERF: Enum.HasFlag is slower and requires allocations.
|
||||
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
|
||||
@@ -78,9 +100,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (speed > 0)
|
||||
{
|
||||
var nodesDict = t.Value.ToDictionary();
|
||||
var cost = nodesDict.ContainsKey("PathingCost")
|
||||
var cost = (nodesDict.ContainsKey("PathingCost")
|
||||
? FieldLoader.GetValue<short>("cost", nodesDict["PathingCost"].Value)
|
||||
: 10000 / speed;
|
||||
: 10000 / speed);
|
||||
ret.Add(t.Key, new TerrainInfo(speed, (short)cost));
|
||||
}
|
||||
}
|
||||
@@ -146,25 +168,6 @@ namespace OpenRA.Mods.Common.Traits
|
||||
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)
|
||||
{
|
||||
// collect our ability to cross *all* terraintypes, in a bitvector
|
||||
@@ -190,80 +193,143 @@ namespace OpenRA.Mods.Common.Traits
|
||||
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; } }
|
||||
|
||||
public virtual object Create(ActorInitializer init) { return new Locomotor(init.Self, this); }
|
||||
}
|
||||
|
||||
public class Locomotor : IWorldLoaded
|
||||
{
|
||||
struct CellCache
|
||||
{
|
||||
var cost = MovementCostForCell(worldMovementInfo.World, worldMovementInfo.TerrainInfos, cell);
|
||||
if (cost == int.MaxValue || !CanMoveFreelyInto(worldMovementInfo.World, self, cell, ignoreActor, check))
|
||||
return int.MaxValue;
|
||||
return cost;
|
||||
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 SubCell GetAvailableSubCell(
|
||||
World world, Actor self, CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, CellConditions check = CellConditions.All)
|
||||
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)
|
||||
{
|
||||
if (MovementCostForCell(world, cell) == int.MaxValue)
|
||||
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;
|
||||
|
||||
if (check.HasCellCondition(CellConditions.TransientActors))
|
||||
{
|
||||
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.FreeSubCell(cell, preferredSubCell, checkTransient);
|
||||
}
|
||||
|
||||
if (!SharesCell)
|
||||
if (!sharesCell)
|
||||
return world.ActorMap.AnyActorsAt(cell, SubCell.FullCell) ? SubCell.Invalid : SubCell.FullCell;
|
||||
|
||||
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)
|
||||
{
|
||||
// We are not blocked by the actor we are ignoring.
|
||||
if (otherActor == ignoreActor)
|
||||
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 (!check.HasCellCondition(CellConditions.BlockedByMovers) &&
|
||||
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 (Crushes.IsEmpty)
|
||||
if (Info.Crushes.IsEmpty)
|
||||
return true;
|
||||
|
||||
// If the other actor in our way cannot be crushed, we are blocked.
|
||||
// PERF: Avoid LINQ.
|
||||
var crushables = otherActor.TraitsImplementing<ICrushable>();
|
||||
foreach (var crushable in crushables)
|
||||
if (crushable.CrushableBy(otherActor, self, Crushes))
|
||||
if (crushable.CrushableBy(otherActor, self, Info.Crushes))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool DisableDomainPassabilityCheck { get { return false; } }
|
||||
|
||||
public virtual object Create(ActorInitializer init) { return new Locomotor(init.Self, this); }
|
||||
}
|
||||
|
||||
public class Locomotor
|
||||
{
|
||||
public readonly LocomotorInfo Info;
|
||||
|
||||
public Locomotor(Actor self, LocomotorInfo info)
|
||||
static bool IsMovingInMyDirection(Actor self, Actor other)
|
||||
{
|
||||
Info = info;
|
||||
// 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;
|
||||
|
||||
// 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 void WorldLoaded(World w, WorldRenderer wr)
|
||||
{
|
||||
world = w;
|
||||
var map = w.Map;
|
||||
actorMap = w.ActorMap;
|
||||
actorMap.CellsUpdated += CellsUpdated;
|
||||
pathabilityCache = new CellLayer<CellCache>(map);
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
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 };
|
||||
|
||||
List<CPos> pb;
|
||||
|
||||
@@ -90,6 +90,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
public interface ICrushable
|
||||
{
|
||||
bool CrushableBy(Actor self, Actor crusher, BitSet<CrushClass> crushClasses);
|
||||
bool TryCalculatePlayerBlocking(Actor self, BitSet<CrushClass> crushClasses, out LongBitSet<PlayerBitMask> blocking);
|
||||
}
|
||||
|
||||
[RequireExplicitImplementation]
|
||||
|
||||
Reference in New Issue
Block a user