Fixes on blocking logic

This commit is contained in:
teinarss
2019-08-01 20:02:42 +02:00
committed by reaperrr
parent 98602cb1cb
commit 277906c657
9 changed files with 121 additions and 134 deletions

View File

@@ -76,8 +76,8 @@ namespace OpenRA
// Each player is identified with a unique bit in the set // Each player is identified with a unique bit in the set
// Cache masks for the player's index and ally/enemy player indices for performance. // Cache masks for the player's index and ally/enemy player indices for performance.
public LongBitSet<PlayerBitMask> PlayerMask; public LongBitSet<PlayerBitMask> PlayerMask;
public LongBitSet<PlayerBitMask> AllyMask = default(LongBitSet<PlayerBitMask>); public LongBitSet<PlayerBitMask> AlliedPlayersMask = default(LongBitSet<PlayerBitMask>);
public LongBitSet<PlayerBitMask> EnemyMask = default(LongBitSet<PlayerBitMask>); public LongBitSet<PlayerBitMask> EnemyPlayersMask = default(LongBitSet<PlayerBitMask>);
public bool UnlockedRenderPlayer public bool UnlockedRenderPlayer
{ {

View File

@@ -260,7 +260,7 @@ namespace OpenRA.Traits
WDist LargestActorRadius { get; } WDist LargestActorRadius { get; }
WDist LargestBlockingActorRadius { get; } WDist LargestBlockingActorRadius { get; }
event Action<IEnumerable<CPos>> CellsUpdated; event Action<CPos> CellUpdated;
} }
[RequireExplicitImplementation] [RequireExplicitImplementation]

View File

@@ -43,7 +43,8 @@ namespace OpenRA
public readonly MersenneTwister SharedRandom; public readonly MersenneTwister SharedRandom;
public readonly MersenneTwister LocalRandom; public readonly MersenneTwister LocalRandom;
public readonly IModelCache ModelCache; public readonly IModelCache ModelCache;
public LongBitSet<PlayerBitMask> AllPlayerMask = default(LongBitSet<PlayerBitMask>); public LongBitSet<PlayerBitMask> AllPlayersMask = default(LongBitSet<PlayerBitMask>);
public readonly LongBitSet<PlayerBitMask> NoPlayersMask = default(LongBitSet<PlayerBitMask>);
public Player[] Players = new Player[0]; public Player[] Players = new Player[0];
@@ -208,7 +209,7 @@ namespace OpenRA
foreach (var p in Players) foreach (var p in Players)
{ {
if (!p.Spectating) if (!p.Spectating)
AllPlayerMask = AllPlayerMask.Union(p.PlayerMask); AllPlayersMask = AllPlayersMask.Union(p.PlayerMask);
foreach (var q in Players) foreach (var q in Players)
{ {
@@ -244,10 +245,10 @@ namespace OpenRA
{ {
case Stance.Enemy: case Stance.Enemy:
case Stance.Neutral: case Stance.Neutral:
p.EnemyMask = p.EnemyMask.Union(bitSet); p.EnemyPlayersMask = p.EnemyPlayersMask.Union(bitSet);
break; break;
case Stance.Ally: case Stance.Ally:
p.AllyMask = p.AllyMask.Union(bitSet); p.AlliedPlayersMask = p.AlliedPlayersMask.Union(bitSet);
break; break;
} }
} }

View File

@@ -59,11 +59,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) LongBitSet<PlayerBitMask> ICrushable.CrushableBy(Actor self, BitSet<CrushClass> crushClasses)
{ {
// Fall back to the slow path if (!info.CrushClasses.Overlaps(crushClasses))
blocking = default(LongBitSet<PlayerBitMask>); return self.World.NoPlayersMask;
return false;
// Friendly units should move around!
return info.BlockFriendly ? self.Owner.EnemyPlayersMask : self.World.AllPlayersMask;
} }
} }

View File

@@ -225,14 +225,9 @@ 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) LongBitSet<PlayerBitMask> ICrushable.CrushableBy(Actor self, BitSet<CrushClass> crushClasses)
{ {
if (self.IsAtGroundLevel() && crushClasses.Contains(info.CrushClass)) return self.IsAtGroundLevel() && crushClasses.Contains(info.CrushClass) ? self.World.AllPlayersMask : self.World.NoPlayersMask;
blocking = default(LongBitSet<PlayerBitMask>);
else
blocking = self.World.AllPlayerMask;
return true;
} }
void INotifyAddedToWorld.AddedToWorld(Actor self) void INotifyAddedToWorld.AddedToWorld(Actor self)

View File

@@ -65,6 +65,14 @@ namespace OpenRA.Mods.Common.Traits
return CrushableInner(crushClasses, crusher.Owner); return CrushableInner(crushClasses, crusher.Owner);
} }
LongBitSet<PlayerBitMask> ICrushable.CrushableBy(Actor self, BitSet<CrushClass> crushClasses)
{
if (IsTraitDisabled || !self.IsAtGroundLevel() || !Info.CrushClasses.Overlaps(crushClasses))
return self.World.NoPlayersMask;
return Info.CrushedByFriendlies ? self.World.AllPlayersMask : self.Owner.EnemyPlayersMask;
}
bool CrushableInner(BitSet<CrushClass> crushClasses, Player crushOwner) bool CrushableInner(BitSet<CrushClass> crushClasses, Player crushOwner)
{ {
if (IsTraitDisabled) if (IsTraitDisabled)
@@ -79,15 +87,5 @@ 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;
}
} }
} }

View File

@@ -173,7 +173,7 @@ namespace OpenRA.Mods.Common.Traits
readonly CellLayer<InfluenceNode> influence; readonly CellLayer<InfluenceNode> influence;
readonly Dictionary<int, CellLayer<InfluenceNode>> customInfluence = new Dictionary<int, CellLayer<InfluenceNode>>(); readonly Dictionary<int, CellLayer<InfluenceNode>> customInfluence = new Dictionary<int, CellLayer<InfluenceNode>>();
public readonly Dictionary<int, ICustomMovementLayer> CustomMovementLayers = new Dictionary<int, ICustomMovementLayer>(); public readonly Dictionary<int, ICustomMovementLayer> CustomMovementLayers = new Dictionary<int, ICustomMovementLayer>();
public event Action<IEnumerable<CPos>> CellsUpdated; public event Action<CPos> CellUpdated;
readonly Bin[] bins; readonly Bin[] bins;
readonly int rows, cols; readonly int rows, cols;
@@ -182,7 +182,6 @@ namespace OpenRA.Mods.Common.Traits
readonly HashSet<Actor> addActorPosition = new HashSet<Actor>(); readonly HashSet<Actor> addActorPosition = new HashSet<Actor>();
readonly HashSet<Actor> removeActorPosition = new HashSet<Actor>(); readonly HashSet<Actor> removeActorPosition = new HashSet<Actor>();
readonly Predicate<Actor> actorShouldBeRemoved; readonly Predicate<Actor> actorShouldBeRemoved;
readonly HashSet<CPos> updatedCells = new HashSet<CPos>();
public WDist LargestActorRadius { get; private set; } public WDist LargestActorRadius { get; private set; }
public WDist LargestBlockingActorRadius { get; private set; } public WDist LargestBlockingActorRadius { get; private set; }
@@ -372,7 +371,8 @@ namespace OpenRA.Mods.Common.Traits
foreach (var t in triggers) foreach (var t in triggers)
t.Dirty = true; t.Dirty = true;
updatedCells.Add(c.First); if (CellUpdated != null)
CellUpdated(c.First);
} }
} }
@@ -394,7 +394,8 @@ namespace OpenRA.Mods.Common.Traits
foreach (var t in triggers) foreach (var t in triggers)
t.Dirty = true; t.Dirty = true;
updatedCells.Add(c.First); if (CellUpdated != null)
CellUpdated(c.First);
} }
} }
@@ -442,15 +443,6 @@ namespace OpenRA.Mods.Common.Traits
foreach (var t in proximityTriggers) foreach (var t in proximityTriggers)
t.Value.Tick(this); t.Value.Tick(this);
self.World.AddFrameEndTask(s =>
{
if (CellsUpdated != null)
{
CellsUpdated(updatedCells);
updatedCells.Clear();
}
});
} }
public int AddCellTrigger(CPos[] cells, Action<Actor> onEntry, Action<Actor> onExit) public int AddCellTrigger(CPos[] cells, Action<Actor> onEntry, Action<Actor> onExit)

View File

@@ -29,12 +29,13 @@ namespace OpenRA.Mods.Common.Traits
} }
[Flags] [Flags]
public enum CellBlocking : byte public enum CellFlag : byte
{ {
Empty = 0, HasFreeSpace = 0,
HasActor = 1, HasActor = 1,
FreeSubCell = 2, HasMovingActor = 2,
Crushable = 4 HasCrushableActor = 4,
HasTemporaryBlocker = 8
} }
public static class LocomoterExts public static class LocomoterExts
@@ -45,10 +46,10 @@ namespace OpenRA.Mods.Common.Traits
return (c & cellCondition) == cellCondition; return (c & cellCondition) == cellCondition;
} }
public static bool HasCellBlocking(this CellBlocking c, CellBlocking cellBlocking) public static bool HasCellFlag(this CellFlag c, CellFlag cellFlag)
{ {
// PERF: Enum.HasFlag is slower and requires allocations. // PERF: Enum.HasFlag is slower and requires allocations.
return (c & cellBlocking) == cellBlocking; return (c & cellFlag) == cellFlag;
} }
public static bool HasMovementType(this MovementType m, MovementType movementType) public static bool HasMovementType(this MovementType m, MovementType movementType)
@@ -203,35 +204,25 @@ namespace OpenRA.Mods.Common.Traits
{ {
struct CellCache struct CellCache
{ {
public readonly CellBlocking CellBlocking;
public readonly short Cost;
public readonly LongBitSet<PlayerBitMask> Blocking; public readonly LongBitSet<PlayerBitMask> Blocking;
public readonly LongBitSet<PlayerBitMask> Crushable;
public readonly CellFlag CellFlag;
public CellCache(short cost, LongBitSet<PlayerBitMask> blocking, CellBlocking cellBlocking) public CellCache(LongBitSet<PlayerBitMask> blocking, CellFlag cellFlag, LongBitSet<PlayerBitMask> crushable = default(LongBitSet<PlayerBitMask>))
{ {
Cost = cost;
Blocking = blocking; Blocking = blocking;
CellBlocking = cellBlocking; Crushable = crushable;
} CellFlag = cellFlag;
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; public readonly LocomotorInfo Info;
CellLayer<CellCache> pathabilityCache; CellLayer<short> cellsCost;
LongBitSet<PlayerBitMask> allPlayerMask = default(LongBitSet<PlayerBitMask>); CellLayer<CellCache> blockingCache;
LocomotorInfo.TerrainInfo[] terrainInfos; LocomotorInfo.TerrainInfo[] terrainInfos;
World world; World world;
readonly HashSet<CPos> updatedTerrainCells = new HashSet<CPos>(); readonly HashSet<CPos> dirtyCells = new HashSet<CPos>();
IActorMap actorMap; IActorMap actorMap;
bool sharesCell; bool sharesCell;
@@ -247,7 +238,7 @@ namespace OpenRA.Mods.Common.Traits
if (!world.Map.Contains(cell)) if (!world.Map.Contains(cell))
return short.MaxValue; return short.MaxValue;
return pathabilityCache[cell].Cost; return cellsCost[cell];
} }
public short MovementCostToEnterCell(Actor actor, CPos destNode, Actor ignoreActor, CellConditions check) public short MovementCostToEnterCell(Actor actor, CPos destNode, Actor ignoreActor, CellConditions check)
@@ -255,51 +246,51 @@ namespace OpenRA.Mods.Common.Traits
if (!world.Map.Contains(destNode)) if (!world.Map.Contains(destNode))
return short.MaxValue; return short.MaxValue;
var cellCache = pathabilityCache[destNode]; var cellCost = cellsCost[destNode];
if (cellCache.Cost == short.MaxValue || if (cellCost == short.MaxValue ||
!CanMoveFreelyInto(actor, ignoreActor, destNode, check, cellCache)) !CanMoveFreelyInto(actor, destNode, ignoreActor, check))
return short.MaxValue; return short.MaxValue;
return cellCache.Cost; return cellCost;
} }
// Determines whether the actor is blocked by other Actors // Determines whether the actor is blocked by other Actors
public bool CanMoveFreelyInto(Actor actor, CPos cell, Actor ignoreActor, CellConditions check) public bool CanMoveFreelyInto(Actor actor, CPos cell, Actor ignoreActor, CellConditions check)
{ {
return CanMoveFreelyInto(actor, ignoreActor, cell, check, pathabilityCache[cell]); var cellCache = GetCache(cell);
} var cellFlag = cellCache.CellFlag;
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)) if (!check.HasCellCondition(CellConditions.TransientActors))
return true; return true;
// No actor in the cell or free SubCell.
if (cellFlag == CellFlag.HasFreeSpace)
return true;
// If actor is null we're just checking what would happen theoretically. // 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. // 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 we have a real actor, we can then perform the extra checks that allow us to avoid being blocked.
if (actor == null) 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; return false;
if (cellBlocking.HasCellBlocking(CellBlocking.Crushable)) // All actors that may be in the cell can be crushed.
if (cellCache.Crushable.Overlaps(actor.Owner.PlayerMask))
return true; return true;
if (sharesCell && cellBlocking.HasCellBlocking(CellBlocking.FreeSubCell)) // Cache doesn't account for ignored actors - these must use the slow path
return true; if (ignoreActor == null)
{
// We are blocked by another actor in the cell.
if (cellCache.Blocking.Overlaps(actor.Owner.PlayerMask))
return false;
if (check == CellConditions.BlockedByMovers && cellFlag < CellFlag.HasCrushableActor)
return false;
}
foreach (var otherActor in world.ActorMap.GetActorsAt(cell)) foreach (var otherActor in world.ActorMap.GetActorsAt(cell))
if (IsBlockedBy(actor, otherActor, ignoreActor, check)) if (IsBlockedBy(actor, otherActor, ignoreActor, check, cellFlag))
return false; return false;
return true; return true;
@@ -307,12 +298,13 @@ namespace OpenRA.Mods.Common.Traits
public SubCell GetAvailableSubCell(Actor self, CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, CellConditions check = CellConditions.All) public SubCell GetAvailableSubCell(Actor self, CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, CellConditions check = CellConditions.All)
{ {
if (MovementCostForCell(cell) == short.MaxValue) var cost = cellsCost[cell];
if (cost == short.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, blockingCache[cell].CellFlag);
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;
@@ -326,19 +318,18 @@ namespace OpenRA.Mods.Common.Traits
return world.ActorMap.FreeSubCell(cell, preferredSubCell); return world.ActorMap.FreeSubCell(cell, preferredSubCell);
} }
bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor, CellConditions check) bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor, CellConditions check, CellFlag cellFlag)
{ {
if (otherActor == ignoreActor) if (otherActor == ignoreActor)
return false; return false;
// 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) && cellFlag.HasCellFlag(CellFlag.HasMovingActor) &&
self.Owner.Stances[otherActor.Owner] == Stance.Ally && self.Owner.Stances[otherActor.Owner] == Stance.Ally &&
IsMovingInMyDirection(self, otherActor)) IsMovingInMyDirection(self, otherActor))
return false; return false;
// PERF: Only perform ITemporaryBlocker trait look-up if mod/map rules contain any actors that are temporary blockers if (cellFlag.HasCellFlag(CellFlag.HasTemporaryBlocker))
if (self.World.RulesContainTemporaryBlocker)
{ {
// If there is a temporary blocker in our path, but we can remove it, we are not blocked. // If there is a temporary blocker in our path, but we can remove it, we are not blocked.
var temporaryBlocker = otherActor.TraitOrDefault<ITemporaryBlocker>(); var temporaryBlocker = otherActor.TraitOrDefault<ITemporaryBlocker>();
@@ -346,6 +337,9 @@ namespace OpenRA.Mods.Common.Traits
return false; return false;
} }
if (!cellFlag.HasCellFlag(CellFlag.HasCrushableActor))
return true;
// 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 (Info.Crushes.IsEmpty) if (Info.Crushes.IsEmpty)
return true; return true;
@@ -382,29 +376,34 @@ namespace OpenRA.Mods.Common.Traits
world = w; world = w;
var map = w.Map; var map = w.Map;
actorMap = w.ActorMap; actorMap = w.ActorMap;
actorMap.CellsUpdated += CellsUpdated; actorMap.CellUpdated += CellUpdated;
pathabilityCache = new CellLayer<CellCache>(map); blockingCache = new CellLayer<CellCache>(map);
cellsCost = new CellLayer<short>(map);
terrainInfos = Info.TilesetTerrainInfo[map.Rules.TileSet]; terrainInfos = Info.TilesetTerrainInfo[map.Rules.TileSet];
allPlayerMask = world.AllPlayerMask;
foreach (var cell in map.AllCells) foreach (var cell in map.AllCells)
UpdateCellCost(cell); UpdateCellCost(cell);
map.CustomTerrain.CellEntryChanged += MapCellEntryChanged; map.CustomTerrain.CellEntryChanged += UpdateCellCost;
map.Tiles.CellEntryChanged += MapCellEntryChanged; map.Tiles.CellEntryChanged += UpdateCellCost;
} }
void CellsUpdated(IEnumerable<CPos> cells) CellCache GetCache(CPos cell)
{
if (dirtyCells.Contains(cell))
{ {
foreach (var cell in cells)
UpdateCellBlocking(cell); UpdateCellBlocking(cell);
dirtyCells.Remove(cell);
}
foreach (var cell in updatedTerrainCells) return blockingCache[cell];
UpdateCellCost(cell); }
updatedTerrainCells.Clear(); void CellUpdated(CPos cell)
{
dirtyCells.Add(cell);
} }
void UpdateCellCost(CPos cell) void UpdateCellCost(CPos cell)
@@ -418,66 +417,66 @@ namespace OpenRA.Mods.Common.Traits
if (index != byte.MaxValue) if (index != byte.MaxValue)
cost = terrainInfos[index].Cost; cost = terrainInfos[index].Cost;
var current = pathabilityCache[cell]; cellsCost[cell] = cost;
pathabilityCache[cell] = current.WithCost(cost);
} }
void UpdateCellBlocking(CPos cell) void UpdateCellBlocking(CPos cell)
{ {
using (new PerfSample("locomotor_cache")) using (new PerfSample("locomotor_cache"))
{ {
var current = pathabilityCache[cell];
var actors = actorMap.GetActorsAt(cell); var actors = actorMap.GetActorsAt(cell);
if (!actors.Any()) if (!actors.Any())
{ {
pathabilityCache[cell] = current.WithBlocking(default(LongBitSet<PlayerBitMask>), CellBlocking.Empty); blockingCache[cell] = new CellCache(default(LongBitSet<PlayerBitMask>), CellFlag.HasFreeSpace);
return; return;
} }
var cellBlocking = CellBlocking.HasActor;
if (sharesCell && actorMap.HasFreeSubCell(cell)) if (sharesCell && actorMap.HasFreeSubCell(cell))
{ {
pathabilityCache[cell] = current.WithBlocking(default(LongBitSet<PlayerBitMask>), cellBlocking | CellBlocking.FreeSubCell); blockingCache[cell] = new CellCache(default(LongBitSet<PlayerBitMask>), CellFlag.HasFreeSpace);
return; return;
} }
var cellBits = default(LongBitSet<PlayerBitMask>); var cellFlag = CellFlag.HasActor;
var cellBlockedPlayers = default(LongBitSet<PlayerBitMask>);
var cellCrushablePlayers = world.AllPlayersMask;
foreach (var actor in actors) foreach (var actor in actors)
{ {
var actorBits = default(LongBitSet<PlayerBitMask>); var actorBlocksPlayers = world.AllPlayersMask;
var crushables = actor.TraitsImplementing<ICrushable>(); var crushables = actor.TraitsImplementing<ICrushable>();
var mobile = actor.OccupiesSpace as Mobile; var mobile = actor.OccupiesSpace as Mobile;
var isMoving = mobile != null && mobile.CurrentMovementTypes.HasFlag(MovementType.Horizontal); var isMoving = mobile != null && mobile.CurrentMovementTypes.HasMovementType(MovementType.Horizontal);
if (crushables.Any()) if (crushables.Any())
{ {
LongBitSet<PlayerBitMask> crushingBits; cellFlag |= CellFlag.HasCrushableActor;
foreach (var crushable in crushables) foreach (var crushable in crushables)
if (crushable.TryCalculatePlayerBlocking(actor, Info.Crushes, out crushingBits)) cellCrushablePlayers = cellCrushablePlayers.Intersect(crushable.CrushableBy(actor, Info.Crushes));
{
actorBits = actorBits.Union(crushingBits);
cellBlocking |= CellBlocking.Crushable;
}
if (isMoving)
actorBits = actorBits.Except(actor.Owner.AllyMask);
} }
else else
actorBits = isMoving ? actor.Owner.EnemyMask : allPlayerMask; cellCrushablePlayers = world.NoPlayersMask;
cellBits = cellBits.Union(actorBits); if (isMoving)
}
pathabilityCache[cell] = current.WithBlocking(cellBits, cellBlocking);
}
}
void MapCellEntryChanged(CPos cell)
{ {
updatedTerrainCells.Add(cell); actorBlocksPlayers = actorBlocksPlayers.Except(actor.Owner.AlliedPlayersMask);
cellFlag |= CellFlag.HasMovingActor;
}
// PERF: Only perform ITemporaryBlocker trait look-up if mod/map rules contain any actors that are temporary blockers
if (world.RulesContainTemporaryBlocker)
{
// If there is a temporary blocker in this cell.
if (actor.TraitOrDefault<ITemporaryBlocker>() != null)
cellFlag |= CellFlag.HasTemporaryBlocker;
}
cellBlockedPlayers = cellBlockedPlayers.Union(actorBlocksPlayers);
}
blockingCache[cell] = new CellCache(cellBlockedPlayers, cellFlag, cellCrushablePlayers);
}
} }
} }
} }

View File

@@ -90,7 +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); LongBitSet<PlayerBitMask> CrushableBy(Actor self, BitSet<CrushClass> crushClasses);
} }
[RequireExplicitImplementation] [RequireExplicitImplementation]