001    //  edu.isi.gamebots.examples.ExampleBot
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.examples;
010    
011    import java.awt.*;
012    import java.awt.geom.*;
013    import java.lang.*;
014    import java.util.*;
015    
016    import javax.swing.*;
017    
018    import edu.isi.gamebots.client.*;
019    import edu.isi.gamebots.examples.*;
020    
021    
022    /**
023     *  This example implementation the Bot class shows basic message handling, Bot
024     *  control, and and uses {@link UTMapUI} for a map in its user interface.
025     *
026     *  @author <a href="mailto:amarshal#gamebots@isi.edu">Andrew n marshall</a>
027     */
028    public class ExampleBot extends Bot {
029      // Private Static Data
030      ///////////////////////////////////////////////////////////////////////////
031      protected static final int MAX_OUTPUT = 100;
032      protected static final double PLAYER_RADIUS = 18.0d;
033      protected static final double THETA_DELTA = (2d*Math.PI)/60d;  // Angular Delta (6 deg)
034      protected static final int NONE = 0;
035    
036      // state
037      protected static final int EXPLORING = 1;
038      protected static final int HEALING = 2;
039      protected static final int HUNTING = 3;
040    
041      // interference
042      protected static final int INTERFERENCE_BUMP = 1;
043      protected static final int INTERFERENCE_WALL = 2;
044      protected static final int INTERFERENCE_DAMAGE = 3;
045    
046      // directional tendancy
047      protected static final int LEFT = 1;
048      protected static final int RIGHT = 2;
049    
050      /**
051       *  Compares Paths based on theta.
052       *
053       *  Note: this comparator imposes orderings that are inconsistent with equals(...).
054       */
055      protected static final Comparator pathComparator = new Comparator() {
056        public int compare( Object aObj, Object bObj ) {
057          Path a = (Path) aObj;
058          Path b = (Path) bObj;
059    
060          if( a.theta < b.theta )
061            return -1;
062          if( a.theta == b.theta )
063            return 0;
064          else // a.theta > b.theta
065            return 1;
066        }
067      };
068    
069      // Private Data
070      ///////////////////////////////////////////////////////////////////////
071      protected NodeMap map = new NodeMap();
072    
073      protected Random random = new Random();
074    
075      protected Thread runnerThread;
076    
077      protected boolean logIntents  = false;
078      protected boolean logMessages = false;
079      protected boolean logSelf     = false;
080    
081      protected boolean didInit = false;
082    
083      protected Object stateLock = new Object();
084      protected long stateChangeTime = 0;
085      protected int state = EXPLORING;
086      protected int direction = NONE;  // for rot/straft tendancies..
087    
088      /**
089       *  This lock is used to synchronized reads from/writes to the various state
090       *  variables.
091       */
092      protected final Object selfLock = new Object();
093      protected double x, y, z;  // in world coordinates
094      protected double yaw, pitch, roll;  // in Radians
095      protected int health, armor, ammo;
096      protected int team = 255;
097      protected String weapon;
098      protected Vector3D here;  // Location as of the last vision update
099    
100      protected boolean interfered = false;
101      protected long interferenceTime = 0;
102      protected int  interferenceType = NONE;
103    
104      protected long lastRotIncTime = 0;
105      protected double yawTarget;
106    
107      protected Object nodeInfoLock = new Object();
108      protected java.util.Map idToNode = new HashMap();  // Id String to Node object
109      protected Collection knownNodes = idToNode.values();  // automatic updates
110      protected Set exploredNodes = new HashSet();
111      protected Set visibleNodes = new HashSet();
112      protected Set reachableNodes = new HashSet();
113    
114      protected Node target, lastTarget;
115      protected long targetAcquiredTime = 0;
116      protected long targetLostTime = 0;
117    
118      //  Public Methods
119      ///////////////////////////////////////////////////////////////////////
120      public JComponent getView() {
121        return map;
122      }
123    
124      //  Protected Methods
125      ///////////////////////////////////////////////////////////////////////
126    
127      // runner
128      protected Runnable runner = new Runnable() {
129        public void run() {
130          Thread thread = Thread.currentThread();
131          while( thread == runnerThread ) {
132            try {
133              if( didInit ) {
134                synchronized( stateLock ) {
135                  switch( state ) {
136                  default:
137                  case EXPLORING:
138                    explore();
139                    break;
140    
141                  case HEALING:
142                    heal();
143                    break;
144    
145                  case HUNTING:
146                    hunt();
147                    break;
148                  }
149                } // end sync
150              } // end if
151              thread.sleep( 1000l );
152            } catch( InterruptedException error ) {
153              // ignore
154            }
155          }
156        }
157      };
158    
159      // Event Handlers
160      protected void connected() {
161        super.connected();
162    
163        log.logNote( "Connected... ("+new Date()+")" );
164    
165        runnerThread = new Thread( runner );
166        runnerThread.start();
167    
168        map.repaint();
169      }
170    
171      protected void receivedAsyncMessage( Message message ) {
172        if( didInit ) {
173          if( message.getType().equals( BUMP ) ) {
174            interferenceTime = System.currentTimeMillis();
175            interferenceType = INTERFERENCE_BUMP;
176            interfered = true;
177          } else if( message.getType().equals( WALL ) ) {
178            interferenceTime = System.currentTimeMillis();
179            interferenceType = INTERFERENCE_WALL;
180            interfered = true;
181          } else if( message.getType().equals( DAMAGE ) ) {
182            interferenceTime = System.currentTimeMillis();
183            interferenceType = INTERFERENCE_DAMAGE;
184            interfered = true;
185          }
186        } else if( message.getType().equals( INFO ) ) {
187          // Init
188          //  Should check to make sure it is only the first...
189          Properties props = new Properties();
190            props.setProperty( client.PLAYER_NAME, getName() );
191            int team = getInitialTeam();
192            if( team != TEAM_ANY )
193              props.setProperty( client.PLAYER_TEAM, Integer.toString(team) );
194          client.sendMessage( client.INIT, props );
195          didInit = true;
196        }
197    
198        if( logMessages )
199          log.logNote( message );
200      }
201    
202      protected void receivedSyncMessage( MessageBlock block ) {
203        StringBuffer sb = new StringBuffer();
204        if( logMessages ) {
205          sb.append( block );
206        }
207    
208        synchronized( selfLock ) {
209          if( here == null ||
210              ( here.x != x ||
211                here.y != y ||
212                here.z != z ) )
213            here = new Vector3D( x, y, z );
214        }
215        Message message;
216        String type, value;
217        Node node;
218    
219        synchronized( nodeInfoLock ) {
220          visibleNodes.clear();
221          reachableNodes.clear();
222    
223          Iterator i = block.getMessages();
224          while( i.hasNext() ) {
225            message = (Message) i.next();
226            type = message.getType();
227            if( type.equals( END ) ) {
228              // ignore
229            } else if( message.getType().equals( SELF ) ) {
230              if( logSelf )
231                log.logNote( message );
232              updateSelf( message );
233              if( logSelf )
234                log.logNote( "DEBUG: Loc: "+x+" "+y+" "+z+";  Rot: "+pitch+" "+yaw+" "+roll );
235            } else if( type.equals( PLAYER ) ) {
236              // TO-DO
237            } else {
238              node = (Node) idToNode.get( message.getProperty( ACTOR_ID ) );
239              if( node == null ) {
240                node = new Node( message );
241                if( node.id != null )
242                  idToNode.put( node.id, node );
243                else if( logMessages )
244                  sb.append( "\n * No ID: " );
245              }
246              value = message.getProperty( ACTOR_REACHABLE );
247              if( value != null &&
248                  value.equals( TRUE ) ) {
249                node.reachableFrom( here );
250                reachableNodes.add( node );
251              }
252              visibleNodes.add( node );
253            }
254            if( logMessages ) {
255              sb.append('\n');
256              sb.append( message );
257            }
258          }
259    
260          runnerThread.interrupt();
261          if( logMessages )
262            log.logNote( sb.toString() );
263        }
264    
265        map.repaint();
266      }
267    
268      protected void disconnected() {
269        didInit = false;
270    
271        log.logNote( "Disconnected... ("+new Date()+")" );
272    
273        if( runnerThread != null ) {
274          Thread oldThread = runnerThread;
275          runner = null;
276          oldThread.interrupt();
277        }
278    
279        map.repaint();
280      }
281    
282      protected void updateSelf( Message message ) {
283        String value;
284        double vector[];
285    
286        synchronized( selfLock ) {
287          value = message.getProperty( PLAYER_NAME );
288          if( value != null )
289            setName( value );
290    
291          value = message.getProperty( PLAYER_TEAM );
292          if( value != null ) {
293            try {
294              team = Integer.parseInt( value );
295            } catch( NumberFormatException error ) {
296              // Log and ignore
297              log.logError( "Illegal \""+PLAYER_TEAM+"\" value: "+value, error );
298            }
299          }
300    
301          value = message.getProperty( LOCATION );
302          if( value != null ) {
303            vector = parseVector( value );
304            x = vector[0];
305            y = vector[1];
306            z = vector[2];
307          }
308    
309          value = message.getProperty( ROTATION );
310          if( value != null ) {
311            vector = parseVector( value );
312            pitch = vector[0] * 2 * Math.PI / 65535;
313            yaw = vector[1] * 2 * Math.PI / 65535;
314            roll = vector[2] * 2 * Math.PI /   65535;
315          }
316    
317          value = message.getProperty( PLAYER_AMMO );
318          if( value != null ) {
319            try {
320              ammo = Integer.parseInt( value );
321            } catch( NumberFormatException error ) {
322              // Log and ignore
323              log.logError( "Illegal \""+PLAYER_AMMO+"\" value: "+value, error );
324            }
325          }
326    
327          value = message.getProperty( PLAYER_ARMOR );
328          if( value != null ) {
329            try {
330              armor = Integer.parseInt( value );
331            } catch( NumberFormatException error ) {
332              // Log and ignore
333              log.logError( "Illegal \""+PLAYER_ARMOR+"\" value: "+value, error );
334            }
335          }
336    
337          value = message.getProperty( PLAYER_HEALTH );
338          if( value != null ) {
339            try {
340              health = Integer.parseInt( value );
341            } catch( NumberFormatException error ) {
342              // Log and ignore
343              log.logError( "Illegal \""+PLAYER_HEALTH+"\" value: "+value, error );
344            }
345          }
346    
347          value = message.getProperty( PLAYER_WEAPON );
348          if( value != null &&
349              value != weapon )
350            weapon = value;
351        }
352    
353        synchronized( nodeInfoLock ) {
354          if( target != null &&
355              target.loc.near( x, y, z, PLAYER_RADIUS ) ) {
356            exploredNodes.add( target );
357            if( target != null )
358              lastTarget = target;
359            target = null;
360          }
361        }
362        map.repaint();
363      }
364    
365      protected void explore() {
366        long now = System.currentTimeMillis();
367        // Assumes synchronized on stateLock
368        if( state != EXPLORING ) {
369          state = EXPLORING;
370          stateChangeTime = now;
371        }
372    
373        // Atomic copy (need a consistent set of variables, but won't write to them;
374        final double x, y, z, yaw, pitch, roll;
375        synchronized( selfLock ) {
376          x = ExampleBot.this.x;
377          y = ExampleBot.this.y;
378          z = ExampleBot.this.z;
379          yaw = ExampleBot.this.yaw;
380          pitch = ExampleBot.this.pitch;
381        }
382    
383        synchronized( nodeInfoLock ) {
384          if( reachableNodes == null )
385            return;  // No game update yet.
386    
387          if( target != null ) {
388            targetLostTime = now;
389          } else if( reachableNodes.contains( target ) ) {
390            long delay = interferenceTime - targetAcquiredTime;
391            if( ( interferenceType == INTERFERENCE_WALL &&
392                  delay > 1000 ) ||
393                ( interferenceType != NONE &&
394                  delay > 3000 ) ||
395                ( delay > 6000 ) ) {  // Give up after some time
396              if( logIntents )
397                log.logNote( "Giving up on target. Interference Type: "+interferenceType+", delay: "+delay );
398              findNewTarget();
399            } else {
400              // Otherwise continue persuing target.
401              runTo( target.id );
402            }
403            return;
404          }
405          if( reachableNodes.isEmpty() ) {
406            if( logIntents )
407              log.logNote( "No visible nodes." );
408            findNewTarget();
409          } else {
410            Set options = new HashSet( reachableNodes );
411            options.removeAll( exploredNodes );
412            if( options.isEmpty() ) {
413              if( random.nextBoolean() ) {
414                if( logIntents )
415                  log.logNote( "No unexplored nodes." );
416                findNewTarget();
417                return;
418              }
419              options = reachableNodes;
420              if( lastTarget != null )
421                reachableNodes.remove( lastTarget );
422            }
423            Node node;
424            Iterator i = options.iterator();
425            while( i.hasNext() ) {
426              node = (Node) i.next();
427              if( node.loc.near( x, y, z, PLAYER_RADIUS ) )
428                i.remove();
429            }
430            if( options.isEmpty() ) {
431              if( logIntents )
432                log.logNote( "No distant nodes." );
433              findNewTarget();
434              return;
435            }
436    
437            node = (Node) options.toArray()[ random.nextInt( options.size() ) ];
438            if( target == null ) {
439              if( lastTarget == node )
440                if( logIntents )
441                  log.logNote( "No visible nodes." );
442                findNewTarget();
443            } else {
444              if( target == node )
445                findNewTarget();
446            }
447    
448            direction = NONE;
449            interferenceType = NONE;
450            targetAcquiredTime = now;
451            if( target != null )
452              lastTarget = target;
453            target = node;
454    
455            runTo( target.id );
456    
457            log.logNote( "Exploring new target: "+target );
458          }
459        }
460      }
461    
462      protected void heal() {
463      }
464    
465      protected void hunt() {
466      }
467    
468      protected void findNewTarget() {
469        interferenceType = NONE;
470        synchronized( nodeInfoLock ) {
471          if( target != null )
472            lastTarget = target;
473          target = null;
474        }
475        interfered = false;
476        incRotation();
477      }
478    
479      protected void incRotation() {
480        long now = System.currentTimeMillis();
481        if( now-lastRotIncTime < 200 ) {
482    //      log.logNote( "Don't turn yet." );
483          return;
484        }
485        if( direction == NONE ) {
486          if( random.nextBoolean() )
487            direction = LEFT;
488          else
489            direction = RIGHT;
490        }
491        double newYaw;
492        if( direction == LEFT )
493          newYaw = yaw+(Math.PI/6);  // 15 deg
494        else
495          newYaw = yaw-(Math.PI/6);  // 15 deg
496        if( newYaw == yawTarget ) {
497          if( logIntents )
498            log.logNote( "Stuck in rotation: Jittering." );
499          jitter();  // get unstuck
500        } else {
501          if( logIntents )
502            log.logNote( "Rotating to "+newYaw );
503          turnTo( pitch, newYaw, 0 );
504          yawTarget = newYaw;
505          lastRotIncTime = now;
506        }
507      }
508    
509      protected void jitter() {
510        switch( random.nextInt(8) ) {
511        case 0:
512          runTo( x+(PLAYER_RADIUS/2), y, z );
513          break;
514        case 1:
515          runTo( x+(PLAYER_RADIUS/2), y+(PLAYER_RADIUS/2), z );
516          break;
517        case 2:
518          runTo( x, y+(PLAYER_RADIUS/2), z );
519          break;
520        case 3:
521          runTo( x-(PLAYER_RADIUS/2), y+(PLAYER_RADIUS/2), z );
522          break;
523        case 4:
524          runTo( x-(PLAYER_RADIUS/2), y, z );
525          break;
526        case 5:
527          runTo( x-(PLAYER_RADIUS/2), y-(PLAYER_RADIUS/2), z );
528          break;
529        case 6:
530          runTo( x, y-(PLAYER_RADIUS/2), z );
531          break;
532        default:
533          runTo( x+(PLAYER_RADIUS/2), y-(PLAYER_RADIUS/2), z );
534          break;
535        }
536      }
537    
538      //  Inner Classes
539      ///////////////////////////////////////////////////////////////////////////
540      protected class Path {
541        public final Vector3D to;   // Usually the location of a node
542        public final Vector3D from;
543        public final double theta;  // Angle on the x-y plane, from x axis
544        public final double phi;    // Angle from the x-y plane
545        public final double distSquared;
546    
547        public final Line2D line;
548    
549        public Path( Vector3D to, Vector3D from ) {
550          this.to = to;
551          this.from = from;
552    
553          double dx = to.x-from.x;
554          double dy = to.y-from.y;
555          double dz = to.z-from.z;
556          theta = Math.atan2( dy, dx );
557          double xyRadiusSquared = dx*dx + dy*dy;
558          phi = Math.atan2( dz, Math.sqrt( xyRadiusSquared ) );
559          distSquared = xyRadiusSquared + dz*dz;
560    
561          line = new Line2D.Double( from.x, from.y, to.x, to.y );
562        }
563      }
564    
565      protected class Node {
566        // Public Data
567        ///////////////////////////////////////////////////////////////////////
568        public final String   id;
569        public final int      type = NONE; // TO-DO: need to define node types
570        public final Vector3D loc;
571        public final SortedSet paths = new TreeSet( pathComparator );
572    
573        // Public Methods
574        ///////////////////////////////////////////////////////////////////////
575        public Node( Message message ) {
576          String value = message.getProperty( LOCATION );
577          Vector3D temp = null;
578          if( value != null ) {
579            try {
580              double[] vector = parseVector( value );
581              temp = new Vector3D( vector[0], vector[1], vector[2] );
582            } catch( RuntimeException error ) {
583              // temp
584            }
585          }
586          loc = temp;
587          id = message.getProperty( ACTOR_ID );
588        }
589    
590        public boolean equals( Object obj ) {
591          if( !(obj instanceof Node) )
592            return false;
593          Node node = (Node) obj;
594          if( id == null )
595            return node.id == null &&
596                   loc.near( node.loc, PLAYER_RADIUS );
597          return id.equals( node.id );
598        }
599    
600        public String toString() {
601          return "{Node "+ACTOR_ID+"="+id+", "+LOCATION+"="+loc+"}";
602        }
603    
604        public void reachableFrom( Vector3D from ) {
605          synchronized( paths ) {
606            Path path = new Path( loc, from );
607            if( !paths.contains( path ) ) {
608              SortedSet sub = paths.headSet( path );
609              if( !sub.isEmpty() ) {
610                Path prev = (Path) sub.last();
611                if( prev != null &&
612                    (path.theta - prev.theta < THETA_DELTA) &&
613                    (path.distSquared > prev.distSquared) )
614                  paths.remove( prev );
615              }
616              sub = paths.tailSet( path );
617              if( !sub.isEmpty() ) {
618                Path next = (Path) sub.first();
619                if( next != null &&
620                    (next.theta - path.theta < THETA_DELTA) &&
621                    (path.distSquared > next.distSquared) )
622                  paths.remove( next );
623              }
624              // TO-DO: special case theta's near +/-pi?
625              paths.add( path );
626            }
627          }
628        }
629      }
630    
631      protected class NodeMap extends JComponent {
632        // Private Constants
633        ///////////////////////////////////////////////////////////////////////
634        protected final double minSymbolSize = 8d;
635        protected final double symbolSize = 12d;
636    
637        protected final Color COLOR_TEAM_GOLD = new Color( 0xCC9900 );  // Gold
638        protected final Color COLOR_TEAM_DEFAULT = Color.black;
639    
640        protected final Color COLOR_BACKGROUND = Color.white;
641        protected final Color COLOR_AXIS = Color.lightGray;
642    
643        protected final Color COLOR_NODE = Color.gray;
644        protected final Color COLOR_NODE_VISIBLE = Color.darkGray;
645        protected final Color COLOR_NODE_TARGET = Color.pink;
646        protected final Color COLOR_NODE_TARGET_VISIBLE = Color.magenta;
647        protected final Color COLOR_PATH = new Color( 0xEEEEEE );  // Very light gray
648    
649        // Private Data
650        ///////////////////////////////////////////////////////////////////////
651        protected Object scaleLock = new Object();
652        protected double scale, symbolScale;
653    
654        protected Line2D xAxis = new Line2D.Double( -Double.MAX_VALUE, 0, Double.MAX_VALUE, 0 );
655        protected Line2D yAxis = new Line2D.Double( 0, -Double.MAX_VALUE, 0, Double.MAX_VALUE );
656        protected Ellipse2D playerCircle = new Ellipse2D.Double( -symbolSize/2, -symbolSize/2,
657                                                                 symbolSize,    symbolSize );
658        protected Ellipse2D navPointCircle = new Ellipse2D.Double( -symbolSize/4, -symbolSize/4,
659                                                                   symbolSize/2,  symbolSize/2 );
660    
661        //  Public Methods
662        ///////////////////////////////////////////////////////////////////////
663        public NodeMap() {
664          setDoubleBuffered( true );
665          setScale( minSymbolSize/(2d*PLAYER_RADIUS) );
666        }
667    
668        public double getScale() {
669          return scale;
670        }
671    
672        public void setScale( double scale ) {
673          synchronized( scaleLock ) {
674            this.scale = scale;
675            this.symbolScale = Math.max( 2*PLAYER_RADIUS, minSymbolSize/scale )/symbolSize;
676          }
677          repaint();
678        }
679    
680        //  Private Methods
681        ///////////////////////////////////////////////////////////////////////
682        protected void paintComponent( Graphics g ) {
683          Color oldColor = g.getColor();
684          Font  oldFont  = g.getFont();
685    
686          Font font = getFont();
687          g.setFont( font );
688          FontMetrics fm = getFontMetrics( font );
689          Dimension size = getSize();
690    
691          if( g instanceof Graphics2D ) {
692            // Clear background
693            g.setColor( COLOR_BACKGROUND );
694            g.fillRect( 0, 0, size.width, size.height );
695    
696            if( paintComponent2D( (Graphics2D) g.create() ) )
697              repaint();
698          } else {
699            // Clear background
700            g.setColor( getBackground() );
701            g.fillRect( 0, 0, size.width, size.height );
702    
703            g.setColor( getForeground() );
704            g.drawString( "Requires Graphics2D", 2, fm.getAscent()+2 );
705          }
706    
707          if( !client.isConnected() ) {
708            final String message = "Disconnected";
709            int width = fm.stringWidth( message );
710    
711            g.setColor( Color.red );
712            g.drawString( message, size.width-width-2, size.height-fm.getDescent()-2 );
713          }
714    
715    
716          g.setColor( oldColor );
717          g.setFont( oldFont );
718        }
719    
720        protected boolean paintComponent2D( Graphics2D g ) {
721          Graphics2D g2, g3;
722          Iterator i, i2;
723          Node node;
724          Path path;
725          boolean needRepaint = false;
726    
727          Dimension size = getSize();
728          Rectangle2D.Double bounds = new Rectangle2D.Double( 0, 0, size.width, size.height );
729    
730          // Atomic copy (need a consistent set of variables, but won't write to them;
731          final double scale, symbolScale;
732          synchronized( scaleLock ) {
733            scale = this.scale;
734            symbolScale = this.symbolScale;
735          }
736          final double x, y, z, yaw, pitch;
737          synchronized( selfLock ) {
738            x = ExampleBot.this.x;
739            y = ExampleBot.this.y;
740            z = ExampleBot.this.z;
741            yaw = ExampleBot.this.yaw;
742            pitch = ExampleBot.this.pitch;
743          }
744    
745          // Scale && translate
746          g.translate( bounds.width/2, bounds.height/2 );
747          g.scale( scale, scale );
748          g2 = (Graphics2D) g.create();
749            g2.translate( -x, -y );
750    
751            // Draw Origin
752            g2.setColor( COLOR_AXIS );
753            g2.draw( xAxis );
754            g2.draw( yAxis );
755    
756    //        synchronized( nodeInfoLock ) {
757            try {
758              // Draw Paths...
759              i = knownNodes.iterator();
760              while( i.hasNext() ) {
761                node = (Node) i.next();
762    
763                i2 = node.paths.iterator();
764                g2.setColor( COLOR_PATH );
765                while( i2.hasNext() ) {
766                  path = (Path) i2.next();
767                  g2.draw( path.line );
768                }
769              }
770    
771              // Draw Nodes...
772              i = knownNodes.iterator();
773              while( i.hasNext() ) {
774                node = (Node) i.next();
775    
776                g3 = (Graphics2D) g2.create();
777                  g3.translate( node.loc.x, node.loc.y );
778                  g3.scale( symbolScale, symbolScale );
779                  drawNode( g3, node );
780                g3.dispose();
781              }
782            } catch( RuntimeException error ) {
783              // Probably just a problem with not blocking while accessing the node Info
784              needRepaint = true;
785            }
786    
787            // Draw Players...
788    
789          g2.dispose();
790    
791          // Draw Self
792          g2 = (Graphics2D) g.create();
793            g2.scale( symbolScale, symbolScale );
794            drawPlayer( g2, team, yaw, pitch );
795          g2.dispose();
796    
797          return needRepaint;
798        }
799    
800        protected void drawPlayer( Graphics2D g, int team, double yaw, double pitch ) {
801          switch( team ) {
802          case TEAM_RED:
803            g.setColor( Color.red );
804            break;
805          case TEAM_BLUE:
806            g.setColor( Color.blue );
807            break;
808          case TEAM_GREEN:
809            g.setColor( Color.green );
810            break;
811          case TEAM_GOLD:
812            g.setColor( COLOR_TEAM_GOLD );
813            break;
814          default:
815            g.setColor( COLOR_TEAM_DEFAULT );
816            break;
817          }
818    
819          double length = symbolSize*Math.cos( pitch );
820          double x = length * Math.cos( yaw );
821          double y = length * Math.sin( yaw );
822    
823          g.draw( playerCircle );
824          g.draw( new Line2D.Double( 0d, 0d, x, y ) );
825        }
826    
827        protected void drawNode( Graphics2D g, Node node ) {
828          switch( node.type ) {
829          default:
830            if( visibleNodes.contains(node) ) {
831              if( node == target )
832                g.setColor( Color.magenta );
833              else
834                g.setColor( Color.darkGray );
835            } else {
836              if( node == target )
837                g.setColor( Color.pink );
838              else
839                g.setColor( Color.lightGray );
840            }
841            g.draw( navPointCircle );
842            break;
843          }
844        }
845      }
846    }