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 > BlockedByActor.None) { Func <Actor, bool> checkTransient = otherActor => IsBlockedBy(self, otherActor, ignoreActor, cell, check, GetCache(cell).CellFlag); if (!sharesCell) { return(world.ActorMap.AnyActorsAt(cell, SubCell.FullCell, checkTransient) ? SubCell.Invalid : SubCell.FullCell); } return(world.ActorMap.FreeSubCell(cell, preferredSubCell, checkTransient)); } if (!sharesCell) { return(world.ActorMap.AnyActorsAt(cell, SubCell.FullCell) ? SubCell.Invalid : SubCell.FullCell); } return(world.ActorMap.FreeSubCell(cell, preferredSubCell)); }
public PathGraph(CellInfoLayerPool layerPool, Locomotor locomotor, Actor actor, World world, BlockedByActor check, Func <CPos, int> customCost, Actor ignoreActor, bool inReverse, bool laneBias) { customMovementLayers = world.GetCustomMovementLayers(); customMovementLayersEnabledForLocomotor = customMovementLayers.Count(cml => cml != null && cml.EnabledForLocomotor(locomotor.Info)); this.locomotor = locomotor; this.world = world; this.actor = actor; this.check = check; this.customCost = customCost; this.ignoreActor = ignoreActor; this.inReverse = inReverse; this.laneBias = laneBias; checkTerrainHeight = world.Map.Grid.MaximumTerrainHeight > 0; // As we support a search over the whole map area, // use the pool to grab the CellInfos we need to track the graph state. // This allows us to avoid the cost of allocating large arrays constantly. // PERF: Avoid LINQ pooledLayer = layerPool.Get(); cellInfoForLayer = new CellLayer <CellInfo> [customMovementLayers.Length]; cellInfoForLayer[0] = pooledLayer.GetLayer(); foreach (var cml in customMovementLayers) { if (cml != null && cml.EnabledForLocomotor(locomotor.Info)) { cellInfoForLayer[cml.Index] = pooledLayer.GetLayer(); } } }
public PathGraph(CellInfoLayerPool layerPool, Locomotor locomotor, Actor actor, World world, BlockedByActor check) { this.locomotor = locomotor; // As we support a search over the whole map area, // use the pool to grab the CellInfos we need to track the graph state. // This allows us to avoid the cost of allocating large arrays constantly. // PERF: Avoid LINQ var cmls = world.GetCustomMovementLayers(); pooledLayer = layerPool.Get(); cellInfoForLayer = new CellLayer <CellInfo> [cmls.Length]; cellInfoForLayer[0] = pooledLayer.GetLayer(); foreach (var cml in cmls) { if (cml != null && cml.EnabledForLocomotor(locomotor.Info)) { cellInfoForLayer[cml.Index] = pooledLayer.GetLayer(); } } World = world; Actor = actor; LaneBias = 1; checkConditions = check; checkTerrainHeight = world.Map.Grid.MaximumTerrainHeight > 0; }
List <CPos> EvalPath(BlockedByActor check) { var path = getPath(check).TakeWhile(a => a != mobile.ToCell).ToList(); mobile.PathHash = HashList(path); return(path); }
List <CPos> CalculatePathToTarget(Actor self, BlockedByActor check) { var loc = self.Location; // PERF: Assume that CandidateMovementCells doesn't change within a tick to avoid repeated queries // when Move enumerates different BlockedByActor values if (searchCellsTick != self.World.WorldTick) { searchCells.Clear(); searchCellsTick = self.World.WorldTick; foreach (var cell in CandidateMovementCells(self)) { if (domainIndex.IsPassable(loc, cell, Mobile.Info.LocomotorInfo) && Mobile.CanEnterCell(cell)) { searchCells.Add(cell); } } } if (!searchCells.Any()) { return(NoPath); } 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)); }
public static PathSearch ToTargetCell( World world, Locomotor locomotor, Actor self, CPos from, CPos target, BlockedByActor check, Func <CPos, int> customCost = null, Actor ignoreActor = null, bool inReverse = false, bool laneBias = DefaultLaneBias) { return(ToTargetCell(world, locomotor, self, new[] { from }, target, check, customCost, ignoreActor, inReverse, laneBias)); }
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 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) { var mobile = otherActor.TraitOrDefault <Mobile>(); if (mobile != null && !mobile.IsTraitDisabled && !mobile.IsTraitPaused && !mobile.IsImmovable) { 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)) { // If there is a temporary blocker in our path, but we can remove it, we are not blocked. var temporaryBlocker = otherActor.TraitOrDefault <ITemporaryBlocker>(); if (temporaryBlocker != null && temporaryBlocker.CanRemoveBlockage(otherActor, self)) { return(false); } } // If we cannot crush the other actor in our way, we are blocked. if (!cellFlag.HasCellFlag(CellFlag.HasCrushableActor) || Info.Crushes.IsEmpty) { return(true); } // If the other actor in our way cannot be crushed, we are blocked. // PERF: Avoid LINQ. var crushables = otherActor.TraitsImplementing <ICrushable>(); foreach (var crushable in crushables) { if (crushable.CrushableBy(otherActor, self, Info.Crushes)) { return(false); } } return(true); }
/// <summary> /// Calculates a path for the actor from multiple possible sources to target. /// Returned path is *reversed* and given target to source. /// The shortest path between a source and the target is returned. /// </summary> /// <remarks> /// Searches that provide a multiple source cells are slower than those than provide only a single source cell, /// as optimizations are possible for the single source case. Use searches from multiple source cells /// sparingly. /// </remarks> public List <CPos> FindUnitPathToTargetCell( Actor self, IEnumerable <CPos> sources, CPos target, BlockedByActor check, Func <CPos, int> customCost = null, Actor ignoreActor = null, bool laneBias = true) { var sourcesList = sources.ToList(); if (sourcesList.Count == 0) { return(NoPath); } var locomotor = GetLocomotor(self); // If the target cell is inaccessible, bail early. var inaccessible = !locomotor.CanMoveFreelyInto(self, target, check, ignoreActor) || (!(customCost is null) && customCost(target) == PathGraph.PathCostForInvalidPath); if (inaccessible) { return(NoPath); } // When searching from only one source cell, some optimizations are possible. if (sourcesList.Count == 1) { var source = sourcesList[0]; // For adjacent cells on the same layer, we can return the path without invoking a full search. if (source.Layer == target.Layer && (source - target).LengthSquared < 3) { return new List <CPos>(2) { target, source } } ; // With one starting point, we can use a bidirectional search. using (var fromTarget = PathSearch.ToTargetCell( world, locomotor, self, new[] { target }, source, check, ignoreActor: ignoreActor)) using (var fromSource = PathSearch.ToTargetCell( world, locomotor, self, new[] { source }, target, check, ignoreActor: ignoreActor, inReverse: true)) return(PathSearch.FindBidiPath(fromTarget, fromSource)); } // With multiple starting points, we can only use a unidirectional search. using (var search = PathSearch.ToTargetCell( world, locomotor, self, sourcesList, target, check, customCost, ignoreActor, laneBias)) return(search.FindPath()); }
protected DensePathGraph(Locomotor locomotor, Actor actor, World world, BlockedByActor check, Func <CPos, int> customCost, Actor ignoreActor, bool laneBias, bool inReverse) { CustomMovementLayers = world.GetCustomMovementLayers(); customMovementLayersEnabledForLocomotor = CustomMovementLayers.Count(cml => cml != null && cml.EnabledForLocomotor(locomotor.Info)); this.locomotor = locomotor; this.world = world; this.actor = actor; this.check = check; this.customCost = customCost; this.ignoreActor = ignoreActor; this.laneBias = laneBias; this.inReverse = inReverse; checkTerrainHeight = world.Map.Grid.MaximumTerrainHeight > 0; }
public bool CanUnload(BlockedByActor check = BlockedByActor.None) { if (checkTerrainType) { var terrainType = self.World.Map.GetTerrainInfo(self.Location).Type; if (!Info.UnloadTerrainTypes.Contains(terrainType)) { return(false); } } return(!IsEmpty(self) && (aircraft == null || aircraft.CanLand(self.Location, blockedByMobile: false)) && CurrentAdjacentCells != null && CurrentAdjacentCells.Any(c => Passengers.Any(p => !p.IsDead && p.Trait <IPositionable>().CanEnterCell(c, null, check)))); }
public short MovementCostToEnterCell(Actor actor, CPos destNode, BlockedByActor check, Actor ignoreActor) { if (!world.Map.Contains(destNode)) { return(short.MaxValue); } var cellCost = destNode.Layer == 0 ? cellsCost[destNode] : customLayerCellsCost[destNode.Layer][destNode]; if (cellCost == short.MaxValue || !CanMoveFreelyInto(actor, destNode, check, ignoreActor)) { return(short.MaxValue); } return(cellCost); }
public PathGraph(CellInfoLayerPool layerPool, Locomotor locomotor, Actor actor, World world, BlockedByActor check) { pooledLayer = layerPool.Get(); groundInfo = pooledLayer.GetLayer(); var locomotorInfo = locomotor.Info; this.locomotor = locomotor; var layers = world.GetCustomMovementLayers().Values .Where(cml => cml.EnabledForActor(actor.Info, locomotorInfo)); foreach (var cml in layers) { customLayerInfo[cml.Index] = Pair.New(cml, pooledLayer.GetLayer()); } World = world; worldMovementInfo = locomotorInfo.GetWorldMovementInfo(world); Actor = actor; LaneBias = 1; checkConditions = check; checkTerrainHeight = world.Map.Grid.MaximumTerrainHeight > 0; }
List <CPos> CalculatePathToTarget(Actor self, BlockedByActor check) { var targetCells = CandidateMovementCells(self); var searchCells = new List <CPos>(); var loc = self.Location; foreach (var cell in targetCells) { if (domainIndex.IsPassable(loc, cell, Mobile.Info.LocomotorInfo) && Mobile.CanEnterCell(cell)) { searchCells.Add(cell); } } if (!searchCells.Any()) { return(NoPath); } 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)); }
public static IPathSearch Search(World world, Locomotor locomotor, Actor self, BlockedByActor check, Func <CPos, bool> goalCondition) { 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 bool CanMoveFreelyInto(Actor actor, CPos cell, SubCell subCell, BlockedByActor check, Actor ignoreActor) { var cellCache = GetCache(cell); var cellFlag = cellCache.CellFlag; // 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. if (cellFlag == CellFlag.HasFreeSpace) { return(true); } // If actor is null we're just checking what would happen theoretically. // In such a scenario - we'll just assume any other actor in the cell will block us by default. // If we have a real actor, we can then perform the extra checks that allow us to avoid being blocked. if (actor == null) { return(false); } // All actors that may be in the cell can be crushed. 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, temporary blockers, or subcells - these must use the slow path. if (ignoreActor == null && !cellFlag.HasCellFlag(CellFlag.HasTemporaryBlocker) && subCell == SubCell.FullCell) { // We already know there are uncrushable actors in the cell so we are always blocked. if (check == BlockedByActor.All) { return(false); } // 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); } } var otherActors = subCell == SubCell.FullCell ? world.ActorMap.GetActorsAt(cell) : world.ActorMap.GetActorsAt(cell, subCell); foreach (var otherActor in otherActors) { if (IsBlockedBy(actor, otherActor, ignoreActor, cell, check, cellFlag)) { return(false); } } return(true); }
// Determines whether the actor is blocked by other Actors public bool CanMoveFreelyInto(Actor actor, CPos cell, BlockedByActor check, Actor ignoreActor) { return(CanMoveFreelyInto(actor, cell, SubCell.FullCell, check, ignoreActor)); }
public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { // Does not use any subcell return(SubCell.Invalid); }
/// <summary> /// Note: If the target <paramref name="cell"/> has any free subcell, the value of <paramref name="subCell"/> is ignored. /// </summary> public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { // PERF: Avoid repeated trait queries on the hot path if (locomotor == null) { locomotor = world.WorldActor.TraitsImplementing <Locomotor>() .SingleOrDefault(l => l.Info.Name == Locomotor); } if (locomotor.MovementCostForCell(cell) == PathGraph.MovementCostForUnreachableCell) { return(false); } return(locomotor.CanMoveFreelyInto(self, cell, subCell, check, ignoreActor)); }
public static IPathSearch FromPoints(World world, Locomotor locomotor, Actor self, IEnumerable <CPos> froms, CPos target, BlockedByActor check) { var graph = new PathGraph(LayerPoolForWorld(world), locomotor, self, world, check); var search = new PathSearch(graph); search.heuristic = search.DefaultEstimator(target); // The search will aim for the shortest path by default, a weight of 100%. // We can allow the search to find paths that aren't optimal by changing the weight. // We provide a weight that limits the worst case length of the path, // e.g. a weight of 110% will find a path no more than 10% longer than the shortest possible. // The benefit of allowing the search to return suboptimal paths is faster computation time. // The search can skip some areas of the search space, meaning it has less work to do. // We allow paths up to 25% longer than the shortest, optimal path, to improve pathfinding time. search.heuristicWeightPercentage = 125; search.isGoal = loc => { var locInfo = search.Graph[loc]; return(locInfo.EstimatedTotal - locInfo.CostSoFar == 0); }; foreach (var sl in froms) { if (world.Map.Contains(sl)) { search.AddInitialCell(sl); } } return(search); }
bool IsBlockedBy(Actor actor, Actor otherActor, Actor ignoreActor, CPos cell, BlockedByActor check, CellFlag cellFlag) { if (otherActor == ignoreActor) { return(false); } // 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) && actor.Owner.RelationshipWith(otherActor.Owner) == PlayerRelationship.Ally) { var mobile = otherActor.OccupiesSpace as Mobile; if (mobile != null && !mobile.IsTraitDisabled && !mobile.IsTraitPaused && !mobile.IsImmovable) { return(false); } } // If the check allows: we are not blocked by moving units. if (check <= BlockedByActor.Stationary && cellFlag.HasCellFlag(CellFlag.HasMovingActor) && IsMoving(actor, otherActor)) { return(false); } if (cellFlag.HasCellFlag(CellFlag.HasTemporaryBlocker)) { // If there is a temporary blocker in our path, but we can remove it, we are not blocked. var temporaryBlocker = otherActor.TraitOrDefault <ITemporaryBlocker>(); if (temporaryBlocker != null && temporaryBlocker.CanRemoveBlockage(otherActor, actor)) { return(false); } } if (cellFlag.HasCellFlag(CellFlag.HasTransitOnlyActor)) { // Transit only tiles should not block movement var building = otherActor.OccupiesSpace as Building; if (building != null && building.TransitOnlyCells().Contains(cell)) { return(false); } } // If we cannot crush the other actor in our way, we are blocked. if (!cellFlag.HasCellFlag(CellFlag.HasCrushableActor) || Info.Crushes.IsEmpty) { return(true); } // If the other actor in our way cannot be crushed, we are blocked. // PERF: Avoid LINQ. var crushables = otherActor.TraitsImplementing <ICrushable>(); foreach (var crushable in crushables) { if (crushable.CrushableBy(otherActor, actor, Info.Crushes)) { return(false); } } return(true); }
public MapPathGraph(CellInfoLayerPool layerPool, Locomotor locomotor, Actor actor, World world, BlockedByActor check, Func <CPos, int> customCost, Actor ignoreActor, bool laneBias, bool inReverse) : base(locomotor, actor, world, check, customCost, ignoreActor, laneBias, inReverse) { // As we support a search over the whole map area, // use the pool to grab the CellInfos we need to track the graph state. // This allows us to avoid the cost of allocating large arrays constantly. // PERF: Avoid LINQ pooledLayer = layerPool.Get(); cellInfoForLayer = new CellLayer <CellInfo> [CustomMovementLayers.Length]; cellInfoForLayer[0] = pooledLayer.GetLayer(); foreach (var cml in CustomMovementLayers) { if (cml != null && cml.EnabledForLocomotor(locomotor.Info)) { cellInfoForLayer[cml.Index] = pooledLayer.GetLayer(); } } }
public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { // Since crates don't share cells and GetAvailableSubCell only returns SubCell.Full or SubCell.Invalid, we ignore the subCell parameter return(GetAvailableSubCell(world, cell, ignoreActor, check) != SubCell.Invalid); }
public static IPathSearch FromPoint(World world, Locomotor locomotor, Actor self, CPos @from, CPos target, BlockedByActor check) { return(FromPoints(world, locomotor, self, new[] { from }, target, check)); }
public SubCell GetAvailableSubCell(World world, CPos cell, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { if (!CanExistInCell(world, cell)) { return(SubCell.Invalid); } if (world.WorldActor.Trait <BuildingInfluence>().GetBuildingAt(cell) != null) { return(SubCell.Invalid); } if (check == BlockedByActor.None) { return(SubCell.FullCell); } return(!world.ActorMap.GetActorsAt(cell).Any(x => x != ignoreActor) ? SubCell.FullCell : SubCell.Invalid); }
public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { // SBMs may not land. return(false); }
public SubCell GetAvailableSubCell(CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { if (!CanExistInCell(cell)) { return(SubCell.Invalid); } 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 cell, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { return(true); }
public bool CanEnterCell(CPos a, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { return(GetAvailableSubCell(a, SubCell.Any, ignoreActor, check) != SubCell.Invalid); }
// Used to determine if actor can spawn public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { return(world.Map.Contains(cell)); }
public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, 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. return(true); }