001 package components; 002 003 import java.awt.event.ActionEvent; 004 import java.awt.event.ActionListener; 005 import java.awt.event.KeyAdapter; 006 import java.util.ArrayList; 007 import java.util.HashMap; 008 import java.util.HashSet; 009 import java.util.List; 010 import java.util.Map; 011 import java.util.Set; 012 013 import javax.swing.Timer; 014 015 import physics3d.Angle; 016 import physics3d.GameConstants; 017 import physics3d.PhysicsShape; 018 import physics3d.Shape; 019 import physics3d.Vect3; 020 021 022 /** 023 * GameObject is the most basic description of an object in gizmo ball. 024 * It is mutable and abstract. 025 * @specfield name : String // the name of the object 026 * @specfield center : Vect3 // the center of rotation of the object 027 * @specfield velocity : Vect3 // the velocity of the object 028 * @specfield orientation // the orientation of the object 029 * @specfield gs : GameSpace // the GameSpace that the object is in 030 * @specfield shape : Shape // the Shape of the object 031 * @specfield targets : set // the other GameObjects that this object affects 032 * @specfield frozen : boolean // whether the GameObject reponds to forces 033 * 034 * @derivfield tlf : Vect3 // the top left front corner of the object 035 */ 036 037 /* 038 * Abstraction Function 039 * AF(r) = a GameObject, go, such that 040 * go.name = r.name 041 * go.tlf = r.tlf 042 * go.center = r.center 043 * go.velocity = r.velocity 044 * go.orientation is given by r.orientVect and r.orientAngle 045 * go.gs = r.gameSpace 046 * go.shape = r.shape 047 * go.targets = r.targets 048 * go.frozen = r.frozen 049 */ 050 051 /* 052 * Rep Invariant 053 * all fields cannot be null 054 * position is within the bounds of its GameSpace, as specified by the Walls of the GameSpace 055 */ 056 057 public abstract class GameObject extends KeyAdapter implements ActionListener { 058 //fields 059 protected GameSpace g; //required - parent gamespace 060 protected Vect3 center; //required - usually physical center, center of rotation 061 protected Vect3 orientVect; //not required - axis of rotation 062 protected Angle orientAngle; //not required - angle of rotation 063 protected Vect3 velocity; //not required - velocity 064 protected Shape shape; //same for all objects of a class 065 protected String name; //required - unique identifier 066 protected double coRef; //coefficent of reflection 067 protected boolean visible; //whether or not this object should be rendered 068 protected boolean frozen; //whether this object responds to forces 069 protected int width; //not required, width in grid spaces (x) 070 protected int height; //not required, height in grid spaces (y) 071 protected int depth; //not required, depth in grid spaces (z) 072 protected int delay; //not required, delay in ms between when action() is called and when it takes place 073 074 //triggers 075 protected Set<GameObject> targets; //targets on collisions 076 077 //for selected texture 078 protected boolean selected; 079 080 //constructors 081 /** 082 * @effects creates a GameObject with zero velocity 083 */ 084 public GameObject(Vect3 tlf, Vect3 ov, Angle oa, String name, GameSpace g) { 085 this(tlf, Vect3.ZERO, ov, oa, name, g); 086 } 087 088 /** 089 * @effects creates a GameObject with zero velocity 090 */ 091 public GameObject(Map<String,String> props, GameSpace gs) { 092 if (props == null) 093 throw new IllegalArgumentException("properties map to GameObject() cannot be null"); 094 if (gs == null) 095 throw new IllegalArgumentException("gamespace to GameObject() cannot be null"); 096 //set properties to defaults, then override with given arguments 097 Map<String, String> properties = defaults(); 098 properties.putAll(props); 099 for (String key : properties.keySet()) 100 setProperty(key, properties.get(key)); 101 // if a properties is not in defaults or props, then checkRep will catch it 102 center = center.plus(getDiff()); //diff points from tlf to center 103 shape = shape(); //shape is shape w.r.t. center 104 105 //set trigger targets 106 targets = new HashSet<GameObject>(); 107 108 //set gamespace 109 this.g = gs; 110 //System.out.println("param.gs="+gs); 111 //System.out.println("this.g="+g); 112 113 //selected defaults to false 114 selected = false; 115 116 checkRep(); //check that all required properties are set 117 } 118 119 /** check whether satisfies representation invariant 120 * @throws RuntimeException if invar not met 121 */ 122 private void checkRep() { 123 if (center == null) 124 throw new RuntimeException("center cannot be null"); 125 if (g == null) 126 throw new RuntimeException("g cannot be null"); 127 if (orientVect == null) 128 throw new RuntimeException("orientVect cannot be null"); 129 if (orientAngle == null) 130 throw new RuntimeException("orientAngle cannot be null"); 131 if (velocity == null) 132 throw new RuntimeException("velocity cannot be null"); 133 if (shape == null) 134 throw new RuntimeException("shape cannot be null"); 135 if (name == null) 136 throw new RuntimeException("name cannot be null"); 137 } 138 139 /** sets all the defaults for this class. override in subclass if you want 140 * different defaults 141 * must be called at the beginning of the constructor. 142 */ 143 protected Map<String, String> defaults() { 144 Map<String, String> defaults = new HashMap<String, String>(); 145 //defaults.put("x", "0"); 146 //defaults.put("y", "0"); 147 //defaults.put("z", "0"); 148 defaults.put("orientation", "0"); //override in flipper, for example -- orientation is required 149 //defaults.put("name", "name"); 150 defaults.put("width", "1"); 151 defaults.put("height", "1"); 152 defaults.put("depth", "1"); 153 defaults.put("xvelocity", "0"); 154 defaults.put("yvelocity", "0"); 155 defaults.put("zvelocity", "0"); 156 defaults.put("frozen", "true"); 157 defaults.put("visible", "true"); 158 defaults.put("delay", "0"); 159 defaults.put("coref", "1.0"); 160 return defaults; 161 } 162 163 /** 164 * @requires string s is a property in this 165 * @effects adds this property to this 166 */ 167 protected void setProperty(String s1, String s2) { 168 //Sets Map(s1) to s2 169 s1 = s1.toLowerCase(); 170 if (s1.equals("x")) { 171 if (center == null) 172 center = Vect3.ZERO; 173 center = center.plus(Vect3.X_HAT.times(Double.valueOf(s2))); 174 } else if (s1.equals("y")) { 175 if (center == null) 176 center = Vect3.ZERO; 177 center = center.plus(Vect3.Y_HAT.times(Double.valueOf(s2))); 178 } else if (s1.equals("z")) { 179 if (center == null) 180 center = Vect3.ZERO; 181 center = center.plus(Vect3.Z_HAT.times(Double.valueOf(s2))); 182 } else if (s1.equals("orientation")) { 183 orientVect = Vect3.Z_HAT; 184 orientAngle = new Angle(Math.PI * Double.valueOf(s2) / 180.0); 185 } else if (s1.equals("name")) { 186 name = s2; 187 } else if (s1.equals("width")) { 188 width = Integer.valueOf(s2); 189 } else if (s1.equals("height")) { 190 height = Integer.valueOf(s2); 191 } else if (s1.equals("depth")) { 192 depth = Integer.valueOf(s2); 193 } else if (s1.equals("xvelocity")) { 194 if (velocity == null) 195 velocity = Vect3.ZERO; 196 velocity = velocity.plus(Vect3.X_HAT.times(Double.valueOf(s2))); 197 } else if (s1.equals("yvelocity")) { 198 if (velocity == null) 199 velocity = Vect3.ZERO; 200 velocity = velocity.plus(Vect3.Y_HAT.times(Double.valueOf(s2))); 201 } else if (s1.equals("zvelocity")) { 202 if (velocity == null) 203 velocity = Vect3.ZERO; 204 velocity = velocity.plus(Vect3.Z_HAT.times(Double.valueOf(s2))); 205 } else if (s1.equals("coref")) { 206 coRef = Double.valueOf(s2); 207 } else if (s1.equals("frozen")) { 208 frozen = Boolean.valueOf(s2); 209 } else if (s1.equals("visible")) { 210 visible = Boolean.valueOf(s2); 211 } else if (s1.equals("delay")) { 212 delay = Integer.valueOf(s2); 213 } else { 214 //This case is ok, it just means an invalid property was given, failing 215 //is not the appropriate response 216 //System.out.println("Invalid property given ...key=" + s1 + " value="+s2); 217 // throw new RuntimeException("wtf, mate? s1=" + s1 + ", s2="+s2); 218 } 219 } 220 221 /** 222 * @effects creates a GameObject 223 */ 224 public GameObject(Vect3 tlf, Vect3 velocity, Vect3 ov, Angle oa, String name, GameSpace g) { 225 this.velocity = velocity; 226 this.orientVect = ov; 227 this.orientAngle = oa; 228 this.name = name; 229 this.g = g; 230 this.targets = new HashSet<GameObject>(); 231 this.visible = true; 232 this.frozen = false; 233 234 //centerRotation will be set in subclasses 235 } 236 237 //methods 238 /** Difference between TLF and center. ZERO for balls, so should be overridden 239 * @return a Vect3 that points from TLF to center. by default this is the center by width, height, depth 240 */ 241 public Vect3 getDiff() { 242 return new Vect3(width/2.0, height/2.0, depth/2.0); 243 } 244 245 246 247 //overridden by each subclass 248 /** the Shape of the object **/ 249 protected abstract Shape shape(); 250 251 /** gets the top-left-front corner, rounded for double precision things 252 * @return the position of this object, rounded 253 */ 254 public Vect3 getTLF() { 255 //tlf + diff = center 256 ////System.out.println(this + " " + center + getDiff()); 257 Vect3 tfl = center.minus(getDiff()); 258 Vect3 roundedTLF = new Vect3((int)Math.round(tfl.x()),(int)Math.round(tfl.y()),(int)Math.round(tfl.z())); 259 return roundedTLF; 260 } 261 262 /** gets the top-left-front corner, no rounding 263 * @return the TLF in its original form 264 */ 265 public Vect3 getNonRoundedTLF() { 266 return center.minus(getDiff()); 267 } 268 269 /** convenience method -- getTLF() is rounded 270 * @return the position of this object, rounded 271 */ 272 public Vect3 getRoundedTLF() { 273 return getTLF(); 274 } 275 276 /** Set the top-left-front corner of the object 277 * @requires tlf != null 278 * @effects sets the top left corner of the object 279 */ 280 public void setTLF(Vect3 tlf) { 281 center = tlf.plus(getDiff()); 282 } 283 284 /** 285 * @return this object's velocity 286 */ 287 public Vect3 getVelocity() { 288 return velocity; 289 } 290 291 /** 292 * @requires newVel != null 293 * @modifies velocity 294 * @effects sets velocity to newVel 295 */ 296 public void setVelocity(Vect3 newVel) { 297 velocity = newVel; 298 } 299 300 /** 301 * @return the gamespace this object is in 302 */ 303 public GameSpace getGameSpace() { 304 return g; 305 } 306 307 /** 308 * @return this.name 309 */ 310 311 public String getName() { 312 return this.name; 313 } 314 315 /** get the center of rotation of the object 316 * @return the center of roation of the object 317 */ 318 public Vect3 getCenter() { 319 return center; 320 } 321 /** 322 * @effects changes the center of rotation for the object 323 */ 324 public void setCenter(Vect3 c) { 325 this.center = c; 326 } 327 328 /** 329 * @return the orientation angle of the object 330 */ 331 public Angle getOrientAngle() { 332 return orientAngle; 333 } 334 335 /** 336 * @requires newAngle != null 337 * @modifies orientAngle 338 * @effects sets orientAngle to newAngle 339 */ 340 public void setOrientAngle(Angle newAngle) { 341 orientAngle = newAngle; 342 } 343 344 /** 345 * @return the orientation vector of the object 346 */ 347 public Vect3 getOrientVector() { 348 return orientVect; 349 } 350 351 /** 352 * @requires newVect != null 353 * @modifies orientVect 354 * @effects sets orientVect to newVect 355 */ 356 public void setOrientVect(Vect3 newVect) { 357 orientVect = newVect; 358 } 359 360 361 /** 362 * @return the targets of this object 363 */ 364 public Set<GameObject> getTargets() { 365 return targets; 366 } 367 368 /** 369 * @requires obj != null, obj.gs == this.gs 370 * @modifies targets 371 * @effects adds obj to targets 372 */ 373 public void addTarget(GameObject obj) { 374 targets.add(obj); 375 } 376 377 /** 378 * @modifies targets 379 * @effects removes all targets of this 380 */ 381 public void clearTargets() { 382 targets.clear(); 383 } 384 385 /** 386 * @modifies targets 387 * @effects removes the given target, if present 388 */ 389 public boolean removeTarget(GameObject t) { 390 return targets.remove(t); 391 } 392 393 /** 394 * This is overriden in mobile objects 395 * @effects specifies what the GameObject does in a frame 396 */ 397 public void stepFrame() { } 398 399 /** Schedules actionPerformed() to happen after this.delay **/ 400 public void action() { 401 Timer t = new Timer(delay, this); 402 t.setRepeats(false); 403 t.start(); 404 } 405 /** 406 * This is overriden in objects where something actually occurs when triggered, 407 * for example, Flippers 408 * @effects specifies what the GameObject does when it is triggered. 409 */ 410 public void actionPerformed(ActionEvent e) {}; 411 412 /** 413 * @effects iterates through targets and tell them to perform action() 414 */ 415 public void onCollision(GameObject projectile) { 416 //most objects don't care about what hit them 417 for (GameObject g : targets) { 418 g.action(); //call triggers 419 } 420 } 421 422 /** 423 * @return a valid hash code for this object 424 */ 425 public int hashCode() { 426 return 1; 427 } 428 429 /** 430 * @return go != null && (go instanceof GameObject) && ... 431 */ 432 public boolean equals(Object go) { 433 if ((go != null) && (go instanceof GameObject)) { 434 GameObject myObj = (GameObject) go; 435 return g.equals(myObj.getGameSpace()) && 436 center.equals(myObj.getCenter()) && 437 orientVect.equals(myObj.getOrientVector()) && 438 orientAngle.equals(myObj.getOrientAngle()) && 439 velocity.equals(myObj.getVelocity()) && 440 name.equals(myObj.getName()); 441 } 442 return false; 443 } 444 445 446 /** 447 * @return the Shape describing this Object 448 */ 449 public Shape getShape() { 450 return shape; 451 } 452 453 /** 454 * @return the bounding shape of this 455 */ 456 public PhysicsShape getBounds() { 457 return this.getShape().getBound(); 458 } 459 460 /** 461 * @return true if the object is visible 462 */ 463 public boolean isVisible() { 464 return visible; 465 } 466 467 /** 468 * @param visible the visible to set 469 */ 470 public void setVisible(boolean visible) { 471 this.visible = visible; 472 } 473 474 /** 475 * @return true if frozen 476 */ 477 public boolean isFrozen() { 478 return frozen; 479 } 480 481 /** 482 * @param frozen the frozen to set 483 */ 484 public void setFrozen(boolean frozen) { 485 this.frozen = frozen; 486 } 487 488 /** 489 * changes the map to represent the values of this GameObject required by the xml spec 490 * @modifies m 491 */ 492 public void getBasicPropertyMap(Map<String,String> m) { 493 494 if (this.delay != 0) { 495 m.put("delay", Integer.toString(delay)); 496 } 497 if (this.getGOClassification() != GameObjectClassification.BALL) { 498 putInVect3(getRoundedTLF(), m, "",true); 499 } 500 else { 501 if (this.frozen) { 502 m.put("frozen", Boolean.toString(this.frozen)); 503 } 504 putInVect3(getCenter(), m, "",true); 505 } 506 m.put("name",name); 507 } 508 509 /** 510 * Given a property map, a vector, and a string, adds the keys 511 * x + title, y + title, and z + title with the corresponding values from v, to m 512 * @requires no variables passed are null 513 * @modifies m, if round is true, puts them in as ints, otherwise as doubles 514 */ 515 public static void putInVect3(Vect3 v, Map<String,String> m, String title, boolean round) { 516 if (round) { 517 m.put("x" + title, Integer.toString((int)v.x())); 518 m.put("y" + title, Integer.toString((int)v.y())); 519 m.put("z" + title, Integer.toString((int)v.z())); 520 } 521 else { 522 m.put("x" + title, Double.toString(v.x())); 523 m.put("y" + title, Double.toString(v.y())); 524 m.put("z" + title, Double.toString(v.z())); 525 } 526 } 527 528 /** 529 * Convience overload of putInVect3, this prevents rounding 530 */ 531 public static void putInVect3(Vect3 v, Map<String,String> m, String title) { 532 putInVect3(v, m, title,false); 533 } 534 535 /** 536 * Requires that all obejects return their own classification 537 */ 538 public abstract GameObjectClassification getGOClassification(); 539 540 /** 541 * @return the top left front coner points of all the grid positions occupied 542 * by this GameObject 543 */ 544 public abstract Set<Vect3> getOccupiedPositions(); 545 546 /** 547 * @return whether this GameObject is selected 548 */ 549 public boolean isSelected() { 550 return selected; 551 } 552 553 /** 554 * @effects sets selected to s 555 */ 556 public void setSelected(boolean s) { 557 selected = s; 558 } 559 560 /** 561 * @return the depth 562 */ 563 public int getDepth() { 564 return depth; 565 } 566 567 /** 568 * @param depth the depth to set 569 */ 570 public void setDepth(int depth) { 571 this.depth = depth; 572 } 573 574 /** 575 * @return the height 576 */ 577 public int getHeight() { 578 return height; 579 } 580 581 /** 582 * @requires height > 0 583 * @modifies this.height 584 * @effects sets this.height to height 585 */ 586 public void setHeight(int height) { 587 this.height = height; 588 } 589 590 /** 591 * @return the width 592 */ 593 public int getWidth() { 594 return width; 595 } 596 597 /** 598 * @requires width > 0 599 * @modifies this.width 600 * @effects sets this.width to width 601 */ 602 public void setWidth(int width) { 603 this.width = width; 604 } 605 606 /** 607 * @requires d >= 0 608 * @modifies delay 609 * @effects sets delay to d 610 */ 611 public void setDelay(int d) { 612 delay = d; 613 } 614 615 /** 616 * @return the delay time of this 617 */ 618 public int getDelay() { 619 return delay; 620 } 621 622 /** string representation of this object. ROUNDED coordinates. **/ 623 public String toString() { 624 return name+"<"+Vect3.roundToNearest100(center.x())+"," 625 +Vect3.roundToNearest100(center.y())+"," 626 +Vect3.roundToNearest100(center.z())+">"; 627 } 628 629 /** 630 * @return the coRef 631 */ 632 public double getCoRef() { 633 return coRef; 634 } 635 636 /** 637 * @param coRef the coRef to set 638 */ 639 public void setCoRef(double coRef) { 640 this.coRef = coRef; 641 } 642 }