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 }