// Inverts GetMoveActions. So, returns the list of move actions whose target is the current voxel. // Very, very slow. public IEnumerable <MoveAction> GetInverseMoveActions(MoveState currentstate, List <GameComponent> teleportObjects) { if (Parent == null) { yield break; } if (Creature == null) { yield break; } var current = currentstate.Voxel; if (Can(MoveType.Teleport)) { foreach (var obj in teleportObjects) { if ((obj.Position - current.WorldPosition).LengthSquared() > 2) { continue; } for (int dx = -TeleportDistance; dx <= TeleportDistance; dx++) { for (int dz = -TeleportDistance; dz <= TeleportDistance; dz++) { for (int dy = -TeleportDistance; dy <= TeleportDistance; dy++) { if (dx * dx + dy * dy + dz * dz > TeleportDistanceSquared) { continue; } VoxelHandle teleportNeighbor = new VoxelHandle(Parent.World.ChunkManager, current.Coordinate + new GlobalVoxelOffset(dx, dy, dz)); var adjacent = VoxelHelpers.GetNeighbor(teleportNeighbor, new GlobalVoxelOffset(0, -1, 0)); if (teleportNeighbor.IsValid && teleportNeighbor.IsEmpty && adjacent.IsValid && adjacent.IsEmpty) { yield return(new MoveAction() { InteractObject = obj, Diff = new Vector3(dx, dx, dz), SourceVoxel = teleportNeighbor, DestinationState = currentstate, MoveType = MoveType.Teleport, CostMultiplier = 1.0f }); } } } } } } var storage = new MoveActionTempStorage(); foreach (var v in VoxelHelpers.EnumerateCube(current.Coordinate) .Select(n => new VoxelHandle(current.Chunk.Manager, n)) .Where(h => h.IsValid)) { foreach (var a in GetMoveActions(new MoveState() { Voxel = v }, teleportObjects, storage).Where(a => a.DestinationState == currentstate)) { yield return(a); } if (!Can(MoveType.RideVehicle)) { continue; } // Now that dwarfs can ride vehicles, the inverse of the move actions becomes extremely complicated. We must now // iterate through all rails intersecting every neighbor and see if we can find a connection from that rail to this one. // Further, we must iterate through the entire rail network and enumerate all possible directions in and out of that rail. // Yay! // Actually - why not just not bother with rails when inverse pathing, since it should only be invoked when forward pathing fails anyway? // Also NOT hacking in inverse elevators! /* * var bodies = new HashSet<GameComponent>(); * OctTree.EnumerateItems(v.GetBoundingBox(), bodies); * * var rails = bodies.OfType<Rail.RailEntity>().Where(r => r.Active); * foreach (var rail in rails) * { * if (rail.GetContainingVoxel() != v) * continue; * * foreach (var neighborRail in rail.NeighborRails.Select(neighbor => Creature.Manager.FindComponent(neighbor.NeighborID) as Rail.RailEntity)) * { * var actions = GetMoveActions(new MoveState() * { * Voxel = v, * VehicleType = VehicleTypes.Rail, * Rail = rail, * PrevRail = neighborRail * }, OctTree, teleportObjects, storage); * foreach (var a in actions.Where(a => a.DestinationState == currentstate)) * { * yield return a; * } * } * * foreach (var a in GetMoveActions(new MoveState() { Voxel = v, VehicleType = VehicleTypes.Rail, Rail = rail, PrevRail = null }, OctTree, teleportObjects, storage).Where(a => a.DestinationState == currentstate)) * yield return a; * } */ } }
/// <summary> gets the list of actions that the creature can take from a given voxel. </summary> public IEnumerable <MoveAction> GetMoveActions(MoveState state, List <GameComponent> teleportObjects, MoveActionTempStorage Storage) { if (Parent == null) { yield break; } if (!state.Voxel.IsValid) { yield break; } if (Creature == null) { yield break; } if (Storage == null) { Storage = new MoveActionTempStorage(); } GetNeighborhood(state.Voxel.Chunk.Manager, state.Voxel, Storage.Neighborhood); bool inWater = (Storage.Neighborhood[1, 1, 1].IsValid && Storage.Neighborhood[1, 1, 1].LiquidLevel > WaterManager.inWaterThreshold); bool standingOnGround = (Storage.Neighborhood[1, 0, 1].IsValid && !Storage.Neighborhood[1, 0, 1].IsEmpty); bool topCovered = (Storage.Neighborhood[1, 2, 1].IsValid && !Storage.Neighborhood[1, 2, 1].IsEmpty); bool hasNeighbors = HasNeighbors(Storage.Neighborhood); bool isRiding = state.VehicleType != VehicleTypes.None; var neighborHoodBounds = new BoundingBox(Storage.Neighborhood[0, 0, 0].GetBoundingBox().Min, Storage.Neighborhood[2, 2, 2].GetBoundingBox().Max); Storage.NeighborObjects.Clear(); Parent.World.EnumerateIntersectingObjects(neighborHoodBounds, Storage.NeighborObjects); if (Can(MoveType.Teleport)) { foreach (var obj in teleportObjects) { if ((obj.Position - state.Voxel.WorldPosition).LengthSquared() < TeleportDistanceSquared) { yield return new MoveAction() { InteractObject = obj, MoveType = MoveType.Teleport, SourceVoxel = state.Voxel, DestinationState = new MoveState() { Voxel = new VoxelHandle(state.Voxel.Chunk.Manager, GlobalVoxelCoordinate.FromVector3(obj.Position)) }, CostMultiplier = 1.0f } } } } ; var successors = EnumerateSuccessors(state, state.Voxel, Storage, inWater, standingOnGround, topCovered, hasNeighbors); // Now, validate each move action that the creature might take. foreach (MoveAction v in successors) { #if DEBUG if (!v.DestinationVoxel.IsValid) { throw new InvalidOperationException(); } #endif var n = v.DestinationVoxel.IsValid ? v.DestinationVoxel : Storage.Neighborhood[(int)v.Diff.X, (int)v.Diff.Y, (int)v.Diff.Z]; if (n.IsValid && (v.MoveType == MoveType.Dig || isRiding || n.IsEmpty || n.LiquidLevel > 0)) { // Do one final check to see if there is an object blocking the motion. bool blockedByObject = false; if (state.VehicleType == VehicleTypes.None) { var objectsAtNeighbor = Storage.NeighborObjects.Where(o => o.GetBoundingBox().Intersects(n.GetBoundingBox())); // If there is an object blocking the motion, determine if it can be passed through. foreach (var body in objectsAtNeighbor) { var door = body as Door; // ** Doors are in the octtree, pretty sure this was always pointless -- var door = body.GetRoot().EnumerateAll().OfType<Door>().FirstOrDefault(); // If there is an enemy door blocking movement, we can destroy it to get through. if (door != null) { if ( Creature.World.Overworld.GetPolitics(door.TeamFaction.ParentFaction, Creature.Faction.ParentFaction) .GetCurrentRelationship() == Relationship.Hateful) { if (Can(MoveType.DestroyObject)) { yield return(new MoveAction { Diff = v.Diff, MoveType = MoveType.DestroyObject, InteractObject = door, DestinationVoxel = n, SourceState = state, CostMultiplier = 1.0f // Todo: Multiply by toughness of object? }); } blockedByObject = true; } } } } // If no object blocked us, we can move freely as normal. if (!blockedByObject && n.LiquidType != LiquidType.Lava) { var newAction = v; newAction.SourceState = state; newAction.DestinationVoxel = n; yield return(newAction); } } } }
private IEnumerable <MoveAction> EnumerateSuccessors( MoveState state, VoxelHandle voxel, MoveActionTempStorage Storage, bool inWater, bool standingOnGround, bool topCovered, bool hasNeighbors) { bool isClimbing = false; if (state.VehicleType == VehicleTypes.Rail) { if (Can(MoveType.ExitVehicle)) // Possibly redundant... If they can ride they should be able to exit right? { yield return(new MoveAction() { SourceState = state, DestinationState = new MoveState() { VehicleType = VehicleTypes.None, Voxel = state.Voxel }, MoveType = MoveType.ExitVehicle, Diff = new Vector3(1, 1, 1), CostMultiplier = 1.0f }); } if (Can(MoveType.RideVehicle)) { foreach (var neighbor in Rail.RailHelper.EnumerateForwardNetworkConnections(state.PrevRail, state.Rail)) { var neighborRail = Creature.Manager.FindComponent(neighbor) as Rail.RailEntity; if (neighborRail == null || !neighborRail.Active) { continue; } yield return(new MoveAction() { SourceState = state, DestinationState = new MoveState() { Voxel = neighborRail.GetContainingVoxel(), Rail = neighborRail, PrevRail = state.Rail, VehicleType = VehicleTypes.Rail }, MoveType = MoveType.RideVehicle, CostMultiplier = 1.0f }); } } yield break; // Nothing can be done without exiting the rails first. } if (CanClimb || Can(MoveType.RideVehicle)) { //Climbing ladders and riding rails. var bodies = Storage.NeighborObjects.Where(o => o.GetBoundingBox().Intersects(voxel.GetBoundingBox())); // if the creature can climb objects and a ladder is in this voxel, // then add a climb action. if (CanClimb) { var ladder = bodies.FirstOrDefault(component => component.Tags.Contains("Climbable")); if (ladder != null) { yield return(new MoveAction { SourceState = state, Diff = new Vector3(1, 2, 1), MoveType = MoveType.Climb, InteractObject = ladder, CostMultiplier = 1.0f, DestinationVoxel = Storage.Neighborhood[1, 2, 1] }); if (!standingOnGround) { yield return(new MoveAction { SourceState = state, Diff = new Vector3(1, 0, 1), MoveType = MoveType.Climb, InteractObject = ladder, CostMultiplier = 1.0f, DestinationVoxel = Storage.Neighborhood[1, 2, 1] }); } standingOnGround = true; } } if (Can(MoveType.RideVehicle)) { var rails = bodies.OfType <Rail.RailEntity>().Where(r => r.Active); if (rails.Count() > 0 && Can(MoveType.RideVehicle)) { { foreach (var rail in rails) { if (rail.GetContainingVoxel() != state.Voxel) { continue; } yield return(new MoveAction() { SourceState = state, DestinationState = new MoveState() { VehicleType = VehicleTypes.Rail, Rail = rail, Voxel = rail.GetContainingVoxel() }, MoveType = MoveType.EnterVehicle, Diff = new Vector3(1, 1, 1), CostMultiplier = 1.0f }); } } } var elevators = bodies.OfType <Elevators.ElevatorShaft>().Where(r => r.Active && System.Math.Abs(r.Position.Y - voxel.Center.Y) < 0.5f); foreach (var elevator in elevators) { foreach (var elevatorExit in Elevators.Helper.EnumerateExits(elevator.Shaft)) { if (object.ReferenceEquals(elevator, elevatorExit.ShaftSegment)) { continue; // Ignore exits from the segment we are entering at. } yield return(new MoveAction() { SourceState = state, DestinationState = new MoveState() { Voxel = elevatorExit.OntoVoxel, VehicleType = VehicleTypes.None, Tag = new Elevators.ElevatorMoveState { Entrance = elevator, Exit = elevatorExit.ShaftSegment } }, MoveType = MoveType.RideElevator, CostMultiplier = elevator.GetQueueSize() + 1.0f }); } } } } // If the creature can fly and is not underwater, it can fly // to any adjacent empty cell. if (CanFly && !inWater) { for (int dx = 0; dx <= 2; dx++) { for (int dz = 0; dz <= 2; dz++) { for (int dy = 0; dy <= 2; dy++) { if (dx == 1 && dz == 1 && dy == 1) { continue; } if (Storage.Neighborhood[dx, dy, dz].IsValid && Storage.Neighborhood[dx, dy, dz].IsEmpty) { yield return(new MoveAction { SourceState = state, Diff = new Vector3(dx, dy, dz), MoveType = MoveType.Fly, CostMultiplier = 1.0f, DestinationVoxel = Storage.Neighborhood[dx, dy, dz] }); } } } } } // If the creature is not in water and is not standing on ground, // it can fall one voxel downward in free space. if (!inWater && !standingOnGround) { yield return(new MoveAction { SourceState = state, Diff = new Vector3(1, 0, 1), MoveType = MoveType.Fall, CostMultiplier = 1.0f, DestinationVoxel = Storage.Neighborhood[1, 0, 1] }); } // If the creature can climb walls and is not blocked by a voxl above. if (CanClimbWalls && !topCovered) { // This monstrosity is unrolling an inner loop so that we don't have to allocate an array or // enumerators. var wall = VoxelHandle.InvalidHandle; var n211 = Storage.Neighborhood[2, 1, 1]; if (n211.IsValid && !n211.IsEmpty) { wall = n211; } else { var n011 = Storage.Neighborhood[0, 1, 1]; if (n011.IsValid && !n011.IsEmpty) { wall = n011; } else { var n112 = Storage.Neighborhood[1, 1, 2]; if (n112.IsValid && !n112.IsEmpty) { wall = n112; } else { var n110 = Storage.Neighborhood[1, 1, 0]; if (n110.IsValid && !n110.IsEmpty) { wall = n110; } } } } if (wall.IsValid) { isClimbing = true; yield return(new MoveAction { SourceState = state, Diff = new Vector3(1, 2, 1), MoveType = MoveType.ClimbWalls, ActionVoxel = wall, CostMultiplier = 1.0f, DestinationVoxel = Storage.Neighborhood[1, 2, 1] }); if (!standingOnGround) { yield return(new MoveAction { SourceState = state, Diff = new Vector3(1, 0, 1), MoveType = MoveType.ClimbWalls, ActionVoxel = wall, CostMultiplier = 1.0f, DestinationVoxel = Storage.Neighborhood[1, 0, 1] }); } } } // If the creature either can walk or is in water, add the // eight-connected free neighbors around the voxel. if ((CanWalk && standingOnGround) || (CanSwim && inWater)) { // If the creature is in water, it can swim. Otherwise, it will walk. var moveType = inWater ? MoveType.Swim : MoveType.Walk; if (Storage.Neighborhood[0, 1, 1].IsValid && Storage.Neighborhood[0, 1, 1].IsEmpty) { // +- x yield return(new MoveAction { SourceState = state, DestinationVoxel = Storage.Neighborhood[0, 1, 1], Diff = new Vector3(0, 1, 1), MoveType = moveType, CostMultiplier = 1.0f }); } if (Storage.Neighborhood[2, 1, 1].IsValid && Storage.Neighborhood[2, 1, 1].IsEmpty) { yield return(new MoveAction { SourceState = state, Diff = new Vector3(2, 1, 1), MoveType = moveType, CostMultiplier = 1.0f, DestinationVoxel = Storage.Neighborhood[2, 1, 1] }); } if (Storage.Neighborhood[1, 1, 0].IsValid && Storage.Neighborhood[1, 1, 0].IsEmpty) { // +- z yield return(new MoveAction { SourceState = state, Diff = new Vector3(1, 1, 0), MoveType = moveType, CostMultiplier = 1.0f, DestinationVoxel = Storage.Neighborhood[1, 1, 0] }); } if (Storage.Neighborhood[1, 1, 2].IsValid && Storage.Neighborhood[1, 1, 2].IsEmpty) { yield return(new MoveAction { SourceState = state, Diff = new Vector3(1, 1, 2), MoveType = moveType, CostMultiplier = 1.0f, DestinationVoxel = Storage.Neighborhood[1, 1, 2] }); } // Only bother worrying about 8-connected movement if there are // no full neighbors around the voxel. if (!hasNeighbors) { if (Storage.Neighborhood[2, 1, 2].IsValid && Storage.Neighborhood[2, 1, 2].IsEmpty) { // +x + z yield return(new MoveAction { SourceState = state, Diff = new Vector3(2, 1, 2), MoveType = moveType, CostMultiplier = 1.0f, DestinationVoxel = Storage.Neighborhood[2, 1, 2] }); } if (Storage.Neighborhood[2, 1, 0].IsValid && Storage.Neighborhood[2, 1, 0].IsEmpty) { yield return(new MoveAction { SourceState = state, Diff = new Vector3(2, 1, 0), MoveType = moveType, CostMultiplier = 1.0f, DestinationVoxel = Storage.Neighborhood[2, 1, 0] }); } if (Storage.Neighborhood[0, 1, 2].IsValid && Storage.Neighborhood[0, 1, 2].IsEmpty) { // -x -z yield return(new MoveAction { SourceState = state, Diff = new Vector3(0, 1, 2), MoveType = moveType, CostMultiplier = 1.0f, DestinationVoxel = Storage.Neighborhood[0, 1, 2] }); } if (Storage.Neighborhood[0, 1, 0].IsValid && Storage.Neighborhood[0, 1, 0].IsEmpty) { yield return(new MoveAction { SourceState = state, Diff = new Vector3(0, 1, 0), MoveType = moveType, CostMultiplier = 1.0f, DestinationVoxel = Storage.Neighborhood[0, 1, 0] }); } } } // If the creature's head is free, and it is standing on ground, // or if it is in water, or if it is climbing, it can also jump // to voxels that are 1 cell away and 1 cell up. if (!topCovered && (standingOnGround || (CanSwim && inWater) || isClimbing)) { for (int dx = 0; dx <= 2; dx++) { for (int dz = 0; dz <= 2; dz++) { if (dx == 1 && dz == 1) { continue; } if (Storage.Neighborhood[dx, 1, dz].IsValid && !Storage.Neighborhood[dx, 1, dz].IsEmpty) { yield return(new MoveAction { SourceState = state, Diff = new Vector3(dx, 2, dz), MoveType = MoveType.Jump, DestinationVoxel = Storage.Neighborhood[dx, 2, dz], CostMultiplier = 1.0f }); } } } } /* * if (CanDig) * { * // This loop is unrolled for speed. It gets the manhattan neighbors and tells the creature that it can mine * // the surrounding rock to get through. * VoxelHandle neighbor = Storage.Neighborhood[0, 1, 1]; * if (neighbor.IsValid && !neighbor.IsEmpty && (!IsDwarf || !neighbor.IsPlayerBuilt)) * { * yield return (new MoveAction * { * SourceState = state, * Diff = new Vector3(0, 1, 1), * MoveType = MoveType.Dig, * DestinationVoxel = neighbor, * CostMultiplier = 1.0f * }); * } * * neighbor = Storage.Neighborhood[2, 1, 1]; * if (neighbor.IsValid && !neighbor.IsEmpty && (!IsDwarf || !neighbor.IsPlayerBuilt)) * { * yield return (new MoveAction * { * SourceState = state, * Diff = new Vector3(2, 1, 1), * MoveType = MoveType.Dig, * DestinationVoxel = neighbor, * CostMultiplier = 1.0f * }); * } * * neighbor = Storage.Neighborhood[1, 1, 2]; * if (neighbor.IsValid && !neighbor.IsEmpty && (!IsDwarf || !neighbor.IsPlayerBuilt)) * { * yield return (new MoveAction * { * SourceState = state, * Diff = new Vector3(1, 1, 2), * MoveType = MoveType.Dig, * DestinationVoxel = neighbor, * CostMultiplier = 1.0f * }); * } * * neighbor = Storage.Neighborhood[1, 1, 0]; * if (neighbor.IsValid && !neighbor.IsEmpty && (!IsDwarf || !neighbor.IsPlayerBuilt)) * { * yield return (new MoveAction * { * SourceState = state, * Diff = new Vector3(1, 1, 0), * MoveType = MoveType.Dig, * DestinationVoxel = neighbor, * CostMultiplier = 1.0f * }); * } * * neighbor = Storage.Neighborhood[1, 2, 1]; * if (neighbor.IsValid && !neighbor.IsEmpty && (!IsDwarf || !neighbor.IsPlayerBuilt)) * { * yield return (new MoveAction * { * SourceState = state, * Diff = new Vector3(1, 2, 1), * MoveType = MoveType.Dig, * DestinationVoxel = neighbor, * CostMultiplier = 1.0f * }); * } * * neighbor = Storage.Neighborhood[1, 0, 1]; * if (neighbor.IsValid && !neighbor.IsEmpty && (!IsDwarf || !neighbor.IsPlayerBuilt)) * { * yield return (new MoveAction * { * SourceState = state, * Diff = new Vector3(1, 0, 1), * MoveType = MoveType.Dig, * DestinationVoxel = neighbor, * CostMultiplier = 1.0f * }); * } * } */ }
// Inverts GetMoveActions. So, returns the list of move actions whose target is the current voxel. // Very, very slow. public IEnumerable <MoveAction> GetInverseMoveActions(MoveState currentstate, OctTreeNode OctTree) { if (Parent == null) { yield break; } var creature = Creature; if (creature == null) { yield break; } var current = currentstate.Voxel; if (Can(MoveType.Teleport)) { var teleportObjects = Parent.Faction.OwnedObjects.Where(obj => obj.Active && obj.Tags.Contains("Teleporter")); foreach (var obj in teleportObjects) { if ((obj.Position - current.WorldPosition).LengthSquared() > 2) { continue; } for (int dx = -TeleportDistance; dx <= TeleportDistance; dx++) { for (int dz = -TeleportDistance; dz <= TeleportDistance; dz++) { for (int dy = -TeleportDistance; dy <= TeleportDistance; dy++) { if (dx * dx + dy * dy + dz * dz > TeleportDistanceSquared) { continue; } VoxelHandle teleportNeighbor = new VoxelHandle(Parent.World.ChunkManager.ChunkData, current.Coordinate + new GlobalVoxelOffset(dx, dy, dz)); if (teleportNeighbor.IsValid && teleportNeighbor.IsEmpty && !VoxelHelpers.GetNeighbor(teleportNeighbor, new GlobalVoxelOffset(0, -1, 0)).IsEmpty) { yield return(new MoveAction() { InteractObject = obj, Diff = new Vector3(dx, dx, dz), SourceVoxel = teleportNeighbor, DestinationState = currentstate, MoveType = MoveType.Teleport }); } } } } } } foreach (var v in VoxelHelpers.EnumerateCube(current.Coordinate) .Select(n => new VoxelHandle(current.Chunk.Manager.ChunkData, n)) .Where(h => h.IsValid)) { foreach (var a in GetMoveActions(new MoveState() { Voxel = v }, OctTree).Where(a => a.DestinationState == currentstate)) { yield return(a); } if (!Can(MoveType.RideVehicle)) { continue; } // Now that dwarfs can ride vehicles, the inverse of the move actions becomes extremely complicated. We must now // iterate through all rails intersecting every neighbor and see if we can find a connection from that rail to this one. // Further, we must iterate through the entire rail network and enumerate all possible directions in and out of that rail. // Yay! var bodies = new HashSet <Body>(); OctTree.EnumerateItems(v.GetBoundingBox(), bodies); var rails = bodies.OfType <Rail.RailEntity>().Where(r => r.Active); foreach (var rail in rails) { if (rail.GetContainingVoxel() != v) { continue; } /* * if (!DwarfGame.IsMainThread) * { * for (int i = 0; i < 1; i++) * { * Drawer3D.DrawBox(rail.GetBoundingBox(), Color.Purple, 0.1f, false); * System.Threading.Thread.Sleep(1); * } * } */ foreach (var neighborRail in rail.NeighborRails.Select(neighbor => creature.Manager.FindComponent(neighbor.NeighborID) as Rail.RailEntity)) { var actions = GetMoveActions(new MoveState() { Voxel = v, VehicleState = new VehicleState() { Rail = rail, PrevRail = neighborRail } }, OctTree); foreach (var a in actions.Where(a => a.DestinationState == currentstate)) { yield return(a); /* * if (!DwarfGame.IsMainThread && a.MoveType == MoveType.RideVehicle) * { * for (int i = 0; i < 10; i++) * { * Drawer3D.DrawBox(rail.GetBoundingBox(), Color.Red, 0.1f, false); * System.Threading.Thread.Sleep(1); * } * } */ } } foreach (var a in GetMoveActions(new MoveState() { Voxel = v, VehicleState = new VehicleState() { Rail = rail, PrevRail = null } }, OctTree).Where(a => a.DestinationState == currentstate)) { yield return(a); } } } }
private IEnumerable <MoveAction> EnumerateSuccessors(MoveState state, VoxelHandle voxel, VoxelHandle[,,] neighborHood, bool inWater, bool standingOnGround, bool topCovered, bool hasNeighbors, bool isRiding, HashSet <Body> neighborObjects) { bool isClimbing = false; if (CanClimb || Can(MoveType.RideVehicle)) { //Climbing ladders. var bodies = neighborObjects.Where(o => o.GetBoundingBox().Intersects(voxel.GetBoundingBox())); if (!isRiding) { var ladder = bodies.FirstOrDefault(component => component.Tags.Contains("Climbable")); // if the creature can climb objects and a ladder is in this voxel, // then add a climb action. if (ladder != null && CanClimb) { yield return(new MoveAction { Diff = new Vector3(1, 2, 1), MoveType = MoveType.Climb, InteractObject = ladder }); if (!standingOnGround) { yield return(new MoveAction { Diff = new Vector3(1, 0, 1), MoveType = MoveType.Climb, InteractObject = ladder }); } standingOnGround = true; } } if (!isRiding) { var rails = bodies.OfType <Rail.RailEntity>().Where(r => r.Active); if (rails.Count() > 0 && Can(MoveType.RideVehicle)) { { foreach (var rail in rails) { if (rail.GetContainingVoxel() != state.Voxel) { continue; } yield return(new MoveAction() { SourceState = state, DestinationState = new MoveState() { VehicleState = new VehicleState() { Rail = rail }, Voxel = rail.GetContainingVoxel() }, MoveType = MoveType.EnterVehicle, Diff = new Vector3(1, 1, 1) }); } } } } if (Can(MoveType.ExitVehicle) && isRiding) { yield return(new MoveAction() { SourceState = state, DestinationState = new MoveState() { VehicleState = new VehicleState(), Voxel = state.Voxel }, MoveType = MoveType.ExitVehicle, Diff = new Vector3(1, 1, 1) }); } if (Can(MoveType.RideVehicle) && isRiding) { foreach (var neighbor in Rail.RailHelper.EnumerateForwardNetworkConnections(state.VehicleState.PrevRail, state.VehicleState.Rail)) { var neighborRail = Creature.Manager.FindComponent(neighbor) as Rail.RailEntity; if (neighborRail == null || !neighborRail.Active) { continue; } yield return(new MoveAction() { SourceState = state, DestinationState = new MoveState() { Voxel = neighborRail.GetContainingVoxel(), VehicleState = new VehicleState() { Rail = neighborRail, PrevRail = state.VehicleState.Rail } }, MoveType = MoveType.RideVehicle, }); } } } // If the creature can climb walls and is not blocked by a voxl above. if (!isRiding && CanClimbWalls && !topCovered) { // This monstrosity is unrolling an inner loop so that we don't have to allocate an array or // enumerators. var wall = VoxelHandle.InvalidHandle; var n211 = neighborHood[2, 1, 1]; if (n211.IsValid && !n211.IsEmpty) { wall = n211; } else { var n011 = neighborHood[0, 1, 1]; if (n011.IsValid && !n011.IsEmpty) { wall = n011; } else { var n112 = neighborHood[1, 1, 2]; if (n112.IsValid && !n112.IsEmpty) { wall = n112; } else { var n110 = neighborHood[1, 1, 0]; if (n110.IsValid && !n110.IsEmpty) { wall = n110; } } } } if (wall.IsValid) { isClimbing = true; yield return(new MoveAction { Diff = new Vector3(1, 2, 1), MoveType = MoveType.ClimbWalls, ActionVoxel = wall }); if (!standingOnGround) { yield return(new MoveAction { Diff = new Vector3(1, 0, 1), MoveType = MoveType.ClimbWalls, ActionVoxel = wall }); } } } // If the creature either can walk or is in water, add the // eight-connected free neighbors around the voxel. if (!isRiding && ((CanWalk && standingOnGround) || (CanSwim && inWater))) { // If the creature is in water, it can swim. Otherwise, it will walk. var moveType = inWater ? MoveType.Swim : MoveType.Walk; if (!neighborHood[0, 1, 1].IsValid || neighborHood[0, 1, 1].IsEmpty) { // +- x yield return(new MoveAction { Diff = new Vector3(0, 1, 1), MoveType = moveType }); } if (!neighborHood[2, 1, 1].IsValid || neighborHood[2, 1, 1].IsEmpty) { yield return(new MoveAction { Diff = new Vector3(2, 1, 1), MoveType = moveType }); } if (!neighborHood[1, 1, 0].IsValid || neighborHood[1, 1, 0].IsEmpty) { // +- z yield return(new MoveAction { Diff = new Vector3(1, 1, 0), MoveType = moveType }); } if (!neighborHood[1, 1, 2].IsValid || neighborHood[1, 1, 2].IsEmpty) { yield return(new MoveAction { Diff = new Vector3(1, 1, 2), MoveType = moveType }); } // Only bother worrying about 8-connected movement if there are // no full neighbors around the voxel. if (!hasNeighbors) { if (!neighborHood[2, 1, 2].IsValid || neighborHood[2, 1, 2].IsEmpty) { // +x + z yield return(new MoveAction { Diff = new Vector3(2, 1, 2), MoveType = moveType }); } if (!neighborHood[2, 1, 0].IsValid || neighborHood[2, 1, 0].IsEmpty) { yield return(new MoveAction { Diff = new Vector3(2, 1, 0), MoveType = moveType }); } if (!neighborHood[0, 1, 2].IsValid || neighborHood[0, 1, 2].IsEmpty) { // -x -z yield return(new MoveAction { Diff = new Vector3(0, 1, 2), MoveType = moveType }); } if (!neighborHood[0, 1, 0].IsValid || neighborHood[0, 1, 0].IsEmpty) { yield return(new MoveAction { Diff = new Vector3(0, 1, 0), MoveType = moveType }); } } } // If the creature's head is free, and it is standing on ground, // or if it is in water, or if it is climbing, it can also jump // to voxels that are 1 cell away and 1 cell up. if (!isRiding && (!topCovered && (standingOnGround || (CanSwim && inWater) || isClimbing))) { for (int dx = 0; dx <= 2; dx++) { for (int dz = 0; dz <= 2; dz++) { if (dx == 1 && dz == 1) { continue; } if (neighborHood[dx, 1, dz].IsValid && !neighborHood[dx, 1, dz].IsEmpty) { yield return(new MoveAction { Diff = new Vector3(dx, 2, dz), MoveType = MoveType.Jump }); } } } } // If the creature is not in water and is not standing on ground, // it can fall one voxel downward in free space. if (!isRiding && !inWater && !standingOnGround) { yield return(new MoveAction { Diff = new Vector3(1, 0, 1), MoveType = MoveType.Fall }); } // If the creature can fly and is not underwater, it can fly // to any adjacent empty cell. if (!isRiding && CanFly && !inWater) { for (int dx = 0; dx <= 2; dx++) { for (int dz = 0; dz <= 2; dz++) { for (int dy = 0; dy <= 2; dy++) { if (dx == 1 && dz == 1 && dy == 1) { continue; } if (!neighborHood[dx, 1, dz].IsValid || neighborHood[dx, 1, dz].IsEmpty) { yield return(new MoveAction { Diff = new Vector3(dx, dy, dz), MoveType = MoveType.Fly }); } } } } } if (!isRiding && CanDig) { // This loop is unrolled for speed. It gets the manhattan neighbors and tells the creature that it can mine // the surrounding rock to get through. int dx = -1; int dy = 0; int dz = 0; VoxelHandle neighbor = neighborHood[dx + 1, dy + 1, dz + 1]; if (neighbor.IsValid && !neighbor.IsEmpty && (!IsDwarf || !neighbor.IsPlayerBuilt)) { yield return(new MoveAction { Diff = new Vector3(dx + 1, dy + 1, dz + 1), MoveType = MoveType.Dig, DestinationVoxel = neighbor, }); } dx = 1; dy = 0; dz = 0; neighbor = neighborHood[dx + 1, dy + 1, dz + 1]; if (neighbor.IsValid && !neighbor.IsEmpty && (!IsDwarf || !neighbor.IsPlayerBuilt)) { yield return(new MoveAction { Diff = new Vector3(dx + 1, dy + 1, dz + 1), MoveType = MoveType.Dig, DestinationVoxel = neighbor, }); } dx = 0; dy = 0; dz = 1; neighbor = neighborHood[dx + 1, dy + 1, dz + 1]; if (neighbor.IsValid && !neighbor.IsEmpty && (!IsDwarf || !neighbor.IsPlayerBuilt)) { yield return(new MoveAction { Diff = new Vector3(dx + 1, dy + 1, dz + 1), MoveType = MoveType.Dig, DestinationVoxel = neighbor, }); } dx = 0; dy = 0; dz = -1; neighbor = neighborHood[dx + 1, dy + 1, dz + 1]; if (neighbor.IsValid && !neighbor.IsEmpty && (!IsDwarf || !neighbor.IsPlayerBuilt)) { yield return(new MoveAction { Diff = new Vector3(dx + 1, dy + 1, dz + 1), MoveType = MoveType.Dig, DestinationVoxel = neighbor, }); } dx = 0; dy = 1; dz = 0; neighbor = neighborHood[dx + 1, dy + 1, dz + 1]; if (neighbor.IsValid && !neighbor.IsEmpty && (!IsDwarf || !neighbor.IsPlayerBuilt)) { yield return(new MoveAction { Diff = new Vector3(dx + 1, dy + 1, dz + 1), MoveType = MoveType.Dig, DestinationVoxel = neighbor, }); } dx = 0; dy = -1; dz = 0; neighbor = neighborHood[dx + 1, dy + 1, dz + 1]; if (neighbor.IsValid && !neighbor.IsEmpty && (!IsDwarf || !neighbor.IsPlayerBuilt)) { yield return(new MoveAction { Diff = new Vector3(dx + 1, dy + 1, dz + 1), MoveType = MoveType.Dig, DestinationVoxel = neighbor, }); } } }
/// <summary> gets the list of actions that the creature can take from a given voxel. </summary> public IEnumerable <MoveAction> GetMoveActions(MoveState state, OctTreeNode OctTree) { if (Parent == null) { yield break; } var voxel = state.Voxel; if (!voxel.IsValid) { yield break; } var creature = Creature; if (creature == null) { yield break; } var neighborHood = GetNeighborhood(voxel.Chunk.Manager.ChunkData, voxel); bool inWater = (neighborHood[1, 1, 1].IsValid && neighborHood[1, 1, 1].LiquidLevel > WaterManager.inWaterThreshold); bool standingOnGround = (neighborHood[1, 0, 1].IsValid && !neighborHood[1, 0, 1].IsEmpty); bool topCovered = (neighborHood[1, 2, 1].IsValid && !neighborHood[1, 2, 1].IsEmpty); bool hasNeighbors = HasNeighbors(neighborHood); bool isRiding = state.VehicleState.IsRidingVehicle; var neighborHoodBounds = new BoundingBox(neighborHood[0, 0, 0].GetBoundingBox().Min, neighborHood[2, 2, 2].GetBoundingBox().Max); var neighborObjects = new HashSet <Body>(); OctTree.EnumerateItems(neighborHoodBounds, neighborObjects); var successors = EnumerateSuccessors(state, voxel, neighborHood, inWater, standingOnGround, topCovered, hasNeighbors, isRiding, neighborObjects); if (Can(MoveType.Teleport)) { var teleportObjects = Parent.Faction.OwnedObjects.Where(obj => obj.Active && obj.Tags.Contains("Teleporter")); foreach (var obj in teleportObjects) { if ((obj.Position - state.Voxel.WorldPosition).LengthSquared() < TeleportDistanceSquared) { yield return(new MoveAction() { InteractObject = obj, MoveType = MoveType.Teleport, SourceVoxel = voxel, DestinationState = new MoveState() { Voxel = new VoxelHandle(voxel.Chunk.Manager.ChunkData, GlobalVoxelCoordinate.FromVector3(obj.Position)) } }); } } } // Now, validate each move action that the creature might take. foreach (MoveAction v in successors) { var n = v.DestinationVoxel.IsValid ? v.DestinationVoxel : neighborHood[(int)v.Diff.X, (int)v.Diff.Y, (int)v.Diff.Z]; if (n.IsValid && (v.MoveType == MoveType.Dig || isRiding || n.IsEmpty || n.LiquidLevel > 0)) { // Do one final check to see if there is an object blocking the motion. bool blockedByObject = false; if (!isRiding) { var objectsAtNeighbor = neighborObjects.Where(o => o.GetBoundingBox().Intersects(n.GetBoundingBox())); // If there is an object blocking the motion, determine if it can be passed through. foreach (var body in objectsAtNeighbor) { var door = body as Door; // ** Doors are in the octtree, pretty sure this was always pointless -- var door = body.GetRoot().EnumerateAll().OfType<Door>().FirstOrDefault(); // If there is an enemy door blocking movement, we can destroy it to get through. if (door != null) { if ( creature.World.Diplomacy.GetPolitics(door.TeamFaction, creature.Faction) .GetCurrentRelationship() == Relationship.Hateful) { if (Can(MoveType.DestroyObject)) { yield return(new MoveAction { Diff = v.Diff, MoveType = MoveType.DestroyObject, InteractObject = door, DestinationVoxel = n, SourceState = state }); } blockedByObject = true; } } } } // If no object blocked us, we can move freely as normal. if (!blockedByObject && n.LiquidType != LiquidType.Lava) { MoveAction newAction = v; newAction.SourceState = state; newAction.DestinationVoxel = n; yield return(newAction); } } } }
// Find a path from the start to the goal by computing an inverse path from goal to the start. Should only be used // if the forward path fails. private static PlanResult InversePath(CreatureMovement mover, VoxelHandle startVoxel, GoalRegion goal, ChunkManager chunks, int maxExpansions, ref List <MoveAction> toReturn, float weight, Func <bool> continueFunc) { // Create a local clone of the octree, using only the objects belonging to the player. var octree = new OctTreeNode <Body>(mover.Creature.World.ChunkManager.Bounds.Min, mover.Creature.World.ChunkManager.Bounds.Max); List <Body> playerObjects = new List <Body>(mover.Creature.World.PlayerFaction.OwnedObjects); List <Body> teleportObjects = mover.Creature.World.PlayerFaction.OwnedObjects.Where(o => o.Tags.Contains("Teleporter")).ToList(); foreach (var obj in playerObjects) { octree.Add(obj, obj.GetBoundingBox()); } MoveState start = new MoveState() { Voxel = startVoxel }; var startTime = DwarfTime.Tick(); if (mover.IsSessile) { return(new PlanResult() { Result = PlanResultCode.Invalid, Expansions = 0, TimeSeconds = 0 }); } if (!start.IsValid) { return(new PlanResult() { Result = PlanResultCode.Invalid, Expansions = 0, TimeSeconds = 0 }); } // Sometimes a goal may not even be achievable a.priori. If this is true, we know there can't be a path // which satisifies that goal. // It only makes sense to do inverse plans for goals that have an associated voxel. if (!goal.IsPossible() || !goal.GetVoxel().IsValid) { toReturn = null; return(new PlanResult() { Result = PlanResultCode.Invalid, Expansions = 0, TimeSeconds = 0 }); } if (goal.IsInGoalRegion(start.Voxel)) { toReturn = new List <MoveAction>(); return(new PlanResult() { Result = PlanResultCode.Success, Expansions = 0, TimeSeconds = 0 }); } // Voxels that have already been explored. var closedSet = new HashSet <MoveState>(); // Voxels which should be explored. var openSet = new HashSet <MoveState>(); // Dictionary of voxels to the optimal action that got the mover to that voxel. var cameFrom = new Dictionary <MoveState, MoveAction>(); // Optimal score of a voxel based on the path it took to get there. var gScore = new Dictionary <MoveState, float>(); // Expansion priority of voxels. var fScore = new PriorityQueue <MoveState>(); var goalVoxels = new List <VoxelHandle>(); goalVoxels.Add(goal.GetVoxel()); // Starting conditions of the search. foreach (var goalVoxel in VoxelHelpers.EnumerateCube(goal.GetVoxel().Coordinate) .Select(c => new VoxelHandle(start.Voxel.Chunk.Manager.ChunkData, c))) { if (!goalVoxel.IsValid) { continue; } var goalState = new MoveState() { Voxel = goalVoxel }; if (goal.IsInGoalRegion(goalVoxel)) { goalVoxels.Add(goalVoxel); openSet.Add(goalState); gScore[goalState] = 0.0f; fScore.Enqueue(goalState, gScore[goalState] + weight * (goalVoxel.WorldPosition - start.Voxel.WorldPosition).LengthSquared()); } } // Keep count of the number of expansions we've taken to get to the goal. int numExpansions = 0; // Loop until we've either checked every possible voxel, or we've exceeded the maximum number of // expansions. while (openSet.Count > 0 && numExpansions < maxExpansions) { if (numExpansions % 10 == 0 && !continueFunc()) { return new PlanResult { Result = PlanResultCode.Cancelled, Expansions = numExpansions, TimeSeconds = DwarfTime.Tock(startTime) } } ; // Check the next voxel to explore. var current = GetStateWithMinimumFScore(fScore); if (!current.IsValid) { continue; } //Drawer3D.DrawBox(current.GetBoundingBox(), Color.Blue, 0.1f); numExpansions++; // If we've reached the goal already, reconstruct the path from the start to the // goal. if (current.Equals(start)) { toReturn = ReconstructInversePath(goal, cameFrom, cameFrom[start]); return(new PlanResult() { Result = PlanResultCode.Success, Expansions = numExpansions, TimeSeconds = DwarfTime.Tock(startTime) }); } // We've already considered the voxel, so add it to the closed set. openSet.Remove(current); closedSet.Add(current); IEnumerable <MoveAction> neighbors = null; // Get the voxels that can be moved to from the current voxel. neighbors = mover.GetInverseMoveActions(current, octree, teleportObjects); // Otherwise, consider all of the neighbors of the current voxel that can be moved to, // and determine how to add them to the list of expansions. foreach (MoveAction n in neighbors) { //Drawer3D.DrawBox(n.SourceVoxel.GetBoundingBox(), Color.Red, 0.1f); // If we've already explored that voxel, don't explore it again. if (closedSet.Contains(n.SourceState)) { continue; } // Otherwise, consider the case of moving to that neighbor. float tenativeGScore = gScore[current] + GetDistance(current.Voxel, n.SourceState.Voxel, n.MoveType, mover); // IF the neighbor can already be reached more efficiently, ignore it. if (openSet.Contains(n.SourceState) && gScore.ContainsKey(n.SourceState) && !(tenativeGScore < gScore[n.SourceState])) { continue; } // Otherwise, add it to the list of voxels for consideration. openSet.Add(n.SourceState); // Add an edge to the voxel from the current voxel. var cameAction = n; cameFrom[n.SourceState] = cameAction; // Update the expansion scores for the next voxel. gScore[n.SourceState] = tenativeGScore; fScore.Enqueue(n.SourceState, gScore[n.SourceState] + weight * ((n.SourceState.Voxel.WorldPosition - start.Voxel.WorldPosition).LengthSquared() + (!n.SourceState.VehicleState.IsRidingVehicle ? 100.0f : 0.0f))); } // If we've expanded too many voxels, just give up. if (numExpansions >= maxExpansions) { return(new PlanResult() { Result = PlanResultCode.MaxExpansionsReached, Expansions = numExpansions, TimeSeconds = DwarfTime.Tock(startTime) }); } } // Somehow we've reached this code without having found a path. Return false. toReturn = null; return(new PlanResult() { Result = PlanResultCode.NoSolution, Expansions = numExpansions, TimeSeconds = DwarfTime.Tock(startTime) }); }
/// <summary> /// Creates a path from the start voxel to some goal region, returning a list of movements that can /// take a mover from the start voxel to the goal region. /// </summary> /// <param name="mover">The agent that we want to find a path for.</param> /// <param name="startVoxel"></param> /// <param name="goal">Goal conditions that the agent is trying to satisfy.</param> /// <param name="chunks">The voxels that the agent is moving through</param> /// <param name="maxExpansions">Maximum number of voxels to consider before giving up.</param> /// <param name="toReturn">The path of movements that the mover should take to reach the goal.</param> /// <param name="weight"> /// A heuristic weight to apply to the planner. If 1.0, the path is garunteed to be optimal. Higher values /// usually result in faster plans that are suboptimal. /// </param> /// <param name="continueFunc"></param> /// <returns>True if a path could be found, or false otherwise.</returns> private static PlanResult Path( CreatureMovement mover, VoxelHandle startVoxel, GoalRegion goal, ChunkManager chunks, int maxExpansions, ref List <MoveAction> toReturn, float weight, Func <bool> continueFunc) { // Create a local clone of the octree, using only the objects belonging to the player. var octree = new OctTreeNode <Body>(mover.Creature.World.ChunkManager.Bounds.Min, mover.Creature.World.ChunkManager.Bounds.Max); List <Body> playerObjects = new List <Body>(mover.Creature.World.PlayerFaction.OwnedObjects); List <Body> teleportObjects = playerObjects.Where(o => o.Tags.Contains("Teleporter")).ToList(); foreach (var obj in playerObjects) { octree.Add(obj, obj.GetBoundingBox()); } var storage = new MoveActionTempStorage(); var start = new MoveState() { Voxel = startVoxel }; var startTime = DwarfTime.Tick(); if (mover.IsSessile) { return(new PlanResult() { Expansions = 0, Result = PlanResultCode.Invalid, TimeSeconds = DwarfTime.Tock(startTime) }); } // Sometimes a goal may not even be achievable a.priori. If this is true, we know there can't be a path // which satisifies that goal. if (!goal.IsPossible()) { toReturn = null; return(new PlanResult() { Expansions = 0, Result = PlanResultCode.Invalid, TimeSeconds = DwarfTime.Tock(startTime) }); } // Voxels that have already been explored. var closedSet = new HashSet <MoveState>(); // Voxels which should be explored. var openSet = new HashSet <MoveState> { start }; // Dictionary of voxels to the optimal action that got the mover to that voxel. var cameFrom = new Dictionary <MoveState, MoveAction>(); // Optimal score of a voxel based on the path it took to get there. var gScore = new Dictionary <MoveState, float>(); // Expansion priority of voxels. var fScore = new PriorityQueue <MoveState>(); // Starting conditions of the search. gScore[start] = 0.0f; fScore.Enqueue(start, gScore[start] + weight * goal.Heuristic(start.Voxel)); // Keep count of the number of expansions we've taken to get to the goal. int numExpansions = 0; // Check the voxels adjacent to the current voxel as a quick test of adjacency to the goal. //var manhattanNeighbors = new List<VoxelHandle>(6); //for (int i = 0; i < 6; i++) //{ // manhattanNeighbors.Add(new VoxelHandle()); //} // Loop until we've either checked every possible voxel, or we've exceeded the maximum number of // expansions. while (openSet.Count > 0 && numExpansions < maxExpansions) { if (numExpansions % 10 == 0 && !continueFunc()) { return new PlanResult { Result = PlanResultCode.Cancelled, Expansions = numExpansions, TimeSeconds = DwarfTime.Tock(startTime) } } ; // Check the next voxel to explore. var current = GetStateWithMinimumFScore(fScore); if (!current.IsValid) { // If there wasn't a voxel to explore, just try to expand from // the start again. numExpansions++; continue; } numExpansions++; // If we've reached the goal already, reconstruct the path from the start to the // goal. if (goal.IsInGoalRegion(current.Voxel) && cameFrom.ContainsKey(current)) { toReturn = ReconstructPath(cameFrom, cameFrom[current], start); return(new PlanResult() { Result = PlanResultCode.Success, Expansions = numExpansions, TimeSeconds = DwarfTime.Tock(startTime) }); } // We've already considered the voxel, so add it to the closed set. openSet.Remove(current); closedSet.Add(current); // Get the voxels that can be moved to from the current voxel. var neighbors = mover.GetMoveActions(current, octree, teleportObjects, storage).ToList(); var foundGoalAdjacent = neighbors.FirstOrDefault(n => !n.DestinationState.VehicleState.IsRidingVehicle && goal.IsInGoalRegion(n.DestinationState.Voxel)); // A quick test to see if we're already adjacent to the goal. If we are, assume // that we can just walk to it. if (foundGoalAdjacent.DestinationState.IsValid && foundGoalAdjacent.SourceState.IsValid) { if (cameFrom.ContainsKey(current)) { toReturn = ReconstructPath(cameFrom, foundGoalAdjacent, start); return(new PlanResult() { Result = PlanResultCode.Success, Expansions = numExpansions, TimeSeconds = DwarfTime.Tock(startTime) }); } } // Otherwise, consider all of the neighbors of the current voxel that can be moved to, // and determine how to add them to the list of expansions. foreach (MoveAction n in neighbors) { // If we've already explored that voxel, don't explore it again. if (closedSet.Contains(n.DestinationState)) { continue; } // Otherwise, consider the case of moving to that neighbor. float tenativeGScore = gScore[current] + GetDistance(current.Voxel, n.DestinationState.Voxel, n.MoveType, mover); // IF the neighbor can already be reached more efficiently, ignore it. if (openSet.Contains(n.DestinationState) && !(tenativeGScore < gScore[n.DestinationState])) { continue; } // Otherwise, add it to the list of voxels for consideration. openSet.Add(n.DestinationState); // Add an edge to the voxel from the current voxel. var cameAction = n; cameFrom[n.DestinationState] = cameAction; // Update the expansion scores for the next voxel. gScore[n.DestinationState] = tenativeGScore; fScore.Enqueue(n.DestinationState, gScore[n.DestinationState] + weight * (goal.Heuristic(n.DestinationState.Voxel) + (!n.DestinationState.VehicleState.IsRidingVehicle ? 100.0f : 0.0f))); } // If we've expanded too many voxels, just give up. if (numExpansions >= maxExpansions) { return(new PlanResult() { Expansions = numExpansions, Result = PlanResultCode.MaxExpansionsReached, TimeSeconds = DwarfTime.Tock(startTime) }); } } // Somehow we've reached this code without having found a path. Return false. toReturn = null; return(new PlanResult() { Expansions = numExpansions, Result = PlanResultCode.NoSolution, TimeSeconds = DwarfTime.Tock(startTime) }); }