
// Handles moving and rendering various moving objects.

#import <gamekit/gamekit.h>
#import <stdio.h>

@implementation GKActor

- init				// initialize the new instance vars
{	// You need to override this to set state and maxFrames at the very least
	[super init];
	NX_X(&boundingBox) = 0;
	NX_Y(&boundingBox) = 0;
	NX_HEIGHT(&boundingBox) = GK_DEFAULT_ACTOR_WIDTH;
	NX_WIDTH(&boundingBox) = GK_DEFAULT_ACTOR_HEIGHT;
	GK_CLEAR_VECTOR(&nextLocation);
	GK_CLEAR_VECTOR(&lastLocation);
	GK_CLEAR_VECTOR(&lastDrawnLocation);
	maxFrames = (int *)malloc(sizeof(int));
	maxFrames[0] = 0; maxSeries = 1; series = 0;
	frame = 0; cycles = 0; state = GK_DEAD_ACTOR;
	renderedImage = nil; drawingLevel = GK_DYNAMIC_ACTOR;
	return self;
}

- (int)drawingLevel { return drawingLevel; }
- setDrawingLevel:(int)aLevel { drawingLevel = aLevel; return self; }
- stage { return stage; }

- setStage:aStage { // ***** probably ought to make sure we're not on any other stage!
	stage = aStage;
	return self;
}

- leaveTheStage
{
	[stage removeActor:self ofType:drawingLevel];
	return self;
}

- setSize:(const NXSize *)aSize
		// set the actor's size (enclosing frame in image)
{
	GK_COPY_SIZES(&(boundingBox.size), aSize);
	return self;
}

- setRenderedImage:anImage	// set the bitmap image used to render
{	// we don't free an old image, if it exists.  This is because others
	// might also be using the image.  What I should do is a reference
	// counting image *****
	renderedImage = anImage;
	return self;
}

- setSeries:(int)anInt		// set the animation series (row) to use in bitmap
{
	if (anInt >= maxSeries) return nil;	// only change to a valid series number
	series = anInt;
	return self;
}

- setMaxFrames:(const int *)anIntArray count:(int)anInt	// array indexed by
		// series number; gives the number of frames in the series
{	// This could possibly be incorporated into an "Array" object, perhaps
	// one which does reference counting. *****
	int i;
	free(maxFrames); maxFrames = (int *)malloc(sizeof(int) * anInt);
	for (i=0; i<anInt; i++) maxFrames[i] = anIntArray[i];
	maxSeries = anInt;
	return self;
}

- move:sender				// Move the Actor one animation frame
{	// you must override this and put something here
	// alter the nextLocation vector to move the actor.
	return self;
}

- (int)collisionType	// called to determine which type of collision
						// detection to use with this actor
{	// override to change type... default is the bounding box
	return GK_RECTANGLE_SHAPE;
}

- (void *)shapeStruct	// if you change the collision type, you must
{	// return a pointer to an appropriate structure defining the shape
	return (&boundingBox);	// Note that if you call this method, do NOT
	// alter the boundingBox returned!  It will cause all sorts of havoc!
}

- collidedWith:anActor	// called when it is detected that we hit something
{	// you should override to do something about the hit, if necessary
	return self;
}

- lastAt:(NXPoint *)aPoint	// called to find out where actor was
{
	GK_COPY_VECTORS(aPoint, &lastLocation);
	return self;
}

- lastDrawnAt:(NXPoint *)aPoint	// called to find out where actor was
{
	GK_COPY_VECTORS(aPoint, &lastDrawnLocation);
	return self;
}

- at:(NXPoint *)aPoint	// called to find out where actor was
{
	GK_COPY_VECTORS(aPoint, &GK_location);
	return self;
}

- getBoundingBox:(NXRect *)box
{
	GK_COPY_RECT(box, &boundingBox);
	return self;
}

- (int)series { return series; } // the series we're currently using
- (int)frame  { return frame;  } // the next frame that will be drawn

- moveOneFrame	// moves the actor along; accessed via renderAt::move:
{
	// flag movement; we won't redraw if no movement...
	if ((GK_VECTOR_X(&GK_location) != GK_VECTOR_X(&nextLocation)) ||
		(GK_VECTOR_Y(&GK_location) != GK_VECTOR_Y(&nextLocation)))
		movedThisFrame = YES;
	GK_COPY_VECTORS(&GK_location, &nextLocation);
	GK_COPY_VECTORS(&lastLocation, &GK_location);
	return self;
}

- eraseInDirtPile:dirtPile
{	// pass our most recent dirty rect to the DirtPile...
	[dirtPile addRegion:GK_VECTOR_X(&lastDrawnLocation)
					   :GK_VECTOR_Y(&lastDrawnLocation)
					   :boundingBox.size.width
					   :boundingBox.size.height];
	return self;
}

- markInDirtPile:dirtPile	// same as above but called at different times
{	// pass our most recent dirty rect to the DirtPile...
	[dirtPile addRegion:GK_VECTOR_X(&lastDrawnLocation)
					   :GK_VECTOR_Y(&lastDrawnLocation)
					   :boundingBox.size.width
					   :boundingBox.size.height];
	return self;
}

- renderAt:(NXPoint *)offset move:(BOOL)moveOk withDirtPile:dirtPile
		// draw actor; lock focus on view that gets the actor before call
{		// this should be overridden by the actor subclass
	NXPoint zero = { 0.0, 0.0 };
	if (state == GK_DEAD_ACTOR) return self;	// we're dead, nothing to do
	if (moveOk) [self moveOneFrame];
	GK_COPY_VECTORS(&lastDrawnLocation, &GK_location); // we need to track
		// this so we can do erase properly
	if (movedThisFrame) {	// handle the drawing if need to redraw.
		if (offset) [self drawActorWithOffset:offset];
		else [self drawActorWithOffset:&zero];
		// mark the drawn area as dirty
		[self markInDirtPile:dirtPile];
		movedThisFrame = NO;
	}
	[self updateDrawingState]; // we update _even_ if no movement;
		// if you object is stationary, but changes shape a lot, then
		// you should set the movedThisFrame flag in this method!
	return self;
}

- drawActorWithOffset:(NXPoint *)offset	// draw the actor
{	// 90% of the time you'll override this completely
	NXRect from = { { frame * NX_WIDTH(&boundingBox),
							series * NX_HEIGHT(&boundingBox) },
					{ NX_WIDTH(&boundingBox), NX_HEIGHT(&boundingBox) } };
	NXPoint pos = { NX_X(&boundingBox) + GK_VECTOR_X(offset),
					NX_Y(&boundingBox) + GK_VECTOR_Y(offset) };
	[renderedImage composite:NX_SOVER fromRect:&from toPoint:&pos];
	return self;
}

- updateDrawingState	// change the internal state machine (ie. advance
{	// frames, etc.  This will also
	cycles++;
	frame++; if (frame >= maxFrames[series]) frame = 0;
	return self;
}

@end
