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 }