This is Actor.m in view mode; [Download] [Up]
#import "Actor.h"
#import "ActorMgr.h"
#import "DisplayManager.h"
#import "CacheManager.h"
#import "collisions.h"
#import <math.h>
extern BOOL coalesce(NXRect *p1, NXRect *p2);
float gx, gy;
@implementation Actor
// Hack Alert! I need 1 class variable for each subclass of Actor.
// Since objc doesn't support class variables, I just appropriate the
// use of the version to store a List... This is really gross and has no
// place in production code; it could break archiving (which I don't do...)
// If you just _had_ to do this, you could make the version point to an
// allocated struct that contained the version and class variables, and
// override +setVersion to access it (but you didn't hear it from me!)
+ initialize
{
[self setVersion: (int)[[List alloc] init]]; // Ack!
return self;
}
// Each Actor subclass keeps a list of its instances; instances are never freed
// since they come and go frequently and allocating them is too expensive.
// Instead, the actor manager walks this list and reuses available actors.
// Plus, since they're never killed, they can wait tables on the side.
+ instanceList
{
return (id)[self version]; // Ack!
}
- init
{
[super init];
actorType = (int)[self class];
[[[self class] instanceList] addObjectIfAbsent:self];
return self;
}
#define S_DEBUG 0
// note that this method is _not_ the object's designated initializer;
// this method may be called more than once (by activate)
- reinitWithImage:(const char *)imageName
frameSize:(NXSize *) size
numFrames:(int)frames
shape: (COLLISION_SHAPE)shape
alliance: (ALLIANCE)al
radius: (float) r
buffered: (BOOL) b
x: (float)xp
y: (float)yp
theta: (float) thta
vel: (float) v
interval: (unsigned) time
distToCorner: (NXSize *)d2c
{
image = [self findImageNamed:imageName];
frame = 0;
interval = time;
changeTime = timeInMS+time;
numFrames = frames;
frameSize = *size;
theta = thta;
vel = v;
xv = vel * -sin(theta);
yv = vel * cos(theta);
collisionShape = shape;
alliance = al;
radius = r;
buffered = b;
drawRect.size = *size;
collisionRect.size = *size;
distToCorner = *d2c;
[self moveTo:xp :yp];
complexShapePtr = NULL;
eraseRect.size.width = 0;
return self;
}
// The ActorManager means objects don't necessarily get created and freed;
// instead they are created once and then activated and retired.
- activate:sender :(int)tag
{
employed = YES;
scoreTaker = sender;
return self;
}
- retire
{
// [self erase];
if (buffered && (eraseRect.size.width > 0))
[cacheMgr displayRect:&eraseRect];
employed = NO;
return self;
}
- erase
{
id mgr = (buffered ? cacheMgr : displayMgr);
[mgr erase:&eraseRect];
if (!buffered) eraseRect.size.width = 0;
return self;
}
- positionChanged
{
if (timeInMS > changeTime)
{
changeTime = timeInMS + interval;
if (++frame >= numFrames) frame = 0;
}
return self;
}
- calcDxDy:(NXPoint *)dp
{
dp->x = timeScale * xv;
dp->y = timeScale * yv;
return self;
}
- calcDrawRect
{
drawRect.origin.x = floor(x - gx - distToCorner.width + xOffset);
drawRect.origin.y = floor(y - gy - distToCorner.height + yOffset);
return self;
}
- moveBy:(float)dx :(float)dy
{
x += dx;
collisionRect.origin.x += dx;
y += dy;
collisionRect.origin.y += dy;
// calculate offset into view
[self calcDrawRect];
return self;
}
- moveTo:(float)newx :(float)newy
{
x = newx;
y = newy;
collisionRect.origin.x = x - distToCorner.width;
collisionRect.origin.y = y - distToCorner.height;
[self calcDrawRect];
return self;
}
- setXvYv:(float)xvel :(float)yvel sync:(BOOL)sync
{
xv = xvel;
yv = yvel;
if (sync)
{
theta = atan2(yvel, xvel);
vel = sqrt(xv*xv + yv*yv);
}
return self;
}
- setVel:(float)newVel theta:(float)newTheta sync:(BOOL)sync
{
vel = newVel;
theta = newTheta;
if (sync)
{
xv = vel * -sin(theta);
yv = vel * cos(theta);
}
return self;
}
- setVel:(float)newVel
{
vel = newVel;
return self;
}
- setTheta:(float)newTheta
{
theta = newTheta;
return self;
}
- oneStep
{
NXPoint dXdY;
if (intersectsRect(&screenRect, &eraseRect))
{
[self erase];
}
[self calcDxDy: &dXdY];
[self moveBy:dXdY.x :dXdY.y];
complexShapePtr = NULL;
[self positionChanged];
return self;
}
- scheduleDrawing
{
id mgr = (buffered ? cacheMgr : displayMgr);
if (employed && intersectsRect(&screenRect, &drawRect))
{
[mgr draw:self];
if (buffered)
{
[self addFlushRects];
}
// eraseRect = drawRect;
}
else if ((eraseRect.size.width > 0) && buffered)
{
// we have an erasure region to flush to the screen
[cacheMgr displayRect:&eraseRect];
eraseRect.size.width = 0;
}
return self;
}
- draw
{
NXRect src;
src.origin.x = (frame & 3) * frameSize.width;
src.origin.y = (frame >> 2) * frameSize.height;
src.size = drawRect.size = frameSize;
[image composite:NX_SOVER fromRect:&src toPoint:&drawRect.origin];
eraseRect = drawRect;
#if S_DEBUG
{
NXRect t = collisionRect;
t.origin.x = floor(collisionRect.origin.x - gx + xOffset);
t.origin.y = floor(collisionRect.origin.y - gy + yOffset);
PSsetrgbcolor(1,0,0);
NXFrameRect(&t);
PSnewpath();
PSsetrgbcolor(0,1,0);
PSarc(floor(x - gx + xOffset), floor(y - gy + yOffset),
radius-1, 0.0, 360.0);
PSclosepath();
PSstroke();
}
#endif
return self;
}
// an actor will be sent a tile message when it is time to draw something
// in the virgin buffered background. This is good for static images so you
// only draw them once. Most actors move and thus shouldn't draw anything here.
// focus is locked on the virgin buffer when this is called
- tile
{
return self;
}
// both actors that have collided will be sent a doYouHurt: message to determine if the
// other will be sent a performCollisionWith: message. You could use this opportunity to
// store the other's pre-collision vector, if you care about it.
- (BOOL) doYouHurt:sender
{ return YES;
}
- performCollisionWith:(Actor *) dude
{
if (pointValue) [dude addToScore:pointValue for:self gen:0];
[actorMgr destroyActor:self];
return nil;
}
- (BOOL) wrapAtDistance:(float)distx :(float)disty
{
float dx = 0, dy = 0;
BOOL didWrap = NO;
// warp things around as necessary...
if (x > gx + distx)
{
dx = -2*distx;
didWrap = YES;
}
else if (x < gx - distx)
{
dx = 2*distx;
didWrap = YES;
}
if (y > gy + disty)
{
dy = -2*disty;
didWrap = YES;
}
else if (y < gy - disty)
{
dy = 2*disty;
didWrap = YES;
}
if (didWrap) [self moveBy:dx :dy];
return didWrap;
}
- (BOOL) bounceAtDistance:(float)distx :(float)disty
{
float dx=0,dy=0;
BOOL didBounce = NO;
if (x > gx + distx)
{
dx = (gx + distx) - x;
if (xv > 0) xv = 0 - xv;
didBounce = YES;
}
else if (x < gx - distx)
{
dx = (gx - distx) - x;
if (xv < 0) xv = 0 - xv;
didBounce = YES;
}
if (y > gy + disty)
{
dy = (gy + disty) - y;
if (yv > 0) yv = 0 - yv;
didBounce = YES;
}
else if (y < gy - disty)
{
dy = (gy - disty) - y;
if (yv < 0) yv = 0 - yv;
didBounce = YES;
}
if (didBounce) [self moveBy:dx :dy];
return didBounce;
}
#define xabs(x) (x >= 0 ? x : 0-x)
extern XXLine *gln2;
// this is a really quick and dirty hack that hopefully is good
// enough to implement a believable bounce off of a nearly
// stationary rectangular object
// (should be more accurate and probably part of a better mechanism
// for bouncing 2 moving arbitrary shape Actors off each other)
- (int) bounceOff:(Actor *)dude
{
XXLine vector[3], line[2], t;
NXPoint pts[3];
float dx, dy;
int i, ret=0;
XXLine *horozLn = &line[0];
line[0].x1 = dude->collisionRect.origin.x;
line[0].x2 = dude->collisionRect.origin.x + dude->collisionRect.size.width;
line[1].y1 = dude->collisionRect.origin.y;
line[1].y2 = dude->collisionRect.origin.y + dude->collisionRect.size.height;
if (yv > 0)
{
line[0].y1 = line[0].y2 = dude->collisionRect.origin.y;
}
else
{
line[0].y1 = line[0].y2 = dude->collisionRect.origin.y +
dude->collisionRect.size.height;
}
if (xv > 0)
{
line[1].x1 = line[1].x2 = dude->collisionRect.origin.x;
}
else
{
line[1].x1 = line[1].x2 = dude->collisionRect.origin.x +
dude->collisionRect.size.width;
}
if (xabs(xv) > xabs(yv)) // then favor horozontal collisions
{
t=line[0]; line[0]=line[1]; line[1]=t;
horozLn = &line[1];
}
dx = xv * 1024.;
dy = yv * 1024.;
pts[0].x = x;
pts[0].y = y;
pts[1].x = x - distToCorner.width;
pts[2].x = x + distToCorner.width;
if ((xv*yv) > 0.0)
{
pts[1].y = y + distToCorner.height;
pts[2].y = y - distToCorner.height;
}
else
{
pts[1].y = y - distToCorner.height;
pts[2].y = y + distToCorner.height;
}
for (i=0; i<3; i++)
{
vector[i].x1 = pts[i].x - dx;
vector[i].x2 = pts[i].x + dx;
vector[i].y1 = pts[i].y - dy;
vector[i].y2 = pts[i].y + dy;
}
if ((linesCollide(vector,3,NO, line, 2, NO)) && (gln2 == horozLn))
{
if (yv > 0) // bounce off bottom
{
[self moveTo:x :horozLn->y1-distToCorner.height];
[self setXvYv:xv :-yv sync:NO];
ret = 1;
}
else // bounce off top
{
[self moveTo:x :horozLn->y1+distToCorner.height];
[self setXvYv:xv :-yv sync:NO];
ret = 2;
}
}
else
{
if (xv > 0) // bounce off left
{
[self moveTo:horozLn->x1-distToCorner.width :y];
[self setXvYv:-xv :yv sync:NO];
ret = 3;
}
else // bounce off top
{
[self moveTo:horozLn->x2+distToCorner.width :y];
[self setXvYv:-xv :yv sync:NO];
ret = 4;
}
}
return ret;
}
// override in subclasses to lazily construct coords for rotated rects
// or rect lists
- constructComplexShape
{
return self;
}
+ findImageNamed:(const char *)name
{
NXImage *ret_image = [NXImage findImageNamed:name];
if (!ret_image)
{
char path[256];
if ([[NXBundle bundleForClass:[self class]]
getPath:path
forResource:name
ofType:"tiff"])
{
ret_image = [[NXImage allocFromZone:[self zone]]
initFromFile:path];
[ret_image setName:name];
}
}
return ret_image;
}
- findImageNamed:(const char *)name
{
return [[self class] findImageNamed:name];
}
+ cacheImage:(const char *)name
{
NXImage *timage;
timage = [self findImageNamed:name];
if ([timage lockFocus]) [timage unlockFocus];
return self;
}
- (int)addToScore:(int)val for:dude gen:(int)age
{
if (age < 2)
return [scoreTaker addToScore:val for:self gen:age+1];
else return 0;
}
- addFlushRects
{
if (!coalesce(&eraseRect, &drawRect))
{
[cacheMgr displayRect:&drawRect];
}
if (eraseRect.size.width > 0)
[cacheMgr displayRect:&eraseRect];
return self;
}
- (BOOL) isGroup
{ return NO;
}
@end
@implementation Object (scoreKeepingMethods)
- (int)setScore:(int)val for:dude
{
return 0;
}
- (int)addToScore:(int)val for:dude gen:(int)age
{
return 0;
}
- (int)score;
{
return 0;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.