// BattleView V2.0 8/28/93 modified by cat >>>>>>>> tabsize 4 - windowwidth 95 char <<<<<<<<<<

#import "BattleView.h"

/*	Resize so that the first line of this file does not break.
  
    Copyright 1992, Stefanos Kiakas. All rights reserved.
  
    You may not delete this notice.
  
*/

// 8/26/93 sounds added by cat
// 8/27/93 bonusPoints rewritten, performance improved with register, sizeTo:: added by cat
// 8/28/93 Scoring and BonusCity improved, 2 based play
// 8/28/93 PSWs rewritten, hundereds of PSflushgraphics,and focusLocks removed 

#define CITYBONUS      100	/* city saved bonus */
#define MSLBONUS        10	/* missiles saved bonus */
#define MSLDSTRCT      100	/* attack missle intercepted ( destroyed ) bonus */
#define BOMBERSCORE    300	/* smartbomb */
#define SMARTSCORE    1000	/* smartbomb */
#define BONUSMARK    10000	/* give bonus city every so many points */

#define RANDTARGET ( random() % TARGETS)	/* select a random target to attack */
#define RANDLAUNCH ( ( random() % 440 ) + 20 )	/* x pos. of attack missles */
#define PLACECITY(x) ( random() % x )	/* selct position to place city */

#define RAND(x) ( ( random() % (1+x) ) + x/2 )	/* random from x/2 to 3/2 x */

#define ZERO			0

#define MAXRADIUS		15.0	/* maximum radius of explosions */
#define TERRAINCOLOUR	0.20

#define DELTA_TIME		100
#define TIMEINTERVAL	DELTA_TIME/1000.0
#define RADINC			1.0	/* radius increment of explosion circles */
#define MAXDEFLAUNCH	15	/* maximum number of defensive missiles */
#define MAXATCKMSSLS	15	/* maximum number of attack missiles */
#define BOMBERSTART		5	/* Level Bombers start on */
#define BOMBEREVERY		3	/* Add another bomb for bombers every */

#define LAUNCH_X         75.0	/* Coords for the defensive launch */
#define LAUNCH_Y         22.0
#define LAUNCH_X2       425.0	/* Coords for the defensive launch */
#define LAUNCH_Y2        22.0

#define DISP             5.0	/* Defensive missile speed */
#define DISPDELTA        0.2	/* Defensive missile speed increment per level */

#define CDX             18.0	/* center of target from display coordinates */

#define OLAUNCH_Y     390.0;	/* Y launch position of attack missles */

#define H_CURSOR    330
#define W_CURSOR    460
#define Y_CURSOR     50
#define X_CURSOR     20

#define H_TRG  Y_CURSOR + H_CURSOR
#define W_TRG  X_CURSOR + W_CURSOR
#define Y_TRG   Y_CURSOR
#define X_TRG   X_CURSOR

#define MAX_dFactor 3.0		/* Max speed for incoming missiles */
#define STARTDIFF   0.5		/* start difficulty level */
#define FACTORINC   0.12		/* increment in difficulty level */

#define SCOREXPOS  150
#define SCOREYPOS  250

#import "MissileCommand.h"	// The new PSWraps ! by cat

#import <mach/mach.h>
#include <sys/param.h>
#import <libc.h>
#import <math.h>
#import <appkit/appkit.h>
#import <soundkit/Sound.h>
#import <soundkit/soundkit.h>
#import "SoundEffect.h"

unsigned
currentTimeInMs()		/* return time in 1/1000th of a second */
{
    struct timeval curTime;

    gettimeofday(&curTime, NULL);
    return ((unsigned)curTime.tv_sec) * 1000 + curTime.tv_usec / 1000;
}

BOOL
testDefensiveBombs(EXP_PNTR DefExplosions, NXPoint testpoint)
{				/* Test to see if incoming missiles are hit */
    while (DefExplosions != NULL)
    {				/* If the distance to the explosion <=
				 * Radius... */
    /* Do 2 quick tests before we start doing sqrt and ^2 */
	if ((fabs(testpoint.x - DefExplosions->currentPoint.x) <= DefExplosions->rad) &&
	    (fabs(testpoint.y - DefExplosions->currentPoint.y) <= DefExplosions->rad) &&
	    (sqrt(pow(testpoint.x - DefExplosions->currentPoint.x, 2) + pow(testpoint.y - DefExplosions->currentPoint.y, 2)) <= DefExplosions->rad))
	{
	    return TRUE;
	}
	DefExplosions = DefExplosions->next;
    }
    return FALSE;
}

BOOL
testDefensiveBombsvSmart(EXP_PNTR DefExplosions, NXPoint testpoint, NXPoint *heatpoint, float *heatdist)
{				/* Is it dead, or near an explosion */
    float    thisdist;

    *heatdist = 0;
    while (DefExplosions != NULL)
    {
	if ((thisdist = sqrt(pow(testpoint.x - DefExplosions->currentPoint.x, 2) + pow(testpoint.y - DefExplosions->currentPoint.y, 2))) <= DefExplosions->rad)
	{
	    return TRUE;
	}
	if ((thisdist <= (MAXRADIUS + 2)) && ((*heatdist == 0.0) || (thisdist < *heatdist)))
	{			/* If it's close, and there are no previos or
				 * closer */
	    *heatdist = thisdist;
	    *heatpoint = DefExplosions->currentPoint;
	}
	DefExplosions = DefExplosions->next;
    }
    return FALSE;
}


@implementation BattleView

- initFrame:(const NXRect *)frm
{
    NXPoint  mySpot;

    [super initFrame:frm];

    [self allocateGState];

    gameRunning = FALSE;
    gamePaused  = FALSE;

	PSWinit();

    redAlertSound = 	[[SoundEffect alloc] initFromSection:"RedAlert.snd"];
	emptyBaseSound = 	[[SoundEffect alloc] initFromSection:"EmptyBase.snd"];
	missileAlertSound = [[SoundEffect alloc] initFromSection:"MissileAlert.snd"];
    countCitySound =	[[SoundEffect alloc] initFromSection:"City.snd"];
    countRocketSound = 	[[SoundEffect alloc] initFromSection:"Rocket.snd"];
	bonusCitySound =  	[[SoundEffect alloc] initFromSection:"BonusCity.snd"];

    exploSound = 		[[SoundEffect alloc] initFromSection:"Explo.snd" 	 withLimit:8];
    surfExploSound =	[[SoundEffect alloc] initFromSection:"SurfExplo.snd" withLimit:4];
    launchSound = 		[[SoundEffect alloc] initFromSection:"Launch.snd"	 withLimit:4];
    smartBombSound = 	[[SoundEffect alloc] initFromSection:"SmartBomb.snd" withLimit:2];

    cityBitmap 		= [NXImage findImageNamed:"city3.tiff"];
    launcherBitmap 	= [NXImage findImageNamed:"mBase.tiff"];
    bomberBitmap 	= [NXImage findImageNamed:"Bomber.tiff"];
    smartbombBitmap = [NXImage findImageNamed:"SmartBomb.tiff"];

    playSound = TRUE;		/* Listen to the neato sound */
    gameLevel = 1;
    bomber.hotspots[0].x = 44.0; /* Set the Bomber HotSpots (are tested for hits) */
    bomber.hotspots[0].y = 2.0;
    bomber.hotspots[1].x = 26.0;
    bomber.hotspots[1].y = 0.0;
    bomber.hotspots[2].x = 12.0;
    bomber.hotspots[2].y = 0.0;
    bomber.hotspots[3].x = 12.0;
    bomber.hotspots[3].y = 10.0;
    cursorImage = [NXCursor newFromImage:[NXImage newFromSection:"targetD3.tiff"]];
    cBounds.origin.x = X_CURSOR;
    cBounds.origin.y = Y_CURSOR;
    cBounds.size.width = W_CURSOR;
    cBounds.size.height = H_CURSOR;

    mySpot.x = 8;		/* Set the cursor hot spot */
    mySpot.y = 7;
    srandom(currentTimeInMs());
    [cursorImage setHotSpot:&mySpot];

    return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height	// this is too easy not to implement (cat)
{
	static NXCoord sx=1,sy=1;

	[super sizeTo:width :height];

	[self scale:sx :sy];				// scale back to 1:1 (scale works relatively)
	[self scale:width/500 :height/400];

	sx=500/width;	// this is for next time to scale back to the original value
	sy=400/height;

	return self;
}

- switchSound:sender
{	
    playSound = !playSound;
	[SoundEffect setSoundEnabled:playSound];
    return self;
}

- pauseGame:sender
{	/* Pause or start the game */
    void stepFun();

    if(gameRunning && !gamePaused)
    {	gameRunning = FALSE;
		gamePaused = TRUE;
		DPSRemoveTimedEntry(gameTimer);
		[[toButtonMatrix cellAt:1 :0] setEnabled:NO];	// start button
		[toButtonMatrix display];
		return self;
    }
	
	if(!gameRunning && gamePaused)
	{	gameRunning = TRUE;
		gamePaused = FALSE;
		gameTimer = DPSAddTimedEntry(TIMEINTERVAL, &stepFun, self, NX_BASETHRESHOLD);
		[[toButtonMatrix cellAt:1 :0] setEnabled:YES];	// start button
		[toButtonMatrix display];
		return self;
	}
	
	return self;
}

- resetCursorRects
{
    [self addCursorRect:&cBounds cursor:cursorImage];
    return self;
}

- newGame:sender
{				/* Initialize a new game, or end existing one */
	char levelString[15];
    int      i;			/* Counter */
    void     stepFun();		/* for the DPStimedentry */
    MSLE_NODE_PNTR currentPointdm, fpdm;	/* *KJW* didn't name these
						 * variables */
    MA_NODE_PNTR currentPointam, fpam;
    EXP_PNTR currentPointe, fpe;
    SMART_NODE_PNTR smarthead, smart2free;
	
	if(gamePaused) return self;
	
    if(!gameRunning)
    {	gameLevel = 1;
		
		[[toButtonMatrix cellAt:0 :0] setEnabled:YES];	// pause button
		[toButtonMatrix display];
	
		sprintf(levelString, "Level %02d", gameLevel);
		[levelText setStringValue:levelString];
		
		dFactor = STARTDIFF;	/* Set the difficulty factor */
		phaseOne = TRUE;
		defensiveMissiles.launched = 0;	/* Initialize the various lists */
		defensiveMissiles.next = NULL;
	
		offensiveMissiles.launched = 0;
		offensiveMissiles.next = NULL;
	
		defensiveExplosions.exploding = 0;
		defensiveExplosions.next = NULL;
	
		smartBombs.launched = 0;
		smartBombs.next = NULL;
	
		bomber.launched = 0;
	
		for (i = 0; i < CITIES; i++)	/* Initialize the cities */
			cityStatus[i] = TRUE;
	
		/* reset number of missiles  */
		aTimeBefore = currentTimeInMs();
	
		/* select attack positions */
		[self selectTargets];
	
		missiles1 = 20;
		missiles2 = 20;
		score = 0;
		lastBonusAt = 0;
		displayBase1 = TRUE;
		displayBase2 = TRUE;
	
		[outMissiles setIntValue:missiles1];
		[toRightMissiles setIntValue:missiles2];
		[window makeKeyAndOrderFront:self];
	
		gameRunning = TRUE;
		gameTimer = DPSAddTimedEntry(TIMEINTERVAL, &stepFun, self, NX_BASETHRESHOLD);
	
		[self display];
		return self;
    }
	
	if (gameRunning)
	{	[[toButtonMatrix cellAt:0 :0] setEnabled:NO];	// pause button
		[toButtonMatrix setState:NO at:1 :0];			// start button
		[toButtonMatrix display];

		[highScoreTable putInHighScores: score];
		gameRunning = FALSE;
		DPSRemoveTimedEntry(gameTimer);
		/* remove entries in linked lists */
		currentPointdm = defensiveMissiles.next;
		while (currentPointdm != NULL)
		{	fpdm = currentPointdm;
			currentPointdm = currentPointdm->next;
			free(fpdm);
		}
	
		/* remove attack missiles from list, and free memory */
		currentPointam = offensiveMissiles.next;
		while (currentPointdm != NULL)
		{	fpam = currentPointam;
			currentPointam = currentPointam->next;
			free(fpam);
		}
	
		/* remove air explosions from list and free memory */
		currentPointe = defensiveExplosions.next;
		while (currentPointe != NULL)
		{	fpe = currentPointe;
			currentPointe = currentPointe->next;
			free(fpe);
		}
	
		/* remove surface explosions from list and free memory */
		currentPointe = surfaceExplosions.next;
		while (currentPointe != NULL)
		{	fpe = currentPointe;
			currentPointe = currentPointe->next;
			free(fpe);
		}
	
		/* remove smartbombs and free memory -KJW */
		smarthead = smartBombs.next;
		smartBombs.launched = 0;
		while (smarthead != NULL)
		{	smart2free = smarthead;
			smarthead = smarthead->next;
			free(smart2free);
		}
	}
	
    return self;
}


- (void)step
{				/* Each frame of movement is done here... */
    NXEvent  dummyEvent;
    unsigned int timeNow, dTime;/* Make sure we've waited the right time */

	if(!gameRunning) return;
	
    do
    {
		[self lockFocus];
		
		timeNow = currentTimeInMs();	/* What time is it now? */
		dTime = timeNow - aTimeBefore;	/* How long's it been? */
		if (dTime < (unsigned)DELTA_TIME)	/* If it's too soon, wait */
		{	/* This is pathetic.  All I want to do is a damn usleep -KJW */
	    	thread_switch(THREAD_NULL, SWITCH_OPTION_WAIT, (int)(DELTA_TIME - dTime));
		}

		aTimeBefore = timeNow;
		/* Do what needs doing */

		if (defensiveMissiles.launched != 0)	[self missilePath];
		if (defensiveExplosions.exploding > 0)	[self expDraw];
		if (offensiveMissiles.launched > 0)		[self attackMissilesPath];
		if (surfaceExplosions.exploding > 0)	[self surfaceExplosionsDraw];
		if (bomber.launched > 0)				[self doBomber];
		if (smartBombs.launched > 0)			[self doSmartBomb];
			
		if(defensiveMissiles.launched == 0 && defensiveExplosions.exploding == 0)
		if(offensiveMissiles.launched == 0 && surfaceExplosions.exploding == 0)
		if(smartBombs.launched == 0 	   && bomber.launched == 0)
		{	if(!phaseOne)
			[self bonusPoints];
			[self nextLevel];
		}
		
		PSflushgraphics();
		NXPing();
		[self unlockFocus];
	
    } while([NXApp peekNextEvent:NX_ALLEVENTS into:&dummyEvent] == NULL);
}

- drawSelf:(const NXRect *)r :(int)c
{
    NXPoint  Pt;
    register int i;

    PSsetgray(1.0);
    NXRectFill(r);
    PSsetgray(NX_BLACK);
    NXFrameRect(r);

    PSsetgray(TERRAINCOLOUR);

    PSmoveto(0.0, 0.0);
    PSrlineto(0.0, 5.0);
    PSrlineto(50.0, 0.0);

    PSrlineto(5.0,10.0);	// B1
    PSrlineto(40.0, 0.0);
    PSrlineto(5.0,-10.0);

    PSrlineto(80.0, 0.0);
    PSrlineto(5.0, 5.0);
    PSrlineto(40.0, 0.0);
	
    PSrlineto(5.0, 3.0);	// centerhill
    PSrlineto(40.0, 0.0);
    PSrlineto(5.0, -3.0);
	
    PSrlineto(40.0, 0.0);
    PSrlineto(5.0, -5.0);
    PSrlineto(80.0, 0.0);
	
    PSrlineto(5.0,10.0);	// B2
    PSrlineto(40.0, 0.0);
    PSrlineto(5.0,-10.0);
    
	PSrlineto(50.0, 0.0);
    PSrlineto(0.0, -5.0);
    PSclosepath();
    PSfill();

	[self drawLaunchPad];

    for (i = 0; i < CITIES; i++)
		if(cityStatus[i])
		    [self drawCityBM:cityBasearray[i].coord];

    Pt.x = 200.0;
    Pt.y = 200.0;

    return self;
}

- (void)drawCityBM:(NXPoint)dcurrentPoint
{	/* Draw a city at dcurrentPoint */
	[[cityBitmap lastRepresentation] drawAt:&dcurrentPoint];
}

- (void)drawLaunchPad
{	/* Draw the Launch pad */
	if(displayBase1) [[launcherBitmap lastRepresentation] drawAt:&cityBasearray[10].coord];
	if(displayBase2) [[launcherBitmap lastRepresentation] drawAt:&cityBasearray[9].coord];
}

- (void)drawMissile
{	/* Draw the missle figure for the score part */
    id       missileBitmap = [NXImage findImageNamed:"missileIcon.tiff"];
    NXPoint  Pt;

    Pt.x = 160.0;
    Pt.y = 190.0;

	[[missileBitmap lastRepresentation] drawAt:&Pt];
}

- rightMouseDown:(NXEvent *)theEvent
{
	rflag = YES;
	[self mouseDown:theEvent];
	rflag = NO;

	if(missiles2)
		[launchSound play];
	else
		[emptyBaseSound play];

	if(missiles2 < 3 && missiles2 != 0)
		[missileAlertSound play];
	
	return self;
}

- mouseDown:(NXEvent *)theEvent
{	/* Deal with the IO... */
    NXPoint  sPt;
    int      pntInRect();
    register MSLE_NODE_PNTR mLaunch, p;
	float    h1,h2;

	if(!gameRunning) return self;

    sPt = theEvent->location;
    [self convertPoint:&sPt fromView:nil];

    if(defensiveMissiles.launched < MAXDEFLAUNCH && pntInRect(&sPt))	// like &&
    if((rflag && missiles2 > 0) || (!rflag && missiles1 > 0))
	{	
		if(rflag)
			[toRightMissiles setIntValue:--missiles2];
		else
			[outMissiles setIntValue:--missiles1];
			
		defensiveMissiles.launched++;
		
		[self lockFocus];			// we really need this one grrrr!
		PSWmarkloc(sPt.x,sPt.y);
		[self unlockFocus];

		// cat was here ...

		mLaunch = malloc(sizeof(MSLE_NODE));	
		mLaunch->destPos  = sPt;		
		if(rflag)
		{	mLaunch->currentPos.x = LAUNCH_X2;
			mLaunch->currentPos.y = LAUNCH_Y2;
		}
		else
		{	mLaunch->currentPos.x = LAUNCH_X;
			mLaunch->currentPos.y = LAUNCH_Y;
		}
		mLaunch->startPos = mLaunch->currentPos;
		mLaunch->next = NULL;
		
		h1=sqrt((sPt.y-LAUNCH_Y)*(sPt.y-mLaunch->currentPos.y) +
				(sPt.x-mLaunch->currentPos.x)*(sPt.x-mLaunch->currentPos.x));
		h2=DISP+DISPDELTA*gameLevel;
		mLaunch->delta_x = h2 * (sPt.x - mLaunch->currentPos.x) / h1;
		mLaunch->delta_y = h2 * (sPt.y - mLaunch->currentPos.y) / h1;
	
		if (defensiveMissiles.next == NULL)
			defensiveMissiles.next = mLaunch;
		else
		{
			p = defensiveMissiles.next;
			while (p->next != NULL)
			p = p->next;
			p->next = mLaunch;
	
		}
    }

	if(!rflag)
	{	if(missiles1)
			[launchSound play];
		else
			[emptyBaseSound play];
		
		if(missiles1 < 3 && missiles1 != 0)
			[missileAlertSound play];
	}
	
    return self;
}

- (void)expDraw
{				/* Draw the explosions (and handle the logic) */
    register EXP_PNTR currentPoint, pp;

    pp = currentPoint = defensiveExplosions.next;
    while (currentPoint != NULL)
	{
		if (currentPoint->rad > currentPoint->maxrad) /* If we've hit radius start to erase */
		{	currentPoint->maxReached = YES;
			currentPoint->rad += (currentPoint->maxReached) ? -RADINC : RADINC;
			pp = currentPoint;
			currentPoint = currentPoint->next;
		}
		else
		if ((currentPoint->rad <= 0.0) && currentPoint->maxReached)
		{			/* If we've erased the whole thing */
			if (defensiveExplosions.next == currentPoint)
			{	pp = defensiveExplosions.next = currentPoint->next;
				free(currentPoint);
				currentPoint = pp;
			}
			else
			{	pp->next = currentPoint->next;
				free(currentPoint);
				currentPoint = pp->next;
			}
			defensiveExplosions.exploding--;
		}
		else
		{	/* Just draw the damn thing and increase the radius */
			PSWexplode((currentPoint->maxReached) ? NX_WHITE : NX_BLACK,
						currentPoint->currentPoint.x,currentPoint->currentPoint.y,
						currentPoint->rad);

			currentPoint->rad += ((currentPoint->maxReached) ? -RADINC : RADINC);
			pp = currentPoint;
			currentPoint = currentPoint->next;
		}
	}
}


- (void)doBomber
{				/* Handle the bomber */
 /* I wrote this (so proud ;-) -KJW */
    NXPoint  Pt;		/* Coundn't tell by the var names, eh? */
    register int i;

    if(bomber.currentPos.x > 0)	/* If we've already plotted, erase */
	{	PSsetgray(NX_WHITE);
		PSrectfill(bomber.currentPos.x,bomber.currentPos.y,44,9);
		// cat was here tooo ... but this is nothing to be proud of :=)
	}

    for (i = 0; i < 4; i++)
	{	/* Test all the hotspots */
		Pt = bomber.hotspots[i];
		Pt.x += bomber.currentPos.x;
		Pt.y += bomber.currentPos.y;
		if (testDefensiveBombs(defensiveExplosions.next, Pt))
		{	/* If hit... */
			score += BOMBERSCORE;
			[outScore setIntValue:score];
	
			[self airExplosionAt:&Pt withRadius:35.0];
						
			bomber.launched--;
			bomber.currentPos.x = 0.0;
			return;
		}
	}

    bomber.currentPos.x += bomber.delta_x;	/* Move the bomber */
	[[bomberBitmap lastRepresentation] drawAt:&bomber.currentPos];

    if (bomber.currentPos.x > 480)
    {				/* Did we make it to the right side? */
		[[bomberBitmap lastRepresentation] drawAt:&bomber.currentPos];
		bomber.launched--;
		bomber.currentPos.x = 0;
		bomber.bombPos.y = 100.0 + RAND(100);
		bomber.currentPos.y = bomber.bombPos.y;
		if (bomber.launched)
		{	/* Want to launch another? */
			bomber.bombs = (int)((gameLevel - BOMBERSTART) / BOMBEREVERY + 1) /
						   (int)((gameLevel + BOMBERSTART) / 10) + 1;
			bomber.bombPos.x = 480 / (bomber.bombs + 1);
		}

		return;
    }
	
    if (bomber.currentPos.x > bomber.bombPos.x && (bomber.bombs > 0))
    {	/* Ready to toss another bomb? */
		NXPoint  bombPoint = bomber.currentPos;
	
		bombPoint.x += 5;	/* Put the bomb under the bomber (not at the
						* back) */
		bomber.bombs--;
		bomber.bombPos.x += (480 - bomber.currentPos.x) / (bomber.bombs + 1);
	 offensiveMissiles.next=[self addToIncoming:bomber.currentPos:1 :offensiveMissiles.next];
    }
	
}

- (void)doSmartBomb
{
    register SMART_NODE_PNTR CurrentSmartBomb = smartBombs.next;
	register SMART_NODE_PNTR PreviousSmartBomb = CurrentSmartBomb;
    NXPoint  hotPoint, plotPoint;	/* hotPoint will be a close defensive
					 * bomb, plotPoint is where the
					 * smartbomb appears */
    float    hotDist,		/* distance to a close defensive bomb */
             delta_x, delta_y, dx, dy;	/* for calcing distance and new
					 * movement (smarts) */


 /*
  * set up pointers to point at head of attack missile list 
  */

    while (CurrentSmartBomb != NULL)
    {	if(testDefensiveBombsvSmart(defensiveExplosions.next, CurrentSmartBomb->currentPos, &hotPoint, &hotDist))
		{			/* Did we get hit?  (and what was close if not (hot)) */
			smartBombs.launched--;	/* missile has been destroyed */
			score += SMARTSCORE;
			[outScore setIntValue:score];
	
			[self airExplosionAt:&(CurrentSmartBomb->currentPos)];
	
		/* remove missile from list */
			if (CurrentSmartBomb == smartBombs.next)
			{			/* Is this the head of the list */
				smartBombs.next = CurrentSmartBomb->next;
				free(CurrentSmartBomb);
				CurrentSmartBomb = PreviousSmartBomb = smartBombs.next;
			}
			else
			{	PreviousSmartBomb->next = CurrentSmartBomb->next;
				free(CurrentSmartBomb);
				CurrentSmartBomb = PreviousSmartBomb;
			}
		}
		else
		{	/* Plot (erase) the bomb where the numbers say it is (not lower left of tiff */
			plotPoint = CurrentSmartBomb->currentPos;
			plotPoint.x -= 3;
			plotPoint.y -= 2;
			//[smartbombBitmap composite:NX_XOR toPoint:&plotPoint];
			PSsetgray(NX_WHITE);
			PSrectfill(plotPoint.x,plotPoint.y,5,5);

			/* calculate next point in trajectory */
	
			/* Find out how far it is to the target */
			dx = CurrentSmartBomb->destPos.x - CurrentSmartBomb->currentPos.x;
			dy = CurrentSmartBomb->destPos.y - CurrentSmartBomb->currentPos.y;
	
			/* And move toward it */
			delta_y =.9 * dy * INCOMINGBASEV / sqrt(dy * dy + dx * dx);
			delta_x =.9 * dx * INCOMINGBASEV / sqrt(dy * dy + dx * dx);
	
			if (hotDist > 0)
			{			/* Any Close calls */
				/* Find out how far it is to the target (defensive bomb) */
				dx = hotPoint.x - CurrentSmartBomb->currentPos.x;
				dy = hotPoint.y - CurrentSmartBomb->currentPos.y;
				/* And move *AWAY FROM* it */
				delta_y -=.9 *.9 * dy * INCOMINGBASEV / sqrt(dy * dy + dx * dx);
				delta_x -=.9 *.9 * dx * INCOMINGBASEV / sqrt(dy * dy + dx * dx);
			}
			/* OK, move the bomb */
			CurrentSmartBomb->currentPos.x += dFactor * delta_x;
			CurrentSmartBomb->currentPos.y += dFactor * delta_y;
	
			/* Plot the bomb where the numbers say it is (not lower left of tiff */
			plotPoint = CurrentSmartBomb->currentPos;
			plotPoint.x -= 3;
			plotPoint.y -= 2;
			//[smartbombBitmap composite:NX_SOVER toPoint:&plotPoint];
			[[smartbombBitmap lastRepresentation] drawAt:&plotPoint];

			if (CurrentSmartBomb->currentPos.y < CurrentSmartBomb->destPos.y)
			{			/* Did we reach target */
				smartBombs.launched--;
				if (CurrentSmartBomb->targetLoc == (TARGETS - 1))
				{	missiles1 = 0;
					[outMissiles setIntValue:missiles1];
					displayBase1 = FALSE;
				}
				else
					if (CurrentSmartBomb->targetLoc == (TARGETS - 2))
					{	missiles2 = 0;
						[toRightMissiles setIntValue:missiles2];
						displayBase2 = FALSE;
					}
					else
						cityStatus[CurrentSmartBomb->targetLoc] = FALSE;
				
				[self surfaceExplosionAt:&(CurrentSmartBomb->currentPos)];
						
				if (CurrentSmartBomb == smartBombs.next)
				{		/* are we the head bomb */
					smartBombs.next = CurrentSmartBomb->next;
					free(CurrentSmartBomb);
					CurrentSmartBomb = smartBombs.next;
				}
				else
				{
					PreviousSmartBomb->next = CurrentSmartBomb->next;
					free(CurrentSmartBomb);
					CurrentSmartBomb = PreviousSmartBomb;
				}
			}
		}
		
		if (CurrentSmartBomb != NULL)
		{	PreviousSmartBomb = CurrentSmartBomb;
			CurrentSmartBomb = CurrentSmartBomb->next;
		}
    }

	if(smartBombs.launched > [smartBombSound soundsPlaying])
			[smartBombSound play];
}

- (void)surfaceExplosionsDraw
{				/* Not my work -KJW -- Plot and deal with surface explosions */
    register EXP_PNTR currentPoint, pp;

    pp = currentPoint = surfaceExplosions.next;

    while (currentPoint != NULL)
	{
		if (currentPoint->rad > MAXRADIUS)
		{
			currentPoint->maxReached = YES;
			currentPoint->rad += (currentPoint->maxReached) ? -RADINC : RADINC;
			pp = currentPoint;
			currentPoint = currentPoint->next;
		}
		else
		if ((currentPoint->rad <= 0.0) && currentPoint->maxReached)
		{
			if (surfaceExplosions.next == currentPoint)
			{
				pp = surfaceExplosions.next = currentPoint->next;
				PSnewpath();
				PSsetgray(NX_WHITE);
				PSmoveto(currentPoint->currentPoint.x, currentPoint->currentPoint.y);
				PSrlineto(-10, 0);
				PSrlineto(0, 20);
				PSrlineto(30, 0);
				PSrlineto(0, -20);
				PSrlineto(-10, 0);
				PSfill();
				free(currentPoint);
				currentPoint = pp;
			}
			else
			{
				pp->next = currentPoint->next;
		
				PSnewpath();
				PSsetgray(NX_WHITE);
				PSmoveto(currentPoint->currentPoint.x, currentPoint->currentPoint.y);
				PSrlineto(-10, 0);
				PSrlineto(0, 20);
				PSrlineto(30, 0);
				PSrlineto(0, -20);
				PSrlineto(-10, 0);
				PSfill();
		
				free(currentPoint);
				currentPoint = pp->next;
			}
			surfaceExplosions.exploding--;
		}
		else
		{	PSWsurfexpl((currentPoint->maxReached) ? NX_WHITE : NX_BLACK,
						 currentPoint->currentPoint.x, currentPoint->currentPoint.y,
						 currentPoint->rad);
			
			currentPoint->rad += ((currentPoint->maxReached) ? -RADINC : RADINC);
			pp = currentPoint;
			currentPoint = currentPoint->next;
	
		}
	}
}


- (void)missilePath
{				/* Not my work -KJW -- Plot and deal with defensive missiles */
    NXPoint  nPos;		/* new missile position */
    register MSLE_NODE_PNTR currentPoint,dp;
	MSLE_NODE_PNTR pp;
    pp = currentPoint = defensiveMissiles.next;

    while (currentPoint != NULL)
    {
	if (currentPoint->destPos.y <= currentPoint->currentPos.y)
	{	/* missile has reached it destination  */
		PSWerasetrajectory(currentPoint->startPos.x,currentPoint->startPos.y,
						   currentPoint->destPos.x ,currentPoint->destPos.y);

	    if (currentPoint == defensiveMissiles.next)
	    {	defensiveMissiles.next = currentPoint->next;
			dp = currentPoint;
			currentPoint = defensiveMissiles.next;
	    }
	    else
	    {	pp->next = currentPoint->next;
			dp = currentPoint;
			currentPoint = pp->next;
	    }

	    [self airExplosionAt:&(dp->destPos)];
	    free(dp);
	    defensiveMissiles.launched--;
	}
	else
	{	/* calculate and plot next position */
	    nPos.x = currentPoint->currentPos.x + currentPoint->delta_x;
	    if (currentPoint->delta_x < 0)
	    {	/* make sure that point is not past detonation point */
			if (nPos.x < currentPoint->destPos.x)
		    nPos.x = currentPoint->destPos.x;
	    }
	    else
	    {	if (nPos.x > currentPoint->destPos.x)
		    nPos.x = currentPoint->destPos.x;
	    }
		
	    nPos.y = currentPoint->currentPos.y + currentPoint->delta_y;

	    if (currentPoint->destPos.y > currentPoint->currentPos.y)
	    {	PSWplottrajectory(currentPoint->currentPos.x,
							  currentPoint->currentPos.y,nPos.x,nPos.y);
			currentPoint->currentPos = nPos;
	    }
	    pp = currentPoint;
	    currentPoint = currentPoint->next;
	}
    }
}

- nextLevel
{				/* initialize the next level */
    int      cities, i;

    cities=0;
    for(i=0;i<CITIES;i++)
	if(cityStatus[i])
	    cities++;

    if(cities == 0)
    {	missiles1 = 0;
		missiles2 = 0;
		
		[self newGame:self];	// Abort
		return self;
	}
    else
    {	if (phaseOne)
	    	phaseOne = FALSE;
		else
		{	phaseOne = TRUE;
		    if (dFactor < MAX_dFactor)
			dFactor += FACTORINC;
		    
			if(cities)
			{	if(gameLevel % 10 == 0)
				{	missiles1 = 99;
					missiles2 = 99;
		    	}
				else
				{	missiles1 = 20 + gameLevel / 5;
					missiles2 = 20 + gameLevel / 5;
		    	}
			}
			
			displayBase1 = TRUE;
			displayBase2 = TRUE;
		}
		/* reset time  */
		aTimeBefore = currentTimeInMs();
	
	
		[outMissiles setIntValue:missiles1];
		[toRightMissiles setIntValue:missiles2];
		[outScore setIntValue:score];
	
		[self display];
	
		/* calculate attack positions */
		[self selectTargets];
	
		{			/* Print level number */
			char     levelString[15];
	
			sprintf(levelString, "Level %02d", gameLevel);
			[levelText setStringValue:levelString];
		}
		/* initialize bomber values for level */
		bomber.currentPos.x = 0.0;
		bomber.bombPos.y = 100.0 + RAND(100);
		bomber.currentPos.y = bomber.bombPos.y;
		bomber.delta_x = 3.0;
		bomber.launched = (int)((gameLevel + BOMBERSTART) / 10);
		if (bomber.launched)
		{	bomber.bombs = (int)((gameLevel - BOMBERSTART) / BOMBEREVERY + 1) /
						   (int)((gameLevel + BOMBERSTART) / 10) + 1;
			bomber.bombPos.x = 480 / (bomber.bombs + 1);
		}
    }
    return self;
} /* End 'nextLevel' */

- bonusPoints				// cat was here ... and anywhere else
{
    NXPoint  cityPt;
    int      cities,i,bmulti = gameLevel/2+1;
    int      vacantPos[CITIES], j;
	char	 str[100];
	static NXRect fr={200,240,200,30};
	static id	cbon=0,rbon,bonc,font;
	
	if(cbon==0)
	{	cbon = [[TextField alloc] initFrame:&fr];
		fr.origin.y = 190;
		rbon = [[TextField alloc] initFrame:&fr];
		fr.origin.x = 190;
		fr.origin.y = 140;
		bonc = [[TextField alloc] initFrame:&fr];
		[cbon setEditable:NO];
		[rbon setEditable:NO];
		[bonc setEditable:NO];
		
		font = [Font newFont:"Ohlfs" size:16.0];
		[cbon setFont:font];
		[rbon setFont:font];
		[bonc setFont:font];
	}
	
	[self addSubview:cbon];
	[self addSubview:rbon];

    cityPt.x = SCOREXPOS;
    cityPt.y = SCOREYPOS;
    cities = 0;
    for (i = 0; i < CITIES; i++)
	if (cityStatus[i])
	    cities++;

    [self drawCityBM:cityPt];

    for (i = 0; i <= cities; i++)
    {	sprintf(str,"%i x %i = %i",i,CITYBONUS*bmulti,i*CITYBONUS*bmulti);
	    [outScore setIntValue:score+i*CITYBONUS*bmulti];
		[cbon setStringValue:str];
		[countCitySound playEvent];
		usleep(300000);
    }

    score += cities * CITYBONUS * bmulti;

    [self drawMissile];

    for (i = 0; i <= missiles1; i++)
    {	sprintf(str,"%i x %i = %i",i,MSLBONUS*bmulti,i*MSLBONUS*bmulti);
	    [outScore setIntValue:score+i*MSLBONUS*bmulti];
		[outMissiles setIntValue:missiles1-i];
		[rbon setStringValue:str];
		[countRocketSound playEvent];
		usleep(170000);
    }

    for (i = 0; i <= missiles2; i++)
    {	sprintf(str,"%i x %i = %i",i+missiles1,MSLBONUS*bmulti,(i+missiles1)*MSLBONUS*bmulti);
	    [outScore setIntValue:score+missiles1*MSLBONUS*bmulti+i*MSLBONUS*bmulti];
		[toRightMissiles setIntValue:missiles2-i];
		[rbon setStringValue:str];
		[countRocketSound playEvent];
		usleep(170000);
    }

    score += (missiles1 + missiles2) * MSLBONUS * bmulti;

    while(score-lastBonusAt > BONUSMARK)
    {	j=0;
		for(i=0;i<CITIES;i++)
		{	if(cityStatus[i]==FALSE)
				vacantPos[j++] = i;
		}
		
		if(j)
		{	cityStatus[vacantPos[PLACECITY(j)]]=TRUE;
			lastBonusAt += BONUSMARK;
			[self addSubview:bonc];
			[bonc setStringValue:"BONUS CITY !"];
			PSflushgraphics();
			NXPing();
			[bonusCitySound play];
		}
		else
			break;	// no empty spot !
    }
	
    gameLevel++;
	
    sleep(4);

	[cbon removeFromSuperview];
	[rbon removeFromSuperview];
	[bonc removeFromSuperview];
	
    return self;
}


- selectTargets
{				/* Get targets for various things */
    float    dx, dy;
    int      i;
    MA_NODE_PNTR head, currentPoint, pp;
    SMART_NODE_PNTR thisSmart, lastSmart;

    pp = head = currentPoint = malloc(sizeof(MA_NODE));

    offensiveMissiles.splitters = MIN(MAXATCKMSSLS, (int)gameLevel / 2);
    while (++offensiveMissiles.launched <= MAXATCKMSSLS + random()%7-6)
    {				/* attack missles init */
	currentPoint->startPos.x = RANDLAUNCH;
	currentPoint->startPos.y = OLAUNCH_Y;
	currentPoint->targetLoc = RANDTARGET;
	currentPoint->destPos.x = cityBasearray[currentPoint->targetLoc].coord.x + CDX;
	currentPoint->destPos.y = cityBasearray[currentPoint->targetLoc].coord.y;

	currentPoint->currentPos = currentPoint->startPos;

	dx = currentPoint->destPos.x - currentPoint->startPos.x;
	dy = currentPoint->destPos.y - OLAUNCH_Y;
	currentPoint->delta_y = 1.0 * dy * INCOMINGBASEV / sqrt(dy * dy + dx * dx);
	currentPoint->delta_x = dx * INCOMINGBASEV / sqrt(dy * dy + dx * dx);

	if (offensiveMissiles.splitters > 0)
	{
	    currentPoint->destPos.y = OLAUNCH_Y;
	    currentPoint->destPos.y -= 1.0 / 4.0 * RAND((int)currentPoint->destPos.y);
	    currentPoint->destPos.y -= gameLevel;
	    currentPoint->missileType = SPLITTER;
	    offensiveMissiles.splitters--;
	}
	else
	{
	    currentPoint->missileType = BOMB;
	}
	currentPoint->next = malloc(sizeof(MA_NODE));
	pp = currentPoint;
	currentPoint = currentPoint->next;

    }
    offensiveMissiles.launched--;
    offensiveMissiles.next = head;
    pp->next = NULL;
    free(currentPoint);

    smartBombs.next = thisSmart = lastSmart = malloc(sizeof(SMART_NODE));
    while (++smartBombs.launched <= (int)(gameLevel - 4) / 3)
    {				/* attack missles init */
		thisSmart->currentPos.x = RANDLAUNCH;
		thisSmart->currentPos.y = OLAUNCH_Y;

		for (i = 0; i < 10; i++)	/* Try to get a good city (bwa-ha-ha) */
		{	if (cityStatus[thisSmart->targetLoc = RANDTARGET] == TRUE)
			break;
		}
	
		thisSmart->targetLoc = RANDTARGET;
		thisSmart->destPos.x = cityBasearray[thisSmart->targetLoc].coord.x + CDX;
		thisSmart->destPos.y = cityBasearray[thisSmart->targetLoc].coord.y;
	
		thisSmart->next = malloc(sizeof(MA_NODE));
		lastSmart = thisSmart;
		thisSmart = thisSmart->next;
    }
    smartBombs.launched--;
    if (smartBombs.launched == 0)
    {
	smartBombs.next = NULL;
    }
    lastSmart->next = NULL;
    free(thisSmart);

    [redAlertSound play:0.1 pan: 0.0];  
    
	return self;
}

- (MA_NODE_PNTR) addToIncoming:(NXPoint)startPoint :(int)number :(MA_NODE_PNTR) next
{				/* attack missles init */

    MA_NODE_PNTR newbomb = malloc(sizeof(MA_NODE));
    float    dx, dy;

    offensiveMissiles.launched++;	/* missile has been added */
    newbomb->startPos = startPoint;
    newbomb->targetLoc = RANDTARGET;
    newbomb->destPos.x = cityBasearray[newbomb->targetLoc].coord.x + CDX;
    newbomb->destPos.y = cityBasearray[newbomb->targetLoc].coord.y;

    newbomb->currentPos = newbomb->startPos;

    dx = newbomb->destPos.x - newbomb->startPos.x;
    dy = newbomb->destPos.y - newbomb->startPos.y;
    newbomb->delta_y = dy * INCOMINGBASEV / sqrt(dy * dy + dx * dx);
    newbomb->delta_x = dx * INCOMINGBASEV / sqrt(dy * dy + dx * dx);
    newbomb->missileType = BOMB;
    newbomb->next = next;
    return newbomb;
}


- (void) airExplosionAt:(NXPoint*)p
{
    register EXP_PNTR newExplosion;
    register EXP_PNTR ep;

	newExplosion = malloc(sizeof(EXP_NODE));
	newExplosion->currentPoint = *p;
	newExplosion->rad = 0.0;
	newExplosion->maxrad = MAXRADIUS + (random() % 10 - 4);	// variety is the spice of life
		// this makes small explosions where you need big ones and the other way round !
	newExplosion->maxReached = NO;
	newExplosion->next = NULL;
	
    if (defensiveExplosions.next == NULL)
		defensiveExplosions.next = newExplosion;
    else
    {	ep = defensiveExplosions.next;
		while (ep->next != NULL)
			ep = ep->next;
	
		ep->next = newExplosion;
    }
	
    defensiveExplosions.exploding++;

   	[exploSound play];
}

- (void) airExplosionAt:(NXPoint*)p withRadius:(float)r // this is for the bomber
{
    register EXP_PNTR newExplosion;
    register EXP_PNTR ep;

	newExplosion = malloc(sizeof(EXP_NODE));
	newExplosion->currentPoint = *p;
	newExplosion->rad = 0.0;
	newExplosion->maxrad = r;
	newExplosion->maxReached = NO;
	newExplosion->next = NULL;
	
    if (defensiveExplosions.next == NULL)
		defensiveExplosions.next = newExplosion;
    else
    {	ep = defensiveExplosions.next;
		while (ep->next != NULL)
			ep = ep->next;
	
		ep->next = newExplosion;
    }
	
    defensiveExplosions.exploding++;

   	[exploSound play];
}

- (void) surfaceExplosionAt:(NXPoint*)p
{
    register EXP_PNTR ep, newExplosion;

	newExplosion = malloc(sizeof(EXP_NODE));
	newExplosion->currentPoint = *p;
	newExplosion->rad = 0.0;
	newExplosion->maxrad = MAXRADIUS + 10;
	newExplosion->maxReached = NO;
	newExplosion->next = NULL;
	
    if (surfaceExplosions.next == NULL)
		surfaceExplosions.next = newExplosion;
    else
    {	ep = surfaceExplosions.next;
		while (ep->next != NULL)
		    ep = ep->next;
		ep->next = newExplosion;
    }
    surfaceExplosions.exploding++;
 
   	[surfExploSound play];  
}

- (void) attackMissilesPath
{
    MA_NODE_PNTR head, currentPoint, pp;
    NXPoint  nextPoint;

    head = currentPoint = pp = offensiveMissiles.next;
 /*
  * set up pointers to point at head of attack missile list 
  */
    while (currentPoint != NULL)
	{
		/* calculate next point in trajectory */
		nextPoint.x = currentPoint->currentPos.x + dFactor * currentPoint->delta_x;
		nextPoint.y = currentPoint->currentPos.y + dFactor * currentPoint->delta_y;
	
		if (nextPoint.y < currentPoint->destPos.y)
		{
		/* attack missile has arrived at target */
	
			switch (currentPoint->missileType)
			{
			case SPLITTER:
			{	currentPoint->next = [self addToIncoming:currentPoint->currentPos:1
													    :currentPoint->next];
				currentPoint->destPos.y = cityBasearray[currentPoint->targetLoc].coord.y;
				currentPoint->missileType = BOMB;
				break;
			}
	
			case BOMB:
			{
				offensiveMissiles.launched--;
				if (currentPoint->targetLoc == (TARGETS - 1))
				{	missiles1 = 0;
					[outMissiles setIntValue:missiles1];
					displayBase1 = FALSE;
				}
				else
					if (currentPoint->targetLoc == (TARGETS - 2))
					{	missiles2 = 0;
						[toRightMissiles setIntValue:missiles2];
						displayBase2 = FALSE;
					}
					else
						cityStatus[currentPoint->targetLoc] = FALSE;
				
				/* erase missile trajectory */
				PSWerasetrajectory(currentPoint->currentPos.x,currentPoint->currentPos.y,
								   currentPoint->startPos.x,currentPoint->startPos.y);
	
				/* creat an explosion node for surface explosions */
				[self surfaceExplosionAt:&(currentPoint->currentPos)];
				
				if (currentPoint == head)
				{	offensiveMissiles.next = head = currentPoint->next;
					free(currentPoint);
					currentPoint = head;
				}
				else
				{	pp->next = currentPoint->next;
					free(currentPoint);
					currentPoint = pp->next;
				}
				
				break;
			}
			}
		}
	
		else	/* Test to see if missile is hit */
		if (testDefensiveBombs(defensiveExplosions.next, currentPoint->currentPos))
		{
			offensiveMissiles.launched--;	/* missile has been destroyed */
			score += MSLDSTRCT;
			[outScore setIntValue:score];
		/* erase missile trajectory */
			PSWerasetrajectory(currentPoint->currentPos.x,currentPoint->currentPos.y,
							   currentPoint->startPos.x,currentPoint->startPos.y);
				
			[self airExplosionAt:&(currentPoint->currentPos)];
	
		/* remove missile from list */
			if (currentPoint == head)
			{
			offensiveMissiles.next = head = currentPoint->next;
			free(currentPoint);
			currentPoint = pp = head;
			}
			else
			{
			pp->next = currentPoint->next;
			free(currentPoint);
			currentPoint = pp->next;
			}
		}
		else
		{
			PSWplottrajectory(currentPoint->currentPos.x,
								currentPoint->currentPos.y, nextPoint.x, nextPoint.y);
			currentPoint->currentPos = nextPoint;
			pp = currentPoint;
			currentPoint = currentPoint->next;
		}
	}
}

- showHighs:sender
{
[highScoreTable displayHighScores: self];
return self;
}

int
pntInRect(pnt) NXPoint *
             pnt;
{
    if (pnt->x >= X_TRG && pnt->y >= Y_TRG && pnt->x <= W_TRG && pnt->y <= H_TRG)
	return (TRUE);
    else
	return (FALSE);
}

void
stepFun(DPSTimedEntry timedE, double timeN, void *data)
{
    [(id)data step];
}



/**************************************************************************************************/
/* all the following code has been copied from the NeXT answers code provided by NeXT */
/* and is not currently used */
/***************************************************************************************************/

/* Returns the color at a certain pixel location in a bitmap. */
/* This version assumes the bitmap is 1, 2, 4, or 8 bps deep  */
/* and its either grayscale or RGB. */

NXColor
colorAt(NXBitmapImageRep * image, int x, int y)
{
    int      sampleCnt, bps, spp, amask;
    unsigned char *planes[5], data[5];
    NXColorSpace colorSpace;

    spp = [image samplesPerPixel];
    bps = [image bitsPerSample];
    colorSpace = [image colorSpace];

#ifdef CHECK_VALIDITY
    if ((bps != 1 && bps != 2 && bps != 4 && bps != 8) ||
	(colorSpace != NX_RGBColorSpace ||
	 colorSpace != NX_OneIsBlackColorSpace ||
	 colorSpace != NX_OneIsWhiteColorSpace))
    {
	NXLogError("colorAt() can't deal with provided image.\n");
	return NX_COLORCLEAR;
    }
#endif
#ifdef CHECK_RANGE
    if ((x < 0) || (y < 0) || (x >= [image pixelsWide]) ||
	(y >= [image pixelsHigh]))
    {
	NXLogError("Pixel out of bounds in colorAt().\n");
	return NX_COLORCLEAR;
    }
#endif

    [image getDataPlanes:planes];

    amask = (1 << bps) - 1;	/* 1, 3, 15, 255 for bps = 1, 2, 4, 8 */

 /* Get the samples into the data[] array. */
    if ([image isPlanar])
    {
	int      pixel = x * bps;
	int      byteLoc = [image bytesPerRow] * y + (pixel >> 3);
	int      bitLoc = pixel & 7;

	for (sampleCnt = 0; sampleCnt < spp; sampleCnt++)
	{
	    data[sampleCnt] =
	      ((*(planes[sampleCnt] + byteLoc)) >> (8 - bitLoc - bps)) & amask;
	}
    }
    else
    {
	unsigned char *byteLoc = planes[0] + [image bytesPerRow] * y;
	int      bitLoc = x * bps * spp;

	for (sampleCnt = 0; sampleCnt < spp; sampleCnt++)
	{
	    data[sampleCnt] =
	      ((byteLoc[bitLoc >> 3]) >> (8 - (bitLoc & 7) - bps)) & amask;
	    bitLoc += bps;
	}
    }

 /* If no alpha, set it to opaque and increment spp. */
 /* Otherwise, compute the true color values (by un-premultipling). */

    if (![image hasAlpha])
    {
	data[spp] = amask;
	spp++;
    }
    else
    if (data[spp - 1] && data[spp - 1] != amask)
    {
	for (sampleCnt = 0; sampleCnt < spp - 1; sampleCnt++)
	{
	    data[sampleCnt] =
	      (unsigned char)(0.5 + amask *
			    (((float)(data[sampleCnt])) / (data[spp - 1])));
	}
    }
 /* At this point data[] contains spp samples, right justified  */
 /* within the range 0..amask.  We can either return those, or  */
 /* return them in a normalized fashion (in the range 0..255,   */
 /* after shifting them over to the left by multiplying them by */
 /* 255/amask), or we can return an NXColor. The latter is less */
 /* efficient, probably, but most abstract.   */

    switch (colorSpace)
    {
    case NX_RGBColorSpace:
	return NXConvertRGBAToColor(((float)data[0]) / amask,
				    ((float)data[1]) / amask,
				    ((float)data[2]) / amask,
		  [image hasAlpha] ? ((float)data[3]) / amask : NX_NOALPHA);

    case NX_OneIsWhiteColorSpace:
	return NXConvertGrayAlphaToColor(((float)data[0]) / amask,
		  [image hasAlpha] ? ((float)data[1]) / amask : NX_NOALPHA);

    case NX_OneIsBlackColorSpace:
	return NXConvertGrayAlphaToColor(((float)(amask - data[0])) / amask,
		     [image hasAlpha] ? ((float)(amask - data[1])) / amask :
					 NX_NOALPHA);
	case NX_CustomColorSpace:;
	case NX_CMYKColorSpace:;
    }

	return NXConvertGrayAlphaToColor(((float)(amask - data[0])) / amask,
		     [image hasAlpha] ? ((float)(amask - data[1])) / amask :
					 NX_NOALPHA); // just get rid of warning
}


@end
