001    //  edu.isi.gamebots.clients.Bot
002    //  Copyright 2000, University of Southern California,
003    //                  Information Science Institute
004    //
005    //  Personal and Educational use is hereby granted.
006    //  Permission required for commercial use and redistribution.
007    
008    
009    package edu.isi.gamebots.client;
010    
011    import java.io.*;
012    import java.lang.*;
013    import java.net.*;
014    import java.util.*;
015    
016    import javax.swing.*;
017    import javax.swing.border.EmptyBorder;
018    
019    import us.ca.la.anm.gui.*;
020    import us.ca.la.anm.util.io.*;
021    
022    
023    /**
024     *  <p>
025     *  This is the base class for all Java Bots.  It handles most low level
026     *  functionality, including bot life cycle hooks, and many helper functions.
027     *  </p>
028     *
029     *  @author <a href="mailto:amarshal#gamebots@isi.edu">Andrew n marshall</a>
030     */
031    public abstract class Bot implements GamebotsConstants {
032      // Private Static Data
033      ///////////////////////////////////////////////////////////////////////
034      private static final int ERROR_MESSAGE = -1;
035      private static final int SYNC_MESSAGE  =  1;
036      private static final int ASYNC_MESSAGE =  2;
037      
038      
039      // Private Data
040      ///////////////////////////////////////////////////////////////////////
041      /** 
042       * <p>
043       * The UTServer
044       * </p>
045       */  
046      protected GamebotsClient client;
047      /** 
048       * <p>
049       * A logging mechanisn
050       * </p>
051       */  
052      protected DebugLog log;
053      
054      //  Event processing member data.
055      private boolean isAlive = true;
056      private final Vector eventQueue = new Vector();
057      private Thread eventThread;
058      
059      private String name;
060      private int initialTeam;
061      
062      //  Public Static Methods
063      ///////////////////////////////////////////////////////////////////////////
064      /**
065       * <p>
066       * Parses any comma or space delimited list of numbers into an array of
067       * doubles.  Particularly useful for parsing location and rotation
068       * information.
069       * <p>
070       * @param in The String object to parse
071       * @throws NumberFormatException If there is a non-number in the list
072       * @return an array of doubles
073       */
074      public static double[] parseVector( String in ) throws NumberFormatException {
075        StringTokenizer st = new StringTokenizer( in, ", " );
076        double[] result = new double[st.countTokens()];
077        
078        int i=0;
079        while( st.hasMoreTokens() )
080          result[i++] = Double.parseDouble( st.nextToken() );
081        
082        return result;
083      }
084      
085      //  Public Methods
086      ///////////////////////////////////////////////////////////////////////
087      /**
088       * <p>
089       * Creates a new Bot
090       * </p>
091       */  
092      public Bot() {
093        eventThread = new Thread( eventProcessor );
094        eventThread.start();
095      }
096      
097      /**
098       * <p>
099       * Main initialization method for {@link BotRunnerApp}.  All Bot specific
100       * initialization should occur in {@link #init}.
101       * </p>
102       * @param name Name of the bot
103       * @param client UTServer that bot talks to
104       * @param team Team that the bot is on
105       * @param log Where to log debug messages to (can specify System.out to log to the console)
106       */
107      public final void init( String name, GamebotsClient client, int team, DebugLog log ) {
108        this.name = name;
109        this.client = client;
110        this.initialTeam = team;
111        this.log = log;
112        
113        client.addListener( clientListener );
114        
115        init();
116      }
117      
118      /**
119       * <p>
120       * Get the bot's name
121       * </p>
122       * @return the Bot name.
123       */
124      public final String getName() {
125        return name;
126      }
127      
128      /**
129       * <p>
130       * Get the server that this bot is running on.
131       * </p>
132       * @return the bot's {@link GamebotsClient}.
133       */
134      public final GamebotsClient getClient() {
135        return client;
136      }
137      
138      /**
139       * <p>
140       * Return the name of the team that the bot was initialized to.
141       * </p>
142       * @return <CODE>int</CODE> team that the bot belongs to
143       */  
144      public final int getInitialTeam() {
145        return initialTeam;
146      }
147      
148      /**
149       * <p>
150       * Returns a user interface for the bot. This is needed if you want to use the
151       * BotRunnerApp to start and view the status of your bots. The JComponent that
152       * this returns is what gets added to a new tab in the interface.
153       * </p>
154       * @return a Swing user interface for the bot.
155       */
156      public JComponent getView() {
157        return new JLabel( getName()+" ("+getClass().getName()+")" );
158      }
159      
160      /**
161       *  This initiates a connection with the server.  It is called from the
162       *  {@link BotRunnerApp}.
163       */
164      public final void connect() {
165        try {
166          client.connect();
167        } catch( IOException error ) {
168          log.logError( error );
169          log.flush();
170        }
171      }
172      
173      /**
174       *  This forces disconnect with the server.  It is called from the
175       *  {@link BotRunnerApp}.
176       */
177      public final void disconnect() {
178        client.disconnect();
179      }
180      
181      // Event Handlers
182      /**
183       * <p>
184       * Event Handler. Override this method to define what to do when the bot is
185       * distroyed/disconnected by the user.
186       * </p>
187       */  
188      public final void destroy() {
189        isAlive = false;
190        eventThread.interrupt();
191        
192        destroyImpl();
193      }
194      
195      /** 
196       * <p>
197       * The bot runs to a perticular target.
198       * </p>
199       * @param target The UnrealTournament ID to run to
200       */  
201      public void runTo( String target ) {
202        if( target == null )
203          throw new IllegalArgumentException( "Target cannot be null." );
204        
205        Properties props = new Properties();
206        props.setProperty( ARG_TARGET, target );
207        client.sendMessage( RUNTO, props );
208      }
209      
210       /** 
211       * <p>
212       * The bot asks for a path to an xyz coordinate
213       * </p>
214       * @param target The UnrealTournament ID to run to
215       */  
216      public void getPath(String pathName, double x, double y, double z) {    
217        Properties props = new Properties();
218        props.setProperty( "Id", pathName);
219        props.setProperty( "Location", x + " " + y + " " + z);
220        client.sendMessage( "GETPATH", props );
221      }
222      
223      /**
224       * <p>
225       * The bot runs to a perticular location
226       * </p>
227       * @param x global x coordinate in the world
228       * @param y global y coordinate in the world
229       * @param z global z coordinate in the world
230       */  
231      public void runTo( double x, double y, double z ) {
232        Properties props = new Properties();
233        props.setProperty( LOCATION, x+" "+y+" "+z );
234        client.sendMessage( RUNTO, props );
235      }
236      
237      /**
238       * <p>
239       * Return the log for the bot
240       * <p>
241       *
242       * @return The <CODE>DebugLog</CODE> of the agent
243       */  
244      public DebugLog getLog(){
245        return log;
246      }
247      
248      /**
249       * <p>
250       * Turn a particular amount in three dimentional space. Yaw is side to side, pitch
251       * up and down, and Roll the equivelent of doing a cartwheel. You probably won't
252       * need Roll, so don't sweat it. A full rotation using UT's measurements is 65535.
253       * To convert the values you are sent to radiens, divide by 65535 and multiply by
254       * 2 * pi.  This is already converted for you to radians though.
255       * UnrealTournament units.
256       * </p>
257       * @param pitch Amount to look up or down in radians
258       * @param yaw Amount to turn side to side in radians
259       * @param roll Amount of 'roll' rotation. Not sure what this does.
260       */  
261      public void turnTo( double pitch, double yaw, double roll ) {
262        Properties props = new Properties();
263        // TO-DO: This will soon become yaw,pitch,roll
264        int p = ((int) (pitch*65536/(2*Math.PI)))%65536;
265        int y = ((int) (yaw*65536/(2*Math.PI)))%65536;
266        int r = ((int) (roll*65536/(2*Math.PI)))%65536;
267        props.setProperty( ROTATION, p+" "+y+" "+r );
268        client.sendMessage( TURNTO, props );
269      }
270      
271      // the following bot commands added by Ryan Rozich - Texas A&M University for completeness
272      
273      /**
274       * <p>
275       * Turn towards a particular location in space
276       * </p>
277       * @param x The global x coordinate to turn toward
278       * @param y The global y coordinate to turn toward
279       * @param z The global z coordinate to turn toward
280       */  
281      public void turnToLoc(double x, double y, double z){
282        Properties props = new Properties();
283        // TO-DO: This will soon become yaw,pitch,roll
284        props.setProperty( LOCATION, x+" "+y+" "+z );
285        client.sendMessage( TURNTO, props );
286      }
287      
288      /**
289       * <p>
290       * Turn towards a particular location in space
291       * </p>
292       * @param coords Same as turnTo(double, double, double) except this takes the three corrdinates as a single string.
293       */  
294      public void turnToLoc(String coords){
295        Properties props = new Properties();
296        // TO-DO: This will soon become yaw,pitch,roll
297        props.setProperty( LOCATION, coords);
298        client.sendMessage( TURNTO, props );
299      }
300      
301      /**
302       * <p>
303       * Set the agent to walk instead of run. It seems intuitive that when true is sent,
304       * the agent should walk. But in our tests, it seems the agent is set to run when
305       * true is sent and walks when false is sent.
306       * </p>
307       * @param doWalk We think that <CODE>true</CODE> means run and <CODE>false</CODE> means walk.
308       */  
309      public void setWalk(boolean doWalk){
310        Properties props = new Properties();
311        if(doWalk) props.setProperty(ARG_WALK, TRUE);
312        else props.setProperty(ARG_WALK, FALSE);
313        
314        client.sendMessage(SETWALK,props);
315      }
316      
317      /**
318       * <p>
319       * Say something that is heard by other players.
320       * </p>
321       * @param message The message to send
322       * @param forEveryone True if this is sent to both teams, false if only to 
323       * your team
324       */
325      public void say(String message, boolean forEveryone){
326        Properties props = new Properties();
327        if(forEveryone) props.setProperty(ARG_GLOBAL, TRUE);
328        else props.setProperty(ARG_GLOBAL, FALSE);
329        props.setProperty(ARG_TEXT, message);
330        
331        client.sendMessage(MESSAGE,props);
332      }
333      
334      /**
335       * <p>
336       * Stop moving. If the agent is en route to a location (via the runTo) command
337       * or is otherwise moving and can stop, the agent will stop.
338       * </p>
339       */  
340      public void stop(){
341        client.sendMessage(STOP,null);
342      }
343      
344      /**
345       * <p>
346       * The agent jumps. I think that it is a bug that the bot is able to jump again
347       * while in the middle of a jump. In a room with high celings this gives bots
348       * magical flying ability.
349       * </p>
350       */  
351    public void jump(){
352        client.sendMessage(JUMP,null);
353      }
354      
355      /**
356       * <p>
357       * Strafe. This causes the agent to run to a particular location while looking at
358       * another.
359       * </p>
360       * @param x Global x coordinate run to.
361       * @param y Global y coordinate run to.
362       * @param z Global z coordinate run to.
363       * @param faceObjectID Object ID to face while running
364       */  
365      public void strafe(double x, double y, double z, String faceObjectID){
366        Properties props = new Properties();
367        props.setProperty(LOCATION, x+" "+y+" "+z);
368        props.setProperty(ARG_TARGET, faceObjectID);
369        client.sendMessage(STRAFE,props);
370      }
371      
372      /**
373       * <P>
374       * Rotate the agent by a particular amount. A full rotation using UT's measurements
375       * is 65535. To convert the values you are sent to radiens, divide by 65535 and multiply by 2 * pi.
376       * </P>
377       * @param amount Amount in UT units to rotate
378       */  
379      public void rotate(double amount){
380        Properties props = new Properties();
381        props.setProperty(ARG_AMOUNT, Double.toString(amount));
382        client.sendMessage(ROTATE,props);
383      }
384      
385      /**
386       * <p>
387       * Agent shoots their current weapon (if they have one). Sometimes the agent will
388       * fire one or two shots and sometimes they fire more.
389       * </p>
390       */  
391      public void shoot(){
392        client.sendMessage(SHOOT,null);
393      }
394      
395      /**
396       * <p>
397       * Shoot at a particular target. The bot will auto aim. As long as the target
398       * stays within the bots visual range, the bot will keep shooting the target until
399       * it runs out of ammo. Use the {{@link #stopShoot() stopShoot} command to stop shooting.
400       * </p>
401       * @param x Global x coordinate to fire toward
402       * @param y Global y coordinate to fire toward
403       * @param z Global z coordinate to fire toward
404       * @param targetID Global UT target ID to fire at
405       * @param altFire <CODE>true</CODE> to use the weapon's alternate fire feature.
406       */  
407      public void shoot(double x, double y, double z, String targetID, boolean altFire){
408        Properties props = new Properties();
409        props.setProperty(LOCATION, x+" "+y+" "+z);
410        props.setProperty(ARG_TARGET, targetID);
411        if(altFire) props.setProperty(ARG_ALT, TRUE);
412        else props.setProperty(ARG_ALT, FALSE);
413        client.sendMessage(SHOOT,props);
414      }
415      
416      /**
417       * <p>
418       * Shoot at a particular target. The bot will auto aim. As long as the target
419       * stays within the bots visual range, the bot will keep shooting the target until
420       * it runs out of ammo. Use the {@link #stopShoot() stopShoot} command to stop shooting.
421       * </p>
422       * @param targetID The UT id of the target to shoot at
423       * @param altFire <CODE>true</CODE> to use the weapon's alternate fire feature.
424       */  
425      public void shoot(String targetID, boolean altFire){
426        Properties props = new Properties();
427        props.setProperty(ARG_TARGET, targetID);
428        if(altFire) props.setProperty(ARG_ALT, TRUE);
429        else props.setProperty(ARG_ALT, FALSE);
430        client.sendMessage(SHOOT,props);
431      }
432      
433      /**
434       * <p>
435       * Changes to another weapon that the agent posseses.
436       * </p>
437       * @param weaponID The UT id of the weapon to switch to
438       */  
439      public void changeWeapon(String weaponID){
440        Properties props = new Properties();
441        props.setProperty(ACTOR_ID, weaponID);
442        client.sendMessage(CHANGEWEAPON, props);
443      }
444      
445      /**
446       * <p>
447       * If currently shooting its weapon, the bot will stop.
448       * </p>
449       */  
450      public void stopShoot(){
451        client.sendMessage(STOP_SHOOT, null);
452      }
453      
454      //  Private Methods
455      ///////////////////////////////////////////////////////////////////////////
456      
457      /**
458       * <p>
459       * Sets the bot's name.  Currently does nothing if the Bot is already
460       * connected.
461       *
462       * <p>Future: request name changes to the server when connected.
463       * @param newName Agent name
464       */
465      protected void setName( String newName ) {
466        if( newName != null &&
467        newName != name ) {
468          if( client.isConnected() ) {
469            // TO-DO: Send a message to the server...
470          } else {
471            name = newName;
472          }
473        }
474      }
475      
476      // Event Handlers
477      /**
478       * <p>
479       * Handles server message events
480       * </p>
481       */  
482      protected GamebotsClient.Listener clientListener = new GamebotsClient.Listener() {
483        //  Connection messages do not go through the event loop
484        public void connected() {
485          Bot.this.connected();
486        }
487        
488        public void receivedAsyncMessage( Message message ) {
489          //      log.logNote( message );
490          //      log.flush();
491          synchronized( eventQueue ) {
492            eventQueue.add( new MessagePair( ASYNC_MESSAGE, message ) );
493            eventQueue.notifyAll();
494          }
495        }
496        
497        public void receivedSyncMessage( MessageBlock block ) {
498          //      log.logNote( block );
499          //      log.flush();
500          synchronized( eventQueue ) {
501            eventQueue.add( new MessagePair( SYNC_MESSAGE, block ) );
502            eventQueue.notifyAll();
503          }
504        }
505        
506        public void receivedError( Throwable error ) {
507          //      log.logNote( error );
508          //      log.flush();
509          synchronized( eventQueue ) {
510            eventQueue.add( new MessagePair( ERROR_MESSAGE, error ) );
511            eventQueue.notifyAll();
512          }
513        }
514        
515        //  Disconnection messages do not go through the event loop
516        public void disconnected() {
517          Bot.this.disconnected();
518        }
519      };
520      
521      /** <p>
522       * Processes events as they arrive
523       * </p>
524       */  
525      protected Runnable eventProcessor = new Runnable() {
526        public void run() {
527          MessagePair pair;
528          while( isAlive ) {
529            try {
530              synchronized( eventQueue ) {
531                while( true ) {
532                  while( eventQueue.isEmpty() )
533                    eventQueue.wait();
534                  pair = (MessagePair) eventQueue.firstElement();
535                  eventQueue.remove( pair );
536                  try {
537                    switch( pair.type ) {
538                      case ASYNC_MESSAGE:
539                        Bot.this.receivedAsyncMessage( (Message) pair.message );
540                        break;
541                      case SYNC_MESSAGE:
542                        Bot.this.receivedSyncMessage( (MessageBlock) pair.message );
543                        break;
544                      case ERROR_MESSAGE:
545                        Bot.this.receivedError( (Throwable) pair.message );
546                        break;
547                    }
548                  } catch( RuntimeException error ) {
549                    Bot.this.receivedError( error );
550                  }
551                }  //  end message loop
552              }  //  end sync
553            } catch( InterruptedException error ) {
554              // breaking out of synchronized loop...
555            }
556          }  //  end alive loop
557        }
558      };
559      
560      /**
561       * <p>
562       * Bot implementations override this to specify what to do (if anything) when the
563       * bot is initialized.
564       * </p>
565       */  
566      protected void init() {
567        // Bot implementatiosn don't _need_ to do anything.
568      }
569      
570      /**
571       * <p>
572       * Bot implementations override this to specify what to do (if anything) after the
573       * bot established a server connection.
574       * </p>
575       */  
576      protected void connected() {
577        // Bot implementatiosn don't _need_ to do anything.
578      }
579      
580      /**
581       * <P>
582       * Bot implementations must override this method. This specifies what to do when an
583       * asynchronous message is recieved. A description of the difference between sync
584       * and async messages can be found at <a href="http://www.planetunreal.com/gamebots/docapi.html">
585       * http://www.planetunreal.com/gamebots/docapi.html</a>. Although this is not
586       * nessesarily a complete reference, it is a good general intro.
587       * </P>
588       * @param message The <CODE>Message</CODE> object to handle
589       */  
590      protected abstract void receivedAsyncMessage( Message message );
591      
592      /**
593       * <P>
594       * Bot implementations must override this method. This specifies what to do when an
595       * synchronous message is recieved. A description of the difference between sync
596       * and async messages can be found at <a href="http://www.planetunreal.com/gamebots/docapi.html">
597       * http://www.planetunreal.com/gamebots/docapi.html</a>. Although this is not
598       * nessesarily a complete reference, it is a good general intro.
599       * </P>
600       * @param message The synchronous <CODE>MessageBlock</CODE> to handle
601       */  
602      protected abstract void receivedSyncMessage( MessageBlock message );
603      
604      /**
605       * <p>
606       * Event handling. What to do when a bot throws an error. Default is to disconnect.
607       * </p>
608       * @param error The error that was recieved to handle.
609       */  
610      protected final void receivedError( Throwable error ) {
611        log.logError( "Bot \""+name+"\": "+error, error );
612        disconnect();
613      }
614      
615      /**
616       * <p>
617       * Event handling. Bot implementation overrides this to tell what to do when
618       * disconnected from the server.
619       * </p>
620       */  
621      protected void disconnected() {
622        // Bot implementatiosn don't _need_ to do anything.
623      }
624      
625      /**
626       * <p>
627       * Event handling. Describes what to do when the bot is destroyed.
628       * </p>
629       */  
630      protected void destroyImpl() {
631        // Bot implementatiosn don't _need_ to do anything.
632      }
633      
634      /**
635       * <p>
636       * check to see if you can move directly to a destination without hitting an obstruction, falling in a pit, etc... 
637       * </p>
638       *
639       * @param id message id made up by you and echoed in respose so you can match up response with querry 
640       * @param target the unique id of a player/object/nav point/whatever. Must be visible.
641       */
642      public void checkReach(final java.lang.String id, final java.lang.String target) {
643        Properties props = new Properties();
644        props.setProperty( "Id", id);
645        props.setProperty( "Target", target);
646        client.sendMessage( "CHECKREACH", props );
647      }  
648      
649      /**
650       * <p>
651       * check to see if you can move directly to a destination without hitting an obstruction, falling in a pit, etc... 
652       * </p>
653       *
654       * @param id message id made up by you and echoed in respose so you can match up response with querry 
655       * @param fromX Location you want to go to. Normal location rules.
656       * @param fromY Location you want to go to. Normal location rules.
657       * @param fromZ Location you want to go to. Normal location rules.
658       */
659      public void checkReach(final java.lang.String id, double fromX, double fromY, double fromZ) {
660        Properties props = new Properties();
661        props.setProperty( "Id", id);
662        props.setProperty( "Location", fromX + " " + fromY + " " + fromZ);
663        client.sendMessage( "CHECKREACH", props );
664      }
665      
666      //  Private Inner Classes
667      ///////////////////////////////////////////////////////////////////////////
668      private class MessagePair {
669        /**
670         * <p>
671         * Type of message recieved. ERROR, SYNC, or ASYNC
672         * </p>
673         */    
674        public final int type;
675        /** <p>
676         * The actual message.
677         * <p>
678         */    
679        public final Object message;
680        
681        /**
682         * <p>
683         * Constructs a new MessagePair object.
684         * </p>
685         * @param type The type of message
686         * @param message The message object
687         */    
688        public MessagePair( int type, Object message ) {
689          this.type = type;
690          this.message = message;
691        }
692      }
693    }