Fixes on blocking logic
This commit is contained in:
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user