package ring.movement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import ring.mobiles.Mobile;
/**
* The main class that implements the movement logic of the MUD. It stores
* the adjacency list of Rooms and Portals and exposes methods for retrieving
* Destination objects.
* @author jeff
*
*/
public class LocationManager {
//Public constants for standard directions
public static final String NORTH = "north";
public static final String SOUTH = "south";
public static final String EAST = "east";
public static final String WEST = "west";
public static final String UP = "up";
public static final String DOWN = "down";
protected static HashMap<Room, HashMap<String, Portal>> worldGrid =
new HashMap<Room, HashMap<String, Portal>>();
private static Room getDestinationFromPortal(Portal portal) {
return portal.getDestination();
}
public static Room getOrigin() {
for (Room room : worldGrid.keySet()) {
return room;
}
return null;
}
public static Room getDestination(Room room, String portalName) {
try {
return getDestinationFromPortal(getPortal(room, portalName));
}
catch (PortalNotFoundException e) {
System.err.println("Portal identifier " + portalName + " not found.");
return null;
}
}
public static Portal getPortal(Room room, String portalName) throws PortalNotFoundException {
Portal p = worldGrid.get(room).get(portalName);
if (p == null)
throw new PortalNotFoundException("Portal identifier " + portalName + " not found");
else
return p;
}
public static List<Portal> getPortals(Room room) {
HashMap<String, Portal> ports = worldGrid.get(room);
if (ports == null || ports.isEmpty()) {
return null;
}
List<Portal> portList = new ArrayList<Portal>();
for (String key : ports.keySet()) {
portList.add(ports.get(key));
}
return portList;
}
/**
* The heart of the movement system. Given a Movable and a Portal, it
* implements the logic of the Movable going into that Portal and arriving
* at a new location. The movement is allowed if the movable itself can move,
* followed by checking if the portal is hidden, followed by if the movement
* is actually possible. Finally, it performs the actual move.
* <br/>
* <br/>
* Note that this method does not produce any indication to connected users
* that a move has occurred. It also does not deal with any object-specific
* necessities, such as subtracting MV points for mobiles. This must be dealt
* with by user code. The built-in movement commands handle this with a class
* called MoveAction.
* @param mov The Movable that will enter the portal <code>port</code>.
* @param port The portal to enter.
* @return true if the move was successful, false otherwise.
* @throws MovementAssertionException if the Portal being used is not at the Movable's current Location.
* @throws PortalNotFoundException if the Portal passed to the method is null.
*/
public static boolean move(Mobile mov, Portal port) throws MovementAssertionException, PortalNotFoundException {
if (port == null) {
throw new PortalNotFoundException("can't move into a null portal!");
}
//Absolute first thing we must check is if the Movable can move or not.
if (mov.canMove() == false) {
return false;
}
//Second thing we must check is if the portal is hidden.
if (port.isHidden()) {
//TODO pending skills rewrite
/*
if (mov.getSearchCheck() < port.getSearchDC())
return false;
*/
}
//Now that we are sure the portal is not hidden, we can continue.
Room locToMoveTo = port.getDestination();
//If the Movable can enter this location, proceed.
if (locToMoveTo.canEnter(mov)) {
Room leavingFrom = mov.getLocation();
//Assert that this is actually in the grid model.
boolean possible = assertMovementPossible(leavingFrom, port);
if (!possible) {
throw new MovementAssertionException("The Location being left does not have the Portal being moved to.");
}
else {
//Now that we know it's possible, perform the move.
synchronized (leavingFrom) {
synchronized (locToMoveTo) {
leavingFrom.removeMobile(mov);
mov.setLocation(locToMoveTo);
locToMoveTo.addMobile(mov);
return true;
}
}
}
}
else {
return false;
}
}
/**
* Tells the LocationManager whether or not the attempted move from the given
* Location to the given Portal is allowed. That is, if the Portal is in the
* Location's list of Portal objects stored in the global grid adjacency list.
* @param from
* @param to
* @return true if the movement is allowed, false otherwise.
*/
private static boolean assertMovementPossible(Room from, Portal to) {
List<Portal> exitPorts = getPortals(from);
return exitPorts.contains(to);
}
/**
* A very important method that forms the heart of constructing the world
* grid. It takes care of inserting new Room -> Portal -> Destination
* combinations into the adjacency list, or updating ones already present.
* @param room
* @param port
* @param dest
* @return true if the combination was added successfully, false if there was
* already a Destination at the specified Room and Portal "coordinate."
*/
public static boolean addToGrid(Room room, Portal port) throws WorldConstructionException {
if (port == null) {
throw new WorldConstructionException("Specified portal was null!");
}
//If this is a new coordinate, we need to add it.
if (getPortals(room) == null) {
return putNewGridEntry(room, port);
}
else {
return updateGridEntry(room, port);
}
}
/**
* A variant of the standard grid creation method that attempts to link
* back the room that was just added to allow for two-way travel. This
* simplifies the internal logic needed to link most rooms together.
* The method will first add the grid entry as normal for the two
* parameters. It will then try to do the reverse. For this to work,
* the Destination of the specified Portal must be a Room and the Portal's
* interactive name must be one of the standard 6 directions.
* @param room
* @param port
* @param autolink
* @return
*/
public static boolean addToGrid(Room room, Portal port, boolean autolink) throws WorldConstructionException {
if (autolink) {
//First add the current combination to the grid
boolean success = addToGrid(room, port);
//Now add an ad-hoc Portal in the opposite direction to the original room.
if (success) {
System.out.println("Creating reverse link for " + port.getDestination() + " to " + room);
String direction = null;
try {
direction = getOppositeDirection(port.getInteractiveName());
}
catch (IllegalArgumentException e) {
throw new WorldConstructionException("Cannot auto-link on non-standard directions!");
}
Room departFrom = port.getDestination();
Portal reversePort = new Portal(room, direction);
if (addToGrid(departFrom, reversePort)) {
return true;
}
else {
throw new WorldConstructionException("There was an error auto-linking " + room + " and " + port.getDestination());
}
}
else {
return false;
}
}
else {
return addToGrid(room, port);
}
}
private static boolean putNewGridEntry(Room room, Portal port) {
HashMap<String, Portal> ports = new HashMap<String, Portal>();
ports.put(port.getInteractiveName(), port);
HashMap<String, Portal> old = worldGrid.put(room, ports);
if (old != null)
return false;
else
return true;
}
private static boolean updateGridEntry(Room room, Portal port) {
HashMap<String, Portal> ports = worldGrid.get(room);
worldGrid.put(room, ports);
return true;
}
/**
* Utility method that returns the opposite direction of a standard direction.
* @param direction
* @return the opposite direction (north from south, etc)
* @throws IllegalArgumentException if a non-standard direction is specified.
*/
public static String getOppositeDirection(String direction) {
if (direction.equalsIgnoreCase(NORTH)) return SOUTH;
else if (direction.equalsIgnoreCase(SOUTH)) return NORTH;
else if (direction.equalsIgnoreCase(EAST)) return WEST;
else if (direction.equalsIgnoreCase(WEST)) return EAST;
else if (direction.equalsIgnoreCase(UP)) return DOWN;
else if (direction.equalsIgnoreCase(DOWN)) return UP;
throw new IllegalArgumentException("incorrect standard direction");
}
public static boolean isStandardDirection(String direction) {
return (direction.equals(NORTH) || direction.equals(SOUTH) ||
direction.equals(EAST) || direction.equals(WEST) ||
direction.equals(UP) || direction.equals(DOWN));
}
}