/*
* (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.net.*;
import java.io.*;
import java.util.*;
import eu.irreality.age.util.xml.DOMUtils;
import eu.irreality.age.debug.Debug;
public class AGEClientProxy implements MultimediaInputOutputClient , ARSPConstants
{
private Hashtable colorCodesTable = new Hashtable();
//options by default
boolean opt_sound = false;
boolean opt_images = false;
boolean opt_color = false;
boolean opt_title = false;
private World mundo;
private Thread asyncModeThread;
private SoundClient sh;
public String getColorCode ( String colorType )
{
if ( colorType == null ) return "";
String code = (String) colorCodesTable.get(colorType);
Debug.println("CLPROXY CODEGET" + code);
if ( code == null ) return "";
else return code;
}
public boolean isColorEnabled()
{ return opt_color;
}
public boolean isLoggingEnabled()
{ return true;
}
public boolean isMemoryEnabled()
{ return false;
}
public boolean isTitleEnabled()
{ return opt_title;
}
/**
* @deprecated Use {@link #clearScreen()} instead
*/
public void borrarPantalla()
{
clearScreen();
}
public void clearScreen()
{
pw.println(CLEAR_SCREEN);
pw.flush();
}
/**
* @deprecated Use {@link #write(String)} instead
*/
public synchronized void escribir(String s) //synchronized para evitar conflicto de escrituras del GameEngineThread con escrituras del thread del ClientProxy (enviar ficheros...)
{
write(s);
}
public synchronized void write(String s) //synchronized para evitar conflicto de escrituras del GameEngineThread con escrituras del thread del ClientProxy (enviar ficheros...)
{
pw.println(WRITE+" "+StringMethods.textualSubstitution(s,"\n","\\n"));
Debug.println("WROTE: " + WRITE+" "+StringMethods.textualSubstitution(s,"\n","\\n"));
pw.flush();
}
/**
* @deprecated Use {@link #writeTitle(String)} instead
*/
public void escribirTitulo(String s)
{
writeTitle(s);
}
public void writeTitle(String s)
{
pw.println(WRITE_TITLE+" "+s);
pw.flush();
}
/**
* @deprecated Use {@link #writeTitle(String,int)} instead
*/
public void escribirTitulo(String s,int pos)
{
writeTitle(s, pos);
}
public void writeTitle(String s,int pos)
{
pw.println(WRITE_TITLE+" "+s+" "+pos);
pw.flush();
}
/**
* @deprecated Use {@link #forceInput(String,boolean)} instead
*/
public void forzarEntrada(String s,boolean output_enabled)
{
forceInput(s, output_enabled);
}
public void forceInput(String s,boolean output_enabled)
{
pw.println(FORCE_INPUT+" "+output_enabled+" "+s);
pw.flush();
}
boolean clientHasDisconnected=false;
public boolean isDisconnected()
{
return clientHasDisconnected;
}
//blocking:
public String getInput(Player p)
{
setSynchronousMode();
pw.println(GET_INPUT);
Debug.println("WROTE: " + GET_INPUT);
pw.flush();
try
{
for (;;)
{
//escribir("SynchronousInput> ");
String str;
synchronized(this)
{
Debug.println("Gonna read.");
str = br.readLine();
}
Debug.println("Line read is " + str);
if ( str == null )
{
clientHasDisconnected=true;
return null;
}
StringTokenizer st = new StringTokenizer ( str );
String cmd;
if ( st.hasMoreTokens() )
cmd = st.nextToken();
else
{
Debug.println("EMPTY CMD");
continue;
}
String args;
if ( cmd.equalsIgnoreCase ( GET_INPUT_RETURN ) )
{
if ( st.hasMoreTokens() ) args = st.nextToken("").trim();
else args = "";
Debug.println("INPUT GOTTEN: " + args );
return args;
}
else if ( cmd.equalsIgnoreCase( GET_INPUT_RETURN_NULL ) )
{
//remote client's getInput will return null only if window has been closed.
clientHasDisconnected=true;
return null;
}
else
{
parseRequest ( str );
}
}
}
catch ( IOException ioe )
{
Debug.println("Uh oh, an IOException!!\n");
clientHasDisconnected=true;
//maybe set an exception flag and ask world to remove player ASAP.
return null;
}
}
//nonblocking:
public String getRealTimeInput(Player p)
{
setAsynchronousMode();
pw.println(GET_INPUT);
Debug.println("WROTE: " + GET_INPUT);
pw.flush();
if ( inputQueue.isEmpty() )
return null;
String str = (String) inputQueue.removeFirst();
return str;
}
public void waitKeyPress()
{
setSynchronousMode();
pw.println(WAIT_KEY_PRESS);
Debug.println("WROTE: " + WAIT_KEY_PRESS);
pw.flush();
boolean terminamos = false;
try
{
while ( !terminamos )
{
String linea = br.readLine();
StringTokenizer st = new StringTokenizer ( linea );
String t = st.nextToken();
if ( t.equalsIgnoreCase ( KEY_PRESSED ) )
{
terminamos = true;
}
else
parseRequest ( linea );
}
}
catch ( IOException ioe )
{
Debug.println(ioe);ioe.printStackTrace();
}
}
//multim
public SoundClient getSoundClient()
{
return sh; //el sound client proxy.
}
public void insertCenteredIcon(String fname)
{
pw.println(INSERT_ICON + " " + "centered" + " " + fname);
pw.flush();
}
public void insertIcon(String fname)
{
pw.println(INSERT_ICON + " " + fname);
pw.flush();
}
public void useImage(String fname,int parm1,int parm2,int parm3)
{
pw.println(USE_IMAGE + " " + parm1 + " " + parm2 + " " + parm3 + " " + fname);
pw.flush();
}
public void addFrame ( int parm1 , int parm2 )
{
pw.println(ADD_FRAME + " " + parm1 + " " + parm2 );
pw.flush();
}
public void removeFrames()
{
pw.println(REMOVE_FRAMES);
pw.flush();
}
public boolean isGraphicsEnabled()
{ return opt_images;
}
public boolean isSoundEnabled()
{ return opt_sound;
}
private Socket sock;
private InputStream is;
private OutputStream os;
private PrintWriter pw;
private BufferedReader br;
private boolean asynchronous;
private LinkedList inputQueue;
public AGEClientProxy ( java.net.Socket s )
{
try
{
sh = new AGESoundClientProxy(s);
}
catch ( Throwable e )
{
e.printStackTrace();
}
this.sock = s;
try
{
if (s != null)
{
br = new BufferedReader(new InputStreamReader((is=new BufferedInputStream(sock.getInputStream(),100000)),"UTF-8"));
pw = new PrintWriter(new OutputStreamWriter((os=new BufferedOutputStream(sock.getOutputStream(),100000)),"UTF-8"))
{
public void println(String linea)
{
print(linea);
print("\r\n");
//System.err.println("ClientProxy says: " + linea);
}
} ;
}
//init
Debug.println("Calling getClientType()");
try
{
Thread.currentThread().sleep(1000);
}
catch ( InterruptedException ie )
{
;
}
Debug.println("Now really calling it");
getClientType();
Debug.println("Returning from proxy constructor.");
}
catch (Exception e)
{
Debug.println("Error: " + e);
e.printStackTrace();
}
}
public void getClientType() throws IOException
{
Debug.println("Sending client type request.\n");
pw.println( CLIENT_TYPE_REQUEST );
//pw.println( "" );
pw.flush();
String linea = br.readLine();
StringTokenizer st = new StringTokenizer ( linea );
String tok1;
if ( st.hasMoreTokens() ) tok1 = st.nextToken();
else tok1="";
if ( tok1.equalsIgnoreCase ( CLIENT_TYPE_REPLY ) )
{
StringTokenizer st2 = new StringTokenizer ( st.nextToken("").trim() , ":" );
String optionString = st2.nextToken();
String nameString = st2.nextToken("").trim();
StringTokenizer optionTokenizer = new StringTokenizer ( optionString , " " );
while ( optionTokenizer.hasMoreTokens() )
{
String optionToken = optionTokenizer.nextToken();
if ( optionToken.equalsIgnoreCase("sound") )
{
opt_sound=true;
}
else if ( optionToken.equalsIgnoreCase("nosound") )
{
opt_sound = false;
}
else if ( optionToken.equalsIgnoreCase("images") )
{
opt_images = true;
}
else if ( optionToken.equalsIgnoreCase("noimages") )
{
opt_images = false;
}
else if ( optionToken.equalsIgnoreCase("color") )
{
opt_color = true;
}
else if ( optionToken.equalsIgnoreCase("nocolor") )
{
opt_color = false;
}
else if ( optionToken.equalsIgnoreCase("title") )
{
opt_title = true;
}
else if ( optionToken.equalsIgnoreCase("notitle") )
{
opt_title = false;
}
}
}
}
public void getColorCodesFromClient() throws IOException
{
pw.println( COLORCODE_REQUEST );
pw.flush();
String tok1="";
while ( !tok1.equalsIgnoreCase(COLORCODE_INFO_BEGIN) )
{
String linea = br.readLine();
StringTokenizer st = new StringTokenizer ( linea );
if ( st.hasMoreTokens() ) tok1 = st.nextToken();
else tok1="";
if ( tok1.equalsIgnoreCase ( COLORCODE_INFO_BEGIN ) )
{
while ( !tok1.equalsIgnoreCase ( COLORCODE_INFO_END ) )
{
linea = br.readLine();
st = new StringTokenizer ( linea );
if ( st.hasMoreTokens() ) tok1 = st.nextToken();
else tok1="";
if ( tok1.equalsIgnoreCase ( COLORCODE_INFO_LINE ) )
{
//process COLORCODE_INFO_LINE
if ( st.hasMoreTokens() )
{
String tok2 = st.nextToken();
if ( st.hasMoreTokens() )
{
String tok3 = st.nextToken("").trim();
/*
if ( tok2.equalsIgnoreCase("action") )
{
actionColorCode=tok3;
}
else if ( tok2.equalsIgnoreCase("description") )
{
descriptionColorCode=tok3;
}
else if ( tok2.equalsIgnoreCase("default") )
{
defaultColorCode=tok3;
}
else if ( tok2.equalsIgnoreCase("information") )
{
infoColorCode=tok3;
}
else if ( tok2.equalsIgnoreCase("error") )
{
gameErrorColorCode=tok3;
}
else if ( tok2.equalsIgnoreCase("denial") )
{
lifeErrorColorCode=tok3;
}
else if ( tok2.equalsIgnoreCase("input") )
{
inputColorCode=tok3;
}
*/
//agregar c�digo de color
colorCodesTable.put(tok2.toLowerCase(),tok3);
Debug.println("To hashtable: "+tok2.toLowerCase());
} //end yet more tokens
else
{
pw.println( UNRECOGNIZED_FORMAT + " " + linea );
pw.println( SERVER_STATE + " expecting " + COLORCODE_INFO_LINE + ", " + COLORCODE_INFO_END );
pw.flush();
}
} //end has more tokens
else
{
pw.println( UNRECOGNIZED_FORMAT + " " + linea );
pw.println( SERVER_STATE + " expecting " + COLORCODE_INFO_LINE + ", " + COLORCODE_INFO_END );
pw.flush();
}
} //end is colorcode_info_line
else if ( !tok1.equalsIgnoreCase(COLORCODE_INFO_END) )
{
Debug.println("I'm the ClientProxy and I can't recognize " + linea );
pw.println( UNRECOGNIZED_MESSAGE + " " + linea );
pw.println( SERVER_STATE + " expecting " + COLORCODE_INFO_LINE + ", " + COLORCODE_INFO_END );
pw.flush();
}
} //end while not colorcode_info_end
Debug.println("Colorcode info end marker received.");
return;
//end received
} //end if is begin
else if ( tok1.equalsIgnoreCase(UNRECOGNIZED_MESSAGE))
{
System.err.println("Protocol error! " + tok1);
}
else
{
pw.println( UNRECOGNIZED_MESSAGE + " " + linea );
pw.println( SERVER_STATE + " expecting " + COLORCODE_INFO_BEGIN );
pw.flush();
}
} //end while is not begin
}
public void bindToWorld ( World mundo )
{
this.mundo = mundo;
try
{
//send world's visual configuration
Debug.println("Sending visconf.");
try
{
//informar de direct. de mundo
pw.println ( WORLD_DIR + " " + mundo.getWorldDir() );
//crear un Document de pacotilla s�lo para poder hacer un getXMLRepresentation()
org.w3c.dom.Document d = null;
javax.xml.parsers.DocumentBuilder db = javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder();
d = db.newDocument();
Debug.println("Mundo: " + mundo);
Debug.println("VisCo: " + mundo.getVisualConfiguration());
//String elXML = mundo.getVisualConfiguration().getXMLRepresentation(d).toString();
String elXML = DOMUtils.nodeToString(mundo.getVisualConfiguration().getXMLRepresentation(d));
StringTokenizer st = new StringTokenizer ( elXML , "\n" );
pw.println ( VISUALCONF_INIT_BEGIN );
while ( st.hasMoreTokens() )
{
String tok = st.nextToken();
pw.println ( VISUALCONF_INIT_LINE + " " + tok );
Debug.println("Sending line: " + VISUALCONF_INIT_LINE + " " + tok );
}
pw.println ( VISUALCONF_INIT_END );
pw.flush();
try
{
Debug.println("Gonna get color codes...\n");
getColorCodesFromClient();
Debug.println("Color codes succ'flly gotten.\n");
}
catch ( IOException ioe )
{
Debug.println(ioe); ioe.printStackTrace();
}
}
catch ( javax.xml.parsers.ParserConfigurationException pce )
{
Debug.println(pce);
}
//now, send world's multimedia file list to propose a download.
List l = mundo.getFileList();
if ( l != null && l.size() > 0 )
{
pw.println(FILE_LIST_BEGIN);
for ( int k = 0 ; k < l.size() ; k++ )
{
pw.println(FILE_LIST_LINE + " " + l.get(k) + " " + (new File((String)l.get(k))).length() );
}
pw.println(FILE_LIST_END);
pw.flush();
}
//filegets will be parsed on normal input-request loops
write("Welcome to " + sock.getLocalAddress().getHostName() + " [port " + sock.getLocalPort() + "] running the Aetheria Game Engine.\n");
mundo.addNewPlayerASAP ( this );
Debug.println("Welcoming and adding player.\n");
}
catch ( XMLtoWorldException e )
{
Debug.println("Couldn't: XMLtoWorldException " + e );
}
}
private synchronized void setSynchronousMode()
{
if ( asyncModeThread != null )
{
asynchronous = false;
//asyncModeThread.interrupt();
while ( asyncModeThread.isAlive() )
{
Debug.println("Die, thread! Die!");
try
{
wait(500);
}
catch ( InterruptedException ie )
{
;
}
}
}
asynchronous = false;
}
private void setAsynchronousMode()
{
if ( !asynchronous )
{
asynchronous = true;
inputQueue = new LinkedList();
asyncModeThread = new Thread ( )
{
//later, maybe do this even on non-async, but hacer caso a RETURNS only async
public void run ( )
{
try
{
for(;;)
{
if ( !asynchronous ) return;
String str;
synchronized(this)
{
str = br.readLine();
}
if ( str == null )
{
clientHasDisconnected=true;
break;
}
StringTokenizer st = new StringTokenizer ( str );
String cmd = st.nextToken();
String args;
if ( st.hasMoreTokens() ) args = st.nextToken("").trim();
else args = "";
if ( cmd.equalsIgnoreCase ( GET_INPUT_RETURN ) )
{
Debug.println("INPUT GOTTEN: " + args );
Debug.println("Adding 2 Que: " + args);
inputQueue.addLast(args);
}
else if ( cmd.equalsIgnoreCase( GET_INPUT_RETURN_NULL ) )
{
;
}
else
{
parseRequest(str);
}
}
}
catch ( IOException ioe )
{
clientHasDisconnected = true;
Debug.println("IO Exception.\n");
}
}
};
asyncModeThread.start();
}
}
//Parsear un comando activo por parte del cliente (algo que no es mera respuesta a
//mensajes emitidos por nosotros)
public void parseRequest ( String request )
{
StringTokenizer st = new StringTokenizer ( request );
String tok = st.nextToken();
if ( tok.equalsIgnoreCase ( "GET_FILE" ) )
{
//do
String fileName = st.nextToken();
List l = mundo.getFileList();
if ( l.contains ( fileName ) ) //no enviaremos ficheros que no est�n en esa lista
{
int k = l.indexOf(fileName);
File f = (new File((String)l.get(k)));
try
{
FileInputStream fis = new FileInputStream ( f );
byte[] barr = new byte[(int)f.length()];
//fis.read(barr);
Debug.println("Reading file from file input stream.");
Debug.println("To read " + barr.length + " bytes from input stream.");
int lei = 0;
while ( lei < barr.length )
{
lei += fis.read ( barr , lei , barr.length-lei );
Debug.println("Currently read " + lei + " bytes.");
}
synchronized(this) //que no se cuelen escrituras entre header y fichero
{
pw.println(FILE_HEADER_LINE + " " + l.get(k) + " " + f.length() );
Debug.println("WROTE: "+ FILE_HEADER_LINE + " " + l.get(k) + " " + f.length());
/*
for(;;)
{
String linea = br.readLine();
Debug.println("Expecting file acc/rej. Read: " + linea);
StringTokenizer lt = new StringTokenizer ( linea );
String reply = lt.nextToken();
if ( reply.equalsIgnoreCase( FILE_REJECT ) )
{
Debug.println("File rejected...");
return;
}
else if ( reply.equalsIgnoreCase( FILE_ACCEPT ) )
{
Debug.println("File accepted...");
break;
}
else
{
Debug.println("File ignored...");
parseRequest(linea);
}
}
*/
//File accepted. Binary mode set by client.
pw.flush();
Debug.println("Writing length " + barr.length + " data to socket stream.");
Debug.println("Socket buffer sizes are " + sock.getSendBufferSize() + " and " + sock.getReceiveBufferSize() );
//os.write(barr);
//pw.print(new String(barr));
//os.write(barr);
os.write(barr);
//for ( int i = 0 ; i < barr.length ; i++ )
// pw.print ( (char) barr[i] );
os.flush();
Debug.println("WROTE: "+new String(barr));
//pw.flush();
}
}
catch ( IOException ioe )
{
Debug.println(ioe);
ioe.printStackTrace();
}
}
}
else if ( GOODBYE.equalsIgnoreCase(tok) )
{
clientHasDisconnected = true;
try {
sock.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}