/*
* (c) 2000-2009 Carlos G�mez Rodr�guez, todos los derechos reservados / all rights reserved.
* Licencia en license/bsd.txt / License in license/bsd.txt
*/
package eu.irreality.age;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import eu.irreality.age.debug.Debug;
//package AetheriaAWT;
/**
* Camino de una habitaci�n a otra (salida de la habitaci�n)
*
*/
public class Path extends Entity implements Descriptible
{
//CONSTANTS
public static final int
NORTE=0,SUR=1,OESTE=2,ESTE=3,
NOROESTE=4,NORDESTE=5,SUROESTE=6,SUDESTE=7,
ARRIBA=8,ABAJO=9;
public static final int
NORTH=NORTE,SOUTH=SUR,WEST=OESTE,EAST=ESTE,
NORTHWEST=NOROESTE,NORTHEAST=NORDESTE,SOUTHWEST=SUROESTE,
SOUTHEAST=SUDESTE,UP=ARRIBA,DOWN=ABAJO;
public static final int NO_DIRECTION = -1;
private World mundo;
/**
* This method is only used to save Path objects to XML, and NOT to parse direction names or output them to
* the player. Therefore, it is not a multilanguage method and must not be used as such.
* The correct multi-language way to do this is to use the getNamesForDirection(int direction) method in class World.
* @param dir
* @return
*/
public static final String directionName ( int dir )
{
String s;
switch ( dir )
{
case NORTE: s = "norte" ; break;
case SUR: s = "sur" ; break;
case OESTE: s = "oeste" ; break;
case ESTE: s = "este" ; break;
case NOROESTE: s = "noroeste" ; break;
case NORDESTE: s = "nordeste" ; break;
case SUROESTE: s = "suroeste" ; break;
case SUDESTE: s = "sudeste" ; break;
case ARRIBA: s = "arriba" ; break;
case ABAJO: default: s = "abajo" ; break;
}
return s;
}
/**
* This method is only used to build Path objects from XML, and NOT to parse direction names or output them to
* the player. Therefore, it is not a multilanguage method and must not be used as such.
* @param dir
* @return
*/
public static final int nameToDirection ( String nombre )
{
String name = nombre.toLowerCase();
if ( name.equalsIgnoreCase("norte") ) return NORTE;
if ( name.equalsIgnoreCase("sur") ) return SUR;
if ( name.equalsIgnoreCase("este") ) return ESTE;
if ( name.equalsIgnoreCase("oeste") ) return OESTE;
if ( name.equalsIgnoreCase("noroeste") ) return NOROESTE;
if ( name.equalsIgnoreCase("nordeste") ) return NORDESTE;
if ( name.equalsIgnoreCase("suroeste") ) return SUROESTE;
if ( name.equalsIgnoreCase("sudeste") ) return SUDESTE;
if ( name.equalsIgnoreCase("arriba") ) return ARRIBA;
if ( name.equalsIgnoreCase("abajo") ) return ABAJO;
else
{
Debug.println("Found strange std. path direction: " + name);
( new Exception() ).printStackTrace();
return 10; //unknown!
}
}
//INSTANCE VARIABLES
protected byte exitTime;
protected Description[] descriptionList;
//protected boolean isStandard;
String[] exitCommand; //comandos que la activan.
int destination; //ID
protected boolean peerable;
//int keyid; //ID
protected boolean isStandard;
Inventory keys; //llaves que lo abren
protected Item associatedItem = null; //el item del que toma el estado (si no es null)
private int direction = NO_DIRECTION; //from 2014-09-20 we store the direction of standard exits explicitly. Not storing it was unwieldy.
public void setDestination ( Room r )
{
destination = r.getID();
}
//mainly SHALLOW copy
public Object clone()
{
Path p = new Path ();
p.mundo = this.mundo;
p.exitTime = this.exitTime;
p.descriptionList = new Description[ this.descriptionList.length ];
p.isStandard = this.isStandard;
p.destination = this.destination;
p.associatedItem = this.associatedItem;
p.peerable = peerable;
if ( this.keys != null )
p.keys = (Inventory) this.keys.clone();
else
p.keys = this.keys;
for ( int i = 0 ; i < this.descriptionList.length ; i++ )
{
p.descriptionList[i] = (Description) this.descriptionList[i].clone();
}
if ( this.exitCommand != null )
{
p.exitCommand = new String[ this.exitCommand.length ];
for ( int i = 0 ; i < this.exitCommand.length ; i++ )
{
p.exitCommand[i] = this.exitCommand[i];
}
}
else
{
p.exitCommand = null;
}
return p;
}
//CONSTRUCTORS
private Path()
{
;
}
Path ( World mundo , boolean isStandard , String curToken )
{
this.mundo = mundo;
byte ntokens = (byte) StringMethods.numToks( curToken , '$' );
this.isStandard = isStandard;
try
{
destination = Integer.valueOf(StringMethods.getTok( curToken , 1 , '$' )).intValue();
}
catch ( NumberFormatException NumExc )
{
destination = 0; //habitaci�n no v�lida.
}
descriptionList = Utility.loadDescriptionListFromString( StringMethods.getTok( curToken , 2 , '$' ) );
if ( ntokens > 2 ) exitTime = Byte.valueOf(StringMethods.getTok( curToken , 3 , '$' )).byteValue();
if ( isStandard )
{
if ( ntokens > 3 ) peerable = Boolean.valueOf(StringMethods.getTok( curToken , 4 , '$' )).booleanValue();
if ( ntokens > 4 )
{
//estado: puede ser "n�mero" o "item $+ n�mero"
//Si tiene "item", la salida tiene un "objeto asociado", y toma su estado. (ej. puerta)
String thetoken = StringMethods.getTok( curToken , 5 , '$' );
if ( thetoken.length() > 4 && thetoken.substring(0,4).equalsIgnoreCase("item") )
{
//est� asociado a un item. No guardamos el estado, sino que
//getState() devolver� el de ese item, se lo asociamos.
associatedItem = mundo.getItem ( thetoken.substring(4) );
}
else
{
setNewState( Integer.valueOf(StringMethods.getTok( curToken , 5 , '$' )).intValue() );
}
}
if ( ntokens > 5 )
{
//init key ID's list
String thetoken = StringMethods.getTok( curToken , 6 , '$' );
int nKeys = StringMethods.numToks(thetoken,'&');
keys = new Inventory(10000000,10000000); //limites de volumen y peso inalcanzables
for ( int i = 0 ; i < nKeys ; i++ )
{
try
{
if ( Integer.valueOf(StringMethods.getTok(thetoken,i+1,'&')).intValue() > 0 )
keys.addItem ( mundo.getItem(StringMethods.getTok(thetoken,i+1,'&'))) ;
}
catch (Exception exc)
{
mundo.write("Excepci�n absurda. �Habr� alguna llave muy pesada?");
Debug.println(exc);
}
}
}
else
{
keys = new Inventory(10000000,10000000);
}
}
else
{
if ( ntokens > 3 )
{
String commandList = StringMethods.getTok ( curToken , 4 , '$' );
exitCommand = new String [ StringMethods.numToks ( commandList , '&' ) ];
for ( int j = 0 ; j < exitCommand.length ; j++ )
exitCommand[j] = StringMethods.getTok ( commandList , j+1 , '&' );
}
if ( ntokens > 4 ) peerable = Boolean.valueOf(StringMethods.getTok( curToken , 5 , '$' )).booleanValue();
if ( ntokens > 5 )
{
//estado: puede ser "n�mero" o "item $+ n�mero"
//Si tiene "item", la salida tiene un "objeto asociado", y toma su estado. (ej. puerta)
String thetoken = StringMethods.getTok( curToken , 6 , '$' );
if ( thetoken.length() > 4 && thetoken.substring(0,4).equalsIgnoreCase("item") )
{
//est� asociado a un item. No guardamos el estado, sino que
//getState() devolver� el de ese item, se lo asociamos.
associatedItem = mundo.getItem ( thetoken.substring(4) );
}
else
{
setNewState( Integer.valueOf(StringMethods.getTok( curToken , 6 , '$' )).intValue() );
}
}
if ( ntokens > 6 )
{
//init key ID's list
String thetoken = StringMethods.getTok( curToken , 7 , '$' );
int nKeys = StringMethods.numToks(thetoken,'&');
keys = new Inventory(10000000,10000000);
for ( int i = 0 ; i < nKeys ; i++ )
{
try
{
if ( Integer.valueOf(StringMethods.getTok(thetoken,i+1,'&')).intValue() > 0 )
keys.addItem ( mundo.getItem(StringMethods.getTok(thetoken,i+1,'&')) );
}
catch (Exception exc)
{
mundo.write("Excepci�n absurda. �Habr� alguna llave muy pesada?");
}
}
}
else
{
keys = new Inventory(10000000,10000000);
}
}
/*
try
{
Debug.println(getXMLRepresentation( javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() ));
}
catch ( javax.xml.parsers.ParserConfigurationException pce )
{
Debug.println(pce);
}
*/
}
//METHODS
public boolean isValid ( )
{
return ( destination != 0 );
}
public int getDestinationID ( )
{
return destination;
}
/**
* Method to get the path destination, added 2014-02-17.
* Sadly the AGE core still uses a lot of numeric room IDs to implement moving, but this way
* at least the programmer can easily access the destination of a path.
*/
public Room getDestination ( )
{
return mundo.getRoom(destination);
}
public String getDescription ( long comparand )
{
String desString="";
for ( int i = 0 ; i < descriptionList.length ; i++ )
{
if ( descriptionList[i].matches(comparand) )
desString += descriptionList[i].getText();
}
return desString;
}
public String getDescription ( Entity viewer )
{
String desString="";
for ( int i = 0 ; i < descriptionList.length ; i++ )
{
if ( descriptionList[i].matchesConditions(this,viewer) )
desString += descriptionList[i].getText();
}
return desString;
}
/**
* Nos dice si la salida "se da por aludida" ante el contenido de un comando "ir" (por ejemplo, "Norte").
* This is a legacy method. It is not used anymore by the AGE kernel since 2013-03-22, but it *is* used by some games, so it must
* be kept.
*
* return Si la salida se corresponde al comando.
*/
public boolean matchExitCommand ( String toParse )
{
for ( int i = 0 ; i < exitCommand.length ; i++ )
//if ( exitCommand[i].equalsIgnoreCase(toParse) ) return true;
if ( toParse.toLowerCase().endsWith(exitCommand[i].toLowerCase()) ) return true;
if ( isStandard() && direction != NO_DIRECTION ) //redundant and, if it's standard it should have a direction
{
if ( mundo.argumentsToDirection(toParse) == direction )
return true;
}
return false;
}
/**
* Nos dice si la salida "se da por aludida" ante el contenido de un comando "ir" (por ejemplo, "Norte").
* 2013-03-23
*
* return >= 0 si esta salida se corresponde al comando, < 0 de lo contrario. En caso de >= 0, el valor de retorno
* es la longitud del nombre de la salida con el cual se corresponde el comando.
*/
public int matchExitCommand ( String toParse , boolean lenient )
{
int match = -1;
for ( int i = 0 ; i < exitCommand.length ; i++ )
{
//if ( exitCommand[i].equalsIgnoreCase(toParse) ) return true;
if ( !lenient && toParse.toLowerCase().endsWith(exitCommand[i].toLowerCase()) )
{
if ( exitCommand[i].length() > match )
match = exitCommand[i].length();
}
if ( lenient && toParse.toLowerCase().contains(exitCommand[i].toLowerCase()) )
{
if ( exitCommand[i].length() > match )
match = exitCommand[i].length();
}
}
return match;
}
public String getNonStandardName ( )
{
Debug.println("Path: " + destination + " " + isStandard );
if ( exitCommand.length > 0 )
return exitCommand[0];
else
return mundo.getMessages().getMessage("unnamed.path");
}
public String[] getNonStandardNames ( )
{
return exitCommand;
}
void go ( Mobile c )
{
c.setNewState ( 2 /*GO*/ , exitTime * 12 / c.getStat("velocidad") /*add modifiers*/ );
c.setNewTarget ( destination );
Debug.println("Target is: " + c.getTarget());
Debug.println("Entity is: " + c.getID());
Debug.println("Exit time: " + exitTime);
}
public void changeState ( World mundo )
{
//definirlo...
}
public int getState ( )
{
//overrideada de Entity
if ( associatedItem == null )
return super.getState();
else
return associatedItem.getState();
}
//override open/close states with associated item's states
public boolean getPropertyValueAsBoolean ( String propName )
{
if ( propName.equals("closed") || propName.equals("locked") )
{
if ( associatedItem == null )
return super.getPropertyValueAsBoolean ( propName );
else
return associatedItem.getPropertyValueAsBoolean ( propName );
}
else
return super.getPropertyValueAsBoolean ( propName );
}
public boolean isOpen()
{
return ( (256 & getState()) == 0 && !getPropertyValueAsBoolean("closed") ) ;
}
public boolean isClosed()
{
return !isOpen();
}
public boolean isLocked()
{
return !isUnlocked();
}
public boolean isUnlocked()
{
return ( (512 & getState()) == 0 && !getPropertyValueAsBoolean("locked") );
}
public boolean unlocksWithKey( Item key )
{
if ( associatedItem != null )
{
return associatedItem.unlocksWithKey(key);
}
else
{
for ( int i = 0 ; i < keys.size() ; i++ )
{
if ( keys.elementAt(i).equals(key) ) return true;
}
return false;
}
}
//tiene una direccion? (norte, arriba, etc.)
public boolean isStandard()
{
return isStandard;
}
//tiene comandos personalizados?
public boolean isExtended()
{
return ( exitCommand != null && exitCommand.length > 0 );
}
//dado "norte" devuelve "sur", "este" devuelve "oeste", etc.
//�Para qu� sirve esto? Para, por ejemplo, transformar "va hacia el norte" en "llega
//desde el sur", y as� poder informar a la habitaci�n de salida y de llegada, aunque
//realmente un path en un grafo dirigido s�lo sea una salida. En el caso de las salidas
//personalizadas no se hilar� tan fino, evidentemente esta funci�n no sabr� convertirlas.
//En todo caso, en general no ser� necesario (si un bicho va por la puerta, llegar� a la
//habitaci�n destino desde la puerta).
public static String invert ( String s )
{
String temp = StringMethods.textualSubstitution( s , "norte" , "$%%N%%$" );
temp = StringMethods.textualSubstitution( temp , "sur" , "$%%S%%$" );
temp = StringMethods.textualSubstitution( temp , "oeste" , "$%%W%%$" );
temp = StringMethods.textualSubstitution( temp , "este" , "$%%E%%$" );
temp = StringMethods.textualSubstitution( temp , "arriba" , "$%%U%%$" );
temp = StringMethods.textualSubstitution( temp , "abajo" , "$%%D%%$" );
temp = StringMethods.textualSubstitution( temp , "dentro" , "$%%I%%$" );
temp = StringMethods.textualSubstitution( temp , "fuera" , "$%%O%%$" );
temp = StringMethods.textualSubstitution( temp , "$%%N%%$" , "sur" );
temp = StringMethods.textualSubstitution( temp , "$%%S%%$" , "norte" );
temp = StringMethods.textualSubstitution( temp , "$%%E%%$" , "oeste" );
temp = StringMethods.textualSubstitution( temp , "$%%W%%$" , "este" );
temp = StringMethods.textualSubstitution( temp , "$%%U%%$" , "abajo" );
temp = StringMethods.textualSubstitution( temp , "$%%D%%$" , "arriba" );
temp = StringMethods.textualSubstitution( temp , "$%%I%%$" , "fuera" );
temp = StringMethods.textualSubstitution( temp , "$%%O%%$" , "dentro" );
return temp;
}
public Path ( World mundo , org.w3c.dom.Node n ) throws XMLtoWorldException
{
this.mundo=mundo;
if ( ! ( n instanceof org.w3c.dom.Element ) )
{
throw ( new XMLtoWorldException ( "Path node not Element" ) );
}
else
{
//default values
peerable = false;
isStandard = false;
org.w3c.dom.Element e = (org.w3c.dom.Element) n;
try
{
if ( !e.hasAttribute("destination") )
throw ( new XMLtoWorldException ( "Destination attribute missing at path. Node is " + n ) );
destination = Integer.valueOf ( e.getAttribute("destination") ) . intValue();
}
catch ( NumberFormatException nfe )
{
int dest = mundo.roomNameToID ( e.getAttribute("destination") );
if ( dest >= 0 )
{
destination = dest;
}
else
{
throw ( new XMLtoWorldException("Destination attribute invalid at path. Value found is " + "\"" + e.getAttribute("destination") + "\"" ));
}
}
try
{
if ( !e.hasAttribute("exitTime") )
//default exit time: 30
exitTime = 30;
//throw ( new XMLtoWorldException ( "exitTime attribute missing at path" ) );
else
exitTime = Byte.valueOf ( e.getAttribute("exitTime") ) . byteValue();
}
catch ( NumberFormatException nfe )
{
throw ( new XMLtoWorldException("exitTime attribute invalid at path"));
}
if ( e.hasAttribute("peerable") )
peerable = Boolean.valueOf ( e.getAttribute("peerable") ) . booleanValue();
if ( e.hasAttribute("standard") )
isStandard = Boolean.valueOf ( e.getAttribute("standard") ) . booleanValue();
//destination, exit time, peerable and isstandard have been set here
//Entity parsing
readPropListFromXML ( mundo , n );
//now, set description list, exit commands, keys inventory and associated item,
//which are nodes coming in any order.
org.w3c.dom.NodeList descrListNodes = e.getElementsByTagName( "DescriptionList" );
if ( descrListNodes.getLength() > 0 )
{
org.w3c.dom.Element descrListNode = (org.w3c.dom.Element) descrListNodes.item(0);
org.w3c.dom.NodeList descrNodes = descrListNode.getElementsByTagName ( "Description" );
descriptionList = new Description[descrNodes.getLength()];
for ( int i = 0 ; i < descrNodes.getLength() ; i++ )
{
org.w3c.dom.Element descrNode = (org.w3c.dom.Element) descrNodes.item(i);
try
{
descriptionList[i] = new Description(mundo,descrNode);
}
catch ( XMLtoWorldException xe )
{
throw ( new XMLtoWorldException ( "Error at path description: " + xe.getMessage() ) );
}
}
}
//prevent null pointer exception if there's no description list
if ( descriptionList == null ) descriptionList = new Description[0];
org.w3c.dom.NodeList cmdListNodes = e.getElementsByTagName( "CommandList" );
if ( cmdListNodes.getLength() > 0 )
{
org.w3c.dom.Element cmdListNode = (org.w3c.dom.Element) cmdListNodes.item(0);
org.w3c.dom.NodeList cmdNodes = cmdListNode.getElementsByTagName ( "Command" );
exitCommand = new String[cmdNodes.getLength()];
for ( int i = 0 ; i < cmdNodes.getLength() ; i++ )
{
org.w3c.dom.Element cmdNode = (org.w3c.dom.Element) cmdNodes.item(i);
if ( cmdNode.hasAttribute("name") )
exitCommand[i] = cmdNode.getAttribute("name");
else
throw ( new XMLtoWorldException ( "Error at path: exit command without a name") );
}
}
org.w3c.dom.NodeList keyListNodes = e.getElementsByTagName( "KeyList" );
if ( keyListNodes.getLength() > 0 )
{
org.w3c.dom.Element keyListNode = (org.w3c.dom.Element) keyListNodes.item(0);
org.w3c.dom.NodeList keyInvList = keyListNode.getElementsByTagName ( "Inventory" );
if ( keyInvList.getLength() > 0 )
{
org.w3c.dom.Element keyInvElt = (org.w3c.dom.Element) keyInvList.item(0);
try
{
keys = new Inventory (mundo,keyInvElt);
}
catch ( XMLtoWorldException xe )
{
throw ( new XMLtoWorldException ( "Error at path key ID's inventory: " + xe.getMessage() ) );
}
}
else
keys = new Inventory(10000,10000);
}
org.w3c.dom.NodeList assocItemNodes = e.getElementsByTagName( "AssociatedItem" );
if ( assocItemNodes.getLength() > 0 )
{
org.w3c.dom.Element assocItemNode = (org.w3c.dom.Element) assocItemNodes.item(0);
if ( assocItemNode.hasAttribute("id") )
{
associatedItem = mundo.getItem ( assocItemNode.getAttribute("id") );
}
else
{
throw ( new XMLtoWorldException ( "Error at path, associated item has no attribute named id.") );
}
}
//for standard exits, we now store the direction explicitly (2014-09-20)
if ( e.hasAttribute("direction") )
{
direction = Path.nameToDirection( e.getAttribute("direction") );
}
} //end if the node is an element
}
/**
* Obtains the direction of the exit, if it's standard (if not, it will be -1: NO_DIRECTION)
* @return
*/
public int getDirection()
{
return direction;
}
//probably we could do this more elegantly now because we store the direction in the Path explicitly, but well, it's here
public org.w3c.dom.Node getXMLRepresentation ( org.w3c.dom.Document doc , String standardExitNameAttr )
{
org.w3c.dom.Node n = getXMLRepresentation ( doc );
if ( n instanceof org.w3c.dom.Element ) //seguro que lo es
((org.w3c.dom.Element)n).setAttribute ( "direction" , standardExitNameAttr );
return n;
}
public org.w3c.dom.Node getXMLRepresentation ( org.w3c.dom.Document doc )
{
org.w3c.dom.Element suElemento = doc.createElement( "Path" );
suElemento.setAttribute ( "destination" , String.valueOf( destination ) );
suElemento.setAttribute ( "exitTime" , String.valueOf ( exitTime ) );
suElemento.setAttribute ( "peerable" , String.valueOf ( peerable ) );
suElemento.setAttribute ( "standard" , String.valueOf ( isStandard ) );
suElemento.appendChild ( getPropListXMLRepresentation ( doc ) );
org.w3c.dom.Element listaDesc = doc.createElement("DescriptionList");
for ( int i = 0 ; i < descriptionList.length ; i++ )
{
Description nuestraDescripcion = descriptionList[i];
listaDesc.appendChild ( nuestraDescripcion.getXMLRepresentation(doc) );
}
suElemento.appendChild(listaDesc);
if ( exitCommand != null )
{
org.w3c.dom.Element listaCmd = doc.createElement("CommandList");
for ( int i = 0 ; i < exitCommand.length ; i++ )
{
String comandoSalida = exitCommand[i];
org.w3c.dom.Element elementoComando = doc.createElement( "Command" );
elementoComando.setAttribute ( "name" , exitCommand[i] );
listaCmd.appendChild(elementoComando);
}
suElemento.appendChild(listaCmd);
}
if ( keys != null )
{
org.w3c.dom.Element eltLlaves = doc.createElement("KeyList");
org.w3c.dom.Node listaLlaves = keys.getXMLRepresentation(doc);
eltLlaves.appendChild(listaLlaves);
suElemento.appendChild(eltLlaves);
}
if ( associatedItem != null )
{
org.w3c.dom.Element item = doc.createElement("AssociatedItem");
item.setAttribute( "id" , String.valueOf(associatedItem.getID()) );
suElemento.appendChild(item);
}
return suElemento;
}
public int getID()
{
//a path has no ID! (at the mom't at least)
return -1;
}
}