#ifndef TANKS_H
#define TANKS_H
+
#ifdef DEBUG
#define TANKS_DEBUG_LOG 1
-#else
+
+#else // DEBUG
#define TANKS_DEBUG_LOG 0
+
#endif // DEBUG
+
#endif // TANKS_H
#import "TanksMacros.h"
+#import "qq/NSArray+QQExtensions.h"
+#import "qq/NSDictionary+QQExtensions.h"
#import "qq/NSSet+QQExtensions.h"
#import "render/QQSparrowExtensions.h"
#define b2s(__val) ((__val)?("YES"):("NO"))
+#define ATTACK_COOLDOWN_NAME @"attack"
+
+
#endif
\ No newline at end of file
#import "game/QQGameTime.h"
#import "game/QQThing.h"
+#import "game/ability/QQCooldown.h"
+
/**
* Anything that takes a turn is QQActive.
*/
@protocol QQActive <QQThing>
-@property (nonatomic, readwrite, getter=isActive) BOOL active;
-// @property (nonatomic, readwrite, getter=isSleeping) BOOL sleeping;
-@property (nonatomic, readwrite, retain) NSArray* cooldowns;
+@property (nonatomic, readwrite, getter=isActive) BOOL active;
+@property (nonatomic, readwrite, getter=isSleeping) BOOL sleeping;
+
+@property (nonatomic, readonly, retain) NSMutableDictionary* cooldowns;
+- (void) addCooldown:(QQCooldown*)cool;
+- (void) tickCooldowns;
- (void) tick;
- (void) act;
+
@end
\ No newline at end of file
/**
* Called to setup the Sparrow shape.
*/
-- (SPDisplayObject*) setupShape;
+- (void) setupShape;
/**
* Called to update appearance after game actions have occurred
#import "Sparrow.h"
+#import "Tanks.h"
#import "qq/event/QQNotificationCenter.h"
#import "physics/QQWorld.h"
@property (nonatomic, readonly) float elapsed;
@property (nonatomic, readonly) long ticks;
-@property (nonatomic, readonly, getter=isPaused) BOOL paused;
+@property (nonatomic, readonly, getter=isRunning) BOOL running;
@property (nonatomic, readwrite, assign) BOOL debugDrawingEnabled;
@property (nonatomic, readonly) SPStage* stage;
@property (nonatomic, readonly) QQLevel* level;
@property (nonatomic, readonly) QQWorld* world;
-
@property (nonatomic, readonly, retain) NSSet* actors;
-- (void) tick:(float)elapsed;
-
/** Alerts the game to a new actor. */
- (QQGame*) addActor:(QQActor*)actor;
- (QQGame*) removeActor:(QQActor*)actor;
/** Pauses the game. */
- (void) stop;
+/** Advances the game clock one step and elapsed ms. */
+- (void) tick:(float)elapsed;
+
+ (QQGame*) current;
#import <Box2D/Box2D.h>
-#import "Tanks.h"
#import "game/QQGame.h"
#import "game/unit/QQActors.h"
#import "physics/event/QQContactNotification.h"
- (void) onEnterFrame:(SPEnterFrameEvent*)event;
- (void) onCollide:(QQContactNotification*)msg;
+- (void) logEvent:(SPEvent*)evt;
@end
return _CurrentGame;
}
+
- (id) init {
if (_CurrentGame) {
[self release];
[[[QQTank alloc] initAtX:wMax/6 y:hMax/6 width:50 height:50 color:0xFF0071] autorelease];
[[[QQUnit alloc] initAtX:wMax/3 y:hMax/2 width:50 height:50 color:0x4596FF] autorelease];
-#if TANKS_DEBUG_LOG
- [_stage addEventListener:@selector(logEvent:) atObject:self forType:SP_EVENT_TYPE_ANY];
-#endif
+ #if TANKS_DEBUG_LOG
+ [_stage addEventListener:@selector(logEvent:) atObject:self forType:SP_EVENT_TYPE_ANY];
+ #endif
+
}
return self;
}
- (id<QQGameTime>) time { return self; }
-@synthesize paused = _running;
+@synthesize running = _running;
@synthesize stage = _stage;
@synthesize root = _root;
/// methods
- (void) tick:(float)elapsed {
- if (!self.isPaused) return;
+ if (!self.isRunning) return;
+
_ticks++;
- _elapsed = elapsed;
- _now += elapsed;
+ _elapsed = elapsed;
+ _now += elapsed;
for (QQActor* actor in self.actors)
[actor tick];
}
- (void) onCollide:(QQContactNotification*)msg {
- NSLog(@"%@", msg);
+// NSLog(@"\v\t%@", msg);
}
- (void) logEvent:(SPEvent*)evt {
// ignore enter-frame events, as they happen constantly
if ([evt.type isEqualToString:SP_EVENT_TYPE_ENTER_FRAME]) return;
- NSLog(@"%@", evt);
+ NSLog(@"\v\t%@", evt);
}
- (NSString*) description {
- return [NSString stringWithFormat:@"<%@: %p> {running=%s, #actors=%i, time={now=%f, elapsed=%f, ticks=%l}}",
- [self class], (long)self, b2s(self.isPaused), [self.actors count],
- self.now, self.elapsed, self.ticks];
+ return [NSString stringWithFormat:@"<%@: %p> {running=%s, #actors=%i, time={now=%f, ticks=%l, elapsed=%f}}",
+ [self class], (long)self,
+ b2s(self.isRunning), [self.actors count],
+ self.now, self.ticks, self.elapsed
+ ];
}
/// The y coordinate in the simulation.
@property (nonatomic, readwrite, assign) float y;
+/// Simulation coordinates
@property (nonatomic, readwrite) CGPoint position;
- (void) setPositionX:(float)x y:(float)y;
/// The height of the object in simulation units.
//@property (nonatomic, assign) float height;
-
-
-
// FIXME: don't expose these, instead aggregate properties from body & fixtures/shapes
@property (nonatomic, readonly) b2Body* body;
@property (nonatomic, readonly) b2Fixture* fixture;
+
+/// methods
+
/**
- * Called after initialization to create the various bodies, fixtures, and shapes
- * that represent the Unit in Box2D.
+ * Called to create the various bodies, fixtures, and shapes that represent
+ * the Unit in Box2D. Normally called after initialization completes, but may
+ * be deferred if in a callback.
*/
-- (void) setupPhysics;
+- (void) setupPhysics:(b2BodyDef*)bd;
+- (void) applyLinearImpulse:(CGPoint)vec;
- (void) applyLinearImpulseX:(float)xNs y:(float)yNs;
-#import "qq/event/QQObservable.h"
+#import "qq/event/QQNotifier.h"
@class QQGame;
@class QQWorld;
+#import "Tanks.h"
#import "game/QQGameTime.h"
*/
@interface QQCooldown : NSObject {
@private
+ NSString* _name;
float _duration;
float _elapsed;
}
+/** Name of this cooldown timer. */
+@property (nonatomic, readwrite, retain) NSString* name;
+
/** Duration of this cooldown timer (ms). */
@property (nonatomic, readwrite, assign) float duration;
@property (nonatomic, readwrite, assign) float elapsed;
/** Whether Cooldown timer is ready to be activated. */
-@property (nonatomic, readwrite) BOOL ready;
+@property (nonatomic, readwrite, getter=isReady) BOOL ready;
/** Percent completed (elapsed / duration), clamped [0, 1.0]. */
@property (nonatomic, readwrite) float ratio;
/**
* Implies ready=YES.
*/
-- (id) initWithDuration:(float)duration;
-- (id) initWithDuration:(float)duration andReady:(BOOL)ready;
+- (id) init:(NSString*)name duration:(float)duration;
+- (id) init:(NSString*)name duration:(float)duration ready:(BOOL)ready;
/**
* Attempts to activate this cooldown, clearing the ready flag and setting elapsed to 0.
- (BOOL) tick:(id<QQGameTime>)time;
-+ (QQCooldown*) cooldownWithDuration:(float)duration;
-+ (QQCooldown*) cooldownWithDuration:(float)duration andReady:(BOOL)ready;
++ (QQCooldown*) cooldown:(NSString*)name duration:(float)duration;
++ (QQCooldown*) cooldown:(NSString*)name duration:(float)duration ready:(BOOL)ready;
@end
/// properties
+@synthesize name = _name;
@synthesize duration = _duration;
@synthesize elapsed = _elapsed;
-- (BOOL) ready { return (self.elapsed >= self.duration); }
+- (BOOL) isReady { return (self.elapsed >= self.duration); }
- (void) setReady:(BOOL)newReady {
if (newReady == self.ready)
return;
self.elapsed = 0;
}
-- (float) ratio { return fminf(1.0f, self.elapsed / self.duration); }
+- (float) ratio { return MIN(1.0f, self.elapsed / self.duration); }
- (void) setRatio:(float)newRatio {
if (newRatio >= 1.0f)
self.elapsed = self.duration;
/// initializers
-- (id) initWithDuration:(float)duration {
- return [self initWithDuration:duration andReady:YES];
+- (id) init:(NSString*)name duration:(float)duration {
+ return [self init:name duration:duration ready:YES];
}
-- (id) initWithDuration:(float)duration andReady:(BOOL)ready {
- if ((self = [super init])){
+- (id) init:(NSString*)name duration:(float)duration ready:(BOOL)ready {
+ if ((self = [super init])) {
+ if (duration <= 0.0f) {
+ [self autorelease];
+ [NSException raise:@"InvalidArgumentException" format:@"duration must be positive!"];
+ }
+ self.name = name;
_duration = duration;
_elapsed = (ready ? _duration : 0);
}
return self.ready;
}
+- (NSString*) description {
+ return [NSString stringWithFormat:@"<%@: %p> {name=%@, ready=%s (%.3f%% %.3f/%.3f)}",
+ [self class], self, _name, b2s(self.isReady),
+ self.ratio, _elapsed, _duration
+ ];
+}
+
+
/// convenience constructors
-+ (QQCooldown*) cooldownWithDuration:(float)duration {
- return [[[QQCooldown alloc] initWithDuration:duration] autorelease];
++ (QQCooldown*) cooldown:(NSString*)name duration:(float)duration {
+ return [[[QQCooldown alloc] init:name duration:duration] autorelease];
}
-+ (QQCooldown*) cooldownWithDuration:(float)duration andReady:(BOOL)ready {
- return [[[QQCooldown alloc] initWithDuration:duration andReady:ready] autorelease];
++ (QQCooldown*) cooldown:(NSString*)name duration:(float)duration ready:(BOOL)ready {
+ return [[[QQCooldown alloc] init:name duration:duration ready:ready] autorelease];
}
#include <Box2D/Box2D.h>
-#import "qq/event/QQNotificationProxy.h"
+#import "Tanks.h"
+#import "qq/event/QQNotifiers.h"
#import "game/QQThing.h"
#import "game/QQActive.h"
#import "game/QQPhysical.h"
#import "game/QQDisplayable.h"
+#import "game/ability/QQCooldown.h"
#import "game/unit/QQActorDelegate.h"
#import "physics/QQWorld.h"
b2Body* _body;
@private
+ id<QQActorDelegate> _delegate;
+
BOOL _active;
BOOL _dirty;
BOOL _dead;
- id<QQActorDelegate> _delegate;
- NSArray* _cooldowns;
+ NSMutableDictionary* _cooldowns;
}
+@property (nonatomic, readwrite, retain) id<QQActorDelegate> delegate;
+
@property (nonatomic, readonly) QQGame* game;
@property (nonatomic, readonly) QQWorld* world;
-@property (nonatomic, readwrite, retain) id<QQActorDelegate> delegate;
-@property (nonatomic, readonly) BOOL dead;
+
+@property (nonatomic, readonly, getter=isDead) BOOL dead;
+@property (nonatomic, readonly, retain) NSMutableDictionary* cooldowns;
+
- (id) initAtX:(float)x y:(float)y;
-- (id) initType:(b2BodyType)type atX:(float)x y:(float)y;
+- (id) initWithType:(b2BodyType)type atX:(float)x y:(float)y;
+- (id) initWithBodyDef:(b2BodyDef*)bd;
- (void) destroy;
#import "QQActor.h"
-#import "render/QQSparrowExtensions.h"
#import "game/QQGame.h"
/// properties
-@dynamic shape;
-
-@synthesize active = _active;
-@synthesize dirty = _dirty;
-@synthesize dead = _dead;
-@synthesize delegate = _delegate;
-@synthesize cooldowns = _cooldowns;
-
-
- (QQGame*) game { return QQGame.current; }
- (QQWorld*) world { return self.game.world; }
- (b2Body*) body { return _body; }
- (b2Fixture*) fixture { return nil; }
-- (id) implicitNotificationSender { return self; }
-- (void) setImplicitNotificationSender:(id)sender {}
-- (id<QQObservableNotifier>) proxiedNotifier { return self.game; }
-- (void) setProxiedNotifier:(id<QQObservableNotifier>)notifier {}
+@synthesize delegate = _delegate;
+@synthesize cooldowns = _cooldowns;
+@synthesize dead = _dead;
+@synthesize dirty = _dirty;
+@synthesize active = _active;
+
+- (BOOL) isSleeping { return _active; }
+- (void) setSleeping:(BOOL)v { _active = v; }
- (float) x { return self.body->GetPosition().x; }
- (void) setX:(float)x { [self setPositionX:x y:self.y]; }
-
- (float) y { return self.body->GetPosition().y; }
- (void) setY:(float)y { [self setPositionX:self.x y:y]; }
-
- (CGPoint) position {
b2Vec2 pos = self.body->GetPosition();
return CGPointMake(pos.x, pos.y);
- (void) setPositionX:(float)x y:(float)y {
self.body->SetTransform(b2Vec2(x,y), self.body->GetAngle());
}
-
- (float) rotation { return self.body->GetAngle(); }
- (void) setRotation:(float)r { self.body->SetTransform(self.body->GetPosition(), r); }
+@dynamic shape;
+
+// sync sparrow with box2d
+
- (void) updateShapeX:(float)x y:(float)y {
float px = self.world.scale;
[self.shape setCenterX:x*px y:y*px];
}
-
- (void) updateShapeX:(float)x y:(float)y rotation:(float)r {
[self updateShapeX:x y:y];
self.shape.rotation = r;
}
-
- (void) updateShape {
b2Transform trans = self.body->GetTransform();
[self updateShapeX:trans.position.x y:trans.position.y rotation:trans.GetAngle()];
/// initializers
+- (id) init {
+ return [self initAtX:0 y:0];
+}
+
- (id) initAtX:(float)x y:(float)y {
- return [self initType:b2_dynamicBody atX:x y:y];
+ return [self initWithType:b2_dynamicBody atX:x y:y];
}
-- (id) initType:(b2BodyType)type atX:(float)x y:(float)y {
+- (id) initWithType:(b2BodyType)type atX:(float)x y:(float)y {
+ b2BodyDef bd;
+ bd.type = type;
+ bd.position = b2Vec2(x,y);
+ bd.userData = self;
+ return [self initWithBodyDef:&bd];
+}
+
+- (id) initWithBodyDef:(b2BodyDef*)bd {
if ((self = [super init])) {
_active = YES;
_dead = NO;
+
+ self.implicitSender = self;
self.proxiedNotifier = self.game;
- self.cooldowns = [NSArray array];
- [self.game addActor:self];
- b2BodyDef bd;
- bd.type = type;
- bd.position = b2Vec2(x, y);
- bd.userData = self;
- _body = self.world.world->CreateBody(&bd);
+ _cooldowns = [[NSMutableDictionary alloc] initWithCapacity:2];
+ [self setupPhysics:bd];
+ [self.game addActor:self];
if ([self respondsToSelector:@selector(onCollideBegin:)])
[self addObserver:self selector:@selector(onCollideBegin:) name:QQ_EVENT_CONTACT_BEGIN];
+ if ([self respondsToSelector:@selector(onCollideEnd:)])
+ [self addObserver:self selector:@selector(onCollideEnd:) name:QQ_EVENT_CONTACT_END];
}
return self;
}
+/// subclassing
+
+- (void) setupShape {
+ // override in subclass
+}
+
+// TODO: you can't create bodies during callbacks
+/// override but call super!
+- (void) setupPhysics:(b2BodyDef*)bd {
+ if (!_body) {
+ _body = self.world.world->CreateBody(bd);
+ }
+}
+
+- (void) act {
+ // override in subclass
+}
+
+
+
/// methods
+- (void) addCooldown:(QQCooldown*)cool {
+ [self.cooldowns setObject:cool forKey:cool.name];
+}
+
- (void) destroy {
- if (self.dead) return;
+ if (self.isDead) return;
_dead = YES;
self.shape = nil;
}
- (void) tick {
- if (self.dead) return;
+ if (self.isDead) return;
- [self.cooldowns makeObjectsPerformSelector:@selector(tick:) withObject:self.game.time];
+ [self tickCooldowns];
if (self.isActive)
[self act];
}
-- (void) act {}
-
-- (SPDisplayObject*) setupShape {
- return self.shape;
+- (void) tickCooldowns {
+ [self.cooldowns makeObjectsPerformSelector:@selector(tick:) withObject:self.game.time];
}
- (void) draw {
- if (self.dead) return;
-
+ if (self.isDead) return;
+ if (!self.shape) [self setupShape];
[self updateShape];
}
-- (void) setupPhysics {}
-
-
+- (void) applyLinearImpulse:(CGPoint)pt {
+ [self applyLinearImpulseX:pt.x y:pt.y];
+}
- (void) applyLinearImpulseX:(float)xNs y:(float)yNs {
self.body->ApplyLinearImpulse(b2Vec2(xNs, yNs), self.body->GetPosition());
}
b2Transform trans = self.body->GetTransform();
b2Vec2 pos = trans.position;
//b2Vec2 v = actor.body->GetLinearVelocity();
- SPDisplayObject* s = self.shape;
- return [NSString stringWithFormat:@"[%@ box2d=[ (%f,%f) rotation=%f], shape=%@]",
- [super description], pos.x,pos.y, trans.GetAngle(), s];
+ return [NSString stringWithFormat:@"\v\t<%@ %p> {\v\t\tbox2d=[ (%f,%f) rotation=%f], \v\t\tshape=%@, \v\t\tcooldowns=%@}",
+ [self class], self, pos.x,pos.y, trans.GetAngle(), self.shape, self.cooldowns];
}
+#import "Tanks.h"
#import "game/unit/QQUnit.h"
}
- (void) onCollideBegin:(QQContactNotification*)msg {
- QQActor* other = (msg.actorA && (msg.actorA != self) ? msg.actorA :
- (msg.actorB && (msg.actorB != self) ? msg.actorB : nil));
+ QQActor* other = ((msg.actorA && (msg.actorA != self)) ? msg.actorA :
+ ((msg.actorB && (msg.actorB != self)) ? msg.actorB : nil));
if (other) {
NSLog(@"BOOM! %@", msg);
[other destroy];
#import "game/unit/QQUnit.h"
-#import "game/ability/QQCooldown.h"
@interface QQTank : QQUnit {
- QQCooldown* _coolAtk;
+
}
-@property (nonatomic, readonly, retain) QQCooldown* coolAtk;
@end
// private interface
@interface QQTank ()
-@property (nonatomic, readwrite, retain) QQCooldown* coolAtk;