public IEnumerable <Status> FindPath() { Vector3 target = Entity.Position; if (Creature.AI.PositionConstraint.Contains(target) == ContainmentType.Disjoint) { target = MathFunctions.RandVector3Box(Creature.AI.PositionConstraint); } List <MoveAction> path = new List <MoveAction>(); VoxelHandle curr = Creature.Physics.CurrentVoxel; var storage = new MoveActionTempStorage(); for (int i = 0; i < PathLength; i++) { var actions = Creature.AI.Movement.GetMoveActions(new MoveState() { Voxel = curr }, new List <GameComponent>(), storage); MoveAction?bestAction = null; float bestDist = float.MinValue; foreach (MoveAction action in actions) { float dist = (action.DestinationVoxel.WorldPosition - target).LengthSquared(); if (dist > bestDist) { bestDist = dist; bestAction = action; } } if (bestAction.HasValue && !path.Any(p => p.DestinationVoxel.Equals(bestAction.Value.DestinationVoxel) && p.MoveType == bestAction.Value.MoveType && Creature.AI.PositionConstraint.Contains(bestAction.Value.DestinationVoxel.WorldPosition + Vector3.One * 0.5f) == ContainmentType.Contains)) { MoveAction action = bestAction.Value; action.DestinationVoxel = curr; path.Add(action); curr = bestAction.Value.DestinationVoxel; } else { break; } } if (path.Count > 0) { Creature.AI.Blackboard.SetData("FleePath", path); yield return(Status.Success); } else { yield return(Status.Fail); } }
public List <MoveAction> ComputeGreedyFallback(int maxsteps = 10, List <VoxelHandle> exploredVoxels = null) { var toReturn = new List <MoveAction>(); var goal = GetGoal(); var creatureVoxel = Agent.Physics.CurrentVoxel; if (goal.IsInGoalRegion(creatureVoxel)) { return(toReturn); } var storage = new MoveActionTempStorage(); var bodies = Agent.World.PlayerFaction.OwnedObjects.Where(o => o.Tags.Contains("Teleporter")).ToList(); var currentVoxel = creatureVoxel; while (toReturn.Count < maxsteps) { var actions = Agent.Movement.GetMoveActions(new MoveState() { Voxel = currentVoxel }, bodies, storage); float minCost = float.MaxValue; var minAction = new MoveAction(); bool hasMinAction = false; foreach (var action in actions) { if (toReturn.Any(a => a.DestinationVoxel == action.DestinationVoxel && a.MoveType == action.MoveType)) { continue; } var vox = action.DestinationVoxel; float cost = goal.Heuristic(vox) * MathFunctions.Rand(1.0f, 1.1f) + Agent.Movement.Cost(action.MoveType); if (exploredVoxels != null && exploredVoxels.Contains(action.DestinationVoxel)) { cost *= 10; } if (cost < minCost) { minAction = action; minCost = cost; hasMinAction = true; } } if (hasMinAction) { MoveAction action = minAction; action.DestinationVoxel = currentVoxel; toReturn.Add(action); currentVoxel = minAction.DestinationVoxel; if (goal.IsInGoalRegion(minAction.DestinationVoxel)) { return(toReturn); } } else { return(toReturn); } } return(toReturn); }
public IEnumerable <Status> FindGreedyPath() { Vector3 target = Target.Position; if (Is2D) { target.Y = Creature.AI.Position.Y; } List <MoveAction> path = new List <MoveAction>(); var curr = Creature.Physics.CurrentVoxel; var bodies = Agent.World.PlayerFaction.OwnedObjects.Where(o => o.Tags.Contains("Teleporter")).ToList(); var storage = new MoveActionTempStorage(); for (int i = 0; i < PathLength; i++) { IEnumerable <MoveAction> actions = Creature.AI.Movement.GetMoveActions(new MoveState() { Voxel = curr }, bodies, storage); MoveAction?bestAction = null; float bestDist = float.MaxValue; foreach (MoveAction action in actions) { // Prevents a stack overflow due to "DestroyObject" task creating a FollowPathAct! if (action.MoveType == MoveType.DestroyObject) { continue; } float dist = (action.DestinationVoxel.WorldPosition - target).LengthSquared(); if (dist < bestDist) { bestDist = dist; bestAction = action; } } Vector3 half = Vector3.One * 0.5f; if (bestAction.HasValue && !path.Any(p => p.DestinationVoxel.Equals(bestAction.Value.DestinationVoxel) && p.MoveType == bestAction.Value.MoveType)) { path.Add(bestAction.Value); MoveAction action = bestAction.Value; action.DestinationVoxel = curr; curr = bestAction.Value.DestinationVoxel; bestAction = action; if (((bestAction.Value.DestinationVoxel.WorldPosition + half) - target).Length() < Threshold) { break; } } } if (path.Count > 0) { Creature.AI.Blackboard.SetData("RandomPath", path); yield return(Status.Success); } else { yield return(Status.Fail); } }
public IEnumerable <Act.Status> GreedyFallbackBehavior(Creature agent) { var edgeGoal = new EdgeGoalRegion(); while (true) { DieTimer.Update(DwarfTime.LastTime); if (DieTimer.HasTriggered) { foreach (var status in Die(agent)) { continue; } yield break; } var creatureVoxel = agent.Physics.CurrentVoxel; List <MoveAction> path = new List <MoveAction>(); var storage = new MoveActionTempStorage(); for (int i = 0; i < 10; i++) { if (edgeGoal.IsInGoalRegion(creatureVoxel)) { foreach (var status in Die(agent)) { continue; } yield return(Act.Status.Success); yield break; } var actions = agent.AI.Movement.GetMoveActions(new MoveState { Voxel = creatureVoxel }, new List <GameComponent>(), storage); float minCost = float.MaxValue; var minAction = new MoveAction(); bool hasMinAction = false; foreach (var action in actions) { var vox = action.DestinationVoxel; float cost = edgeGoal.Heuristic(vox) * 10 + MathFunctions.Rand(0.0f, 0.1f) + agent.AI.Movement.Cost(action.MoveType); if (cost < minCost) { minAction = action; minCost = cost; hasMinAction = true; } } if (hasMinAction) { path.Add(minAction); creatureVoxel = minAction.DestinationVoxel; } else { foreach (var status in Die(agent)) { continue; } yield return(Act.Status.Success); yield break; } } if (path.Count == 0) { foreach (var status in Die(agent)) { continue; } yield return(Act.Status.Success); yield break; } agent.AI.Blackboard.SetData("GreedyPath", path); var pathAct = new FollowPathAct(agent.AI, "GreedyPath"); pathAct.Initialize(); foreach (Act.Status status in pathAct.Run()) { yield return(Act.Status.Running); } yield return(Act.Status.Running); } }
// 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, List <Body> 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.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 }); } } } } } } var storage = new MoveActionTempStorage(); 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, 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? 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; } 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, teleportObjects, storage); foreach (var a in actions.Where(a => a.DestinationState == currentstate)) { yield return(a); } } foreach (var a in GetMoveActions(new MoveState() { Voxel = v, VehicleState = new VehicleState() { Rail = rail, PrevRail = null } }, OctTree, teleportObjects, storage).Where(a => a.DestinationState == currentstate)) { yield return(a); } } } }
private IEnumerable <MoveAction> EnumerateSuccessors(MoveState state, VoxelHandle voxel, MoveActionTempStorage Storage, bool inWater, bool standingOnGround, bool topCovered, bool hasNeighbors, bool isRiding) { bool isClimbing = false; if (CanClimb || Can(MoveType.RideVehicle)) { //Climbing ladders. var bodies = Storage.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 = 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 { 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 (!Storage.Neighborhood[0, 1, 1].IsValid || Storage.Neighborhood[0, 1, 1].IsEmpty) { // +- x yield return(new MoveAction { Diff = new Vector3(0, 1, 1), MoveType = moveType }); } if (!Storage.Neighborhood[2, 1, 1].IsValid || Storage.Neighborhood[2, 1, 1].IsEmpty) { yield return(new MoveAction { Diff = new Vector3(2, 1, 1), MoveType = moveType }); } if (!Storage.Neighborhood[1, 1, 0].IsValid || Storage.Neighborhood[1, 1, 0].IsEmpty) { // +- z yield return(new MoveAction { Diff = new Vector3(1, 1, 0), MoveType = moveType }); } if (!Storage.Neighborhood[1, 1, 2].IsValid || Storage.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 (!Storage.Neighborhood[2, 1, 2].IsValid || Storage.Neighborhood[2, 1, 2].IsEmpty) { // +x + z yield return(new MoveAction { Diff = new Vector3(2, 1, 2), MoveType = moveType }); } if (!Storage.Neighborhood[2, 1, 0].IsValid || Storage.Neighborhood[2, 1, 0].IsEmpty) { yield return(new MoveAction { Diff = new Vector3(2, 1, 0), MoveType = moveType }); } if (!Storage.Neighborhood[0, 1, 2].IsValid || Storage.Neighborhood[0, 1, 2].IsEmpty) { // -x -z yield return(new MoveAction { Diff = new Vector3(0, 1, 2), MoveType = moveType }); } if (!Storage.Neighborhood[0, 1, 0].IsValid || Storage.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 (Storage.Neighborhood[dx, 1, dz].IsValid && !Storage.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 (!Storage.Neighborhood[dx, 1, dz].IsValid || Storage.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 = Storage.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 = Storage.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 = Storage.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 = Storage.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 = Storage.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 = Storage.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, List <Body> 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.ChunkData, 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.VehicleState.IsRidingVehicle; var neighborHoodBounds = new BoundingBox(Storage.Neighborhood[0, 0, 0].GetBoundingBox().Min, Storage.Neighborhood[2, 2, 2].GetBoundingBox().Max); Storage.NeighborObjects.Clear(); OctTree.EnumerateItems(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.ChunkData, GlobalVoxelCoordinate.FromVector3(obj.Position)) } } } } } ; var successors = EnumerateSuccessors(state, state.Voxel, Storage, inWater, standingOnGround, topCovered, hasNeighbors, isRiding); // Now, validate each move action that the creature might take. foreach (MoveAction v in successors) { 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 (!isRiding) { 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.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); } } } }
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 && Storage.Neighborhood[1, 0, 1].IsValid) { 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; if (Storage.Neighborhood[2, 1, 1].IsValid && !Storage.Neighborhood[2, 1, 1].IsEmpty) { wall = Storage.Neighborhood[2, 1, 1]; } else if (Storage.Neighborhood[0, 1, 1].IsValid && !Storage.Neighborhood[0, 1, 1].IsEmpty) { wall = Storage.Neighborhood[0, 1, 1]; } else if (Storage.Neighborhood[1, 1, 2].IsValid && !Storage.Neighborhood[1, 1, 2].IsEmpty) { wall = Storage.Neighborhood[1, 1, 2]; } else if (Storage.Neighborhood[1, 1, 0].IsValid && !Storage.Neighborhood[1, 1, 0].IsEmpty) { wall = Storage.Neighborhood[1, 1, 0]; } if (wall.IsValid) { isClimbing = true; if (Storage.Neighborhood[1, 2, 1].IsValid) { 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 && Storage.Neighborhood[1, 0, 1].IsValid) { 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) { // Check to see if there is headspace for a higher jump. //var highAbove = state.Voxel.Chunk.Manager.CreateVoxelHandle(Storage.Neighborhood[dx, 1, dz].Coordinate + new GlobalVoxelOffset(0, 2, 0)); //if (highAbove.IsValid && highAbove.IsEmpty) // yield return new MoveAction // { // SourceState = state, // Diff = new Vector3(dx, 2, dz), // MoveType = MoveType.HighJump, // DestinationVoxel = Storage.Neighborhood[dx, 2, dz], // CostMultiplier = 1.0f // }; //else 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 * }); * } * } */ }
public IEnumerable <Status> FindRandomPath() { var target = MathFunctions.RandVector3Cube() * Radius + Creature.AI.Position; if (Creature.AI.PositionConstraint.Contains(target) == ContainmentType.Disjoint) { target = MathFunctions.RandVector3Box(Creature.AI.PositionConstraint); } if (Is2D) { target.Y = Creature.AI.Position.Y; } var path = new List <MoveAction>(); var curr = Creature.Physics.CurrentVoxel; var bodies = Agent.World.PlayerFaction.OwnedObjects.Where(o => o.Tags.Contains("Teleporter")).ToList(); var storage = new MoveActionTempStorage(); var previousMoveState = new MoveState { Voxel = curr }; for (int i = 0; i < PathLength; i++) { var actions = Creature.AI.Movement.GetMoveActions(previousMoveState, bodies, storage); MoveAction?bestAction = null; var bestDist = float.MaxValue; foreach (MoveAction action in actions) { if (Is2D && (action.MoveType == MoveType.Climb || action.MoveType == MoveType.ClimbWalls || action.MoveType == MoveType.Fall)) { continue; } float dist = (action.DestinationVoxel.WorldPosition - target).LengthSquared() * Creature.AI.Movement.Cost(action.MoveType); if (dist < bestDist && !path.Any(a => a.DestinationVoxel == action.DestinationVoxel)) { bestDist = dist; bestAction = action; } } if (bestAction.HasValue && !path.Any(p => p.DestinationVoxel.Equals(bestAction.Value.DestinationVoxel) && p.MoveType == bestAction.Value.MoveType && Creature.AI.PositionConstraint.Contains(bestAction.Value.DestinationVoxel.WorldPosition + Vector3.One * 0.5f) == ContainmentType.Contains)) { MoveAction action = bestAction.Value; path.Add(action); previousMoveState = action.DestinationState; } else { break; } } if (path.Count > 0) { Creature.AI.Blackboard.SetData("RandomPath", path); yield return(Status.Success); } else { yield return(Status.Fail); } }
/// <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 <GameComponent>(mover.Creature.World.ChunkManager.Bounds.Min, mover.Creature.World.ChunkManager.Bounds.Max); List <GameComponent> playerObjects = new List <GameComponent>(mover.Creature.World.PlayerFaction.OwnedObjects); List <GameComponent> 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, teleportObjects, storage).ToList(); var foundGoalAdjacent = neighbors.FirstOrDefault(n => n.DestinationState.VehicleType == VehicleTypes.None && 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) * n.CostMultiplier; // 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.VehicleType == VehicleTypes.None ? 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) }); }