// include this first
#include "glover.h"

//#include "cameo.h"
//#include "bff_load.h"
//#include "puzzles.h"
//#include "water.h"
//#define ADD2POINTER(a,b) ( (void *) ( (int)(a) + (b))  )

#define DUST_COLOUR 0x201810


void handPowerUpdate2();
void effectsUpdate_BlastRings();
void effectsDraw_BlastRings();
void effectsUpdateSpeedTrail();
void effectsDrawSpeedTrail();

void pickupUpdateSpells();
void pickupDrawSpells();
void pickupClearSpells();
void Draw_RawBlastring(SVECTOR *vec, LONG radius, ULONG colour);


void effectsAimToolStuff();

void	Draw_GaribOvl();

//void UnitsCheck();


//int ttube_colour = 0;
#define RGBC(R,G,B,C)	(ULONG)((R)|(G<<8)|(B<<16)|(C<<24))


//SPRITEX*	overlayssprpointer;
ULONG numberofgaribs; //set to garibs on THIS level, including extra lives;
ULONG numGaribs;	// normal garib count, including ones inside enemies
GARIBPOS *garibpositions;
NEWMODEL *bringstar;
ULONG garibscollected;


//MRTIPS *mrtippositions;
//ULONG numberofmrtips; 


//ENEMYPOS *enemies;
//ULONG numberofenemies;

//SCENERYPOS *sceneries;
//ULONG numberofsceneries;


SPRITEX *cardbase;
SPRITEX *extralifebase;
SPRITEX *mrtipbase;
SPRITEX *glowbase;
SPRITEX *sparklebase;
SPRITEX *garibscorebase;
SPRITEX *shadbase;
SPRITEX *bstarbase;
SPRITEX *starbase;
SPRITEX *wispbase;
SPRITEX *SPR_tubebase;
SPRITEX *SPR_fairbase;
SPRITEX *SPR_aiglowbase;
//SPRITEX *SPR_lightningbase;
SPRITEX *SPR_lightningAdd;
SPRITEX *SPR_lightningSub;

  SPRITEX *SPR_fardabase;
SPRITEX *SPR_fardsbase;

SPRITEX *SPR_firebbase;
SPRITEX *SPR_smokebase;
SPRITEX *SPR_bringbase;
SPRITEX *SPR_puffbase;

//SPRITEX *SPR_cheatbase;
SPRITEX *SPR_cheats[4];

ULONG garibbulging;
ULONG lastgaribframe;
BYTE  currentgaribscore;

int garibsparkrad = 0;
int garibsparky;
int garibsparkvel;

int sinewave1 = 0;
int sinewave2 = 0;

DEBRISSTR *debris;
DEBRISSTR *active_debris = NULL;
DEBRISSTR *free_debris = NULL;

int effectsOVduration  = 0;
int effectsOVtimer  = 0;
int effectsOVcolour = 0;
SPRITEX *gar_frame;


NEWMODEL *orbiterpsas[NUM_SPELLS];
BLASTRING blastrings[MAXBLASTRINGS];

int current_spell;
SPELLSTR spells[MAX_SPELLS];

//SPRITEX *gar_frame;

// ========================================================================================================

const int ttube_colours[NUM_SPELLS] =
{
	RGBC(192,192,192,0),	// 	SPELL_BALL_NORMAL,
	RGBC(192,192,192,0),	// 	SPELL_BALL_BOWLING,
	RGBC(192,192,192,0),	// 	SPELL_BALL_POWER,
	RGBC(192,192,192,0),	// 	SPELL_BALL_BEARING,
	RGBC(192,192,192,0),	// 	SPELL_BALL_BEACH,
	RGBC(192,192,192,0),	// 	SPELL_BALL_SNOW,
	RGBC(192,192,192,0),	// 	SPELL_BALL_CRYSTAL,
	RGBC(0,0,0,0),			// SPELL_DEATH,
	RGBC(0,192,0,0),		// SPELL_FROGGY,
	RGBC(192,120,40,0),		// SPELL_SUCTION,
	RGBC(192,0,0,0),		// SPELL_HERCULES,
	RGBC(192,192,0,0),		// SPELL_SPEEDUP,
	RGBC(0,192,192,0),		// SPELL_ROTORBLADES,
	RGBC(192,0,0,0),		// SPELL_BOOMERANG,
	RGBC(0,192,192,0),		// SPELL_VANISH,
};

void	Init_All_Effects()
{

	effectsOVtimer = 0;

//	for (k=0; k<MAXGLITTER;k++)glitter[k].count=0;
//	LINKLISTINIT(glitter[0]);

//	numberofmrtips = 0;
//	numberofenemies = 0;
	numberofgaribs = 0;

	numGaribs = 0;
	garibscollected=0;
	cardbase = NULL;
	garibbulging = 0;
	lastgaribframe = 0;
	currentgaribscore = 0;
/*
	(ULONG *)garibpositions=MALLOC(sizeof(GARIBPOS)*(MAX_GARIBS), "Garib positions"); 
	(ULONG *)mrtippositions=MALLOC(sizeof(MRTIPS)*(MAX_MR_TIPS), "MrTip positions"); 
	(ULONG *)enemies=MALLOC(sizeof(ENEMYPOS)*(MAX_ENEMIES), "Enemy positions"); 
	(ULONG *)sceneries=MALLOC(sizeof(SCENERYPOS)*(MAX_SCENERIES), "Scenery positions"); 
*/


	(ULONG *)garibpositions=MALLOC(sizeof(GARIBPOS)*(MAX_GARIBS),"gars");
//	(ULONG *)enemies=MALLOC(sizeof(ENEMYPOS)*(MAX_ENEMIES),"nmes");	// oops! not used any more!

/*
	(ULONG *)garibpositions=MALLOC(
		 sizeof(GARIBPOS)*(MAX_GARIBS)
		+sizeof(MRTIPS)*(MAX_MR_TIPS)
		+sizeof(ENEMYPOS)*(MAX_ENEMIES)
		+sizeof(SCENERYPOS)*(MAX_SCENERIES), "Effects stuff"); 
		,"effects");
*/


//	(ULONG *)mrtippositions=ADD2POINTER(garibpositions,sizeof(GARIBPOS)*(MAX_GARIBS));
//	(ULONG *)enemies=ADD2POINTER(mrtippositions,sizeof(MRTIPS)*(MAX_MR_TIPS));
//	(ULONG *)enemies=ADD2POINTER(garibpositions,sizeof(GARIBPOS)*(MAX_GARIBS));
//	(ULONG *)sceneries=ADD2POINTER(enemies,sizeof(ENEMYPOS)*(MAX_ENEMIES));

	
	DebrisInit();
	pickupClearSpells();
}

void effectsStartOverlay(int timer, int colour)
{
	effectsOVduration = timer;
	effectsOVtimer    = timer;
	effectsOVcolour   = colour;
}


void effectsOVScreen(unsigned int bgr, int mode)
{
	register	GsOTA 	*ot = (GsOTA*)(PolyList->org+1);
	{
		POLY_F4 *si = (POLY_F4 *) GsOUT_PACKET_P; 

		*(unsigned long *)(&si->r0) = bgr | ((GPU_COM_F4+2) << 24);

		si->x0=si->x2=-320;
		si->x1=si->x3=320;
		si->y0=si->y1=-128;
		si->y2=si->y3=128;

		PUTPACKETINTABLE(si,ot,POLYF4_LEN);
		GsOUT_PACKET_P+=sizeof(POLY_F4);
	}
//	si++;

	{
		DR_TPAGE *si = (DR_TPAGE *) GsOUT_PACKET_P;
//		SetDrawTPage(si,1,1,((SEMITRANS_ADD-1)<<5));	// this is a macro, anyway
		setDrawTPage(si,1,1,((mode-1)<<5));	// this is a macro, anyway

		PUTPACKETINTABLE(si,ot,DRTPAGE_LEN);
		GsOUT_PACKET_P+=sizeof(DR_TPAGE);
	}
}

// time for a big transparent thing...
void effectsDrawOverlay()
{
	int r,g,b;
	int len;

	r = (effectsOVcolour>>16) & 0xff;
	g = (effectsOVcolour>>8 ) & 0xff;
	b = (effectsOVcolour    ) & 0xff;

	if(effectsOVtimer > effectsOVduration * 3/4)
	{
		int now;
		len = effectsOVduration - (effectsOVduration * 3/4);
		if(!len) len = 1;
		r = 255 - r;
		g = 255 - g;
		b = 255 - b;
		now = effectsOVduration - effectsOVtimer;
		r = r * now/len;
		g = g * now/len;
		b = b * now/len;

		r = 255 - r;
		g = 255 - g;
		b = 255 - b;
	}
	else
	{
		len = effectsOVduration * 3/4;
		if(!len)
			len = 1;
		r = r * effectsOVtimer/len;
		g = g * effectsOVtimer/len;
		b = b * effectsOVtimer/len;
	}


	effectsOVScreen( (r) | (g<<8) | (b<<16), SEMITRANS_ADD);

	effectsOVtimer--;
}

/***************************************************************************************/

void	Update_All_Effects()
{
	static int counter = 0;

	lightningDrawAll();

	gar_frame = cardbase+counter;

	if (gameCtrl.gameActive==FALSE)
		return;

	counter++;
	if(counter >= 12)	//13)
		counter = 0;

	TIMER_START(TIMER_NMEMOVE);

	sinewave1+= 0x1400000;
	sinewave2+= 0x1074000;


	Update_Ripples();
	if (level==CARNIVALBONUS) Update_Garibs_Cabonus();
	else Update_Garibs();
//   	Update_MrTips();
//   	Update_Glitter();
	Update_Enemies();
	bosses_Update();
	Update_Debris();
	windUpdate();

	effectsUpdate_BlastRings();

	effectsUpdateSpeedTrail();

	pickupUpdateSpells();


	handPowerUpdate();
	handPowerUpdate2();	// below

	if(BallCtrl.type == BALL_MODE_CRYSTAL && !(activeframe & 3))
	{
		VECTOR tvec;
		tvec.vx = ballPos.vx + (((random(16)) - 8)<<12);
		tvec.vy = ballPos.vy + (((random(16)) - 8)<<12);
		tvec.vz = ballPos.vz + (((random(16)) - 8)<<12);

		New_Debris(DEBRIS_WISP,&tvec,0);
	}

//	RadiusCheckCheck();
//	UnitsCheck();

	puzzleCheckPuzzles();


	cameoUpdateCameos();

	effectsAimToolStuff();

	TIMER_STOP(TIMER_NMEMOVE);
}

void effectsGetGraphics()
{
/*
	SPELL_BALL_NORMAL,
	SPELL_BALL_BOWLING,
	SPELL_BALL_POWER,
	SPELL_BALL_BEARING,
	SPELL_BALL_BEACH,
	SPELL_BALL_SNOW,
	SPELL_BALL_CRYSTAL,

// others
 // The ball can certainly be hit by a boomerang spell, for a start...

 // you fire these at baddies
	SPELL_DEATH,
	SPELL_FROGGY,

// pickups fire these at the glove
	SPELL_SUCTION,
	SPELL_HERCULES,
	SPELL_SPEEDUP,
	SPELL_ROTORBLADES,
	
// pickups fire these at the ball
	SPELL_BOOMERANG,
	SPELL_VANISH,
	NUM_SPELLS
*/

	cardbase = textureFindFrames("ACARD00");	// acard0001 ... acard0013
	extralifebase = textureFindFrames("MARBLE");

	mrtipbase = textureFindInAllBanks("MRTIP");
	textureFindInAllBanks("MRTIP2");
	glowbase = textureFindFrames("GLOW");		// glow01 ... glow09
	ASSERT(glowbase);
	sparklebase = textureFindInAllBanks("SSPARK1");	// sspark1 is what the n64 uses for the garib panel sparkle
	ASSERT(sparklebase);
	garibscorebase = textureFindFrames("SCORE");
	ASSERT(garibscorebase);

	SPR_cheats[0] = fontList[2] + 0x5b - 32;
	SPR_cheats[1] = fontList[2] + 0x3c - 32;
	SPR_cheats[2] = fontList[2] + 0x5d - 32;
	SPR_cheats[3] = fontList[2] + 0x3e - 32;

	shadbase = textureFindInAllBanks("NEWSHADOW");
	ASSERT(shadbase);
	starbase = textureFindInAllBanks("STAR01");
	ASSERT(starbase);
	bstarbase = textureFindFrames("BSTAR");
	ASSERT(bstarbase);
	wispbase = textureFindFrames("NEWISP");
	ASSERT(wispbase);
	SPR_tubebase = textureFindInAllBanks("TESTUBE");
	ASSERT(SPR_tubebase);
	SPR_fairbase = textureFindFrames("SFAIR");
	ASSERT(SPR_fairbase);
//	SPR_fardbase = textureFindFrames("FARDUS");
	SPR_fardsbase = textureFindFrames("FARDUS");	// rats, the old ones have vanished, and the new ones are red
	ASSERT(SPR_fardsbase);
	SPR_fardabase = textureFindFrames("FARDUSA");	// rats, the old ones have vanished, and the new ones are red
	ASSERT(SPR_fardabase);

	SPR_aiglowbase = textureFindInAllBanks("AI_GLOW");
	ASSERT(SPR_aiglowbase);

	SPR_lightningSub = textureFindInAllBanks("AI_ELEC");
	ASSERT(SPR_lightningSub);
 	SPR_lightningAdd = textureFindInAllBanks("AI_ELEC2");
	ASSERT(SPR_lightningAdd);

	SPR_firebbase = textureFindFrames("FIREB");
	ASSERT(SPR_firebbase);
	SPR_smokebase = textureFindFrames("SMK");
	ASSERT(SPR_smokebase);
	SPR_bringbase = textureFindInAllBanks("BLASTR");
	ASSERT(SPR_bringbase);
	SPR_puffbase = textureFindInAllBanks("PUFF01");
	ASSERT(SPR_puffbase);

	bringstar = BFF_IsModelLoaded("SLAMSTAR", NULL);
	objectSetupSegmentSort(bringstar);


/*
	{"DEATH",	1024,20,0,0,Update_Pickup},			//DEATH
	{"FROGSPEL",1024,20,0,0,Update_Pickup},			//FROG - nb nb nb - not "frogspell" - 8 character name
	{"STICKY",	1024,20,0,0,Update_Pickup},			//SUCTION      
	{"HERCULES",1024,20,0,0,Update_Pickup},			//HERCULES     
	{"FAST",	1024,20,0,0,Update_Pickup},			//SPEEDUP      
	{"BLADES",	1024,20,0,0,Update_Pickup},			//ROTORBLADES
*/
}


void Effects_LevelStart()
{
	int i;
	BLASTRING *br;
	KillAllDebris();
	effectsGetGraphics();
//	cardbase = NULL;
	garibscollected=0;

	for(i = 0, br = &blastrings[0]; i < MAXBLASTRINGS; i++,br++)
	{
		br->active = 0;
	}
}

void Effects_LoadShapes()
{
	int i;
	static char *orbiternames[NUM_SPELLS] =
	{
		NULL,NULL,NULL,NULL,"BEACH",NULL,NULL,
		"DEATH","FROGSPEL",
		"STICKY","HERCULES","FAST","BLADES",
		"BOOMER",NULL
//		NULL,"DEATH","FROGSPEL","STICKY","HERCULES","FAST","BLADES"
	};

	for(i = 0; i < NUM_SPELLS; i++)
	{
//		LOADEDMODEL *model;
//note that other levels currently run out of mem if we load all the shapes in (!)
//		if(orbiternames[i])	// messy, but it'll do for now
//		{
//			orbiterpsas[i] = BFF_IsModelLoaded(orbiternames[i], NULL);	// don't worry if it isn't
//		}
		if(orbiternames[i])	// messy, but it'll do for now
		{
			LOADEDMODEL *orbit;
			if(BFF_IsModelLoaded(orbiternames[i], NULL))
			{
				orbit = EnsureModelLoaded(0,orbiternames[i],1024);	// make sure it's got a segment-sorter
				orbiterpsas[i] = orbit->psa;
			}
			else
			{
				orbiterpsas[i] = NULL;
			}
//			orbiterpsas[i] = BFF_IsModelLoaded(orbiternames[i], NULL);	// don't worry if it isn't
		}
		else
		{
			orbiterpsas[i] = NULL;
		}

/*
		LOADEDMODEL *model;
//note that other levels currently run out of mem if we load all the shapes in (!)
		if(orbiternames[i] && (level == ATLANTIS1 || level == ATTEST))	// messy, but it'll do for now
		{
			model = EnsureModelLoaded(0,orbiternames[i],1024);
			orbiterpsas[i] = model->psa;
		}
		else
*/

/*
		{
			orbiterpsas[i] = NULL;
		}
*/
//		enemypos->animinfo.segInfo = model->seg;
//		enemypos->anim.animInfo = &enemypos->animinfo;
	}

}

/**************************************************************************************/
void	Draw_All_Effects()
{
	TIMER_START(TIMER_NME);

// Draw stuff that uses the world matrix
  	gte_SetRotMatrix(&GsWSMATRIX);
  	gte_SetTransMatrix(&GsWSMATRIX);

	Draw_Garibs();

	if(gameInfo.keyRecordFlag!=PLAYBACK)
		Draw_GaribOvl();

//	   	Draw_MrTips();
	Draw_Ripples();
	Draw_Debris();
	pickupDrawSpells();
	effectsDraw_BlastRings();
	effectsDrawSpeedTrail();

// cameos has some none-setters, and some setters
	cameoDrawCameos();

// Draw stuff that changes the matrix

//		Draw_Sceneries();

//		Draw_Glitter();
	Draw_Enemies();


	handPowerDraw();

	if(effectsOVtimer)
	{
		effectsDrawOverlay();
	}



	{
		unsigned int h;
		int rval;
		int g,b;
		VECTOR temp;
		static int rotmax = 0;


		temp.vx = CamVars.camera.vpx;
		temp.vy = CamVars.camera.vpy;
		temp.vz = CamVars.camera.vpz;
		rval = waterGetWaterInfo(&temp,10,&h);
		if(rval == BELOWWATER)
		{
//			DB("below\n");
//			if(rotmax < 0x200)
//				rotmax += 0x10;
			if(rotmax < 0x50)
				rotmax += 0x1;
			g = (rcos((sinewave1>>18) & 4095) +4096) >> 7;
			b = (rcos((sinewave2>>18) & 4095) +4096) >> 7;

			effectsOVScreen( (b<<16) | (g<<8), SEMITRANS_ADD);

			CamVars.camera.rz  = (rsin((sinewave2>>19) & 4095) * rotmax) >> 4;	// >> 12;	// tis a fixed point nimber
//			DB("rotmax = %d,rz = %d\n",rotmax,CamVars.camera.rz);
		}
		else
		{
			CamVars.camera.rz  = CamVars.camera.rz * 3/4;
			rotmax = 0;
		}
	}


	TIMER_STOP(TIMER_NME);

}

/***************************************************************************************/
void	Kill_All_Effects()
{
	DebrisFree();
	pickupClearSpells();
}

/***************************************************************************************/



/*-------------------------------*/

void effectsDraw_BlastRings()
{
	SVECTOR sv;
	int r;
	int bri;
	int i;
	BLASTRING *br;

	for(i = 0, br = &blastrings[0]; i < MAXBLASTRINGS; i++,br++)
	{
		if(br->active)
		{

			r = (br->maxrad>>12) - br->count * ((br->maxrad-br->minrad)>>12)/br->time;

			sv.vx = br->v.vx >> 12;
			sv.vy = br->v.vy >> 12;
			sv.vz = br->v.vz >> 12;


			if(br->count < br->time/2)
			{
				bri = (br->count * 0x40/ (br->time/2));
			}
			else
			{
				bri = 0x40;
			}
			Draw_RawBlastring(&sv, r, bri | (bri<<8) | (bri<<16));


			if(!br->enemy)
			{

				r = (((br->time - br->count) * br->maxrad >>12)+ 1) / br->time;

				bringstar->position.vx=sv.vx;
				bringstar->position.vy=sv.vy;
				bringstar->position.vz=sv.vz;
				bringstar->world.rotate.vx = 0; 
				bringstar->world.rotate.vy = 0;
				bringstar->world.rotate.vz = 0;	//(val*48) MOD 4096;

				bringstar->globalscale.vx = r << 4;
				bringstar->globalscale.vy = r << 4;
				bringstar->globalscale.vz = r << 4;



		//		fadeStar->globalscale.vx = 2200+(val*30);
		//		fadeStar->globalscale.vy = 2200+(val*30);
		//		fadeStar->globalscale.vz = 2200+(val*30);

				objectSetAnimation(bringstar,0);
				objectDraw(bringstar);

  				gte_SetRotMatrix(&GsWSMATRIX);
  				gte_SetTransMatrix(&GsWSMATRIX);
			}
		}
	}
}


void effectsStartBlastRing(VECTOR *pos, int minrad, int maxrad, int time)
{
	int i;
	BLASTRING *br;

	for(i = 0, br = &blastrings[0]; i < MAXBLASTRINGS; i++,br++)
	{
		if(!br->active)
		{
			br->v = *pos;
			br->time = time;
			br->maxrad = maxrad;
			br->minrad = minrad;
			br->count = time;
			br->active = 1;
			br->enemy = 0;
		}
	}
}

void effectsStartNMEBlastRing(VECTOR *pos, int minrad, int maxrad, int time)
{
	int i;
	BLASTRING *br;

	for(i = 0, br = &blastrings[0]; i < MAXBLASTRINGS; i++,br++)
	{
		if(!br->active)
		{
			br->v = *pos;
			br->time = time;
			br->maxrad = maxrad;
			br->minrad = minrad;
			br->count = time;
			br->active = 1;
			br->enemy = 1;
		}
	}
}



void effectsUpdate_BlastRings()
{
	int i;
	BLASTRING *br;
	VECTOR vec;
	int rad;
	int mag;

	for(i = 0, br = &blastrings[0]; i < MAXBLASTRINGS; i++,br++)
	{
		if(br->active)
		{
			br->count--;
			rad = br->maxrad - (br->maxrad-br->minrad) * br->count/br->time;

// Stun enemies
			if(!br->enemy)
			{
				CheckForNmeHitsBlast(&br->v,rad, 4096 * 8);
			}

// make the ball bounce
			SUBVECTOR(&vec,&ballPos,&br->v);

			if(vec.vy < ballColl.radius+8192 && vec.vy > -ballColl.radius-8192)
			{
				vec.vy = 0;
				//mag = Magnitude(&vec);
				mag=Magnitude2D(vec.vx,vec.vz);

				if(mag < rad && mag > rad - (4096 * 30))
				{
					ballVel.vy = 7 * 4096;	// ballvel's -ve, rememver
				}
			}

// hurt the glove
			if(br->enemy && GloveCtrl.onGround)
			{
				SUBVECTOR(&vec,&glovePos,&br->v);

				if(vec.vy < gloveColl.radius+8192 && vec.vy > -gloveColl.radius-8192)
				{
					vec.vy = 0;
//					mag = Magnitude(&vec);
					mag=Magnitude2D(vec.vx,vec.vz);


					if(mag < rad && mag > rad - (4096 * 30) && !GloveCtrl.hurtFlag)
					{
						NMEHurtsGloveDirect();
					}
				}

			}

			if(!br->count)
				br->active = 0;
		}
	}
}


/*-------------------------------*/




// then do this

// put sprite position into vert
/*
width = (sprite->w) * 6;	// might have to play with the scaling factor, 6 works for us
	gte_SetLDDQB(0);			// clear offset control reg (C2_DQB)
	gte_ldv0(&vert);
	gte_SetLDDQA(width);		// shove sprite width into control reg (C2_DQA)
	gte_rtps();				// do the rtps
	gte_stsxy(&scrxy);		// get screen x and y
	gte_stszotz(&depth);		// get order table z
	gte_stopz(&width);		// get scaled width of sprite
	width >>= 16;
*/


// quickie "is a sphere visible?" test to avoid plotting off-screen bad guys
// (both vector and radius are "big" (*4096) jobs)
// (tbd - check figures are accurate)
int RadiusCheck4096(VECTOR *vec, LONG radius, int maxdist)
{
	VERT vect;
	SHORT xy[2];
	LONG distancescale;
	LONG spritez;

	vect.vx=vec->vx >> 12;
	vect.vy=vec->vy >> 12;
	vect.vz=vec->vz >> 12;

	gte_SetLDDQB(0);			// clear offset control reg (C2_DQB)
	gte_ldv0(&vect);
	gte_SetLDDQA((radius>>12) * 6);		// shove sprite width into control reg (C2_DQA)
	gte_rtps();
	gte_stsxy(&xy[0]);

	gte_stszotz(&spritez);
	gte_stopz(&distancescale);
	distancescale >>= 16;

	if(spritez <= 0)
		return 0;
	if(spritez >= maxdist)
		return 0;
	if(distancescale < 4)
		return 0;
	if(xy[0]+distancescale < -256 || xy[0] - distancescale > 256)
		return 0;
	if(xy[1]+distancescale < -128 || xy[1] - distancescale > 128)
		return 0;

	return 1;
}
// (both vector and radius are "small" (not *4096) jobs)
// (tbd - check figures are accurate)
int RadiusCheck(SVECTOR *vec, SHORT radius)
{
	VERT vect;

	SHORT xy[2];
	LONG distancescale;
	LONG spritez;

	vect.vx=vec->vx;
	vect.vy=vec->vy;
	vect.vz=vec->vz;

	gte_SetLDDQB(0);			// clear offset control reg (C2_DQB)
	gte_ldv0(&vect);
	gte_SetLDDQA((long)radius * 6);		// shove sprite width into control reg (C2_DQA)
	gte_rtps();
	gte_stsxy(&xy[0]);

	gte_stszotz(&spritez);
	gte_stopz(&distancescale);
	distancescale >>= 16;

// actually, this never happens... you just get 1024's in the x/y
	if(spritez <= 0)
	{
//		printf("Radcheck %d %d %d Behind\n", xy[0],xy[1],distancescale);
		return 0;
	}
	if(distancescale < 4)
	{
//		printf("Radcheck %d %d %d Tiny\n", xy[0],xy[1],distancescale);
		return 0;
	}
	if(xy[0]+distancescale < -256 || xy[0] - distancescale > 256)	// tbd - check "resolution"
	{
//		printf("Radcheck %d %d %d Off H\n", xy[0],xy[1],distancescale);
		return 0;
	}
	if(xy[1]+distancescale < -128 || xy[1] - distancescale > 128)
	{
//		printf("Radcheck %d %d %d Off V\n", xy[0],xy[1],distancescale);
		return 0;
	}

//	printf("** Radcheck %d %d %d on\n", xy[0],xy[1],distancescale);

	return 1;
}


/******************************************************************************/

// =================== Axis-aligned rings =================


#if (GOLDCD==NO) && (RELEASE==NO)
void Draw_RadCheckRing2(SVECTOR *vec, LONG radius, ULONG colour)
{
	LONG spritez[4];
//	LONG spritezn;
	VERT vect[4];
	POLY_FT4 *si;
	GsOTA 	*otptr;
	int axis;
	LONG spritezn;

  	gte_SetRotMatrix(&GsWSMATRIX);
  	gte_SetTransMatrix(&GsWSMATRIX);

	for(axis = 0; axis < 6; axis++)
	{
	if (TOOMANYPOLYS(4*MAXPACKETSIZE,"Draw_RadCheckRing"))return;
// 0 1
// 2 3

		switch(axis)
		{
			case 0:
			case 3:
			{
				vect[0].vx = vec->vx - radius;
				vect[0].vz = vec->vz - radius;
				vect[1].vx = vec->vx - radius;
				vect[1].vz = vec->vz + radius;

				vect[2].vx = vec->vx + radius;
				vect[2].vz = vec->vz - radius;
				vect[3].vx = vec->vx + radius;
				vect[3].vz = vec->vz + radius;

				vect[0].vy = vect[1].vy = vect[2].vy = vect[3].vy = vec->vy;
				break;
			}
			case 1:
			case 4:
			{
				vect[0].vy = vec->vy - radius;
				vect[0].vz = vec->vz - radius;
				vect[1].vy = vec->vy - radius;
				vect[1].vz = vec->vz + radius;

				vect[2].vy = vec->vy + radius;
				vect[2].vz = vec->vz - radius;
				vect[3].vy = vec->vy + radius;
				vect[3].vz = vec->vz + radius;

				vect[0].vx = vect[1].vx = vect[2].vx = vect[3].vx = vec->vx;
				break;
			}
			case 2:
			case 5:
			{
				vect[0].vx = vec->vx - radius;
				vect[0].vy = vec->vy - radius;
				vect[1].vx = vec->vx - radius;
				vect[1].vy = vec->vy + radius;

				vect[2].vx = vec->vx + radius;
				vect[2].vy = vec->vy - radius;
				vect[3].vx = vec->vx + radius;
				vect[3].vy = vec->vy + radius;

				vect[0].vz = vect[1].vz = vect[2].vz = vect[3].vz = vec->vz;
				break;
			}
		}
		if(axis >= 3)
		{
			VERT temp;
			temp = vect[0];
			vect[0] = vect[1];
			vect[1] = temp;
			temp = vect[2];
			vect[2] = vect[3];
			vect[3] = temp;
		}

		si = (POLY_FT4 *) GsOUT_PACKET_P;

	// 	*(ULONG*)&si->r0=sprctrl.r|(sprctrl.g<<8)|(sprctrl.b<<16)|(GPU_COM_TF4<<24);
 		*(ULONG*)&si->r0=colour|(GPU_COM_TF4<<24);

		*(ULONG*)&si->u0=*(ULONG*)&(SPR_ripbase)->u0;
		*(ULONG*)&si->u1=*(ULONG*)&(SPR_ripbase)->u1;
		*(ULONG*)&si->u2=*(ULONG*)&(SPR_ripbase)->u2;
		*(ULONG*)&si->u3=*(ULONG*)&(SPR_ripbase)->u3;

		gte_ldv3c(&vect[0].vx);
		gte_rtpt();
		gte_stsxy3_ft4((ULONG*)si);
		gte_stsz3c(&spritez[0]);
		
		gte_ldv0(&vect[3].vx);
		gte_rtps();
		gte_stsxy(&si->x3);
		gte_stsz(&spritez[3]);

		{
			spritezn = spritez[0];
			if(spritez[1]<spritezn) spritezn = spritez[1];
			if(spritez[2]<spritezn) spritezn = spritez[2];
			if(spritez[3]<spritezn) spritezn = spritez[3];
			spritezn >>= 2;
	//	spritezn -= 1;	// 16's ok...? as is 12, 8
		}
 		si->clut = SPR_ripbase->clut;
 		si->tpage = SPR_ripbase->tpage|((SEMITRANS_ADD-1)<<5);

 		si->code=GPU_COM_TF4+2;

		if(spritezn > 2000)
			return;
		if(spritezn < 0)
			return;

	// shadows need to be plotted after the lscape they're on, but before the object they're the shadow of
	// (flying objects, where things can go between the obj & its shadow, complicate matters somewhat)

		otptr=(GsOTA*)(PolyList->org+( (spritezn & MAXDEPTH) >> (PolyList->shift) ));

		PUTPACKETINTABLE(si,otptr,POLYTF4_LEN);

		INCPOLYSDRAWN;
		si++;							//incr. packet building address by sizeof POLY_FT4
		GsOUT_PACKET_P=(PACKET*)si;		 	//return new packet building address
	}
}
#endif

// ========================================================



// note - radius is diagonal
void drawFlatPolygon(SPRITEX *sprite, VECTOR *vec, LONG radius, int spin, ULONG colour)
{
	LONG spritez[4];
//	LONG spritezn;
	VERT vect[4];
	POLY_FT4 *si;
	GsOTA 	*otptr;
	int s,c;

	if (TOOMANYPOLYS(4*MAXPACKETSIZE,"Draw_FlatPoly"))return;
// 0 1
// 2 3

	if(radius == 0)
		return;
	if(radius > 0)
	{
		s = (radius * rsin(spin)) >> 12;
		c = (radius * rcos(spin)) >> 12;

		vect[0].vx = vec->vx + s;
		vect[0].vz = vec->vz + c;

		vect[1].vx = vec->vx + c;
		vect[1].vz = vec->vz - s;

		vect[2].vx = vec->vx - c;
		vect[2].vz = vec->vz + s;

		vect[3].vx = vec->vx - s;
		vect[3].vz = vec->vz - c;
	}
	else
	{
		s = (-radius * rsin(spin)) >> 12;
		c = (-radius * rcos(spin)) >> 12;

		vect[0].vx = vec->vx + s;
		vect[0].vz = vec->vz + c;

		vect[1].vx = vec->vx - c;
		vect[1].vz = vec->vz + s;

		vect[2].vx = vec->vx + c;
		vect[2].vz = vec->vz - s;

		vect[3].vx = vec->vx - s;
		vect[3].vz = vec->vz - c;
	}


	vect[0].vy = vect[1].vy = vect[2].vy = vect[3].vy = vec->vy;

	si = (POLY_FT4 *) GsOUT_PACKET_P;

	gte_ldv3c(&vect[0].vx);
	gte_rtpt();
	gte_stsxy3_ft4((ULONG*)si);
	gte_stsz3c(&spritez[0]);
	
	gte_ldv0(&vect[3].vx);
	gte_rtps();
	gte_stsxy(&si->x3);
	gte_stsz(&spritez[3]);

	spritez[0] = spritez[0] +spritez[1] + spritez[2] + spritez[3];
//	spritez[0] = spritez[0] >> 4;
	spritez[0] = spritez[0] >> 3;

	if(spritez[0] > 2000)
		return;
	if(spritez[0] < 0)
		return;


	*(ULONG*)&si->u0=*(ULONG*)&(sprite)->u0;	// u0,v0, and clut info
	*(ULONG*)&si->u2=*(ULONG*)&(sprite)->u2;	// plus pad1
	*(ULONG*)&si->u3=*(ULONG*)&(sprite)->u3;	// plus pad2

	if( ! (colour & 0xF0000000) )
	{
		*(ULONG*)&si->u1=*(ULONG*)&(sprite)->u1;	// u1,v1, and tpage info
		*(ULONG*)&si->r0 = colour | (GPU_COM_TF4<<24);
	}
	else
	{
		*(ULONG*)&si->u1=(*(ULONG*)&(sprite)->u1) | ((colour >> 8) & 0x00600000);
		*(ULONG*)&si->r0 = (colour & 0xffffff) | ((GPU_COM_TF4+2)<<24);
	}

//	otptr=(GsOTA*)(PolyList->org+( (spritez[0] & MAXDEPTH) >> (PolyList->shift) ));
	spritez[0] = spritez[0] >> PolyList->shift;
	otptr=(GsOTA*)(PolyList->org+spritez[0]);

	PUTPACKETINTABLE(si,otptr,POLYTF4_LEN);

	INCPOLYSDRAWN;
	si++;
	GsOUT_PACKET_P=(PACKET*)si;
}











/*
#define BP0 0
#define BP1 1
#define BP2 2
#define BP3 3
*/
/*
#define BP0 2
#define BP1 0
#define BP2 3
#define BP3 1
*/
#define BP0 1
#define BP1 3
#define BP2 0
#define BP3 2
void Draw_RawBlastring(SVECTOR *vec, LONG radius, ULONG colour)
{
	LONG spritez[4];
//	LONG spritezn;
	VERT vect[4];
	POLY_FT4 *si;
	GsOTA 	*otptr;
	int i;
	int spritezn;

	if (TOOMANYPOLYS(4*MAXPACKETSIZE,"Draw_RawBlast"))return;
// 0 1
// 2 3
	vect[BP0].vx = vec->vx;
	vect[BP0].vz = vec->vz;

	vect[BP0].vy = vect[BP1].vy = vect[BP2].vy = vect[BP3].vy = vec->vy;

	vect[BP1].vx = vec->vx;
	vect[BP1].vz = vec->vz + radius;

	vect[BP2].vx = vec->vx + radius;
	vect[BP2].vz = vec->vz;
	vect[BP3].vx = vec->vx + radius;
	vect[BP3].vz = vec->vz + radius;

	si = (POLY_FT4 *) GsOUT_PACKET_P;

	for(i = 0; i < 4; i++)
	{
 		*(ULONG*)&si->r0=colour|(GPU_COM_TF4<<24);

		*(ULONG*)&si->u0=*(ULONG*)&(SPR_bringbase)->u0;
		*(ULONG*)&si->u1=*(ULONG*)&(SPR_bringbase)->u1;
		*(ULONG*)&si->u2=*(ULONG*)&(SPR_bringbase)->u2;
		*(ULONG*)&si->u3=*(ULONG*)&(SPR_bringbase)->u3;

		gte_ldv3c(&vect[0].vx);
		gte_rtpt();
		gte_stsxy3_ft4((ULONG*)si);
		gte_stsz3c(&spritez[0]);
		
		gte_ldv0(&vect[3].vx);
		gte_rtps();
		gte_stsxy(&si->x3);
		gte_stsz(&spritez[3]);

		{
			spritezn = spritez[0];
			if(spritez[1]<spritezn) spritezn = spritez[1];
			if(spritez[2]<spritezn) spritezn = spritez[2];
			if(spritez[3]<spritezn) spritezn = spritez[3];
			spritezn >>= 2;
	//	spritezn -= 1;	// 16's ok...? as is 12, 8
		}
 		si->clut = SPR_bringbase->clut;
 		si->tpage = SPR_bringbase->tpage|((SEMITRANS_ADD-1)<<5);

 		si->code=GPU_COM_TF4+2;

		if(spritezn < 2000 && spritezn > 0)
		{

			otptr=(GsOTA*)(PolyList->org+( (spritezn & MAXDEPTH) >> (PolyList->shift) ));

			PUTPACKETINTABLE(si,otptr,POLYTF4_LEN);

			INCPOLYSDRAWN;
			si++;							//incr. packet building address by sizeof POLY_FT4
		}
		vect[BP1] = vect[BP2];
		switch(i)
		{
			case 0:
				vect[BP2].vx = vec->vx;
				vect[BP2].vz = vec->vz-radius;
				vect[BP3].vx = vec->vx+radius;
				vect[BP3].vz = vec->vz-radius;
				break;
			case 1:
				vect[BP2].vx = vec->vx-radius;
				vect[BP2].vz = vec->vz;
				vect[BP3].vx = vec->vx-radius;
				vect[BP3].vz = vec->vz-radius;
				break;
			case 2:
				vect[BP2].vx = vec->vx;
				vect[BP2].vz = vec->vz+radius;
				vect[BP3].vx = vec->vx-radius;
				vect[BP3].vz = vec->vz+radius;
				break;
		}
	}
	GsOUT_PACKET_P=(PACKET*)si;		 	//return new packet building address
}




/******************************************************************************/
//extern	VECTOR ballPos; 
//#define BALLGARIBCOLLRADIUS 10
//#define GLOVEGARIBCOLLRADIUS 10



//#define CARDSPRITE SPR_ACARD0001
// for now...
//#define EXTRASPRITE 0
int garibScores[]={10,20,50,100,500};
int garibPitches[] =
{
	DEFAULT_PITCH,
	DEFAULT_PITCH + 0x40,
	DEFAULT_PITCH + 0x80,
	DEFAULT_PITCH + 0xc0,
	DEFAULT_PITCH + 0x100,
};

void Collect_Garib(GARIBPOS *gar, int with_ball)
{
	VECTOR pos;
	pos.vx = gar->x << 12;
	pos.vy = gar->y << 12;
	pos.vz = gar->z << 12;

	switch(gar->type)
	{
		case GENERATED_GARIB:
			if(gar->yvel < 0)	// just so you SEE the thing going up
				return;


		case NORMAL_GARIB:
			if(currentgaribscore)	// purely a "first garib" thing.
			{
				ULONG diff;
				diff = activeframe - lastgaribframe;
//				PRINTF("garib time %d\n",diff);	// =development only

				if(diff < GARIBSCORINGTIME)
				{
		// the frames are (10,20,50,100,500,x2)
		// (the 500-pointer is the extra life)
					if(currentgaribscore < 4)
						currentgaribscore++;
				}
				else
				{
					currentgaribscore = 1;
				}
			}
			else
			{
				currentgaribscore = 1;
			}

			if(with_ball && currentgaribscore < 5)
				currentgaribscore++;

			lastgaribframe = activeframe;

		//	printf("got gar %d %d %d\n",gar->x,-gar->y,-gar->z);

//			gar->num|=0x8000;
			gar->flags |= GARIBFLAG_COLLECTED;

//			gar->scoretimer = 0;
//			gar->scorevalue = currentgaribscore-1;
			garibscollected++;

			garibbulging = 9;

			garibsparkrad = 1;
			garibsparky = -95;
			garibsparkvel = -5;

			New_Debris(DEBRIS_POINTS,&pos,currentgaribscore-1);	// (0,1,2,3) for (10,20,50,100)
			if(with_ball && BallCtrl.type == BALL_MODE_CRYSTAL)
			{
				sfxSetSamplePitch(globalFX, SFX_COLLECT_POWERUP_3, garibPitches[currentgaribscore-1]);
				sfxPlay3D(globalFX,SFX_COLLECT_POWERUP_3,&pos);
				New_Debris(DEBRIS_POINTS,&pos,5);	// (0,1,2,3) for (10,20,50,100)
				gameScore+=garibScores[currentgaribscore-1] * 2;
			}
			else
			{
				sfxSetSamplePitch(globalFX, SFX_COLLECT_BONUS_1, garibPitches[currentgaribscore-1]);
				sfxPlay3D(globalFX,SFX_COLLECT_BONUS_1,&pos);
				gameScore+=garibScores[currentgaribscore-1];
			}

// The "hand.c" code doesn't deal with collecting all the garibs when you're the ball
			if((level == FEARBONUS || level == PREHISTORICBONUS) && garibscollected == numGaribs)
			{
				GloveCtrl.lives++;
				sfxPlay3D(globalFX,SFX_COLLECT_POWERUP_1,&pos);
			}




			if(numGaribs == garibscollected && world != HUB)
			{
// If we'd not got all the garibs on this world before...
				if( ! (  (GameState.lives_got[(world-ATLANTIS) * 5])
						& (GameState.lives_got[(world-ATLANTIS) * 5+1])
						& (GameState.lives_got[(world-ATLANTIS) * 5+2])
						& 0x8000
					)
				  )

				{
					int lev;


					DB("all garibs in world not set so far...\n");

					GameState.lives_got[level - ATLANTIS1] |= 0x8000;

// And we have NOW...
					if(   (GameState.lives_got[(world-ATLANTIS) * 5])
						& (GameState.lives_got[(world-ATLANTIS) * 5+1])
						& (GameState.lives_got[(world-ATLANTIS) * 5+2])
						& 0x8000
					  )
					{
						DB("Extra heart-tastic\n");
						GameState.maxhealth++;
						GloveCtrl.health = GloveCtrl.maxHealth = GameState.maxhealth;
						effectsStartOverlay(10, 0xff00ff);
#ifdef MULTI_LANGUAGE
						cheatSetText(fontFindLangString(STR_EXTRAHEART));
#else
						cheatSetText("EXTRA HEART AWARDED!");
#endif
					}
				}

// Flag this level as having had all its garibs collected
// (In case we return-to-castle or something
//				if(!(GameState.lives_got[level - ATLANTIS1] & 0x8000)
				{
					DB("Setting all-garibs flag for this level\n");
					GameState.lives_got[level - ATLANTIS1] |= 0x8000;
				}
			}


			break;

		case GENERATED_LIFE:
			if(gar->yvel < 0)	// just so you SEE the thing going up
				return;
		case EXTRA_LIFE:
			gameScore+=500;
			gar->flags |= GARIBFLAG_COLLECTED;

			
			GloveCtrl.lives++;

//			gar->num|=0x8000;
//			gar->scoretimer = 0;
//			gar->scorevalue = 4;	// 500

// two samples that always play on xtra life, regardless of how gained
			sfxPlay3D(globalFX,SFX_HAND2_VICTORY1,&pos);	// note - could be glovepos or ballpos, but will be roughtly here
			sfxPlay3D(globalFX,SFX_COLLECT_POWERUP_1,&pos);

			New_Debris(DEBRIS_POINTS,&pos,4);
			break;

		case SUPER_GARIB:
		default:
			gar->flags |= GARIBFLAG_COLLECTED;
//			gar->num|=0x8000;
//			gar->scoretimer = 100;	// ensure it starts off vanished
//			gar->scorevalue = 0;
			break;
	}

	New_Debris(DEBRIS_BLUESTAR,&pos,0);
}


int effect_BoxCollideWithPlayer(int test,SVECTOR *pos, int radius)
{
	SVECTOR d;
	int r;
	int retval;

	retval = 0;
	if(test & EFFECT_BOXCOLL_BALL)
	{
		r = radius + (ballColl.radius >> 12);
		d.vx = pos->vx - pBallPSA->position.vx;
		d.vy = pos->vy - pBallPSA->position.vy;
		d.vz = pos->vz - pBallPSA->position.vz;
		if(d.vx > -r && d.vx < r && d.vy > -r && d.vy < r && d.vz > -r && d.vz < r)
		{
			retval |= EFFECT_BOXCOLL_BALL;
		}
	}
	if(test & EFFECT_BOXCOLL_GLOVE)
	{
		r = radius + (gloveColl.radius >> 12);
		d.vx = pos->vx - pGlovePSA->position.vx;
		d.vy = pos->vy - pGlovePSA->position.vy;
		d.vz = pos->vz - pGlovePSA->position.vz;
		if(d.vx > -r && d.vx < r && d.vy > -r && d.vy < r && d.vz > -r && d.vz < r)
		{
			retval |= EFFECT_BOXCOLL_GLOVE;
		}
	}
	return retval;
}




void EnableGaribGroup(int group, int enable)
{
	ULONG k;
	GARIBPOS *gar;

	gar = &garibpositions[0];
	for (k=0;k<numberofgaribs;k++,gar++)
	{
		if(gar->group == group)
		{
			if(enable)
			{
				gar->flags |= GARIBFLAG_DISPLAY;
			}
			else
			{
				gar->flags &= ~GARIBFLAG_DISPLAY;
			}
		}
	}
}

void DropGaribGroup(int group, int yval)
{
	ULONG k;
	GARIBPOS *gar;

	gar = &garibpositions[0];
	for (k=0;k<numberofgaribs;k++,gar++)
	{
		if(gar->group == group)
		{
			if(gar->type == NORMAL_GARIB || gar->type == EXTRA_LIFE)
			{
				gar->flags |= GARIBFLAG_DISPLAY;
	//			gar->y = yval;

				if(gar->type == NORMAL_GARIB)
					gar->type = GENERATED_GARIB;
				else
					gar->type = GENERATED_LIFE;

				gar->accy = ((int)gar->y)<<12;
				gar->yvel = - 4096 * 10 + (RANDOM256() <<3);
				gar->targy = yval;
			}
		}
	}
}

void ReleaseEnemyGarib(VECTOR *pos)
{
	GARIBPOS *gar;
	ASSERT(numberofgaribs < MAX_GARIBS);

	gar = &garibpositions[numberofgaribs];
	gar->x = pos->vx>>12;
	gar->y = pos->vy>>12;
	gar->z = pos->vz>>12;
	gar->accy = pos->vy;
	gar->yvel = -4096 * 10;

	gar->type = GENERATED_GARIB;

	gar->flags = GARIBFLAG_DISPLAY;
	gar->group = 0;

	gar->targy = (pos->vy + getHeightAt(pos->vx,pos->vy,pos->vz))>>12;
	gar->targy -= 20;

	gar->shadowheight=0x7fff;

	numberofgaribs++;
}

void	Update_Garibs()
{
	ULONG k;
	GARIBPOS *gar;
	int coll;

	gar = &garibpositions[0];

	for (k=0;k<numberofgaribs;k++,gar++)
	{
	
//		if (!(gar->num&0x8000))
		if((gar->flags & (GARIBFLAG_DISPLAY + GARIBFLAG_COLLECTED)) == GARIBFLAG_DISPLAY)
		{
			if (gar->shadowheight==0x7fff && frame < 3)
			{
				VECTOR vec;
				int hag;
				vec.vx = ((int)(gar->x)) << 12;
				vec.vy = ((int)(gar->y)) << 12;
				vec.vz = ((int)(gar->z)) << 12;

				hag = getHeightAt(vec.vx, vec.vy, vec.vz);

//				if(gar->type == EXTRA_LIFE)
//					DB("gar hag = %d\n",(hag) >> 12);

				if(hag != 0x7fffffff)
				{
					vec.vy += hag;
					gar->shadowheight = vec.vy >> 12;
				}

//				printf("gar height at %d %d %d = %d\n",gar->x,gar->y,gar->z,gar->shadowheight);
			}

			coll = (gar->x<<12) - ballPos.vx;
			if(coll < ballColl.radius + GARIB_COLL_W*4096 && coll > -(ballColl.radius + GARIB_COLL_W*4096))
			{
				coll = (gar->z<<12) - ballPos.vz;
				if(coll < ballColl.radius + GARIB_COLL_W*4096 && coll > -(ballColl.radius + GARIB_COLL_W*4096))
				{
					coll = (gar->y<<12) - ballPos.vy;
					if(coll < ballColl.radius + GARIB_COLL_W*4096 && coll > -(ballColl.radius + GARIB_COLL_H*4096))
					{
					  	Collect_Garib(&garibpositions[k],1);
					}
				}
			}
			if(!(gar->flags  & GARIBFLAG_COLLECTED))
			{
				coll = (gar->x<<12) - glovePos.vx;
				if(coll < gloveColl.radius + GARIB_COLL_W*4096 && coll > -(gloveColl.radius + GARIB_COLL_W*4096))
				{
					coll = (gar->z<<12) - glovePos.vz;
					if(coll < gloveColl.radius + GARIB_COLL_W*4096 && coll > -(gloveColl.radius + GARIB_COLL_W*4096))
					{
						coll = (gar->y<<12) - glovePos.vy;
						if(coll < gloveColl.radius + GARIB_COLL_W*4096 && coll > -(gloveColl.radius + GARIB_COLL_H*4096))
						{
					  		Collect_Garib(&garibpositions[k],0);
						}
					}
				}
			}
/*
			coll = effect_BoxCollideWithPlayer(EFFECT_BOXCOLL_GLOVE | EFFECT_BOXCOLL_BALL, (SVECTOR *)(&gar->x), GARIBCOLLRADIUS);
			if(coll & EFFECT_BOXCOLL_BALL)
			{
			  	Collect_Garib(&garibpositions[k],1);	// you get 20,100,100,100 with (ball) or (ball+glove)
			}
			else if(coll & EFFECT_BOXCOLL_GLOVE)
			{
			  	Collect_Garib(&garibpositions[k],0);
			}
*/

			switch(gar->type)
			{
				case NORMAL_GARIB:
				case SUPER_GARIB:
					break;

				case EXTRA_LIFE:
				{
					VECTOR pos;
					pos.vx = (gar->x << 12) + rsin((sinewave1>>16) & 4095) * 10;
					pos.vy = (gar->y << 12) + rsin((sinewave2>>16) & 4095) * 6;
					pos.vz = (gar->z << 12) + rcos((sinewave1>>16) & 4095) * 10;
					New_Debris(DEBRIS_LIFEGLOW,&pos,0);
					break;
				}
				case GENERATED_LIFE:
				{
					VECTOR pos;
					pos.vx = (gar->x << 12) + rsin((sinewave1>>16) & 4095) * 10;
					pos.vy = (gar->y << 12) + rsin((sinewave2>>16) & 4095) * 6;
					pos.vz = (gar->z << 12) + rcos((sinewave1>>16) & 4095) * 10;
					New_Debris(DEBRIS_LIFEGLOW,&pos,0);
				}// dropthru...

				case GENERATED_GARIB:
				{

					gar->yvel += gravity;
					gar->accy += gar->yvel;

//					hag = getHeightAt(gar->x<<12, gar->accy, gar->z<<12);

					if (gar->accy > ((int)gar->targy)<<12)
					{
						gar->accy = ((int)gar->targy)<<12;
						if(gar->yvel > 0)
						{
							gar->yvel = -(gar->yvel / 2);
						}
						if(gar->yvel > -4096)
						{
							if(gar->type == GENERATED_GARIB)
								gar->type = NORMAL_GARIB;
							else
								gar->type = EXTRA_LIFE;
						}
					}
					gar->y = gar->accy>>12;
//					DB("gen-gar y = %d, vel = %d\n",gar->y, gar->yvel);
					break;
				}
			}
		}	
	}
}
/**********************************************************************************/
void	Update_Garibs_Cabonus()
{
	ULONG k,l;
	GARIBPOS *gar;
	int coll;

	gar = &garibpositions[0];

	for (k=0;k<numberofgaribs;k++,gar++)
	{
		for (l=0;l<10;l++)
		{
			if( ((gar->flags & (GARIBFLAG_DISPLAY + GARIBFLAG_COLLECTED)) == GARIBFLAG_DISPLAY))
			{
				if(CannonCtrl.alive[l])
				{
					ballPos.vx=CannonCtrl.bulletPos[l].vx<<12;
					ballPos.vy=-(CannonCtrl.bulletPos[l].vy<<12);
					ballPos.vz=CannonCtrl.bulletPos[l].vz<<12;

					ballColl.radius=40000;

					if (gar->shadowheight==0x7fff)
					{
						VECTOR vec;
						vec.vx = ((int)(gar->x)) << 12;
						vec.vy = ((int)(gar->y)) << 12;
						vec.vz = ((int)(gar->z)) << 12;

						vec.vy = vec.vy + getHeightAt(vec.vx, vec.vy, vec.vz);
						gar->shadowheight = vec.vy >> 12;
					}

					coll = (gar->x<<12) - ballPos.vx;
					if(coll < ballColl.radius + 10*4096 && coll > -(ballColl.radius + 10*4096))
					{
						coll = (gar->z<<12) - ballPos.vz;
						if(coll < ballColl.radius + 10*4096 && coll > -(ballColl.radius + 10*4096))
						{
							coll = (gar->y<<12) - ballPos.vy;
							if(coll < ballColl.radius + 12*4096 && coll > -(ballColl.radius + 12*4096))
							{
						  		Collect_Garib(&garibpositions[k],1);
							}
						}
					}
// no Glove on CaBonus
				}

				switch(gar->type)
				{
					case NORMAL_GARIB:
					case SUPER_GARIB:
						break;

					case EXTRA_LIFE:
					{
						VECTOR pos;
						pos.vx = (gar->x << 12) + rsin((sinewave1>>16) & 4095) * 10;
						pos.vy = (gar->y << 12) + rsin((sinewave2>>16) & 4095) * 6;
						pos.vz = (gar->z << 12) + rcos((sinewave1>>16) & 4095) * 10;
						New_Debris(DEBRIS_LIFEGLOW,&pos,0);
						break;
					}
// No generated Garibs/Lives on CaBonus
				}
			}	
		}
	}
}


/**********************************************************************************/
// returns psx-style bgr
// hex = 0...5, dist = 0...255, pastel = 0...127
unsigned int effectsGetPrimary(int hex, int dist, int pastel)
{
	int r,g,b;
	int antipastel = 127-pastel;

	switch(hex)
	{
	case 0:	// fade green up
		r = 127;
		g = pastel + ((dist * antipastel) >> 8);
		b = pastel;
		break;

	case 1:	// fade red out
		r = pastel + (((256-dist) * antipastel)>>8);
		g = 127;
		b = pastel;
		break;

	case 2:	// fade blue up
		r = pastel;
		g = 127;
		b = pastel + ((dist * antipastel) >> 8);
		break;

	case 3:	// fade green out
		r = pastel;
		g = pastel + (((256-dist) * antipastel)>>8);
		b = 127;
		break;

	case 4:	// fade red up
		r = pastel + ((dist * antipastel) >> 8);
		g = pastel;
		b = 127;
		break;

//	case 5:	// fade blue out
	default:
		r = 127;
		g = pastel;
		b = pastel + (((256-dist) * antipastel)>>8);
		break;
	}
	return ((b<<16) | (g<<8) | (r));
}

/*************************************************************************/

unsigned int effectsColourNear(unsigned int bgr, int range)
{
	int r,g,b;
	r = (bgr & 0xff);
	g = (bgr>>8)& 0xff;
	b = (bgr>>16)& 0xff;
	r += random(range<<1) - range;
	g += random(range<<1) - range;
	b += random(range<<1) - range;
	if(r < 0) r = 0;
	if(g < 0) g = 0;
	if(b < 0) b = 0;
	if(r > 127) r = 127;
	if(g > 127) g = 127;
	if(b > 127) b = 127;
	return ( (b<<16)|(g<<8)|r);

}
/*************************************************************************/
void	Draw_GaribOvl()
{
	UBYTE tempmess[16];


//	printf("Glove Height= %d\n",(glovePos.vy - getHeightAt(glovePos.vx, glovePos.vy, glovePos.vz)>>12));


	if(numGaribs)
	{
//		gar_frame = cardbase+(frame)MOD 13;
		sprctrl.scalex = sprctrl.scaley = 4096;

// Things that happen...
// there's the score, which rises up & fades
// theres the "bstar" thing, which happens right there
// there's the 6 spinny sparkles that come out of the garib counter
// theres the garib counter's sprite, which bulges

		if(garibsparkrad)
		{
			int fade;

			garibsparkrad+=2;	// ummm this is update, not draw...

			garibsparkvel++;

			garibsparky += garibsparkvel;

			fade = (42-garibsparkrad) * 128 / 42;

			SETRGBC(sprctrl.r, fade,fade,0, 0);
			sprctrl.semitrans = SEMITRANS_ADD;

			spritePrint(sparklebase,-210 -garibsparkrad, garibsparky,10);
			spritePrint(sparklebase,-210 +garibsparkrad, garibsparky,10);

			spritePrint(sparklebase,-210 -garibsparkrad/2, garibsparky -garibsparkrad * 2/3,10);
			spritePrint(sparklebase,-210 +garibsparkrad/2, garibsparky -garibsparkrad * 2/3,10);

			spritePrint(sparklebase,-210 -garibsparkrad/2, garibsparky +garibsparkrad * 2/3,10);
			spritePrint(sparklebase,-210 +garibsparkrad/2, garibsparky +garibsparkrad * 2/3,10);

			sprctrl.semitrans = SEMITRANS_NONE;

			if(garibsparkrad >= 40)	// timeout
				garibsparkrad = 0;
		}

		SETRGBC(sprctrl.r, 128,128,128, 0);

// make the garib counter bulge
		if(garibbulging) // 9...1 -> 0...8
		{
			garibbulging--;
			sprctrl.scalex = sprctrl.scaley = 4096 + 200 * garibbulging;
		}

#if PALMODE==YES
		spritePrint(gar_frame,-210,-100,10);
#else
//		spritePrint(gar_frame,-210,-100+8,10);
		spritePrint(gar_frame,-210,-100+16,10);
#endif
		sprctrl.scalex = sprctrl.scaley = 4096;

		{
/*
			static int rcount = 0;
			int bgr;
			rcount++;
			if(rcount >= 6 * 32)
			{
				rcount = 0;
			}
			bgr = effectsGetPrimary((rcount>>5),(rcount & 31)<<3,96);
			sprctrl.r = bgr & 0xff;	// tbd - do as a single op
			sprctrl.g = (bgr >>8)&0xff;
			sprctrl.b = (bgr >>16)&0xff;
*/
			SETRGBC(sprctrl.r,128,128,0,0);

			TEXTSETFONT(fontList[0]);

//			sprctrl.r1=128-sprctrl.r/2;
//			sprctrl.g1=128-sprctrl.g/2;
//			sprctrl.b1=128-sprctrl.b/2;

			//if(gameInfo.keyRecordFlag!=PLAYBACK)
			{
				SETRGBC(sprctrl.r1,128,40,8,TRUE);

				messctrl.justify=LEFTTEXT;
				sprintf(tempmess,"%d/%d",(int)garibscollected,(int)numGaribs);
				TEXTPRINTAT(80,30,tempmess);

				messctrl.justify=RIGHTTEXT;
				sprintf(tempmess,"%d", gameScore);
				TEXTPRINTAT(480,30,tempmess);

				sprctrl.shaded=FALSE;

				SETRGBC(sprctrl.r, 128,128,128, 0);
			}
		}

	}
}

/*********************************************************************************************/
// Debris
// (just the sprite-based stuff. object-based debris can be done by something specialist)

void KillAllDebris()
{
	int i;

	active_debris = NULL;
	free_debris = &debris[0];
	for(i = 0; i < MAX_DEBRIS-1; i++)
	{
		debris[i].next = &debris[i+1];
	}
	debris[i].next = NULL;
}
void DebrisInit()
{
	debris = MALLOC(MAX_DEBRIS * sizeof(DEBRISSTR),"debris");
	KillAllDebris();
}
void DebrisFree()
{
	FREE(debris);
	debris = NULL;
}


// grab the first entry in the free list
// tbd - keep track of the tail end of the active list
// and grab the oldest one if there's none free ?
// (careful - new debs may be allocced within the deb routine)
DEBRISSTR *New_Debris(int type,VECTOR *pos, int value)
{
	DEBRISSTR *deb;
	if(!free_debris)
		return NULL;

  	gte_SetRotMatrix(&GsWSMATRIX);
  	gte_SetTransMatrix(&GsWSMATRIX);
// note - prehist boss's smoke needs to be seen from the far end of the level
// 2000 ain't enough, so let's try 2500

	if(level == SPACEBOSS2 || level == PREHISTORICBOSS)
	{
		if(!RadiusCheck4096(pos, (20<<12), 5000))
			return NULL;
	}
	else
	{
		if(!RadiusCheck4096(pos, (20<<12), 2000))
			return NULL;
	}



	deb = free_debris;
	free_debris = deb->next;

	deb->next = active_debris;
	active_debris = deb;

	deb->type = type;
	deb->pos = *pos;
	deb->value = value;
	deb->timer = 0;
/*
	switch(deb->type)
	{
	}
*/
	return deb;
}

// ah. for this, we need the prev in the linked list.
// But since this should be called during a LL scan,
// we should already know it
void Kill_Debris(DEBRISSTR *debris, DEBRISSTR **prev)
{
	*prev = debris->next;
	debris->next = free_debris;
	debris->type = 0;
	free_debris = debris;
}

void Update_Debris()
{
	DEBRISSTR *deb;
	DEBRISSTR **prev;

// if deb->type becomes zero, it means we just removed it from the active list
	for(prev = (&active_debris); (deb = (*prev)) != 0; )
	{
		switch(deb->type)
		{
			case DEBRIS_BLUESTAR:
				deb->timer++;
				if(deb->timer == 18)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_POINTS:
				deb->timer++;
				if(deb->timer == 20)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_LIFEGLOW:
				deb->timer++;	// 01...09
				if(deb->timer == 9)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_RINGSTAR:
				deb->timer++;
				if(deb->timer == 12)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_WISP:
				deb->timer++;
				if(deb->timer == 7)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_SPELLDUST:
				deb->timer++;
				if(deb->timer == 10)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_SPELLFLASH:
				deb->timer++;
				if(deb->timer == 10)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_FLAME:
				deb->timer++;
				if(deb->timer == 30)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_FASTFLAME:
				deb->timer++;
				if(deb->timer == 15)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_STUNSTAR:
				deb->timer++;
//				deb->pos.vy += deb->timer * 256;
				if(deb->timer == 12)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_AIMBLOB:
				deb->timer++;
				if(deb->timer == 20)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_DUST:
				deb->timer++;
				if(deb->timer == 7)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_PORTALPUFF:
			case DEBRIS_PORTALPUFF2:
				deb->timer++;
				deb->pos.vy -= deb->timer * 3000;	//512;
				if(deb->timer == 12)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_STINGEXPLODE:
				deb->timer++;
				if(deb->timer == 7)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_HOOPHOVER:
				deb->timer++;
//				deb->pos.vy += (2 << 12);	//deb->timer * 1000;
				deb->pos.vy += deb->timer * 2000;
				if(deb->timer == 12)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_ROBOTHOVER:
				deb->timer++;
//				deb->pos.vy += (2 << 12);	//deb->timer * 1000;
				deb->pos.vy += deb->timer * 4000;
				if(deb->timer == 12)
					Kill_Debris(deb,prev);
				break;

			case DEBRIS_SMOKE:
				deb->timer++;
				deb->pos.vy -= deb->timer * 800;
				if(deb->timer == 30)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_TRACY_HEART:
				deb->timer++;
				deb->pos.vy -= deb->timer * 800;
				deb->pos.vx += rsin(deb->value)*2;
				deb->pos.vy += rcos(deb->value)*2;
				if(deb->timer == 20)
					Kill_Debris(deb,prev);
				break;
			case DEBRIS_OPEC_HOVER:
				deb->timer++;
				deb->pos.vy += deb->timer * 500;
				if(deb->timer == 10)
					Kill_Debris(deb,prev);
				break;

			case DEBRIS_CANNON:
				deb->timer+=3;
				deb->pos.vy -= deb->timer * 800;
				if(deb->timer >= 30)
					Kill_Debris(deb,prev);
				break;

			case DEBRIS_FRANKLECCY:
			case DEBRIS_FRANKLECCY2:
				deb->timer++;
				if(deb->timer >= 12)
					Kill_Debris(deb,prev);
				break;

		} 
		if(deb->type)
			prev = &deb->next;
	}
}


void New_StarRingDebris(VECTOR *pos, int num_stars)
{
	int i;
	for(i = 0; i < num_stars; i++)
	{
		New_Debris(DEBRIS_RINGSTAR,pos, (i << 12) / num_stars);
	}
}

void New_PortalDebris(VECTOR *portpos)
{
	VECTOR pos;
	unsigned long colour;
	int type;
//	colour = random(256 * 6);

	colour = (activeframe * 60) % (256*6);

	if(random(256) > 180)
		type = DEBRIS_PORTALPUFF2;
	else
		type = DEBRIS_PORTALPUFF;

	colour = effectsGetPrimary(colour>>8,(colour &0xff),32);

	pos.vx = portpos->vx + rcos((sinewave1>>17) & 4095) * 16;
	pos.vy = portpos->vy - 4096 * 5;
	pos.vz = portpos->vz + rsin((sinewave1>>17) & 4095) * 16;
	New_Debris(type,&pos,colour);

	pos.vx = portpos->vx - rcos((sinewave1>>17) & 4095) * 16;
	pos.vy = portpos->vy - 4096 * 5;
	pos.vz = portpos->vz - rsin((sinewave1>>17) & 4095) * 16;
	New_Debris(type,&pos,colour);
}

void New_RobotHoverDebris(VECTOR *portpos)
{
	VECTOR pos;
	unsigned long colour;
	colour = (activeframe) & 255;

	if(colour >= 128)
	{
		colour = 0x000080 + (colour<<8);
	}
	else
	{
		colour -= 128;
		colour = 0x008080 - (colour<<8);
	}

	pos.vx = portpos->vx + rcos((sinewave1>>17) & 4095) * 48;
	pos.vy = portpos->vy - 4096 * 5;
	pos.vz = portpos->vz + rsin((sinewave1>>17) & 4095) * 48;
	New_Debris(DEBRIS_ROBOTHOVER,&pos,colour);

	pos.vx = portpos->vx - rcos((sinewave1>>17) & 4095) * 48;
	pos.vy = portpos->vy - 4096 * 5;
	pos.vz = portpos->vz - rsin((sinewave1>>17) & 4095) * 48;
	New_Debris(DEBRIS_ROBOTHOVER,&pos,colour);
}

// ============ random lightening bolts ==================
#define LIGHTNING_WID 32
#define TRAIL_WID 32
LONG effectsLightningPosTrans(SVECTOR *vec,short *p1,short *p2,int wid)
{
	LONG spritez;
	int scaled_wid;
	int h,v,m,calc;

	gte_SetLDDQB(0);
	gte_ldv0(vec);
	gte_SetLDDQA(wid);	//LIGHTNING_WID);
	gte_rtps();
	gte_stsxy(p1);	//&si->x0);
	gte_stszotz(&spritez);
	gte_stopz(&scaled_wid);
	scaled_wid >>= 16;

	h =  p1[1];
	v = -p1[0];
	calc = h*h+v*v;
//	m = fast_sqrt(m);
	FASTSQRT(m,calc);


	m = m >> 16;
	if(m == 0) m = 1;
	h = h * scaled_wid / m;
	v = v * scaled_wid / m;

//	printf("mid = %d %d, sqr = %d, delta = %d %d\n",p1[0],p1[1],m, h,v);

	p2[0] = p1[0] + h;
	p2[1] = p1[1] + v;

	p1[0] = p1[0] - h;
	p1[1] = p1[1] - v;

	return spritez;
}

// bgrcolour = 0x808000 for cyan lightening
void effectsDrawLightningStrand(SVECTOR *v1, SVECTOR *v2, LONG bgr_colour)
{
	SVECTOR curr;
	SVECTOR next;
	POLY_FT4 *si;
	GsOTA 	*otptr;
	LONG spritez;
	int tick = 0;
	static int seed=0;

	if(gameCtrl.gameActive)
	{
		seed=GetRandomSeed();
	}
	else
	{
		SetRandomSeed(seed);
	}

	curr = *v1;
	next = *v1;

// tbd - check there's enough space
	si = (POLY_FT4 *) GsOUT_PACKET_P; 

	effectsLightningPosTrans(&curr,&si->x0,&si->x1,LIGHTNING_WID);

	while(!SVectorMoveTo(&next, v2, 32) && tick++ < 20)
	{
	 //	int scaled_wid[2];
		next.vx += random(32) - 16;
		next.vy += random(32) - 16;
		next.vz += random(32) - 16;

// end of scaling-and-transform
		spritez = effectsLightningPosTrans(&next,&si->x2,&si->x3,LIGHTNING_WID);

// tbd - make this ditch according to on-screen SIZE
// limit to "max poly depth", and we can ditch the "MAXDEPTH" "and" below...

		if(spritez > 20 && spritez < 1000)
		{
		 	*(ULONG*)&si->r0= bgr_colour | ( (GPU_COM_TF4+2) <<24);	// bgr, remember
		 	//*(ULONG*)&si->r0= bgr_colour | ( (GPU_COM_TF4) <<24);	// bgr, remember

			*(ULONG*)&si->u0=*(ULONG*)&(SPR_lightningAdd)->u0;
			*(ULONG*)&si->u1=*(ULONG*)&(SPR_lightningAdd)->u1;
			*(ULONG*)&si->u2=*(ULONG*)&(SPR_lightningAdd)->u2;
			*(ULONG*)&si->u3=*(ULONG*)&(SPR_lightningAdd)->u3;

			si->tpage = SPR_lightningAdd->tpage | ((SEMITRANS_ADD-1)<<5);

			otptr=(GsOTA*)(PolyList->org+( spritez & MAXDEPTH ));
			PUTPACKETINTABLE(si,otptr,POLYTF4_LEN);
			INCPOLYSDRAWN;

			//si[1].x0 = si[0].x2;
			//si[1].y0 = si[0].y2;
			//si[1].x1 = si[0].x3;
			//si[1].y1 = si[0].y3;

			FMEMCPY(si+1, si, sizeof(POLY_FT4));

			si++;

			*(ULONG*)&si->u0=*(ULONG*)&(SPR_lightningSub)->u0;
			*(ULONG*)&si->u1=*(ULONG*)&(SPR_lightningSub)->u1;
			*(ULONG*)&si->u2=*(ULONG*)&(SPR_lightningSub)->u2;
			*(ULONG*)&si->u3=*(ULONG*)&(SPR_lightningSub)->u3;

			si->tpage = SPR_lightningSub->tpage | ((SEMITRANS_SUB-1)<<5);

			si[1].x0 = si[0].x2;
			si[1].y0 = si[0].y2;
			si[1].x1 = si[0].x3;
			si[1].y1 = si[0].y3;

			si++;							//incr. packet building address by sizeof POLY_FT4
		}

		curr = next;
	}
	GsOUT_PACKET_P=(PACKET*)si;		 	//return new packet building address
}



// the "speedup" spell trail
#define SPEED_TRAIL_LEN 10
int effectsSpeedTrailLen = 0;
SVECTOR effectsSpeedList[SPEED_TRAIL_LEN];

void effectsUpdateSpeedTrail()
{
	int i;

	if(handpower_timer == 0 || handpower_type != SPELL_SPEEDUP)
	{
		effectsSpeedTrailLen = 0;
		return;
	}
	for(i = SPEED_TRAIL_LEN-1; i >0 ; i--)
	{
		effectsSpeedList[i] = effectsSpeedList[i-1];
	}
	effectsSpeedList[0].vx = glovePos.vx >> 12;
	effectsSpeedList[0].vy = glovePos.vy >> 12;
	effectsSpeedList[0].vz = glovePos.vz >> 12;

	if(effectsSpeedTrailLen < SPEED_TRAIL_LEN)
		effectsSpeedTrailLen++;
}

void effectsDrawSpeedTrail()
{
	SVECTOR curr;
	SVECTOR next;
	POLY_GT4 *si;
	GsOTA 	*otptr;
	LONG spritez;
   //	int tick = 0;
	int i;
	int bri;

	if(effectsSpeedTrailLen < 2)
		return;

	curr = effectsSpeedList[0];
//	next = effectsSpeedList[0];

// tbd - check there's enough space
	si = (POLY_GT4 *) GsOUT_PACKET_P; 

	effectsLightningPosTrans(&curr,&si->x1,&si->x3,TRAIL_WID);

//	while(!SVectorMoveTo(&next, v2, 32) && tick++ < 20)
	for(i = 1; i < effectsSpeedTrailLen; i++)
	{
		//int scaled_wid[2];
		next = effectsSpeedList[i];

		spritez = effectsLightningPosTrans(&next,&si->x0,&si->x2,TRAIL_WID);

		if(spritez > 20 && spritez < 1000)
		{
// 0 is the glove end

//			bri = 0x80 * (effectsSpeedTrailLen-i) /effectsSpeedTrailLen;
//			bri = bri | (bri<<8) | (bri << 16);
			bri = 0x808080;

		 	*(ULONG*)&si->r1= *(ULONG*)&si->r3 = bri;

//			bri = 0x80 * (effectsSpeedTrailLen-i-1) /effectsSpeedTrailLen;
//			bri = bri | (bri<<8) | (bri << 16);

			if(i == effectsSpeedTrailLen-1)
				bri = 0x000000;
			else
				bri = 0x808080;

	 		*(ULONG*)&si->r0= bri | ( (GPU_COM_TG4+2) <<24);
	 		*(ULONG*)&si->r2= bri;

			*(ULONG*)&si->u0=*(ULONG*)&(SPR_aiglowbase)->u0;
			*(ULONG*)&si->u1=*(ULONG*)&(SPR_aiglowbase)->u1;
			*(ULONG*)&si->u2=*(ULONG*)&(SPR_aiglowbase)->u2;
			*(ULONG*)&si->u3=*(ULONG*)&(SPR_aiglowbase)->u3;

			si->tpage = SPR_aiglowbase->tpage | ((SEMITRANS_ADD-1)<<5);

			otptr=(GsOTA*)(PolyList->org+( (spritez & MAXDEPTH) >> (PolyList->shift) ));
			PUTPACKETINTABLE(si,otptr,POLYTG4_LEN);
			INCPOLYSDRAWN;

			si[1].x1 = si[0].x0;
			si[1].y1 = si[0].y0;
			si[1].x3 = si[0].x2;
			si[1].y3 = si[0].y2;

			si++;							//incr. packet building address by sizeof POLY_FT4
		}

		curr = next;
	}
	GsOUT_PACKET_P=(PACKET*)si;		 	//return new packet building address
}


/*********************************************************************************************/

/*
BEHAVIOUR_PHYSICS	GloveBehaviour=
{
	10*4096,			// mass;
	GRAVITY,			// gravity;
	0*4096,				// bounce (glove dont bounce)
	(4096-(4096*0.1)),	// drag
	4096*10,			// maxspeed
	(25*4096*NORMAL_SCALE),			// radius
	4096*4,				// accel
};
// COLLDATA
*/

/*
void Draw_Sceneries()
{
	ULONG i;
	SCENERYPOS *scenery;

	scenery = &sceneries[0];
	for(i = 0; i < numberofsceneries; i++, scenery++)
	{
#ifdef PLACEHOLDERS
		sprctrl.rotate = (frame<<8) & 4095;
		Print3DSprite(cardbase,scenery->pos.vx>>12,scenery->pos.vy>>12,scenery->pos.vz>>12,256,2560);
		sprctrl.rotate = 0;
#endif

	}
}
*/
// what we need is a displayed sphere of known size, and a note as to whether it's flagged as "onscn" or not...
/*
void RadiusCheckCheck(SVECTOR *pos, int r)
{
	static SVECTOR mid = {24,-174,1560};
	Point3DType points[16];
	SHORT radius = 20;
	int i;
	int j;

	if(pos)
	{
		mid.vx = pos->vx;
		mid.vy = pos->vy;
		mid.vz = pos->vz;
	}
	if(r)
	{
		radius = r;
	}

	points[0].x = mid.vx;
	points[0].y = mid.vy;
	points[0].z = mid.vz;
	points[1] = points[2] = points[3] = points[4] = points[5] = points[0];

	points[0].x -= radius;
	points[1].x += radius;
	points[2].y -= radius;
	points[3].y += radius;
	points[4].z -= radius;
	points[5].z += radius;

	lscapeDrawBoundPoly3D(2, &points[0], 0x00,0x80,0x80);
	lscapeDrawBoundPoly3D(2, &points[2], 0x00,0x80,0x80);
	lscapeDrawBoundPoly3D(2, &points[4], 0x00,0x80,0x80);

	for(j = 0; j < 16; j+=2)
	{
		int sj = rsin(j << 8);
		int cj = rcos(j << 8);
	for(i = 0; i < 16; i+=2)
	{
		int si = rsin(i << 8) * radius / 4096;
		int ci = rcos(i << 8) * radius / 4096;

		points[i].x = si * sj / 4096 + mid.vx;
		points[i].y = ci + mid.vy;
		points[i].z = si * cj / 4096 + mid.vz;
	}
	lscapeDrawBoundPoly3D(16, &points[0], 0x00,0xff,0xff);
	}


  	gte_SetRotMatrix(&GsWSMATRIX);
  	gte_SetTransMatrix(&GsWSMATRIX);
	RadiusCheck(&mid,radius);
}
*/
/*
void UnitsCheck()
{
	static SVECTOR mid = {24,-174,1560};
	Point3DType points[2];
	SHORT radius = 20;
	int i;
	int j;


	for(i = 0; i < 16; i++)
	{
		points[0].x = mid.vx + i;
		points[0].y = mid.vy;
		points[0].z = mid.vz;

		points[1].x = mid.vx + i;
		points[1].y = mid.vy+16;
		points[1].z = mid.vz;
		lscapeDrawBoundPoly3D(2, &points[0], 0x00,0xff,0xff);

		points[0].x = mid.vx;
		points[0].y = mid.vy+i;
		points[0].z = mid.vz;

		points[1].x = mid.vx+16;
		points[1].y = mid.vy+i;
		points[1].z = mid.vz;
		lscapeDrawBoundPoly3D(2, &points[0], 0x00,0xff,0xff);
	}

}
*/

// =============================== Spells ===================================




/*
int spelltype = 0;
int spelltimer = 0;
int spellcolour = 0;
VECTOR spellpos;
VECTOR *spelltarg;
//VECTOR spellvel;
int spellangs[2] = {0,0};
int spellturnrate = 0;
*/

void pickupClearSpells()
{
	int i;
	for(i = 0; i < MAX_SPELLS; i++)
	{
		spells[i].timer = 0;
	}
}



// note - pos is transferred, targ is kept as a pointer.
SPELLSTR *pickupFireSpell(int type, VECTOR *pos, SHORT dirn, void *target, char action)	//VECTOR *targ)
{
	SPELLSTR *spell;

// note - bbggrr
	static const int colours[] =
	{
		0x000080,0x202040,	// normal = red
		0x000000,0x404040,	// bowling = black
		0x800080,0x402040,	// power = pink
		0x808080,0x404040,	// bearing = white
		0x800000,0x404020,	// beach = blue
		0x808000,0x404020,	// snow = cyan
		0x008000,0x204020,	// xtal = green

		0x000000,0x404040,	// death = black
		0x008000,0x204020,	// green = froggy

		0x306080,0x404040,	// suction = orange
		0x000080,0x204020,	// hercules = red
		0x008080,0x404040,	// speedup = yello
		0x808000,0x204020,	// rotorblades = cyn

		0x000080,0x404040,	// boomerang = red
		0x808000,0x404040,	// vanish = cyn

		0x800080,0x402040,	// carnival cameo = pink
		0x008000,0x204020,	// green = froggify
		0x008000,0x204020,	// green = pirates unfroggify
		0x800080,0x402040,	// pink = pirates growing spell again
		0x000000,0x404040	// black. Cancer

	};


	current_spell++;
	if(current_spell >= MAX_SPELLS)
		current_spell = 0;
	spell = &spells[current_spell];

	DB("PickupFireSpell %d, action %d\n",type,action);

	spell->type = type;
	spell->headcolour = colours[type*2];
	spell->tailcolour = colours[type*2+1];



// yup, this way round gives us sin/cos = forwards...
	spell->angles[0] = 0;
	spell->angles[1] = (2048 + dirn) & 4095;

	spell->pos = *pos;

	if(action == SPELLACTION_RUNOUT)
	{
		spell->timer = 100;
	}
	else
	{
		spell->timer = 500;
	}
//	spell->targ = targ;
	spell->target = target;
	spell->action = action;
	spell->duration = 200;	// default time of 15 seconds

	spell->turnrate = 0x40;

	New_Debris(DEBRIS_SPELLFLASH,&spell->pos,spell->headcolour);
	return spell;
}
/*
	SPELLACTION_BALLTRANS,
	SPELLACTION_NMETRANS,
	SPELLACTION_RUNOUT,
*/

int pickupFireGloveSpell(int type)
{
	VECTOR pos;
	LONG cosangle;
	LONG sinangle;
	ENEMYPOS *nme;

	cosangle = rcos(GloveCtrl.direction);
	sinangle = rsin(GloveCtrl.direction);

	pos.vx = glovePos.vx - sinangle * 16;
	pos.vy = glovePos.vy - 4096 * 5;
	pos.vz = glovePos.vz - cosangle * 16;

//	type = SPELL_FROGGY;

	DB("PickupFireGloveSpell %d\n",type);

	if(type == SPELL_FROGGY || type == SPELL_DEATH)
	{
// scan through the enemies list looking for a target
		nme = nmeFindEnemyToShootAt();
		if(!nme)
		{
// fire straight out, a long way. For now, shoot at the ball
//			pickupFireSpell(type,&pos,GloveCtrl.direction,&ballPos);
			pickupFireSpell(type,&pos,GloveCtrl.direction,NULL,SPELLACTION_RUNOUT);
		}
		else
		{
//			pickupFireSpell(type,&pos,GloveCtrl.direction,&nme->pos);	// note - the nme mustn't be reallocced
			pickupFireSpell(type,&pos,GloveCtrl.direction,nme,SPELLACTION_NMETRANS);
		}

	}
	else if( (type >= SPELL_BALL_NORMAL && type <= SPELL_BALL_CRYSTAL) || (type == SPELL_BOOMERANG) || (type == SPELL_CANCERBALL))
	{
//		pickupFireSpell(type,&pos,GloveCtrl.direction,&ballPos);
		pickupFireSpell(type,&pos,GloveCtrl.direction,NULL,SPELLACTION_BALLTRANS);
	}
	return 1;
}

// go through the powerup, & it fires off 4 spells, *upwards*

int pickupFirePickupSpell(int action,VECTOR *pos, int type, int duration)
{
	SPELLSTR *spell;
	VECTOR tpos;
	int i;

	tpos = *pos;
	tpos.vy -= 4096 * 10;	// put above the glove...

	for(i = 0; i < 4; i++)
	{
		spell = pickupFireSpell(type,&tpos,i * 1024,NULL,action);
		spell->angles[0] = 4096 - 0x3C0;
		spell->duration = duration;
	}
	return 1;
}


#define SPELL_TARGET_ACC 8000


int AngleHomer(int ang, int targ, int speed)
{
	int da;

	if(speed < 0)
		return targ;

	da  = (targ - ang) & 4095;
	if(da < 2048)
	{
		if(da < speed)
			ang += da;
		else
			ang += speed;
	}
	else
	{
		da = 4096 - da;
		if(da < speed)
			ang -= da;
		else
			ang -= speed;
	}
	return (ang & 4095);
}

int IntHomer(int val, int targ, int speed)
{
	if(speed < 0)
		return targ;

	if(val < targ)
	{
		val += speed;
		if(val > targ)
			val = targ;
	}
	else if(val > targ)
	{
		val -= speed;
		if(val < targ)
			val = targ;
	}
	return val;
}

int LimitVectorSize(VECTOR *vector)
{
	int i = 0;
	while(vector->vx > 26754 || vector->vx < -26754
	   || vector->vy > 26754 || vector->vy < -26754
	   || vector->vz > 26754 || vector->vz < -26754
	)
	{
		vector->vx = vector->vx >> 2;
		vector->vy = vector->vy >> 2;
		vector->vz = vector->vz >> 2;
		i+= 2;
	}
	return i;
}

// speed starts at 8
// Note  - some magnitude checks to see if the spboss missile crash is down to zero-magnitude vectors
void pickupHomer(VECTOR *pos, SHORT *angs, VECTOR *targ, SHORT turnrate, int speed) // d'oh
{
	VECTOR delta;
	int a,calc;
	int hyp;

	delta.vx = targ->vx - pos->vx;
	delta.vy = targ->vy - pos->vy;
	delta.vz = targ->vz - pos->vz;

//	printf("delta mag = %d\n",Magnitude(&delta));

	LimitVectorSize(&delta);

	if(delta.vx == 0 && delta.vz == 0)
		delta.vz = -10;

	a = calc_angle(delta.vx,delta.vz) & 4095;
	angs[1] = AngleHomer(angs[1],a,turnrate);
//	angs[1] = (angs[1] + spellturnrate) & 4095;


//	hyp = fast_sqrt(delta.vx * delta.vx + delta.vz * delta.vz) >> 16;
	calc=(delta.vx*delta.vx+delta.vz*delta.vz);
	if(calc >= 0)
	{
		FASTSQRT(hyp,calc);
	}
	else
	{
		hyp = 0;
	}
	hyp>>=16;

//	printf("dy,hyp = %d %d\n",delta.vy,hyp);

	if(hyp == 0 && delta.vy == 0)
	{
		delta.vy = 10;
	}

	a = calc_angle(delta.vy,hyp) & 4095;
	angs[0] = AngleHomer(angs[0],a,turnrate);

	delta.vz = rcos(angs[0]);
	delta.vy = rsin(angs[0]);

	delta.vx = (delta.vz *rsin(angs[1])) >> 12;
	delta.vz = (delta.vz *rcos(angs[1])) >> 12;

	pos->vx += delta.vx*speed;
	pos->vy += delta.vy*speed;
	pos->vz += delta.vz*speed;
}


void pickupUpdateSpells()
{
	VECTOR delta;
	VECTOR targ;
	int i;
	int speed;
	SPELLSTR *spell;

	for(i = 0, spell = & spells[0]; i < MAX_SPELLS; i++, spell++)
	{
		if(spell->timer)
		{
			spell->timer--;
			spell->turnrate++;		// so it never circles
			if(spell->timer < 450)
			{
				spell->turnrate+= 10;	// so it never slowly spirals
			}
			speed = 8 + spell->turnrate * 16/450;


			switch(spell->action)
			{
				case SPELLACTION_BALLTRANS:
					targ = ballPos;
					break;
				case SPELLACTION_GLOVETRANS:
					targ = glovePos;
					break;


				case SPELLACTION_NMETRANS:
					targ = ((ENEMYPOS *)(spell->target))->pos;
					break;

				case SPELLACTION_RUNOUT:
					targ.vx = spell->pos.vx + (rsin(spell->angles[1])<<8);
					targ.vy = spell->pos.vy;
					targ.vz = spell->pos.vz + (rcos(spell->angles[1])<<8);
					break;
			}

/*
			delta.vx = spell->targ->vx - spell->pos.vx;
			delta.vy = spell->targ->vy - spell->pos.vy;
			delta.vz = spell->targ->vz - spell->pos.vz;
*/
			delta.vx = targ.vx - spell->pos.vx;
			delta.vy = targ.vy - spell->pos.vy;
			delta.vz = targ.vz - spell->pos.vz;
			New_Debris(DEBRIS_SPELLDUST,&spell->pos,spell->tailcolour);

			if (  delta.vx < ballRadius+(speed<<12) && delta.vx > - ballRadius-(speed<<12)
			   && delta.vy < ballRadius+(speed<<12) && delta.vy > - ballRadius-(speed<<12)
			   && delta.vz < ballRadius+(speed<<12) && delta.vz > - ballRadius-(speed<<12)
			)
			{

// okay, we've arrived...
				spell->timer = 0;
				switch(spell->action)
				{
					case SPELLACTION_BALLTRANS:
						//ballHitBySpell(spell->type,200);	// ,spell->duration);
						sfxPlay3D(globalFX,SFX_COLLECT_POWERUP_4,&spell->pos);
						ballHitBySpell(spell->type,spell->duration);
						{
							int j;
							SPELLSTR *spell2;

							for(j = 0, spell2 = & spells[0]; j < MAX_SPELLS; j++, spell2++)
							{
								if(spell->type == spell2->type && spell->action == spell2->action)
								{
									spell2->timer = 0;
								}
							}
						}
						break;

					case SPELLACTION_GLOVETRANS:
//						ballHitBySpell(spell->type);

						sfxPlay3D(globalFX,SFX_COLLECT_POWERUP_4,&spell->pos);
						handGivePlayerPower(spell->type,spell->duration);	//200);

// prevent the remainder of the chasers from the same spell from doing anything...
						{
							int j;
							SPELLSTR *spell2;

							for(j = 0, spell2 = & spells[0]; j < MAX_SPELLS; j++, spell2++)
							{
								if(spell->type == spell2->type && spell->action == spell2->action)
								{
									spell2->timer = 0;
								}
							}
						}
						break;


					case SPELLACTION_NMETRANS:
						sfxPlay3D(globalFX,SFX_COLLECT_POWERUP_2,&spell->pos);

						if(spell->type == SPELL_FROGGY)
						{
							EnemyFroggify((ENEMYPOS *)spell->target);
						}
						else if(spell->type == SPELL_DEATH)
						{
							EnemyKill((ENEMYPOS *)spell->target);
						}
						else if(spell->type == SPELL_PIRATES_UNFROG)
						{
							ENEMYPOS * enemy = (ENEMYPOS *)spell->target;

							enemy->pos.vy -= 20 << 12;
							enemy->vel.vy = -10 << 12;
							sfxPlay(globalFX,SFX_CAST_SPELL_5);
							effectsStartOverlay(20, 0x00ff00);
							enemy->doing = 0;
							enemy->ticker = 0;

							enemy->flags &= ~(NMEFLAG_FROGGED);



							enemy->psa =	babySpankModel->psa;
							enemy->animinfo.segInfo = babySpankModel->seg;
							enemy->anim.animInfo = &enemy->animinfo;

							ClearAnimQueue(&enemy->anim);
							AddToQueue(&enemy->anim,NMEANIM_WALK,YES,NO,4096);




//	{CAMEO_NME_MOVETO,		  PI_SPELL2_T+35,10,0,	{12, 133,MONKEY_Y,-150, -1}},
//	{CAMEO_PI_MONKEY,		  PI_SPELL2_T+35,20,0,	{12}},
//	{CAMEO_SFX,				  PI_SPELL2_T+35, 1,0, {13,0,SFX_CAST_SPELL_5,0}},
//	{CAMEO_NME_DOING,		  PI_SPELL2_T+35, 1,0,	{12,0}},
//	{CAMEO_FX_FLASH,		  PI_SPELL2_T+35,20,0,	{0,255,0}},

						}
						break;

					case SPELLACTION_RUNOUT:
						break;
				}

			}
//			pickupHomer(&spell->pos, &spell->angles[0], spell->targ, spell->turnrate);
			pickupHomer(&spell->pos, &spell->angles[0], &targ, spell->turnrate,speed);
		}
	}
}




// =================== Aiming tool effect ==============================

/*
// we need a copy of the ball's colldata,
// with pointers pointing to our local stuff...
typedef struct tagCOLLDATA
{
	int nBoxes;
//	int bounce;
	int radius;
	COLLBOX *pCollBox;
	VECTOR *pVel;
	VECTOR *pPos;
	VECTOR *pRot;	// changed from FVECTOR
	int nHits;
	int nHitPlats;
	HITPLAT hitPlats[MAXPLATHITS];

	BEHAVIOUR_PHYSICS	*physics;
	int		physicsModel;
	ULONG	dbug;
	// internal use only
	VECTOR	oldPos;
	VECTOR	oldRot;
	VECTOR	oldVel;
	VECTOR	preVel;
	VECTOR	hitPos;	// changed from FVECTOR
	int lastHit;
}COLLDATA;
*/

COLLDATA effectsAimColl;
VECTOR effectsAimVel;
VECTOR effectsAimPos;
VECTOR effectsAimRot;
int effectsAimAction = 0;
int effectsAimTimer = 0;

void effectsStartAimTool(int action)
{
	VECTOR tempVect;

	effectsAimAction = action;

	effectsAimColl = ballColl;
	effectsAimColl.pVel = &effectsAimVel;
	effectsAimColl.pPos = &effectsAimPos;
	effectsAimColl.pRot = &effectsAimRot;
	switch(action)
	{
		case 1:	// whack
			tempVect.vz = -BALLTHROWSPEED*2;
			tempVect.vx = 0;
			RotateVector2D(&tempVect, &tempVect,GloveCtrl.direction );	
			effectsAimVel.vx = tempVect.vx;
			effectsAimVel.vy = 9*4096;
			effectsAimVel.vz = tempVect.vz;
			break;

		case 3:	// lob
			tempVect.vz = LOBDISTANCE;
			effectsAimVel.vy = LOBHEIGHT;
			tempVect.vx = 0;
			RotateVector2D(&tempVect, &tempVect,(GloveCtrl.direction+2048)&4095 );
			effectsAimVel.vx = tempVect.vx;
			effectsAimVel.vz = tempVect.vz;
			break;
		default:
		case 2:	// throw...
			tempVect.vz = -BALLTHROWSPEED;
			effectsAimVel.vy = GRAVITY*14;
			tempVect.vx = 0;
			RotateVector2D(&tempVect, &tempVect,GloveCtrl.direction );
			effectsAimVel.vx = tempVect.vx;
			effectsAimVel.vz = tempVect.vz;
			break;
	}



	effectsAimPos = ballPos;
	effectsAimRot = ballRot;
	effectsAimTimer = 0;
}

#define AIMTOOL_SPEED 2
#define AIMTOOL_MAXLENGTH 50

void effectsUpdateAimTool()
{
	VECTOR	tempV;
	int col;
	int i;

	for(i = 0; i < AIMTOOL_SPEED; i++)
	{
		effectsAimTimer++;

		switch(effectsAimAction)
		{
			case 1:	// whack
				col = 0x008080;
				break;
			case 3:	// lob
				col = 0x108000;
				break;
			default:
			case 2:	// throw
				col = 0x307080;
				break;
		}
		New_Debris(DEBRIS_AIMBLOB,&effectsAimPos,col);


		tempV.vx=effectsAimVel.vx;
		tempV.vy=0;
		tempV.vz=effectsAimVel.vz;

		// DRAG

		effectsAimVel.vx=(effectsAimVel.vx* BallBehaviour[BallCtrl.type].drag)/4096;
		effectsAimVel.vy=(effectsAimVel.vy*(BallBehaviour[BallCtrl.type].drag+4096)/2)/4096;
		effectsAimVel.vz=(effectsAimVel.vz* BallBehaviour[BallCtrl.type].drag)/4096;

		effectsAimVel.vy-=gravity;
		ADDVECTOR(&effectsAimPos, &effectsAimPos, &effectsAimVel);
		effectsAimPos.vy-=(effectsAimVel.vy*2);


	//	New_Debris(DEBRIS_WISP,&effectsAimPos,0);


		if (collboxCheckSphere(&effectsAimColl))
		{
			New_Debris(DEBRIS_BLUESTAR,&pHitData[0].pos,0);

	// sick an impact effect there.
			if(effectsAimAction)
				effectsStartAimTool(effectsAimAction);
			break;	// quit the "i->aimtool_speed" loop
		}

		if(effectsAimTimer >= AIMTOOL_MAXLENGTH)
		{
			effectsStartAimTool(effectsAimAction);
		}
	}
}

void effectsAimToolStuff()
{
	static short old_action = 0;
//	static char old_bong = 0;
//	static char old_gong = 0;
	static char glove_air_timer = 0;
	static char ball_air_timer = 0;
	VECTOR pos;

	short action = GloveCtrl.action;

	if(action == HAND_THROWAIM || action == HAND_PREWHACK)
	{
		if(action != old_action)
		{
//			printf("aim tool start\n");
			if(action == HAND_THROWAIM)
				effectsStartAimTool(2);
			else
				effectsStartAimTool(1);
		}
// otherise leave the aim tool running
//		printf("aim tool running\n");
		effectsUpdateAimTool();

	}
	else
	{
//		printf("aim tool off\n");
		effectsAimAction = 0;
	}


//	if(GloveCtrl.onGround && !old_gong && (gloveVel.vy > 4096 || gloveVel.vy <-4096))
	if(GloveCtrl.onGround)
	{
		if(glove_air_timer > 5)
		{
			pos = glovePos;
			pos.vy += 10 * 4096;
			pos.vx -= 16 * 4096;
			New_Debris(DEBRIS_DUST,&pos,DUST_COLOUR);
			pos.vx += 32 * 4096;
			New_Debris(DEBRIS_DUST,&pos,DUST_COLOUR);
			pos.vx -= 16 * 4096;
			pos.vz -= 16 * 4096;
			New_Debris(DEBRIS_DUST,&pos,DUST_COLOUR);
			pos.vz += 32 * 4096;
			New_Debris(DEBRIS_DUST,&pos,DUST_COLOUR);
			sfxPlay3D(globalFX,SFX_HAND_LAND,&glovePos);
		}
		glove_air_timer = 0;
	}
	else
	{
		if(glove_air_timer < 20)
			glove_air_timer++;
	}

	if(BallCtrl.ballOnGround)
	{
		if(ball_air_timer > 5)
		{
			ballPlayBounceEffect();
			pos = ballPos;
			pos.vy += ballRadius;
			New_Debris(DEBRIS_DUST,&pos,DUST_COLOUR);
		}

		if(BallCtrl.type==BALL_MODE_CRYSTAL
			&& RANDOM256() > 220
			&& Magnitude(&ballVel) > 4096 * 2
			)
		{
			ballPlayBounceEffect();// make the xtal ball rattle a bit
		}
		ball_air_timer = 0;
	}
	else
	{
		if(ball_air_timer < 20)
			ball_air_timer++;
	}

//	old_gong = GloveCtrl.onGround;
//	old_bong = BallCtrl.ballOnGround;

	old_action = GloveCtrl.action;
}



// ======== glove orbiter ==========
//extern int handpower_duration;
//extern int handpower_timer;
//extern int handpower_type;


void handPowerUpdate2()
{
	VECTOR pos;
	int ballspell = 0;
	int r;


// Extra life at 10K points
	{
		static int oldscore = 0;

		if(gameScore / 10000 > oldscore / 10000 && frame > 2)	// to allow for loadgames where the score starts high
		{
			GloveCtrl.lives++;
			sfxPlay3D(globalFX,SFX_HAND2_VICTORY1,&pos);
			sfxPlay3D(globalFX,SFX_COLLECT_POWERUP_1,&pos);
		}
		oldscore = gameScore;
	}


	if (BallCtrl.boomerangOn)
	{
/*
		//printf ("ball in hand %d\n",GloveCtrl.ballWithHand);
		if (GloveCtrl.action==HAND_JOINED || GloveCtrl.action==HAND_BALLWALK || GloveCtrl.action==HAND_BOUNCE )
		{
			BallCtrl.boomerangTime=0;
			BallCtrl.boomerangOn=FALSE;
		}

		if (BallCtrl.boomerangActiveTime) BallCtrl.boomerangActiveTime--;
*/
		ballspell = 1;
		r = 16;
	}
	if(BallCtrl.type == BALL_MODE_BEACH)
	{
		ballspell = 1;
		r = 30;
	}


	if(ballspell)
	{
		pos.vx = (ballPos.vx) + rsin((sinewave1>>16) & 4095) * 20;
		pos.vy = (ballPos.vy) + rsin((sinewave2>>16) & 4095) * 15;
		pos.vz = (ballPos.vz) + rcos((sinewave1>>16) & 4095) * 20;
		New_Debris(DEBRIS_LIFEGLOW,&pos,0);
	}



 	pGlovePSA->globalscale.vx=(400*NORMAL_SCALE);
 	pGlovePSA->globalscale.vy=(400*NORMAL_SCALE);
 	pGlovePSA->globalscale.vz=(400*NORMAL_SCALE);
	GloveCtrl.radius=GLOVENORMALRADIUS;

	if(!handpower_timer)
		return;

// this'll screw up the crawl

	if(handpower_type == SPELL_HERCULES)
	{
 		pGlovePSA->globalscale.vx=(600*NORMAL_SCALE);
 		pGlovePSA->globalscale.vy=(600*NORMAL_SCALE);
 		pGlovePSA->globalscale.vz=(600*NORMAL_SCALE);
		GloveCtrl.radius=GLOVENORMALRADIUS * 3/2;
	}
	else if (handpower_type == SPELL_SPEEDUP)
	{
		if (GloveCtrl.speed>WALKSPEED && !(activeframe & 3))
		{
			sfxPlay3D(globalFX,SFX_GE_FUMBLE_PANTING_LOOP,&glovePos);
		}
	}
	else if(handpower_type == SPELL_ROTORBLADES && !(activeframe % 5))
	{
		sfxPlay3D(globalFX,SFX_GE_ROTORBLADE,&glovePos);
	}



	pos.vx = (glovePos.vx) + rsin((sinewave1>>16) & 4095) * 20;
	pos.vy = (glovePos.vy) + rsin((sinewave2>>16) & 4095) * 15;
	pos.vz = (glovePos.vz) + rcos((sinewave1>>16) & 4095) * 20;
	New_Debris(DEBRIS_LIFEGLOW,&pos,0);


}

void handPowerDraw()
{
	NEWMODEL *psa;
	int ttube_colour;
	POLY_G4 	*si;
	int x;
	int y;
	int r;


	if(BallCtrl.powerwhack)
	{
		New_Debris(DEBRIS_SPELLDUST,&ballPos,0xc08040);
	}
	if(BallCtrl.boomerangOn || BallCtrl.type == BALL_MODE_BEACH)
	{

		if(BallCtrl.boomerangOn)
		{
			psa = orbiterpsas[SPELL_BOOMERANG];
			r = 16;
		}
		else
		{
			psa = orbiterpsas[SPELL_BALL_BEACH];
			r = 30;
		}
		if(psa)
		{
			psa->position.vx = ((ballPos.vx) + rsin((sinewave1>>16) & 4095) * r)>>12;
			psa->position.vy = ((ballPos.vy) + rsin((sinewave2>>16) & 4095) * 4)>>12;
			psa->position.vz = ((ballPos.vz) + rcos((sinewave1>>16) & 4095) * r)>>12;
			psa->globalscale.vx=1000;
			psa->globalscale.vy=1000;
			psa->globalscale.vz=1000;

			objectSetAnimation(psa,0);
			objectDraw(psa);
		}
	}


//	printf("hpt = %d\n",handpower_type);
	if(!handpower_timer || handpower_type > NUM_SPELLS)
		return;

// Draw the panel test tube
	ttube_colour = ttube_colours[handpower_type];
	x = -210;
//	y = 0;
	y = -20;

  	gte_SetRotMatrix(&GsWSMATRIX);
  	gte_SetTransMatrix(&GsWSMATRIX);


	si = (POLY_G4 *) GsOUT_PACKET_P; 
	if (TOOMANYPOLYS(5*MAXPACKETSIZE,"ttube"))
		return;

	si->x0 = x - 10;
	si->y0 = y+30 - 60 * handpower_timer / handpower_duration;
	si->x1 = x + 7;
	si->y1 = y+30 - 60 * handpower_timer / handpower_duration;
	si->x2 = x - 10;
	si->y2 = y+30;
	si->x3 = x + 7;
	si->y3 = y+30;

	*(ULONG *)(&si->r0) = ttube_colour | (GPU_COM_G4 << 24);
	*(ULONG *)(&si->r2) = ttube_colour;
	si->r1 = si->r3 = si->r0 >> 1;
	si->g1 = si->g3 = si->g0 >> 1;
	si->b1 = si->b3 = si->b0 >> 1;

	PUTPACKETINTABLE(si, (GsOTA*)(PolyList->org) + (10 >> PolyList->shift), POLYG4_LEN);
	si++;
	(POLY_G4*)GsOUT_PACKET_P = si;

	spritePrint(SPR_tubebase,x,y,10);	// defined in effects.h/c at the moment

// tbd - Draw the orbiter





	psa = orbiterpsas[handpower_type];
	if(!psa)
		return;
	psa->position.vx = ((glovePos.vx) + rsin((sinewave1>>16) & 4095) * 16)>>12;
	psa->position.vy = ((glovePos.vy) + rsin((sinewave2>>16) & 4095) * 4)>>12;
	psa->position.vz = ((glovePos.vz) + rcos((sinewave1>>16) & 4095) * 16)>>12;
	psa->globalscale.vx=1000;
	psa->globalscale.vy=1000;
	psa->globalscale.vz=1000;

	objectSetAnimation(psa,0);
	objectDraw(psa);


}


