package com.rebelkeithy.ftl.crew; import java.util.HashSet; import java.util.LinkedList; import java.util.Queue; import java.util.Set; import java.util.Stack; import com.rebelkeithy.ftl.Clock; import com.rebelkeithy.ftl.properties.Properties; import com.rebelkeithy.ftl.ship.Direction; import com.rebelkeithy.ftl.ship.Door; import com.rebelkeithy.ftl.ship.Room; import com.rebelkeithy.ftl.ship.Tile; public class CrewPathfinder { private double crewX; private double crewY; private Tile targetTile; private double targetX; private double targetY; //private Room room; private int roomX; private int roomY; private Stack<Door> path; // TODO: state implementation is too rigid, trying to change paths in the middle of a path will cause things to quickly become too // complex. Instead this should build a path of waypoints, with waypoints on either side of each door. It should check for the // crew member to reach the edge of a room and then try to unlock a door, and change the crew member's room when it detects the crew // member moving over the edge of a room public final int IDLE = 0; public final int MOVING_TO_DOOR = 1; // crew is moving to the tile in front of the door public final int MOVING_IN_DOOR = 2; // crew is moving up to the door, but is has not opened yet public final int MOVING_OUT_DOOR = 3; // the door has opened and crew is moving to the tile on the other side public final int ATTACKING_DOOR = 4; // door has not opened and the crew is trying to break it down public final int MOVING_TO_POSITION = 5; // crew has reached the final room and is moving to the correct position public final int MOVING_TO_START = 6; // Used to get crew in position if he was already moving and the path changed public int state; public CrewPathfinder() { state = IDLE; path = new Stack<Door>(); } // This only updates internal variables, it does not change crew variables. public void update(double dt, Crew crew) { crewX = crew.getX(); crewY = crew.getY(); if(state == IDLE) return; if(state == MOVING_TO_DOOR) { moveToTarget(crew.getSpeed() * dt); } if(state == MOVING_IN_DOOR) { moveToTarget(crew.getSpeed() * dt); } if(state == MOVING_OUT_DOOR) { moveToTarget(crew.getSpeed() * dt); Door door = path.peek(); if(crew.getRoom() == door.room1 && distSqr() < 0.25) { Clock.log(crew.getName() + " now in room " + door.room2.getName()); door.room1.removeCrew(crew); door.room2.addCrew(crew); crew.setRoom(door.room2); } } if(state == MOVING_TO_POSITION) { moveToTarget(crew.getSpeed() * dt); } if(crewX == targetX && crewY == targetY) nextState(crew); } public double distSqr() { return (crewX - targetX) * (crewX - targetX) + (crewY - targetY) * (crewY - targetY); } private void moveToTarget(double speed) { if(Math.abs(crewX - targetX) < 0.1) crewX = targetX; else if(crewX < targetX) crewX += speed; else if(crewX > targetX) crewX -= speed; if(Math.abs(crewY - targetY) < 0.1) crewY = targetY; else if(crewY < targetY) crewY += speed; else if(crewY > targetY) crewY -= speed; } private void nextState(Crew crew) { if(state == MOVING_TO_DOOR) { state = MOVING_IN_DOOR; Door door = path.peek(); setTargetAtDoor(door); } else if(state == MOVING_IN_DOOR) { Door door = path.peek(); if(door.unlock(crew)) { door.addUser(crew); state = MOVING_OUT_DOOR; setTargetBehindDoor(door); } else { state = ATTACKING_DOOR; } } else if(state == MOVING_OUT_DOOR) { Door door = path.pop(); // Move on to the next door door.removeUser(crew); // Let the door know it can close if(path.isEmpty()) { state = MOVING_TO_POSITION; targetX = roomX; targetY = roomY; } else { state = MOVING_TO_DOOR; setTargetInFrontOfDoor(path.peek()); } } else if(state == ATTACKING_DOOR) { Door door = path.peek(); door.attack(crew); if(door.unlock(crew)) { door.addUser(crew); state = MOVING_OUT_DOOR; setTargetBehindDoor(door); } } else if(state == MOVING_TO_POSITION) { state = IDLE; } } private void setTargetAtDoor(Door door) { setTargetRelativeToDoor(door, -0.25); } private void setTargetBehindDoor(Door door) { setTargetRelativeToDoor(door, 0.5); } private void setTargetInFrontOfDoor(Door door) { setTargetRelativeToDoor(door, -0.5); } private void setTargetRelativeToDoor(Door door, double offset) { if(door.direction == Direction.UP) { targetX = door.getX(); targetY = (door.getY() + offset - 0.5); } if(door.direction == Direction.DOWN) { targetX = door.getX(); targetY = (door.getY() - offset - 0.5); } if(door.direction == Direction.LEFT) { targetX = (door.getX() - offset - 0.5); targetY = door.getY(); } if(door.direction == Direction.RIGHT) { targetX = (door.getX() + offset - 0.5); targetY = door.getY(); } } public Tile setPath(Crew crew, Room target) { Tile targetTile = null; for(int y = target.getHeight()-1; y >= 0 ; y--) { for(int x = 0; x < target.getWidth(); x++) { Properties p = target.getTile(x, y).getProperties(); if(!p.containsValue("crew") && !p.containsValue("blocked")) { targetTile = target.getTile(x, y); break; } else if(p.containsValue("crew")) { if(p.getString("crew").equals(crew.getName())) { return null; } } } if(targetTile != null) { break; } } if(targetTile == null) { return null; } return setPath(crew, target, targetTile.getX(), targetTile.getY()); } public Tile setPath(Crew crew, Room target, int x, int y) { if(state != IDLE) { Clock.log("Wait for crew member to finish current path"); //return null; } //room = target; roomX = target.getX() + x; roomY = target.getY() + y; path = new Stack<Door>(); HashSet<Door> searched = new HashSet<Door>(); Queue<Node> front = new LinkedList<Node>(); path = bfsSearthStart(target, crew.getRoom(), searched, front); Clock.log("found path " + path); if(path != null) { if(targetTile != null) { targetTile.getProperties().removeProperty("crew"); } targetTile = target.getTile(x, y); target.getTile(x, y).getProperties().setString("crew", crew.getName()); if(state != IDLE) { } if(!path.isEmpty()) { state = MOVING_TO_DOOR; setTargetInFrontOfDoor(path.peek()); } else { state = MOVING_TO_POSITION; targetX = roomX; targetY = roomY; } } else { return null; } return target.getTile(x, y); } public void cancel(Crew crew) { targetTile.getProperties().setString("crew", crew.getName()); } private class Node { Node parent; Door door; public Node(Node parent, Door door) { this.parent = parent; this.door = door; } } private Stack<Door> bfsSearthStart(Room target, Room start, Set<Door> visited, Queue<Node> front) { if(target == start) { return new Stack<Door>(); } Stack<Door> best = null; int bestLength = Integer.MAX_VALUE; for(Door door : start.getDoors()) { if(door.room2 == null) continue; visited.clear(); front.clear(); Node node = bfsSearch(target, new Node(null, door), visited, front); if(node != null) { Stack<Door> path = new Stack<Door>(); while(node != null) { path.push(node.door); node = node.parent; } float length = path.size(); if(best == null || length < bestLength) { best = path; bestLength = (int) length; } } } return best; } private Node bfsSearch(Room target, Node current, Set<Door> visited, Queue<Node> front) { if(visited.contains(current.door)) { return null; } else if(current.door.room2 == target) { return current; } else { visited.add(current.door); if(current.door.room2 != null) { for(Door door : current.door.room2.getDoors()) { Node node = new Node(current, door); front.add(node); } } while(!front.isEmpty()) { Node node = front.poll(); Node path = bfsSearch(target, node, visited, front); if(path != null) { return path; } } } return null; } public double getX() { return crewX; } public double getY() { return crewY; } public int getState() { return state; } public int targetX() { return roomX; } public int targetY() { return roomY; } }