From 4a609bbee81f4a82bd85ea8244857f02145649c8 Mon Sep 17 00:00:00 2001 From: tovl Date: Fri, 12 Apr 2019 19:26:22 +0200 Subject: [PATCH] Allow units to give way when path is blocked by oncoming unit. --- OpenRA.Game/Traits/TraitsInterfaces.cs | 16 +- OpenRA.Mods.Cnc/Traits/Minelayer.cs | 4 +- OpenRA.Mods.Cnc/Traits/TDGunboat.cs | 6 +- OpenRA.Mods.Common/Activities/Attack.cs | 2 +- .../Activities/FindAndDeliverResources.cs | 2 +- .../Activities/HarvestResource.cs | 2 +- OpenRA.Mods.Common/Activities/Move/Move.cs | 145 ++++++++++++++---- .../Activities/Move/MoveAdjacentTo.cs | 10 +- OpenRA.Mods.Common/Activities/UnloadCargo.cs | 2 +- .../PathFinderUnitPathCacheDecorator.cs | 34 ++-- OpenRA.Mods.Common/Pathfinder/PathGraph.cs | 10 +- OpenRA.Mods.Common/Pathfinder/PathSearch.cs | 13 +- OpenRA.Mods.Common/Traits/Air/Aircraft.cs | 8 +- .../Traits/Attack/AttackBase.cs | 2 +- .../Traits/BotModules/HarvesterBotModule.cs | 2 +- .../Traits/Buildings/TransformsIntoMobile.cs | 2 +- OpenRA.Mods.Common/Traits/Cargo.cs | 8 +- OpenRA.Mods.Common/Traits/Crates/Crate.cs | 16 +- OpenRA.Mods.Common/Traits/Harvester.cs | 2 +- OpenRA.Mods.Common/Traits/Husk.cs | 10 +- OpenRA.Mods.Common/Traits/Mobile.cs | 53 ++++--- OpenRA.Mods.Common/Traits/World/Locomotor.cs | 116 +++++++------- OpenRA.Mods.Common/Traits/World/PathFinder.cs | 22 +-- OpenRA.Mods.D2k/Activities/SwallowActor.cs | 2 +- OpenRA.Mods.D2k/Traits/Sandworm.cs | 4 +- 25 files changed, 309 insertions(+), 184 deletions(-) diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index fb8846f63c..2327aa94f5 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -32,6 +32,16 @@ namespace OpenRA.Traits Dead = 32 } + // NOTE: Each subsequent category is a superset of the previous categories + // and categories are mutually exclusive. + public enum BlockedByActor + { + None, + Immovable, + Stationary, + All + } + /// /// Type tag for DamageTypes . /// @@ -312,16 +322,16 @@ namespace OpenRA.Traits public interface IPositionableInfo : IOccupySpaceInfo { - bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true); + bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All); } public interface IPositionable : IOccupySpace { bool CanExistInCell(CPos location); bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any); - bool CanEnterCell(CPos location, Actor ignoreActor = null, bool checkTransientActors = true); + bool CanEnterCell(CPos location, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All); SubCell GetValidSubCell(SubCell preferred = SubCell.Any); - SubCell GetAvailableSubCell(CPos location, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true); + SubCell GetAvailableSubCell(CPos location, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All); void SetPosition(Actor self, CPos cell, SubCell subCell = SubCell.Any); void SetPosition(Actor self, WPos pos); void SetVisualPosition(Actor self, WPos pos); diff --git a/OpenRA.Mods.Cnc/Traits/Minelayer.cs b/OpenRA.Mods.Cnc/Traits/Minelayer.cs index 9bcc3f673d..2ca0099672 100644 --- a/OpenRA.Mods.Cnc/Traits/Minelayer.cs +++ b/OpenRA.Mods.Cnc/Traits/Minelayer.cs @@ -107,7 +107,7 @@ namespace OpenRA.Mods.Cnc.Traits var movement = self.Trait(); var minefield = GetMinefieldCells(minefieldStart, cell, Info.MinefieldDepth) - .Where(c => movement.CanEnterCell(c, null, false)) + .Where(c => movement.CanEnterCell(c, null, BlockedByActor.None)) .OrderBy(c => (c - minefieldStart).LengthSquared).ToList(); self.QueueActivity(order.Queued, new LayMines(self, minefield)); @@ -216,7 +216,7 @@ namespace OpenRA.Mods.Cnc.Traits var pal = wr.Palette(TileSet.TerrainPaletteInternalName); foreach (var c in minefield) { - var tile = movement.CanEnterCell(c, null, false) && !world.ShroudObscures(c) ? tileOk : tileBlocked; + var tile = movement.CanEnterCell(c, null, BlockedByActor.None) && !world.ShroudObscures(c) ? tileOk : tileBlocked; yield return new SpriteRenderable(tile, world.Map.CenterOfCell(c), WVec.Zero, -511, pal, 1f, true); } diff --git a/OpenRA.Mods.Cnc/Traits/TDGunboat.cs b/OpenRA.Mods.Cnc/Traits/TDGunboat.cs index 74e8f7ae7a..6b1d84bd68 100644 --- a/OpenRA.Mods.Cnc/Traits/TDGunboat.cs +++ b/OpenRA.Mods.Cnc/Traits/TDGunboat.cs @@ -47,7 +47,7 @@ namespace OpenRA.Mods.Cnc.Traits bool IOccupySpaceInfo.SharesCell { get { return false; } } // Used to determine if actor can spawn - public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = false) + public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { if (!world.Map.Contains(cell)) return false; @@ -158,9 +158,9 @@ namespace OpenRA.Mods.Cnc.Traits public bool CanExistInCell(CPos cell) { return true; } public bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any) { return false; } - public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = false) { return true; } + public bool CanEnterCell(CPos cell, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { return true; } public SubCell GetValidSubCell(SubCell preferred) { return SubCell.Invalid; } - 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, BlockedByActor check = BlockedByActor.All) { // Does not use any subcell return SubCell.Invalid; diff --git a/OpenRA.Mods.Common/Activities/Attack.cs b/OpenRA.Mods.Common/Activities/Attack.cs index 1a187cc29b..873265c50f 100644 --- a/OpenRA.Mods.Common/Activities/Attack.cs +++ b/OpenRA.Mods.Common/Activities/Attack.cs @@ -152,7 +152,7 @@ namespace OpenRA.Mods.Common.Activities if (!target.IsValidFor(self)) return AttackStatus.UnableToAttack; - if (attack.Info.AttackRequiresEnteringCell && !positionable.CanEnterCell(target.Actor.Location, null, false)) + if (attack.Info.AttackRequiresEnteringCell && !positionable.CanEnterCell(target.Actor.Location, null, BlockedByActor.None)) return AttackStatus.UnableToAttack; if (!attack.Info.TargetFrozenActors && !forceAttack && target.Type == TargetType.FrozenActor) diff --git a/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs b/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs index febe0784d2..940dfd05f3 100644 --- a/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs +++ b/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs @@ -194,7 +194,7 @@ namespace OpenRA.Mods.Common.Activities // Find any harvestable resources: List path; - using (var search = PathSearch.Search(self.World, mobile.Locomotor, self, true, loc => + using (var search = PathSearch.Search(self.World, mobile.Locomotor, self, BlockedByActor.Stationary, loc => domainIndex.IsPassable(self.Location, loc, locomotorInfo) && harv.CanHarvestCell(self, loc) && claimLayer.CanClaimCell(self, loc)) .WithCustomCost(loc => { diff --git a/OpenRA.Mods.Common/Activities/HarvestResource.cs b/OpenRA.Mods.Common/Activities/HarvestResource.cs index b7a9fb0eb9..74064e5346 100644 --- a/OpenRA.Mods.Common/Activities/HarvestResource.cs +++ b/OpenRA.Mods.Common/Activities/HarvestResource.cs @@ -62,7 +62,7 @@ namespace OpenRA.Mods.Common.Activities foreach (var n in notifyHarvesterActions) n.MovingToResources(self, targetCell); - QueueChild(move.MoveTo(targetCell, 2)); + QueueChild(move.MoveTo(targetCell, 0)); return false; } diff --git a/OpenRA.Mods.Common/Activities/Move/Move.cs b/OpenRA.Mods.Common/Activities/Move/Move.cs index f60ad74c2b..73418b5192 100644 --- a/OpenRA.Mods.Common/Activities/Move/Move.cs +++ b/OpenRA.Mods.Common/Activities/Move/Move.cs @@ -11,7 +11,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Pathfinder; @@ -27,7 +26,7 @@ namespace OpenRA.Mods.Common.Activities readonly Mobile mobile; readonly WDist nearEnough; - readonly Func> getPath; + readonly Func> getPath; readonly Actor ignoreActor; readonly Color? targetLineColor; @@ -36,7 +35,6 @@ namespace OpenRA.Mods.Common.Activities // For dealing with blockers bool hasWaited; - bool hasNotifiedBlocker; int waitTicksRemaining; // To work around queued activity issues while minimizing changes to legacy behaviour @@ -48,11 +46,11 @@ namespace OpenRA.Mods.Common.Activities { mobile = self.Trait(); - getPath = () => + getPath = check => { List path; using (var search = - PathSearch.FromPoint(self.World, mobile.Locomotor, self, mobile.ToCell, destination, false) + PathSearch.FromPoint(self.World, mobile.Locomotor, self, mobile.ToCell, destination, check) .WithoutLaneBias()) path = self.World.WorldActor.Trait().FindPath(search); return path; @@ -67,13 +65,13 @@ namespace OpenRA.Mods.Common.Activities { mobile = self.Trait(); - getPath = () => + getPath = check => { if (!this.destination.HasValue) return NoPath; return self.World.WorldActor.Trait() - .FindUnitPath(mobile.ToCell, this.destination.Value, self, ignoreActor); + .FindUnitPath(mobile.ToCell, this.destination.Value, self, ignoreActor, check); }; // Note: Will be recalculated from OnFirstRun if evaluateNearestMovableCell is true @@ -89,8 +87,8 @@ namespace OpenRA.Mods.Common.Activities { mobile = self.Trait(); - getPath = () => self.World.WorldActor.Trait() - .FindUnitPathToRange(mobile.FromCell, subCell, self.World.Map.CenterOfSubCell(destination, subCell), nearEnough, self); + getPath = check => self.World.WorldActor.Trait() + .FindUnitPathToRange(mobile.FromCell, subCell, self.World.Map.CenterOfSubCell(destination, subCell), nearEnough, self, check); this.destination = destination; this.nearEnough = nearEnough; this.targetLineColor = targetLineColor; @@ -100,13 +98,13 @@ namespace OpenRA.Mods.Common.Activities { mobile = self.Trait(); - getPath = () => + getPath = check => { if (!target.IsValidFor(self)) return NoPath; return self.World.WorldActor.Trait().FindUnitPathToRange( - mobile.ToCell, mobile.ToSubCell, target.CenterPosition, range, self); + mobile.ToCell, mobile.ToSubCell, target.CenterPosition, range, self, check); }; destination = null; @@ -114,7 +112,7 @@ namespace OpenRA.Mods.Common.Activities this.targetLineColor = targetLineColor; } - public Move(Actor self, Func> getPath, Color? targetLineColor = null) + public Move(Actor self, Func> getPath, Color? targetLineColor = null) { mobile = self.Trait(); @@ -135,9 +133,9 @@ namespace OpenRA.Mods.Common.Activities return hash; } - List EvalPath() + List EvalPath(BlockedByActor check) { - var path = getPath().TakeWhile(a => a != mobile.ToCell).ToList(); + var path = getPath(check).TakeWhile(a => a != mobile.ToCell).ToList(); mobile.PathHash = HashList(path); return path; } @@ -149,10 +147,16 @@ namespace OpenRA.Mods.Common.Activities var movableDestination = mobile.NearestMoveableCell(destination.Value); destination = mobile.CanEnterCell(movableDestination) ? movableDestination : (CPos?)null; } + + path = EvalPath(BlockedByActor.Stationary); + if (path.Count == 0) + path = EvalPath(BlockedByActor.None); } public override bool Tick(Actor self) { + mobile.TurnToMove = false; + // If the actor is inside a tunnel then we must let them move // all the way through before moving to the next activity if (IsCanceling && self.Location.Layer != CustomMovementLayerType.Tunnel) @@ -164,9 +168,6 @@ namespace OpenRA.Mods.Common.Activities if (destination == mobile.ToCell) return true; - if (path == null) - path = EvalPath(); - if (path.Count == 0) { destination = mobile.ToCell; @@ -184,6 +185,7 @@ namespace OpenRA.Mods.Common.Activities { path.Add(nextCell.Value.First); QueueChild(new Turn(self, firstFacing)); + mobile.TurnToMove = true; return false; } @@ -211,7 +213,7 @@ namespace OpenRA.Mods.Common.Activities var containsTemporaryBlocker = WorldUtils.ContainsTemporaryBlocker(self.World, nextCell, self); // Next cell in the move is blocked by another actor - if (containsTemporaryBlocker || !mobile.CanEnterCell(nextCell, ignoreActor, true)) + if (containsTemporaryBlocker || !mobile.CanEnterCell(nextCell, ignoreActor)) { // Are we close enough? var cellRange = nearEnough.Length / 1024; @@ -221,42 +223,116 @@ namespace OpenRA.Mods.Common.Activities return null; } - // See if they will move - if (!hasNotifiedBlocker) + // There is no point in waiting for the other actor to move if it is incapable of moving. + if (!mobile.CanEnterCell(nextCell, ignoreActor, BlockedByActor.Immovable)) { - self.NotifyBlocker(nextCell); - hasNotifiedBlocker = true; + path = EvalPath(BlockedByActor.Immovable); + return null; } + // See if they will move + self.NotifyBlocker(nextCell); + // Wait a bit to see if they leave if (!hasWaited) { - waitTicksRemaining = mobile.Info.LocomotorInfo.WaitAverage - + self.World.SharedRandom.Next(-mobile.Info.LocomotorInfo.WaitSpread, mobile.Info.LocomotorInfo.WaitSpread); - + waitTicksRemaining = mobile.Info.LocomotorInfo.WaitAverage; hasWaited = true; + return null; } if (--waitTicksRemaining >= 0) return null; + hasWaited = false; + + // If the blocking actors are already leaving, wait a little longer instead of repathing + if (CellIsEvacuating(self, nextCell)) + return null; + // Calculate a new path mobile.RemoveInfluence(); - var newPath = EvalPath(); + var newPath = EvalPath(BlockedByActor.All); mobile.AddInfluence(); if (newPath.Count != 0) + { path = newPath; + var newCell = path[path.Count - 1]; + path.RemoveAt(path.Count - 1); + + return Pair.New(newCell, mobile.GetAvailableSubCell(nextCell, SubCell.Any, ignoreActor)); + } + else if (mobile.IsBlocking) + { + // If there is no way around the blocker and blocker will not move and we are blocking others, back up to let others pass. + var newCell = GetAdjacentCell(self, nextCell); + if (newCell != null) + { + if ((nextCell - newCell).Value.LengthSquared > 2) + path.Add(mobile.ToCell); + + return Pair.New(newCell.Value, mobile.GetAvailableSubCell(newCell.Value, SubCell.Any, ignoreActor)); + } + } return null; } - hasNotifiedBlocker = false; hasWaited = false; path.RemoveAt(path.Count - 1); - var subCell = mobile.GetAvailableSubCell(nextCell, SubCell.Any, ignoreActor); - return Pair.New(nextCell, subCell); + return Pair.New(nextCell, mobile.GetAvailableSubCell(nextCell, SubCell.Any, ignoreActor)); + } + + protected override void OnLastRun(Actor self) + { + path = null; + } + + bool CellIsEvacuating(Actor self, CPos cell) + { + foreach (var actor in self.World.ActorMap.GetActorsAt(cell)) + { + var move = actor.TraitOrDefault(); + if (move == null || !move.IsTraitEnabled() || !move.IsLeaving()) + return false; + } + + return true; + } + + CPos? GetAdjacentCell(Actor self, CPos nextCell) + { + var availCells = new List(); + var notStupidCells = new List(); + for (var i = -1; i <= 1; i++) + { + for (var j = -1; j <= 1; j++) + { + var p = mobile.ToCell + new CVec(i, j); + if (mobile.CanEnterCell(p)) + availCells.Add(p); + else if (p != nextCell && p != mobile.ToCell) + notStupidCells.Add(p); + } + } + + CPos? newCell = null; + if (availCells.Count > 0) + newCell = availCells.Random(self.World.SharedRandom); + else + { + var cellInfo = notStupidCells + .SelectMany(c => self.World.ActorMap.GetActorsAt(c) + .Where(a => a.IsIdle && a.Info.HasTraitInfo()), + (c, a) => new { Cell = c, Actor = a }) + .RandomOrDefault(self.World.SharedRandom); + if (cellInfo != null) + newCell = cellInfo.Cell; + } + + return newCell; } public override void Cancel(Actor self, bool keepQueue = false) @@ -397,10 +473,13 @@ namespace OpenRA.Mods.Common.Activities public MoveFirstHalf(Move move, WPos from, WPos to, int fromFacing, int toFacing, int startingFraction) : base(move, from, to, fromFacing, toFacing, startingFraction) { } - static bool IsTurn(Mobile mobile, CPos nextCell) + static bool IsTurn(Mobile mobile, CPos nextCell, Map map) { - return nextCell - mobile.ToCell != - mobile.ToCell - mobile.FromCell; + // Tight U-turns should be done in place instead of making silly looking loops. + var nextFacing = map.FacingBetween(nextCell, mobile.ToCell, mobile.Facing); + var currentFacing = map.FacingBetween(mobile.ToCell, mobile.FromCell, mobile.Facing); + var delta = Util.NormalizeFacing(nextFacing - currentFacing); + return delta != 0 && (delta < 96 || delta > 160); } protected override MovePart OnComplete(Actor self, Mobile mobile, Move parent) @@ -412,7 +491,7 @@ namespace OpenRA.Mods.Common.Activities var nextCell = parent.PopPath(self); if (nextCell != null) { - if (IsTurn(mobile, nextCell.Value.First)) + if (IsTurn(mobile, nextCell.Value.First, map)) { var nextSubcellOffset = map.Grid.OffsetOfSubCell(nextCell.Value.Second); var ret = new MoveFirstHalf( diff --git a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs index 4ca12068da..4064f00e5d 100644 --- a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs +++ b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs @@ -82,7 +82,7 @@ namespace OpenRA.Mods.Common.Activities protected override void OnFirstRun(Actor self) { - QueueChild(Mobile.MoveTo(() => CalculatePathToTarget(self))); + QueueChild(Mobile.MoveTo(check => CalculatePathToTarget(self, check))); } public override bool Tick(Actor self) @@ -112,14 +112,14 @@ namespace OpenRA.Mods.Common.Activities // Target has moved, and MoveAdjacentTo is still valid. if (!IsCanceling && shouldRepath) - QueueChild(Mobile.MoveTo(() => CalculatePathToTarget(self))); + QueueChild(Mobile.MoveTo(check => CalculatePathToTarget(self, check))); // The last queued childactivity is guaranteed to be the inner move, so if the childactivity // queue is empty it means we have reached our destination. return TickChild(self); } - List CalculatePathToTarget(Actor self) + List CalculatePathToTarget(Actor self, BlockedByActor check) { var targetCells = CandidateMovementCells(self); var searchCells = new List(); @@ -132,8 +132,8 @@ namespace OpenRA.Mods.Common.Activities if (!searchCells.Any()) return NoPath; - using (var fromSrc = PathSearch.FromPoints(self.World, Mobile.Locomotor, self, searchCells, loc, true)) - using (var fromDest = PathSearch.FromPoint(self.World, Mobile.Locomotor, self, loc, lastVisibleTargetLocation, true).Reverse()) + using (var fromSrc = PathSearch.FromPoints(self.World, Mobile.Locomotor, self, searchCells, loc, check)) + using (var fromDest = PathSearch.FromPoint(self.World, Mobile.Locomotor, self, loc, lastVisibleTargetLocation, check).Reverse()) return pathFinder.FindBidiPath(fromSrc, fromDest); } diff --git a/OpenRA.Mods.Common/Activities/UnloadCargo.cs b/OpenRA.Mods.Common/Activities/UnloadCargo.cs index 09c68826a2..1b15b96b76 100644 --- a/OpenRA.Mods.Common/Activities/UnloadCargo.cs +++ b/OpenRA.Mods.Common/Activities/UnloadCargo.cs @@ -67,7 +67,7 @@ namespace OpenRA.Mods.Common.Activities // Find the cells that are blocked by transient actors return cargo.CurrentAdjacentCells - .Where(c => pos.CanEnterCell(c, null, true) != pos.CanEnterCell(c, null, false)); + .Where(c => pos.CanEnterCell(c, null, BlockedByActor.All) != pos.CanEnterCell(c, null, BlockedByActor.None)); } protected override void OnFirstRun(Actor self) diff --git a/OpenRA.Mods.Common/Pathfinder/PathFinderUnitPathCacheDecorator.cs b/OpenRA.Mods.Common/Pathfinder/PathFinderUnitPathCacheDecorator.cs index 2f26173503..6eff9763df 100644 --- a/OpenRA.Mods.Common/Pathfinder/PathFinderUnitPathCacheDecorator.cs +++ b/OpenRA.Mods.Common/Pathfinder/PathFinderUnitPathCacheDecorator.cs @@ -30,37 +30,47 @@ namespace OpenRA.Mods.Common.Pathfinder this.cacheStorage = cacheStorage; } - public List FindUnitPath(CPos source, CPos target, Actor self, Actor ignoreActor) + public List FindUnitPath(CPos source, CPos target, Actor self, Actor ignoreActor, BlockedByActor check) { using (new PerfSample("Pathfinder")) { var key = "FindUnitPath" + self.ActorID + source.X + source.Y + target.X + target.Y; - var cachedPath = cacheStorage.Retrieve(key); - if (cachedPath != null) - return cachedPath; + // Only cache path when transient actors are ignored, otherwise there is no guarantee that the path + // is still valid at the next check. + if (check == BlockedByActor.None) + { + var cachedPath = cacheStorage.Retrieve(key); + if (cachedPath != null) + return cachedPath; + } - var pb = pathFinder.FindUnitPath(source, target, self, ignoreActor); + var pb = pathFinder.FindUnitPath(source, target, self, ignoreActor, check); - cacheStorage.Store(key, pb); + if (check == BlockedByActor.None) + cacheStorage.Store(key, pb); return pb; } } - public List FindUnitPathToRange(CPos source, SubCell srcSub, WPos target, WDist range, Actor self) + public List FindUnitPathToRange(CPos source, SubCell srcSub, WPos target, WDist range, Actor self, BlockedByActor check) { using (new PerfSample("Pathfinder")) { var key = "FindUnitPathToRange" + self.ActorID + source.X + source.Y + target.X + target.Y; - var cachedPath = cacheStorage.Retrieve(key); - if (cachedPath != null) - return cachedPath; + if (check == BlockedByActor.None) + { + var cachedPath = cacheStorage.Retrieve(key); + if (cachedPath != null) + return cachedPath; + } - var pb = pathFinder.FindUnitPathToRange(source, srcSub, target, range, self); + var pb = pathFinder.FindUnitPathToRange(source, srcSub, target, range, self, check); - cacheStorage.Store(key, pb); + if (check == BlockedByActor.None) + cacheStorage.Store(key, pb); return pb; } diff --git a/OpenRA.Mods.Common/Pathfinder/PathGraph.cs b/OpenRA.Mods.Common/Pathfinder/PathGraph.cs index 384912b8e1..53859de3bf 100644 --- a/OpenRA.Mods.Common/Pathfinder/PathGraph.cs +++ b/OpenRA.Mods.Common/Pathfinder/PathGraph.cs @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.Linq; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; +using OpenRA.Traits; namespace OpenRA.Mods.Common.Pathfinder { @@ -82,7 +83,7 @@ namespace OpenRA.Mods.Common.Pathfinder public bool InReverse { get; set; } public Actor IgnoreActor { get; set; } - readonly CellConditions checkConditions; + readonly BlockedByActor checkConditions; readonly Locomotor locomotor; readonly LocomotorInfo.WorldMovementInfo worldMovementInfo; readonly CellInfoLayerPool.PooledCellInfoLayer pooledLayer; @@ -92,7 +93,7 @@ namespace OpenRA.Mods.Common.Pathfinder readonly Dictionary>> customLayerInfo = new Dictionary>>(); - public PathGraph(CellInfoLayerPool layerPool, Locomotor locomotor, Actor actor, World world, bool checkForBlocked) + public PathGraph(CellInfoLayerPool layerPool, Locomotor locomotor, Actor actor, World world, BlockedByActor check) { pooledLayer = layerPool.Get(); groundInfo = pooledLayer.GetLayer(); @@ -108,7 +109,7 @@ namespace OpenRA.Mods.Common.Pathfinder worldMovementInfo = locomotorInfo.GetWorldMovementInfo(world); Actor = actor; LaneBias = 1; - checkConditions = checkForBlocked ? CellConditions.TransientActors : CellConditions.None; + checkConditions = check; checkTerrainHeight = world.Map.Grid.MaximumTerrainHeight > 0; } @@ -172,8 +173,7 @@ namespace OpenRA.Mods.Common.Pathfinder int GetCostToNode(CPos destNode, CVec direction) { - var movementCost = locomotor.MovementCostToEnterCell(Actor, destNode, IgnoreActor, checkConditions); - + var movementCost = locomotor.MovementCostToEnterCell(Actor, destNode, checkConditions, IgnoreActor); if (movementCost != short.MaxValue && !(CustomBlock != null && CustomBlock(destNode))) return CalculateCellCost(destNode, direction, movementCost); diff --git a/OpenRA.Mods.Common/Pathfinder/PathSearch.cs b/OpenRA.Mods.Common/Pathfinder/PathSearch.cs index 3a6e55f704..782c126f8e 100644 --- a/OpenRA.Mods.Common/Pathfinder/PathSearch.cs +++ b/OpenRA.Mods.Common/Pathfinder/PathSearch.cs @@ -15,6 +15,7 @@ using System.Linq; using System.Runtime.CompilerServices; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; +using OpenRA.Traits; namespace OpenRA.Mods.Common.Pathfinder { @@ -45,18 +46,18 @@ namespace OpenRA.Mods.Common.Pathfinder considered = new LinkedList>(); } - public static IPathSearch Search(World world, Locomotor locomotor, Actor self, bool checkForBlocked, Func goalCondition) + public static IPathSearch Search(World world, Locomotor locomotor, Actor self, BlockedByActor check, Func goalCondition) { - var graph = new PathGraph(LayerPoolForWorld(world), locomotor, self, world, checkForBlocked); + var graph = new PathGraph(LayerPoolForWorld(world), locomotor, self, world, check); var search = new PathSearch(graph); search.isGoal = goalCondition; search.heuristic = loc => 0; return search; } - public static IPathSearch FromPoint(World world, Locomotor locomotor, Actor self, CPos @from, CPos target, bool checkForBlocked) + public static IPathSearch FromPoint(World world, Locomotor locomotor, Actor self, CPos @from, CPos target, BlockedByActor check) { - var graph = new PathGraph(LayerPoolForWorld(world), locomotor, self, world, checkForBlocked); + var graph = new PathGraph(LayerPoolForWorld(world), locomotor, self, world, check); var search = new PathSearch(graph) { heuristic = DefaultEstimator(target) @@ -74,9 +75,9 @@ namespace OpenRA.Mods.Common.Pathfinder return search; } - public static IPathSearch FromPoints(World world, Locomotor locomotor, Actor self, IEnumerable froms, CPos target, bool checkForBlocked) + public static IPathSearch FromPoints(World world, Locomotor locomotor, Actor self, IEnumerable froms, CPos target, BlockedByActor check) { - var graph = new PathGraph(LayerPoolForWorld(world), locomotor, self, world, checkForBlocked); + var graph = new PathGraph(LayerPoolForWorld(world), locomotor, self, world, check); var search = new PathSearch(graph) { heuristic = DefaultEstimator(target) diff --git a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs index eb59335dd6..663d40bea2 100644 --- a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs @@ -158,7 +158,7 @@ namespace OpenRA.Mods.Common.Traits bool IOccupySpaceInfo.SharesCell { get { return false; } } // Used to determine if an aircraft can spawn landed - 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, BlockedByActor check = BlockedByActor.All) { if (!world.Map.Contains(cell)) return false; @@ -170,7 +170,7 @@ namespace OpenRA.Mods.Common.Traits if (world.WorldActor.Trait().GetBuildingAt(cell) != null) return false; - if (!checkTransientActors) + if (check == BlockedByActor.None) return true; return !world.ActorMap.GetActorsAt(cell).Any(x => x != ignoreActor); @@ -721,9 +721,9 @@ namespace OpenRA.Mods.Common.Traits public bool CanExistInCell(CPos cell) { return true; } public bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any) { return false; } // TODO: Handle landing - public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true) { return true; } + public bool CanEnterCell(CPos cell, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { return true; } public SubCell GetValidSubCell(SubCell preferred) { return SubCell.Invalid; } - 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, BlockedByActor check = BlockedByActor.All) { // Does not use any subcell return SubCell.Invalid; diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs index 168d1bb7fb..334c82fded 100644 --- a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs +++ b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs @@ -231,7 +231,7 @@ namespace OpenRA.Mods.Common.Traits if (IsTraitDisabled) return false; - if (Info.AttackRequiresEnteringCell && (positionable == null || !positionable.CanEnterCell(t.Actor.Location, null, false))) + if (Info.AttackRequiresEnteringCell && (positionable == null || !positionable.CanEnterCell(t.Actor.Location, null, BlockedByActor.None))) return false; // PERF: Avoid LINQ. diff --git a/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs index 439568b52e..7ba3c88543 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs @@ -146,7 +146,7 @@ namespace OpenRA.Mods.Common.Traits claimLayer.CanClaimCell(actor, cell); var path = pathfinder.FindPath( - PathSearch.Search(world, harv.Locomotor, actor, true, isValidResource) + PathSearch.Search(world, harv.Locomotor, actor, BlockedByActor.Stationary, isValidResource) .WithCustomCost(loc => world.FindActorsInCircle(world.Map.CenterOfCell(loc), Info.HarvesterEnemyAvoidanceRadius) .Where(u => !u.IsDead && actor.Owner.Stances[u.Owner] == Stance.Enemy) .Sum(u => Math.Max(WDist.Zero.Length, Info.HarvesterEnemyAvoidanceRadius.Length - (world.Map.CenterOfCell(loc) - u.CenterPosition).Length))) diff --git a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoMobile.cs b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoMobile.cs index b5608a053c..d26295f4ef 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoMobile.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoMobile.cs @@ -198,7 +198,7 @@ namespace OpenRA.Mods.Common.Traits if (mobile.locomotor.MovementCostForCell(cell) == short.MaxValue) return false; - return mobile.locomotor.CanMoveFreelyInto(self, cell, null, CellConditions.BlockedByMovers); + return mobile.locomotor.CanMoveFreelyInto(self, cell, BlockedByActor.All, null); } } } diff --git a/OpenRA.Mods.Common/Traits/Cargo.cs b/OpenRA.Mods.Common/Traits/Cargo.cs index be4a538bcf..1b5eff592b 100644 --- a/OpenRA.Mods.Common/Traits/Cargo.cs +++ b/OpenRA.Mods.Common/Traits/Cargo.cs @@ -218,7 +218,7 @@ namespace OpenRA.Mods.Common.Traits return Util.AdjacentCells(self.World, Target.FromActor(self)).Where(c => self.Location != c); } - public bool CanUnload(bool immediate = false) + public bool CanUnload(BlockedByActor check = BlockedByActor.None) { if (checkTerrainType) { @@ -229,7 +229,7 @@ namespace OpenRA.Mods.Common.Traits } return !IsEmpty(self) && (aircraft == null || aircraft.CanLand(self.Location, blockedByMobile: false)) - && CurrentAdjacentCells != null && CurrentAdjacentCells.Any(c => Passengers.Any(p => !p.IsDead && p.Trait().CanEnterCell(c, null, immediate))); + && CurrentAdjacentCells != null && CurrentAdjacentCells.Any(c => Passengers.Any(p => !p.IsDead && p.Trait().CanEnterCell(c, null, check))); } public bool CanLoad(Actor self, Actor a) @@ -430,7 +430,7 @@ namespace OpenRA.Mods.Common.Traits void INotifyKilled.Killed(Actor self, AttackInfo e) { if (Info.EjectOnDeath) - while (!IsEmpty(self) && CanUnload(true)) + while (!IsEmpty(self) && CanUnload(BlockedByActor.All)) { var passenger = Unload(self); var cp = self.CenterPosition; @@ -438,7 +438,7 @@ namespace OpenRA.Mods.Common.Traits var positionable = passenger.Trait(); positionable.SetPosition(passenger, self.Location); - if (!inAir && positionable.CanEnterCell(self.Location, self, false)) + if (!inAir && positionable.CanEnterCell(self.Location, self, BlockedByActor.None)) { self.World.AddFrameEndTask(w => w.Add(passenger)); var nbms = passenger.TraitsImplementing(); diff --git a/OpenRA.Mods.Common/Traits/Crates/Crate.cs b/OpenRA.Mods.Common/Traits/Crates/Crate.cs index 97691e2474..21e147f083 100644 --- a/OpenRA.Mods.Common/Traits/Crates/Crate.cs +++ b/OpenRA.Mods.Common/Traits/Crates/Crate.cs @@ -40,9 +40,9 @@ namespace OpenRA.Mods.Common.Traits bool IOccupySpaceInfo.SharesCell { get { return false; } } - 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, BlockedByActor check = BlockedByActor.All) { - return GetAvailableSubCell(world, cell, ignoreActor, checkTransientActors) != SubCell.Invalid; + return GetAvailableSubCell(world, cell, ignoreActor, check) != SubCell.Invalid; } public bool CanExistInCell(World world, CPos cell) @@ -57,7 +57,7 @@ namespace OpenRA.Mods.Common.Traits return true; } - public SubCell GetAvailableSubCell(World world, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true) + public SubCell GetAvailableSubCell(World world, CPos cell, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { if (!CanExistInCell(world, cell)) return SubCell.Invalid; @@ -65,7 +65,7 @@ namespace OpenRA.Mods.Common.Traits if (world.WorldActor.Trait().GetBuildingAt(cell) != null) return SubCell.Invalid; - if (!checkTransientActors) + if (check == BlockedByActor.None) return SubCell.FullCell; return !world.ActorMap.GetActorsAt(cell).Any(x => x != ignoreActor) @@ -208,16 +208,16 @@ namespace OpenRA.Mods.Common.Traits public bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any) { return self.Location == location && ticks + 1 == info.Lifetime * 25; } public SubCell GetValidSubCell(SubCell preferred = SubCell.Any) { return SubCell.FullCell; } - public SubCell GetAvailableSubCell(CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true) + public SubCell GetAvailableSubCell(CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { - return info.GetAvailableSubCell(self.World, cell, ignoreActor, checkTransientActors); + return info.GetAvailableSubCell(self.World, cell, ignoreActor, check); } public bool CanExistInCell(CPos cell) { return info.CanExistInCell(self.World, cell); } - public bool CanEnterCell(CPos a, Actor ignoreActor = null, bool checkTransientActors = true) + public bool CanEnterCell(CPos a, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { - return GetAvailableSubCell(a, SubCell.Any, ignoreActor, checkTransientActors) != SubCell.Invalid; + return GetAvailableSubCell(a, SubCell.Any, ignoreActor, check) != SubCell.Invalid; } bool ICrushable.CrushableBy(Actor self, Actor crusher, BitSet crushClasses) diff --git a/OpenRA.Mods.Common/Traits/Harvester.cs b/OpenRA.Mods.Common/Traits/Harvester.cs index b15f161a0b..f42200e438 100644 --- a/OpenRA.Mods.Common/Traits/Harvester.cs +++ b/OpenRA.Mods.Common/Traits/Harvester.cs @@ -184,7 +184,7 @@ namespace OpenRA.Mods.Common.Traits // Start a search from each refinery's delivery location: List path; - using (var search = PathSearch.FromPoints(self.World, mobile.Locomotor, self, refs.Values.Select(r => r.Location), self.Location, false) + using (var search = PathSearch.FromPoints(self.World, mobile.Locomotor, self, refs.Values.Select(r => r.Location), self.Location, BlockedByActor.None) .WithCustomCost(loc => { if (!refs.ContainsKey(loc)) diff --git a/OpenRA.Mods.Common/Traits/Husk.cs b/OpenRA.Mods.Common/Traits/Husk.cs index 95a793fe0a..fb5df595ff 100644 --- a/OpenRA.Mods.Common/Traits/Husk.cs +++ b/OpenRA.Mods.Common/Traits/Husk.cs @@ -42,7 +42,7 @@ namespace OpenRA.Mods.Common.Traits bool IOccupySpaceInfo.SharesCell { get { return false; } } - 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, BlockedByActor check = BlockedByActor.All) { // IPositionable*Info*.CanEnterCell is only ever used for things like exiting production facilities, // all places relevant for husks check IPositionable.CanEnterCell instead, so we can safely set this to true. @@ -107,21 +107,21 @@ namespace OpenRA.Mods.Common.Traits public Pair[] OccupiedCells() { return new[] { Pair.New(TopLeft, SubCell.FullCell) }; } public bool IsLeavingCell(CPos location, SubCell subCell = SubCell.Any) { return false; } public SubCell GetValidSubCell(SubCell preferred = SubCell.Any) { return SubCell.FullCell; } - public SubCell GetAvailableSubCell(CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true) + public SubCell GetAvailableSubCell(CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { if (!CanExistInCell(cell)) return SubCell.Invalid; - if (!checkTransientActors) + if (check == BlockedByActor.None) return SubCell.FullCell; return self.World.ActorMap.GetActorsAt(cell) .All(x => x == ignoreActor) ? SubCell.FullCell : SubCell.Invalid; } - public bool CanEnterCell(CPos a, Actor ignoreActor = null, bool checkTransientActors = true) + public bool CanEnterCell(CPos a, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { - return GetAvailableSubCell(a, SubCell.Any, ignoreActor, checkTransientActors) != SubCell.Invalid; + return GetAvailableSubCell(a, SubCell.Any, ignoreActor, check) != SubCell.Invalid; } public void SetPosition(Actor self, CPos cell, SubCell subCell = SubCell.Any) { SetPosition(self, self.World.Map.CenterOfCell(cell)); } diff --git a/OpenRA.Mods.Common/Traits/Mobile.cs b/OpenRA.Mods.Common/Traits/Mobile.cs index ab1e94ff75..f7c7aaa163 100644 --- a/OpenRA.Mods.Common/Traits/Mobile.cs +++ b/OpenRA.Mods.Common/Traits/Mobile.cs @@ -82,7 +82,7 @@ namespace OpenRA.Mods.Common.Traits // 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, BlockedByActor check = BlockedByActor.All) { // PERF: Avoid repeated trait queries on the hot path if (locomotor == null) @@ -92,8 +92,7 @@ namespace OpenRA.Mods.Common.Traits if (locomotor.MovementCostForCell(cell) == short.MaxValue) return false; - var check = checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers; - return locomotor.CanMoveFreelyInto(self, cell, ignoreActor, check); + return locomotor.CanMoveFreelyInto(self, cell, check, ignoreActor); } public IReadOnlyDictionary OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any) @@ -176,6 +175,8 @@ namespace OpenRA.Mods.Common.Traits INotifyFinishedMoving[] notifyFinishedMoving; IWrapMove[] moveWrappers; bool requireForceMove; + public bool TurnToMove; + public bool IsBlocking { get; private set; } #region IFacing [Sync] @@ -298,15 +299,6 @@ namespace OpenRA.Mods.Common.Traits if (IsTraitDisabled || IsTraitPaused) return; - // Initial fairly braindead implementation. - // don't allow ourselves to be pushed around by the enemy! - if (!force && self.Owner.Stances[nudger.Owner] != Stance.Ally) - return; - - // Don't nudge if we're busy doing something! - if (!force && !self.IsIdle) - return; - // Pick an adjacent available cell. var availCells = new List(); var notStupidCells = new List(); @@ -358,6 +350,17 @@ namespace OpenRA.Mods.Common.Traits } } + public bool IsLeaving() + { + if (CurrentMovementTypes.HasFlag(MovementType.Horizontal)) + return true; + + if (CurrentMovementTypes.HasFlag(MovementType.Turn)) + return TurnToMove; + + return false; + } + public bool CanInteractWithGroundLayer(Actor self) { // TODO: Think about extending this to support arbitrary layer-layer checks @@ -442,10 +445,9 @@ namespace OpenRA.Mods.Common.Traits && (subCell == SubCell.Any || FromSubCell == subCell || subCell == SubCell.FullCell || FromSubCell == SubCell.FullCell); } - 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, BlockedByActor check = BlockedByActor.All) { - var cellConditions = checkTransientActors ? CellConditions.All : CellConditions.None; - return Locomotor.GetAvailableSubCell(self, a, preferredSubCell, ignoreActor, cellConditions); + return Locomotor.GetAvailableSubCell(self, a, check, preferredSubCell, ignoreActor); } public bool CanExistInCell(CPos cell) @@ -453,9 +455,9 @@ namespace OpenRA.Mods.Common.Traits return Locomotor.MovementCostForCell(cell) != short.MaxValue; } - public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true) + public bool CanEnterCell(CPos cell, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { - return Info.CanEnterCell(self.World, self, cell, ignoreActor, checkTransientActors); + return Info.CanEnterCell(self.World, self, cell, ignoreActor, check); } #endregion @@ -474,6 +476,7 @@ namespace OpenRA.Mods.Common.Traits FromSubCell = fromSub; ToSubCell = toSub; AddInfluence(); + IsBlocking = false; // Most custom layer conditions are added/removed when starting the transition between layers. if (toCell.Layer != fromCell.Layer) @@ -737,7 +740,7 @@ namespace OpenRA.Mods.Common.Traits } public Activity ScriptedMove(CPos cell) { return new Move(self, cell); } - public Activity MoveTo(Func> pathFunc) { return new Move(self, pathFunc); } + public Activity MoveTo(Func> pathFunc) { return new Move(self, pathFunc); } Activity VisualMove(Actor self, WPos fromPos, WPos toPos, CPos cell) { @@ -758,7 +761,7 @@ namespace OpenRA.Mods.Common.Traits var pathFinder = self.World.WorldActor.Trait(); List path; - using (var search = PathSearch.Search(self.World, Locomotor, self, true, + using (var search = PathSearch.Search(self.World, Locomotor, self, BlockedByActor.All, loc => loc.Layer == 0 && CanEnterCell(loc)) .FromPoint(self.Location)) path = pathFinder.FindPath(search); @@ -782,7 +785,7 @@ namespace OpenRA.Mods.Common.Traits init.Add(new FacingInit(facing)); // Allows the husk to drag to its final position - if (CanEnterCell(self.Location, self, false)) + if (CanEnterCell(self.Location, self, BlockedByActor.Stationary)) init.Add(new HuskSpeedInit(MovementSpeedForCell(self, self.Location))); } @@ -804,8 +807,16 @@ namespace OpenRA.Mods.Common.Traits void INotifyBlockingMove.OnNotifyBlockingMove(Actor self, Actor blocking) { - if (self.IsIdle && self.AppearsFriendlyTo(blocking)) + if (!self.AppearsFriendlyTo(blocking)) + return; + + if (self.IsIdle) + { Nudge(self, blocking, true); + return; + } + + IsBlocking = true; } public override IEnumerable GetVariableObservers() diff --git a/OpenRA.Mods.Common/Traits/World/Locomotor.cs b/OpenRA.Mods.Common/Traits/World/Locomotor.cs index d1e734872b..392fd88d47 100644 --- a/OpenRA.Mods.Common/Traits/World/Locomotor.cs +++ b/OpenRA.Mods.Common/Traits/World/Locomotor.cs @@ -19,33 +19,19 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { - [Flags] - public enum CellConditions - { - None = 0, - TransientActors, - BlockedByMovers, - All = TransientActors | BlockedByMovers - } - [Flags] public enum CellFlag : byte { HasFreeSpace = 0, - HasActor = 1, - HasMovingActor = 2, - HasCrushableActor = 4, - HasTemporaryBlocker = 8 + HasMovingActor = 1, + HasStationaryActor = 2, + HasMovableActor = 4, + HasCrushableActor = 8, + HasTemporaryBlocker = 16 } 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 HasCellFlag(this CellFlag c, CellFlag cellFlag) { // PERF: Enum.HasFlag is slower and requires allocations. @@ -204,13 +190,13 @@ namespace OpenRA.Mods.Common.Traits { struct CellCache { - public readonly LongBitSet Blocking; + public readonly LongBitSet Immovable; public readonly LongBitSet Crushable; public readonly CellFlag CellFlag; - public CellCache(LongBitSet blocking, CellFlag cellFlag, LongBitSet crushable = default(LongBitSet)) + public CellCache(LongBitSet immovable, CellFlag cellFlag, LongBitSet crushable = default(LongBitSet)) { - Blocking = blocking; + Immovable = immovable; Crushable = crushable; CellFlag = cellFlag; } @@ -244,7 +230,7 @@ namespace OpenRA.Mods.Common.Traits return cell.Layer == 0 ? cellsCost[cell] : customLayerCellsCost[cell.Layer][cell]; } - public short MovementCostToEnterCell(Actor actor, CPos destNode, Actor ignoreActor, CellConditions check) + public short MovementCostToEnterCell(Actor actor, CPos destNode, BlockedByActor check, Actor ignoreActor) { if (!world.Map.Contains(destNode)) return short.MaxValue; @@ -252,19 +238,20 @@ namespace OpenRA.Mods.Common.Traits var cellCost = destNode.Layer == 0 ? cellsCost[destNode] : customLayerCellsCost[destNode.Layer][destNode]; if (cellCost == short.MaxValue || - !CanMoveFreelyInto(actor, destNode, ignoreActor, check)) + !CanMoveFreelyInto(actor, destNode, check, ignoreActor)) return short.MaxValue; return cellCost; } // 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, BlockedByActor check, Actor ignoreActor) { var cellCache = GetCache(cell); var cellFlag = cellCache.CellFlag; - if (!check.HasCellCondition(CellConditions.TransientActors)) + // If the check allows: We are not blocked by transient actors. + if (check == BlockedByActor.None) return true; // No actor in the cell or free SubCell. @@ -281,14 +268,31 @@ namespace OpenRA.Mods.Common.Traits if (cellCache.Crushable.Overlaps(actor.Owner.PlayerMask)) return true; + // If the check allows: We are not blocked by moving units. + if (check <= BlockedByActor.Stationary && !cellFlag.HasCellFlag(CellFlag.HasStationaryActor)) + return true; + + // If the check allows: We are not blocked by units that we can force to move out of the way. + if (check <= BlockedByActor.Immovable && !cellCache.Immovable.Overlaps(actor.Owner.PlayerMask)) + return true; + // Cache doesn't account for ignored actors or temporary blockers - these must use the slow path. if (ignoreActor == null && !cellFlag.HasCellFlag(CellFlag.HasTemporaryBlocker)) { - // We are blocked by another actor in the cell. - if (cellCache.Blocking.Overlaps(actor.Owner.PlayerMask)) + // We already know there are uncrushable actors in the cell so we are always blocked. + if (check == BlockedByActor.All) return false; - if (check == CellConditions.BlockedByMovers && cellFlag < CellFlag.HasCrushableActor) + // We already know there are either immovable or stationary actors which the check does not allow. + if (!cellFlag.HasCellFlag(CellFlag.HasCrushableActor)) + return false; + + // All actors in the cell are immovable and some cannot be crushed. + if (!cellFlag.HasCellFlag(CellFlag.HasMovableActor)) + return false; + + // All actors in the cell are stationary and some cannot be crushed. + if (check == BlockedByActor.Stationary && !cellFlag.HasCellFlag(CellFlag.HasMovingActor)) return false; } @@ -299,12 +303,12 @@ namespace OpenRA.Mods.Common.Traits return true; } - 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, BlockedByActor check, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null) { if (MovementCostForCell(cell) == short.MaxValue) return SubCell.Invalid; - if (check.HasCellCondition(CellConditions.TransientActors)) + if (check > BlockedByActor.None) { Func checkTransient = otherActor => IsBlockedBy(self, otherActor, ignoreActor, check, GetCache(cell).CellFlag); @@ -320,15 +324,20 @@ namespace OpenRA.Mods.Common.Traits return world.ActorMap.FreeSubCell(cell, preferredSubCell); } - bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor, CellConditions check, CellFlag cellFlag) + bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor, BlockedByActor check, CellFlag cellFlag) { if (otherActor == ignoreActor) return false; - // If the check allows: we are not blocked by allied units moving in our direction. - if (!check.HasCellCondition(CellConditions.BlockedByMovers) && cellFlag.HasCellFlag(CellFlag.HasMovingActor) && + // If the check allows: We are not blocked by units that we can force to move out of the way. + if (check <= BlockedByActor.Immovable && cellFlag.HasCellFlag(CellFlag.HasMovableActor) && self.Owner.Stances[otherActor.Owner] == Stance.Ally && - IsMovingInMyDirection(self, otherActor)) + otherActor.TraitOrDefault() != null) + return false; + + // If the check allows: we are not blocked by moving units. + if (check <= BlockedByActor.Stationary && cellFlag.HasCellFlag(CellFlag.HasMovingActor) && + IsMoving(self, otherActor)) return false; if (cellFlag.HasCellFlag(CellFlag.HasTemporaryBlocker)) @@ -339,11 +348,8 @@ namespace OpenRA.Mods.Common.Traits return false; } - if (!cellFlag.HasCellFlag(CellFlag.HasCrushableActor)) - return true; - // If we cannot crush the other actor in our way, we are blocked. - if (Info.Crushes.IsEmpty) + if (!cellFlag.HasCellFlag(CellFlag.HasCrushableActor) || Info.Crushes.IsEmpty) return true; // If the other actor in our way cannot be crushed, we are blocked. @@ -356,7 +362,7 @@ namespace OpenRA.Mods.Common.Traits return true; } - static bool IsMovingInMyDirection(Actor self, Actor other) + static bool IsMoving(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; @@ -368,9 +374,7 @@ namespace OpenRA.Mods.Common.Traits 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; + return true; } public void WorldLoaded(World w, WorldRenderer wr) @@ -456,31 +460,32 @@ namespace OpenRA.Mods.Common.Traits var cache = cell.Layer == 0 ? blockingCache : customLayerBlockingCache[cell.Layer]; var actors = actorMap.GetActorsAt(cell); + var cellFlag = CellFlag.HasFreeSpace; if (!actors.Any()) { - cache[cell] = new CellCache(default(LongBitSet), CellFlag.HasFreeSpace); + cache[cell] = new CellCache(default(LongBitSet), cellFlag); return; } if (sharesCell && actorMap.HasFreeSubCell(cell)) { - cache[cell] = new CellCache(default(LongBitSet), CellFlag.HasFreeSpace); + cache[cell] = new CellCache(default(LongBitSet), cellFlag); return; } - var cellFlag = CellFlag.HasActor; - var cellBlockedPlayers = default(LongBitSet); + var cellImmovablePlayers = default(LongBitSet); var cellCrushablePlayers = world.AllPlayersMask; foreach (var actor in actors) { - var actorBlocksPlayers = world.AllPlayersMask; + var actorImmovablePlayers = world.AllPlayersMask; var actorCrushablePlayers = world.NoPlayersMask; var crushables = actor.TraitsImplementing(); var mobile = actor.OccupiesSpace as Mobile; - var isMoving = mobile != null && mobile.CurrentMovementTypes.HasMovementType(MovementType.Horizontal); + var isMovable = mobile != null; + var isMoving = isMovable && mobile.CurrentMovementTypes.HasMovementType(MovementType.Horizontal); if (crushables.Any()) { @@ -490,9 +495,14 @@ namespace OpenRA.Mods.Common.Traits } if (isMoving) - { - actorBlocksPlayers = actorBlocksPlayers.Except(actor.Owner.AlliedPlayersMask); cellFlag |= CellFlag.HasMovingActor; + else + cellFlag |= CellFlag.HasStationaryActor; + + if (isMovable) + { + cellFlag |= CellFlag.HasMovableActor; + actorImmovablePlayers = actorImmovablePlayers.Except(actor.Owner.AlliedPlayersMask); } // PERF: Only perform ITemporaryBlocker trait look-up if mod/map rules contain any actors that are temporary blockers @@ -504,10 +514,10 @@ namespace OpenRA.Mods.Common.Traits } cellCrushablePlayers = cellCrushablePlayers.Intersect(actorCrushablePlayers); - cellBlockedPlayers = cellBlockedPlayers.Union(actorBlocksPlayers); + cellImmovablePlayers = cellImmovablePlayers.Union(actorImmovablePlayers); } - cache[cell] = new CellCache(cellBlockedPlayers, cellFlag, cellCrushablePlayers); + cache[cell] = new CellCache(cellImmovablePlayers, cellFlag, cellCrushablePlayers); } } } diff --git a/OpenRA.Mods.Common/Traits/World/PathFinder.cs b/OpenRA.Mods.Common/Traits/World/PathFinder.cs index 92b2c538d8..bdac120af3 100644 --- a/OpenRA.Mods.Common/Traits/World/PathFinder.cs +++ b/OpenRA.Mods.Common/Traits/World/PathFinder.cs @@ -33,9 +33,9 @@ namespace OpenRA.Mods.Common.Traits /// Calculates a path for the actor from source to destination /// /// A path from start to target - List FindUnitPath(CPos source, CPos target, Actor self, Actor ignoreActor); + List FindUnitPath(CPos source, CPos target, Actor self, Actor ignoreActor, BlockedByActor check); - List FindUnitPathToRange(CPos source, SubCell srcSub, WPos target, WDist range, Actor self); + List FindUnitPathToRange(CPos source, SubCell srcSub, WPos target, WDist range, Actor self, BlockedByActor check); /// /// Calculates a path given a search specification @@ -62,7 +62,7 @@ namespace OpenRA.Mods.Common.Traits this.world = world; } - public List FindUnitPath(CPos source, CPos target, Actor self, Actor ignoreActor) + public List FindUnitPath(CPos source, CPos target, Actor self, Actor ignoreActor, BlockedByActor check) { var mobile = self.Trait(); var locomotor = mobile.Locomotor; @@ -78,19 +78,23 @@ namespace OpenRA.Mods.Common.Traits return EmptyPath; var distance = source - target; - if (source.Layer == target.Layer && distance.LengthSquared < 3 && locomotor.CanMoveFreelyInto(self, target, null, CellConditions.All)) + var canMoveFreely = locomotor.CanMoveFreelyInto(self, target, check, null); + if (distance.LengthSquared < 3 && !canMoveFreely) + return new List { }; + + if (source.Layer == target.Layer && distance.LengthSquared < 3 && canMoveFreely) return new List { target }; List pb; - using (var fromSrc = PathSearch.FromPoint(world, locomotor, self, target, source, true).WithIgnoredActor(ignoreActor)) - using (var fromDest = PathSearch.FromPoint(world, locomotor, self, source, target, true).WithIgnoredActor(ignoreActor).Reverse()) + using (var fromSrc = PathSearch.FromPoint(world, locomotor, self, target, source, check).WithIgnoredActor(ignoreActor)) + using (var fromDest = PathSearch.FromPoint(world, locomotor, self, source, target, check).WithIgnoredActor(ignoreActor).Reverse()) pb = FindBidiPath(fromSrc, fromDest); return pb; } - public List FindUnitPathToRange(CPos source, SubCell srcSub, WPos target, WDist range, Actor self) + public List FindUnitPathToRange(CPos source, SubCell srcSub, WPos target, WDist range, Actor self, BlockedByActor check) { if (!cached) { @@ -123,8 +127,8 @@ namespace OpenRA.Mods.Common.Traits var locomotor = mobile.Locomotor; - using (var fromSrc = PathSearch.FromPoints(world, locomotor, self, tilesInRange, source, true)) - using (var fromDest = PathSearch.FromPoint(world, locomotor, self, source, targetCell, true).Reverse()) + using (var fromSrc = PathSearch.FromPoints(world, locomotor, self, tilesInRange, source, check)) + using (var fromDest = PathSearch.FromPoint(world, locomotor, self, source, targetCell, check).Reverse()) return FindBidiPath(fromSrc, fromDest); } diff --git a/OpenRA.Mods.D2k/Activities/SwallowActor.cs b/OpenRA.Mods.D2k/Activities/SwallowActor.cs index c49fea3750..c2c0016891 100644 --- a/OpenRA.Mods.D2k/Activities/SwallowActor.cs +++ b/OpenRA.Mods.D2k/Activities/SwallowActor.cs @@ -121,7 +121,7 @@ namespace OpenRA.Mods.D2k.Activities } // The target reached solid ground - if (!positionable.CanEnterCell(targetLocation, null, false)) + if (!positionable.CanEnterCell(targetLocation, null, BlockedByActor.None)) { RevokeCondition(self); return true; diff --git a/OpenRA.Mods.D2k/Traits/Sandworm.cs b/OpenRA.Mods.D2k/Traits/Sandworm.cs index 38f67bdd21..93d7119446 100644 --- a/OpenRA.Mods.D2k/Traits/Sandworm.cs +++ b/OpenRA.Mods.D2k/Traits/Sandworm.cs @@ -98,7 +98,7 @@ namespace OpenRA.Mods.D2k.Traits if (!a.Info.HasTraitInfo()) return false; - return mobile.CanEnterCell(a.Location, null, false); + return mobile.CanEnterCell(a.Location, null, BlockedByActor.None); }; var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, WormInfo.MaxSearchRadius) @@ -112,7 +112,7 @@ namespace OpenRA.Mods.D2k.Traits var moveTo = self.World.Map.CellContaining(self.CenterPosition + noiseDirection); - while (!self.World.Map.Contains(moveTo) || !mobile.CanEnterCell(moveTo, null, false)) + while (!self.World.Map.Contains(moveTo) || !mobile.CanEnterCell(moveTo, null, BlockedByActor.None)) { // without this check, this while can be infinity loop if (moveTo == self.Location)