#include "glover.h"
#include "puzzles.h"
#include "cameo.h"
#include "bosses.h"
//#include "types.nh"

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

void CymonIntRou(int with, ENEMYPOS *enemy, VECTOR *dpos, int sphererad);
void SuckIntRou(int with, ENEMYPOS *enemy, VECTOR *dpos, int sphererad);
void NMEPushedBySphere(ENEMYPOS *nme, VECTOR *spherepos, int total_rad);
void ChickIntRou(int with, ENEMYPOS *enemy, VECTOR *dpos, int sphererad);
void CluckPushOffBall(ENEMYPOS *nme);
void enemySideDamp(ENEMYPOS *enemy, int fract);

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

#if GOLDCD == NO
//	#define ENEMY_SHOW_VIS_SPHERES
//	#define ENEMY_SHOW_COLL_SPHERES
#endif

// Gameplay modification to prevent any enemies from doing damage to the ball
#define BALL_HURTPROOF

#define BALL_STUN_KILL

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

//char cheat_all_hoops = 1;
char cheat_any_ball_powerwhacks = 0;

ENEMYPOS *current_enemy;
SPRITEX *SPR_mrtipbase;


//int nme_positions_read = 0;

ENEMYPOS *nme_list;
ENEMYPOS *nme_list_tail;

ENEMYPOS *ca_pickup_nme[2];

#define TEMP_ENEMY_MAX 60
//int temp_enemy_counter;

int enemy_has_ball;

extern void puzzleDrawBbox(SVECTOR *pos, SVECTOR *size);

void Update_Mallet(ENEMYPOS *enemy);
void Update_GeneralWu(ENEMYPOS *enemy);
void Update_Lionfish(ENEMYPOS *enemy);

void Update_Denis(ENEMYPOS *enemy);
void Update_Cluck(ENEMYPOS *enemy);
void Update_Bovva(ENEMYPOS *enemy);
void Update_Bugle(ENEMYPOS *enemy);

void Update_Swish(ENEMYPOS *enemy);
void Update_Reggie(ENEMYPOS *enemy);
void Update_Cannon(ENEMYPOS *enemy);
void Update_Chester(ENEMYPOS *enemy);


void Update_Robes(ENEMYPOS *enemy);
void Update_Samtex(ENEMYPOS *enemy);
void Update_Fumble(ENEMYPOS *enemy);
void Update_Mike(ENEMYPOS *enemy);
void Update_Knight(ENEMYPOS *enemy);

void Update_Crumpet(ENEMYPOS *enemy);
void Update_Raptor(ENEMYPOS *enemy);
void Update_Tracey(ENEMYPOS *enemy);

void Update_YooFow(ENEMYPOS *enemy);
void Update_Cymon(ENEMYPOS *enemy);
void Update_Sucker(ENEMYPOS *enemy);
void Update_Opec(ENEMYPOS *enemy);

void Update_Pickup(ENEMYPOS *enemy);
void Update_Dibber(ENEMYPOS *enemy);
void Update_LittleFish(ENEMYPOS *enemy);
void Update_Rnd3dMover(ENEMYPOS *enemy);
void Update_MrTip(ENEMYPOS *enemy);
void Update_Rnd3dGroundMover(ENEMYPOS *enemy);

void Update_NMEFrog(ENEMYPOS *nme);

void Update_Hubchick(ENEMYPOS *enemy);

void Update_Portal(ENEMYPOS *enemy);
void Update_Hoop(ENEMYPOS *enemy);
void Update_Vent(ENEMYPOS *enemy);
void Update_Teleport(ENEMYPOS *enemy);


void Update_Sting(ENEMYPOS *enemy);
void Update_Egg(ENEMYPOS *nme);

int PowerWhackHandler(ENEMYPOS *nme, int stunfirst);
void SuckerDropsBall(ENEMYPOS *enemy);
void CanIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad);


LOADEDMODEL *nmeFrogModel;
LOADEDMODEL *babySpankModel;
ENEMYPOS *bovva_sting_enemy;
ENEMYPOS *chuck_egg_enemy;
SPRITEX  *chuck_egg_sprite = NULL;
SPRITEX  *chuck_splat_sprite = NULL;
SPRITEX  *hoop_sprite = NULL;
SPRITEX  *tracy_heart_sprite = NULL;

SPRITEX  *joff_spray_sprite = NULL;


NEWMODEL *robot_missile_psa;
NEWMODEL *robot_laser_psa;
//NEWMODEL *robot_spider_psa;
LOADEDMODEL *robot_spider_model;
LOADEDMODEL *robot_lsho_model;
LOADEDMODEL *robot_lgun_model;
LOADEDMODEL *robot_rsho_model;
LOADEDMODEL *robot_rgun_model;
//LOADEDMODEL *robot_model;

LOADEDMODEL *bugleInsideModel;
LOADEDMODEL *bugleOutsideModel;


void Update_Flame(ENEMYPOS *nme);
void Update_Steam(ENEMYPOS *nme);
void Update_UfoZap(ENEMYPOS *nme);

//DYNCOLLBOX *pBugleCollBox;

int numberofhoops;
int lasthoopused = 0;
int cameo_tag_counter;

char ball_at_hoop = 1;
extern ENEMYPOS *enemies_CreateEnemy(int type);

SVECTOR pathvector_buffer[NME_MAX_PATHVECTORS];	// converted to big daddy vectors later

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



// if spheres is NULL, then the enemy is assumed to be...
// vector = zero
// radius = equal to the display sphere
// type   = taken from the enemy flags


// enemy flags:-
// hurt_by_slam (dibber,mallet,reggie, *conditionally*:cymon,selwynfish/kirk)


#define BLASTRING_STUNS		1
#define BLASTRING_KILLS		2
#define NME_SPELLABLE		4
#define NME_NO_INTER		8
#define BLASTRING_SPECIAL	16
#define NME_ONEANIM			32
#define NO_RESPAWN			64
#define STARTS_INACTIVE		128
#define DROPS_GARIB			256
// NULL respawn means nothing changes on respawn - it keeps doing what it was doing
#define NULL_RESPAWN		512
#define XYZ_PHYSICS			1024
#define TEMP_NME			2048



// hub critters don't interact at all - they catch the blast ring info rather than scanning for glove position ?
// (ummm, that'd mean they can't be squished with the bowling ball, though. Ooops. Okay, we need a FeebleRou
// (note - on the n64, feeble enemies are bowling-ball proof)

// FeebleRou is for hubcritters
// PushableRou is for meek, pushable, enemy sphere
// SplatRou    is for solid & painful, but splattable with a fistslam
// HardBastRou is for solid (permanantly) and painful when not stunned


NME_SPHERE GenWuSpheres[2] =
{
	{&GenWuSpheres[1],	PushableRou,{  0,  30,  -22},   15, 0},
	{NULL,				HardBastRou,{  0, -12,  -25},   25, 256+0}
};

NME_SPHERE ChuckSpheres[3] =
{
	{&ChuckSpheres[1],	CluckIntRou,{  0,  -30, 0},   10, -1},
	{&ChuckSpheres[2],	CluckIntRou,{  0,  -10, 0},   15, -1},
	{NULL,				CluckIntRou,{  0,   10,  0},   15, -1}
};

NME_SPHERE ChickSpheres[1] =
{
	{NULL,				ChickIntRou,{  0,   -25,  0},   35, 1}
};


NME_SPHERE SamSpheres[2] =
{
	{&SamSpheres[1],	HardBastRou,{  0,  -10, 0},   15, -1},
	{NULL,				HardBastRou,{  0,   10, 0},   15, -1}
};

NME_SPHERE MalletSpheres[1] =
{
	{NULL,				SplatRou,	{  0,   0,  -5},	15, -1}
};
NME_SPHERE ChestSpheres[1] =
{
	{NULL,				SplatRou,	{  0,   0,   0},	23, -1}	// smaller that the full thing
};
NME_SPHERE RegSpheres[1] =
{
	{NULL,				SplatRou,	{  0,   0,   0},	20, -1}
};

NME_SPHERE PortalSpheres[1] =
{
	{NULL,				PortIntRou,	{  0,   -15,  0},	25, -1}
};

NME_SPHERE DennisSpheres[2] =
{
	{&DennisSpheres[1],	DenisBintRou,{  0,   0,  0},   20, -1},
	{NULL,				DenisTintRou,{  0, -20,  0},   15, -1}
};

NME_SPHERE SwishSpheres[3] =
{
	{&SwishSpheres[1],	SwishIntRou,{  0,   0,  0},   12, 1},
	{&SwishSpheres[2],	SwordIntRou,{  0, -8,  0},   15, 256+2},
	{NULL,				SwordIntRou,{  0,  15,  0},   10, 256+2}
};


// this guy is gonna need two spheres
// the spikey thing is shape 13
// and the point rotater doesn't work properly yet...

// tbd - knight's "13" sphere doesn't work
NME_SPHERE KnightSpheres[2] =
{
//	{NULL,					HardBastRou,{  0,   0,  0},   20, 0}
	{&KnightSpheres[1],		HardBastRou,{  0,   0,  0},   20,  0},
	{NULL,					HardBastRou,{  0,   0,  0},   12,  256+5}
};


NME_SPHERE MikeSpheres[2] =
{
	{&MikeSpheres[1],		HardBastRou,{  0,  10,-10},   24,  2},
	{NULL,					HardBastRou,{  0,  10,-10},   20,  256+1}
};

NME_SPHERE FumbSpheres[1] =
{
	{NULL,		FumbIntRou,{  0,  10,-10},   20,  -1}
};


NME_SPHERE SuckSpheres[2] =
{
	{&SuckSpheres[1],		SuckIntRou,{  0,  10,   5},   12,  -1},
	{NULL,					SuckIntRou,{  0,  -5,   0},   18,  1}
};


/*
NME_SPHERE RapSpheres[1] =
{
	{NULL,		HardBastRou,{  0,   0,  0},   20, -1}
};
*/
NME_SPHERE RapSpheres[1] =
{
	{NULL,		HardBastRou,{  0,   0,  0},   25, -1}
};
NME_SPHERE CrumSpheres[1] =
{
	{NULL,		SplatRou,{  0,   0,  0},   13, -1}
};

void TracIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad);


NME_SPHERE TracSpheres[2] =
{
	{&TracSpheres[1],		TracIntRou,{  0,   0,  0},   32, 3},
	{NULL,					TracIntRou,{  0, -10,  0},   24, 256+4},
};

//  fname,		scale,r,hag,visdist, n_paths, flags,								spheres,			interact,	update
ENEMYBEHAVIOUR nmeinfo[] =
{
	{NULL,		 256, 50,  0,1500,	 2,0,										NULL,				NULL,		NULL},			//0 camera
	{NULL,		 256, 50,  0,1500,	 2,0,										NULL,				NULL,		NULL},			//1 hand
	{NULL,		 256, 50,  0,1500,	 2,0,										NULL,				NULL,		NULL},			//2 ball
	{NULL,		 256, 50,  0,1500,	 2,0,										NULL,				NULL,		NULL},			//3 debris
	{NULL,		 256, 50,  0,1500,	 2,0,										NULL,				NULL,		NULL},			//4 bullet
	{NULL,	     256, 50,  0,1500,	 2,0,										NULL,				NULL,		NULL}, 			//5 sneezy
	{NULL,	     256, 50,  0,1500,	 2,0,										NULL,				NULL,		NULL}, 			//6 moocow
	{"BOVVA",	 256, 25,  0,1200,	 3,NME_SPELLABLE,							NULL,				BovIntRou,	Update_Bovva},			//7 (CA)

// cannon uses pathvectors as a quaternion store
	{"CANNON",	 256, 16,  0,1200,	 3,BLASTRING_SPECIAL|NME_SPELLABLE,			NULL,				CanIntRou,	Update_Cannon},			//8 (PI)
	{"SAMTEX2",	 300, 30, 30,1200,	 3,BLASTRING_STUNS|NME_SPELLABLE,			&SamSpheres[0],		NULL,		Update_Samtex},	//9 "SAMTEX", (FF)
	{"MALLET",	 256, 20, 10,1000,	 2,BLASTRING_STUNS|NME_SPELLABLE|DROPS_GARIB,&MalletSpheres[0],	NULL,		Update_Mallet},			//10	(AT)
	{"GENERALW", 256, 80, 20,1200,	 3,BLASTRING_STUNS|NME_SPELLABLE,			&GenWuSpheres[0],	NULL,		Update_GeneralWu},			//11	(AT)
	{"LIONFISH", 256, 22,  0,1000,	 2,BLASTRING_STUNS|NME_SPELLABLE,			NULL,				SplatRou,	Update_Lionfish},			//12	(AT)
	{"CHESTER",	 256, 42, 13,1200,	 1,BLASTRING_STUNS|NME_SPELLABLE,			&ChestSpheres[0],	NULL,		Update_Chester},			//13 (PI)
	{NULL,		 256, 50,  0,1200,	 0,0,										NULL,				NULL,		NULL},			//"KEG",		//14
	{"REGGIE",	 256, 30,  0,1200,	-1,BLASTRING_STUNS|NME_SPELLABLE|DROPS_GARIB,&RegSpheres[0],	SplatRou,	Update_Reggie},			//15 (PI)

	{"SWISH",	 400, 38, 12,1200,	-1,BLASTRING_STUNS|NME_SPELLABLE,			&SwishSpheres[0],	SwishIntRou,Update_Swish},			//16 (PI)
	{"THRICE",	 256, 50,  0,1200,	 0,BLASTRING_STUNS|NME_SPELLABLE,			&KnightSpheres[0],	NULL,		Update_Knight},			//17 "THRICE"/knight (FF)
	{"EVILROBE", 400, 50,  0,1200,	 3,NME_SPELLABLE,							NULL,				NULL,		Update_Robes},			//18 (FF) "ROBES"
	{"FUMBLE",	 256, 25, 15,1200,	 3,BLASTRING_SPECIAL|NME_SPELLABLE,			&FumbSpheres[0],	FumbIntRou,	Update_Fumble},			//19 "FUMBLE" (FF)
	{"MIKE",	 256, 50, 18,1200,	 3,NME_SPELLABLE,							&MikeSpheres[0],	NULL,		Update_Mike},			//20 "MIKE" (FF)
	{"RAPTOR",	 400, 30, 20,1200,	 3,NME_SPELLABLE | BLASTRING_STUNS,			&RapSpheres[0],		NULL,		Update_Raptor},			//21 "RAPTOR" (PR) 
	{"CRUMPET",	 256, 20,  0,1200,	-1,NME_SPELLABLE,							&CrumSpheres[0],	SplatRou,	Update_Crumpet},			//22 "CRUMPET" (PR)
	{"TRACEY",	 256, 50, 40,1200,	 0,NME_SPELLABLE,							&TracSpheres[0],	NULL,		Update_Tracey},			//23 "TRACEY" (PR)

	{"YOOFOW",	 256, 23,  0,1200,	-1,NME_SPELLABLE,							NULL,				SplatRou,	Update_YooFow},			//24 "YOOFOW" (SP),			//24		     
	{"OPEC",	 256, 15,  15,1200,	-1,NME_SPELLABLE,							NULL,				OpecIntRou,	Update_Opec},			//25 "OPEC" (SP),				//25		     
	{"CYLON",	 256, 25, 15,1200,	-1,NME_SPELLABLE | BLASTRING_STUNS,			NULL,				CymonIntRou,Update_Cymon},			//26 "CYMON" / cylon (SP),			//26		     
	{"SUCKER",	 256, 35, 20,1200,	 3,NME_SPELLABLE | BLASTRING_SPECIAL,		&SuckSpheres[0],	SuckIntRou,	Update_Sucker},			//27 "SUCKER" (SP),			//27		     
//	{"BUGLE",	 256, 40,  0,1200,	 3,NME_SPELLABLE,							NULL,				BugleIntRou,Update_Bugle},			//28	(CA)
	{"BUGLEI",	 256, 40,  0,1200,	 3,NME_SPELLABLE,							NULL,				BugleIntRou,Update_Bugle},			//28	(CA)
	{"DENNIS",	 256, 45, 20,1200,	 3,NME_SPELLABLE,							&DennisSpheres[0],	NULL,		Update_Denis},			//29	(CA)
	{"CHUCK",	 256, 50, 20,1200,	 3,NME_SPELLABLE | BLASTRING_SPECIAL,		&ChuckSpheres[0],	CluckIntRou,Update_Cluck},			//30	(CA)
//	{"HUBCHICK",1024, 150,  0,1200,	 0,NME_SPELLABLE,							NULL,				ChickIntRou,Update_Hubchick},		// 31 = the hub chicken	//"FRANKIE",			//31
	{"HUBCHICK",1024, 150,  0,1200,	 0,NME_SPELLABLE,							&ChickSpheres[0],	ChickIntRou,Update_Hubchick},		// 31 = the hub chicken	//"FRANKIE",			//31

//	{"GRAHAM",	 300, 80,  0,1200,	 3,NULL_RESPAWN | BLASTRING_STUNS,			&FrankSpheres[0],	NULL,		Update_Frank},			//32 (FFboss - frankie/graham)
	{"GRAHAM",	 300, 80,  0,1200,	 3,NULL_RESPAWN,							&FrankSpheres[0],	NULL,		Update_Frank},			//32 (FFboss - frankie/graham)
	{"KLOSET",	 256, 60,  0,1200,	 3,NULL_RESPAWN,							&KlosSpheres[0],	NULL,		Update_Kloset},			//33 (CAboss)
	{"WILLY",	2048,180,  0,2000,	 3,NULL_RESPAWN,							&WillySpheres[0],	NULL,		Update_Willy},			//34 (PRBoss)	{NULL,		256,50},			//"BRUNDLE",		//34
	{"JOFF",	 256, 50, 35,1200,	 4,NULL_RESPAWN,							&JoffSpheres[0],	NULL,		Update_Joff},			//35 (ATboss),selwyn whale
	{"CANCER", 	 256, 50,  0,1200,	 4,BLASTRING_SPECIAL+NULL_RESPAWN,			&CancerSpheres[0],	NULL,		Update_Cancer},			//36 (ATboss),selwun crab
	{"KIRK",	 256, 40,  0,1200,	 4,NULL_RESPAWN,							&KirkSpheres[0],	NULL,		Update_Kirk},			//37 (ATboss), selwyn fish
	{NULL,		 256, 50,  0,1200,	 4,0,										NULL,				SplatRou,	NULL},			//"ROBOT",			//38
	{"EVILROBOT",EVIL_SCALE,
					300*EVIL_SCALE/2048,
						   0,12000,	 4,NULL_RESPAWN,							&EvilRSpheres[0],	NULL,		Update_EvilBot},			//"EVIL ROBOT",			//39

	{"SPANK",	  64,100,  0,1200,	 3,NULL_RESPAWN,							&SpankSpheres[0],	NULL,	Update_Spank}, 		//40 (PI)
	{"BABYSPK2", 200, 50,  0,1200,	 3,NULL_RESPAWN | BLASTRING_SPECIAL,		&BabySpankSpheres[0],	NULL,	Update_BabySpank},			//41 (PI)
	{"EVILGLOV", 200, 32,  0,2000,	 0,NO_RESPAWN,								NULL,				SplatRou,	NULL},			//42
	{"DIBBER",	 512, 15,  0,1000,	-1,NME_SPELLABLE | BLASTRING_KILLS|DROPS_GARIB,				NULL,				SplatRou,	Update_Dibber},			//43 is the current number for a dibber
	{"BRUNDLE",	 256, 15,  0, 700,	 3,0,										NULL,				FeebleRou,	Update_Rnd3dMover},			//44 = brundle/fly
	{"MALCOM",	 256, 15,  0, 700,	 3,BLASTRING_KILLS,							NULL,				FeebleRou,	Update_Rnd3dGroundMover},			// snail
	{NULL,		 256, 50,  0, 700,	 3,0,										NULL,				FeebleRou,	NULL},			//"SIDNEY" (bigfish)
	{"GORDON", 	 256,  8,  0, 700,	 3,0,										NULL,				FeebleRou,	Update_LittleFish},	  		// 47 gordon the goldfish

//	{"LADYBIRD", 512, 15, 10, 700,	 3,BLASTRING_KILLS,							NULL,				FeebleRou,	Update_Rnd3dGroundMover},		// 48 = Ladybird
	{"LADYBIRD", 512, 15, 0, 700,	 3,BLASTRING_KILLS,							NULL,				FeebleRou,	Update_Rnd3dGroundMover},		// 48 = Ladybird
	{"WEEVIL",	 256, 15, 10, 700,	 3,BLASTRING_KILLS,							NULL,				FeebleRou,	Update_Rnd3dGroundMover},		// 49 = weevil
	{"CHOPSTIK", 256, 15, 10, 700,	 3,0,										NULL,				FeebleRou,	Update_Rnd3dGroundMover},		// 50 = caterpillar	//50
	{"BUTTERFLY1",1024, 15, 0, 700,	 3,NME_ONEANIM,								NULL,				FeebleRou,	Update_Rnd3dMover},			//"51 = butterfly
	{"SPIDER",	 256, 15, 10, 700,	 3,BLASTRING_KILLS,							NULL,				FeebleRou,	Update_Rnd3dGroundMover},		// 51 = spider
	{"BAT",		 256, 15,  0, 700,	 3,0,										NULL,				FeebleRou,	Update_Rnd3dMover},			//"53 = bat
	{"NMEFROG",  512, 50,  0, 700,	 3,BLASTRING_KILLS | NULL_RESPAWN,			NULL,				HarmlessRou,Update_NMEFrog},			//54"BOXTHING",			//54
	{"DRAGON",	 1024, 15, 0, 700,	 3,NME_ONEANIM,								NULL,				FeebleRou,	Update_Rnd3dMover},			//55 = dragonfly

	{NULL,		 256, 50,  0,8000,	 2,0,										NULL,				NULL,		NULL}, 			//boxthing
	{NULL,		 256, 50,  0,8000,	 2,0,										NULL,				NULL,		NULL},			//bug
	{"NMEFROG",	 512, 20,  0,1200,	 3,BLASTRING_KILLS,							NULL,				HarmlessRou,Update_NMEFrog},
	{"MRTIP",	 256, 20,  0,1200,	 0,0,										NULL,				TipIntRou,	Update_MrTip},			//"Mr Tip",			//59
	//
	{"RBLADES",	1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup},			//STUN
	{"DEATH",	1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup},			//DEATH
	{"FROGSPEL",1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup},			//FROG - nb nb nb - not "frogspell" - 8 character name
	{"STICKY",	1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup},			//SUCTION      
	{"HERCULES",1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup},			//HERCULES     
	{"FAST",	1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup},			//SPEEDUP      
	{"BLADES",	1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup},			//ROTORBLADES
	{"BOWLING",	1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup}, 			//BOWLINGBALL    
	{"POWER",	1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup}, 			//POWERBALL
	{"BEARING",	1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup}, 			//BALLBEARING
	{"BEACH",	1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup}, 			//BEACHBALL
	{NULL,		1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup}, 			//SNOWBALL
	{NULL,		1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup}, 			//CRYSTALBALL
	{"BOOMER",	1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup}, 			//BOOMERANGBALL
	{"VANISH",	1024, 20,  0,1000,	 2,NME_NO_INTER,							NULL,				NULL,		Update_Pickup}, 			//VANISHBALL

	{"PORTAL",	4096, 20,  0,1200,	 2,NULL_RESPAWN,							&PortalSpheres[0],	PortIntRou,	Update_Portal},
	{NULL,		4096, 50,  0,1200,	 2,NULL_RESPAWN,							NULL,				HoopIntRou,	Update_Hoop},
	{NULL,		4096, 10,  0,1200,	 2,0,										NULL,				NULL,		Update_Vent},
	{NULL,		4096, 30,  0,1200,	 2,0,										NULL,				TeleIntRou,	Update_Teleport},
	{"STING",	 256, 10,  0,1200,	 2,STARTS_INACTIVE+NO_RESPAWN,				NULL,				StingIntRou,Update_Sting},
	{NULL,		 256, 10,  0,1200,	 2,STARTS_INACTIVE+NO_RESPAWN,				NULL,				EggIntRou,	Update_Egg},
	{NULL,		 256, 12,  0,1200,	 2,STARTS_INACTIVE+TEMP_NME,				NULL,				FBallIntRou,Update_Flame},
	{NULL,		 256, 12,  0,1200,	 2,STARTS_INACTIVE+TEMP_NME,				NULL,				NULL,		Update_Steam},
	{NULL,		 256,  8,  0,1200,	 2,STARTS_INACTIVE+TEMP_NME,				NULL,				UZapIntRou,	Update_UfoZap},
	{NULL,		 256,  8,  0,1200,	 2,STARTS_INACTIVE+TEMP_NME,				NULL,				SprayIntRou,Update_Spray},
	{"CLAW",	 256, 18,  0,1200,	 2,STARTS_INACTIVE+TEMP_NME,				NULL,				ClawIntRou,	Update_Claw},
	{"BUBBLE1",	1024, 12,  0,1200,	 2,STARTS_INACTIVE+XYZ_PHYSICS+TEMP_NME,	NULL,				Bub1IntRou,	Update_Bubble1},
	{"BUBBLE2",	1024, 18,  0,1200,	 2,STARTS_INACTIVE+XYZ_PHYSICS+TEMP_NME,	NULL,				Bub2IntRou,	Update_Bubble2},
	{"FIREBALL",2048, 18,  0,1200,	 2,STARTS_INACTIVE+XYZ_PHYSICS+TEMP_NME,	NULL,				FireBIntRou,Update_FireBall},

	{"ROBOLEFTSHO",	EVIL_SCALE, 18, 0,8000,	 2,NULL_RESPAWN,					NULL,		NULL,		NULL},
	{"ROBORIGSHO",  EVIL_SCALE, 18, 0,8000,	 2,NULL_RESPAWN,					NULL,		NULL,		NULL},
	{"ROBOGUN",	    EVIL_SCALE, 18, 0,8000,	 2,NULL_RESPAWN,					NULL,		NULL,		NULL},
//	{"SPIDERBOMB",  2048, 18,  0,1200,	NO_RESPAWN,							NULL,				SplatRou,	Update_Spiderbomb},
	{"SPIDERBOMB",  EVIL_SCALE*4, 50,  0,8000,	2,STARTS_INACTIVE+TEMP_NME,			NULL,		SBombIntRou,Update_Spiderbomb},
	{"MISSILE",	  EVIL_SCALE*4, 40 * EVIL_SCALE/2048,  0,8000,	2,STARTS_INACTIVE+TEMP_NME,		&MissSpheres[0],	NULL,	Update_Missile},
	{"BOLT",	  EVIL_SCALE*4, 36 * EVIL_SCALE/2048,  0,8000,	2,STARTS_INACTIVE+TEMP_NME,		NULL,				NULL,	Update_Laser},
	{NULL,		  EVIL_SCALE, 40,  0,8000,	2,STARTS_INACTIVE+TEMP_NME,			NULL,			NULL,		Update_Fragment},


	{"KLOSPIE",	256, 40,  0,8000,	 2,STARTS_INACTIVE+NO_RESPAWN,				NULL,				PieIntRou,	Update_CaPie},
	{"BOXINGG",	192, 40,  0,8000,	 2,STARTS_INACTIVE+NO_RESPAWN,				NULL,				NULL,		Update_CaGlove},
	{"BOMB",	256, 40,  0,8000,	 2,STARTS_INACTIVE+NO_RESPAWN,				NULL,				NULL,		Update_CaBomb},
	{"PIANO2",	256, 40,  0,8000,	 2,STARTS_INACTIVE+NO_RESPAWN,				NULL,				NULL,		Update_CaPiano},


//	{NULL,		 256, 50,  0,8000,	 2,0,										NULL,				NULL,		NULL} 			//CAMEO ACTOR
	{NULL,		 256, 500,  0,8000,	 2,0,										NULL,				NULL,		NULL} 			//CAMEO ACTOR
};


//#define	NMEPlaySound(vab, sfx, vol, pitch, time)\
//	SFXPlayNME3D(vab, sfx, vol, enemy, pitch, time)









#define	NMEPlaySound(vab, sfx, vol, pitch, time)





// ==========================
void enemiesBeginFile()
{
//	int i;
//	numberofenemies = 0;

	enemy_has_ball = 0;
	nme_list = NULL;
	nme_list_tail = NULL;
//	temp_enemy_counter = 0;

	numberofhoops = 0;
	lasthoopused = 0;
	cameo_tag_counter = 300;
//	memset(enemies,0,MAX_ENEMIES * sizeof(ENEMYPOS));

	bosses_BeginFile();

/*
	for(i = 0; i < MAX_ENEMIES; i++)
	{
		enemies[i].type = 0;
		enemies[i].flags = 0;
	}
*/

}
void enemiesLoadShapes()
{
	nmeFrogModel = EnsureModelLoaded(0,"NMEFROG",nmeinfo[NME_FROG].scale);
}

/*
typedef struct tagCOLLDATA
{
	int		nBoxes;
//	int		bounce;
	int		radius;
	COLLBOX	*pCollBox;
	VECTOR	*pVel;
	VECTOR	*pPos;
	VECTOR	*pRot;	// changed from FVECTOR
	int		nCornerHits;
	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;

*/

//VECTOR NmeVel;
//VECTOR NmePos;
//VECTOR NmeRot;

//COLLDATA	NmeColl;


// Kinda bowling-bally
BEHAVIOUR_PHYSICS	CannonballBehaviour=
{
	18*4096,			// mass;
	GRAVITY,			// gravity;
	100,//	.6*4096,			// bounce

	4096*0.91,			// drag
	//4096*0.99,			// drag (this gives the boulder
	1*(19*4096*NORMAL_SCALE)*1.4, //
	//4096*10,			// maxspeed
	(16*4096*NORMAL_SCALE), 		// radius 	(may need to come back and remove floats)
	4096/1.98,			// accel
	0,
};

BEHAVIOUR_PHYSICS	BovvaBehaviour=
{
//	10*4096,			// mass;
	0,					// mass;
	0,					//GRAVITY,			// gravity;
	4096,				// bounce
	4096,				// drag	** NONE **
	4096*200,			// maxspeed
	25*4096,			// radius
	4096*200,				// accel
};


BEHAVIOUR_PHYSICS	BugleBehaviour=
{
	0,					// mass;
	0,					//GRAVITY,			// gravity;
	4096,				// bounce
	4096,				// drag	** NONE **
	4096*200,			// maxspeed
	32*4096,			// radius
	4096*200,				// accel
};


void enemiesEndFile()
{
	int i;
	ENEMYPOS *nme;


	if(level == CARNIVAL1)
	{
		static VECTOR pos = {0,0,0};
		ca_pickup_nme[0] = enemies_CreateEnemy(PICKUP_ENEMY_STUN);	//*(short *)(data));
		ca_pickup_nme[1] = enemies_CreateEnemy(PICKUP_ENEMY_STUN);	//*(short *)(data));

		enemyCreatePickup(
			ca_pickup_nme[0],
			PICKUP_ENEMY_HERCULES - PICKUP_ENEMY_STUN,
			&pos,
			600,	// regen
			600		// duration
			);
		enemyCreatePickup(
			ca_pickup_nme[1],
			PICKUP_ENEMY_FROGGY - PICKUP_ENEMY_STUN,
			&pos,
			600,	// regen
			600		// duration
			);

		ca_pickup_nme[0]->flags &= ~(NMEFLAG_ENABLED);
		ca_pickup_nme[1]->flags &= ~(NMEFLAG_ENABLED);
	}




	for(i = 0; i < TEMP_ENEMY_MAX; i++)
	{
		nme = enemies_ReserveEnemy(0);
	}


	Spawn_Enemies(0);	// First time!

	if(gameCtrl.previousLevel==TITLE && level == HUB1)	// quickie little bodge
	{
		gameCtrl.port_with_ball = FALSE;
		ball_at_hoop = 0;
	}

	if(numberofhoops)
	{
		Use_Hoop(lasthoopused);
	}
	else
	{
		GloveCtrl.enabled = TRUE;
		DB("Ball enabled by enemies-endfile (no hoops)\n");
		BallCtrl.enabled = TRUE;
	}
	bosses_Endfile();


}

// When we load up an enemy that requires true physics, malloc it a colldata buffer. Remember to free it post-level.
// radius is 13 (aka 25) for bovva
COLLDATA * enemySetupPhysics(ENEMYPOS *nme,BEHAVIOUR_PHYSICS *physics, int radius)
{
	COLLDATA *coll;

	coll = MALLOC(sizeof(COLLDATA),"nme_phys");
	memset(coll,0,sizeof(COLLDATA));

	coll->physics = physics;
	coll->pVel = &nme->vel;
	coll->pPos = &nme->pos;
//	coll->pRot = NULL;

	coll->oldPos = *coll->pPos;
	coll->oldVel = *coll->pVel;

	coll->nBoxes=numCollBoxes;
//	coll->radius=(25*4096*NORMAL_SCALE);
	coll->radius = radius<<12;
	coll->pCollBox=collBox;
	return coll;
}


// note - type = "SPELL_", which is internally converted to a "PICKUP_ENEMY_"
void enemyCreatePickup(ENEMYPOS *nme, int type, VECTOR *pos, int regen, int duration)
{
	LOADEDMODEL *model;


	nme->type   = type + PICKUP_ENEMY_STUN;

//	DB("pickup type %d\n",type);
	ASSERT(nme->type >= PICKUP_ENEMY_STUN && nme->type <= PICKUP_ENEMY_VANISHBALL);

	nme->pathvectors[1].vx = regen;	// s'ok. They're created with two
	nme->pathvectors[1].vy = duration;

	nme->init_pos.vx = pos->vx;
	nme->init_pos.vy = pos->vy;
	nme->init_pos.vz = pos->vz;

	nme->pos.vx = nme->init_pos.vx << 12;
	nme->pos.vy = nme->init_pos.vy << 12;
	nme->pos.vz = nme->init_pos.vz << 12;

	model = EnsureModelLoaded(0,nmeinfo[nme->type].fname,nmeinfo[nme->type].scale);
	nme->psa =	model->psa;
	nme->animinfo.segInfo = model->seg;
	nme->anim.animInfo = &nme->animinfo;

	nme->doing = -1;
	nme->ticker = 0;

	if(model->seg)
	{
		ClearAnimQueue(&nme->anim);
		AddToQueue(&nme->anim,NMEANIM_IDLE,YES,NO,4096);	// anim,numberloop,queue,speed
	}
}

void enemiesFreeNmeExtraMemory(ENEMYPOS *nme)
{
	switch(nme->type)
	{
	case BUGLE:
		FREE(nme->extras);
		// collbox (not necessary - it's freed in "platforminitialise"
		break;

	case CANNONBALL:
	case VENT_ENEMY:	// vent pattern pointer
	case BOVVA:			// physics
	case SELWYN_FISH:	// physics
//		case SELWYN_CRAB:	// physics
		FREE(nme->extras);
		break;
	}

// we can't do this by checking if pathvectors isn't "sizeof" away, coz that could happen by chance
	if(nmeinfo[nme->type].n_paths <= 0 && nme->pathvectors)
	{
		FREE(nme->pathvectors);
	}
}

void enemiesFreeEnemies()
{
	ENEMYPOS *nme;
	ENEMYPOS *next;
	
	for (nme = nme_list; nme; nme = next)
	{
		next = nme->next;

		enemiesFreeNmeExtraMemory(nme);

		FREE(nme);
	}
	nme_list = NULL;
	nme_list_tail = NULL;

}

void enemiesFreeTempEnemies()
{
	ENEMYPOS *nme;
	ENEMYPOS **ptr;
	
	ptr = &nme_list;

	while(*ptr)
	{
		nme = *ptr;
		if(nme->type == 0)
		{
			*ptr = nme->next;
			FREE(nme);
		}
		else
		{
			ptr = &nme->next;
		}
	}
}

// roughly as for rubber ball
BEHAVIOUR_PHYSICS	EnemyDefaultPhysics=
	{
		10*4096,			// mass;
		GRAVITY,			// gravity;
		.8*4096,			// bounce
		4096*0.91,			//(4096-(4096*0.05)),	// drag
		1*(19*4096*NORMAL_SCALE)*1,			// maxspeed (radius*2)
		10*4096,			// radius
		4096,				// accel
		(0.4*4096),			// max squash amount
	};
// roughly as per bowling ball
BEHAVIOUR_PHYSICS	EnemyHeavyPhysics=
	{
		10*4096,			// mass;
		GRAVITY,			// gravity;
		.6*4096,			// bounce
		4096*0.91,			//(4096-(4096*0.05)),	// drag
		1*(19*4096*NORMAL_SCALE)*1,			// maxspeed (radius*2)
		10*4096,			// radius
		4096,				// accel
		(0.4*4096),			// max squash amount
	};

COLLDATA enemy_Colldata;	// static coz it's a bit big

int enemy_UsePhysics(ENEMYPOS *nme, BEHAVIOUR_PHYSICS *physics, VECTOR *oldpos, int radius)
{
	VECTOR vel;
	COLLDATA *coll = &enemy_Colldata;


	if(nmeinfo[nme->type].flags & XYZ_PHYSICS)
	{
		vel = nme->vel;
	}
	else
	{
		SUBVECTOR(&vel,&nme->pos,oldpos);
		vel.vy = nme->vel.vy;	// enemies do generally keep track of their y-vel (but the x & z are sometimes fore/side ones)
	}


	vel.vx = -vel.vx;
	vel.vz = -vel.vz;

	coll->pPos = &nme->pos;
	coll->pVel = &vel;
	coll->oldVel = vel;

	coll->oldPos = *oldpos;

	if(!physics)
	{
		physics = &EnemyDefaultPhysics;
	}

	coll->physics = physics;
	coll->radius = physics->radius = radius<<12;


	coll->nBoxes=numCollBoxes;
	coll->pCollBox=collBox;

	if(collboxCheckSphere(coll))
	{
//		static int count = 0;
//		DB("hit %d\n",count++);
//		nme->vel.vy = vel.vy;
		if(nmeinfo[nme->type].flags & XYZ_PHYSICS)
		{
			nme->vel.vx = -vel.vx;
			nme->vel.vy = vel.vy;
			nme->vel.vz = -vel.vz;
		}
		else
		{
			nme->vel.vy = vel.vy;
		}
		return coll->flags | 0x80000000;
	}
	else
	{
		return 0;
	}
}

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

void Spawn_Enemies(int again)
{
	ENEMYPOS *nme;

	for (nme = nme_list; nme; nme = nme->next)
	{
		if(nme->flags & NMEFLAG_STUNNED)
		{
			nme->flags &= ~(NMEFLAG_STUNNED);
			nme->animinfo.speed = 4096;	// more things anim at 4096 than at 2048...
		}
		if(nmeinfo[nme->type].flags & TEMP_NME)
		{
			nme->type = 0;
		}

// Note - Frogged is a permanant state of mind

		if(    (!(nmeinfo[nme->type].flags & NULL_RESPAWN))
			|| (!again))
		{
			nme->pos.vx = nme->init_pos.vx << 12;
			nme->pos.vy = nme->init_pos.vy << 12;
			nme->pos.vz = nme->init_pos.vz << 12;
			nme->ya     = nme->init_ya;

	// tbd: undo other stuff...
	// (eg - bring killed baddies to life, etc)

			nme->vel.vx = 0;
			nme->vel.vy = 0;
			nme->vel.vz = 0;

			nme->xa = 0;
			nme->za = 0;

			nme->doing = -1;
			nme->ticker = 0;

	// this might need to be conditional on the type
	// ah, the prob is puzzles of the "numtimes 1" kind that reset the nme active flag to zero... ok, I'll make it nmetype dependant
	// For the moment, I've frigged the one important example. tbd: do properly

			if(!again)
			{
				if(nmeinfo[nme->type].flags & STARTS_INACTIVE)	// any & all bullets
				{
					nme->flags &= ~(NMEFLAG_ACTIVE);	// enabled is left alone
				}
				else
				{
					if(nme->flags & NMEFLAG_ENABLED)
					{
						nme->flags |= NMEFLAG_ACTIVE;
						nme->flags &= ~NMEFLAG_DEAD;
					}
				}
				nme->flags &= ~(NMEFLAG_GARIB_DROPPED);
			}
			if(again)
			{
				if(nmeinfo[nme->type].flags & NO_RESPAWN)
				{
					if(nmeinfo[nme->type].flags & STARTS_INACTIVE)
					{
						nme->flags &= ~NMEFLAG_ACTIVE;	// enabled is left alone
					}

	// leave in current alive/dead state
				}
				else
				{
	// respawn it
					if(nme->flags & NMEFLAG_ENABLED)
					{
						nme->flags |= NMEFLAG_ACTIVE;
						nme->flags &= ~NMEFLAG_DEAD;
					}
				}
			}

// Special cases...
			switch(nme->type)
			{
				case BUGLE:
				case BOVVA:
				case CANNONBALL:
				case SELWYN_FISH:
				{
					COLLDATA *coll;
					coll = (void *)(nme->extras);
					coll->oldPos = *coll->pPos;
					coll->oldVel = *coll->pVel;
				}
				break;


			}
		}

		if(nme->type == 0)	// kill off any temporary enemies
		{
			nme->flags &= ~(NMEFLAG_ACTIVE + NMEFLAG_ENABLED);
		}
	}
}

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


void enemiesAddExitPortal(int x, int y, int z, int tag)
{
	LOADEDMODEL *model;
	ENEMYPOS *enemypos;

	enemypos = enemies_CreateEnemy(PORTAL_ENEMY);

	enemypos->tag = tag;

	enemypos->init_pos.vx = x>>12;
	enemypos->init_pos.vy = y>>12;
	enemypos->init_pos.vz = z>>12;

	model = EnsureModelLoaded(0,nmeinfo[enemypos->type].fname,nmeinfo[enemypos->type].scale);
	enemypos->psa =	model->psa;
	enemypos->animinfo.segInfo = model->seg;
	enemypos->anim.animInfo = &enemypos->animinfo;

	enemypos->doing = -1;
	enemypos->ticker = 0;
	enemypos->flags |= NMEFLAG_ACTIVE  + NMEFLAG_ENABLED;	// coz the resetter doesn't



	if(model->seg)
	{
		ClearAnimQueue(&enemypos->anim);
		AddToQueue(&enemypos->anim,0,YES,NO,8192);	// anim,number,loop,queue,speed NB - FAST!
	}
}


void enemiesAddHoop(int x, int y, int z, int ya)
{
	static SVECTOR hubvectors[] =
	{
		{   -5, -38-20, -601},	// cave / title
		{   49, -63-20,-1038},	// at
		{-1032, -63-20, -644},	// ca
		{ 1056, -63-20, -711},	// pi
		{-1040, -56-20,  541},	// pr
		{  909, -69-20,  653},	// ff
		{   -1, -60-20,  988},	// sp
//		{  239,   -195,  -42}	// new start position
		{  261,   -195,  -166}	// new start position
	};
	static SHORT hubangles[] =
	{
		0x0000, 0x780,
		0x680,0xA80,
		0x200,0xE00,
		0x080,
		-15
	};
	static SVECTOR wayvectors[] =
	{
		{  -8,-13-20,-300},	// from hub
		{-253,-13-20,-107},	// from l1
		{-258,-13-20, 126},	// from L2
		{ 259,-13-20, 131},	// From L3
		{ 255,-13-20,-108},	// From Boss
		{   2,-13-20, 395}	// From Bonus
	};


	ENEMYPOS *enemypos;
//	hoop_sprite = textureFindInAllBanks("SPINNY");
	hoop_sprite = textureFindInAllBanks("SPINNEY2");
	ASSERT(hoop_sprite);


	enemypos = enemies_CreateEnemy(HOOP_ENEMY);

	enemypos->init_ya = ya;
	enemypos->init_pos.vx = x>>12;
	enemypos->init_pos.vy = y>>12;
	enemypos->init_pos.vz = z>>12;


// A little bit of a temporary fudging for the hub and the wayroom

	if(level == WAYROOM)
	{
		if(gameCtrl.previousLevel >= HUB1 && gameCtrl.previousLevel <= HUB8)
		{
			enemypos->init_pos = wayvectors[0];
		}
		else
		{
			int sublevel;

			if(gameCtrl.previousLevel == SPACEBOSS2)
			{
				sublevel = 4;
			}
			else if(gameCtrl.previousLevel == SPACEBONUS)
			{
				sublevel = 5;
			}
			else
			{
				sublevel = (gameCtrl.previousLevel - ATLANTIS1) % 5;
				sublevel++;
			}
			enemypos->init_pos = wayvectors[sublevel];
		}
	}

	if(level >= HUB1 && level <= HUB8)
	{
		switch(gameCtrl.previousLevel)
		{

			case WAYROOM:
				ASSERT(gameCtrl.wayroom_world >= 0 && gameCtrl.wayroom_world <= 6);
				DB("wayroom -> hub:- Wayroom world = %d\n",gameCtrl.wayroom_world);
				enemypos->init_pos = hubvectors[gameCtrl.wayroom_world];
				enemypos->init_ya  = hubangles[gameCtrl.wayroom_world];
				break;

			case TITLE:
				if( gameInfo.keyRecordFlag!=PLAYBACK
					&& gameInfo.keyRecordFlag!=RECORD
					)
				{
						enemypos->init_pos = hubvectors[7];
						enemypos->init_ya  = hubangles[7];
				}
				else
				{
						enemypos->init_pos = hubvectors[0];
						enemypos->init_ya  = hubangles[0];
				}
				break;

			case CAVE:
				enemypos->init_pos = hubvectors[0];
				enemypos->init_ya  = hubangles[0];

				break;

			default:
				ASSERT(gameCtrl.wayroom_world >= 0 && gameCtrl.wayroom_world <= 6);
				enemypos->init_pos = hubvectors[gameCtrl.wayroom_world];
				enemypos->init_ya  = hubangles[gameCtrl.wayroom_world];
				break;
		}
	}


// Hurrah!


	enemypos->doing = -1;
	enemypos->ticker = 0;
	enemypos->flags |= NMEFLAG_ACTIVE + NMEFLAG_ENABLED;	// coz the resetter doesn't

	numberofhoops++;
}


void enemiesAddVent(VENT *vent)
{
	ENEMYPOS *enemypos;

	enemypos = enemies_CreateEnemy(VENT_ENEMY);

	enemypos->extras = vent;

	enemypos->init_pos.vx = vent->head.pos.vx>>12;
	enemypos->init_pos.vy = vent->head.pos.vy>>12;
	enemypos->init_pos.vz = vent->head.pos.vz>>12;

//		printf("vent type %d, at %d %d %d\n",vent->head.type,enemypos->init_pos.vx,enemypos->init_pos.vy,enemypos->init_pos.vz);
	enemypos->tag = vent->head.tag;

	enemypos->doing = -1;
	enemypos->ticker = 0;
}


void enemiesAddTeleport(VECTOR *pos, TELEPORTDEFSTR *def, int tag)
{
	ENEMYPOS *enemypos;

	enemypos = enemies_CreateEnemy(TELEPORT_ENEMY);

	enemypos->init_pos.vx = pos->vx>>12;
	enemypos->init_pos.vy = pos->vy>>12;
	enemypos->init_pos.vz = pos->vz>>12;

	enemypos->tag = tag;
/*
short flags;
short platform_tag;
short disappear_delay;
short appear_delay;
short x,dummy,y,dummy2,z,dummy3;
*/
	enemypos->pathvectors[0].vx = def->x << 12;
	enemypos->pathvectors[0].vy = def->y << 12;
	enemypos->pathvectors[0].vz = def->z << 12;
	enemypos->n_points = def->flags;
	enemypos->pathvectors[1].vx = def->disappear_delay;
	enemypos->pathvectors[1].vy = def->appear_delay;
	enemypos->pathvectors[1].vz = def->platform_tag;



	enemypos->doing = -1;
	enemypos->ticker = 0;

/*
	printf("teleport from %d %d %d, to %d %d %d!!!\n",
		enemypos->init_pos.vx,enemypos->init_pos.vy,enemypos->init_pos.vz,
		def->x,def->y,def->z
		);
*/

}

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

// old-style LNDX mr-tip
void enemiesParseTokenTip(UBYTE **pdata)
{

	UBYTE		*data = *pdata;
	short		enemy_type;
	ENEMYPOS	*enemypos;

//	printf("Token Tip\n");

	SPR_mrtipbase =  textureFindFrames("TIP");	// 01...12
	ASSERT(SPR_mrtipbase);	// make sure people regrab the generic bank

	enemypos = enemies_CreateEnemy(PICKUP_ENEMY_MRTIP);

	current_enemy = enemypos;

	//enemypos->type = *(short *)(data);
	//enemypos->tag  = *(short *)(data + 2);

	enemypos->tag    = NULL;	// not sure about this
	enemy_type = enemypos->type;

	enemypos->init_pos.vx = ( *(short *)(data +  0));
	enemypos->init_pos.vy = (-*(short *)(data +  4));
	enemypos->init_pos.vz = (-*(short *)(data + 8));
	enemypos->init_ya = (*(short *)(data + 12)) & 4095;	// tip number

//				PRINTF("tip type %d at %d %d %d\n",enemy_type,enemypos->init_pos.vx,enemypos->init_pos.vy,enemypos->init_pos.vz);

//				if(nmeObjectName[enemy_type])	// which will be set as soon as I've got it in...


// mrt's are back to being sprites
/*
	if(nmeinfo[enemy_type].fname)
	{
//					model = EnsureModelLoaded(0,nmeObjectName[enemy_type],nmeObjectScale[enemy_type]);
		model = EnsureModelLoaded(0,nmeinfo[enemy_type].fname,nmeinfo[enemy_type].scale);
		enemypos->psa =	model->psa;
		enemypos->animinfo.segInfo = model->seg;
		enemypos->anim.animInfo = &enemypos->animinfo;

//					nme_positions_read = 0;

		if(model->seg)
		{
			ClearAnimQueue(&enemypos->anim);
			AddToQueue(&enemypos->anim,NMEANIM_IDLE,YES,NO,4096);	// anim,numberloop,queue,speed
		}
	}
*/

}

// ==========================
ENEMYPOS *enemies_CreateEnemy(int type)
{
	ENEMYPOS *enemypos;
	int size;

	size = sizeof(ENEMYPOS);
	if(nmeinfo[type].n_paths > 0)	// (ie, not zero, not -1)
	{
		size += nmeinfo[type].n_paths * sizeof(VECTOR);
	}



#if (GOLDCD==NO) && (RELEASE==NO)
	{
		char temp[20];
		sprintf(temp,"nme typ %d",type);
		enemypos = MALLOC(size,temp);
	}
#else
	enemypos = MALLOC(size,"nme");
#endif
	FMEMZERO(enemypos,size);	// note - sets "active" = 0

	if(nmeinfo[type].n_paths > 0)	// (ie, not zero, not -1)
	{
		enemypos->pathvectors = ADD2POINTER(enemypos,sizeof(ENEMYPOS));
	}
	else
	{
// -1 (variable)
//  0 (none)
// pathvectors left null
//		printf("enemy with no or -ve pathvectors\n");
	}

	if(nme_list_tail)
	{
		nme_list_tail->next = enemypos;
		nme_list_tail = enemypos;
	}
	else
	{
		nme_list = enemypos;
		nme_list_tail = enemypos;
	}
	enemypos->type = type;
	if(!(nmeinfo[type].flags & STARTS_INACTIVE))
	{
		enemypos->flags |= NMEFLAG_ENABLED;
	}

	return enemypos;
}

void enemyAddPathVector(int x, int y, int z)
{
	ENEMYPOS *nme = current_enemy;
	int i;
	i = nme->n_points;

	if(nme->pathvectors)
	{
		ASSERT(i < nmeinfo[nme->type].n_paths);
		nme->pathvectors[i].vx = x<<12;
		nme->pathvectors[i].vy = y<<12;
		nme->pathvectors[i].vz = z<<12;
	}
	else
	{
		ASSERT(i < NME_MAX_PATHVECTORS);
		pathvector_buffer[i].vx = x;
		pathvector_buffer[i].vy = y;
		pathvector_buffer[i].vz = z;
	}
	nme->n_points++;
}

void enemyFlushPathVectors()
{
	ENEMYPOS *nme = current_enemy;
	int i;
	int n;


	n = nme->n_points;

//	printf("flushing %d pathvectors\n",n);

	if((!nme->pathvectors) && (n != 0))
	{
//		printf("doing it\n");
		current_enemy->pathvectors = MALLOC(n * sizeof(VECTOR),"path");
		for(i = 0; i < n; i++)
		{
			nme->pathvectors[i].vx = pathvector_buffer[i].vx<<12;
			nme->pathvectors[i].vy = pathvector_buffer[i].vy<<12;
			nme->pathvectors[i].vz = pathvector_buffer[i].vz<<12;
		}
	}
}

ENEMYPOS *enemies_ReserveEnemy(int type)
{
	ENEMYPOS *enemypos;
	LOADEDMODEL *model;

	enemypos = enemies_CreateEnemy(type);
	if(!enemypos)
		return NULL;

	enemypos->type = type;

	if(nmeinfo[type].fname)
	{
		model = EnsureModelLoaded(0,nmeinfo[type].fname,nmeinfo[type].scale);
		enemypos->psa =	model->psa;
		enemypos->animinfo.segInfo = model->seg;
		enemypos->anim.animInfo = &enemypos->animinfo;
	}
//	numberofenemies++;

	return enemypos;
}
// ==========================




int enemiesParseTokenEnemy(short token,UBYTE **pdata)
{
	UBYTE *data = *pdata;

	switch(token)
	{
// New style tip token as part of an "enemy mr_tip"
		case TOKEN_TIP_NUMBER:
			current_enemy->init_ya = *(short *)data;
			data += 0x2;
			break;

// cameo objects, like the jack in the box, decoy through to the enemy code...
		case TOKEN_CAMEO_OBJ:
		{
			ENEMYPOS *enemypos;
			LOADEDMODEL *model;

			enemypos = enemies_CreateEnemy(CAMEO_ENEMY);
			current_enemy = enemypos;

			enemypos->init_pos.vx = ( *(short *)(data + 12));
			enemypos->init_pos.vy = (-*(short *)(data + 16));
			enemypos->init_pos.vz = (-*(short *)(data + 20));
			enemypos->init_ya = (*(short *)(data + 24)) & 4095;
			enemypos->tag = cameo_tag_counter;
			cameo_tag_counter++;

//				PRINTF("cameo actor at %d %d %d\n",enemypos->init_pos.vx,enemypos->init_pos.vy,enemypos->init_pos.vz);

			{
				upperStr(data+4);	// oops! - carnival-boss bug in milstone # 4

				model = EnsureModelLoaded(0,data + 4,nmeinfo[CAMEO_ENEMY].scale);	// "jackbox" will work here with no mods
				enemypos->psa =	model->psa;
				enemypos->animinfo.segInfo = model->seg;
				enemypos->anim.animInfo = &enemypos->animinfo;

//					nme_positions_read = 0;
				if(model->seg)
				{
					ClearAnimQueue(&enemypos->anim);
					AddToQueue(&enemypos->anim,NMEANIM_IDLE,YES,NO,4096);	// anim,numberloop,queue,speed
				}
			}

			data += 0x18;
			break;
		}

		case TOKEN_ENEMY:	// enemy... 2:type, 2:tag, 12:x,y,z, 4:ya
		{
			short enemy_type;
			ENEMYPOS *enemypos;
			LOADEDMODEL *model;

			enemy_type = *(short *)data;

			if(nmeinfo[enemy_type].flags & DROPS_GARIB)	// DIBBER, MALLET, REGGIE
			{
				numGaribs++;
			}


			enemypos = enemies_CreateEnemy(enemy_type);
			current_enemy = enemypos;

			enemypos->tag  = *(short *)(data + 2);

			enemypos->init_pos.vx = ( *(short *)(data +  4));
			enemypos->init_pos.vy = (-*(short *)(data +  8));
			enemypos->init_pos.vz = (-*(short *)(data + 12));
			enemypos->init_ya = (*(short *)(data + 16)) & 4095;

			DB("enemy type %d at %d %d %d\n",enemy_type,enemypos->init_pos.vx,enemypos->init_pos.vy,enemypos->init_pos.vz);

			if(nmeinfo[enemy_type].fname)
			{
				model = EnsureModelLoaded(0,nmeinfo[enemy_type].fname,nmeinfo[enemy_type].scale);
				enemypos->psa =	model->psa;
				enemypos->animinfo.segInfo = model->seg;
				enemypos->anim.animInfo = &enemypos->animinfo;

				if(model->seg)
				{
					ClearAnimQueue(&enemypos->anim);
					AddToQueue(&enemypos->anim,NMEANIM_IDLE,YES,NO,4096);	// anim,numberloop,queue,speed
				}
				if(enemypos->type == BABY_SPANK)
					babySpankModel = model;
			}
			else
			{
				PRINTF("*** Enemy type %d not implemented ***\n",enemy_type);
	// and the rest of the system will cope with it not having a psa
			}

// --- Enemies that need extra enemies ---



			if(enemypos->type == EVIL_ROBOT)
			{
				ENEMYPOS *base = enemypos;

//					robot_model = model;

				robot_lsho_model = EnsureModelLoaded(0,"ROBOLEFTSHO",EVIL_SCALE);
				bot_original_meshes[0] = base->psa->world.child->child->next->next->meshdata;
				base->psa->world.child->child->next->next->meshdata = robot_lsho_model->psa->world.meshdata;

				robot_lgun_model = EnsureModelLoaded(0,"ROBOGUN",EVIL_SCALE);
				bot_original_meshes[1] = base->psa->world.child->child->next->next->child->meshdata;
				base->psa->world.child->child->next->next->child->meshdata = robot_lgun_model->psa->world.meshdata;

				robot_lgun_model->psa->world.meshdata->name[3] = 'l';


				robot_rsho_model = EnsureModelLoaded(0,"ROBORIGSHO",EVIL_SCALE);
				bot_original_meshes[2] = base->psa->world.child->child->meshdata;
				base->psa->world.child->child->meshdata = robot_rsho_model->psa->world.meshdata;

				robot_rgun_model = EnsureModelLoaded(0,"ROBOGUN2",EVIL_SCALE);
				bot_original_meshes[3] = base->psa->world.child->child->child->meshdata;
				base->psa->world.child->child->child->meshdata = robot_rgun_model->psa->world.meshdata;


				model = EnsureModelLoaded(0,nmeinfo[ROBOT_MISSILE].fname,nmeinfo[ROBOT_MISSILE].scale);
				robot_missile_psa =	model->psa;


				model = EnsureModelLoaded(0,nmeinfo[ROBOT_BOLT].fname,nmeinfo[ROBOT_BOLT].scale);
				robot_laser_psa = model->psa;


				robot_spider_model = EnsureModelLoaded(0,nmeinfo[SPIDERBOMB].fname,nmeinfo[SPIDERBOMB].scale);
			}


			if(enemypos->type == BOVVA)
			{
				enemypos->extras = (void *)enemySetupPhysics(enemypos,&BovvaBehaviour,(25*NORMAL_SCALE));
				bovva_sting_enemy = enemies_ReserveEnemy(BULLET_ENEMY_STING);
			}

			if(enemypos->type == CANNONBALL)
			{
				enemypos->extras = (void *)enemySetupPhysics(enemypos,&CannonballBehaviour,(25*NORMAL_SCALE));
			}


			if(enemypos->type == SELWYN_FISH)
			{
				enemypos->extras = (void *)enemySetupPhysics(enemypos,&KirkBehaviour,(25*NORMAL_SCALE));
				model = EnsureModelLoaded(0,nmeinfo[KIRK_BUBBLE1_ENEMY].fname,nmeinfo[KIRK_BUBBLE1_ENEMY].scale);
				kirk_bubble1_psa =	model->psa;
				model = EnsureModelLoaded(0,nmeinfo[KIRK_BUBBLE2_ENEMY].fname,nmeinfo[KIRK_BUBBLE2_ENEMY].scale);
				kirk_bubble2_psa =	model->psa;
			}

			if(enemypos->type == SELWYN_CRAB)
			{
				model = EnsureModelLoaded(0,nmeinfo[BULLET_ENEMY_CLAW].fname,nmeinfo[BULLET_ENEMY_CLAW].scale);
				cancer_claw_psa =	model->psa;
			}

			if(enemypos->type == FLANNEL)
			{
				model = EnsureModelLoaded(0,nmeinfo[WILLY_FIREBALL_ENEMY].fname,nmeinfo[WILLY_FIREBALL_ENEMY].scale);
				willy_fireball_psa = model->psa;
			}

			if(enemypos->type == EVIL_GLOVE)
			{
				enemypos->flags |= NMEFLAG_ACTIVE + NMEFLAG_ENABLED;
			}

			if(enemypos->type == CHUCK)	// NB - the egg is a SPRITE enemy
			{
//				enemypos[1] = enemypos[0];	// ouch - we'd better check that chuck works...
				enemypos = enemies_CreateEnemy(BULLET_ENEMY_EGG);
				chuck_egg_enemy = enemypos;
				chuck_egg_sprite = textureFindFrames("EGG");	// 01...06
				chuck_splat_sprite = textureFindFrames("SPLAT");	//01..03
			}

			if(enemypos->type == PICKUP_ENEMY_MRTIP)	// SPRITE-BASED ENEMY
			{
				SPR_mrtipbase =  textureFindFrames("TIP");	// 01...12
				ASSERT(SPR_mrtipbase);	// make sure people regrab the generic bank
			}

			if(enemypos->type == TRACEY)
			{
				tracy_heart_sprite = textureFindInAllBanks("HEALTH01");	// use the panel sprite
			}

			if(enemypos->type == SELWYN_WHALE)
			{
				joff_spray_sprite = textureFindInAllBanks("TEAR01");
			}

			if(enemypos->type == BUGLE)
			{
				enemypos->extras = enemySetupPhysics(enemypos,&BugleBehaviour, 16);
				bugleInsideModel  = EnsureModelLoaded(0,"BUGLEI",nmeinfo[enemy_type].scale);
				bugleOutsideModel = EnsureModelLoaded(0,"BUGLEO",nmeinfo[enemy_type].scale);
			}

			data += 20;
			break;
		}

		case TOKEN_END_ENEMY:
			enemyFlushPathVectors();
//			printf("&%8x:   END ENEMY token\n",(data-realData-2));VSync(0);
			break;

		case TOKEN_CONFINE_ENEMY:

			enemyAddPathVector(
				(  *(SHORT *)(data + 0x00)),
				(-(*(SHORT *)(data + 0x04)+*(SHORT *)(data + 0x10))),
				(-(*(SHORT *)(data + 0x08)+*(SHORT *)(data + 0x14)))
				);

			enemyAddPathVector(
				(*(SHORT *)(data + 0x0c)),
				(*(SHORT *)(data + 0x10)),
				(*(SHORT *)(data + 0x14))
				);

			data += 0x18;	//12 * 2;	//sizeof(BOX);
			break;
		case TOKEN_DISABLE_REDIRECTION:
			break;



// enemy sub-tokens
// "behave.c" in the n64 stuff
		case TOKEN_NORMAL_INSTRUCTION:
		case TOKEN_CONDITIONAL_INSTRUCTION:
		case TOKEN_ATTACK_INSTRUCTION:
		{
			unsigned short inst;
			inst = *(unsigned short *)data;

			switch(inst)
			{
				case NME_MOVETO2D:	// 2 token, 2 time, 12 vector
				case NME_MOVETO:
				{
					enemyAddPathVector(
						( *(SHORT *)(data + 0x04)),
						(-*(SHORT *)(data + 0x08)),
						(-*(SHORT *)(data + 0x0c))
						);
					data += 4+ 12 + 4*4 + 2;
					break;
				}

// Note Samtex only faces a point (& then jumps), without a "moveto" instruction
				case NME_FACETO:
					if(current_enemy->type == SAMTEX)
					{
						enemyAddPathVector(
							( *(SHORT *)(data + 0x04)),
							(-*(SHORT *)(data + 0x08)),
							(-*(SHORT *)(data + 0x0c))
							);
					}
					data += 4+ 12 + 4*4 + 2;
					break;


				case NME_MOVE_VECTOR:
				case NME_FOLLOWPLAYER:
				case NME_CIRCLE:
					data += 4+ 12 + 4*4 + 2;
					break;
				case NME_RANDOMMOVE:
					if(current_enemy->n_points == 0)
					{
						enemyAddPathVector(
							(  *(SHORT *)(data + 0x04)),
							(-(*(SHORT *)(data + 0x08)+*(SHORT *)(data + 0x14))),
							(-(*(SHORT *)(data + 0x0c)+*(SHORT *)(data + 0x18)))
							);
						enemyAddPathVector(
							(*(SHORT *)(data + 0x10)),
							(*(SHORT *)(data + 0x14)),
							(*(SHORT *)(data + 0x18))
							);
					}

					data += 4+12+12 +4*4 + 2;
					break;
				case NME_WAIT:
				case NME_FIRE1:
				case NME_FIRE2:
				case NME_SPECIAL:
				case NME_FLEEPLAYER:
				case NME_ATTACK:
				case NME_DONTATTACK:
				case NME_ENDATTACK:
				case NME_BRANCH:
				case NME_RETURN:
				case NME_LOOP:
				case NME_LOADCOUNTER:
				case NME_DIE:
					data += 4 + 4*5 + 2;
					break;
				case NME_FACEPLAYER:
				case NME_CHARGE:
					data += 4 + 4*5 + 2;
					break;

				case NME_HOLDPLAYER:
				case NME_DROPPLAYER:
				case NME_JUMP:
					data += 4 + 12 + 4*4 + 2;
					break;


				default:
					DB("unrecognised instruction! eek\n");
					break;
			}
			break;
		}


// (N64 did pickups as a special case of collectable, I'm doing 'em as a special case of enemy)
// pickup locations...
// Hercules:- AT2
// Speedup, suction:-  AT3
// Speed,froggy, ballbearing CA1
// Froggy,Rotors,bowling,boomerang:-   CA2
// Bowling, suction:- CA3
// Hercules, suction

		case TOKEN_PICKUP:
		{
			VECTOR pos;
			ENEMYPOS *enemy;

			pos.vx = ( *(short *)(data +  6));
			pos.vy = (-*(short *)(data + 10));
			pos.vz = (-*(short *)(data + 14));
			enemy = enemies_CreateEnemy(PICKUP_ENEMY_STUN);	//*(short *)(data));

			enemyCreatePickup(
				enemy,
				*(short *)(data),	// type
				&pos,
				*(short *)(data+2),	// regen
				*(short *)(data+4)	// duration
				);
			data += 18;
			break;
		}

		default:
			return NO;
	}

// only get here if the token WAS recognised

	*pdata = data;
	return YES;
}





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

//	SHORT doing;	// position in instruction list (I'm thinking of hardcoding this rather than parsing)
//	SHORT ticker;	// time left on this instruction
//	VECTOR pathvectors[2];


int NME_FaceTo(ENEMYPOS *enemy, VECTOR *target, SHORT rate)
{
	SHORT ya;
	SHORT dya;

//	objectLookAtPoint(NEWMODEL *actor, VERT *point);


	if(enemy->pos.vx>>8 == target->vx>> 8 && enemy->pos.vz>>8 == target->vz>> 8)
		return 1;

	ya = (calc_angle( (enemy->pos.vx - target->vx)>>8, (enemy->pos.vz - target->vz)>>8) & 4095);

	dya = enemy->ya - ya;
	if((dya >= 0 && dya < 2048) || (dya < -2048))
	{
		enemy->ya -= rate;
		enemy->ya &= 4095;
		dya = enemy->ya - ya;
		if((dya >= 0 && dya < 2048) || (dya < -2048))
			return 0;
		else
		{
			enemy->ya = ya;
			return 1;
		}

	}
	else
	{
		enemy->ya += rate;
		enemy->ya &= 4095;
		dya = enemy->ya - ya;
		if((dya >= 0 && dya < 2048) || (dya < -2048))
		{
			enemy->ya = ya;
			return 1;
		}
		else
			return 0;
	}
}

// 3 * 4096
/*
int NME_GoTo2D(ENEMYPOS *enemy, VECTOR *target, int speed)
{
	return VectorMoveTo2D(&enemy->pos, target, speed);
}
int NME_GoTo3D(ENEMYPOS *enemy, VECTOR *target, int speed)
{
	return VectorMoveTo(&enemy->pos, target, speed);
}
*/

void NME_GoForwards(ENEMYPOS *enemy, int speed)
{
	enemy->pos.vx -= rsin(enemy->ya) * speed / 4096;
	enemy->pos.vz -= rcos(enemy->ya) * speed / 4096;
}

// keep a velocity/acceleration based enemy in its box
void NME_KeepInBox(ENEMYPOS *enemy)
{
	if(enemy->pos.vx < enemy->pathvectors[0].vx)
		enemy->pos.vx = enemy->pathvectors[0].vx;
	if(enemy->pos.vy < enemy->pathvectors[0].vy)
		enemy->pos.vy = enemy->pathvectors[0].vy;
	if(enemy->pos.vz < enemy->pathvectors[0].vz)
		enemy->pos.vz = enemy->pathvectors[0].vz;

	if(enemy->pos.vx > enemy->pathvectors[0].vx+enemy->pathvectors[1].vx)
		enemy->pos.vx = enemy->pathvectors[0].vx+enemy->pathvectors[1].vx;
	if(enemy->pos.vy > enemy->pathvectors[0].vy+enemy->pathvectors[1].vy)
		enemy->pos.vy = enemy->pathvectors[0].vy+enemy->pathvectors[1].vy;
	if(enemy->pos.vz > enemy->pathvectors[0].vz+enemy->pathvectors[1].vz)
		enemy->pos.vz = enemy->pathvectors[0].vz+enemy->pathvectors[1].vz;
}

// taken/adapted from the N64's "MoveTo2D"

BOOL NME_AccelTo2D(ENEMYPOS *enemy, VECTOR *dest,const NMEAccelStr *accel)
{
	LONG speed;
	VECTOR temp;

	temp.vx = (dest->vx - enemy->pos.vx) >> 12;
	temp.vy = 0;
	temp.vz = (dest->vz - enemy->pos.vz) >> 12;

	if(temp.vx * temp.vx + temp.vz * temp.vz < accel->dsquared)
	{
//		printf("arrived!!\n");
		return TRUE;
	}
//	else
//	{
//		printf("   dist %d > %d\n",temp.vx * temp.vx + temp.vz * temp.vz,accel->dsquared);
//	}
	NME_FaceTo( enemy, dest,accel->turnrate );

	enemy->vel.vx -= rsin(enemy->ya) * accel->acceleration/4096;
	enemy->vel.vz -= rcos(enemy->ya) * accel->acceleration/4096;

	speed = Magnitude(&enemy->vel);


	if ( speed > accel->maxspeed )
	{
		enemy->vel.vx = enemy->vel.vx * accel->maxspeed / speed;
		enemy->vel.vz = enemy->vel.vz * accel->maxspeed / speed;
	}

	enemy->pos.vx += enemy->vel.vx;
	enemy->pos.vy += enemy->vel.vy;
	enemy->pos.vz += enemy->vel.vz;

// there was another check here for banking

	return FALSE;
}


BOOL NME_AccelTo3D(ENEMYPOS *enemy, VECTOR *dest,const NMEAccelStr *accel)
{
	LONG speed;
	VECTOR temp;
	ULONG sqdist;

	temp.vx = (dest->vx - enemy->pos.vx) >> 12;
	temp.vy = (dest->vy - enemy->pos.vy) >> 12;
	temp.vz = (dest->vz - enemy->pos.vz) >> 12;

	sqdist = (temp.vx * temp.vx + temp.vz * temp.vz + temp.vy * temp.vy);
/*
	if(sqdist < accel->dsquared)
	{
//		printf("arrived!!\n");
		return TRUE;
	}
//	else
//	{
//		printf("   dist %d > %d\n",temp.vx * temp.vx + temp.vz * temp.vz,accel->dsquared);
//	}
*/

	NME_FaceTo( enemy, dest,accel->turnrate );

	enemy->vel.vx -= rsin(enemy->ya) * accel->acceleration/4096;
	enemy->vel.vz -= rcos(enemy->ya) * accel->acceleration/4096;

	if(sqdist < 80 * 80)
	{
		enemy->vel.vx = enemy->vel.vx * 250/256;
		enemy->vel.vz = enemy->vel.vz * 250/256;
	}

	speed = Magnitude(&enemy->vel);


	if(enemy->pos.vy < dest->vy)
		enemy->vel.vy += accel->acceleration;
	if(enemy->pos.vy > dest->vy)
		enemy->vel.vy -= accel->acceleration;
	enemy->vel.vy = enemy->vel.vy * 250/256;

/*
	if(enemy->pos.vy < dest->vy)
		enemy->vel.vy += accel->acceleration / 8;
	if(enemy->pos.vy > dest->vy)
		enemy->vel.vy -= accel->acceleration / 8;
*/
	if ( speed > accel->maxspeed )
	{
		enemy->vel.vx = enemy->vel.vx * accel->maxspeed / speed;
		enemy->vel.vz = enemy->vel.vz * accel->maxspeed / speed;
	}

	enemy->pos.vx += enemy->vel.vx;
	enemy->pos.vy += enemy->vel.vy;
	enemy->pos.vz += enemy->vel.vz;

// there was another check here for banking

	if(sqdist < accel->dsquared)
	{
//		printf("arrived!!\n");
		return TRUE;
	}
//	else
//	{
//		printf("   dist %d > %d\n",temp.vx * temp.vx + temp.vz * temp.vz,accel->dsquared);
//	}

	return FALSE;
}

// no turning, purely a velocity hover
BOOL NME_HoverTo3D(ENEMYPOS *enemy, VECTOR *dest, int accel, int maxspeed)
{
	LONG speed;
	VECTOR temp;
	ULONG sqdist;

	temp.vx = (dest->vx - enemy->pos.vx) >> 12;
	temp.vy = (dest->vy - enemy->pos.vy) >> 12;
	temp.vz = (dest->vz - enemy->pos.vz) >> 12;
	if(temp.vx > 200) temp.vx = 200;
	if(temp.vy > 200) temp.vy = 200;
	if(temp.vz > 200) temp.vz = 200;
	if(temp.vx < -200) temp.vx = -200;
	if(temp.vy < -200) temp.vy = -200;
	if(temp.vz < -200) temp.vz = -200;

	sqdist = (temp.vx * temp.vx + temp.vz * temp.vz + temp.vy * temp.vy);

	enemy->vel.vx += temp.vx * accel;	// note - there's a "/4096" here that's already been done
	enemy->vel.vy += temp.vy * accel;
	enemy->vel.vz += temp.vz * accel;

	if(sqdist < 40 * 40)
	{
		enemy->vel.vx = enemy->vel.vx * 250/256;
		enemy->vel.vy = enemy->vel.vy * 250/256;
		enemy->vel.vz = enemy->vel.vz * 250/256;
	}

	speed = Magnitude(&enemy->vel);

	if ( speed > maxspeed )
	{
		enemy->vel.vx = enemy->vel.vx * maxspeed / speed;
		enemy->vel.vy = enemy->vel.vy * maxspeed / speed;
		enemy->vel.vz = enemy->vel.vz * maxspeed / speed;
	}

	enemy->pos.vx += enemy->vel.vx;
	enemy->pos.vy += enemy->vel.vy;
	enemy->pos.vz += enemy->vel.vz;

	if(sqdist < 20 *20)
	{
		return TRUE;
	}
	return FALSE;
}







void NMEChooseRandomPoint(ENEMYPOS *enemy, int in2d)
{
	VECTOR temp;
	VECTOR offs;
	long ds;
	int i;

	i = 5;
	do
	{
// y should be copied from the nme's current y coord unless it's a "flying" type enemy
		if(enemy->pathvectors[1].vx == 0)
			temp.vx= enemy->pathvectors[0].vx;
		else
			temp.vx= enemy->pathvectors[0].vx + random(enemy->pathvectors[1].vx);
		if(in2d)
		{
			temp.vy = enemy->pos.vy;
		}
		else
		{
			if(enemy->pathvectors[1].vy == 0)
				temp.vy = enemy->pathvectors[0].vy;
			else
				temp.vy = enemy->pathvectors[0].vy + random(enemy->pathvectors[1].vy);
		}
		if(enemy->pathvectors[1].vz == 0)
			temp.vz = enemy->pathvectors[0].vz;
		else
			temp.vz = enemy->pathvectors[0].vz + random(enemy->pathvectors[1].vz);

		offs.vx = (temp.vx - enemy->pos.vx);
		offs.vy = (temp.vy - enemy->pos.vy);
		offs.vz = (temp.vz - enemy->pos.vz);
		ds = offs.vx*offs.vx + offs.vy*offs.vy + offs.vz*offs.vz;
	}while(ds < 4096 * 4096 * 5 * 5 && (--i > 0));	// use an arbitary "slop * slop" value for now...

	enemy->pathvectors[2].vx = temp.vx;
	enemy->pathvectors[2].vy = temp.vy;
	enemy->pathvectors[2].vz = temp.vz;

//	DB("Random point = %d,%d,%d\n",temp.vx>>12,temp.vy>>12,temp.vz>>12);
}

int NMECheckInOurBox(ENEMYPOS *enemy, VECTOR *vec)
{
	VECTOR diff;
	diff.vx = vec->vx - enemy->pathvectors[0].vx;
	diff.vy = vec->vy - enemy->pathvectors[0].vy;
	diff.vz = vec->vz - enemy->pathvectors[0].vz;
	if(    diff.vx < 0 || diff.vx > enemy->pathvectors[1].vx
		|| diff.vy < 0 || diff.vy > enemy->pathvectors[1].vy
		|| diff.vz < 0 || diff.vz > enemy->pathvectors[1].vz
		)
		return FALSE;
	return TRUE;
}

// returns range+1, or zero
int NMECheckInRange(ENEMYPOS *enemy, VECTOR *vec,LONG range, int boxed_enemy)
{
	VECTOR diff;
	int mag;

// check that the target is within the enemy's bounds

	if(boxed_enemy)
	{
		diff.vx = vec->vx - enemy->pathvectors[0].vx;
		diff.vz = vec->vz - enemy->pathvectors[0].vz;
		if(diff.vx < 0 || diff.vx > enemy->pathvectors[1].vx || diff.vz < 0 || diff.vz > enemy->pathvectors[1].vz)
			return FALSE;
	}

// do a square-range test rather than a dist-squared < radius-squared test
	diff.vx = enemy->pos.vx - vec->vx;
	diff.vy = enemy->pos.vy - vec->vy;
	diff.vz = enemy->pos.vz - vec->vz;
	if(diff.vx < -range || diff.vx > range)
		return FALSE;
	if(diff.vy < -range || diff.vy > range)
		return FALSE;
	if(diff.vz < -range || diff.vz > range)
		return FALSE;
	mag = Magnitude(&diff);
	if( mag > range)
		return FALSE;
	return mag+1;
}

int NMECheckInRange2D(ENEMYPOS *enemy, VECTOR *vec,LONG range, int boxed_enemy)
{
	VECTOR diff;
	int mag;

// check that the target is within the enemy's bounds

	if(boxed_enemy)
	{
		diff.vx = vec->vx - enemy->pathvectors[0].vx;
		diff.vz = vec->vz - enemy->pathvectors[0].vz;
		if(diff.vx < 0 || diff.vx > enemy->pathvectors[1].vx || diff.vz < 0 || diff.vz > enemy->pathvectors[1].vz)
			return FALSE;
	}

// do a square-range test rather than a dist-squared < radius-squared test
	diff.vx = enemy->pos.vx - vec->vx;
	diff.vy = 0;
	diff.vz = enemy->pos.vz - vec->vz;
	if(diff.vx < -range || diff.vx > range)
		return FALSE;
	if(diff.vz < -range || diff.vz > range)
		return FALSE;
	mag = Magnitude(&diff);
	if( mag > range)
		return FALSE;
	return mag+1;
}


int NMECheckVisible(ENEMYPOS *enemy, VECTOR *vec, LONG range, SHORT angrange)
{
	VECTOR diff;
	SHORT ya;

	diff.vx = enemy->pos.vx - vec->vx;
	diff.vy = enemy->pos.vy - vec->vy;
	diff.vz = enemy->pos.vz - vec->vz;

	if(diff.vx < -range || diff.vx > range)
		return FALSE;
	if(diff.vy < -range || diff.vy > range)
		return FALSE;
	if(diff.vz < -range || diff.vz > range)
		return FALSE;

	if(Magnitude(&diff) > range)
		return FALSE;

// Pull the vector down into a range that'll be ok for the maths
	diff.vx = diff.vx >>8;
	diff.vy = diff.vy >>8;
	diff.vz = diff.vz >>8;

	ya = (calc_angle(diff.vx, diff.vz) & 4095);
	ya = (ya - enemy->ya) & 4095;


	if(ya < angrange || ya > 4096 - angrange)
	{
		return TRUE;
	}
	return FALSE;
}


// general routine using "doing" states 100...107 for following a path...
void nmeWalkRoundPath(ENEMYPOS *enemy, int speed)
{
	int there;


	if(enemy->doing >= 100 && enemy->doing < 100 + NME_MAX_PATHVECTORS)
	{
		enemy->ticker++;
		if(NME_FaceTo(enemy,&enemy->pathvectors[enemy->doing - 100],80))
		{
			if(enemy->type == YOOFOW)
			{
				AddToQueue(&enemy->anim,NMEANIM_STARTMOVE,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);
			}

			enemy->ticker = 0;
			enemy->doing += NME_MAX_PATHVECTORS;
		}
	}

// Note - it *can* drop through & do this bit without a pause if the only dirn change is vertical
	if(enemy->doing >= 100 + NME_MAX_PATHVECTORS && enemy->doing < 100 + NME_MAX_PATHVECTORS * 2)
	{
		enemy->ticker++;

		if(enemy->type == SWISH)
			there = NME_GoTo2D(enemy,&enemy->pathvectors[enemy->doing - (NME_MAX_PATHVECTORS+100)],speed);
		else
			there = NME_GoTo3D(enemy,&enemy->pathvectors[enemy->doing - (NME_MAX_PATHVECTORS+100)],speed);
		if(there)
		{
			enemy->ticker = 0;
			enemy->doing++;
			if(enemy->doing - (100+NME_MAX_PATHVECTORS) >= enemy->n_points)
				enemy->doing = 100;
			else
				enemy->doing -= NME_MAX_PATHVECTORS;

			if(enemy->type == YOOFOW)
			{
				AddToQueue(&enemy->anim,NMEANIM_STOPMOVE,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
			}
		}
	}


	if(enemy->doing < 100 ||  enemy->doing >= 100 + NME_MAX_PATHVECTORS * 2)
	{
		if(enemy->type == DIBBER)
		{
			if(enemy->doing >= 200 && enemy->doing < 300)	// special case for the ca1 ones, really
				return;

			AddToQueue(&enemy->anim,0,YES,NO,4096);	// dibs only have the one anim
		}
		else
		{
			AddToQueue(&enemy->anim,NMEANIM_WALK,YES,NO,4096);	// anim,numberloop,queue,speed
		}
		enemy->doing = 100;
	}
}

// pick the nearest point on the path, & head for it
// tbd - do (!)
void nmeReturnToPath(ENEMYPOS *enemy)
{
	enemy->doing = -1;
}

/*
#define NME_GRAVITY 1024
#define NME_MAXVEL (4096 * 6)

int NMEDropToGround(ENEMYPOS *enemy)
{
	int hag;

	enemy->pos.vx += enemy->vel.vx;
	enemy->pos.vz += enemy->vel.vz;

	hag = getHeightAt(enemy->pos.vx, enemy->pos.vy + (nmeinfo[enemy->type].hag << 12), enemy->pos.vz);

	enemy->vel.vy += NME_GRAVITY;
	if(enemy->vel.vy > NME_MAXVEL)
		enemy->vel.vy = NME_MAXVEL;

	if(hag != -1)
	{
		if(hag < enemy->vel.vy)
		{
			enemy->vel.vy = hag;
			enemy->pos.vy += enemy->vel.vy;
			return 1;
		}
	}
	enemy->pos.vy += enemy->vel.vy;
	return 0;
}
*/


void PlaceNMEOnGround(ENEMYPOS *enemy, int fall)
{
	int hag;

	hag = getHeightAt(enemy->pos.vx, enemy->pos.vy + (nmeinfo[enemy->type].hag << 12) - NME_H_FRIG, enemy->pos.vz);
//	if(hag != -1)
	{
		enemy->pos.vy += (hag - NME_H_FRIG);
		return;
	}
//	if(fall)
//		enemy->pos.vy += 4096 * 2;
}

// ============== Not-Quite-Physics that makes enemies avoid large drops & high walls ==============

#define SCAN_OK		  0
#define SCAN_BIGDROP -1
#define SCAN_BIGWALL -2
int NME_ScanPosition(ENEMYPOS *enemy, VECTOR *pos, int maxrise, int maxfall, int *hagp)
{
	int hag;
	hag = getHeightAt(pos->vx, pos->vy + (nmeinfo[enemy->type].hag << 12) - NME_H_FRIG, pos->vz);

//	if(hag == -1)
//	{
//		*hagp = 4096 * 200;
//		return SCAN_BIGDROP;
//	}
	hag -= NME_H_FRIG;
	*hagp = hag;
	if(hag > maxfall)
	{
		return SCAN_BIGDROP;
	}
	if(hag < -maxrise)
	{
		return SCAN_BIGWALL;
	}
	return SCAN_OK;
}

int NME_UppyDowny(ENEMYPOS *nme, VECTOR *pos, int hag)
{
	if(hag < 0)
	{
		pos->vy += hag;
//		nme->vel.vy = hag;
		if(nme->vel.vy > 0)
			nme->vel.vy = 0;
		nme->pos = *pos;
		return 1;
	}
	else
	{
//		pos->vy += hag;
//		nme->vel.vy = 0;

		if(hag < nme->vel.vy)
		{


			pos->vy += hag;
			if(nme->vel.vy > 4096 * 6)
			{
				nme->vel.vy = -nme->vel.vy /2;
			}
			else
			{
				nme->vel.vy = 0;
			}
			nme->pos = *pos;
			return 1;
		}
		else
		{
			pos->vy += nme->vel.vy;
			nme->vel.vy += gravity;
			nme->pos = *pos;
			return 0;
		}

	}
//	nme->pos = *pos;
//	return 0;

}

// move an enemy around with a suggested velocity
// (point collision)

int Kong_HitWall;
int KeepNMEOnGround(ENEMYPOS *nme, int maxrise, int maxfall)
{
	int hag;
	VECTOR test;

	Kong_HitWall = 0;

	ADDVECTOR(&test,&nme->pos,&nme->vel);
	if(!NME_ScanPosition(nme,&test,maxrise,maxfall,&hag))
	{
		return NME_UppyDowny(nme,&test,hag);
	}

	Kong_HitWall = 1;
// try locking the horizontal
	test.vx = nme->pos.vx;
	if(!NME_ScanPosition(nme,&test,maxrise,maxfall,&hag))
	{
		return NME_UppyDowny(nme,&test,hag);		
	}

// try locking the vertical
	ADDVECTOR(&test,&nme->pos,&nme->vel);
	test.vz = nme->pos.vz;
	if(!NME_ScanPosition(nme,&test,maxrise,maxfall,&hag))
	{
		return NME_UppyDowny(nme,&test,hag);
	}

// Do gravity on our current position
	test = nme->pos;
	NME_ScanPosition(nme,&test,maxrise,maxfall,&hag);
	return NME_UppyDowny(nme,&test,hag);
}


void NMEWaterBob(ENEMYPOS *enemy)
{
	if(enemy->pos.vy < enemy->pathvectors[0].vy) // - radius)
	{
		enemy->vel.vy += 1024;
		enemy->pos.vy += enemy->vel.vy;
	}
	else
	{
		enemy->vel.vy -= 2048;
		if(enemy->vel.vy < -4096)
			enemy->vel.vy = -4096;
		enemy->pos.vy += enemy->vel.vy;
	}
}

// littlefish (gordon)
//	NORMAL_INSTRUCTION		NME_RANDOMMOVE	-1		57 116 -2366	250 10 230     160 1 ALWAYS	0 0

void Update_LittleFish(ENEMYPOS *enemy)
{
	static const NMEAccelStr fishaccel =
	{
		0x180,
		0x2400,
		100,
		1000
	};
	switch(enemy->doing)
	{
		case 0:	// picking a rnd position
			enemy->ticker = 0;		
			enemy->doing = 1;

			NMEChooseRandomPoint(enemy,YES);

// ... dropthru
		case 1:

			{
				enemy->ticker++;
				if(NME_AccelTo2D(enemy,&enemy->pathvectors[2],&fishaccel) || enemy->ticker > 300)
				{
					enemy->doing = 0;
				}
				enemySideDamp(enemy, 3800);

			}
			break;
		default:
			enemy->doing = 0;
			AddToQueue(&enemy->anim,NMEANIM_WALK,YES,NO,4096);	// anim,numberloop,queue,speed
			break;
	}
}


void Update_Rnd3dMover(ENEMYPOS *enemy)
{
	switch(enemy->doing)
	{
		case 0:	// picking a rnd position
			enemy->ticker = 0;		
			enemy->doing = 1;

			NMEChooseRandomPoint(enemy,NO);

// ... dropthru
		case 1:

			{
				enemy->ticker++;
//				if(NME_AccelTo3D(enemy,&enemy->pathvectors[2],&fishaccel) || enemy->ticker > 300)
				NME_FaceTo(enemy,&enemy->pathvectors[2],100);
				if(NME_GoTo3D(enemy,&enemy->pathvectors[2],0x2400))
				{
					enemy->doing = 0;
				}
			}
			break;
		default:
// bats rely on being in their walking anim
			if(nmeinfo[enemy->type].flags & NME_ONEANIM)
			{
				AddToQueue(&enemy->anim,0,YES,NO,8192);	// anim,numberloop,queue,speed (butterfly)
			}
			else
			{
				AddToQueue(&enemy->anim,NMEANIM_WALK,YES,NO,4096);	// anim,numberloop,queue,speed
			}

			enemy->doing = 0;
			break;
	}
}

// ========== It's the amazing new enemy-based mr tip! ========

void Update_MrTip(ENEMYPOS *enemy)
{
	VECTOR pos;
	if(enemy->doing == 1)
	{
		enemy->ticker += 6;	// 16 = one per frame
	}
	else
	{
		enemy->ticker += 9;	// 16 = one per frame
		pos.vx = (enemy->pos.vx) + rsin((sinewave1>>16) & 4095) * 14;
		pos.vy = (enemy->pos.vy + enemy->vel.vx) + rsin((sinewave2>>16) & 4095) * 6;
		pos.vz = (enemy->pos.vz) + rcos((sinewave1>>16) & 4095) * 14;
		New_Debris(DEBRIS_LIFEGLOW,&pos,0);
	}
	while(enemy->ticker > (11 << 4))
		enemy->ticker -= (11 << 4);


// vel.vx is an addition to vy
	enemy->vel.vx += enemy->vel.vy;

	enemy->vel.vy += gravity;
	if(enemy->vel.vx > 0)
	{
		enemy->vel.vy = -enemy->vel.vy / 2;

		if(enemy->vel.vy > -gravity*2)
			enemy->vel.vy = 0;

		enemy->vel.vx = 0;
	}

	if(level == HUB1 && !MrTipDimmed(401)
		&& enemy->init_ya == 401
		&& GloveCtrl.enabled
		&& GloveCtrl.onGround

		&& gameInfo.keyRecordFlag!=PLAYBACK
		&& gameInfo.keyRecordFlag!=RECORD
		)
	{
		GloveCtrl.tipFlag=TRUE;
		GloveCtrl.tipNumber=401;

		gameCtrl.MrTipActive=TRUE;
		gameCtrl.state=FALSE;
		DB ("activating tip %d\n", enemy->init_ya);
		GloveCtrl.disableTimer=10;
//		aHeld=0;
		ActorStartRoll(pBallPSA, 0 , 0 );
//		enemy->doing = 1;


	}
}

void TipIntRou(int with, ENEMYPOS *enemy, VECTOR *dpos, int sphererad)
{
	int tipno;

	if(with == 0)
	{
		tipno = enemy->init_ya;

//		if(enemy->vel.vy == 0 && (enemy->pos.vy == enemy->init_pos.vy << 12))
		if(enemy->vel.vx == 0)
		{
			enemy->vel.vy = - (((unsigned int)(RANDOM256())) << 5) - (4096 * 4);
		}

		if(!tipaddr)
			return;
											//	handOverTip(enemy,tipno);
											//void handOverTip(ENEMYPOS *enemy, int tipno)
		//DB ("glove close to tip num %d\n",tipno);
		GloveCtrl.tipFlag=TRUE;
		GloveCtrl.tipNumber=tipno;
	}
}

void enemyDimTip(int number)
{
	ENEMYPOS *nme;
	for (nme = nme_list; nme; nme = nme->next)
	{
		if(nme->type == PICKUP_ENEMY_MRTIP && nme->init_ya == number)
		{
			nme->doing = 1;
		}
	}
}

int MrTipDimmed(int number)
{
	ENEMYPOS *nme;
	for (nme = nme_list; nme; nme = nme->next)
	{
		if(nme->type == PICKUP_ENEMY_MRTIP && nme->init_ya == number)
		{
			return(nme->doing == 1);
		}
	}
	return 0;
}

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

// fract is 4096 for no side-damping
void enemySideDamp(ENEMYPOS *enemy, int fract)
{
	int fvel;
	int svel;
	int s,c;

	s = rsin((enemy->ya + 2048) & 4095);
	c = rcos((enemy->ya + 2048) & 4095);
	fvel  = ( enemy->vel.vx * s) >> 12;
	fvel += ( enemy->vel.vz * c) >> 12;

	svel  = ( enemy->vel.vx * c) >> 12;
	svel += (-enemy->vel.vz * s) >> 12;
//	DB("enemy vels f=%d, s=%d\n",fvel>>8, svel>>8);

	enemy->vel.vx = (fvel * s) >> 12;
	enemy->vel.vz = (fvel * c) >> 12;

	svel = (svel * fract) >> 12;
	enemy->vel.vx += (svel * c) >> 12;
	enemy->vel.vz += (-svel * s) >> 12;

}

void Update_Rnd3dGroundMover(ENEMYPOS *enemy)
{

	static const NMEAccelStr fishaccel =
	{
		0x180,
		0x2400,
		100,
		1000
	};
	switch(enemy->doing)
	{
		case 0:	// picking a rnd position
			enemy->ticker = 0;		
			enemy->doing = 1;

			NMEChooseRandomPoint(enemy,NO);

// ... dropthru
		case 1:

			{
//				if(enemy->type == LADYBIRD)
//					printf("enemypos %d %d %d\n",enemy->pos.vx>>12,enemy->pos.vy>>12,enemy->pos.vz>>12);

				enemy->ticker++;
//				if(NME_AccelTo2D(enemy,&enemy->pathvectors[2],&fishaccel) || enemy->ticker > 300)
				if(NME_AccelTo2D(enemy,&enemy->pathvectors[2],&fishaccel) || enemy->ticker > 300)
				{
					enemy->doing = 0;
				}
				PlaceNMEOnGround(enemy,1);
				enemySideDamp(enemy,3500);
			}
			break;
		default:
			enemy->pos.vy -= 4096 * 100;
			enemy->doing = 0;
			break;
	}
}




#define MALLETSPEED (4 * 4096)

// Mallet:- (faceto, special,wait,moveto2d(RUN,NOMOVE,MOVE_VEL),wait(DONTANIMATE)) *2
void Update_Mallet(ENEMYPOS *enemy)
{
//	static SFXHANDLE hStampede;	// obviously can't work if there's more than one of 'em

	switch(enemy->doing)
	{
		case 0:	// face the other posn, go with the special when we're there

			enemy->ticker++;
			if(!(enemy->ticker & 7))
			{
				if(!(enemy->ticker & 3))
					sfxPlayNME3D(levelFX, SFX_MALLET_WALK_LEFT,enemy);
				else
					sfxPlayNME3D(levelFX, SFX_MALLET_WALK_RIGHT,enemy);
			}

			if(NME_FaceTo(enemy,&enemy->pathvectors[0],64))
			{
				AddToQueue(&enemy->anim,NMEANIM_SPECIAL2,NO,NO,4096);	// anim,numberloop,queue,speed
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);	// anim,numberloop,queue,speed
				enemy->ticker = 0;
				enemy->doing++;
			}
			break;

		case 1:
			enemy->ticker++;


			if(enemy->ticker >= 100)
			{
				enemy->doing++;
// note - he shouldn't MOVE during the startmove
				AddToQueue(&enemy->anim,NMEANIM_STARTMOVE,NO,NO,4096);	// anim,numberloop,queue,speed
				AddToQueue(&enemy->anim,NMEANIM_RUN,YES,YES,4096);	// anim,numberloop,queue,speed
			}
			break;

		case 2:	// run across to the other posn
			enemy->ticker++;
			if(enemy->ticker >= 10)
			{
				sfxPlayNME3D(levelFX, SFX_MALLET_STAMPEDE,enemy);
				enemy->ticker = 0;
			}

			if(NME_GoTo2D(enemy,&enemy->pathvectors[0],MALLETSPEED))
			{
				AddToQueue(&enemy->anim,NMEANIM_STOPMOVE,NO,NO,4096);	// anim,numberloop,queue,speed
				AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);	// anim,numberloop,queue,speed

//				sfxStopVoice( (int)enemy->extras);

				//SFXShutUp(hStampede);
				sfxPlayNME3D(globalFX, SFX_HAND_SKID,enemy);

				enemy->doing++;
			}
			break;
		case 3: // pause
			if(enemy->animinfo.num == NMEANIM_WALK)
			{
// this is where it moos
//				NMEPlaySound(levelFX, SFX_MALLET_WAIT,64,32, 300);
				sfxPlayNME3D(levelFX, SFX_MALLET_WAIT,enemy);

				enemy->doing++;
			}
			break;




		case 4:
			enemy->ticker++;
			if(!(enemy->ticker & 7))
			{
				if(!(enemy->ticker & 3))
					sfxPlayNME3D(levelFX, SFX_MALLET_WALK_LEFT,enemy);
				else
					sfxPlayNME3D(levelFX, SFX_MALLET_WALK_RIGHT,enemy);
			}

			if(NME_FaceTo(enemy,&enemy->pathvectors[1],64))
			{
				AddToQueue(&enemy->anim,NMEANIM_SPECIAL2,NO,NO,4096);	// anim,numberloop,queue,speed
// settles down, & waits a while
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);	// anim,numberloop,queue,speed
				enemy->ticker = 0;
				enemy->doing++;
			}
			break;
		case 5:
			enemy->ticker++;
			if(enemy->ticker >= 100)
			{
// jumps up, & charges
				enemy->doing++;
				AddToQueue(&enemy->anim,NMEANIM_STARTMOVE,NO,NO,4096);	// anim,numberloop,queue,speed
				AddToQueue(&enemy->anim,NMEANIM_RUN,YES,YES,4096);	// anim,numberloop,queue,speed
			}
			break;

		case 6:
			enemy->ticker++;
			if(enemy->ticker >= 10)
			{
				sfxPlayNME3D(levelFX, SFX_MALLET_STAMPEDE,enemy);
				enemy->ticker = 0;
			}
			if(NME_GoTo2D(enemy,&enemy->pathvectors[1],MALLETSPEED))
			{
// shids to halt & idles on the spot *standing up* (all of which is in the stopmove anim

				AddToQueue(&enemy->anim,NMEANIM_STOPMOVE,NO,NO,4096);	// anim,numberloop,queue,speed
				AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);	// anim,numberloop,queue,speed

				//SFXShutUp(hStampede);
//				NMEPlaySound(globalFX, SFX_HAND_SKID,64,40, 300);
				sfxPlayNME3D(globalFX, SFX_HAND_SKID,enemy);

				enemy->doing++;
			}
			break;

		case 7:
			if(enemy->animinfo.num == NMEANIM_WALK)
			{
// this is where it moos
				enemy->doing = 0;
//				sfxStopVoice( (int)enemy->extras);

//				NMEPlaySound(levelFX, SFX_MALLET_WAIT,64,32, 400);
				sfxPlayNME3D(levelFX, SFX_MALLET_WAIT,enemy);

			}
			break;

		default:	// default is the "startup"
			AddToQueue(&enemy->anim,NMEANIM_WALK,YES,NO,4096);	// anim,numberloop,queue,speed
			enemy->doing = 0;
			break;
	}
	PlaceNMEOnGround(enemy, 1);
}


/*
 ENEMY DIBBER -392.000000 115.000000 361.000000 0 0.000000
 NORMAL_INSTRUCTION NME_SPECIAL 1 -1 0 1 ALWAYS 0 0
 NORMAL_INSTRUCTION NME_FACETO -1 -392.083740 114.811325 361.392639 0 16384 ALWAYS 0 0
 NORMAL_INSTRUCTION NME_MOVETO -1 -392.083740 114.811325 361.392639 10 16385 ALWAYS 0 0
 NORMAL_INSTRUCTION NME_FACETO -1 -565.083740 114.811325 361.392639 0 16384 ALWAYS 0 0
 NORMAL_INSTRUCTION NME_MOVETO -1 -565.083740 114.811325 361.392639 10 16385 ALWAYS 0 0
  NORMAL_INSTRUCTION NME_BRANCH  1  1 0  0 ALWAYS 0 0
 END_ENEMY
*/
// tbd - we need a points counter
void Update_Dibber(ENEMYPOS *enemy)
{
	/*
	switch(enemy->doing)
	{
	case -1:
		AddToQueue(&enemy->anim,0,YES,NO,4096);	// dibs only have the one anim
		enemy->doing = 0;
		break;
	default:	// moveto2d
		nmeWalkRoundPath(enemy,3*4096);
		break;
	}
*/
	nmeWalkRoundPath(enemy,3*4096);
}


// note - the fish's jump has a "don't animate" flag on it
// does a startattack (which moves forward a bit)
// then jumps, long distance & forwards
// hangs around at the top for a while.
// Drops & bobs in the water
// goes forward to the finish

// The instructions are...
//	ATTACK_INSTRUCTION		NME_WAIT		20		0				1	16384	ALWAYS	0	0
//	ATTACK_INSTRUCTION		NME_JUMP		60		0 9 3	 	16384		0	ALWAYS	0	0
//	ATTACK_INSTRUCTION		NME_WAIT		30		0				1	16384	ALWAYS	0	0
//	ATTACK_INSTRUCTION		NME_ENDATTACK	0		0				0	16384	ALWAYS	0	0





void Update_NMEFrog(ENEMYPOS *nme)
{
	switch(nme->doing)
	{
	case 0:	// waiting / aiming
		nme->vel.vx = 0;
		nme->vel.vz = 0;
		nme->ticker--;

		if(RANDOM256() < 6)
		{
			int i;
			i = (RANDOM256()-128);
			sfxSetSamplePitch(globalFX, SFX_FROG_CALL, i+DEFAULT_PITCH);
			sfxPlayNME3D(globalFX, SFX_FROG_CALL,nme);

		}


		nme->ya = AngleHomer(nme->ya, nme->init_ya, 30);

		if(!nme->ticker)
		{
			AddToQueue(&nme->anim,NMEANIM_JUMP,NO,NO,4096);
			nme->doing = 1;

// If enemies are going to be let out of froggification,
// don't let the buggers move around
// (otherwise enemies'll have to cope with being out-of-box)
// Or... Leave 'em frogged permanantly

			if(nme->type == BABY_SPANK)
			{
				nme->vel.vx = -3 * rsin(nme->ya);
				nme->vel.vz = -3 * rcos(nme->ya);
				nme->vel.vy = -5 << 12;
			}
			else
			{
				nme->vel.vx = -5 * rsin(nme->ya);
				nme->vel.vz = -5 * rcos(nme->ya);
				nme->vel.vy = -7 << 12;
			}
		}
		KeepNMEOnGround(nme,20,1000000);	// Frogs don't mind large drops
		break;

	case 1:	// hopping
		if(KeepNMEOnGround(nme,20,1000000))
		{
			nme->doing = 0;
			nme->ticker = (RANDOM256() & 31) + 32;
			nme->init_ya = (RANDOM256()) << 4;
			AddToQueue(&nme->anim,NMEANIM_IDLE,YES,NO,4096);
		}
		break;

	case -1:	// just started
		nme->ticker = (RANDOM256() & 31) + 32;

		nme->init_ya = (RANDOM256()) << 4;
		nme->doing = 0;
		AddToQueue(&nme->anim,NMEANIM_IDLE,YES,NO,4096);

		break;
	}
//	DB("frog doing %d\n",nme->doing);
}

// disable the enemy. Stick a frog in the same place
// If the enemy was bugle/cluck/whatever, do whatever's necessary
// to switch their states such that they don't retain the ball
// awww, soddit, make the thing permanant... Life is so much easier

void EnemyFroggify(ENEMYPOS *src_enemy)
{
	ENEMYPOS *nme;

// not a particularly pleasany botch, but it'll do nicely.
	enemiesFreeNmeExtraMemory(src_enemy);	// get rid of Bugle's physics space, etc

	src_enemy->n_points = 0;
	switch(src_enemy->type)
	{
	case BUGLE:
		BallCtrl.inside_bugle = 0;
		break;

	}
	if(nmeinfo[src_enemy->type].flags & DROPS_GARIB)
	{
		src_enemy->n_points = 1;
	}

/*
	nme = enemy_FindFreeEnemy();
	if(!nme)
		return;

// tbd - drop a garib if you need to
	New_StarRingDebris(&src_enemy->pos, 6);
	src_enemy->flags |= NMEFLAG_FROGGED;
	src_enemy->flags &= ~NMEFLAG_ACTIVE;

	nme->pos = src_nme->pos;
	(void *)enemy->extras = nme;
*/
	nme = src_enemy;

	nme->type = NME_FROG;
	nme->flags = NMEFLAG_ACTIVE + NMEFLAG_ENABLED;

	nme->ticker = 0;	// set the thing off
	nme->doing = -1;

	nme->xa = 0;	// tilt bovva, etc upright...
	nme->za = 0;


	nme->psa =	nmeFrogModel->psa;
	nme->animinfo.segInfo = nmeFrogModel->seg;
	nme->anim.animInfo = &nme->animinfo;

	effectsStartOverlay(10, 0x00ff00);
}





#define LIONFISHSPEED (5 * 4096)

#define LIONFISH_MOVETO0	0

#define LIONFISH_FACETO1	1
#define LIONFISH_JUMPTO1	2
#define LIONFISH_JUMPINGTO1	3
#define LIONFISH_MOVETO1	4

#define LIONFISH_FACETO0	5
#define LIONFISH_JUMPTO0	6
#define LIONFISH_JUMPINGTO0	7

void LionFishEffect(int i,ENEMYPOS *nme)
{
	switch(world)
	{
	case ATLANTIS:
	break;
	case PIRATES:
		i = i - SFX_SHIRLEY_SWIM_FINS + SFX_SHIRLEYSWIM_FINS_P;
	break;
	default:
		return;
	}
	sfxPlayNME3D(levelFX, i,nme);
}

void Update_Lionfish(ENEMYPOS *enemy)
{
	switch(enemy->doing)
	{
		case LIONFISH_MOVETO0:	// moveto2d
			if(!(frame & 7))
			{
				LionFishEffect(SFX_SHIRLEY_SWIM_FINS,enemy);
			}

			NMEWaterBob(enemy);
			if(NME_GoTo2D(enemy,&enemy->pathvectors[0],LIONFISHSPEED))	//3 * 4096))
			{
				enemy->doing = LIONFISH_FACETO1;
			}
			break;
		case LIONFISH_FACETO1:	// faceto
			NMEWaterBob(enemy);
			if(NME_FaceTo(enemy,&enemy->pathvectors[1],64))
			{
				enemy->doing=LIONFISH_JUMPTO1;
				AddToQueue(&enemy->anim,NMEANIM_STARTATTACK,NO,NO,4096);
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);	// idle's the wrong anim for this
				enemy->ticker = 0;
			}
			break;
		case LIONFISH_JUMPTO1:	// attack1 = wait
			NMEWaterBob(enemy);
			enemy->ticker++;
			if(enemy->ticker >= 30)
			{
				enemy->vel.vy = -5 * 4096;
				enemy->pos.vy += enemy->vel.vy;
				enemy->doing = LIONFISH_JUMPINGTO1;
				LionFishEffect(SFX_SHIRLEY_LAUNCH,enemy);
			}
			break;

		case LIONFISH_JUMPINGTO1: // attack2 = jump
			enemy->ticker++;
//			enemy->pos.vy += enemy->vel.vy;
//			enemy->vel.vy += 512;

			NMEWaterBob(enemy);

			NME_GoTo2D(enemy,&enemy->pathvectors[1],LIONFISHSPEED);	//3 * 4096);

			if(enemy->ticker >= 60)
			{
				enemy->doing=LIONFISH_MOVETO1;
			}
			break;

		case LIONFISH_MOVETO1:
			NMEWaterBob(enemy);
			if(NME_GoTo2D(enemy,&enemy->pathvectors[1],LIONFISHSPEED))
			{
				enemy->doing = LIONFISH_FACETO0;
			}
			break;

		case LIONFISH_FACETO0:
			NMEWaterBob(enemy);
			if(NME_FaceTo(enemy,&enemy->pathvectors[0],64))
			{

				enemy->doing= LIONFISH_JUMPTO0;
				AddToQueue(&enemy->anim,NMEANIM_STARTATTACK,NO,NO,4096);
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);	// idle's the wrong anim for this
				enemy->ticker = 0;
			}
			break;

		case LIONFISH_JUMPTO0:
			NMEWaterBob(enemy);
			enemy->ticker++;
			if(enemy->ticker >= 30)
			{
				enemy->vel.vy = -5 * 4096;
				enemy->pos.vy += enemy->vel.vy;
				enemy->doing = LIONFISH_JUMPINGTO0;
				LionFishEffect(SFX_SHIRLEY_LAUNCH,enemy);
			}
			break;

		case LIONFISH_JUMPINGTO0:
			enemy->ticker++;
			NMEWaterBob(enemy);
			NME_GoTo2D(enemy,&enemy->pathvectors[0],LIONFISHSPEED);	//3 * 4096);
			if(enemy->ticker >= 60)
			{
				enemy->doing = LIONFISH_MOVETO0;
			}
			break;


		default:
			AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,NO,4096);
			enemy->doing = LIONFISH_MOVETO0;
			break;
	}

	{
		int temp;
		VECTOR tempv;
		tempv.vx=enemy->pos.vx/4096;
		tempv.vy=enemy->pos.vy/4096;
		tempv.vz=enemy->pos.vz/4096;

		enemy->extras = (void *)waterDoSplashiness(&tempv, 30, (int)enemy->extras, &temp);
	}
}




/*
 ENEMY GENERALWU 26.000000 63.000000 -146.000000 0 0.000000
 

// 5 = move_accel + don't_slow_when_near
   NORMAL_INSTRUCTION NME_RANDOMMOVE
     -1
     -107.000000 28.000000 -263.932190
     226.419205 142.795013 250.932190
       60
        5
        ALWAYS 0 0
  NORMAL_INSTRUCTION NME_BRANCH 0 0 0 0 ALWAYS 0 0

   CONDITIONAL_INSTRUCTION NME_FOLLOWPLAYER 50 0 0 0 0 69 IF_HAND_IN_RANGE 0 100
   CONDITIONAL_INSTRUCTION NME_ATTACK -1 0 0 0 IF_HAND_IN_RANGE 0 70	

// ie, if it can't see the hand, it drops down to the follow-player code... 
   ATTACK_INSTRUCTION NME_WAIT 2 3 1 0 IF_CANSEE_HAND 1 0
   ATTACK_INSTRUCTION NME_SPECIAL -1 0 0 0 IF_CANSEE_HAND 1 0
   ATTACK_INSTRUCTION NME_ENDATTACK 0 0 0 16384 ALWAYS 0 0
 
 
 END_ENEMY
*/
/*
SFX_GENERALWU_DIVESNAP = 0x40,
SFX_GENERALWU_HITGROUND,	// 6k
SFX_GENERALWU_PULLUP,
SFX_GENERALWU_WALK_LEFT,
SFX_GENERALWU_WALK_RIGHT,
*/

void Update_GeneralWu(ENEMYPOS *enemy)
{
	static const NMEAccelStr sharkaccel =
	{
		0x180,
		0x2400,
		100,
		1000
	};

	static int oldFrame=0;
	int newFrame;
	int rnd2;

	switch(enemy->doing)
	{
		case 0:	// picking a rnd position
			enemy->ticker = 0;		
			enemy->doing = 1;

			NMEChooseRandomPoint(enemy,YES);

// ... dropthru
		case 1:
			rnd2 = RANDOM256();

			newFrame=iframe&0x200;

			if(oldFrame!=newFrame)
			{
				if(rnd2 >= 128)
//					sfxPlayNME3D(levelFX, SFX_GENERALWU_WALK_LEFT,enemy);
					sfxPlayVol3D(levelFX, SFX_GENERALWU_WALK_LEFT,&enemy->pos,32);
				else
					sfxPlayVol3D(levelFX, SFX_GENERALWU_WALK_RIGHT,&enemy->pos,32);
//					sfxPlayNME3D(levelFX, SFX_GENERALWU_WALK_RIGHT,enemy);
			}

			oldFrame=newFrame;

			if(NMECheckInRange(enemy, &glovePos, 4096 * 100,YES))
			{
				if(NMECheckVisible(enemy, &glovePos, 4096 * 70, 128))
				{
					AddToQueue(&enemy->anim,NMEANIM_STARTATTACK,NO,NO,4096);
					AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
					enemy->doing = 2;
					enemy->ticker =0;
				}
				NME_AccelTo2D(enemy,&glovePos,&sharkaccel);
			}
			else
			{
				enemy->ticker++;
				if(NME_AccelTo2D(enemy,&enemy->pathvectors[2],&sharkaccel) || enemy->ticker > 300)
				{
					enemy->doing = 0;
				}
			}

			break;

		case 2:	// await,attack wait,lift
			enemy->ticker++;

			if(enemy->ticker == 1)
				sfxPlayNME3D(levelFX, SFX_GENERALWU_DIVESNAP,enemy);
			else if(enemy->ticker == 10)
				sfxPlayNME3D(levelFX, SFX_GENERALWU_HITGROUND,enemy);
			else if(enemy->ticker == 30)
				sfxPlayNME3D(levelFX, SFX_GENERALWU_PULLUP,enemy);



			if(enemy->animinfo.num == NMEANIM_IDLE)
			{
				enemy->doing = 0;
			}
			break;


		default:
			PlaceNMEOnGround(enemy,1);
			enemy->init_pos.vy = enemy->pos.vy >> 12;
			enemy->doing = 0;
			break;
	}

	PlaceNMEOnGround(enemy, 1);

}



/*
// okeydokey, here's denis's instruction set...
ENEMY DENIS 330 10 -180 0 0
	NORMAL_INSTRUCTION		NME_BRANCH		-1		7	0		0		IF_OUT_OF_BOUNDS 0 0

	NORMAL_INSTRUCTION		NME_BRANCH		-1		7	0		0		IF_OUT_OF_BOUNDS 0 0
	NORMAL_INSTRUCTION		NME_FACEPLAYER	-1		0	0		16512	ALWAYS 0 0 (&4080 - don't animate, specify ball)
	NORMAL_INSTRUCTION		NME_WAIT		30		0	1		2097152	ALWAYS	0	0 (&20 0000 - faceball)
	NORMAL_INSTRUCTION		NME_SPECIAL		-1		0	0		2097152	ALWAYS 0 0 (face ball)
	NORMAL_INSTRUCTION		NME_JUMP		-1	0 12 6	16384	8388736	ALWAYS 0 0 (&80 0080) (land on player,specify ball

	NORMAL_INSTRUCTION		NME_BRANCH		-1		7	0		0	ALWAYS 0 0


	NORMAL_INSTRUCTION		NME_BRANCH		-1		1	0			0	 	IF_BALL_IN_RANGE_2D 0 100 
	NORMAL_INSTRUCTION		NME_FACETO		-1		480 10 -180	0	16384	ALWAYS 0 0 (don't animate)
	NORMAL_INSTRUCTION		NME_WAIT		30		0	1			0	 	ALWAYS	0	0
	NORMAL_INSTRUCTION		NME_SPECIAL		-1		0	0			0	 	ALWAYS 0 0
	NORMAL_INSTRUCTION		NME_JUMP		-1	0 12 6	16384		8388736	ALWAYS 0 0

	NORMAL_INSTRUCTION		NME_BRANCH		-1		1	0			0		IF_BALL_IN_RANGE_2D 0 100 
	NORMAL_INSTRUCTION		NME_FACETO		-1		330 10 -180	0	16384	ALWAYS 0 0	(don't animate)
	NORMAL_INSTRUCTION		NME_WAIT		30		0	1			0		ALWAYS	0	0
	NORMAL_INSTRUCTION		NME_SPECIAL		-1		0	0			0		ALWAYS 0 0
	NORMAL_INSTRUCTION		NME_JUMP		-1	0 12 6	16384		8388736	ALWAYS 0 0

	NORMAL_INSTRUCTION		NME_BRANCH		-1		7	0		0	ALWAYS 0 0

CONFINE_ENEMY	320 0 -420 370 130 440
END_ENEMY
*/

// denis's anims don't seem to match up with any sensible events
// (ie all of the normal anims end with a twitch, the jump doesn't link into or out of anything...)
#define DENIS_NORMAL 	0
#define DENIS_GLOVED	1
#define DENIS_DISMOUNT	2
#define DENIS_PREJUMP   3
#define DENIS_JUMPING   4
#define DENIS_RETURNING 5
#define DENIS_RETURN_PREJUMP 6
#define DENIS_RETURN_JUMP 7
#define DENIS_RETURN_WAIT 8

extern COLLDATA	DennisColl;
extern VECTOR		DennisPos,DennisVel;



// tbd - deal with denis falling off the world

void Update_Denis(ENEMYPOS *enemy)
{
	int temp;
	int on_gnd;

	if(enemy->pos.vy >= world_base_y)
	{
		enemy->flags &= ~(NMEFLAG_ACTIVE);
	}

	switch(enemy->doing)
	{
		case DENIS_NORMAL:
			enemy->vel.vx = 0;
			enemy->vel.vy = 0;
			enemy->vel.vz = 0;
			if(NME_FaceTo(enemy,&ballPos,32))
			{
/*
				printf("box = %d %d %d + %d %d %d... ball = %d %d %d\n",
					enemy->pathvectors[0].vx >>12,enemy->pathvectors[0].vy >>12,enemy->pathvectors[0].vz >>12,
					enemy->pathvectors[1].vx >>12,enemy->pathvectors[1].vy >>12,enemy->pathvectors[1].vz >>12,
					ballPos.vx >>12,ballPos.vy >>12,ballPos.vz >>12);
*/
				if(NMECheckInOurBox(enemy, &ballPos))
				{
					enemy->pathvectors[2] = ballPos;
					enemy->doing = DENIS_PREJUMP;

					AddToQueue(&enemy->anim,NMEANIM_SPECIAL1,NO,NO,2048);
					AddToQueue(&enemy->anim,NMEANIM_JUMP,NO,YES,2048);
				}
			}
			break;

		case DENIS_PREJUMP:
			temp=enemy->anim.animInfo->frame;
			temp+=enemy->anim.animInfo->segInfo[enemy->anim.currentAnimation].segStart;
			if (temp>=61)
			{
				sfxPlayNME3D(globalFX, SFX_GE_DENNIS_LAUNCH,enemy);

				enemy->vel.vy = -6 * 4096;
				enemy->vel.vx = -2 * rsin(enemy->ya);
				enemy->vel.vz = -2 * rcos(enemy->ya);

				enemy->pos.vy += enemy->vel.vy;
				enemy->doing = DENIS_JUMPING;
			}
			break;


		case DENIS_JUMPING:
		{
			ADDVECTOR(&enemy->pos, &enemy->pos, &enemy->vel);
//			NME_GoTo2D(enemy,&enemy->pathvectors[2],2 * 4096);
/*
			printf("enemy pos = %d, init pos = %d, vel = %d\n",
				enemy->pos.vy>>12,
				enemy->init_pos.vy,
				enemy->vel.vy
			   	);
*/

			if(enemy->pos.vy < enemy->init_pos.vy << 12)
			{
				enemy->vel.vy += 1024;
//				enemy->pos.vy += enemy->vel.vy;
			}
			else
			{
				sfxPlayNME3D(levelFX, SFX_CA_DENNIS_LAND,enemy);
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,NO,2048);
				enemy->pos.vy = enemy->init_pos.vy << 12;
				enemy->doing = DENIS_NORMAL;
			}		  
			break;
		}


// if the glove's gotten off, drop to the ground, don't interact for a while, head for home
		case DENIS_DISMOUNT:
			DennisPos = enemy->pos;
			DennisVel = enemy->vel;

			DennisVel.vy += 1024;	//gravity;
			ADDVECTOR(&DennisPos, &DennisPos, &DennisVel);

			DennisPos.vy -= DennisColl.radius-(20*4096);
			on_gnd = collboxCheckSphere(&DennisColl);
			DennisPos.vy+=DennisColl.radius-(20*4096);

			if(on_gnd)
			{	
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,NO,2048);
				DennisVel.vx=0;
				DennisVel.vz=0;
			}

			enemy->pos = DennisPos;
			enemy->vel = DennisVel;


//			printf ("dismounting \n");
			if (enemy->ticker)
				enemy->ticker--;

			if (!enemy->ticker && on_gnd)
			{
				enemy->doing=DENIS_RETURNING;
				enemy->pathvectors[2].vx = enemy->init_pos.vx << 12;
				enemy->pathvectors[2].vy = enemy->init_pos.vy << 12;
				enemy->pathvectors[2].vz = enemy->init_pos.vz << 12;
			}
			break;


		case DENIS_GLOVED:	// control left to tom...? ... grab the glove pos & figre out our own...?
			break;

// going back to his box, using real physics, and setting active = 0 if he falls off the bottom of the world.
		case DENIS_RETURNING:
			if(NMECheckInOurBox(enemy, &enemy->pos))
			{
				enemy->doing = DENIS_NORMAL;
			}
			if(NME_FaceTo(enemy,&enemy->pathvectors[2],32))
			{
				AddToQueue(&enemy->anim,NMEANIM_SPECIAL1,NO,NO,2048);
				AddToQueue(&enemy->anim,NMEANIM_JUMP,NO,YES,2048);
				enemy->doing = DENIS_RETURN_PREJUMP;
			}
			break;

		case DENIS_RETURN_PREJUMP:
			temp=enemy->anim.animInfo->frame;
			temp+=enemy->anim.animInfo->segInfo[enemy->anim.currentAnimation].segStart;
			if (temp>=61)
			{
				enemy->vel.vy = -6 * 4096;
				enemy->vel.vx = -2 * rsin(enemy->ya);
				enemy->vel.vz = -2 * rcos(enemy->ya);

				sfxPlayNME3D(globalFX, SFX_GE_DENNIS_LAUNCH,enemy);
				enemy->pos.vy += enemy->vel.vy;
				enemy->doing = DENIS_RETURN_JUMP;
			}
			break;

// see "HAND_HOPPING"
		case DENIS_RETURN_JUMP:
		{
			DennisPos = enemy->pos;
			DennisVel = enemy->vel;

			DennisVel.vy += 1024;	//gravity;
			ADDVECTOR(&DennisPos, &DennisPos, &DennisVel);

			DennisPos.vy -= DennisColl.radius-(20*4096);
			on_gnd = collboxCheckSphere(&DennisColl);
			DennisPos.vy+=DennisColl.radius-(20*4096);

			if(on_gnd)
			{	
				sfxPlayNME3D(levelFX, SFX_CA_DENNIS_LAND,enemy);
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,NO,2048);
				enemy->doing = DENIS_RETURN_WAIT;
				enemy->ticker = 60;
				DennisVel.vx=0;
				DennisVel.vz=0;
			}

			enemy->pos = DennisPos;
			enemy->vel = DennisVel;

			break;
		}

		case DENIS_RETURN_WAIT:
			enemy->ticker--;
			if(!enemy->ticker)
			{
				enemy->doing = DENIS_RETURNING;
			}
			break;


		default:
			PlaceNMEOnGround(enemy,1);
			enemy->init_pos.vy = enemy->pos.vy >> 12;
			enemy->doing = DENIS_NORMAL;
			break;
	}

//	printf("Denis doing %d\n",enemy->doing);
}


/*
ENEMY CHUCK -183 300 -582 0 0
	NORMAL_INSTRUCTION		NME_WAIT		   0	0	     1 1048577	ALWAYS 0 0 (&100001) = accelerate, facehand
	NORMAL_INSTRUCTION		NME_SPECIAL		   1	-1		 15		 1 ALWAYS 0 0 (15 = walk)
	NORMAL_INSTRUCTION		NME_RANDOMMOVE	   0   -288 300 -693 290 0 270 50 16385 ALWAYS 0 0  (don't animate, face hand)


	CONDITIONAL_INSTRUCTION	NME_ATTACK		  -1	0	     0       0  IF_BALL_IN_RANGE	0	100


	ATTACK_INSTRUCTION		NME_FACEPLAYER	  -1	0		 0	   128	ALWAYS 0 0	(128 = ball)
	ATTACK_INSTRUCTION		NME_FOLLOWPLAYER 100	0 0	0	40	   129	ALWAYS 0 0	(129 = ball,movevel)

	ATTACK_INSTRUCTION		NME_SPECIAL		  -1	0        0 2097154	ALWAYS 0 0	(action 1, 200002 = face ball, movevel)
	ATTACK_INSTRUCTION		NME_JUMP		  -1	0 5 3 16384 2097281	ALWAYS 0 50	(200081 = face ball, specify ball)
	ATTACK_INSTRUCTION		NME_BRANCH		  -1	7        0       0  IF_IM_HOLDING_BALL 0 0

	ATTACK_INSTRUCTION		NME_SPECIAL		   1	-1		 15		 3	ALWAYS 0 0	(back to walk anim)
	ATTACK_INSTRUCTION		NME_ENDATTACK	   0	0		 0	 16384	ALWAYS 0 0


	ATTACK_INSTRUCTION		NME_LOADCOUNTER	   1	10	0					0 ALWAYS 0 0
	ATTACK_INSTRUCTION		NME_RANDOMMOVE	 100   -288 300 -693 290 0 270 50 20546 ALWAYS 0 0  (5042) = (use "run" anim, don't face dir, specify hand, move vel)
	ATTACK_INSTRUCTION		NME_WAIT		  30	0 0				  1064960 IF_HAND_IN_RANGE 0 180 	(104000 = facehand, use "run" anim)
	ATTACK_INSTRUCTION		NME_LOOP		  -1	8 0					    0 ALWAYS 0 0

	ATTACK_INSTRUCTION		NME_JUMP		   1	0 10 -5 16384		16384 ALWAYS 0 0	(don't anim)
	ATTACK_INSTRUCTION		NME_DROPPLAYER	   1	0 0 0 0				  128 ALWAYS 0 0	(specify ball, again)
	ATTACK_INSTRUCTION		NME_SPECIAL		   1	-1		 15		        3 ALWAYS 0 0	(walk)
	ATTACK_INSTRUCTION		NME_ENDATTACK	   0	100 0				16384 ALWAYS 0 0
END_ENEMY
*/



//#define CLUCK_JUMP_RANGE_OUTER	(4096 * 90)
//#define CLUCK_JUMP_RANGE_INNER	(4096 * 80)
#define CLUCK_JUMP_RANGE_OUTER	(4096 * 60)

#define CLUCK_CHASE_RANGE		(4096 * 400)
#define CLUCK_GIVEUP_RANGE		(4096 * 600)

#define CLUCK_JUMPING    5
#define CLUCK_WITH_BALL  6
#define CLUCK_PUSHED_OFF 7


void ChuckFireEgg(ENEMYPOS *nme)
{
	ENEMYPOS *egg;
	egg = chuck_egg_enemy;
	if(egg->flags & NMEFLAG_ACTIVE)
		return;

//	printf("!");

	sfxPlayNME3D(levelFX, SFX_CA_CHUCK_CALL,egg);

	egg->doing = 0;
	egg->ticker = 0;
	egg->flags |= NMEFLAG_ACTIVE + NMEFLAG_ENABLED;
	egg->pos = nme->pos;
	egg->vel.vx = random(32)<<8;
	egg->vel.vy = - 6 * 4096;
	egg->vel.vz = random(32)<<9;
}
void Update_Egg(ENEMYPOS *nme)
{
	switch(nme->doing)
	{
	case 0:
		nme->ticker++;
		if(nme->ticker >= 6)
			nme->ticker = 0;

		nme->vel.vy += 1024;
		nme->pos.vx += nme->vel.vx;
		nme->pos.vy += nme->vel.vy;
		nme->pos.vz += nme->vel.vz;

		if(isPointSolid(nme->pos.vx,nme->pos.vy,nme->pos.vz))
		{
			sfxPlayNME3D(levelFX, SFX_CA_CHUCK_ATTACK,nme);
			sfxPlayNME3D(levelFX, SFX_KLOSET_ATTACK,nme);

			nme->doing = 1;
			nme->ticker = 0;
		}
		break;
	case 1:
		nme->ticker++;
		if(nme->ticker >= 3)
		{
			nme->flags &= ~(NMEFLAG_ACTIVE + NMEFLAG_ENABLED);
		}
		break;
	}
}



// all the range checks are to ensure it doesn't go outside its boundaries...
void Update_Cluck(ENEMYPOS *enemy)
{

	enemy->ticker++;
	if(! (enemy->ticker & 31) )
	{
		ChuckFireEgg(enemy);
	}

	switch(enemy->doing)
	{
// walking around at random...
		case 0:
			NMEChooseRandomPoint(enemy,YES);	// set pathv's 2 from 0 & 1
			enemy->doing = 1;
// ...dropthru...
		case 1:
			if(NME_FaceTo(enemy,&enemy->pathvectors[2],80))
			{
				enemy->doing = 2;
			}

			if(NMECheckInRange(enemy, &ballPos, CLUCK_CHASE_RANGE,YES) && !enemy_has_ball)
				enemy->doing = 3;

			break;
		case 2:
			if(NME_GoTo2D(enemy,&enemy->pathvectors[2],2 * 4096))
			{
				enemy->doing = 0;
			}
			if(NMECheckInRange(enemy, &ballPos, CLUCK_CHASE_RANGE,YES) && !enemy_has_ball)
				enemy->doing = 3;
			break;

// turn to face towards the ball...
		case 3:
			if(NME_FaceTo(enemy,&ballPos,80))
			{
				enemy->doing = 4;
			}
			if(!NMECheckInRange(enemy, &ballPos, CLUCK_GIVEUP_RANGE,YES) ||  enemy_has_ball)
				enemy->doing = 0;
			break;

// walk to the ball
		case 4:
			NME_FaceTo(enemy,&ballPos,80);

// don't try to jump on during a cameo. If we're already jumping, tough.
			if ((CamVars.flags&(FIXEDPOSITION + FIXEDLOOKAT)) ||  enemy_has_ball)
			{
				enemy->doing = 0;
				break;
			}

			if(NME_GoTo2D(enemy,&ballPos,2 * 4096))
			{
				enemy->doing = 0;
			}
			if(!NMECheckInRange(enemy, &ballPos, CLUCK_GIVEUP_RANGE,YES))
				enemy->doing = 0;

// Jump on.
			if(NMECheckInRange(enemy, &ballPos, CLUCK_JUMP_RANGE_OUTER,YES)
//				&& !NMECheckInRange(enemy, &ballPos, CLUCK_JUMP_RANGE_INNER,YES)
			  )
			{
				enemy->vel.vy = -12 * 4096;
				enemy->pos.vy += enemy->vel.vy;
				enemy->doing = CLUCK_JUMPING;
			}

			break;

// jumping...
// At the end of the jump, revert to walking around. Landing on the ball is dealt with in the interaction routines
		case CLUCK_JUMPING:
			NME_FaceTo(enemy,&ballPos,80);
			NME_GoTo2D(enemy,&ballPos,2 * 4096);

			if(enemy->pos.vy < enemy->init_pos.vy << 12)
			{
				enemy->vel.vy += 4096;
				enemy->pos.vy += enemy->vel.vy;
			}
			else
			{
				enemy->pos.vy = enemy->init_pos.vy << 12;
				enemy->doing = 0;
			}
			break;


		case CLUCK_WITH_BALL:
		{
			VECTOR temp;

			if(!BallCtrl.enabled)	// eg - the player uses a restart hoop
			{
				CluckPushOffBall(enemy);
			}

			enemy_has_ball = 2;

			if(!(activeframe & 7))
			{
				sfxPlayNME3D(levelFX, SFX_CA_CHUCK_JUGGLE,enemy);
			}

			temp = enemy->pos;
			NME_FaceTo(enemy,&enemy->pathvectors[2],80);
			if(NME_GoTo2D(enemy,&enemy->pathvectors[2],4 * 4096))	// faster (depend on ballspeed)
			{
				NMEChooseRandomPoint(enemy,YES);	// set pathv's 2 from 0 & 1
			}

			ballPos.vx = enemy->pos.vx;
			ballPos.vz = enemy->pos.vz;
			enemy->pos.vy = ballPos.vy - ballColl.radius - (20 <<12);	// so you can change the ballstate

			ballVel.vx = enemy->pos.vx - temp.vx;
			ballVel.vz = enemy->pos.vx - temp.vx;
			break;
		}

		case CLUCK_PUSHED_OFF:
			NME_FaceTo(enemy,&enemy->pathvectors[2],80);
			NME_GoTo2D(enemy,&enemy->pathvectors[2],2 * 4096);
			if(enemy->pos.vy < enemy->init_pos.vy << 12)
			{
				enemy->vel.vy += 4096;
				enemy->pos.vy += enemy->vel.vy;
			}
			else
			{
				enemy->pos.vy = enemy->init_pos.vy << 12;
				enemy->doing = 0;
			}
			break;

		default:
			PlaceNMEOnGround(enemy,1);
			enemy->init_pos.vy = enemy->pos.vy >> 12;

			AddToQueue(&enemy->anim,NMEANIM_WALK,YES,NO,4096);	// anim,numberloop,queue,speed
			enemy->doing = 0;
			break;
	}
/*
	if(enemy->doing != 2)
	{
		printf("Cluck %d %d %d ",enemy->pos.vx>>12,enemy->pos.vy>>12,enemy->pos.vz>>12);
		printf(" doing %d\n",enemy->doing);
	}
*/
}


void CluckPushOffBall(ENEMYPOS *nme)
{
	if(nme->doing == CLUCK_WITH_BALL)
	{
		sfxPlayNME3D(levelFX, SFX_CA_CHUCK_JUGGLE_ENDING,nme);

		AddToQueue(&nme->anim,NMEANIM_WALK,YES,NO,4096);	// anim,numberloop,queue,speed
		nme->doing = CLUCK_PUSHED_OFF;
		nme->vel.vy = -10 * 4096;
		nme->pos.vy += nme->vel.vy;
	}
	else
	{
//		sfxPlayNME3D(globalFX, FX_CHICKEN_CLUCKMAD,enemy);	// this isn't generic ATM
		if(nme->doing != CLUCK_PUSHED_OFF)
			EnemyStun(nme);
	}
}

void CluckIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{

	if(with == 1)
	{
		if(BallCtrl.powerwhack)
		{
			PowerWhackHandler(nme,0);
			return;
		}

		if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
			DisconnectGloveFromBall(DISCONNECT_WHEN_WHATEVER);
		}
		if(nme->doing == CLUCK_WITH_BALL)
		{
			nme->pos.vy = ballPos.vy - ballColl.radius - (25 <<12);
			return;	// he's SUPPOSED to be in collision with it
		}
		else
		{
// if we're coming down onto the ball, then mount it, otherwise push it
			if(nme->doing == CLUCK_JUMPING
				&& !enemy_has_ball	// Just in case it's inside bugle or something
//				&& dvec->vx > -10<<12 && dvec->vx < 10<<12
//				&& dvec->vz > -10<<12 && dvec->vz < 10<<12
//				&& nme->pos.vy < ballPos.vy
			  )
			{
				AddToQueue(&nme->anim,NMEANIM_RUN,YES,NO,4096);	// anim,numberloop,queue,speed
				nme->pos = ballPos;
				nme->pos.vy -= ballColl.radius - (30 <<12);
				nme->doing = CLUCK_WITH_BALL;
				enemy_has_ball = 2;
			}
			else if(nme->doing != CLUCK_PUSHED_OFF)
			{
				NMEPushesBall(nme,dvec,sphererad);
			}
		}
	}
	else
	{
		if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
			if(!IsBallShield(dvec))
				DisconnectGloveFromBall(DISCONNECT_WHEN_WHATEVER);
		}
		if(GloveCtrl.action == HAND_SLAM || GloveCtrl.action == HAND_SLAM2ST )
		{
			if(nme->doing == CLUCK_WITH_BALL)
			{
				nme->doing = CLUCK_PUSHED_OFF;
			}
		}
		PushableRou(with,nme,dvec,sphererad);
	}
}



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

#define CHICK_SINGING 0
#define CHICK_WAITING 1
#define CHICK_SWINGING -1

signed char cheat_list[] = {0,1,2,3,0,1,2,3,-1};

// enemy->n_points = cheat playing
void Update_Hubchick(ENEMYPOS *enemy)
{
	signed char sfxno;
	static char sfx_nos[] =
	{
		SFX_GE_KEG_CALL,
		SFX_GE_CHICKEN_BELCH,
		SFX_GE_CHICKEN_FART,
		SFX_GE_CHICKEN_CLUCKMAD
	};

	enemy->ya = 2048;

	switch(enemy->doing)
	{
	case CHICK_SINGING:	// singing a happy little cheat song
		if(!(enemy->ticker % 12))
		{
//			sfxno = cheat_list[enemy->ticker/12];

			sfxno = cheatInfo[(int)enemy->n_points].string[enemy->ticker / 12] - 1;

			if(sfxno == -1)
			{
				enemy->doing = CHICK_WAITING;
				enemy->ticker = 100;
			}
			else
			{
				sfxPlayNME3D(globalFX, sfx_nos[(int)sfxno],enemy);
			}
		}
		enemy->ticker++;

		break;

	case CHICK_WAITING:
		enemy->ticker--;
		if(!enemy->ticker)
			enemy->doing = CHICK_SWINGING;
		break;

	default:	// swinging away happily to itself
		if(!(activeframe & 31) && RANDOM256() < 50)
		{
// n64 played according to the chicken type
//SFX_CHICKEN_CLUCKNORMAL,
//SFX_CHICKEN_CLUCKHARFMAD,
//SFX_GE_CHICKEN_CLUCKMAD,

			if(RANDOM256() > 192)
				sfxPlayNME3D(levelFX, SFX_CHICKEN_CLUCKHARFMAD,enemy);
			else
				sfxPlayNME3D(levelFX, SFX_CHICKEN_CLUCKNORMAL,enemy);

		}

		break;
	}


}

void ChickIntRou(int with, ENEMYPOS *enemy, VECTOR *dpos, int sphererad)
{
	NMEPushesGlove(enemy,dpos,sphererad);

	if(enemy->doing != CHICK_SINGING)
	{
//		if (debounce[0] & (PAD_CROSS | PAD_CIRCLE))
		{
			enemy->doing = CHICK_SINGING;
			enemy->ticker = 0;


			switch(CaveCrystalsGot())
			{
			case 1:	// taken hub one in
				enemy->n_points = CHEAT_ALL_OFF;
				break;
			case 2:	// got at
				enemy->n_points = CHEAT_SUMMON_BALL;
				break;
			case 3:	// got ca
				enemy->n_points = CHEAT_ACCESS_ALL_RESTARTS;
				break;
			case 4:	// got pi
//				enemy->n_points = CHEAT_WIN_LEVEL;
//				break;
			case 5:	// got pr
				enemy->n_points = CHEAT_LEVEL_SELECT;
				break;
			case 6:	// got ff
				enemy->n_points = CHEAT_INFINITE_ENERGY;
				break;
			case 7:	// got sp (this never happens, coz you finish the game)
//				enemy->n_points = RANDOM256() % NUM_CHEATS;
				enemy->n_points = CHEAT_INFINITE_LIVES;
				break;

			case 0:	// not even taken the first one in
			default:
				enemy->n_points = 0;
				break;

			}


// the n64 chicken also restores your health.

			GloveCtrl.health = GloveCtrl.maxHealth;
			GloveCtrl.healthChange=TRUE;

// Progression of cheats on the N64
//enemy->script.tempFloat = CHEAT_BALL_TO_ENEMY;
//enemy->script.tempFloat = CHEAT_FISHEYE;
//enemy->script.tempFloat = CHEAT_ROTATE_CAM_LEFT;
//enemy->script.tempFloat = CHEAT_REAL_GARIB;
//enemy->script.tempFloat = CHEAT_HAND_TO_FROG;
//enemy->script.tempFloat = CHEAT_CKBONUS;
//enemy->script.tempFloat = CHEAT_CONTROLBALL;
// hub8 picks one at random from the remainder

		}
	}
}




void GetPointingAngles(VECTOR *us, VECTOR *targ, int *xa, int *ya)
{
	int angle;
	VECTOR tempVec;
	int dist;

	angle=calc_angle( (us->vx-targ->vx)/4096,(us->vz-targ->vz)/4096 );
	*ya = (angle & 4095);

	tempVec.vx=(us->vx-targ->vx)/4096;
	tempVec.vz=(us->vz-targ->vz)/4096;
	tempVec.vy=0;
	dist=Magnitude(&tempVec);
	angle=calc_angle( (targ->vy-us->vy)/4096,dist );
	*xa = (angle & 4095);
}

/* Example Bovva instruction list
ENEMY BOVVA 326 36 474 1 -1
	NORMAL_INSTRUCTION		NME_RANDOMMOVE	-1		250 40 190	380 20 440  100 8193 ALWAYS	0 0 (&2001 = bank, accel)

	CONDITIONAL_INSTRUCTION	NME_ATTACK		-1					 0			0	   0 IF_HAND_IN_RANGE 0 100

	ATTACK_INSTRUCTION		NME_SPECIAL		-1					-1		   12	   2 ALWAYS 0 0
	ATTACK_INSTRUCTION		NME_ENDATTACK   20				   100			0      0 ALWAYS 0 0
END_ENEMY
*/
// Bovva....
// Idle = pick random points within the box, & fly to them
// If ball is within box , home in on ball, & push it around
// Otherwise, if glove is within box, shoot stings at him.

void Update_Bovva(ENEMYPOS *enemy)
{
	static const NMEAccelStr bovaccel =
	{
		0x180,
		0x2400,
		100,
		1000
	};

	int oya;

	VECTOR target;
	int targno;
	int arrived;
	COLLDATA *NmeColl = (COLLDATA *)(enemy->extras);


//	NmeColl.pVel=&enemy->vel;
//	NmeColl.pPos=&enemy->pos;

//	if(!(enemy->ticker & 15))
	if(!(activeframe & 15))
	{
		switch(world)
		{
		case PREHISTORIC:
			sfxPlayNME3D(levelFX, SFX_PR_BOVVA_GENERAL,enemy);
			break;
		case CARNIVAL:
			sfxPlayNME3D(levelFX, SFX_CA_BOVVA_GENERAL,enemy);
			break;
		}
	}


	switch(enemy->doing)
	{
		case 0:	// picking a rnd position
			enemy->ticker = 0;		
			enemy->doing = 1;
			NMEChooseRandomPoint(enemy,NO);

// homing in on stuff
		case 1:

// pick a target
			if(NMECheckInOurBox(enemy,&ballPos))
			{
				target = ballPos;
				targno = 0;
			}
			else if(NMECheckInOurBox(enemy,&glovePos))
			{
				target = glovePos;
				target.vy -= 50*4096;
				targno = 1;
			}
			else
			{
				target = enemy->pathvectors[2];
				targno = 2;
				enemy->ticker++;
			}

// home in on it
			{
				oya = enemy->ya;

				arrived = NME_AccelTo3D(enemy,&target,&bovaccel);

// tilt
				oya = (enemy->ya - oya) & 4095;
				if(oya > 2048) oya |= (~4095);
				if(oya > 16) oya = 16;
				if(oya < -16) oya = -16;
				enemy->za = AngleHomer(enemy->za, -(oya * 32), 8);

				enemy->xa = AngleHomer(enemy->xa,0,bovaccel.turnrate);	// recover from aiming
			}

// fire a sting at the glove ?
			if(targno == 1)
			{
				if(NMECheckInRange(enemy, &glovePos, 4096 * 80,NO)
					&& !(bovva_sting_enemy->flags & NMEFLAG_ACTIVE)
					&& glovePos.vy > enemy->pos.vy
					)
				{
					enemy->doing = 2;
					enemy->ticker = 0;
				}
			}

// pick a new random target ?
			if(targno == 2 && (arrived || enemy->ticker > 300))
			{
				enemy->doing = 0;
			}

			break;

// firing at the glove
		case 2:
		{
			int xa,ya;

// damp velocity to zero, aim rear end at glove
// (don't continue aim if it goes too far out of range ?)
			enemy->vel.vx = IntHomer(enemy->vel.vx,0,4096);
			enemy->vel.vy = IntHomer(enemy->vel.vy,0,4096);
			enemy->vel.vz = IntHomer(enemy->vel.vz,0,4096);

			GetPointingAngles(&enemy->pos, &glovePos,&xa,&ya);
			ya += 2048;	// aim rear-end...
			xa = -xa;
			if(xa > 2048) xa -= 4096;
			if(xa < -2048) xa += 4096;

// xa is (obv) zero for horizontal, -ve numbers to 1024 being straight down
			if (xa > -256)
				xa = -256;

//			DB("bovva targ xa = %d\n",xa);
//			DB("bovva      xa = %d\n",enemy->xa);

			enemy->ya = AngleHomer(enemy->ya,ya,bovaccel.turnrate);
			enemy->xa = AngleHomer(enemy->xa,xa,bovaccel.turnrate);
			enemy->za = AngleHomer(enemy->za, 0, 8);

			enemy->ticker++;
			if(enemy->ticker == 30)
			{
				AddToQueue(&enemy->anim,NMEANIM_STARTATTACK,NO,NO,4096);
				AddToQueue(&enemy->anim,NMEANIM_ENDATTACK,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
			}
			if(enemy->ticker == 30+16)
			{
				static VECTOR ref = {0,0,100};

				ENEMYPOS *sting;
				VECTOR vec;

				EnemyRotatePoint(enemy, &ref, &vec, -1);


/*
				vec.vx = glovePos.vx - enemy->pos.vx;
				vec.vy = glovePos.vy - enemy->pos.vy;
				vec.vz = glovePos.vz - enemy->pos.vz;
*/
				MakeUnit(&vec);
				sting = bovva_sting_enemy;

// fire sting
				sting->xa = enemy->xa;
				sting->ya = enemy->ya;
				sting->za = enemy->za;
				sting->flags |= NMEFLAG_ACTIVE + NMEFLAG_ENABLED;
				sting->doing = 0;

				sting->pos.vx = enemy->pos.vx + vec.vx * 10;
				sting->pos.vy = enemy->pos.vy + vec.vy * 10;
				sting->pos.vz = enemy->pos.vz + vec.vz * 10;

				sting->vel.vx = vec.vx * 3;
				sting->vel.vy = vec.vy * 3;
				sting->vel.vz = vec.vz * 3;

				switch(world)
				{
				case PREHISTORIC:
					sfxPlayNME3D(levelFX, SFX_PR_BOVVA_ATTACK,enemy);
					break;
				case CARNIVAL:
					sfxPlayNME3D(levelFX, SFX_CA_BOVVA_ATTACK,enemy);
					break;
				}


//				printf("bovva pos = %d %d %d sting pos = %d %d %d\n",
//					enemy->pos.vx,enemy->pos.vy,enemy->pos.vz,
//					sting->pos.vx,sting->pos.vy,sting->pos.vz
//					);

//				effectsStartOverlay(10, 0xffff00);
			}

			if(enemy->ticker >= 100)
				enemy->doing = 0;
			break;
		}


		default:
			AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,NO,4096);
//			NmeColl.oldPos = enemy->pos;

			enemy->doing = 0;
			break;
	}

//	if (collboxCheckSphere(&NmeColl))
	if (collboxCheckSphere(NmeColl))
	{
//		printf("hit");
	}

}



// check for ball/glove near explosion, & push as appropriate...
// (rad is a small number)
void ForceExplosion(VECTOR *vec, int rad, int power)
{
	VECTOR dvec;
	int dist;
	int powhere;

	dvec.vx = (ballPos.vx - vec->vx)>>12;
//	dvec.vy = ballPos.vy - 10 * 4096 - vec->vy;
	dvec.vy = (ballPos.vy - vec->vy)>>12;
	dvec.vz = (ballPos.vz - vec->vz)>>12;

// ickle botch to ensure you're always blasted a little upwards, & never downwards
	if(dvec.vy > -10 && dvec.vy < rad)
		dvec.vy = -10;

	dist = Magnitude(&dvec);
	if(dist < rad)
	{
		powhere = power * (rad-dist)/rad;
		MakeUnit(&dvec);

		dvec.vx = (powhere * dvec.vx);
		dvec.vy = (powhere * dvec.vy);
		dvec.vz = (powhere * dvec.vz);
		ballVel.vx += dvec.vx;
		ballVel.vy += dvec.vy;
		ballVel.vz += dvec.vz;
	}


	dvec.vx = (glovePos.vx - vec->vx)>>12;
//	dvec.vy = glovePos.vy - 10 * 4096 - vec->vy;
	dvec.vy = (glovePos.vy - vec->vy)>>12;
	dvec.vz = (glovePos.vz - vec->vz)>>12;

	if(dvec.vy > -10 && dvec.vy < rad<<12)
		dvec.vy = -10;


	dist = Magnitude(&dvec);
	if(dist < rad)
	{
// Rotorblades either needs to be immune from force explosions, or be able to deal with them.
		if(!handpower_timer || handpower_type != SPELL_ROTORBLADES)
		{
			powhere = power * (rad-dist)/rad;
			MakeUnit(&dvec);

			dvec.vx = (powhere * dvec.vx);
			dvec.vy = (powhere * dvec.vy);
			dvec.vz = (powhere * dvec.vz);

	//		gloveVel.vx = dvec.vx / 2;
	//		gloveVel.vy = -dvec.vy * 2;	// No, I don't know why the glove vel's upsidedown
	//		gloveVel.vz = dvec.vz / 2;

			gloveVel.vx = dvec.vx / 4;
			gloveVel.vy = -dvec.vy ;	// No, I don't know why the glove vel's upsidedown
			gloveVel.vz = dvec.vz / 4;

			GloveCtrl.action=HAND_FALLING;
			GloveCtrl.onGround=FALSE;	// coz otherwise he won't even start to go upwards
			GloveCtrl.deathType=HURTFALL;

			GloveCtrl.hurtFlag=0;
			AddToQueue(&Glover,HANDANIM_FALL,YES,NO,4096);
			GloveCtrl.ballCollision=FALSE;
		}
	}
}

// move in a straight line.
// blow up if we timeout or hit any kind of collision data
void Update_Sting(ENEMYPOS *enemy)
{
	VECTOR temp;
	switch(enemy->doing)
	{
	case 1:
		enemy->pos.vx += enemy->vel.vx;
		enemy->pos.vy += enemy->vel.vy;
		enemy->pos.vz += enemy->vel.vz;

		enemy->ticker++;

// hit the ground
		if(isPointSolid(enemy->pos.vx,enemy->pos.vy,enemy->pos.vz))
		{
			enemy->ticker = 0;
			enemy->doing = 2;
//			ForceExplosion(&enemy->pos, 100 * 8, 40);
//			ForceExplosion(&enemy->pos, 100 * 4, 30);
			ForceExplosion(&enemy->pos, 250, 30);
		}

		// just timed out
		if(enemy->ticker >= 500)
			enemy->flags &= ~(NMEFLAG_ACTIVE + NMEFLAG_ENABLED);
		break;
	case 2:
		enemy->ticker++;
		if(enemy->ticker == 10)
		{
			enemy->flags &= ~(NMEFLAG_ACTIVE + NMEFLAG_ENABLED);
		}
//		if(enemy->ticker == 1)
//			effectsStartOverlay(10, 0xffff00);

		temp = enemy->pos;
		temp.vx += (random(32)-16)<<12;
		temp.vy -= (random(32)-16)<<12;
		temp.vz += (random(32)-16)<<12;
		New_Debris(DEBRIS_STINGEXPLODE,&temp,effectsColourNear(0x106080, 0x40));
		break;

	case 0:
		enemy->ticker = 0;
		enemy->doing = 1;
		break;
	}
}


/*
ENEMY BUGLE 180 130 280 0	0
	NORMAL_INSTRUCTION		NME_RANDOMMOVE	-1		-6 39 16 250 120 210 30 4102	ALWAYS 0 0 (&1006 = don't face in dir of travel, move-vel, don't slow when near)

	CONDITIONAL_INSTRUCTION	NME_ATTACK		-1		0	0					   0	IF_BALL_IN_RANGE	0	150

	CONDITIONAL_INSTRUCTION NME_FOLLOWPLAYER 50		0 60 0  10				4166	IF_IM_HOLDING_BALL 0 0  (&1046 = don't face in dir, specify hand, move vel, don't slow)

	ATTACK_INSTRUCTION		NME_ENDATTACK	 1		0 0					   16384	IF_IM_HOLDING_BALL 0 0	(don't anim)
	ATTACK_INSTRUCTION		NME_FOLLOWPLAYER 200	0 60 0  30				4226	ALWAYS 0 0 (&1082) = (don't face move, specify ball, move vel)
	ATTACK_INSTRUCTION		NME_SPECIAL		-1		0 0						   0	IF_BALL_IN_RANGE_2D 0 90  (play anim 0 (special 1)
	ATTACK_INSTRUCTION		NME_HOLDPLAYER	-1		0 -15 0 0				 128	IF_BALL_IN_RANGE_2D 0 90  (specify ball)
	ATTACK_INSTRUCTION		NME_SPECIAL		-1		1 0						   0	IF_IM_HOLDING_BALL 0 0	  (play anim 1) (special 2)
	ATTACK_INSTRUCTION		NME_ENDATTACK	 1		0		0			   16384	ALWAYS 0 0
END_ENEMY
*/




#define BUGLE_WITH_BALL 4
void Update_Bugle(ENEMYPOS *enemy)
{
	VECTOR target;
	int targno;

	COLLDATA *NmeColl = (COLLDATA *)(enemy->extras);

	static const NMEAccelStr bugleaccel =
	{
		0x100,
		0x1800,//		0x1200,
		50,
		1000
	};
/*
	{
		DYNCOLLBOX *plat;
		// 108
		loadlndFindPlatform(108, NULL, &plat);
		PlatformSetCollideTerrainType(plat,C_LAVA);
	}
*/
	if(!(frame & 63))
	{
		if(RANDOM256() < 64)
			sfxPlayNME3D(levelFX, SFX_CA_BUGLE_CALL,enemy);

	}

	switch(enemy->doing)
	{
		case 0:	// picking a rnd position
			enemy->ticker = 0;		
			enemy->doing = 1;

			NMEChooseRandomPoint(enemy,NO);

// ... dropthru
		case 1:

// pick a target
			if(NMECheckInOurBox(enemy,&ballPos))
			{
				target = ballPos;
				target.vy -= 40*4096;
				targno = 0;
			}
			else
			{
				target = enemy->pathvectors[2];
				targno = 2;
				enemy->ticker++;
			}


			enemy->ticker++;
			if(NME_AccelTo3D(enemy,&target,&bugleaccel) || enemy->ticker > 300)
			{
				enemy->doing = 0;
			}

//			if(NMECheckInRange(enemy, &ballPos, 4096 * 150,NO) && !enemy_has_ball)
			if(NMECheckInRange(enemy, &ballPos, 4096 * 80,NO) && !enemy_has_ball)
			{
				enemy->doing = 2;
				enemy->ticker = 0;
			}
			break;

// case 2= homing in, case 3 = trying to suck...
		case 2:
			enemy->ticker = 0;
// dropthrough

		case 3:
		{
			int xa,ya;

// damp velocity to zero, aim at ball
			enemy->vel.vx = IntHomer(enemy->vel.vx,0,4096);
			enemy->vel.vy = IntHomer(enemy->vel.vy,0,4096);
			enemy->vel.vz = IntHomer(enemy->vel.vz,0,4096);

			GetPointingAngles(&enemy->pos, &ballPos,&xa,&ya);
//			ya += 2048;	// aim rear-end...
//			xa = -xa;
			enemy->ya = AngleHomer(enemy->ya,ya,bugleaccel.turnrate);
			enemy->xa = AngleHomer(enemy->xa,xa,bugleaccel.turnrate);
			enemy->za = AngleHomer(enemy->za, 0, 8);

// (if the ball is moved out of range, revert to normal behaviour)
			if(!NMECheckInRange(enemy, &ballPos, 4096 * 80,NO) || enemy_has_ball)
			{
				enemy->doing = 0;
			}

			if(enemy->xa == xa && enemy->ya == ya)
			{
				if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
				{
					DisconnectGloveFromBall(DISCONNECT_WHEN_WHATEVER);
				}
				AddToQueue(&enemy->anim,NMEANIM_SPECIAL1,NO,NO,4096);
				enemy->doing = 3;
			}
			enemy->ticker++;

			if(!(enemy->ticker & 7))
				sfxPlayNME3D(levelFX, SFX_CA_BUGLE_ATTACK,enemy);

			if(enemy->ticker == 30)
			{
// he managed to get through his sucking anim without the ball getting out of range. Suck that ball...
				AddToQueue(&enemy->anim,NMEANIM_SPECIAL2,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
				enemy->doing = BUGLE_WITH_BALL;

				ballPos = enemy->pos;	// a bit nasty for now, & it means that it ain't gonna roll around the bottom
//			ballVel.vx = 0;
//			ballVel.vy = 0;
//			ballVel.vz = 0;

			}
			break;
		}

// Got the ball. Home in on the glove. Adjust our gravity/bounce according to the ball type.
// we explode if we hit spikes, remember
		case BUGLE_WITH_BALL:
		{

// make bloody sure that the ball doesn't escape. Because otherwise you can fistslam the thing & it falls out
/*
			VECTOR temp;
			int dist;
			temp.vx = enemy->pos.vx - ballPos.vx;
			temp.vy = enemy->pos.vy - ballPos.vy;
			temp.vz = enemy->pos.vz - ballPos.vz;
			dist = Magnitude(&temp);
			if(dist > 4096 * (BUGLE_SPHERE_RAD-5))
			{
				MakeUnit(&temp);
				temp.vx = temp.vx * (BUGLE_SPHERE_RAD-5);
				temp.vy = temp.vy * (BUGLE_SPHERE_RAD-5);
				temp.vz = temp.vz * (BUGLE_SPHERE_RAD-5);
				ballPos.vx = enemy->pos.vx - temp.vx;
				ballPos.vy = enemy->pos.vy - temp.vy;
				ballPos.vz = enemy->pos.vz - temp.vz;
			}
*/

//			pBugleCollBox->active=TRUE;

			enemy->xa = AngleHomer(enemy->xa,0,bugleaccel.turnrate);
			enemy->za = AngleHomer(enemy->za, 0, 8);

			target = glovePos;
//			target.vy -= 50*4096;
			target.vy -= 40*4096;
			enemy_has_ball = 2;

			NME_AccelTo3D(enemy,&target,&bugleaccel);
			break;
		}

		default:
			AddToQueue(&enemy->anim,NMEANIM_WALK,YES,NO,4096);	// anim,numberloop,queue,speed
			enemy->doing = 0;
//			pBugleCollBox->active=FALSE;
			break;
	}
//	printf("bugle pos %d %d %d, %d %d %d\n",enemy->pos.vx>>12,enemy->pos.vy>>12,enemy->pos.vz>>12,
//		pBugleCollBox->cb.pX>>12,pBugleCollBox->cb.pY>>12,pBugleCollBox->cb.pZ>>12);

//	platformMoveDynamicBox(pBugleCollBox,enemy->pos.vx,enemy->pos.vy,enemy->pos.vz);
//	platformMoveDynamicBox(pBugleCollBox,enemy->pos.vx,enemy->pos.vy,enemy->pos.vz);	// zero it's old-new
//	pBugleCollBox->active=FALSE;

	if (collboxCheckSphere(NmeColl))
	{
//		printf("hit %);
		switch (NmeColl->flags)
		{
			case P_SPIKE:
			case P_LAVA:
				sfxPlayNME3D(levelFX, SFX_CA_BUGLE_DEATH,enemy);

// and pop gfx ? (but the pop gfx is awful!)
				effectsStartOverlay(10, 0xff00ff);


				EnemyKill(enemy);
				BallCtrl.inside_bugle = 0;
				enemy->doing = -1;	// to prevent the next bit from picking up...!
				break;
		}
	}

	if(enemy->doing == BUGLE_WITH_BALL)
	{
		if(!BallCtrl.enabled)
		{
			enemy->doing = -1;
			BallCtrl.inside_bugle = NULL;
		}
		else
		{
	//		pBugleCollBox->active = TRUE;
			BallCtrl.inside_bugle = enemy;
			enemy_has_ball = 2;
	//		BugleBehaviour.mass = ballColl.physics->mass - 10*4096;
	//		if(BugleBehaviour.mass < 0 )
	//			BugleBehaviour.mass = 0;

	//		BugleBehaviour.mass = ballColl.physics->mass - 10*4096;
	//		BugleBehaviour.gravity = GRAVITY;


			switch(BallCtrl.type)
			{
				case BALL_MODE_BOWLING:
					enemy->vel.vy += 400;
					enemy->vel.vx = enemy->vel.vx * 200/256;
					enemy->vel.vz = enemy->vel.vz * 200/256;
					break;
				case BALL_MODE_BEARING:
					enemy->vel.vy += 300;
					break;
				case BALL_MODE_CRYSTAL:
					enemy->vel.vy += 200;
					break;
				default:
					break;

			}
		}
	}
	else
	{
	}
}


void BugleIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(nme->doing != BUGLE_WITH_BALL)
	{
		VeryPushableRou(with,nme,dvec,sphererad, 4000);
	}

	if(with == 0)
	{
		VeryPushableRou(with,nme,dvec,sphererad,4000);
	}
	else
	{
		return;	// interaction is done by dynamic collision box
	}
}




/*
 ENEMY SWISH -150 20 +150 0 0.000000   
 	NORMAL_INSTRUCTION		NME_SPECIAL			1		-1 15 1 ALWAYS 0 0	(15 = n64's "walk" animation)
 
 	NORMAL_INSTRUCTION		NME_FACETO			-1		xyz 	0		0	ALWAYS	0 0
	NORMAL_INSTRUCTION		NME_MOVETO2D		-1		xyz 	5	16385	ALWAYS	0 0 (no anim, accel)
	NORMAL_INSTRUCTION		NME_FACETO			-1		xyz 	0		0	ALWAYS	0 0
	NORMAL_INSTRUCTION		NME_MOVETO2D		-1		xyz 	5	16385	ALWAYS	0 0 (no anim, accel)
																								
 	NORMAL_INSTRUCTION		NME_BRANCH			-1		1	0		0	ALWAYS	0 0

  
 	CONDITIONAL_INSTRUCTION	NME_ATTACK			-1  0 0 0 IF_CANSEE_BALL 0.5 0
 												
 	ATTACK_INSTRUCTION		NME_ENDATTACK			0		50 0 16384 IF_BALL_IN_RANGE_2D 200 10000 (no anim)
 	
 	ATTACK_INSTRUCTION		NME_SPECIAL			-1		-1 12 2 ALWAYS 0 0 (start attack)
 	ATTACK_INSTRUCTION		NME_SPECIAL			1		-1 9 1 ALWAYS 0 0  (run)

 	ATTACK_INSTRUCTION		NME_FOLLOWPLAYER	50 0 0 0 40 129 ALWAYS 0 0 (specify ball, accel)

 	ATTACK_INSTRUCTION		NME_SPECIAL			-1 -1 3 2 ALWAYS 0 0    (end attack)
 	ATTACK_INSTRUCTION		NME_SPECIAL			-1 -1 13 0 ALWAYS 0 0   (startmove)
 	ATTACK_INSTRUCTION		NME_SPECIAL			1 -1 15 1 ALWAYS 0 0    (walk)
 	ATTACK_INSTRUCTION		NME_ENDATTACK		1 50 0 16384 ALWAYS 0 0 (no anim)
 END_ENEMY
*/

// note - swish uses the path follower stuff, states 100...108
#define SWISH_GLOVED   50
#define SWISH_DISMOUNT 51
#define SWISH_RETURN   3

void Update_Swish(ENEMYPOS *enemy)
{
	int f;
	VECTOR oldpos;

	if(enemy->pos.vy >= world_base_y)
	{
		enemy->flags &= ~(NMEFLAG_ACTIVE);
	}


	oldpos = enemy->pos;

	f = enemy->anim.animInfo->frame;
	f+=enemy->anim.animInfo->segInfo[enemy->anim.currentAnimation].segStart;

	switch(f)
	{
	case 52:
	case 11:
		sfxPlayNME3D(levelFX, SFX_SWISH_WALK_LEFT,enemy);
		break;
	case 59:
	case 24:
		sfxPlayNME3D(levelFX, SFX_SWISH_WALK_RIGHT,enemy);
		break;

	case 144:
	case 146:
		sfxPlayNME3D(levelFX, SFX_SWISH_ATTACK,enemy);
		break;

	}

	switch(enemy->doing)
	{
		case SWISH_GLOVED:
			break;

		case SWISH_DISMOUNT:
			if (enemy->ticker) enemy->ticker--;
			if (!enemy->ticker) enemy->doing=SWISH_RETURN;
			break;


		case 1:
			NME_FaceTo(enemy, &ballPos, 90);
			NME_GoForwards(enemy,4096 * 3);
//			KeepNMEOnGround(enemy,4096 * 10, 4096 * 10);

//			NME_GoTo2D(enemy, &ballPos, 4096 * 3);

			if(!NMECheckInRange(enemy, &ballPos, 4096 * 250,NO))
			{
//				printf("(swish gives up)\n");
				AddToQueue(&enemy->anim,NMEANIM_ENDATTACK,NO,NO,4096);
				AddToQueue(&enemy->anim,NMEANIM_STARTMOVE,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);
				enemy->doing = 2;	// ick. should keep track of previous target point somehow
				enemy->ticker = 0;
			}
			break;
		case 2:
			enemy->ticker++;
			if(enemy->ticker >= 20)
			{
				enemy->doing = SWISH_RETURN;
			}
			break;

// tbd:- returning to pathfollowing, needs to use better physics
		case SWISH_RETURN:
			enemy->doing = 100;
			break;

		default:
			nmeWalkRoundPath(enemy,2*4096);
//			KeepNMEOnGround(enemy,4096 * 10, 4096 * 10);

			if(NMECheckVisible(enemy, &ballPos, 4096 * 230, 256))
			{
//				printf("swish sees ball\n");

				AddToQueue(&enemy->anim,NMEANIM_STARTATTACK,NO,NO,4096);
				AddToQueue(&enemy->anim,NMEANIM_RUN,YES,YES,4096);

				enemy->doing = 1;
			}
			break;
	}


	enemy->vel.vx = enemy->pos.vx - oldpos.vx;
	enemy->vel.vz = enemy->pos.vz - oldpos.vz;
// y vel still changes according to velocity

// don't run off the edge
	enemy->pos = oldpos;
	KeepNMEOnGround(enemy,4096 * 10, 4096 * 10);

// use real physics
	enemy->vel.vy += gravity;
	enemy->pos.vy += enemy->vel.vy;
	enemy->pos.vy -= 10<<12;
	oldpos.vy -= 10<< 12;
	enemy_UsePhysics(enemy, &EnemyHeavyPhysics, &oldpos, 20);
	enemy->pos.vy += 10<<12;
}

// note - cannonballs can be dynamically created
// (fired out of a gun - tru physics rqd)
/*
 ENEMY CANNONBALL 384.000000 15.000000 -3404.000000 3 -1.000000
 	NORMAL_INSTRUCTION	NME_WAIT		0	0			0		0	ALWAYS			0 	0
 	NORMAL_INSTRUCTION	NME_BRANCH		-1	0			0		0	ALWAYS			0 	0
 
 	CONDITIONAL_INSTRUCTION	NME_FOLLOWPLAYER	20	0 0 0 0		32898	IF_BALL_IN_RANGE 	0 	90
 
 (&8082 = don't get a new action when true, specify ball, velocity)
 END_ENEMY
*/
// note - needs physics.
// needs to be pushable by the glove


// tbd - figure why he rolls up hills

void StunCannonball(ENEMYPOS *nme)
{
	nme->doing = 1;
	nme->ticker = 200;
}

void Update_Cannon(ENEMYPOS *enemy)
{
	if(enemy->pos.vy >= world_base_y)
	{
		enemy->flags &= ~(NMEFLAG_ACTIVE);
	}

	if(Magnitude(&enemy->vel) > 8192 && !(activeframe & 7))
	{
		switch(world)
		{
		case PIRATES:
			sfxPlayNME3D(levelFX, SFX_PI_CANNONBALL_WALK,enemy);
			break;
		case FORTRESS:
			sfxPlayNME3D(levelFX, SFX_FF_CANNONBALL_WALK,enemy);
			break;
		}
	}

	if(enemy->doing == -1)
	{
		IQUATERNION *qRot = (void *)&enemy->pathvectors[0];

		qRot->x=-2896;
		qRot->y=0;
		qRot->z=0;
		qRot->w=2896;
		enemy->doing = 0;
	}

	if(enemy->doing == 0)
	{
		if(NMECheckInRange(enemy, &ballPos, 4096 * 200,NO))
		{
	//		NME_FaceTo( enemy, &ballPos,40);
			enemy->ya = (calc_angle( (enemy->pos.vx - ballPos.vx)>>8, (enemy->pos.vz - ballPos.vz)>>8) & 4095);

			enemy->vel.vx -= rsin(enemy->ya) * 0x300/4096;
			enemy->vel.vz -= rcos(enemy->ya) * 0x300/4096;
			enemy->vel.vx = enemy->vel.vx * 245 / 256;
			enemy->vel.vz = enemy->vel.vz * 245 / 256;
		}
		else
		{
			enemy->vel.vx = enemy->vel.vx * 245 / 256;
			enemy->vel.vz = enemy->vel.vz * 245 / 256;
		}
	}
	else	// stunned
	{
		enemy->ticker--;
		if(!enemy->ticker)
			enemy->doing = 0;

	StunDebris(&enemy->pos,nmeinfo[enemy->type].radius,18);

		enemy->vel.vx = enemy->vel.vx * 245 / 256;
		enemy->vel.vz = enemy->vel.vz * 245 / 256;
	}

	enemy->pos.vx += enemy->vel.vx;
	enemy->pos.vy += enemy->vel.vy;
	enemy->pos.vz += enemy->vel.vz;
	enemy->vel.vy += gravity;


//void NMEPushedBySphere(ENEMYPOS *nme, VECTOR *spherepos, int total_rad, int amount)
	{
		ENEMYPOS *other;

// scan through the enemy list & get pushed by other cannonballs

		for (other = nme_list; other; other = other->next)
		{
			if(other != enemy && other->type == CANNONBALL && (other->flags & NMEFLAG_ACTIVE))
			{
				NMEPushedBySphere(enemy,&other->pos,12 * 2);
			}
		}
	}

// why do the velocities need swapping !? (see ball.c, which swaps all 3, but the ball has an upside-down yvel
	enemy->vel.vx = -enemy->vel.vx;
	enemy->vel.vz = -enemy->vel.vz;
	collboxCheckSphere((void *)enemy->extras);
	enemy->vel.vx = -enemy->vel.vx;
	enemy->vel.vz = -enemy->vel.vz;
//	DB("canpos = %d %d %d\n",enemy->pos.vx>>12,enemy->pos.vy>>12,enemy->pos.vz>>12);
//	enemy_UsePhysics(enemy, &EnemyHeavyPhysics, &oldpos, 14);


	if (!(frame%100))
	{
		IQUATERNION *qRot = (void *)&enemy->pathvectors[0];
		UnifyQuat(qRot);
//		UnifyQuat(&pBallPSA->world.qRot);
	}
}



// startmove/stopmove anims
/*
 ENEMY REGGIE 177.000000 291.000000 52.000000 0 0.000000
  	NORMAL_INSTRUCTION	NME_FACETO		-1	177.009445 290.674042 52.133652	0		0	ALWAYS 		0 	0
    NORMAL_INSTRUCTION	NME_SPECIAL		-1	0		0		0	SOMETIMES 		300 	0
	NORMAL_INSTRUCTION	NME_MOVETO2D		-1	177.009445 290.674042 52.133652	20		1	ALWAYS 		0 	0

	NORMAL_INSTRUCTION	NME_FACETO		-1	189.611420 290.475891 -88.797401	0		0	ALWAYS 		0 	0
    NORMAL_INSTRUCTION	NME_SPECIAL		-1	0		0		0	SOMETIMES 		300 	0
	NORMAL_INSTRUCTION	NME_MOVETO2D		-1	189.611420 290.475891 -88.797401	20		1	ALWAYS 		0 	0
	NORMAL_INSTRUCTION	NME_BRANCH		-1	0	0				0	ALWAYS 0 0
END_ENEMY
*/


void Update_Reggie(ENEMYPOS *enemy)
{
	nmeWalkRoundPath(enemy,2*4096);
	if(!(activeframe & 15))
		sfxPlayNME3D(levelFX, SFX_REGGIE_WALK,enemy);	// and why not...?

}


/*
 ENEMY CHESTER 1571.000000 177.000000 -197.000000 0 0.000000
 
 
 NORMAL_INSTRUCTION	NME_SPECIAL		-1	-1		5		0	ALWAYS			0 	0 
 NORMAL_INSTRUCTION	NME_FACEPLAYER 	100	0		0		16640	ALWAYS 	0 	0 (&4100 = don't anim, specify closest)
 NORMAL_INSTRUCTION	NME_SPECIAL		-1	-1		6		4194304	SOMETIMES		250 	0  (&40 0000 = face closest, 6 = idle2)
 NORMAL_INSTRUCTION	NME_BRANCH		-1	0		0		0	ALWAYS 		0 	0
 
 CONDITIONAL_INSTRUCTION	NME_ATTACK		-1	 0		0		0	IF_CLOSEST_IN_RANGE 	0 	100
 	
 ATTACK_INSTRUCTION	NME_SPECIAL		11	-1		12		0	ALWAYS 		0 	0
 ATTACK_INSTRUCTION	NME_JUMP		-1	0 8 10		0		16384	ALWAYS 		0 	0 (&4000 = don't anim)
   	
 ATTACK_INSTRUCTION	NME_SPECIAL		 5	-1		15		0	ALWAYS 		0 	0
 
 ATTACK_INSTRUCTION	NME_MOVETO2D		-1	x y z		10		20482 	ALWAYS 		0 	0 (&5002 = don't anim, don't face in dirn of travel)
 ATTACK_INSTRUCTION	NME_ENDATTACK	 	0	50			0		16384	ALWAYS 		0 	0 (&4000 = don't anim)
 
 
 END_ENEMY
*/
void Update_Chester(ENEMYPOS *enemy)
{
	VECTOR d1,d2;

	int m1,m2;
//	printf("ches doing %d - time %d\n",enemy->doing,enemy->ticker);

	switch(enemy->doing)
	{
		case 0:
			d1.vx = enemy->pos.vx - glovePos.vx;
			d1.vy = enemy->pos.vy - glovePos.vy;
			d1.vz = enemy->pos.vz - glovePos.vz;
			m1 = Magnitude(&d1);
			d2.vx = enemy->pos.vx - ballPos.vx;
			d2.vy = enemy->pos.vy - ballPos.vy;
			d2.vz = enemy->pos.vz - ballPos.vz;
			m2 = Magnitude(&d2);
			if(m1 < 200 * 4096 || m2 < 200 * 4096)
			{
				if(m2 < m1)
				{
					if(NME_FaceTo(enemy,&ballPos,80))
					{
						enemy->ticker = 0;
						enemy->doing = 1;
					}
				}
				else
				{
					if(NME_FaceTo(enemy,&glovePos,80))
					{
						enemy->ticker = 0;
						enemy->doing = 1;
					}
				}
			}

	// occasionally do the other idle anim
			if(!enemy->anim.numAnimations && !random(20))
			{
				AddToQueue(&enemy->anim,NMEANIM_IDLE2,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
				sfxPlayNME3D(levelFX, SFX_CHESTER_CALL,enemy);

//				NMEPlaySound(levelFX, SFX_CHESTER_CALL,64,40, 200);

			}
			break;

	// attack start
		case 1:
			if(enemy->ticker == 0)
			{
				AddToQueue(&enemy->anim,NMEANIM_STARTATTACK,NO,NO,4096);
				sfxPlayNME3D(levelFX, SFX_CHESTER_GROWL,enemy);


			}
			enemy->ticker++;
			if(enemy->ticker >= 10)
			{
				sfxPlayNME3D(levelFX, SFX_CHESTER_ATTACK,enemy);

				enemy->vel.vy = -6 * 4096;
				enemy->vel.vx = -3 * rsin(enemy->ya);
				enemy->vel.vz = -3 * rcos(enemy->ya);
				enemy->doing=2;
			}

			break;

	// jump....
		case 2:
			enemy->pos.vx += enemy->vel.vx;
			enemy->pos.vy += enemy->vel.vy;
			enemy->pos.vz += enemy->vel.vz;

			if(enemy->pos.vy < enemy->init_pos.vy << 12)
			{
				enemy->vel.vy += 2048;
				enemy->pos.vy += enemy->vel.vy;
			}
			else
			{
	// backup start
				enemy->pos.vy = enemy->init_pos.vy << 12;
				enemy->doing = 3;
				enemy->ticker = 0;
				AddToQueue(&enemy->anim,NMEANIM_LAND,NO,YES,4096);
			}
			break;

		case 3:
			enemy->ticker++;
			if(enemy->ticker >= 20)
			{
				AddToQueue(&enemy->anim,NMEANIM_WALK,NO,YES,4096);	// walk anim is a single loop
				enemy->doing = 4;
				enemy->ticker = 0;
			}
			break;

// time the walk speed & the anim speed such that the anim matches up to the distance (!)
		case 4:
		{
			VECTOR v;
			enemy->ticker++;
			v.vx = enemy->init_pos.vx<<12;
			v.vy = enemy->init_pos.vy<<12;
			v.vz = enemy->init_pos.vz<<12;

			if(!(enemy->ticker & 15))
			{
				sfxPlayNME3D(levelFX, SFX_CHESTER_WALK,enemy);
			}

			if(NME_GoTo2D(enemy, &v, 2 * 4096))
			{
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
				enemy->doing = 0;
			}

			break;
		}

		default:
			PlaceNMEOnGround(enemy,1);
			enemy->init_pos.vy = enemy->pos.vy >> 12;

			AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,NO,4096);	// anim,number,loop,queue,speed
			enemy->ticker = 0;
			enemy->doing = 0;
			break;
	}

}



/*
// example robes instruction list
// note "1" "2" and "4" vary according to instr type
// normal flags
//#define	NMEFLAGS_MOVE_ACCEL				(1<<0)	//1	signals to accellerate enemies
//#define NMEFLAGS_MOVE_VEL				(1<<1)	//2	signals to add to velocity
//#define NMEFLAGS_DONT_SLOW_WHEN_NEAR	(1<<2)	//4	used with moveto's

//wait flags
//#define NMEFLAGS_SKID					(1<<0)	
//#define NMEFLAGS_STOPMOVE				(1<<1)
//#define NMEFLAGS_WAITFOREND				(1<<2) //queue the wait anim

//special flags
//#define ANIM_LOOP						(1<<0)	//1 loop animation
//#define ANIM_QUEUE						(1<<1)	//2	queue animation
//#define ANIM_KILLQ						(1<<2)	//4 kill anim queue



  NORMAL_INSTRUCTION	NME_RANDOMMOVE
  	400
  	-451.148590 16.108673 -451.485870 903.409424 153.423218 895.069214
  	120 	16385	ALWAYS			0	0

(16385 = &4001 = don't animate, move accel)


   NORMAL_INSTRUCTION	NME_WAIT		100	0  			0		16384	SOMETIMES		700 	0
(&4000 = don't animate)


    NORMAL_INSTRUCTION 	NME_BRANCH		-1 	0 			0		0	ALWAYS			0	0
    
     CONDITIONAL_INSTRUCTION	NME_ATTACK		-1	0   			0   		65536	IF_BALL_IN_RANGE 	0 	200
     CONDITIONAL_INSTRUCTION	NME_BRANCH		-1	3   			0		65536	IF_HAND_IN_RANGE 	0 	120
(65536 = onlyanimate start



 
// this is if the hand's in range
 	NORMAL_INSTRUCTION	NME_DONTATTACK  	-1   	-200   			0		0	ALWAYS 		0 	0
(just sets script->blockAttack)

 	NORMAL_INSTRUCTION	NME_SPECIAL		-1	-1			14	  	1048576	ALWAYS 		0 	0	stopmove
(&10 0000 = face hand)
 	NORMAL_INSTRUCTION	NME_FACEPLAYER  	-1   	0   			0		16448	ALWAYS 		0 	0
(&4040 =  specify hand, don't animate)
 	NORMAL_INSTRUCTION	NME_SPECIAL		-1	-1	 		1	  	1048576	ALWAYS 		0 	0	action2
 	NORMAL_INSTRUCTION	NME_BRANCH		-1	 0	 		0		0	ALWAYS 		0 	0




// so this is if the ball's in range 
 	ATTACK_INSTRUCTION	NME_SPECIAL		-1	-1			14	  	2097152	ALWAYS 		0 	0 stopmove //(2097152 = face ball)
 	ATTACK_INSTRUCTION	NME_SPECIAL		-1	-1			12	  	2097152	ALWAYS 		0 	0 startttack
 	ATTACK_INSTRUCTION	NME_SPECIAL		 1	-1			9	  	2097153	ALWAYS 		0 	0 run
//(and that's face ball & 1-loop anim)

 	ATTACK_INSTRUCTION	NME_FOLLOWPLAYER 	500 	0 45 0 			50	  	129	ALWAYS 		0 	0
//(&81 = 1-loopanim, specify ball)

 	ATTACK_INSTRUCTION	NME_FACEPLAYER		-1	0			0		16512 	IF_BALL_IN_RANGE 	0 	110	
//(&4080 = don't animate, specify ball)

 	ATTACK_INSTRUCTION	NME_SPECIAL		-1	-1			0		0	IF_BALL_IN_RANGE 	0 	110		action1
 	ATTACK_INSTRUCTION	NME_ENDATTACK		0 	-200  			0		16384	ALWAYS 		0 	0
*/


// so... 14,12,9...followplayer...0 in N64 anims means stopmove,startattack,run...action1

#define ROBES_CHOOSING  0
#define ROBES_WANDERING 1
#define ROBES_WAITING   3
#define ROBES_CHASE_BALL 2
#define ROBES_CHASE_GLOVE 4


#define ROBES_BALLSPELL 5
#define ROBES_GLOVESPELL 6

#define ROBES_CHOOSING2  7
#define ROBES_WANDERING2 8

#define ROBES_CHASE_TIME 200

#define ROBES_BALL_CHASE_RANGE (4096 * 300)
#define ROBES_BALL_GIVEUP_RANGE (4096 * 400)
#define ROBES_BALL_SPELL_RANGE (4096 * 100)

int CheckRobesConditions(ENEMYPOS *enemy)
{
	ENEMYPOS *other;
	if(NMECheckInRange2D(enemy, &ballPos, ROBES_BALL_CHASE_RANGE,YES))
	{

// scan through the enemy list & don't chase if another ethel is already chasing (fear bonus)

		for (other = nme_list; other; other = other->next)
		{
			if(other->type == ROBES && (other->flags & NMEFLAG_ACTIVE))
			{
				if(other->doing == ROBES_CHASE_BALL || other->doing == ROBES_BALLSPELL)
					break;
			}
		}

		if(!other)
		{
			AddToQueue(&enemy->anim,NMEANIM_STOPMOVE,NO,NO,4096);	// (loop, queue)
			AddToQueue(&enemy->anim,NMEANIM_STARTATTACK,NO,YES,4096);
			AddToQueue(&enemy->anim,NMEANIM_RUN,YES,YES,4096);
			enemy->ticker = ROBES_CHASE_TIME;	// how long to follow the ball for
			enemy->doing = ROBES_CHASE_BALL;
			return 1;
		}
	}

// Ethel no longer chases after the glove
/*
	else if(NMECheckInRange(enemy, &glovePos, 4096 * 120,YES) && enemy->doing != ROBES_CHASE_GLOVE)
	{
		AddToQueue(&enemy->anim,NMEANIM_STOPMOVE,NO,NO,4096);	// (loop, queue)
		AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);

		enemy->ticker = ROBES_CHASE_TIME;
		enemy->doing = ROBES_CHASE_GLOVE;
		return 1;
	}
*/
	return 0;
}

void Robes_SetWalking(ENEMYPOS *enemy)
{
	AddToQueue(&enemy->anim,NMEANIM_STARTMOVE,NO,NO,4096);
	AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);
	enemy->doing = ROBES_CHOOSING;
}
void Robes_SetWalking2(ENEMYPOS *enemy)
{
	AddToQueue(&enemy->anim,NMEANIM_STARTMOVE,NO,NO,4096);
	AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);
	enemy->doing = ROBES_CHOOSING2;
}

void Robes_Spellpos(ENEMYPOS *enemy, VECTOR *out)
{
	static VECTOR refpos = {0,-5,0};
	VECTOR pos;

	EnemyRotatePoint(enemy,&refpos,&pos,6);
	out->vx = enemy->pos.vx + (pos.vx << 12);
	out->vy = enemy->pos.vy + (pos.vy << 12);
	out->vz = enemy->pos.vz + (pos.vz << 12);
}


void Update_Robes(ENEMYPOS *enemy)
{
	static const NMEAccelStr robeaccel =
	{
		0x180,
		0x2400,
		100,
		1000
	};

	switch(enemy->doing)
	{
	case ROBES_CHOOSING:	// picking a rnd position
			enemy->ticker = 0;		
			enemy->doing = ROBES_WANDERING;

			NMEChooseRandomPoint(enemy,YES);
			sfxPlayNME3D(levelFX, SFX_FF_EERIE_WIND,enemy);	// and why not...?


// ... dropthru
		case ROBES_WANDERING:
			if(!CheckRobesConditions(enemy))
			{
				enemy->ticker++;
				if(NME_AccelTo2D(enemy,&enemy->pathvectors[2],&robeaccel) || enemy->ticker > 200)
				{
// "sometimes" do a wait...
					if(random(1000) < 700)
					{
						sfxPlayNME3D(levelFX, SFX_ETHEL_CALL,enemy);

						AddToQueue(&enemy->anim,NMEANIM_STOPMOVE,NO,NO,4096);
						AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
						enemy->doing = ROBES_WAITING;
						enemy->ticker = 100;
					}
					else
					{
						enemy->doing = ROBES_CHOOSING;
					}
				}
			}
			break;

		case ROBES_CHASE_BALL:	// running towards the ball (this is the "attack" sequence
// ummm, there's no limiter on range here
			enemy->ticker--;
			NME_AccelTo2D(enemy,&ballPos,&robeaccel);

			if(NMECheckInRange2D(enemy, &ballPos, ROBES_BALL_SPELL_RANGE, TRUE))
			{
				enemy->doing = ROBES_BALLSPELL;
				AddToQueue(&enemy->anim,NMEANIM_ACTION1,NO,NO,4096);	// for now
				enemy->ticker = 30;
			}
			else if(enemy->ticker <= 0 || !NMECheckInRange(enemy, &ballPos, ROBES_BALL_GIVEUP_RANGE, TRUE))
			{
// tbd - If we're close to the ball, do a "startattack"
				Robes_SetWalking(enemy);
			}
			break;


		case ROBES_BALLSPELL:
			enemy->ticker--;
			if(enemy->ticker == 10)
			{
//				static int spells[] = {SPELL_BALL_CRYSTAL,SPELL_CANCERBALL,SPELL_BALL_BOWLING,SPELL_BALL_POWER};
				static int spells[] = {SPELL_BALL_CRYSTAL,SPELL_CANCERBALL,SPELL_BALL_BOWLING,SPELL_BALL_BEARING};
				SPELLSTR *spell;
				VECTOR pos;

				sfxPlayNME3D(levelFX, SFX_ETHEL_CALL,enemy);
				sfxPlayNME3D(globalFX, SFX_GE_ETHEL_ATTACK,enemy);

				Robes_Spellpos(enemy, &pos);

				spell = pickupFireSpell(spells[RANDOM256() & 3],&pos,enemy->ya,NULL,SPELLACTION_BALLTRANS);


//				enemy->doing = 0;
				spell->duration = 300;
			}

			if(!enemy->ticker)
			{
				Robes_SetWalking2(enemy);
			}
			break;



		case ROBES_WAITING:	// waiting for a while
			enemy->ticker--;
			if(enemy->ticker == 0)
			{
				AddToQueue(&enemy->anim,NMEANIM_STARTMOVE,NO,NO,4096);
				AddToQueue(&enemy->anim,NMEANIM_WALK,NO,NO,4096);
				enemy->doing = ROBES_CHOOSING;
			}
			break;

// when close to the glove
// (doesn't happen any more)
		case ROBES_CHASE_GLOVE:
			if(!CheckRobesConditions(enemy))	// allow it to break back into the ball-chasing mode
			{
				if(NME_FaceTo(enemy, &glovePos,robeaccel.turnrate))	// doesn't chase
				{
//					spell =
					AddToQueue(&enemy->anim,NMEANIM_ACTION2,NO,NO,4096);
					enemy->doing = ROBES_GLOVESPELL;
					enemy->ticker = 30;
				};
			}
			break;

		case ROBES_GLOVESPELL:
			enemy->ticker--;
			if(enemy->ticker == 10)
			{
				SPELLSTR *spell;
				VECTOR pos;
				Robes_Spellpos(enemy, &pos);
				sfxPlayNME3D(globalFX, SFX_GE_ETHEL_ATTACK,enemy);

				spell = pickupFireSpell(SPELL_FROGGIFY,&pos,enemy->ya,NULL,SPELLACTION_GLOVETRANS);
//				enemy->doing = 0;
				spell->duration = 600;
			}
			if(!enemy->ticker)
			{
				Robes_SetWalking2(enemy);
			}
			break;


	case ROBES_CHOOSING2:	// picking a rnd position
			enemy->ticker = 0;		
			enemy->doing = ROBES_WANDERING2;

			NMEChooseRandomPoint(enemy,YES);

// ... dropthru
		case ROBES_WANDERING2:
			enemy->ticker++;
			DB("robes wander 2\n");
			if(NME_AccelTo2D(enemy,&enemy->pathvectors[2],&robeaccel) || enemy->ticker > 300)
			{
				enemy->doing = ROBES_CHOOSING;
			}
			break;


		default:
			AddToQueue(&enemy->anim,NMEANIM_WALK,YES,NO,4096);
			enemy->doing = 0;
			break;
	}
}




/*
 ENEMY SAMTEX 400 120 400 0 0.000000
 
 
  NORMAL_INSTRUCTION	NME_FACETO		-1	400 120 400		0		0	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_WAIT		30	0			1		0	ALWAYS			0	0
 NORMAL_INSTRUCTION	NME_SPECIAL		-1	0			0		0	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_JUMP		-1	0 12 8			16384		0	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_SPECIAL		-1	0			0		0	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_JUMP		-1	0 12 8			16384		0	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_SPECIAL		-1	0			0		0	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_JUMP		-1	0 12 8		0		0	ALWAYS 		0 	0


 NORMAL_INSTRUCTION	NME_FACETO		-1	400 120 600		0		0	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_WAIT		30	0			1		0	ALWAYS			0	0
 NORMAL_INSTRUCTION	NME_SPECIAL		-1	0			0		0	ALWAYS 		0 	0 (normal->squished)
 NORMAL_INSTRUCTION	NME_JUMP		-1	0 12 8			16384		0	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_SPECIAL		-1	0			0		0	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_JUMP		-1	0 12 8			16384		0	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_SPECIAL		-1	0			0		0	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_JUMP		-1	0 12 8		0		0	ALWAYS 		0 	0

   NORMAL_INSTRUCTION	NME_BRANCH		-1	0			0		0	ALWAYS 		0 	0
 
   CONDITIONAL_INSTRUCTION	NME_ATTACK		-1	0			0		0	IF_HAND_IN_RANGE_2D 	0 	100
 
   ATTACK_INSTRUCTION	NME_SPECIAL		-1	0			0		1048576	ALWAYS 		0 	0
   ATTACK_INSTRUCTION	NME_JUMP		-1	0 6.5 7			16384		1048640	ALWAYS 		0 	0
   ATTACK_INSTRUCTION	NME_BRANCH		-1	0			0		0	ALWAYS 		0 	0
 
 
 END_ENEMY

*/

#define SAMTEX_FACE1 1

#define SAMTEX_PREJUMP1A 2
#define SAMTEX_PREJUMP1B 4
#define SAMTEX_PREJUMP1C 6

#define SAMTEX_JUMP1A 3
#define SAMTEX_JUMP1B 5
#define SAMTEX_JUMP1C 7

#define SAMTEX_FACE2 129
#define SAMTEX_PREJUMP2A 130
#define SAMTEX_PREJUMP2B 132
#define SAMTEX_PREJUMP2C 134

#define SAMTEX_JUMP2A 131
#define SAMTEX_JUMP2B 133
#define SAMTEX_JUMP2C 135

#define SAMTEX_CHASE_FACE	 160
#define SAMTEX_CHASE_PREJUMP 161
#define SAMTEX_CHASE_JUMP    162
#define SAMTEX_CHASE_EXPLODE 163

#define SAMTEX_CHASE_RANGE (4096 * 600)
#define SAMTEX_EXPLODE_RANGE (4096 * 100)

#define SAMTEX_EXPLOSION_RANGE (100 * 8)
#define SAMTEX_EXPLOSION_POWER 80

// zoiks, so it's three jumps towards one point, turn, three jumps towards t'other, break from path & jump
// to glover (which will need true physics & the ability to cope with falling off the world)

void Samtex_ChaseCheck(ENEMYPOS *enemy)
{
	if(NMECheckInRange(enemy,&glovePos,SAMTEX_CHASE_RANGE,FALSE))
	{
		enemy->doing = SAMTEX_CHASE_FACE;
		enemy->ticker = 0;
	}
}

void Update_Samtex(ENEMYPOS *enemy)
{
	switch(enemy->doing)
	{

	case SAMTEX_FACE1:
		if(NME_FaceTo(enemy, &enemy->pathvectors[0],30))
		{
			enemy->doing = SAMTEX_PREJUMP1A;
			enemy->ticker = 0;
		}
		Samtex_ChaseCheck(enemy);
		break;
	case SAMTEX_FACE2:
		if(NME_FaceTo(enemy, &enemy->pathvectors[1],30))
		{
			enemy->doing = SAMTEX_PREJUMP2A;
			enemy->ticker = 0;
		}
		Samtex_ChaseCheck(enemy);
		break;


	case SAMTEX_PREJUMP1A:
	case SAMTEX_PREJUMP1B:
	case SAMTEX_PREJUMP1C:
	case SAMTEX_PREJUMP2A:
	case SAMTEX_PREJUMP2B:
	case SAMTEX_PREJUMP2C:
		if(enemy->ticker == 0)
		{
			AddToQueue(&enemy->anim,NMEANIM_SPECIAL1,NO,NO,4096);	// squish down
			AddToQueue(&enemy->anim,NMEANIM_JUMP,NO,YES,4096);		// jump
		}

		if(enemy->ticker >= 5)
		{
			sfxPlayNME3D(globalFX, SFX_GE_SAMTEX_WALK,enemy);

			enemy->ticker = 0;
			enemy->vel.vx = -3 * rsin(enemy->ya);
			enemy->vel.vy = -5 * 4096;
			enemy->vel.vz = -3 * rcos(enemy->ya);
			enemy->pos.vy += enemy->vel.vy;
			enemy->ticker = 0;
			enemy->doing++;
		}
		enemy->ticker++;
		Samtex_ChaseCheck(enemy);
		break;

	case SAMTEX_JUMP1A:
	case SAMTEX_JUMP1B:
	case SAMTEX_JUMP1C:
	case SAMTEX_JUMP2A:
	case SAMTEX_JUMP2B:
	case SAMTEX_JUMP2C:
		if(enemy->pos.vy < enemy->init_pos.vy << 12)
		{
			enemy->vel.vy += 1024;
			enemy->pos.vx += enemy->vel.vx;
			enemy->pos.vy += enemy->vel.vy;
			enemy->pos.vz += enemy->vel.vz;
		}
		else
		{
			enemy->pos.vy = enemy->init_pos.vy << 12;
			AddToQueue(&enemy->anim,NMEANIM_LAND,NO,NO,4096);
			AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);

			if(enemy->doing == SAMTEX_JUMP1C)
			{
				enemy->doing = SAMTEX_FACE2;
			}
			else if (enemy->doing == SAMTEX_JUMP2C)
			{
				enemy->doing = SAMTEX_FACE1;
			}
			else
			{
				enemy->doing++;
			}
			enemy->ticker = 0;

		}

		break;


	case SAMTEX_CHASE_FACE:
		if(!(activeframe & 3))
			sfxPlayNME3D(levelFX, SFX_SAMTEX_FUSE,enemy);

		if(NME_FaceTo(enemy, &glovePos,50))
		{
			enemy->doing = SAMTEX_CHASE_PREJUMP;
			enemy->ticker = 0;
		}
		break;

	case SAMTEX_CHASE_PREJUMP:
		if(!(activeframe & 3))
			sfxPlayNME3D(levelFX, SFX_SAMTEX_FUSE,enemy);

		if(enemy->ticker == 0)
		{
			AddToQueue(&enemy->anim,NMEANIM_SPECIAL1,NO,NO,4096);	// squish down
			AddToQueue(&enemy->anim,NMEANIM_JUMP,NO,YES,4096);		// jump
		}

		if(enemy->ticker >= 5)
		{
			sfxPlayNME3D(globalFX, SFX_GE_SAMTEX_WALK,enemy);
			enemy->ticker = 0;
			enemy->vel.vx = -3 * rsin(enemy->ya);
			enemy->vel.vy = -5 * 4096;
			enemy->vel.vz = -3 * rcos(enemy->ya);
			enemy->pos.vy += enemy->vel.vy;
			enemy->ticker = 0;
			enemy->doing++;
		}
		enemy->ticker++;
		break;

// tbd - this is where we need some realworld collision checks
	case SAMTEX_CHASE_JUMP:
		if(!(activeframe & 3))
			sfxPlayNME3D(levelFX, SFX_SAMTEX_FUSE,enemy);

		if(enemy->pos.vy < enemy->init_pos.vy << 12)
		{
			enemy->vel.vy += 1024;
			enemy->pos.vx += enemy->vel.vx;
			enemy->pos.vy += enemy->vel.vy;
			enemy->pos.vz += enemy->vel.vz;
		}
		else
		{
			enemy->pos.vy = enemy->init_pos.vy << 12;
			AddToQueue(&enemy->anim,NMEANIM_LAND,NO,NO,4096);


			if(NMECheckInRange(enemy,&glovePos,SAMTEX_EXPLODE_RANGE,FALSE))
			{
				enemy->doing = SAMTEX_CHASE_EXPLODE;
				AddToQueue(&enemy->anim,NMEANIM_STARTATTACK,YES,YES,4096);
				enemy->ticker = 0;
			}
			else
			{
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
				enemy->doing = SAMTEX_CHASE_FACE;
				enemy->ticker = 0;
			}
		}

		break;

	case SAMTEX_CHASE_EXPLODE:
		enemy->ticker++;
		if(enemy->ticker >= 20)
		{
			ForceExplosion(&enemy->pos, SAMTEX_EXPLOSION_RANGE, SAMTEX_EXPLOSION_POWER);
			sfxPlayNME3D(globalFX, SFX_GE_SAMTEX_EXPLODE,enemy);

			enemy->flags &= ~(NMEFLAG_ACTIVE);	// don't disable - needs respawn
			enemy->flags |= (NMEFLAG_DEAD);
		}
		break;


	default:
		AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
		PlaceNMEOnGround(enemy,0);
		enemy->init_pos.vy = enemy->pos.vy >> 12;
		enemy->doing = SAMTEX_FACE1;
		break;
	}
//	printf("samtex doing %d\n",enemy->doing);
}

/*
 ENEMY FUMBLE -400 120 400 1 0.000000

  	NORMAL_INSTRUCTION		NME_WAIT 0 0 1 0 ALWAYS 0 0
 
 	NORMAL_INSTRUCTION		NME_SPECIAL -1 -1 13 0 ALWAYS 0 0	(startmove)
 	NORMAL_INSTRUCTION		NME_RANDOMMOVE 0 -400 -20 400 400 400 400 30 1 ALWAYS 0 0
	NORMAL_INSTRUCTION		NME_SPECIAL	-1 -1 4 0 ALWAYS 0 0	(hurt)
 	NORMAL_INSTRUCTION		NME_SPECIAL	 1 -1 6 1 ALWAYS 0 0	(idle2)
 	NORMAL_INSTRUCTION		NME_WAIT	50 0 0  16384 ALWAYS 0 0
 	NORMAL_INSTRUCTION		NME_SPECIAL -1 -1 0 0 ALWAYS 0 0	(actiion1)
 	NORMAL_INSTRUCTION		NME_BRANCH  1  2  0 0 ALWAYS 0 0
 
 	CONDITIONAL_INSTRUCTION	NME_ATTACK 0 0 0 0 IF_BALL_IN_RANGE 0 70
 
 	ATTACK_INSTRUCTION		NME_SPECIAL 1 -1 15 1 ALWAYS 0 0	(walk)
 	ATTACK_INSTRUCTION		NME_FOLLOWPLAYER 100 0 0 0 20 129 ALWAYS 0 0
 	ATTACK_INSTRUCTION		NME_ENDATTACK    1 0 0 16384		IF_BALL_IN_RANGE_2D 20 10000
 	ATTACK_INSTRUCTION		NME_SPECIAL		 -1 0 0 2097152 ALWAYS 0 0	(special1)
 	ATTACK_INSTRUCTION		NME_ENDATTACK    1 0 0 16384		IF_BALL_IN_RANGE_2D 25 10000
 	ATTACK_INSTRUCTION		NME_HOLDPLAYER	 1  0 0 0 1 131200 ALWAYS 0 0 
 	ATTACK_INSTRUCTION		NME_SPECIAL		 -1 1 0 0 ALWAYS 0 0 (special2)
 	ATTACK_INSTRUCTION		NME_RANDOMMOVE 0 -400 -20 400 400 400 400  100 262145 ALWAYS 0 0

 
 END_ENEMY
*/
// action1 = get up from lying on back
// startmove = get up from bent
// hurt = falls onto back

// idle = bent
// idle2 = on back

// run & walk are different
// special1, special2 = open & shut grabs

#define FUMBLE_SLEEP_RANGE (4096 * 600)
#define FUMBLE_CHASE_RANGE (4096 * 300)
#define FUMBLE_GIVEUP_RANGE (4096 * 400)
#define FUMBLE_GRAB_RANGE (4096 * 20)

#define FUMBLE_RSPEED 80
//#define FUMBLE_WALK_SPEED (4096 * 2)
#define FUMBLE_WALK_SPEED (6000)
#define FUMBLE_CHASE_SPEED (4096 * 3)
#define FUMBLE_RUN_SPEED (4096 * 4)

#define FUMBLE_WITH_BALL1 6
#define FUMBLE_WITH_BALL2 7
#define FUMBLE_STUNNED    8

void Update_Fumble(ENEMYPOS *enemy)
{
	int f;

	f = enemy->anim.animInfo->frame;
	f+=enemy->anim.animInfo->segInfo[enemy->anim.currentAnimation].segStart;

	switch(f)
	{
	case 69:
	case 84:
	case 119:
	case 123:
		sfxPlayNME3D(levelFX, SFX_FUMBLE_WALK,enemy);
		break;
	case 87:
		sfxPlayNME3D(levelFX, SFX_FUMBLE_SPOTBALL,enemy);
		break;
	case 136:
		sfxPlayNME3D(levelFX, SFX_FUMBLE_KEEPBALANCE,enemy);
		break;
	}

	switch(enemy->doing)
	{
	case 0:	// waiting
		if(NMECheckInRange(enemy, &glovePos, FUMBLE_SLEEP_RANGE,TRUE)
			||
			NMECheckInRange(enemy, &ballPos, FUMBLE_SLEEP_RANGE,TRUE)
			)
		{
			enemy->doing = 1;
			enemy->ticker = 0;
			sfxPlayNME3D(levelFX, SFX_FUMBLE_CALL,enemy);

			AddToQueue(&enemy->anim,NMEANIM_STARTMOVE,NO,YES,4096);	// up you get...
			AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);
		}
		break;

// post stunned/waking delay
	case 1:
		enemy->ticker++;
		if(enemy->ticker == 30)
		{
			enemy->doing = 2;
		}
		break;

	case 2:
		NMEChooseRandomPoint(enemy,YES);
		enemy->doing = 3;
		enemy->ticker = 0;

	case 3:
		if(NME_FaceTo(enemy, &enemy->pathvectors[2], FUMBLE_RSPEED))
		{
			if(NME_GoTo2D(enemy, &enemy->pathvectors[2], FUMBLE_WALK_SPEED))
			{
				enemy->doing = 2;
			}
		}

		if(NMECheckInRange(enemy, &ballPos, FUMBLE_CHASE_RANGE,TRUE))
		{
			enemy->doing = 4;
		}
		break;

// chasing ball
	case 4:
		if(NME_FaceTo(enemy, &ballPos, FUMBLE_RSPEED))
		{
			NME_GoTo2D(enemy, &ballPos, FUMBLE_CHASE_SPEED);
		}

		if(!NMECheckInRange(enemy, &ballPos, FUMBLE_GIVEUP_RANGE,TRUE))
		{
			enemy->doing = 2;
		}
		if(NMECheckInRange(enemy, &ballPos, FUMBLE_GRAB_RANGE,TRUE))
		{
			if(NMECheckVisible(enemy, &ballPos, 4096 * FUMBLE_GRAB_RANGE, 128))
			{
				AddToQueue(&enemy->anim,NMEANIM_SPECIAL1,NO,NO,4096);
				AddToQueue(&enemy->anim,NMEANIM_SPECIAL2,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_RUN,YES,YES,4096);
				enemy->doing = 5;
				enemy->ticker = 0;
			}
		}
		break;

// fumble grabbing the ball
	case 5:
		enemy->ticker++;
		if(enemy->ticker >= 30)
		{
			if(NMECheckInRange(enemy, &ballPos, FUMBLE_GRAB_RANGE,TRUE))
			{
				if(NMECheckVisible(enemy, &ballPos, 4096 * FUMBLE_GRAB_RANGE, 128))
				{
					enemy->ticker = 0;
					enemy->doing = FUMBLE_WITH_BALL1;
					enemy_has_ball = 2;
				}
			}
			else	// if the ball's been knocked away by this time...
			{
				AddToQueue(&enemy->anim,NMEANIM_WALK,YES,NO,4096);
				enemy->doing = 2;
			}
		}
		break;

// fumble running around with the ball
	case FUMBLE_WITH_BALL1:
		NMEChooseRandomPoint(enemy,YES);
		enemy->doing = 7;
		enemy->ticker = 0;

	case FUMBLE_WITH_BALL2:
	{
		static VECTOR balloffs = {0,-15,-15};
		VECTOR temp;

		if(!(activeframe & 3))
			sfxPlayNME3D(globalFX, SFX_GE_FUMBLE_PANTING_LOOP, enemy);

		if(NME_FaceTo(enemy, &enemy->pathvectors[2], FUMBLE_RSPEED))
		{
			if(NME_GoTo2D(enemy, &enemy->pathvectors[2], FUMBLE_RUN_SPEED))
			{
				enemy->doing = FUMBLE_WITH_BALL1;
			}
		}

/*
		ballVel.vx = 0;
		ballVel.vy = 0;
		ballVel.vz = 0;

		EnemyRotatePoint(enemy,&balloffs,&temp,0);
		ballPos.vx = enemy->pos.vx + (temp.vx << 12);
		ballPos.vy = enemy->pos.vy + (temp.vy << 12);
		ballPos.vz = enemy->pos.vz + (temp.vz << 12);
*/

		EnemyRotatePoint(enemy,&balloffs,&temp,0);
		temp.vx = enemy->pos.vx + (temp.vx << 12);
		temp.vy = enemy->pos.vy + (temp.vy << 12);
		temp.vz = enemy->pos.vz + (temp.vz << 12);
		ballPlaceAt(&temp);

		enemy_has_ball = 2;

		break;
	}

	case FUMBLE_STUNNED:
		enemy->ticker++;
		if(enemy->ticker >= 200)
		{
			AddToQueue(&enemy->anim,NMEANIM_ACTION1,NO,YES,4096);
			AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);
			enemy->ticker = 0;
			enemy->doing = 1;

		}
		break;

	default:
		AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,NO,4096);	// bent over
		PlaceNMEOnGround(enemy,0);
		enemy->init_pos.vy = enemy->pos.vy >> 12;
		enemy->doing = 0;
		break;
	}
}

// actually, you can knock him over when he;s not got the ball, too
void FumbleDropBall(ENEMYPOS *nme)
{
	if(nme->doing == FUMBLE_WITH_BALL1 || nme->doing == FUMBLE_WITH_BALL2)
	{
		ballVel.vx = -5 * rsin(nme->ya);
		ballVel.vy = 8 * 4096;
		ballVel.vz = -5 * rcos(nme->ya);;

	}
	if(nme->doing != FUMBLE_STUNNED)
	{
		AddToQueue(&nme->anim,NMEANIM_HURT,NO,YES,4096);
		AddToQueue(&nme->anim,NMEANIM_IDLE2,YES,YES,4096);
	}
	nme->doing = FUMBLE_STUNNED;
	nme->ticker = 0;
}

// Fumble doesn't push the ball around - he just grabs it
void FumbIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
		if(nme->doing == FUMBLE_STUNNED)
		{
			if(GloveCtrl.action == HAND_SLAM|| GloveCtrl.action == HAND_SLAM2ST) // && Glover.animInfo->frame>=4)	// the frame check's a tad messy, but WTF?
			{
				EnemyKill(nme);
			}
			else
			{
				NMEPushesGlove(nme,dvec,sphererad);
			}
		}
		else if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
//			if(!IsBallShield(dvec))
			DisconnectGloveFromBall(DISCONNECT_WHEN_JOINED | DISCONNECT_WHEN_BOUNCING | DISCONNECT_WHEN_WALKINGON);	// | DISCONNECT_GENTLY);
		}
		else
		{
			NMEPushesGlove(nme,dvec,sphererad);
//			if(!IsBallShield(dvec))
//				NMEHurtsGlove(nme,dvec,sphererad);
		}
	}
	else
	{
		if(BallCtrl.powerwhack)
		{
			PowerWhackHandler(nme,1);
			return;
		}

		if(nme->doing == FUMBLE_STUNNED)
		{
			NMEPushesBall(nme,dvec,sphererad);
		}
		else if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
			DisconnectGloveFromBall(DISCONNECT_WHEN_JOINED | DISCONNECT_WHEN_BOUNCING | DISCONNECT_WHEN_WALKINGON);	// | DISCONNECT_GENTLY);
		}
	}
}



/*
 ENEMY KNIGHT -100 120 100 0 0.000000

 NORMAL_INSTRUCTION  NME_WAIT 100 0 0 16384 ALWAYS 0 0		(don't anim
 NORMAL_INSTRUCTION  NME_SPECIAL -1 1 0 0 ALWAYS 0 0		(special2)
 NORMAL_INSTRUCTION  NME_SPECIAL -1 -1 6 0 SOMETIMES 500 0	(idle2)
 NORMAL_INSTRUCTION  NME_BRANCH -1 0 0 0 ALWAYS 0 0
 END_ENEMY
*/
// special1 is a -> to -> hit
// special2 =s a ^ to ^ hit
// idle1 is a -> idle
// idle2 is a ^ idle

void Update_Knight(ENEMYPOS *enemy)
{
	int f;

	f = enemy->anim.animInfo->frame;
	f+=enemy->anim.animInfo->segInfo[enemy->anim.currentAnimation].segStart;

	switch(f)
	{
	case 55:
	case 73:
		sfxPlayNME3D(levelFX, SFX_THRICE_ATTACK,enemy);
		break;

	case 173:
	case 343:
		sfxPlayNME3D(levelFX, SFX_THRICE_IDLE,enemy);
		break;
	}

	switch(enemy->doing)
	{
		case 0:
// don't allow him to hit if the camera's fixed
// (end of fear1)
			if ((CamVars.flags&(FIXEDPOSITION + FIXEDLOOKAT)))
			{
				if(enemy->ticker > 80)
					enemy->ticker = 80;
			}

			enemy->ticker++;

			if(enemy->ticker >= 100)
			{

				
				enemy->ticker = 0;
//				enemy->doing = 1;
				AddToQueue(&enemy->anim,NMEANIM_SPECIAL2,NO,NO,4096);	// (loop, queue)
				if(!random(4))
				{
					AddToQueue(&enemy->anim,NMEANIM_IDLE2,NO,YES,4096);		// (loop, queue)
					enemy->ticker = -50;
				}
//				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);	// (loop, queue)
			}
			break;

		case -1:
			enemy->doing = 0;
			enemy->ticker = RANDOM256() & 63;	// just to offset the anims a bit


			AddToQueue(&enemy->anim,NMEANIM_IDLE2,NO,YES,4096);
			break;
	}
}
/*
 ENEMY MIKE -200 120 -200 0 0.000000
 

NORMAL_INSTRUCTION	NME_RANDOMMOVE	100	-200 120 -200       400 400 400      0 	5 	ALWAYS 		0 	0

NORMAL_INSTRUCTION	NME_WAIT		-1	4			0		0	ALWAYS 		0 	0
NORMAL_INSTRUCTION	NME_WAIT		-1	0			0		0	SOMETIMES 		800 	0
NORMAL_INSTRUCTION	NME_BRANCH		-1	0			0		0	ALWAYS 		0 	0


CONDITIONAL_INSTRUCTION	NME_ATTACK		-1	0			0		0	IF_BALL_IN_RANGE 	0 	200

ATTACK_INSTRUCTION	NME_SPECIAL		1	-1			15		1	ALWAYS 		0 	0	// special2 = stretchy
ATTACK_INSTRUCTION	NME_FOLLOWPLAYER	100	0 0 0			100		133	ALWAYS 		0 	0	// ball, don't slow

ATTACK_INSTRUCTION	NME_ENDATTACK		 0	50			0		16384	IF_BALL_IN_RANGE_2D 	100 	10000
ATTACK_INSTRUCTION	NME_BRANCH			-1	5			0		0	IF_CANSEE_BALL		1 	1
ATTACK_INSTRUCTION	NME_ENDATTACK		 0	50			0		16384	ALWAYS			0 	0	// no anim


ATTACK_INSTRUCTION	NME_SPECIAL		-1	0			0		2097154	ALWAYS 		0 	0	// face ball, action1
ATTACK_INSTRUCTION	NME_JUMP		-1	0 12 15			0		8388736	ALWAYS 		0 	0
ATTACK_INSTRUCTION	NME_WAIT		10	0			0		16384	ALWAYS 		0 	0	// no anum
ATTACK_INSTRUCTION	NME_SPECIAL		-1	-1			6		0	ALWAYS 		0 	0	// idle2
ATTACK_INSTRUCTION  NME_WAIT -1 0 0 0 ALWAYS 0 0 
ATTACK_INSTRUCTION	NME_ENDATTACK		 0	50			0		16384	ALWAYS 		0 	0 // no anim


 END_ENEMY

*/
#define MIKERSPEED 80
#define MIKESPEED 4096
#define MIKE_CHASE_RANGE (4096 * 300)
#define MIKE_GIVEUP_RANGE (4096 * 350)
#define MIKE_JUMP_RANGE (4096 * 90)

void Mike_TapsFoot(ENEMYPOS *enemy)
{
	AddToQueue(&enemy->anim,NMEANIM_IDLE,NO,YES,4096);
	AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);
	enemy->doing = 2;
	enemy->ticker = 30;
}

void Update_Mike(ENEMYPOS *enemy)
{
	int f;

	f = enemy->anim.animInfo->frame;
	f+=enemy->anim.animInfo->segInfo[enemy->anim.currentAnimation].segStart;

	switch(f)
	{
	case 42:
	case 25:
		sfxPlayNME3D(levelFX, SFX_MIKE_WALK_LEFT,enemy);
		break;

	case 60:
	case 35:
		sfxPlayNME3D(levelFX, SFX_MIKE_WALK_RIGHT,enemy);
		break;
	}


	switch(enemy->doing)
	{
		case 0:
			enemy->ticker = 0;		
			enemy->doing = 1;
			NMEChooseRandomPoint(enemy,YES);
// ...
		case 1:
			if(NME_FaceTo(enemy, &enemy->pathvectors[2], MIKERSPEED))
			{
				if(NME_GoTo2D(enemy, &enemy->pathvectors[2], MIKESPEED))
				{
					if(!random(4))
					{
						Mike_TapsFoot(enemy);
					}
					enemy->doing = 0;
				}
			}

			if(NMECheckInRange(enemy, &ballPos, MIKE_CHASE_RANGE,TRUE))
			{
				enemy->doing = 3;
			}

			PlaceNMEOnGround(enemy,1);
			break;

		case 2:	// wait, foot-tap
			enemy->ticker--;
			if(!enemy->ticker)
			{
				enemy->doing = 0;
			}
			break;
// chasing ball
		case 3:
			if(NME_FaceTo(enemy, &ballPos, MIKERSPEED))
			{
				NME_GoTo2D(enemy, &ballPos, MIKESPEED);
			}

			if(!NMECheckInRange(enemy, &ballPos, MIKE_GIVEUP_RANGE,TRUE))
			{
				Mike_TapsFoot(enemy);
			}
			if(NMECheckInRange(enemy, &ballPos, MIKE_JUMP_RANGE,TRUE))
			{
				if(NMECheckVisible(enemy, &ballPos, MIKE_JUMP_RANGE, 128))
				{
					AddToQueue(&enemy->anim,NMEANIM_JUMP,NO,NO,4096);
					AddToQueue(&enemy->anim,NMEANIM_SPECIAL2,YES,YES,4096);
					enemy->doing = 4;
					enemy->ticker = 0;
					enemy->vel.vy = -5 * 4096;
					enemy->pos.vy += enemy->vel.vy;
				}
			}
			break;

// jumping
		case 4:
			NME_FaceTo(enemy,&ballPos,80);

			enemy->ticker++;
			if(enemy->ticker > 20)
			{

				NME_GoTo2D(enemy,&ballPos,2 * 4096);

				if(enemy->pos.vy < enemy->init_pos.vy << 12)
				{
					enemy->vel.vy += 1024;
					enemy->pos.vy += enemy->vel.vy;
				}
				else
				{
					sfxPlayNME3D(levelFX, SFX_MIKE_ATTACK,enemy);

					enemy->pos.vy = enemy->init_pos.vy << 12;
					Mike_TapsFoot(enemy);
				}
			}
			break;

		default:
			PlaceNMEOnGround(enemy,1);
			AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);
			enemy->init_pos.vy = enemy->pos.vy >> 12;
			enemy->doing = 0;
			break;
	}
//	printf("mike doing %d\n",enemy->doing);
}


/*
 ENEMY CRUMPET -200 120 -200 0 0.000000	
 
 
   NORMAL_INSTRUCTION	NME_FACETO		-1	-200 120 -200			0		16384	ALWAYS			0 	0	(no anim)
  NORMAL_INSTRUCTION	NME_MOVETO		-1	-200 120 -200			15		2	ALWAYS			0 	0		(move vel)
  NORMAL_INSTRUCTION	NME_WAIT		-1	0			0		16384	ALWAYS			0 	0				(no anim)

  NORMAL_INSTRUCTION	NME_FACETO		-1	-200 120 0			0		16384	ALWAYS			0 	0
  NORMAL_INSTRUCTION	NME_MOVETO		-1	-200 120 0			15		2	ALWAYS			0 	0
  NORMAL_INSTRUCTION	NME_WAIT		-1	0			0		16384	ALWAYS			0 	0

  NORMAL_INSTRUCTION	NME_FACETO		-1	0 120 0			0		16384	ALWAYS			0 	0
  NORMAL_INSTRUCTION	NME_MOVETO		-1	0 120 0			15		2	ALWAYS			0 	0
  NORMAL_INSTRUCTION	NME_WAIT		-1	0			0		16384	ALWAYS			0 	0

  NORMAL_INSTRUCTION	NME_FACETO		-1	0 120 -200			0		16384	ALWAYS			0 	0
  NORMAL_INSTRUCTION	NME_MOVETO		-1	0 120 -200			15		2	ALWAYS			0 	0
  NORMAL_INSTRUCTION	NME_WAIT		-1	0			0		16384	ALWAYS			0 	0

 
   NORMAL_INSTRUCTION	NME_BRANCH		-1	0			0		0	ALWAYS			0 	0
 											

  CONDITIONAL_INSTRUCTION	NME_ATTACK		-1	0			0		0	IF_CLOSEST_IN_RANGE 	0 	90
 											
   ATTACK_INSTRUCTION	NME_SPECIAL		-1	-1			12		0	ALWAYS			0 	0	(12 = start attack)
   ATTACK_INSTRUCTION	NME_ENDATTACK		-1	100			0		0	ALWAYS			0 	0
 
 
 END_ENEMY
*/


#define CRUMPET_FIRE_TIME 22	// 30
#define CRUMPET_FIRE_TIME_TOTAL 35	// 30
#define CRUMPET_TWEEN_FIRE_TIME 180	// 180

void Crumpet_FireCheck(ENEMYPOS *enemy)
{
	int m1,m2;
	int *xp;

	xp = (int *)(&enemy->extras);

	enemy->xa = AngleHomer(enemy->xa,0,30);	// return to steady x angle after aiming
	if( (*xp) != 0)
	{
		(*xp)--;
	}

	m1 = NMECheckInRange(enemy, &glovePos,4096 * 200, NO);
	m2 = NMECheckInRange(enemy, &ballPos,4096 * 200, NO);
// attack without messing up "doing"...
	if((m1 || m2) && enemy->doing < 1000 && (int)(enemy->extras) == 0)
	{
		if(m1 < m2 && m1 != 0)
		{
			enemy->doing += 2000;	// attack glove
			enemy->vel = glovePos;
		}
		else
		{
			enemy->doing += 1000;	// attack ball
			enemy->vel = ballPos;
		}

		enemy->ticker = CRUMPET_FIRE_TIME_TOTAL;		// time spent firing stuff
		AddToQueue(&enemy->anim,NMEANIM_STARTATTACK,NO,NO,4096);		// (loop, queue)
		AddToQueue(&enemy->anim,NMEANIM_ENDATTACK,YES,YES,4096);		// (loop, queue)

		(*xp) = CRUMPET_TWEEN_FIRE_TIME;	// minimum time between attacks
		sfxPlayNME3D(levelFX,SFX_CRUMPET_CALL,enemy);


	}
}


// Uses "velocity" to mean "aim"

void Update_Crumpet(ENEMYPOS *enemy)
{
	if(!(activeframe & 3))
	{
		sfxPlayNME3D(levelFX, SFX_CRUMPET_FLY,enemy);
	}

	switch(enemy->doing)
	{
		case 100:
		case 101:
		case 102:
		case 103:
			enemy->ticker++;
			if(RANDOM256() > 200)
				sfxPlayNME3D(levelFX,SFX_CRUMPET_CALL,enemy);

			if(NME_FaceTo(enemy,&enemy->pathvectors[enemy->doing - 100],80))
			{
				enemy->ticker = 0;
				enemy->doing += 4;
				AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);
			}
			Crumpet_FireCheck(enemy);
			break;

		case 104:
		case 105:
		case 106:
		case 107:
			enemy->ticker++;
			if(NME_GoTo3D(enemy,&enemy->pathvectors[enemy->doing - 104],2 * 4096))
			{
				enemy->ticker = 0;
				enemy->doing+=4;
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
			}
			Crumpet_FireCheck(enemy);
			break;

		case 108:
		case 109:
		case 110:
		case 111:
			enemy->ticker++;
			if(enemy->ticker >= 50)
			{
				enemy->ticker = 0;
				enemy->doing++;
				if(enemy->doing - 108 >= enemy->n_points)
					enemy->doing = 100;
				else
					enemy->doing -= 8;
			}
			Crumpet_FireCheck(enemy);
			break;

		case -1:
			AddToQueue(&enemy->anim,NMEANIM_WALK,YES,NO,4096);	// anim,numberloop,queue,speed
			enemy->doing = 100;
			break;

// 64 added to the path-followers
		default:
		{
			VECTOR *temp;
			int xa,ya;

//			if(enemy->doing >= 2000)
//				temp = &ballPos;
//			else
//				temp = &glovePos;
			temp = &enemy->vel;

			enemy->ticker--;
			if(!enemy->ticker)
			{
				enemy->ticker = 0;
				enemy->doing -= 1000;
				if(enemy->doing >= 1000)
					enemy->doing -= 1000;
				AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);
			}

			GetPointingAngles(&enemy->pos, temp,&xa,&ya);
//			ya += 2048;	// aim rear-end...
//			xa = -xa;
			enemy->ya = AngleHomer(enemy->ya,ya,40);
			enemy->xa = AngleHomer(enemy->xa,xa,40);

// spout fire bullets at the good guys. Bwahahahaha

			if(enemy->ticker < CRUMPET_FIRE_TIME)
			{
				if(!(enemy->ticker & 3))
				{
					VECTOR vec;

					vec.vx = temp->vx - enemy->pos.vx;
					vec.vy = temp->vy - enemy->pos.vy;
					vec.vz = temp->vz - enemy->pos.vz;
					MakeUnit(&vec);
					vec.vx *= 3;
					vec.vy *= 3;
					vec.vz *= 3;

					if(!(enemy->ticker & 7))
					{
						sfxPlayFull3D(globalFX,SFX_GE_CRUMPET_ATTACK,&enemy->pos);
					}

					enemy_ShootFlames(&enemy->pos, &vec,0);
				}
			}

			break;
		}
	}
//	printf("crum doing %d (%d)\n",enemy->doing,enemy->ticker);
}


ENEMYPOS * enemy_FindFreeEnemy()
{
	ENEMYPOS * nme;
	VECTOR *temp;

// See if there's a created one that's been freed

// (tbd - keep an active / inactive / free list ?)

	for (nme = nme_list; nme; nme = nme->next)
	{
		if(nme->type == 0)
		{
			temp = nme->pathvectors;
			FMEMZERO(ADD2POINTER(nme,4),sizeof(ENEMYPOS)-4);	// NB - assumes "next" pointer is the 1st bit
			nme->pathvectors = temp;
			return(nme);
		}
	}

	return NULL;

// we're tight on memory. We can no longer afford to do any dynamic mallocs ingame
// temp enemies are now reserved at "enemiesendfile
/*
	if(temp_enemy_counter >= TEMP_ENEMY_MAX)
	{
//		DB("Out of temporary enemies\n");
		return NULL;
	}

	temp_enemy_counter++;
	return enemies_CreateEnemy(BLANK_ENEMY);	// be very careful how many of the buggers we create
*/
}

// eg - Crumpet the prehistoric birdy thing, or some classes of vent...
// (rats, we need to dynamically create quite a few enemies here...)

// 0 = crumpet long-range flames
// 1 = flame vent flames
void enemy_ShootFlames(VECTOR *org, VECTOR *vel, int type)
{
	ENEMYPOS *nme;
	nme = enemy_FindFreeEnemy();
	if(!nme)
	{
		return;
	}
	nme->pos = *org;
	nme->vel = *vel;
	nme->type = BULLET_ENEMY_FLAMES;
	nme->flags = NMEFLAG_ACTIVE + NMEFLAG_ENABLED;
	nme->ticker = 0;
	nme->doing = 0;
	nme->n_points = type;
}

void Update_Flame(ENEMYPOS *nme)
{
	int max;
	nme->ticker++;

	switch(nme->n_points)
	{
		case 0:
			max = 80;
			break;
		case 1:
		default:
			nme->vel.vy += gravity/2;
			max = 20;
			break;

	}

	nme->pos.vx += nme->vel.vx;
	nme->pos.vy += nme->vel.vy;
	nme->pos.vz += nme->vel.vz;

	if(nme->n_points == 0)
	{
		if(isPointSolid(nme->pos.vx,nme->pos.vy,nme->pos.vz) || nme->ticker >= max)
		{
			nme->flags &= ~(NMEFLAG_ACTIVE + NMEFLAG_ENABLED);
			nme->type = 0;
		}
	}
	else
	{
		if(nme->ticker >= max)
		{
			nme->flags &= ~(NMEFLAG_ACTIVE + NMEFLAG_ENABLED);
			nme->type = 0;
		}
	}


	nme->doing = nme->ticker * 8/max;
}

void enemy_ShootSteam(VECTOR *org, VECTOR *vel, int type)
{
	ENEMYPOS *nme;

	nme = enemy_FindFreeEnemy();
	if(!nme)
		return;
	nme->pos = *org;
	nme->vel = *vel;
	nme->type = BULLET_ENEMY_STEAM;
	nme->flags = NMEFLAG_ACTIVE + NMEFLAG_ENABLED;
	nme->ticker = 0;
	nme->doing = 0;
	nme->n_points = type;
}

void Update_Steam(ENEMYPOS *nme)
{
	int max;

	static char frames[30] =
	{

	0,0,0,0,0,
	0,0,1,1,1,
	1,1,1,2,2,
	2,2,2,2,2,
	3,3,3,3,3,
	3,4,4,5,6
	};

/*
	static char frames[15] =
	{
		0,0,0,0,0,
		1,1,1,2,2,
		3,3,4,5,6
	};
*/
	nme->ticker++;

	switch(nme->n_points)
	{
	case 0:	// steam
		max = 30;
		break;
	case 1:	// smoke
	default:
		max = 30;
		break;
	}

//	max = 15;
	nme->pos.vx += nme->vel.vx;
	nme->pos.vy += nme->vel.vy;
	nme->pos.vz += nme->vel.vz;

	if(nme->ticker >= max)
	{
		nme->flags &= ~(NMEFLAG_ACTIVE + NMEFLAG_ENABLED);
		nme->type = 0;
	}

/*
	if(isPointSolid(nme->pos.vx,nme->pos.vy,nme->pos.vz) || nme->ticker >= max)
	{
		nme->flags &= ~(NMEFLAG_ACTIVE + NMEFLAG_ENABLED);
		nme->type = 0;
	}
*/

//	nme->doing = nme->ticker * 7/max;
	nme->doing = frames[nme->ticker * 30/max];
}


void enemy_ShootUfoZap(VECTOR *org, VECTOR *vel, int type)
{
	ENEMYPOS *nme;
	nme = enemy_FindFreeEnemy();
	if(!nme)
		return;
	nme->pos = *org;
	nme->vel = *vel;
	nme->type = BULLET_ENEMY_UFOZAP;
	nme->flags = NMEFLAG_ACTIVE + NMEFLAG_ENABLED;
	nme->ticker = 0;

	if(type)
	{
		int colour;

		colour = random(6 * 256);
		colour = effectsGetPrimary(colour>>8,(colour &0xff),64);
		nme->extras = (void *)colour;
	}
	else
	{
//		nme->extras = (void *)0x808020;	// cyan bullets from yoofow

//		nme->extras = (void *)(effectsColourNear(0x808020, 0x20);
		nme->extras = (void *)(effectsColourNear(0x787820, 0x10));

	}
	nme->doing = 0;
}
void Update_UfoZap(ENEMYPOS *nme)
{
	nme->ticker++;

	nme->pos.vx += nme->vel.vx;
	nme->pos.vy += nme->vel.vy;
	nme->pos.vz += nme->vel.vz;

	if(isPointSolid(nme->pos.vx,nme->pos.vy,nme->pos.vz) || nme->ticker >= 80)
	{
		nme->flags &= ~(NMEFLAG_ACTIVE + NMEFLAG_ENABLED);
		nme->type = 0;
	}

	nme->doing = nme->ticker & 7;	// 01234 567
	if(nme->doing > 4)				// 01234 321
		nme->doing = 8-nme->doing;
}


/*
 ENEMY RAPTOR 400 120 -400 0 0.000000
 
 
   NORMAL_INSTRUCTION	NME_RANDOMMOVE	200	400 120 -400 400 400 400  40	1	ALWAYS			0 	0

   NORMAL_INSTRUCTION	NME_WAIT		-1	4			0		0	SOMETIMES 		500 	0
   NORMAL_INSTRUCTION	NME_BRANCH		-1	0			0		0	ALWAYS			0 	0 
 
   CONDITIONAL_INSTRUCTION	NME_ATTACK		-1 	0 			0		0	IF_BALL_IN_RANGE 	0 	200
 
   ATTACK_INSTRUCTION	NME_FACEPLAYER 	-1 	0			0		16512	ALWAYS 		0 	0 (&4080 = don't anim: ball)
   ATTACK_INSTRUCTION	NME_SPECIAL		1	-1			9		3	ALWAYS 		0 	0		(run)

   ATTACK_INSTRUCTION	NME_FOLLOWPLAYER  	30 	0 0 0			15	  	129	ALWAYS 		0 	0 (ball,accel)
   ATTACK_INSTRUCTION	NME_FOLLOWPLAYER 	100 	0 0 0		50	  	129	ALWAYS 		0 	0 (ball,accel)

   ATTACK_INSTRUCTION	NME_SPECIAL		1  	-1   			5		0	IF_BALL_IN_RANGE_2D 	50 	10000 (idle = roar)
   ATTACK_INSTRUCTION	NME_ENDATTACK		-1	50			0		16384	IF_BALL_IN_RANGE_2D 	50 	10000 (don't anim)
   ATTACK_INSTRUCTION	NME_JUMP		1	0 0 5			16384		16385	ALWAYS 		0 	0 (don't anim, vel)

   ATTACK_INSTRUCTION	NME_SPECIAL		-1  	-1 	   12		0	ALWAYS 		0 	0 (start attack)
   ATTACK_INSTRUCTION	NME_SPECIAL		-1  	-1		3		0	ALWAYS 		0 	0 (end attack)
   ATTACK_INSTRUCTION	NME_SPECIAL		-1  	-1   	5		0	ALWAYS 		0 	0 (idle = roar)
   ATTACK_INSTRUCTION	NME_ENDATTACK	 	0	50		0		16384	ALWAYS 		0 	0	(don't anim)
 
 
 END_ENEMY
*/

// reet...
// wander round
// chase ball,
//    belly flop on ball. roar
//    ball outofrange, roar

#define RAP_PLOD_RSPEED 50
#define RAP_PLOD_SPEED (4096)

#define RAP_CHASE_RSPEED 80
#define RAP_CHASE_SPEED (4 * 4096)
#define RAP_JUMP_SPEED (3 * 4096)

#define RAP_CHASE_RANGE  (200 * 4096)
#define RAP_GIVEUP_RANGE (300 * 4096)
#define RAP_MAX_JUMP_RANGE   (40 * 4096)
#define RAP_MIN_JUMP_RANGE   (30 * 4096)
#define RAP_JUMP_VEL (-6 * 4096)

void Raptor_StartRoar(ENEMYPOS *enemy)
{
	AddToQueue(&enemy->anim,NMEANIM_IDLE,NO,YES,4096);
	AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);

//	weatherStartEarthquake(int duration, int maxStr, int minStr, int cycles, int flags)

	enemy->doing = 2;
}

void Update_Raptor(ENEMYPOS *enemy)
{

	int f;

	f = enemy->anim.animInfo->frame;
	f+=enemy->anim.animInfo->segInfo[enemy->anim.currentAnimation].segStart;

	switch(f)
	{
	case 1:
	case 28:
	case 36:
		sfxPlayNME3D(levelFX, SFX_TREX_FOOTSTEP_LEFT,enemy);
		break;

	case 14:
	case 32:
	case 40:
		sfxPlayNME3D(levelFX, SFX_TREX_FOOTSTEP_RIGHT,enemy);
		break;
	case 84:
		sfxPlayNME3D(levelFX, SFX_TREX_CALL,enemy);
		weatherStartEarthquake(30, 40, 30, 1, 0);
		break;
	case 56:
		sfxPlayNME3D(levelFX, SFX_TREX_ATTACK,enemy);
		break;
	}

	switch(enemy->doing)
	{
		case 0:
			enemy->ticker = 0;		
			enemy->doing = 1;
			NMEChooseRandomPoint(enemy,YES);
			AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);

// ...
		case 1:
			NME_FaceTo(enemy, &enemy->pathvectors[2], RAP_PLOD_RSPEED);
			if(NME_GoTo2D(enemy, &enemy->pathvectors[2], RAP_PLOD_SPEED))
			{
				if(!random(4))
				{
					Raptor_StartRoar(enemy);
				}
				else
				{
					enemy->doing = 0;
				}
			}
//			PlaceNMEOnGround(enemy,1);

			if(NMECheckInRange(enemy,&ballPos, RAP_CHASE_RANGE, TRUE))
			{
				AddToQueue(&enemy->anim,NMEANIM_RUN,YES,YES,4096);
				enemy->doing = 3;
			}
			break;

// pause & roar
		case 2:
			if(enemy->animinfo.num == NMEANIM_WALK)
			{
				enemy->doing = 0;
			}
			break;

// chase ball
		case 3:

			if(!(activeframe & 3))
//				sfxPlayNME3D(levelFX, SFX_TREX_PANTING_LOOP,enemy);
				sfxPlayNME3D(globalFX, SFX_GE_FUMBLE_PANTING_LOOP,enemy);



			NME_FaceTo(enemy, &ballPos, RAP_CHASE_RSPEED);
			NME_GoForwards(enemy, RAP_CHASE_SPEED);
			if(!NMECheckInRange(enemy,&ballPos, RAP_GIVEUP_RANGE, TRUE))
			{
				Raptor_StartRoar(enemy);
			}
			else if(NMECheckInRange(enemy,&ballPos, RAP_MAX_JUMP_RANGE, TRUE)
				&& !NMECheckInRange(enemy,&ballPos, RAP_MIN_JUMP_RANGE, TRUE))
			{
//				AddToQueue(&enemy->anim,NMEANIM_JUMP,NO,YES,4096);		// jump (don't loop = loop,queue,speed)
				AddToQueue(&enemy->anim,NMEANIM_STARTATTACK,NO,YES,4096);		// jump (don't loop = loop,queue,speed)
				enemy->doing = 4;
				enemy->vel.vy = RAP_JUMP_VEL;
				enemy->pos.vy += enemy->vel.vy;
			}
			break;

// jumping
		case 4:
			enemy->vel.vy += gravity;
			enemy->pos.vy += enemy->vel.vy;
			NME_GoForwards(enemy, RAP_JUMP_SPEED);

			if(enemy->pos.vy >= enemy->init_pos.vy << 12)
			{
				VECTOR temp;
				int hag;
				hag = getHeightAt(enemy->pos.vx, enemy->pos.vy, enemy->pos.vz);
				temp = enemy->pos;
				temp.vy += hag;

				AddToQueue(&enemy->anim,NMEANIM_ENDATTACK,NO,YES,4096);		// jump (don't loop = loop,queue,speed)
				Raptor_StartRoar(enemy);
			}
			break;


		default:
			PlaceNMEOnGround(enemy,1);
			enemy->init_pos.vy = enemy->pos.vy>>12;
			AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);
			enemy->doing = 0;
			break;
	}
	NME_KeepInBox(enemy);
	KeepNMEOnGround(enemy,4096 * 10, 4096 * 10);

//	printf("raptor doing %d\n",enemy->doing);
}

/*
 ENEMY TRACEY -400 120 -400 0 0.000000
 
NORMAL_INSTRUCTION		NME_WAIT		   0	0	     1 1048576	ALWAYS 0 0 (NMEFLAGS_FACEHAND)

CONDITIONAL_INSTRUCTION	NME_ATTACK		  -1	0	     0       0  IF_HAND_IN_RANGE	0	150

ATTACK_INSTRUCTION		NME_SPECIAL		  -1    0        0 1048576	ALWAYS 0 0 (face hand)
ATTACK_INSTRUCTION		NME_SPECIAL		   1    -1       9 1048577	ALWAYS 0 0 (f hand, moveaccel?)
ATTACK_INSTRUCTION		NME_FOLLOWPLAYER 100	0 0 0   75      65  ALWAYS 0 0 (specify hand, moveaccel)

ATTACK_INSTRUCTION		NME_ENDATTACK      1    100      0   16384	IF_HAND_IN_RANGE_2D 75 10000	(don't anim)
ATTACK_INSTRUCTION		NME_SPECIAL		  -1    1        0 1048578	ALWAYS 0 0	(face hand, move vel)
ATTACK_INSTRUCTION		NME_JUMP		  -1    0 10 12  0 8388672	ALWAYS 0 0 (...608 = landonplayer, specify hand)
ATTACK_INSTRUCTION		NME_WAIT		  30	0		 0	 16384  ALWAYS 0 0	(don't anim)
ATTACK_INSTRUCTION		NME_ENDATTACK	  -1    100      0       0  ALWAYS 0 0
 
 
 END_ENEMY


*/

// okay, let's tone tracy down a bit
//#define TRACY_CHASE_TURNRATE 60
//#define TRACY_CHASE_RANGE    (250 * 4096)
//#define TRACY_GIVEUP_RANGE   (450 * 4096)
//#define TRACY_RUN_SPEED      (4 * 4096)
//#define TRACY_WAIT_TIME		60

#define TRACY_DANCE_TURNRATE 40
#define TRACY_CHASE_TURNRATE 50
#define TRACY_CHASE_RANGE    (170 * 4096)
#define TRACY_GIVEUP_RANGE   (280 * 4096)
#define TRACY_JUMP_RANGE     (80 * 4096)
#define TRACY_RUN_SPEED      (14000)
#define TRACY_JUMP_VEL       (-7 * 4096)
#define TRACY_WAIT_TIME		100

void Update_Tracey(ENEMYPOS *enemy)
{
	VECTOR oldpos = enemy->pos;
	int f;

	f = enemy->anim.animInfo->frame;
	f+=enemy->anim.animInfo->segInfo[enemy->anim.currentAnimation].segStart;


	enemy->vel.vy += gravity;
	enemy->pos.vy += enemy->vel.vy;

	switch(enemy->doing)
	{
		case 0:
			enemy->ticker++;

			if(!(enemy->ticker & 3))
				sfxPlayNME3D(levelFX,SFX_TRACY_DANCING,enemy);

			if(f == 14 || f == 47)
				sfxPlayNME3D(levelFX,SFX_TRACY_WALK_LEFT,enemy);
			if(f == 31 || f == 63)
				sfxPlayNME3D(levelFX,SFX_TRACY_WALK_LEFT,enemy);

			NME_FaceTo(enemy,&glovePos,TRACY_DANCE_TURNRATE);

			if(NMECheckInRange(enemy,&glovePos, TRACY_CHASE_RANGE, FALSE))
			{
				sfxPlayNME3D(levelFX,SFX_TRACY_CALL,enemy);

				AddToQueue(&enemy->anim,NMEANIM_SPECIAL1,NO,NO,4096);	// pick up handbag
				AddToQueue(&enemy->anim,NMEANIM_RUN,YES,YES,4096);		// run, natch
				enemy->doing = 1;
			}
			break;

// running...
		case 1:
			NME_FaceTo(enemy,&glovePos,TRACY_CHASE_TURNRATE);

			enemy->ticker++;

			if(enemy->animinfo.num != NMEANIM_RUN)	// don't go till we've done the intro anim
			{
				VECTOR temp;

				if(f == 98 || f == 112)
					sfxPlayNME3D(levelFX,SFX_TRACY_WALK_LEFT,enemy);
				if(f == 104)
					sfxPlayNME3D(levelFX,SFX_TRACY_WALK_RIGHT,enemy);

				temp = enemy->pos;
				temp.vx += (random(32)-16)<<12;
				temp.vy -= ((32+ random(16))<<12) ;
				temp.vz += (random(32)-16)<<12;

				New_Debris(DEBRIS_TRACY_HEART,&temp,random(4096));

			}
			else
			{
				NME_GoForwards(enemy, TRACY_RUN_SPEED);
				if(!(enemy->ticker & 7))
					sfxPlayNME3D(levelFX,SFX_TRACY_CHARGE,enemy);


				if(!NMECheckInRange(enemy,&glovePos, TRACY_GIVEUP_RANGE, FALSE))
				{
	// we could play the "pick up bag" anim backwards at this point, or alternatively, we could just jump anyway
					AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,NO,4096);
					enemy->doing = 0;
				}
				else if(NMECheckInRange(enemy,&glovePos, TRACY_JUMP_RANGE, FALSE))
				{
					AddToQueue(&enemy->anim,NMEANIM_SPECIAL2,NO,NO,4096);	// start jump
					AddToQueue(&enemy->anim,NMEANIM_JUMP,NO,YES,4096);		// jump (don't loop = loop,queue,speed)
					enemy->doing = 2;
					enemy->vel.vy = TRACY_JUMP_VEL;
					enemy->pos.vy += enemy->vel.vy;

					sfxPlayNME3D(levelFX,SFX_TRACY_JUMP,enemy);
				}
			}
			break;

// jumping
		case 2:
		{
			int hag;

//			enemy->vel.vy += gravity;
//			enemy->pos.vy += enemy->vel.vy;
			NME_GoForwards(enemy, TRACY_RUN_SPEED);

			hag = getHeightAt(enemy->pos.vx, enemy->pos.vy + 30 * 4096, enemy->pos.vz);

//			if(enemy->pos.vy >= enemy->init_pos.vy << 12)
//			if(hag != -1 && hag < 0)
			if(hag < 0)
			{
				VECTOR temp;
				temp = enemy->pos;
				temp.vy += hag;
				temp.vy += 30 * 4096;

				sfxPlayNME3D(levelFX,SFX_TRACY_LAND,enemy);
				effectsStartNMEBlastRing(&temp,5*4096, 80 * 4096,8);

				AddToQueue(&enemy->anim,NMEANIM_LAND,NO,NO,4096);
//				enemy->pos.vy = enemy->init_pos.vy << 12;
				enemy->pos.vy += hag;

				enemy->doing = 3;
				enemy->ticker = TRACY_WAIT_TIME;
			}
			break;
		}

		case 3:
			enemy->ticker--;
			if(!enemy->ticker)
			{
				AddToQueue(&enemy->anim,NMEANIM_ENDATTACK,NO,NO,4096);
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
				enemy->doing = 0;
			}

			break;

		default:
			AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,NO,4096);	// anim,number,loop,queue,speed
//			PlaceNMEOnGround(enemy,0);
			enemy->init_pos.vy = enemy->pos.vy>>12;

			enemy->doing = 0;
			break;
	}


	{
		int hit;

		hit = enemy_UsePhysics(enemy, &EnemyHeavyPhysics, &oldpos, 30);

		switch (hit& 0x7fffffff)
		{
			case P_SPIKE:
			case P_LAVA:
				EnemyKill(enemy);
				enemy->doing = -1;
				break;
		}

		if(hit && enemy_Colldata.nHitPlats)
		{
			enemy->extras = (void *)enemy_Colldata.hitPlats[0].pPlatform;
		}
		else
		{
			enemy->extras = NULL;
		}
	}
}

void TracIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(nme->doing == 3)
	{
		if(GloveCtrl.action == HAND_SLAM|| GloveCtrl.action == HAND_SLAM2ST) // && Glover.animInfo->frame>=4)	// the frame check's a tad messy, but WTF?
		{
			EnemyKill(nme);
		}
		else
		{
			PushableRou(with,nme,dvec,sphererad);
		}
	}
	else
	{
		HardBastRou(with,nme,dvec,sphererad);
	}
}

/*
// Opec is 99% hand-coded, even on the N64. "All the bad guys are scripted". Are they bollocks.
 ENEMY OPEC -300 120 -300 0 0.000000
   NORMAL_INSTRUCTION	NME_WAIT		0	0			1		8 	ALWAYS			0	0

 	NORMAL_INSTRUCTION 	NME_MOVETO		-1	-300 120 -300		10		20486	ALWAYS			0	0
	NORMAL_INSTRUCTION 	NME_MOVETO		-1	300 120 -300		10		20486	ALWAYS			0	0

   NORMAL_INSTRUCTION 	NME_BRANCH		-1	1		 	0		0	ALWAYS			0	0
 END_ENEMY
*/

// introu - if we're hit by the ball, we get stunned & do anim "hurt"

// normal = pathfollow
// glove in range = hover & shoot, from a rather ill-defined position that I can't figure out, even with the N64 source

#define OPEC_CHASE_RANGE  (150 * 4096)
#define OPEC_GIVEUP_RANGE (350 * 4096)

#define OPEC_TARGET_RANGE  (45 * 4096)
#define OPEC_FIRE_RANGE  (52 * 4096)
#define OPEC_STOPFIRE_RANGE  (80 * 4096)

#define OPEC_RUN_SPEED (3 * 4096)

#define OPEC_WALK_TURNRATE 30
#define OPEC_CHASE_TURNRATE 80
#define OPEC_FIRE_TURNRATE 80
#define OPEC_WALKING  0
#define OPEC_WALKING2 1
#define OPEC_WALKING3 2
#define OPEC_WALKING4 3

#define OPEC_CHASING 10
#define OPEC_FIRING  11
#define OPEC_HURT    12

// opec is going to need real physics...
// (tbd - finish off this bastard)

// target position is our glove-relative angle, plus a bit
void Opec_GetTargPos(ENEMYPOS *enemy, VECTOR *targ)
{
	int ya;
	ya = (calc_angle( (enemy->pos.vx - glovePos.vx)>>8, (enemy->pos.vz - glovePos.vz)>>8) & 4095);
	ya = (ya + 0x6) & 4095;

	targ->vx = glovePos.vx + (OPEC_TARGET_RANGE >> 12) * rsin(ya);
	targ->vz = glovePos.vz + (OPEC_TARGET_RANGE >> 12) * rcos(ya);
	targ->vy = glovePos.vy - 25 * 4096 + 10 * rsin(activeframe << 5);
}

void Update_Opec(ENEMYPOS *enemy)
{
	VECTOR targ;

	if(!(activeframe & 7) && enemy->flags & NMEFLAG_VISIBLE)
	{
		sfxPlayNME3D(levelFX, SFX_OPEC_IDLE,enemy);
	}


	switch(enemy->doing)
	{

// tbd - falls to ground
		case OPEC_HURT:
			enemy->vel.vy += gravity;
			enemy->vel.vx = enemy->vel.vx * 250/256;
			enemy->vel.vz = enemy->vel.vz * 250/256;

			enemy->pos.vx += enemy->vel.vx;
			enemy->pos.vy += enemy->vel.vy;
			enemy->pos.vx += enemy->vel.vx;

			PlaceNMEOnGround(enemy,1);

			enemy->xa = AngleHomer(enemy->xa, 0, 8);
			enemy->ticker++;
			if(enemy->ticker >= 100)
			{
			AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
				enemy->doing = OPEC_WALKING;
			}
			break;

		case OPEC_CHASING:

//			NME_FaceTo(enemy,&glovePos,OPEC_CHASE_TURNRATE);	//actually, we want a kinda floaty "accel" feel for this, rather than a "vel" one
//			NME_GoForwards(enemy, OPEC_RUN_SPEED);

//			NME_AccelTo2D(ENEMYPOS *enemy, VECTOR *dest,const NMEAccelStr *accel)

			enemy->xa = AngleHomer(enemy->xa, 0, 8);

			Opec_GetTargPos(enemy,&targ);
			NME_HoverTo3D(enemy, &targ, 256, 10000);
			NME_FaceTo(enemy,&glovePos,OPEC_CHASE_TURNRATE);

			if(!NMECheckInRange(enemy,&glovePos, OPEC_GIVEUP_RANGE, FALSE))
				enemy->doing = 0;

			if(NMECheckInRange(enemy,&glovePos, OPEC_FIRE_RANGE, FALSE))
			{
				AddToQueue(&enemy->anim,NMEANIM_STARTATTACK,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_SPECIAL2,YES,YES,4096);
				enemy->doing = OPEC_FIRING;
			}
			break;

		case OPEC_FIRING:
		{
			int xa,ya;
			enemy->ticker++;
			Opec_GetTargPos(enemy,&targ);
			NME_HoverTo3D(enemy, &targ, 256, 10000);
//			NME_FaceTo(enemy,&glovePos,OPEC_FIRE_TURNRATE);
			GetPointingAngles(&enemy->pos, &glovePos,&xa,&ya);
			enemy->ya = AngleHomer(enemy->ya,ya,60);
			enemy->xa = AngleHomer(enemy->xa,xa,10);


			if(!(enemy->ticker & 1))
			{
				static VECTOR refpos1 = {-10,0,-2};
				static VECTOR refpos2 = { 10,0,-2};
				static VECTOR refpos3 = {  0,0,-4096 * 3};

				VECTOR pos;
				VECTOR vec;

				if(enemy->ticker & 2)
				{
					EnemyRotatePoint(enemy,&refpos1,&pos, -1);
				}
				else
				{
					EnemyRotatePoint(enemy,&refpos2,&pos, -1);
				}
				pos.vx = (pos.vx<<12) + enemy->pos.vx;
				pos.vy = (pos.vy<<12) + enemy->pos.vy;
				pos.vz = (pos.vz<<12) + enemy->pos.vz;

				EnemyRotatePoint(enemy,&refpos3,&vec, -1);
				enemy_ShootUfoZap(&pos, &vec, 1);

				if(!(enemy->ticker & 3))
				{
					sfxPlayNME3D(levelFX, SFX_OPEC_ATTACK_SINGLE,enemy);
				}


			}

			if(!NMECheckInRange(enemy,&glovePos, OPEC_STOPFIRE_RANGE, FALSE))
			{
				AddToQueue(&enemy->anim,NMEANIM_ENDATTACK,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_IDLE2,YES,YES,4096);
				enemy->doing = OPEC_WALKING;
			}
			break;
		}

// this should be a path following device, dude...
		case OPEC_WALKING:
		case OPEC_WALKING2:
		case OPEC_WALKING3:
		case OPEC_WALKING4:
			targ = enemy->pathvectors[enemy->doing - OPEC_WALKING];
			if(NME_HoverTo3D(enemy, &targ, 256, 10000))
			{
				enemy->doing++;
				if(enemy->doing - OPEC_WALKING >= enemy->n_points)
					enemy->doing = OPEC_WALKING;
			}

			NME_HoverTo3D(enemy, &targ, 256, 10000);
			NME_FaceTo(enemy,&targ,OPEC_WALK_TURNRATE);

			enemy->xa = AngleHomer(enemy->xa, 0, 8);

			if(NMECheckInRange(enemy,&glovePos, OPEC_CHASE_RANGE, FALSE))	// this guy doesn't have a box
			{
				AddToQueue(&enemy->anim,NMEANIM_SPECIAL1,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);

				enemy->doing = OPEC_CHASING;
			}
			break;

		case -1:
			AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
			enemy->doing = OPEC_WALKING;
			break;
	}


	if(enemy->doing != OPEC_HURT && !(activeframe & 3))
	{
		VECTOR temp;
		temp = enemy->pos;
		temp.vy += 12 * 4096;
		temp.vx += (random(8192)-4096);
		temp.vz += (random(8192)-4096);
		New_Debris(DEBRIS_OPEC_HOVER,&temp,256);
	}

//	printf("opec doing %d\n",enemy->doing);

//	nmeWalkRoundPath(enemy,2*4096);	// actually, he doesn't turn-to-face, & waits at point 0 but what the hell...
}


void OpecIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
		if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
			if(!IsBallShield(dvec))
				DisconnectGloveFromBall(DISCONNECT_WHEN_JOINED | DISCONNECT_WHEN_BOUNCING | DISCONNECT_WHEN_WALKINGON);
			NMEShovesBall(nme,dvec,sphererad,12);
		}
		else
		{
			if(!IsBallShield(dvec))
				NMEHurtsGlove(nme,dvec,sphererad);
		}
	}
	else
	{
		if(BallCtrl.powerwhack)
		{
			PowerWhackHandler(nme,1);
			return;
		}

		if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
//			DisconnectGloveFromBall(DISCONNECT_WHEN_JOINED | DISCONNECT_WHEN_BOUNCING | DISCONNECT_WHEN_WALKINGON);
			NMEShovesBall(nme,dvec,sphererad,12);
		}
		else
		{
			NMEShovesBall(nme,dvec,sphererad,12);
			nme->doing = OPEC_HURT;
			AddToQueue(&nme->anim,NMEANIM_HURT,YES,YES,4096);
			nme->ticker = 0;
		}
	}
}



/*
 ENEMY CYMON -400 120 400 0 0.000000
 
 
  NORMAL_INSTRUCTION	NME_FACETO		-1	-400 120 400		0		16384	ALWAYS			0	0
 NORMAL_INSTRUCTION 	NME_WAIT		10	0			1		0	ALWAYS			0	0
 NORMAL_INSTRUCTION 	NME_MOVETO2D		-1	-400 120 400		10		1	ALWAYS			0	0
 NORMAL_INSTRUCTION 	NME_WAIT		20	2			1		0	ALWAYS			0	0

 NORMAL_INSTRUCTION	NME_FACETO		-1	-400 120 0		0		16384	ALWAYS			0	0
 NORMAL_INSTRUCTION 	NME_WAIT		10	0			1		0	ALWAYS			0	0
 NORMAL_INSTRUCTION 	NME_MOVETO2D		-1	-400 120 0		10		1	ALWAYS			0	0
 NORMAL_INSTRUCTION 	NME_WAIT		20	2			1		0	ALWAYS			0	0

 NORMAL_INSTRUCTION	NME_FACETO		-1	-200 120 0		0		16384	ALWAYS			0	0
 NORMAL_INSTRUCTION 	NME_WAIT		10	0			1		0	ALWAYS			0	0
 NORMAL_INSTRUCTION 	NME_MOVETO2D		-1-200 120 0		10		1	ALWAYS			0	0
 NORMAL_INSTRUCTION 	NME_WAIT		20	2			1		0	ALWAYS			0	0

 NORMAL_INSTRUCTION	NME_FACETO		-1	-200 120 400		0		16384	ALWAYS			0	0
 NORMAL_INSTRUCTION 	NME_WAIT		10	0			1		0	ALWAYS			0	0
 NORMAL_INSTRUCTION 	NME_MOVETO2D		-1 -200 120 400		10		1	ALWAYS			0	0
 NORMAL_INSTRUCTION 	NME_WAIT		20	2			1		0	ALWAYS			0	0

  NORMAL_INSTRUCTION 	NME_BRANCH		-1	0			0		0	ALWAYS			0	0


 
  CONDITIONAL_INSTRUCTION	NME_ATTACK	-1	0			0		0	IF_HAND_IN_RANGE	0	90
 
  ATTACK_INSTRUCTION	NME_SPECIAL		-1	-1			12		1048576	ALWAYS			0	0	
 
  ATTACK_INSTRUCTION	NME_FOLLOWPLAYER 	150   	0 0 0			30		8257	ALWAYS			0	0	
  ATTACK_INSTRUCTION	NME_HOLDPLAYER 	-1	0 0 5			2		131136	IF_HAND_IN_RANGE_2D	0   	30
  ATTACK_INSTRUCTION	NME_SPECIAL		-1	1			0		0	IF_IM_NOT_HOLDING_HAND	0	0
  ATTACK_INSTRUCTION	NME_SPECIAL		-1	-1			0		0	IF_IM_HOLDING_HAND	0	0
 
 
  ATTACK_INSTRUCTION	NME_SPECIAL		 4	-1			1		0	IF_IM_HOLDING_HAND	0	0
 
  ATTACK_INSTRUCTION	NME_DROPPLAYER	-1	0 15.000000 20.000000			0		64	IF_IM_HOLDING_HAND	0	0

  ATTACK_INSTRUCTION	NME_ENDATTACK		0	50			0		16384	ALWAYS			0	0
 
 
 END_ENEMY
*/

// Adjusted to be closer to the new-look tracy
#define CYMON_CHASE_RANGE (180 * 4096)
#define CYMON_GIVEUP_RANGE (280 * 4096)

#define CYMON_TOOCLOSE_RANGE (80 * 4096)
#define CYMON_GRAB_RANGE (30 * 4096)
#define CYMON_CHASING_GLOVE 256
#define CYMON_THROWING_GLOVE 257
#define CYMON_POST_THROW 258
#define CYMON_GIVING_UP 259

void cymonGrabsGlove()
{
	DisconnectGloveFromBall(DISCONNECT_WHEN_WHATEVER);
	GloveCtrl.disableTimer=100;
}
void cymonThrowsGlove(ENEMYPOS *enemy)
{
//	gloveVel.vy = 4096 * 8;
	gloveVel.vx = -rsin(enemy->ya) * 10;
	gloveVel.vz = -rcos(enemy->ya) * 10;
	gloveVel.vy = 4096 * 8;

	glovePos.vy -= 4096 * 8;

	GloveCtrl.disableTimer=10;

	GloveCtrl.action=HAND_FALLING;
	GloveCtrl.onGround=FALSE;	// coz otherwise he won't even start to go upwards
	GloveCtrl.deathType=HURTFALL;

	GloveCtrl.hurtFlag=0;
	AddToQueue(&Glover,HANDANIM_FALL,YES,NO,4096);
	GloveCtrl.ballCollision=FALSE;
}

void Update_Cymon(ENEMYPOS *enemy)
{
	VECTOR oldpos;

	oldpos = enemy->pos;

	if(!(activeframe & 7) && enemy->flags & NMEFLAG_VISIBLE  && !enemy->flags & NMEFLAG_STUNNED)
	{
		if(!(activeframe & 4))
			sfxPlayNME3D(levelFX, SFX_CYMON_WALK_LEFT,enemy);
		else
			sfxPlayNME3D(levelFX, SFX_CYMON_WALK_RIGHT,enemy);
	}


	switch(enemy->doing)
	{
		case CYMON_CHASING_GLOVE:
			NME_FaceTo( enemy, &glovePos,80 );
			if(enemy->animinfo.num == NMEANIM_RUN)
			{
				NME_GoForwards(enemy,4096 * 3);
			}

			if(!NMECheckInRange(enemy, &glovePos, CYMON_GIVEUP_RANGE,FALSE))
			{
				AddToQueue(&enemy->anim,NMEANIM_SPECIAL2,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_WALK,NO,YES,4096);
				enemy->doing = CYMON_GIVING_UP;
				enemy->ticker = 0;
			}

			if(NMECheckInRange(enemy, &glovePos, CYMON_GRAB_RANGE,FALSE))
			{
				enemy->doing = CYMON_THROWING_GLOVE;
				enemy->ticker = 0;
				AddToQueue(&enemy->anim,NMEANIM_ACTION1,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_ACTION2,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_WALK,NO,YES,4096);
			}
			break;

		case CYMON_THROWING_GLOVE:
			enemy->ticker++;
			{
				static VECTOR armrel_pos = {0,0,0};
				VECTOR out;
//				EnemyRotatePoint(enemy,&armrel_pos, &out, 8);
				EnemyRotatePoint(enemy,&armrel_pos, &out, 2);

				GloveCtrl.direction = enemy->ya;
				pGlovePSA->world.rotate.vx = 0x300;

				out.vx = (out.vx<<12)+enemy->pos.vx;
				out.vy = (out.vy<<12)+enemy->pos.vy;
				out.vz = (out.vz<<12)+enemy->pos.vz;
				glovePos = gloveColl.oldPos = out;
				gloveVel.vx = 0;
				gloveVel.vy = 0;
				gloveVel.vz = 0;


			}

//			glovePos = gloveColl.oldPos = enemy->pos;	// time for an "enemyrotatepoint" ?
			GloveCtrl.disableTimer=10;

// move glove such that he's being held...
// Time to get enemyrotatepoint to work properly, dude...

			if(enemy->ticker >= 10)	// the throwing action's pretty quick...
//			if(enemy->ticker >= 40)
			{
				cymonThrowsGlove(enemy);
				enemy->doing = CYMON_POST_THROW;
				enemy->ticker=0;
			}
			break;

		case CYMON_POST_THROW:
		case CYMON_GIVING_UP:
			enemy->ticker++;
			if(enemy->ticker >= 100)
			{
				nmeReturnToPath(enemy);
			}
			break;


		case -1:
			PlaceNMEOnGround(enemy,0);
			((int)(enemy->extras)) = 0;

		default:
			nmeWalkRoundPath(enemy,3*4096);

			if(NMECheckInRange(enemy, &glovePos, CYMON_CHASE_RANGE,FALSE)
				&& !NMECheckInRange(enemy, &glovePos, CYMON_TOOCLOSE_RANGE,FALSE))
			{
				AddToQueue(&enemy->anim,NMEANIM_STARTATTACK,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_RUN,YES,YES,4096);
				sfxPlayNME3D(levelFX, SFX_CYMON_GENERAL,enemy);

				enemy->doing = CYMON_CHASING_GLOVE;
			}
			break;
	}

	enemy->vel.vx = enemy->pos.vx - oldpos.vx;
	enemy->vel.vz = enemy->pos.vz - oldpos.vz;
// y vel still changes according to velocity
	enemy->pos = oldpos;
	KeepNMEOnGround(enemy,4096 * 10, 4096 * 100);

// rats, cymon wobbles a bit too much to do it this way
//	if(KeepNMEOnGround(enemy,4096 * 10, 4096 * 100))


// So keep on doing it this way
// If we've been stuck in the same spot, and we're not in the "turning" phase of pathfollowing, then jump
	if(enemy->pos.vx == oldpos.vx && enemy->pos.vy == oldpos.vy && enemy->pos.vz == oldpos.vz &&
		(enemy->doing < 100 || enemy->doing >= 100 + NME_MAX_PATHVECTORS))
	{
//		if(Kong_HitWall)
		{
//			DB("cymon hit wall\n");
			((int)(enemy->extras))++;
			if(((int)(enemy->extras)) > 16)
			{
//				DB("cymon jumps!\n");
				enemy->vel.vy = -4 * 4096;
				((int)(enemy->extras)) = RANDOM256() & 7;
	//			DB("Cymon locked\n");
			}
		}
	}
	else
	{
//		DB("cymon off ground\n");
		((int)(enemy->extras)) = 0;
	}

//	printf("cymon doing %d\n",enemy->doing);
}

void CymonIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
// glove interaction is handled internally
		if(nme->flags & NMEFLAG_STUNNED)
		{
			NMEPushesGlove(nme,dvec,sphererad);
		}
		return;	//NMEPushesGlove(nme,dvec,sphererad);
	}
	else
	{
		if(BallCtrl.powerwhack)
		{
			PowerWhackHandler(nme,1);
			return;
		}
		NMEPushesBall(nme,dvec,sphererad);
	}
}

/*
 ENEMY YOOFOW -200 120 -200 0 0.000000

 NORMAL_INSTRUCTION	NME_FACETO		0	-200 120 -200		0		16384	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_SPECIAL		5	-1			13		0	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_SPECIAL		5	-1			15		3	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_MOVETO		0	-200 120 -200		40		6	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_WAIT		-1	2			1		1	ALWAYS 		0 	0

 NORMAL_INSTRUCTION	NME_FACETO		0	-200 120 200		0		16384	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_SPECIAL		5	-1			13		0	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_SPECIAL		5	-1			15		3	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_MOVETO		0	-200 120 200		40		6	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_WAIT		-1	2			1		1	ALWAYS 		0 	0
 NORMAL_INSTRUCTION	NME_BRANCH		0	0			0		0	ALWAYS 		0 	0
 

 CONDITIONAL_INSTRUCTION	NME_ATTACK		0	0			0		0	IF_CLOSEST_IN_RANGE_2D	60	150
 
 ATTACK_INSTRUCTION	NME_SPECIAL		-1	-1			14		4	ALWAYS			0 	0	(stopmove, don't slow when near)
 ATTACK_INSTRUCTION	NME_FACEPLAYER		-1 	 0			0		16640	ALWAYS			0 	0 (&4038 = no anim, uze x/y/z axes
 ATTACK_INSTRUCTION	NME_SPECIAL		-1	-1			0		4194304	ALWAYS			0 	0	(faceclosest)

 ATTACK_INSTRUCTION	NME_SPECIAL		5	-1			13		0	ALWAYS 		0 	0	(startmove)
 ATTACK_INSTRUCTION	NME_SPECIAL		5	-1			15		3	ALWAYS 		0 	0	(walk)
 ATTACK_INSTRUCTION	NME_ENDATTACK		0	50			0		16384	ALWAYS			0 	0
 
 END_ENEMY

*/
// ("action1" is the firing anim, btw)
#define YOOFOW_GIVEUP_RANGE (200 * 4096)
#define YOOFOW_ATTACK_RANGE (170 * 4096)
#define YOOFOW_STOPPING 256
#define YOOFOW_FIRING 257

void Update_YooFow(ENEMYPOS *enemy)
{
	int m1,m2;
	VECTOR d1,d2;
	VECTOR *targ;
	int xa,ya;


	if(!(activeframe & 3) && enemy->flags & NMEFLAG_VISIBLE)
	{
		sfxPlayNME3D(levelFX, SFX_YOOFOO_FLY,enemy);
	}

	d1.vx = enemy->pos.vx - glovePos.vx;
	d1.vy = enemy->pos.vy - glovePos.vy;
	d1.vz = enemy->pos.vz - glovePos.vz;
	m1 = Magnitude(&d1);
	d2.vx = enemy->pos.vx - ballPos.vx;
	d2.vy = enemy->pos.vy - ballPos.vy;
	d2.vz = enemy->pos.vz - ballPos.vz;
	m2 = Magnitude(&d2);
	if(m1 < m2)
	{
		targ = &glovePos;
	}
	else
	{
		targ = &ballPos;
		m1 = m2;
	}


	switch(enemy->doing)
	{
		case YOOFOW_STOPPING:
			GetPointingAngles(&enemy->pos, targ,&xa,&ya);
			xa = (xa - 0x100) & 4095;	// because the firing anim's offset - 4096 = &1000
			enemy->ya = AngleHomer(enemy->ya,ya,30);
			enemy->xa = AngleHomer(enemy->xa,xa,30);

			if(enemy->xa == xa && enemy->ya == ya)
			{
				AddToQueue(&enemy->anim,NMEANIM_ACTION1,YES,NO,4096);	// don't bother waiting for the end of the stop/idle
				enemy->doing = YOOFOW_FIRING;
				enemy->ticker = 0;
			}
			break;

		case YOOFOW_FIRING:
			GetPointingAngles(&enemy->pos, targ,&xa,&ya);
			xa = (xa - 0x100) & 4095;	// because the firing anim's offset
			enemy->ya = AngleHomer(enemy->ya,ya,30);
			enemy->xa = AngleHomer(enemy->xa,xa,30);
			enemy->ticker++;


			if(enemy->anim.currentAnimation == NMEANIM_ACTION1
				&& enemy->anim.animInfo->frame > 20
				&& enemy->anim.animInfo->frame < 34
				&& !(enemy->ticker & 3))
			{
				static VECTOR refpos = {0,-10,-20};
				VECTOR pos;
				VECTOR vec;

				EnemyRotatePoint(enemy,&refpos,&pos, -1);
				pos.vx = (pos.vx<<12) + enemy->pos.vx;
				pos.vy = (pos.vy<<12) + enemy->pos.vy;
				pos.vz = (pos.vz<<12) + enemy->pos.vz;

				vec.vx = targ->vx - pos.vx;
				vec.vy = targ->vy - pos.vy;
				vec.vz = targ->vz - pos.vz;
				MakeUnit(&vec);
				vec.vx *= 3;
				vec.vy *= 3;
				vec.vz *= 3;
//				sfxPlayNME3D(levelFX, SFX_YOOFOO_ATTACK,enemy);
				sfxPlayNME3D(levelFX, SFX_OPEC_ATTACK_SINGLE,enemy);
				enemy_ShootUfoZap(&pos, &vec, 0);
			}


			if(m1 > YOOFOW_GIVEUP_RANGE)
			{
				enemy->doing = -1;
				AddToQueue(&enemy->anim,NMEANIM_STARTMOVE,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);
			}

			break;

		default:
//			enemy->ya = AngleHomer(enemy->ya,0,30);
			enemy->xa = AngleHomer(enemy->xa,0,30);

			nmeWalkRoundPath(enemy,4*4096);
			if(m1 < YOOFOW_ATTACK_RANGE)
			{
				enemy->doing = YOOFOW_STOPPING;
				AddToQueue(&enemy->anim,NMEANIM_STOPMOVE,NO,YES,4096);
				AddToQueue(&enemy->anim,NMEANIM_IDLE,YES,YES,4096);
			}
			break;
	}
/*
	printf("yoofow doing %d: Queued %d, anim %d, frame %d\n",
		enemy->doing,
		enemy->anim.numAnimations,
		enemy->anim.currentAnimation,
		enemy->anim.animInfo->frame
		);
*/
}
/*
 ENEMY SUCKER 300 120 -300 0 0.000000
 
 
  NORMAL_INSTRUCTION	NME_RANDOMMOVE	-1	 300 120 -300 255.490784 62.255806 165.570908 10	1	ALWAYS			0 	0

   CONDITIONAL_INSTRUCTION NME_ATTACK		-1	 0			0 		0	IF_BALL_IN_RANGE 	0 	200
 
   ATTACK_INSTRUCTION	NME_FOLLOWPLAYER 	150 	0 0 0 			100 		129	ALWAYS 		0 	0	(specify ball)
   ATTACK_INSTRUCTION	NME_SPECIAL		-1	-1			12		0	IF_BALL_IN_RANGE 	0 	100
   ATTACK_INSTRUCTION	NME_HOLDPLAYER	-1	0 0 0			 1 		131200	IF_BALL_IN_RANGE 	0 	100
   ATTACK_INSTRUCTION	NME_ENDATTACK		-1   	50			0 		0	ALWAYS			0 	0
 
 
 END_ENEMY

*/

#define SUCKER_CHASE_RANGE (4096 * 180)
#define SUCKER_GIVEUP_RANGE (4096 * 250)
#define SUCKER_SUCK_RANGE (4096 * 50)

void SuckerPlaceBall(ENEMYPOS *enemy)
{
	VECTOR pos;
	static VECTOR ref = {0,-10,0};
	EnemyRotatePoint(enemy,&ref,&pos,1);

	pos.vx = enemy->pos.vx + (pos.vx << 12);
	pos.vy = enemy->pos.vy + (pos.vy << 12);
	pos.vz = enemy->pos.vz + (pos.vz << 12);

	ballPlaceAt(&pos);
	enemy_has_ball = 2;

	if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		DisconnectGloveFromBall(DISCONNECT_WHEN_WHATEVER);

}


#define SUCKER_SUCKING				3
#define SUCKER_HAS_BALL_SEARCH		8
#define SUCKER_HAS_BALL_MOVE		9
#define SUCKER_DROPBALL				10

#define SUCKER_DELAY				11

void Update_Sucker(ENEMYPOS *enemy)
{
	VECTOR oldpos;

	oldpos = enemy->pos;

	if(RANDOM256() < 3)
	{
		sfxPlayNME3D(levelFX, SFX_SUCKER_CALL_1 + (RANDOM256() % 3),enemy);
	}

	switch(enemy->doing)
	{
		case 0:	// picking a rnd position
			enemy->ticker = 0;		
			enemy->doing = 1;
			NMEChooseRandomPoint(enemy,YES);

// ... dropthru

		case 1:
			if(NME_FaceTo( enemy, &enemy->pathvectors[2],40))
			{
				enemy->ticker++;
				if(NME_GoTo2D(enemy,&enemy->pathvectors[2], 0x3500) || enemy->ticker > 300)
				{
					enemy->doing = 0;
				}
			}

			if(NMECheckInRange(enemy,&ballPos, SUCKER_CHASE_RANGE, TRUE))
			{
				enemy->doing = 2;
			}
			break;

		case 2:
			if(NME_FaceTo( enemy, &ballPos,50))
			{
				enemy->ticker++;
				NME_GoTo2D(enemy,&ballPos, 0x3500);

// tbd - ball interaction
				if(!NMECheckInRange(enemy,&ballPos, SUCKER_GIVEUP_RANGE, TRUE))
				{
					enemy->doing = 0;
				}
				else if (NMECheckVisible(enemy, &ballPos, SUCKER_SUCK_RANGE, 0x400))
				{
// Don't disconnect the glove yet
//					if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
//						DisconnectGloveFromBall(DISCONNECT_WHEN_WHATEVER);

					AddToQueue(&enemy->anim,NMEANIM_STARTATTACK,NO,NO,4096);	// suck
//					AddToQueue(&enemy->anim,NMEANIM_RUN,YES,YES,4096);
					AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);	// coz "run" is a deeply crap animation
					enemy->doing = SUCKER_SUCKING;
					enemy->ticker = 30;
				}
			}
			break;

// In the process of sucking
		case SUCKER_SUCKING:	// 3

			enemy->ticker--;

			if(!(enemy->ticker & 3))
				sfxPlayNME3D(globalFX, SFX_GE_SUCKER_ATTACK_LOOP,enemy);

			if(!enemy->ticker)
			{
				enemy->doing = SUCKER_HAS_BALL_SEARCH;
			}

			if(enemy->ticker < 20)
			{
				if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
					DisconnectGloveFromBall(DISCONNECT_WHEN_WHATEVER);
			}

			if(enemy->ticker < 15)
			{
				SuckerPlaceBall(enemy);	// from here on in, the glove can't grab the ball
			}
			else
			{
				if(!NMECheckInRange(enemy, &ballPos, SUCKER_SUCK_RANGE, 0x400))
				{
					enemy->doing = 0;
					AddToQueue(&enemy->anim,NMEANIM_WALK,YES,NO,4096);	// anim,numberloop,queue,speed

				}
/*
				else	// if the glove re-grabs the ball throw him off
				{
					if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
						DisconnectGloveFromBall(DISCONNECT_WHEN_WHATEVER);
				}
*/
			}
			break;


// running around with the ball...
		case SUCKER_HAS_BALL_SEARCH:
			enemy->ticker = 0;		
			enemy->doing = SUCKER_HAS_BALL_MOVE;
			NMEChooseRandomPoint(enemy,YES);
// dropthrough
		case SUCKER_HAS_BALL_MOVE:
			if(NME_FaceTo( enemy, &enemy->pathvectors[2],40))
			{
				enemy->ticker++;
				if(NME_GoTo2D(enemy,&enemy->pathvectors[2], 0x3500) || enemy->ticker > 300)
				{
					enemy->doing = SUCKER_HAS_BALL_SEARCH;
				}
			}
			SuckerPlaceBall(enemy);



//			if( BallChange.changeOn == 0)
			{


				if(    (!(activeframe & 31))

					|| (BallCtrl.type != BALL_MODE_CRYSTAL && BallCtrl.type != BALL_MODE_BEARING)
				  )
				{
					switch(activeframe & (32+64))
					{
					case 0:
						sfxPlayNME3D(levelFX, SFX_SUCKER_CRYSTAL,enemy);

						ballHitBySpell(SPELL_BALL_CRYSTAL,NULL);
						break;
					case 64:
						sfxPlayNME3D(levelFX, SFX_SUCKER_CRYSTAL,enemy);
						ballHitBySpell(SPELL_BALL_BEARING,NULL);
						break;
	// 64 and 96 stay the same
					}
				}
			}

			break;

		case SUCKER_DROPBALL:
			enemy->ticker--;

			if(enemy->ticker >= 50-7)
			{
				SuckerPlaceBall(enemy);
				if(enemy->ticker == 50 - 7)
				{
					sfxPlayNME3D(globalFX, SFX_GE_SUCKER_SPIT,enemy);


					ballVel.vy = 18 * 4096;	// shoot the ball upwards
					ballVel.vx = (RANDOM256()-128)<< 8;
					ballVel.vz = (RANDOM256()-128)<< 8;
				}
			}

			if(!enemy->ticker)
			{
				enemy->doing = SUCKER_DELAY;
//				enemy->ticker = 0;
			}
			break;


		case SUCKER_DELAY:
			if(!enemy->ticker)
			{
				NMEChooseRandomPoint(enemy,YES);
			}
			if(NME_FaceTo( enemy, &enemy->pathvectors[2],40))
			{
				enemy->ticker++;
				if(NME_GoTo2D(enemy,&enemy->pathvectors[2], 0x3500) || enemy->ticker > 300)
				{
					enemy->doing = 0;
				}
			}
			break;


		default:
			enemy->doing = 0;
			AddToQueue(&enemy->anim,NMEANIM_WALK,YES,NO,4096);	// anim,numberloop,queue,speed

			break;
	}

	enemy->vel.vx = enemy->pos.vx - oldpos.vx;
	enemy->vel.vz = enemy->pos.vz - oldpos.vz;
// y vel still changes according to velocity
	enemy->pos = oldpos;
	if(KeepNMEOnGround(enemy,4096 * 10, 4096 * 100))
	{


// If we've been stuck in the same spot, and we're not in the "turning" phase of pathfollowing, then jump
//	if(enemy->pos.vx == oldpos.vx && enemy->pos.vy == oldpos.vy && enemy->pos.vz == oldpos.vz
	if(Kong_HitWall)
	{
		DB("Sucker stuck\n");
		((int)(enemy->extras))++;
		if(((int)(enemy->extras)) > 16)
		{
			DB("Sucker jumps!\n");

			enemy->vel.vy = -4 * 4096;
			((int)(enemy->extras)) = RANDOM256() & 7;
//			DB("Cymon locked\n");
		}
	}
	else
	{
		((int)(enemy->extras)) = 0;
	}
	}


//	DB("bc = %d\n",BallChange.changeOn);
}

void SuckerDropsBall(ENEMYPOS *enemy)
{
	if(enemy->doing == SUCKER_HAS_BALL_SEARCH || enemy->doing == SUCKER_HAS_BALL_MOVE)
	{
		AddToQueue(&enemy->anim,NMEANIM_SPECIAL1,NO,NO,4096);
		AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);
		enemy->doing = SUCKER_DROPBALL;
		enemy->ticker = 50;
	}
	else if(enemy->doing == SUCKER_SUCKING)
	{
		enemy->doing = 0;
		AddToQueue(&enemy->anim,NMEANIM_WALK,YES,YES,4096);	// anim,numberloop,queue,speed
		EnemyStun(enemy);
	}
	else if(enemy->doing != SUCKER_DROPBALL)
	{
		EnemyStun(enemy);
	}
}

void SuckIntRou(int with, ENEMYPOS *enemy, VECTOR *dpos, int sphererad)
{
	if(with == 0)
	{
		NMEPushesGlove(enemy,dpos,sphererad);
	}
	else
	{
		if(BallCtrl.powerwhack)
		{
			PowerWhackHandler(enemy,0);
			return;
		}

		if(enemy->doing != SUCKER_HAS_BALL_SEARCH
			&& enemy->doing != SUCKER_HAS_BALL_MOVE
			&& enemy->doing != SUCKER_DROPBALL
			)
		{
			NMEPushesBall(enemy,dpos,sphererad);
		}

// interact with the ball doesn't happen, so he can grab it ok.
	}
}



// currently, I'm only doing upwards-moving smoke...
void Update_Vent(ENEMYPOS *enemy)
{
	VENT *vent;
	VENTPATTERN *pat;
	VECTOR svec;
	VECTOR svel;

	vent = enemy->extras;

	if(enemy->doing < 0)
	{
		vent->head.platform = NULL;
//		if(vent->head.plat_tag)
		if(vent->head.plat_tag > 1)	// for some reason, the thing defaults to 1's across the boars..
		{
			loadlndFindPlatform(vent->head.plat_tag, NULL,&vent->head.platform);
#if (GOLDCD==NO) && (RELEASE==NO)
			if(!vent->head.platform)
			{
				DB("Platform %d is required by a vent, but doesn't exist\n",vent->head.plat_tag);
			}
#endif
			ASSERT(vent->head.platform);
		}
		enemy->doing = 0;
	}

	if(vent->head.platform)
	{

// Genesis way
//		SUBVECTOR(&enemy->pos,&vent->head.pos,&vent->head.platform->pPlatDef->points[0].pos);
//		ADDVECTOR(&enemy->pos,&enemy->pos,&vent->head.platform->pos);

// Sensible way
		ADDVECTOR(&enemy->pos,&vent->head.pos,&vent->head.platform->pos);
	}


// note that this would prevent the boulder runs from working, if they used this code.
	if(!(enemy->flags & NMEFLAG_VISIBLE))
		return;


	pat = &vent->pat[enemy->doing];


	if(enemy->ticker >= (pat->delay))
	{
		switch(vent->head.type)
		{
			case SMOKE_VENT:

//				DB ("Vent Smoke %d %d %d, glover %d %d %d\n",
//					enemy->pos.vx>>12,enemy->pos.vy>>12,enemy->pos.vz>>12,
//					glovePos.vx>>12,glovePos.vy>>12,glovePos.vz>>12
//					);

//				New_Debris(DEBRIS_SMOKE,&enemy->pos,600);

				enemy_ShootSteam(&enemy->pos, &vent->head.vel,1);
				break;


			case FLAME_VENT:
				enemy_ShootFlames(&enemy->pos, &vent->head.vel,1);
				break;

			case WATER_VENT:
				svec.vx = enemy->pos.vx >> 12;
				svec.vy = enemy->pos.vy >> 12;
				svec.vz = enemy->pos.vz >> 12;
				Start_Droplet(&svec, &vent->head.vel,0);
				break;


			case STEAM_VENT:
				enemy_ShootSteam(&enemy->pos, &vent->head.vel,0);
				break;


			case BUBBLE_VENT:
				svec.vx = enemy->pos.vx >> 12;
				svec.vy = enemy->pos.vy >> 12;
				svec.vz = enemy->pos.vz >> 12;

				svel.vx = vent->head.vel.vx >> 2;	// 100 -> 10, methinks
				svel.vy = vent->head.vel.vy >> 2;
				svel.vz = vent->head.vel.vz >> 2;

				svel.vx = svel.vx * (RANDOM256() + 256) >>9;
				svel.vz = svel.vz * (RANDOM256() + 256) >>9;

//				printf("droplet vel = %d %d %d\n",vent->head.vel.vx,vent->head.vel.vy,vent->head.vel.vz);
				Start_Droplet(&svec, &svel,1);	//&vent->head.vel,1);
				break;

			case CANNONBALL_VENT:
				break;


			
			case AIR_VENT:	// unused
			case ICICLE_VENT:	// unused
			case FLOATY_VENT:	// unused
			case SNOWBALL_VENT:	// hard coded by tom...
			case BOULDER_VENT:
				break;
		}
	}

	enemy->ticker++;
	if(enemy->ticker >= (pat->delay + pat->duration))
	{
		enemy->doing++;
		if(enemy->doing >= vent->head.n_patterns)
			enemy->doing = 0;
//		DB("Vent switch to pattern %d. pats %d (delay %d, durn %d)\n",enemy->doing,vent->head.n_patterns,vent->pat[enemy->doing].delay,vent->pat[enemy->doing].duration);
		enemy->ticker = 0;
	}
}









#define HOOP_WAITING    0
#define HOOP_COLLECTED  1
#define HOOP_ENTRY_GROW 2
#define HOOP_ENTRY_WAIT 3
#define HOOP_ENTRY_SHRINK 4

#define HOOP_MAIN_RADIUS 64

void HoopSetCamera(ENEMYPOS *enemy)
{
		VECTOR pos;
		VECTOR targ;

		pos = enemy->pos;
		targ = pos;
		pos.vx +=(rsin(enemy->init_ya) * 250);
		pos.vy -= 100 * 4096;
		pos.vz -= (rcos(enemy->init_ya) * 250);

		HitCameraPosition(&pos, &targ);

}



// See also "enemiesAddHoop"
VECTOR caveballpos = {34<<12,-40<<12,-1000<<12};
VECTOR wayballpos_hub = {0,-20<<12,-500 << 12};

VECTOR wayballpos_levs[] =
{
	{-300<<12, -20<<12,-100<<12},	// ->l1
	{-300<<12, -20<<12, 100<<12},	// ->l2
	{ 300<<12, -20<<12, 100<<12},	// ->l3
	{ 300<<12, -20<<12,-100<<12}	// ->boss
};

VECTOR hubballpos[] =
{
	{   49<<12, -70<<12, -1115<<12},	// at
	{-1110<<12, -70<<12,  -709<<12},	// ca
	{ 1107<<12, -51<<12,  -748<<12},	// pi
	{-1077<<12, -50<<12,   583<<12},	// pr
	{  996<<12, -76<<12,   646<<12},	// ff
	{   -2<<12, -60<<12,  1078<<12}	// sp
};



// tbd - we only want to do this when moving between levels, not when
// "restarting" the hub

void restoreHubBall()
{
	VECTOR temp;



	temp.vx = GameState.hub_ball_pos.vx << 12;
	temp.vy = GameState.hub_ball_pos.vy << 12;
	temp.vz = GameState.hub_ball_pos.vz << 12;

	ballPlaceAt(&temp);

	BallCtrl.type = GameState.hub_ball_type;
	BallCtrl.lastType = BallCtrl.type;
	if(BallCtrl.type == BALL_MODE_CRYSTAL)
		pBallPSA=pCballPSA;

//	ballStartRadius=ballRadius=BallBehaviour[BallCtrl.type].radius=ballColl.radius= BallBehaviour[BallCtrl.type].radius;
	ballStartRadius = ballEndRadius = ballRadius = ballColl.radius = BallBehaviour[BallCtrl.type].radius;

//	ballStartRadius = ballRadius = ballColl.radius = BallBehaviour[BallCtrl.type].radius;

//	ballStartRadius=ballRadius=BallBehaviour[BallCtrl.type].radius=ballColl.radius=(19*4096*(NORMAL_SCALE) );


	pBallPSA->globalscale.vx=ballRadius/152;
	pBallPSA->globalscale.vy=ballRadius/152;
	pBallPSA->globalscale.vz=ballRadius/152;

	ballColl.physics=&BallBehaviour[0];
	ballColl.physicsModel=BallCtrl.type;

	ChangeBall(&pBallPSA->world,ballTex[BallCtrl.type]);
}

// Returns
// 0 for ball does not exist on this level
// 1 for ball exists on the level but not at the hoop
// 2 for ball exists, and appears at the hoop with you



int DoesBallExist()
{
	DB("Does Ball Exist?\n");

// the title->hub1 frig that was here has moved to "enemiesEndFile", so it only happens on 1st spawn, not on crystal deaths

	if((level >= HUB2 && level <= HUB8) || level == CAVE || level == WAYROOM)
	{
		int ball_exists;
		int i;

// Have we beaten (any - coz you can wander round the wayrooms) boss, but not delivered the crystal yet ?
// (In the case of the cave, we need to deal with carrying the ball in from outside)
// If we've not got the ball with us, does it exist on the level ?
		ball_exists = 0;

		if(gameCtrl.hub_to_use == 1 && !(GameState.levels_open[0] & LEVOPEN_L1_OPEN))
		{
			DB("ball exists in cave / wayroom, taken from hub1\n");
			ball_exists = 1;
		}
		else
		{
			for(i = 0; i < 6; i++)
			{
				DB("Level state %i = %x\n",i,GameState.levels_open[i]);
				if(    !(GameState.levels_open[i] & LEVOPEN_CRYSTAL_DELIVERED)
					&&  (GameState.levels_open[i] & LEVOPEN_BOSS_BEATEN)
				  )
				{
					DB("Ball exists from level %d\n",i);
					ball_exists = 1;

// This is actually essential for post-boss savegames to work ok
					if(gameCtrl.wayroom_world == HUB)
						gameCtrl.wayroom_world = i + ATLANTIS;

					break;
				}
			}
		}
		if(!ball_exists)
		{
			DB("Ball does not exist on this hub/cave level\n");

// Place the non-existant ball either
// (a) at level 1/2/3/boss door if you're in a wayroom that's not complete
// (b) at the hub door if you're in ANOTHER wayroom

// (c) at the relevant world portal if you're in the hub
// (d) at the way back to the hub if you're in the cave
			if(level == CAVE)
			{
				ballPos = caveballpos;
			}
			else if (level == WAYROOM)
			{
				if(GameState.levels_open[gameCtrl.wayroom_world-1] & (LEVOPEN_BOSS_BEATEN | LEVOPEN_CRYSTAL_DELIVERED))
				{
					ballPos = wayballpos_hub;
				}
				else
				{
					switch(GameState.levels_open[gameCtrl.wayroom_world-1]
						&
						(LEVOPEN_L1_OPEN | LEVOPEN_L2_OPEN  | LEVOPEN_L3_OPEN  | LEVOPEN_BOSS_OPEN | LEVOPEN_BOSS_BEATEN)
						)
					{
					case 0:
					case LEVOPEN_L1_OPEN | LEVOPEN_L2_OPEN  | LEVOPEN_L3_OPEN  | LEVOPEN_BOSS_OPEN | LEVOPEN_BOSS_BEATEN:
						ballPos = wayballpos_hub;
						break;
					case LEVOPEN_L1_OPEN | LEVOPEN_L2_OPEN  | LEVOPEN_L3_OPEN  | LEVOPEN_BOSS_OPEN:
						ballPos = wayballpos_levs[3];
						break;
					case LEVOPEN_L1_OPEN | LEVOPEN_L2_OPEN  | LEVOPEN_L3_OPEN:
						ballPos = wayballpos_levs[2];
						break;
					case LEVOPEN_L1_OPEN | LEVOPEN_L2_OPEN:
						ballPos = wayballpos_levs[1];
						break;
					case LEVOPEN_L1_OPEN:
					default:
						ballPos = wayballpos_levs[0];
						break;
					}
				}
			}
			else
			{
// hub. Pick an unfinished wayroom
				ballPos = hubballpos[0];
				for(i = 0; i < 6; i++)
				{
					if(    !(GameState.levels_open[i] & LEVOPEN_BOSS_BEATEN)
						&&  (GameState.levels_open[i] & LEVOPEN_L1_OPEN)
					  )
					{
						DB("placing ball at entrance to %d\n",i);
						ballPos = hubballpos[i];
						break;
					}
				}
			}
			return 0;
		}

/*	// No. Choosing hub2..8 from the menu will be without the ball, so the savegame works OK.
		if(gameCtrl.previousLevel == TITLE)
		{
			DB("Ball exists - coming from title\n");
			return 2;
		}
		else
*/

		{
			if(gameCtrl.port_with_ball)
			{
				DB("Ported with ball\n");
				return 2;
			}
			else
			{
				if(level == CAVE)
				{
					ballPos = caveballpos;
					DB("cave - didn't port with ball, so not got it\n");
					return 0;	// We left the ball behind on the hub
				}
				else if (level == WAYROOM)
				{
					ballPos = wayballpos_hub;
					DB("wayroom - didn't port with ball, so not got it\n");
					return 0;	// We left the ball behind on the hub
				}
				else
				{
					DB("hub - didn't port with ball. So it's where it was left\n");

// if we LEFT the ball in the wayroom, we want to port in with it.
// (but you can leave the ball in the hub, & visit wayrooms on foot)
					if(GameState.hub_ball_pos.vy == 0x7fff)	// took the ball out. Come back with it.
					{
						gameCtrl.port_with_ball = 1;
						return 2;
					}

					
					restoreHubBall();
					return 1;	// the ball's wherever we left it
				}
			}
		}
	}

// What happens on a normal level-shaped-level

// ** inluding hub1 - The only way out of hub1 is into the hubcave,
// where you get the ball taken off you, and when you come out, it's into hub2/


	if(ball_at_hoop)
	{
		DB("Ball exists by basically being at the hoop\n");
		return 2;
	}
	else
	{
		if(level == HUB1)	// Actually, previous will always be hubcave, and you must have walked in
		{
			if(gameCtrl.previousLevel == TITLE)
			{
				if(gameInfo.keyRecordFlag==PLAYBACK || gameInfo.keyRecordFlag==RECORD)
				{
					gameCtrl.port_with_ball = 1;
					ball_at_hoop = 1;
					return 2;
				}
			}
			else
			{
				restoreHubBall();
			}
		}
		DB("Ball exists somewhere on the level\n");
		return 1;
	}
}


void Update_Hoop(ENEMYPOS *enemy)
{
	int d;
	int r;
	VECTOR vec;
	unsigned int colour;
	int want_camera;

	want_camera = 0;




	switch(enemy->doing)
	{
		case HOOP_WAITING:		// normal spinning
			break;

		case HOOP_COLLECTED:		// vanishing post ball-hit
		case HOOP_ENTRY_SHRINK:			// vanishing post re-entry
			enemy->n_points-=2;
			if(enemy->n_points <= 0)
			{
				enemy->n_points = 0;
				enemy->flags &= ~NMEFLAG_ACTIVE;	// don't disable - needs respawn ?? tbd: check
			}
			break;

// tbd - grab the camera (eek!)
		case HOOP_ENTRY_GROW:

// Bit of a bodge... Fix an atboss problem
//			if(!(CamVars.flags) && !cameo_running)
//				HoopSetCamera(enemy);

			if((!(CamVars.flags & LOOKATPLAT)  && !cameo_running) || (enemy->n_points < HOOP_MAIN_RADIUS/2))
			{

			enemy->n_points+=2;
			want_camera = 1;
			if(enemy->n_points >= HOOP_MAIN_RADIUS)
			{
				enemy->n_points = HOOP_MAIN_RADIUS;
				enemy->doing = HOOP_ENTRY_WAIT;
//				enemy->ticker = 40;
				enemy->ticker = 20;

// throw the glove into the world
				sfxPlay3D(globalFX,SFX_HAND2_JUMP1 + random(4),&glovePos);
				handComeOutOfHoop(&enemy->pos,enemy->init_ya);

				if(level != FEARBOSS)
					Camera_BeginLookAtPlayer();
			}
			}
			break;

		case HOOP_ENTRY_WAIT:
		{
			int exist;

			enemy->ticker--;
			want_camera = 1;

			if(!enemy->ticker)
			{
				enemy->doing = HOOP_ENTRY_SHRINK;


// Deal with circumstances when you don't have the ball at a restart point


// note - if you take a ball into the cave, it WILL NOT come back.

// taking a ball into the cave / wayroom needs to be dependant on whether you had a ball when you entered it

				exist = DoesBallExist();

				if(exist == 0)	// some checking to prevent silliness by calling the ball in the hub
				{
					ball_at_hoop = 1;
					gameCtrl.port_with_ball = 0;
				}

				if(exist == 2)
				{
					ballComeOutOfHoop(&enemy->pos,enemy->init_ya);
					sfxPlay3D(globalFX,SFX_GE_SUCKER_SPIT,&ballPos);
				}
			}
			break;
		}


		default:
			enemy->doing = HOOP_WAITING;
			enemy->n_points = HOOP_MAIN_RADIUS;
			break;
	}


//	if(want_camera)
//	{
//		HoopSetCamera(enemy);
//	}


	if(enemy->flags & NMEFLAG_VISIBLE)
	{
		d = random(4096);
		r = enemy->n_points/2;	//40;	//random(30);
		colour = random(6 * 256);
		vec.vx = enemy->pos.vx + r * rsin(d);
		vec.vy = enemy->pos.vy + 5*4096;
		vec.vz = enemy->pos.vz + r * rcos(d);
		colour = effectsGetPrimary(colour>>8,(colour &0xff),32);

		New_Debris(DEBRIS_HOOPHOVER,&vec,colour);
	}
}

// input 1 for "next", -1 for "previous"
// returns the same number as you passed in if another one ain't available
int nextHoopAvailable(int hoop_number, int nextprev)
{
	int c;
	int rval;
	ENEMYPOS *nme;

	c = 0;

//	if(cheat_all_hoops)
	if(cheatActivated & ACCESS_ALL_RESTARTS_CHEAT)
	{
		hoop_number += nextprev;
		if(hoop_number < 0) hoop_number = 0;
		if(hoop_number >= numberofhoops) hoop_number = numberofhoops-1;
		return hoop_number;
	}

	rval = hoop_number;
	for(nme = nme_list; nme; nme = nme ->next)
	{
		if(nme->type == HOOP_ENEMY)
		{
			if(nextprev==0 && !(nme->flags & NMEFLAG_ACTIVE))
				return c;
			if(nextprev > 0 && c > hoop_number && !(nme->flags & NMEFLAG_ACTIVE))
				return c;

			if(nextprev < 0 && c < hoop_number && !(nme->flags & NMEFLAG_ACTIVE))
			{
				rval = c;
			}
			c++;
		}
	}
	return rval;
}

// 1st hoop of each zone, you don't get the ball until you've collected it once
void Use_Hoop(int hoop_number)
{
	int c;
	ENEMYPOS *nme;
	c = 0;
	if(!numberofhoops)
	{
		glovePos = GloveStartPos;
		ballPos = BallStartPos;
		GloveCtrl.enabled = TRUE;
		DB("Ball enabled by use_hoop (no hoops)\n");

		BallCtrl.enabled = TRUE;
		return;
	}

	for(nme = nme_list; nme;nme = nme->next)
	{
		if(nme->type == HOOP_ENEMY)
		{
			if(c == hoop_number)
			{
				nme->doing = HOOP_ENTRY_GROW;
				nme->flags |= (NMEFLAG_ACTIVE + NMEFLAG_ENABLED);
				nme->n_points = 0;


				lasthoopused = hoop_number;

				if(ball_at_hoop)
				{
					BallStartPos = nme->pos;
					BallStartPos.vy -= 10 * 4096;	// moved up a bit, coz the hand kept catching it

					ballColl.oldPos = ballPos = BallStartPos;	// sort out the camera, in theory
					pBallPSA->position.vx = ballPos.vx>>12;
					pBallPSA->position.vy = ballPos.vy>>12;
					pBallPSA->position.vz = ballPos.vz>>12;

					BallCtrl.enabled = FALSE;
					BallCtrl.ballOnGround = FALSE;

				}
				else
				{
					if(DoesBallExist() != 0)
					{
						DB("Ball enabled by exist != 0\n");

						BallCtrl.enabled = TRUE;	// coz when you die, this gets zeroed
					}
					else
					{
						BallCtrl.enabled = FALSE;	// Just make sure
						BallCtrl.ballOnGround = FALSE;
					}
				}

				GloveCtrl.enabled = FALSE;
				GloveCtrl.onGround = FALSE;
				gloveColl.oldPos = glovePos = GloveStartPos = nme->pos;
				pGlovePSA->position.vx = nme->pos.vx>>12;
				pGlovePSA->position.vy = nme->pos.vy>>12;
				pGlovePSA->position.vz = nme->pos.vz>>12;

				DB("Ball-at-hoop = %d, enabled = %d\n",ball_at_hoop, BallCtrl.enabled);

				if(level != FEARBOSS)
					HoopSetCamera(nme);

				pickupClearSpells();
				return;
			}
			c++;
		}
	}
}






//#define	EXIT_PORTALRADIUS		8
#define	EXIT_PORTALRADIUS		20

void Update_Portal(ENEMYPOS *enemy)
{
	if(enemy->flags & NMEFLAG_VISIBLE)
		New_PortalDebris(&enemy->pos);
}


#define TELEPORT_WORKING_BALL 1
#define TELEPORT_WORKING_GLOVE 2

void Update_Teleport(ENEMYPOS *enemy)
{
	VECTOR pos;
	VECTOR targ;

	if(enemy->flags & NMEFLAG_VISIBLE)
		New_PortalDebris(&enemy->pos);

// Highlight the exit
//	New_PortalDebris(&enemy->pathvectors[0]);

	switch(enemy->doing)
	{
	case TELEPORT_WORKING_GLOVE:
		pos = enemy->pathvectors[0];
		targ = pos;

//		handGoThroughTeleport(&targ,0);
		if(teleportTrigger(0,&targ,enemy->n_points,enemy->pathvectors[1].vx,enemy->pathvectors[1].vy,0))
		{

	// anglular botches to match up with the original data
			pos.vx +=(rsin(0) * 250);
			pos.vy -= 100 * 4096;
			pos.vz -= (rcos(0) * 250);

			HitCameraPosition(&pos, &targ);
	//			PuzzleVars.camera_fixed_endtime = iframe+1000;
		}
		enemy->doing = 0;

		break;


	case TELEPORT_WORKING_BALL:
		pos = enemy->pathvectors[0];
		targ = pos;

//		handGoThroughTeleport(&targ,0);

		if(teleportTrigger(1,&targ,enemy->n_points,enemy->pathvectors[1].vx,enemy->pathvectors[1].vy,0))
		{

			if(!GloveCtrl.enabled)
			{
	// anglular botches to match up with the original data
				pos.vx +=(rsin(0) * 250);
				pos.vy -= 100 * 4096;
				pos.vz -= (rcos(0) * 250);

				HitCameraPosition(&pos, &targ);
		//			PuzzleVars.camera_fixed_endtime = iframe+1000;
			}
		}
		enemy->doing = 0;
		break;



	default:
		break;
	}
}




// Flags needs adjusting to tell it what to teleport, according to what touched the box
// "which" is whether it's the ball or the hand that's triggered the telep


//(FLAGS)%i{BALL-HAND-BALLANDHAND-BALLANDHANDJOINED-SWAPHANDBALL--}{17-18-19-24-64--}
//(FLAGS)%i{BALL-HAND-BALLANDHAND-BALLANDHANDJOINED-WHENEVER-SWAPHANDBALL--}{17-18-19-24-27-64--}

int teleportTrigger(int which,VECTOR *dest,int flags,int diss,int app, int ya)
{
	VECTOR temp;
/*
	DB("Teleport triggering to (%d,%d,%d). Which = %d, flags = %d\n",
		dest->vx>>12,dest->vy>>12,dest->vz>>12,
		which,flags);
*/
	if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
	{
// If the telep allows "joined", send 'em through

//		if(flags & (TELEPORT_HAND + TELEPORT_JOINED))

// In particular, note that "hand_snatch" is fraught
		if(GloveCtrl.action == HAND_JOINED || GloveCtrl.action == HAND_BALLWALK || GloveCtrl.action == HAND_BOUNCE)
		{
			if(flags & TELEPORT_JOINED)
			{
				handGoThroughTeleport(dest,ya);

				temp.vx = dest->vx + (rsin(0) * 250);
				temp.vy = dest->vy - 100 * 4096;
				temp.vz = dest->vz - (rcos(0) * 250);
				HitCameraPosition(&temp, dest);
				sfxPlay(globalFX,SFX_TELEPORT_1);

				return 1;
			}
		}
	}
	else if(which == 0)	// glove
	{
		if(flags & TELEPORT_HAND)
		{
			gloveVel.vx = 0;
			gloveVel.vy = 0;
			gloveVel.vz = 0;

			GloveCtrl.direction = ya;
			GloveCtrl.disableTimer=12;

			GloveCtrl.onGround=FALSE;
			glovePos = gloveColl.oldPos = *dest;

			temp.vx = dest->vx + (rsin(0) * 250);
			temp.vy = dest->vy - 100 * 4096;
			temp.vz = dest->vz - (rcos(0) * 250);
			HitCameraPosition(&temp, dest);
			sfxPlay(globalFX,SFX_TELEPORT_1);

			return 1;
		}
	}
	else		// ball
	{
		if(flags & TELEPORT_BALL)
		{
			ballPlaceAt(dest);

			if(!GloveCtrl.enabled)
			{
				temp.vx = dest->vx + (rsin(0) * 250);
				temp.vy = dest->vy - 100 * 4096;
				temp.vz = dest->vz - (rcos(0) * 250);
				HitCameraPosition(&temp, dest);
			}
			sfxPlay(globalFX,SFX_TELEPORT_1);

			return 1;
		}
	}
	return 0;
}

// what pickups do from a display POV:-
// spin whilst tilted,
// stretch-v/squeeze-h,
// release blue decaying sparkles in a sineing-up-and-down cylinder around it
// has shadow
// exit - grows then shrinks to zero. stars. big powerup effect on the glove
// entry - grows from zero, gains shadow.
void Update_Pickup(ENEMYPOS *enemy)
{
	int coll;
	SVECTOR vec;
	enemy->ya = (enemy->ya + 0x140) & 4095;
	enemy->xa = 0xC0;	// 4096 = 0x1000
	enemy->za = 0;


	enemy->psa->globalscale.vx=1800 + (rsin(enemy->ya) >> 4);	// 4096...256
	enemy->psa->globalscale.vy=1800 - (rsin(enemy->ya) >> 4);
	enemy->psa->globalscale.vz=1800 + (rsin(enemy->ya) >> 4);

	switch(enemy->doing)
	{
// exit - tbd: note that scaling the psa here isn't going to work if we have multiple pickups of the same type
// (so we need to get some more space in the enemy structure, & do this at print-time
		case 1:
// ticker = 0...32
// rqd output is 0x180 ... 0x800
			coll = rsin(enemy->ticker * (0x680>>5) + 0x180);
			coll = coll * 4096 / rsin(0x180);	// this is a constant, and should be precalcced

			enemy->psa->globalscale.vx = (enemy->psa->globalscale.vx * coll) >> 12;
			enemy->psa->globalscale.vy = (enemy->psa->globalscale.vy * coll) >> 12;
			enemy->psa->globalscale.vz = (enemy->psa->globalscale.vz * coll) >> 12;
			enemy->ticker++;
			if(enemy->ticker == 32)
			{
				enemy->doing = 2;
				enemy->ticker = 0;
			}
			break;

		case 2:	// pause

			enemy->ticker++;
			if(enemy->ticker >= enemy->pathvectors[1].vx)	// regen time (.vy = duration)
			{
				enemy->psa->globalscale.vx = enemy->psa->globalscale.vy = enemy->psa->globalscale.vz = 1;
				enemy->doing=3;
				enemy->ticker = 1;
			}
			break;

		case 3:	// entry
			enemy->psa->globalscale.vx = (enemy->psa->globalscale.vx * enemy->ticker) >> 5;
			enemy->psa->globalscale.vy = (enemy->psa->globalscale.vy * enemy->ticker) >> 5;
			enemy->psa->globalscale.vz = (enemy->psa->globalscale.vz * enemy->ticker) >> 5;
			enemy->ticker++;
			if(enemy->ticker == 32)
			{
				enemy->doing = 0;
				enemy->ticker = 0;
			}
			break;

		case 0:
		default:
			vec.vx = enemy->pos.vx >> 12;
			vec.vy = enemy->pos.vy >> 12;
			vec.vz = enemy->pos.vz >> 12;

			coll = effect_BoxCollideWithPlayer(EFFECT_BOXCOLL_GLOVE, &vec, GARIBCOLLRADIUS);
			if(coll)
			{
				New_StarRingDebris(&enemy->pos, 6);
				enemy->doing = 1;
				enemy->ticker = 0;

				sfxPlay3D(globalFX,SFX_COLLECT_POWERUP_5,&enemy->pos);

				switch(enemy->type)
				{
					case PICKUP_ENEMY_STUN:
						break;
					case PICKUP_ENEMY_DEATH:
						pickupFirePickupSpell(SPELLACTION_GLOVETRANS,&enemy->pos,SPELL_DEATH,enemy->pathvectors[1].vy);
						break;
					case PICKUP_ENEMY_FROGGY:
						pickupFirePickupSpell(SPELLACTION_GLOVETRANS,&enemy->pos,SPELL_FROGGY,enemy->pathvectors[1].vy);
						break;
					case PICKUP_ENEMY_SUCTION:
						pickupFirePickupSpell(SPELLACTION_GLOVETRANS,&enemy->pos,SPELL_SUCTION,enemy->pathvectors[1].vy);
						break;
					case PICKUP_ENEMY_HERCULES:
						pickupFirePickupSpell(SPELLACTION_GLOVETRANS,&enemy->pos,SPELL_HERCULES,enemy->pathvectors[1].vy);
						break;
					case PICKUP_ENEMY_SPEEDUP:
						pickupFirePickupSpell(SPELLACTION_GLOVETRANS,&enemy->pos,SPELL_SPEEDUP,enemy->pathvectors[1].vy);
						break;
					case PICKUP_ENEMY_ROTORBLADES:
						pickupFirePickupSpell(SPELLACTION_GLOVETRANS,&enemy->pos,SPELL_ROTORBLADES,enemy->pathvectors[1].vy);
						break;

					case PICKUP_ENEMY_BOWLINGBALL:
					case PICKUP_ENEMY_POWERBALL:
					case PICKUP_ENEMY_BALLBEARING:
					case PICKUP_ENEMY_BEACHBALL:
					case PICKUP_ENEMY_SNOWBALL:
					case PICKUP_ENEMY_CRYSTALBALL:
						pickupFirePickupSpell(SPELLACTION_BALLTRANS,&enemy->pos,enemy->type - PICKUP_ENEMY_BOWLINGBALL + SPELL_BALL_BOWLING,enemy->pathvectors[1].vy);
						break;

					case PICKUP_ENEMY_BOOMERANGBALL:
						pickupFirePickupSpell(SPELLACTION_BALLTRANS,&enemy->pos,SPELL_BOOMERANG,enemy->pathvectors[1].vy);
						break;

					case PICKUP_ENEMY_VANISHBALL:
						pickupFirePickupSpell(SPELLACTION_BALLTRANS,&enemy->pos,SPELL_VANISH,enemy->pathvectors[1].vy);
						break;
				}



//				handGivePlayerPower(enemy->type,200);
			}

			{
				VECTOR pos;
				pos.vx = (enemy->pos.vx) + rsin((sinewave1>>16) & 4095) * 14;
				pos.vz = (enemy->pos.vz) + rcos((sinewave1>>16) & 4095) * 14;
				pos.vy = (enemy->pos.vy) + rsin((sinewave2>>16) & 4095) * 8;
				New_Debris(DEBRIS_LIFEGLOW,&pos,0);
			}
			break;
	}
}



void EnemyKill(ENEMYPOS *enemy)
{
// tbd - drop a garib if you need to
	New_StarRingDebris(&enemy->pos, 6);
	enemy->flags &= ~NMEFLAG_ACTIVE;	// don't reset "enabled" - the enemy needs to respawn (ummm, unless it's a non-respawning enemy)
	enemy->flags |= NMEFLAG_DEAD;

	sfxPlayNME3D(globalFX, SFX_GE_REGGIE_DEATH,enemy);

	if(!(enemy->flags & NMEFLAG_GARIB_DROPPED))
	{
		if(    (nmeinfo[enemy->type].flags & DROPS_GARIB)
			|| (enemy->type == NME_FROG && enemy->n_points == 1)
			)
		{
			ReleaseEnemyGarib(&enemy->pos);
			enemy->flags |= NMEFLAG_GARIB_DROPPED;
		}
	}
}


void EnemyStun(ENEMYPOS *enemy)
{
// If cymon'got hold of the glove, make him let go, & restore the glove to normal
	if(enemy->type == CYMON && enemy->doing == CYMON_THROWING_GLOVE)
	{
		AddToQueue(&enemy->anim,NMEANIM_SPECIAL2,NO,NO,4096);
		AddToQueue(&enemy->anim,NMEANIM_WALK,NO,YES,4096);
		enemy->doing = CYMON_GIVING_UP;
		enemy->ticker = 0;
		pGlovePSA->world.rotate.vx = 0;
	}
	if(enemy->type == CHUCK && enemy->doing == CLUCK_WITH_BALL)
	{
		CluckPushOffBall(enemy);
	}

//	New_StarRingDebris(&enemy->pos, 6);
	enemy->animinfo.speed = 0;
	enemy->flags |= NMEFLAG_STUNNED;
//	enemy->ticker = 0;
	enemy->stuntime=0;
}

#define STUNTIME 200
void Update_Stunned(ENEMYPOS *enemy)
{
	StunDebris(&enemy->pos,nmeinfo[enemy->type].radius,18);

/*
	pos.vx = enemy->pos.vx + rsin((sinewave1>>17) & 4095) * 18;
	pos.vy = enemy->pos.vy - (nmeinfo[enemy->type].radius << 12) + rsin((sinewave2>>16) & 4095) * 5;	// stick the stunrings at the top of the NME
	pos.vz = enemy->pos.vz + rcos((sinewave1>>17) & 4095) * 18;
	New_Debris(DEBRIS_STUNSTAR,&pos,0);

	pos.vx = enemy->pos.vx - rsin((sinewave1>>17) & 4095) * 18;
	pos.vy = enemy->pos.vy - (nmeinfo[enemy->type].radius << 12) - rsin((sinewave2>>16) & 4095) * 5;	// stick the stunrings at the top of the NME
	pos.vz = enemy->pos.vz - rcos((sinewave1>>17) & 4095) * 18;
	New_Debris(DEBRIS_STUNSTAR,&pos,0);
*/
//	enemy->ticker++;
//	if(enemy->ticker >= STUNTIME)
	enemy->stuntime++;
	if(enemy->stuntime >= STUNTIME)
	{
// tbd - we should really preserve the timer, too
		enemy->flags &= (~NMEFLAG_STUNNED);
		enemy->animinfo.speed = 4096;	// more things anim at 4096 than at 2048...

	}
}

void StunDebris(VECTOR *refpos, int h, int rad)
{
	VECTOR pos;

	pos.vx = refpos->vx + rsin((sinewave1>>17) & 4095) * rad;
	pos.vy = refpos->vy + rsin((sinewave2>>16) & 4095) * 5;	// stick the stunrings at the top of the NME
	pos.vz = refpos->vz + rcos((sinewave1>>17) & 4095) * rad;
	pos.vy -= h<<12;
	New_Debris(DEBRIS_STUNSTAR,&pos,0);

	pos.vx = refpos->vx - rsin((sinewave1>>17) & 4095) * rad;
	pos.vy = refpos->vy - rsin((sinewave2>>16) & 4095) * 5;	// stick the stunrings at the top of the NME
	pos.vz = refpos->vz - rcos((sinewave1>>17) & 4095) * rad;
	pos.vy -= h<<12;
	New_Debris(DEBRIS_STUNSTAR,&pos,0);

}

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

void Update_Enemies()
{
	ENEMYPOS *enemy;

	if(!ball_at_hoop)
	{
		if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
			ball_at_hoop = 1;
		}
	}


/*
// Froggificator testing
	{
		static int ticker = 0;
		ticker++;

		if (!(ticker & 127))
			handGivePlayerPower(SPELL_FROGGY,200);
	}
*/

// boss-exit platforms
	if(PuzzleVars.user_variables[1] == -1)
	{
		NextLevelBasics();
		gameCtrl.dropOutFlag=GAME_COMPLETED;
	}

	if(enemy_has_ball)
		enemy_has_ball--;


	if(GloveCtrl.insideBubble)
		GloveCtrl.insideBubble--;

#if RELEASE==NO
	if(pad[1] & PAD_CIRCLE)
	{
		static char temp[30];
		messctrl.justify=CENTRETEXT;
		sprintf(temp,"glove %d %d %d\n",(int)glovePos.vx>>12,(int)glovePos.vy>>12,(int)glovePos.vz>>12);
		TEXTPRINTAT(100,32,temp);
	}
#endif
//	printf("glove %d %d %d\n",(int)glovePos.vx>>12,(int)glovePos.vy>>12,(int)glovePos.vz>>12);


	for(enemy = nme_list; enemy; enemy = enemy->next)
	{
//		enemy->smPos.vx=enemy->pos.vx/4096;
//		enemy->smPos.vy=enemy->pos.vy/4096;
//		enemy->smPos.vz=enemy->pos.vz/4096;

		if(enemy->flags & NMEFLAG_ACTIVE)
		{
// rather a nasty frig, but this means we can get away without segs for now...
			if(enemy->flags & NMEFLAG_STUNNED)
			{
				Update_Stunned(enemy);
			}
/*
			else if(enemy->flags & NMEFLAG_FROGGED)	// Frogging should be either done permanantly, or by creating a temp enemy
			{
				if(nmeinfo[NME_FROG].update)
				{
					nmeinfo[NME_FROG].update(enemy);
				}
			}
*/
			else if(nmeinfo[enemy->type].update)
			{
				nmeinfo[enemy->type].update(enemy);
			}
		}
	}
}


/*
void EnemyRP_Recurse(NEWOBJECT *current, VECTOR * globalscale)
{
// Go up through parent objects
	if(current->parent != WORLD)
	{
		EnemyRP_Recurse(current->parent,globalscale);
	}
	else
	{
   		current->matrixscale = current->matrix;
   		ScaleMatrix(&current->matrixscale,&current->scale);


		ScaleMatrix(&current->matrix,globalscale);
   		ScaleMatrix(&current->matrixscale,globalscale);
// ignore the camera transform
		return;
	}

// Go back down to the specific child

   	current->matrixscale = current->matrix;
   	ScaleMatrix(&current->matrixscale,&current->scale);

	gte_MulMatrix0(&current->parent->matrix, &current->matrix, &current->matrix);
	gte_MulMatrix0(&current->parent->matrix, &current->matrixscale, &current->matrixscale);


	gte_SetRotMatrix(&current->parent->matrixscale);
	gte_SetTransMatrix(&current->parent->matrixscale);

	gte_ldlvl(&current->matrix.t);
	gte_rtirtr();
	gte_stlvl(&current->matrixscale.t);

	return;
}
*/

//recursive routine could be a problem for FASTSTACK()!
// multiply matrices up the strand without changing 'em


void EnemyRP_Recurse2(NEWOBJECT *current, VECTOR * globalscale,MATRIX *rval)
{
// Go up through parent objects
	//MATRIX temp;
	if(current->parent != WORLD)
	{
		matrixstackcount++;
		if (matrixstackcount==MAXMATRIXSTACK){DB("MAX matrix stack hit- EnemyRPrecurse!\n");CRASH;}
		EnemyRP_Recurse2(current->parent,globalscale,rval);
		matrixstackcount--;
	}
	else
	{
   		*rval = current->matrix;
   		ScaleMatrix(rval,&current->scale);
   		ScaleMatrix(rval,globalscale);
// ignore the camera transform
		return;
	}

// Go back down to the specific child

//   	temp = current->matrix;
	matrixstack[matrixstackcount] = current->matrix;
   	ScaleMatrix(&matrixstack[matrixstackcount],&current->scale);

	gte_SetRotMatrix(rval);
	gte_SetTransMatrix(rval);

	gte_MulMatrix0(rval, &matrixstack[matrixstackcount], rval);

	gte_ldlvl(&current->matrix.t);
	gte_rtirtr();
	gte_stlvl(&rval->t);

	return;
}


// ==========================
void EnemyRotatePoint(ENEMYPOS *enemy,VECTOR *in, VECTOR *out, int segment)
{
	ACTOR_ANIMATION *anim;
	AnimType		*animInfo;
	int anim_frame;
	objectSegDataType	*seg;
	NEWMODEL *psa;
	NEWOBJECT	*world;
	NEWOBJECT *current;

	psa = enemy->psa;

	if(!psa)	// eg - chuck egg sprites
	{
		*out = *in;
		return;
	}

	psa->world.rotate.vx = enemy->xa;
	psa->world.rotate.vy = enemy->ya;
	psa->world.rotate.vz = enemy->za;
	psa->position.vx= 0;//enemy->pos.vx>>12;
	psa->position.vy= 0;//enemy->pos.vy>>12;
	psa->position.vz= 0;//enemy->pos.vz>>12;


	if(segment == -1)
	{
// xz position of sphere, adjusted for enemy's Y-angle
// (ahem!)
		static MATRIX		rotmat1;

		current = world = &psa->world;
		quaternionGetMatrix((IQUATERNION *)&world->rotatekeys->vect, &world->matrix);	// get the enemies heart matrix into "world"
		RotMatrixYXZ_gte(&world->rotate,&rotmat1);	// get the world rotate matrix into rotmat1
		gte_MulMatrix0(&rotmat1,&world->matrix,&world->matrix);	// multiply 'em together
		world->matrix.t[0] = 0;
		world->matrix.t[1] = 0;
		world->matrix.t[2] = 0;

		gte_SetRotMatrix(&current->matrix);
		gte_SetTransMatrix(&current->matrix);

		gte_ldlvl(in);
		gte_rtirtr();
		gte_stlvl(out);
	}
	else
	{
		if(!(segment & 0xff00))
		{
			if(enemy->animinfo.segInfo)
			{
				anim = &enemy->anim;
				animInfo= anim->animInfo;
				seg =anim->animInfo->segInfo;
				anim_frame = animInfo->frame+seg[anim->currentAnimation].segStart;
				objectSetAnimation(psa,anim_frame);
			}
			else
			{
				objectSetAnimation(psa,0);
			}
		}

//		printf("ok\n");


// ok, why doesn't ISL's object format include an object-list?
// (other than the segmenttable, which changes order all the time, obviously)
		{
			static NEWOBJECT *objs[12];
			int depth = 0;
			int count = 0;

			current = &psa->world;

//			if(segment == 2)
//				printf("hunt\n");

			for(count = 0; count < (segment & 0xff); count++)
			{
//				printf("%d\n",count);

				if(current->child)
				{
//					printf("  child\n");

					objs[depth] = current;
					depth++;
					current = current->child;
				}
				else if(current->next)
				{
//					printf("  next\n");

					current = current->next;
				}
				else
				{
					do
					{
//						printf("  up\n");

						depth--;
						current = objs[depth];
					}
					while(!current->next);
					current = current->next;
				}
			}
		}
//		printf("found\n");

//		current = psa->segmenttable[segment];

//		current = psa->world.child->child->child;
		{
			static MATRIX temp;
			matrixstackcount=0;
			EnemyRP_Recurse2(current,&psa->globalscale,&temp);
			gte_SetRotMatrix(&temp);
			gte_SetTransMatrix(&temp);
		}
//		EnemyRP_Recurse(current,&psa->globalscale);
//		gte_SetRotMatrix(&current->matrixscale);
//		gte_SetTransMatrix(&current->matrixscale);


// grrr... this is the thing I missed - all the matrix calcs are based around object coords, & we need the objects
// to be scaled down. tsk.
		{
			static VECTOR temp;
			temp.vx = in->vx * 4096 / psa->globalscale.vx;
			temp.vy = in->vy * 4096 / psa->globalscale.vy;
			temp.vz = in->vz * 4096 / psa->globalscale.vz;
			gte_ldlvl(&temp);
		}

		gte_rtirtr();
		gte_stlvl(out);
	}

}
/******************************************************************************************/

// === Diagnostic target rings for visibility / Collision ===

#ifdef ENEMY_SHOW_VIS_SPHERES
void EnemyShowVisSphere(ENEMYPOS *nme)
{
	SVECTOR temp;

	temp.vx = nme->pos.vx>>12;
	temp.vy = nme->pos.vy>>12;
	temp.vz = nme->pos.vz>>12;
	Draw_RadCheckRing2(&temp, nmeinfo[nme->type].radius, 0x008000);
}
#endif
#ifdef ENEMY_SHOW_COLL_SPHERES
void EnemyShowCollSphere(ENEMYPOS *nme)
{
	NEWMODEL *psa;
	SVECTOR temp;



	if(nmeinfo[nme->type].spheres)
	{
		NME_SPHERE *sphere;
		psa = nme->psa;
		if(psa)
		{
			psa->position.vx= 0;//nme->pos.vx>>12;	//-100;
			psa->position.vy= 0;//nme->pos.vy>>12;	//-50;
			psa->position.vz= 0;//nme->pos.vz>>12;	//-100;
			psa->world.rotate.vx = nme->xa;
			psa->world.rotate.vy = nme->ya;
			psa->world.rotate.vz = nme->za;
		}

		for(sphere = nmeinfo[nme->type].spheres; sphere; sphere = sphere->next)
		{
			VECTOR temp2;

			EnemyRotatePoint(nme, &sphere->pos, &temp2, sphere->seg);

			temp.vx = temp2.vx + (nme->pos.vx >> 12);
			temp.vy = temp2.vy + (nme->pos.vy >> 12);
			temp.vz = temp2.vz + (nme->pos.vz >> 12);
			Draw_RadCheckRing2(&temp, sphere->r, 0x400080);
		}
	}
	else
	{
		temp.vx = nme->pos.vx>>12;
		temp.vy = nme->pos.vy>>12;
		temp.vz = nme->pos.vz>>12;

		Draw_RadCheckRing2(&temp, nmeinfo[nme->type].radius, 0x400080);
	}

/*
	{
		int i;
		NEWMODEL *psa;

		DB("========Model seg thang=========\n");
		psa = nme->psa;

		for(i = 0; i < psa->numberofsegments; i++)
		{
			DB("seg %i = %s\n",i,psa->segmenttable[i]->meshdata->name);
		}
	}
*/
}
#endif

void Draw_Enemies()
{
	ENEMYPOS *enemy;
	NEWMODEL *psa;
	int r;

// Part one - Check out which enemies are visible
	for(enemy = nme_list; enemy; enemy = enemy->next)
	{
		if(enemy->flags & NMEFLAG_ACTIVE)
		{
// Frogging is now a permamant type change
// (or if it changes, it'll be done with a temporary enemy)
/*
			if(enemy->flags & NMEFLAG_FROGGED)
				r = nmeinfo[NME_FROG].radius;
			else
*/
				r = nmeinfo[enemy->type].radius;

			if(RadiusCheck4096(&enemy->pos, r << 12,nmeinfo[enemy->type].visdist))
			{

				enemy->flags |= NMEFLAG_VISIBLE;
			}
			else
			{
				enemy->flags &= ~NMEFLAG_VISIBLE;
			}
		}
		else
		{
			enemy->flags &= ~NMEFLAG_VISIBLE;
		}
	}

// Part two - Draw the ones that were visible

	for(enemy = nme_list; enemy; enemy = enemy->next)
	{
		if(enemy->flags & NMEFLAG_VISIBLE)	// non-active will be non-visible
		{
#ifdef ENEMY_SHOW_VIS_SPHERES
			EnemyShowVisSphere(enemy);
#endif
#ifdef ENEMY_SHOW_COLL_SPHERES
			EnemyShowCollSphere(enemy);
#endif
/*
			if(enemy->flags & NMEFLAG_FROGGED)
			{
				psa = nmeFrogModel->psa;
				psa->position.vx= enemy->pos.vx>>12;
				psa->position.vy= enemy->pos.vy>>12;
				psa->position.vz= enemy->pos.vz>>12;
				psa->world.rotate.vx = enemy->xa;
				psa->world.rotate.vy = enemy->ya;
				psa->world.rotate.vz = enemy->za;

		 		objectSetAnimation(psa,30);	// start of the idle anim
				objectDraw(psa);
			}
			else
*/
			{
				if(enemy->type == BULLET_ENEMY_EGG)
				{
					VERT temp;
					temp.vx = enemy->pos.vx >> 12;
					temp.vy = enemy->pos.vy >> 12;
					temp.vz = enemy->pos.vz >> 12;

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

					if(enemy->doing == 0)
					{
						Print3DSprite(chuck_egg_sprite + enemy->ticker,&temp,256,0x808080);
					}
					else
					{
						Print3DSprite(chuck_splat_sprite + enemy->ticker,&temp,256,0x808080);
					}
				}
				else if(enemy->type == BULLET_ENEMY_SPRAY)
				{
					VERT temp;
					temp.vx = enemy->pos.vx >> 12;
					temp.vy = enemy->pos.vy >> 12;
					temp.vz = enemy->pos.vz >> 12;

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

//					Print3DSprite(joff_spray_sprite,&temp,128,0x808080);
					Print3DAlphaSprite(joff_spray_sprite,&temp,128,0x40606060);
				}
				else if(enemy->type == BULLET_ENEMY_FLAMES)
				{
					VERT temp;
					temp.vx = enemy->pos.vx >> 12;
					temp.vy = enemy->pos.vy >> 12;
					temp.vz = enemy->pos.vz >> 12;

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

//					Print3DSprite(SPR_firebbase+enemy->doing,&temp,512,0xc0c0c0 | P3DS_SEMI);
					Print3DSprite(SPR_firebbase+enemy->doing,&temp,256,0xc0c0c0 | P3DS_SEMI);
				}
				else if(enemy->type == BULLET_ENEMY_STEAM)
				{
					VERT temp;
					temp.vx = enemy->pos.vx >> 12;
					temp.vy = enemy->pos.vy >> 12;
					temp.vz = enemy->pos.vz >> 12;

  					gte_SetRotMatrix(&GsWSMATRIX);
  					gte_SetTransMatrix(&GsWSMATRIX);
					switch(enemy->n_points)
					{
					case 0:	// steam
						Print3DSprite(SPR_smokebase+enemy->doing,&temp,512,0xc0c0c0 | P3DS_ADD);
						break;
					case 1:	// smoke
						Print3DSprite(SPR_smokebase+enemy->doing,&temp,600,0xc0c0c0 | P3DS_SUB);
						break;
					}
				}
				else if(enemy->type == BULLET_ENEMY_UFOZAP)
				{
					VERT temp;
					temp.vx = enemy->pos.vx >> 12;
					temp.vy = enemy->pos.vy >> 12;
					temp.vz = enemy->pos.vz >> 12;

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

//					Print3DSprite(SPR_fardabase+enemy->doing,&temp,512,0x404040 | P3DS_ADD);

//					Print3DSprite(SPR_fardsbase + enemy->doing,&temp,256,0x808080 | P3DS_ADD);
//					Print3DSprite(SPR_fardabase + enemy->doing,&temp,256,0x808080 | P3DS_SUB);
					Print3DSprite(SPR_fardsbase + enemy->doing,&temp,256,((int)(enemy->extras)) | P3DS_ADD);
					Print3DSprite(SPR_fardabase + enemy->doing,&temp,256,0x404040 | P3DS_SUB);
				}
				else if(enemy->type == PICKUP_ENEMY_MRTIP)
				{
					VERT temp;
					SVECTOR v;

					temp.vx = enemy->pos.vx >> 12;
					temp.vy = (enemy->pos.vy+enemy->vel.vx) >> 12;
					temp.vz = enemy->pos.vz >> 12;

  					gte_SetRotMatrix(&GsWSMATRIX);
  					gte_SetTransMatrix(&GsWSMATRIX);
					if(enemy->doing == 1)
					{
						Print3DAlphaSprite(SPR_mrtipbase + (enemy->ticker>>4),&temp,150,0x40404040);
					}
					else
					{
						Print3DSprite(SPR_mrtipbase + (enemy->ticker>>4),&temp,150,0x808080);
					}

					v.vx = temp.vx;
					v.vz = temp.vz;
					v.vy = (enemy->pos.vy>>12) + (getHeightAt(enemy->pos.vx,enemy->pos.vy,enemy->pos.vz)>>12);	// tbd - precalc

					Draw_RawShadow(&v, 5, 0x303030,-1);

				}
				else if(enemy->type == HOOP_ENEMY)
				{
					VECTOR temp;
					temp.vx = enemy->pos.vx >> 12;
					temp.vy = enemy->pos.vy >> 12;
					temp.vz = enemy->pos.vz >> 12;

  					gte_SetRotMatrix(&GsWSMATRIX);
  					gte_SetTransMatrix(&GsWSMATRIX);
					drawFlatPolygon(hoop_sprite, &temp, enemy->n_points, (activeframe * 191) & 4095, P3DS_ADD | 0x000080);
					drawFlatPolygon(hoop_sprite, &temp, enemy->n_points, (activeframe * 191) & 4095, P3DS_SUB | 0x505050);

					drawFlatPolygon(hoop_sprite, &temp, -enemy->n_points, (activeframe * 153) & 4095, P3DS_ADD | 0x008000);
					drawFlatPolygon(hoop_sprite, &temp, -enemy->n_points, (activeframe * 153) & 4095, P3DS_SUB | 0x505050);

					drawFlatPolygon(hoop_sprite, &temp, enemy->n_points, (activeframe *  110) & 4095, P3DS_ADD | 0x803000);
					drawFlatPolygon(hoop_sprite, &temp, enemy->n_points, (activeframe *  110) & 4095, P3DS_SUB | 0x505050);
				}
				else  if(enemy->psa)
				{
					psa = enemy->psa;


					psa->position.vx= enemy->pos.vx>>12;	//-100;
					psa->position.vy= enemy->pos.vy>>12;	//-50;
					psa->position.vz= enemy->pos.vz>>12;	//-100;

					psa->world.rotate.vx = enemy->xa;
					psa->world.rotate.vy = enemy->ya;
					psa->world.rotate.vz = enemy->za;

					switch(enemy->type)
					{
						case BLANK_ENEMY:
							break;

						case PICKUP_ENEMY_STUN:
						case PICKUP_ENEMY_DEATH:
						case PICKUP_ENEMY_FROGGY:
						case PICKUP_ENEMY_SUCTION:
						case PICKUP_ENEMY_HERCULES:
						case PICKUP_ENEMY_SPEEDUP:
						case PICKUP_ENEMY_ROTORBLADES:
						case PICKUP_ENEMY_BOWLINGBALL:
						case PICKUP_ENEMY_POWERBALL:
						case PICKUP_ENEMY_BALLBEARING:
						case PICKUP_ENEMY_BEACHBALL:
						case PICKUP_ENEMY_SNOWBALL:
						case PICKUP_ENEMY_CRYSTALBALL:
						case PICKUP_ENEMY_BOOMERANGBALL:
						case PICKUP_ENEMY_VANISHBALL:

							if(enemy->doing != 2)
							{
		  						objectSetAnimation(psa,0);
//								objectSegmentSort(psa);
								objectDraw(psa);
							}

							break;

						case EVIL_ROBOT:
						{
// 4000's too far. so is 3k
  							gte_SetRotMatrix(&GsWSMATRIX);
  							gte_SetTransMatrix(&GsWSMATRIX);
							if(!RadiusCheck4096(&enemy->pos, (nmeinfo[enemy->type].radius) << 12,2000))
							{
//								DB("Forcing Robot to be closer\n");
								modelctrl.depthoveride = MAXOTZ-70;
							}

							objectSetAnimation(psa, animate(&enemy->anim));
							objectDrawWithShadow(psa,200,100,0);
							modelctrl.depthoveride = 0;

							break;
						}

						case CA_PIE:
						{
							objectSetAnimation(psa, 0);
							objectDrawWithShadow(psa,5,5,0);
							break;
						}

// For all those non-animated thangs..
/*						
						case SPIDERBOMB:
							objectSetAnimation(psa, 1);
							objectSegmentSort(psa);
							objectDraw(psa);
							break;
*/

						case LADYBIRD:
						case BULLET_ENEMY_STING:
						case BULLET_ENEMY_CLAW:
						case WILLY_FIREBALL_ENEMY:
						case ROBOT_MISSILE:
						case ROBOT_BOLT:
						case ROBOT_FRAGMENT:
							objectSetAnimation(psa, 0);
							objectDraw(psa);
							break;

						case KIRK_BUBBLE1_ENEMY:
						case KIRK_BUBBLE2_ENEMY:
							psa->globalscale.vx = enemy->pathvectors[0].vx;
							psa->globalscale.vy = enemy->pathvectors[0].vy;
							psa->globalscale.vz = enemy->pathvectors[0].vz;
//							DB("nme %8x, scales = %d %d %d\n",(int)&enemy->pathvectors[0],psa->globalscale.vx,psa->globalscale.vy,psa->globalscale.vz);
							objectSetAnimation(psa, 0);
							objectDrawWithShadow(psa,20,6,0);

							break;

						case SELWYN_FISH:
							objectSetAnimation(psa, animate(&enemy->anim));
							if(enemy->doing == -1)
							{
								objectDraw(psa);
							}
							else
							{
								objectDrawWithShadow(psa,nmeinfo[SELWYN_FISH].radius,10,0);
							}
							break;


						case CANNONBALL:
						{
							IQUATERNION *qRot = (void *)&enemy->pathvectors[0];
							IQUATERNION tempQ;


							if(gameCtrl.gameActive==FALSE)
								ActorStartRoll(psa,0,0);
							else
								ActorStartRoll(psa,enemy->vel.vx,enemy->vel.vz);

							GetQuaternionFromRotation(&tempQ,&psa->world.qRotVel);
							QuaternionMultiply(qRot,qRot,&tempQ);

							psa->world.numRotateKeys=1;
							psa->world.rotatekeys->vect.x=qRot->x;
							psa->world.rotatekeys->vect.y=qRot->y;
							psa->world.rotatekeys->vect.z=qRot->z;
							psa->world.rotatekeys->vect.w=qRot->w;

							psa->world.rotate.vx = 0;
							psa->world.rotate.vy = 0;
							psa->world.rotate.vz = 0;
							objectSetAnimation(psa, 0);
							objectDraw(psa);
							break;
						}

// Anything that flies or jumps should have a shadow
						case LIONFISH:
						case CHESTER:
						case YOOFOW:
						case OPEC:
						case CRUMPET:
						case BOVVA:
						case DENNIS:
						case CHUCK:
						case MIKE:
						case SAMTEX:

						case SWISH:
							objectSetAnimation(psa, animate(&enemy->anim));
							objectDrawWithShadow(psa,nmeinfo[enemy->type].radius,8,0);
							break;

						case BUGLE:
						{
							int frame;
							frame = animate(&enemy->anim);

							psa = bugleInsideModel->psa;
							psa->position.vx= enemy->pos.vx>>12;
							psa->position.vy= enemy->pos.vy>>12;
							psa->position.vz= enemy->pos.vz>>12;

							psa->world.rotate.vx = enemy->xa;
							psa->world.rotate.vy = enemy->ya;
							psa->world.rotate.vz = enemy->za;
							objectSetAnimation(psa, frame);
//							objectSegmentSort(psa);

							if(BallCtrl.ot_depth != OFFSCREEN_DEPTH && enemy->doing == BUGLE_WITH_BALL)
							{
								modelctrl.depthoveride = BallCtrl.ot_depth+2;	// inside model is further away
							}
							objectDraw(psa);

							psa = bugleOutsideModel->psa;
							psa->position.vx= (enemy->pos.vx>>12);
							psa->position.vy= enemy->pos.vy>>12;
							psa->position.vz= enemy->pos.vz>>12;

							psa->world.rotate.vx = enemy->xa;
							psa->world.rotate.vy = enemy->ya;
							psa->world.rotate.vz = enemy->za;
							objectSetAnimation(psa, frame);
//							objectSegmentSort(psa);
							if(BallCtrl.ot_depth != OFFSCREEN_DEPTH && enemy->doing == BUGLE_WITH_BALL)
								modelctrl.depthoveride = BallCtrl.ot_depth-2;	// outside model is nearer
							objectDrawWithShadow(psa,nmeinfo[BUGLE].radius,16,0);

							modelctrl.depthoveride = 0;
							break;
						}

						case CA_GLOVE:
						{
							int frame;

							frame = animate(&enemy->anim);
							objectSetAnimation(psa, frame);

// The glove has it's "centre" right at the back, and consequently has a habit of sorting behind the floor
							modelctrl.depthoveride = 80;
							objectDraw(psa);
							modelctrl.depthoveride = 0;
							break;
						}


						case BABY_SPANK:
						{
							int scale;

							if(enemy->flags & NMEFLAG_FROGGED)
							{
								psa = nmeFrogModel->psa;
								psa->position.vx= enemy->pos.vx>>12;
								psa->position.vy= enemy->pos.vy>>12;
								psa->position.vz= enemy->pos.vz>>12;
								psa->world.rotate.vx = enemy->xa;
								psa->world.rotate.vy = enemy->ya;
								psa->world.rotate.vz = enemy->za;

//		 						objectSetAnimation(psa,30);	// start of the idle anim
								enemy->animinfo.segInfo = nmeFrogModel->seg;

								objectSetAnimation(psa, animate(&enemy->anim));
								objectDraw(psa);
							}
							else
							{
//baby spanks are temporarily frogged at the start of the level
								scale = nmeinfo[BABY_SPANK].scale;
								if(enemy->doing == 0)
								{
									scale = scale * enemy->ticker / BABY_GROWTIME;
								}
								if(scale < 10) scale = 10;
								psa->globalscale.vx=scale;
								psa->globalscale.vy=scale;
								psa->globalscale.vz=scale;
								enemy->animinfo.segInfo = babySpankModel->seg;

								objectSetAnimation(psa, animate(&enemy->anim));
	//								objectSegmentSort(psa);
								objectDraw(psa);
							}
							break;
						}

						case BUTTERFLY:	// deal with those silly enemies that are defined pointing backwards
//						case DRAGFLY:	// looks like the artists have turned it round, too
							psa->world.rotate.vy = psa->world.rotate.vy ^ 0x800;
							objectSetAnimation(psa, animate(&enemy->anim));
							objectDraw(psa);
							break;



						case CAMEO_ENEMY:
							if(level == SPACEBOSS2)
							{
								modelctrl.depthoveride = 20;	// Explosion sorting
							}
							if(enemy->animinfo.segInfo)
							{
								objectSetAnimation(psa, animate(&enemy->anim));
								objectDraw(psa);
							}
							else
							{
		 						objectSetAnimation(psa,0);
								objectDraw(psa);
							}

							if(level == SPACEBOSS2)
							{
								modelctrl.depthoveride = 0;
							}

							break;


// note that the default case is prone to screwing up now we've got some animated bullets
// (specifically on the space boss)


						default:
							if(enemy->animinfo.segInfo)
							{
								objectSetAnimation(psa, animate(&enemy->anim));
								objectDraw(psa);
							}
							else
							{
		 						objectSetAnimation(psa,0);
								objectDraw(psa);
							}
							break;
					}
				}
	#ifdef PLACEHOLDERS
				else
				{
					sprctrl.rotate = (frame<<8) & 4095;
					Print3DSprite(cardbase,enemy->pos.vx>>12,enemy->pos.vy>>12,enemy->pos.vz>>12,256,256);
					sprctrl.rotate = 0;
				}
	#endif
			}
		}
		else
		{	// not visible, but it's state machine might depend on the animation frame
			if(enemy->flags & NMEFLAG_ACTIVE)
			{
				psa = enemy->psa;
				if(psa && enemy->animinfo.segInfo)
				{
					animate(&enemy->anim);
	//				objectSetAnimation(psa, animate(&enemy->anim));
				}
			}
		}
	}
	bosses_Panel();
}

// ===================================================================================
//    Ball'n'Glove  ---  Enemy interaction
// ===================================================================================

// dvec is (nme sphere pos - ball pos)
// tbd - we need a frig to ensure that the ball never bounces straight up/down here...



ULONG	NMEPushesBall(ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	LONG	dist;
	LONG	rqd;
	VECTOR	temp;
	VECTOR  ivel;
	VECTOR  ovel;
	int dot;

//	rqd = ballColl.radius + (sphererad<<12);
	rqd = sphererad + (ballColl.radius >> 12);

	dist=Magnitude(dvec);

	// bodge to stop division by zero crash
	if(dist == 0)
	{
		dist = rqd/2;
		dvec->vy = rqd/2;
		dvec->vx = 0;
		dvec->vz = 0;
	}

	if (rqd>dist)
	{
		nmeBallPushVector.vx -= ((rqd-dist) * dvec->vx/dist) <<12;
		nmeBallPushVector.vy -= ((rqd-dist) * dvec->vy/dist) <<12;
		nmeBallPushVector.vz -= ((rqd-dist) * dvec->vz/dist) <<12;

// take a copy of the ball's velocity, & map it to use the same coordinate system (ie, flip y)
		ivel = ballVel;
		ivel.vy = -ivel.vy;

// find component of ball's vel into the sphere...
		temp = *dvec;
		MakeUnit(&temp);
		dot = ((ivel.vx>>12) * temp.vx) + ((ivel.vy>>12) * temp.vy) + ((ivel.vz>>12) * temp.vz);

// ...and bounce it...
		if(dot > 0)
		{
			ovel.vx = ivel.vx - (((dot << 1) * temp.vx)>>12);
			ovel.vy = ivel.vy - (((dot << 1) * temp.vy)>>12);
			ovel.vz = ivel.vz - (((dot << 1) * temp.vz)>>12);

			ballVel.vx =  ovel.vx;
			if (ovel.vy<0) ovel.vy=0;			// make sure it never gos down
			ballVel.vy = -(ovel.vy+(3*4096));	// add a bit of upwards vel
			ballVel.vz =  ovel.vz;
		}	
		return(TRUE);
	}
	return(FALSE);
}



ULONG	NMEShovesBall(ENEMYPOS *nme, VECTOR *dvec, int sphererad, int amount)
{
//	return NMEPushesBall(nme,dvec,sphererad);
	LONG	dist;
	LONG	rqd;
	VECTOR	temp;
	VECTOR  ivel;
	VECTOR  ovel;
	int dot;

//	rqd = ballColl.radius + (sphererad<<12);
	rqd = sphererad + (ballColl.radius >> 12);

	dist=Magnitude(dvec);
	if(dist == 0)
	{
		dist = rqd/2;
		dvec->vy = rqd/2;
		dvec->vx = 0;
		dvec->vz = 0;
	}

	if (rqd>dist)
	{
		nmeBallPushVector.vx -= ((rqd-dist) * dvec->vx/dist) <<12;
		nmeBallPushVector.vy -= ((rqd-dist) * dvec->vy/dist) <<12;
		nmeBallPushVector.vz -= ((rqd-dist) * dvec->vz/dist) <<12;

// take a copy of the ball's velocity, & map it to use the same coordinate system (ie, flip y)
		ivel = ballVel;
		ivel.vy = -ivel.vy;

// find component of ball's vel into the sphere...
		temp = *dvec;
		MakeUnit(&temp);
		dot = ((ivel.vx>>12) * temp.vx) + ((ivel.vy>>12) * temp.vy) + ((ivel.vz>>12) * temp.vz);

// ...and bounce it...
		if(dot > 0)
		{
			switch(BallCtrl.type)
			{
				case BALL_MODE_BOWLING:
					dot = dot * 2 + amount/4;
					break;

				default:
					dot = dot * 2 + amount;
					break;
			}

			ovel.vx = ivel.vx - ((dot * temp.vx)>>12);
			ovel.vy = ivel.vy - ((dot * temp.vy)>>12);
			ovel.vz = ivel.vz - ((dot * temp.vz)>>12);

			ballVel.vx =  ovel.vx;
			ballVel.vy = -ovel.vy;
			ballVel.vz =  ovel.vz;
		}	
		return(TRUE);
	}
	return(FALSE);
}

#ifdef BALL_HURTPROOF
void NMEHurtsBall(ENEMYPOS *nme, VECTOR *dvec, int sphererad);
#define NMEHurtsBall(nme, dvec, sphererad) NMEPushesBall(nme,dvec,sphererad)
#else
void NMEHurtsBall(ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{


	VECTOR check;
	int ballNmeDist;
	int distCheck;

	if (GloveCtrl.cameoDisableDamage) return;	// Added to Tom to stop glove/ball gtting hurt in cam cases

	distCheck=ballColl.radius + (sphererad<<12);

	if (BallCtrl.type==BALL_MODE_BOWLING || BallCtrl.type==BALL_MODE_BEARING)
	{
		NMEPushesBall(nme,dvec,sphererad);// these types not hurt by nmes
		return;
	}

	ballNmeDist=Magnitude(dvec);

	if (distCheck>ballNmeDist)
	{
		check = *dvec;
		ballColl.preVel.vx = ballColl.pVel->vx;
		ballColl.preVel.vy = ballColl.pVel->vy;
		ballColl.preVel.vz = ballColl.pVel->vz;
		BallApplySquash();
		MakeUnit(&check);
		ScaleVector(&check,HITMOVESPEED);

		ballVel.vx += check.vx;
		ballVel.vy += check.vy;
		ballVel.vz += check.vz;


		BallCtrl.hurt=TRUE;
		BallCtrl.health--;
		if (BallCtrl.type==BALL_MODE_CRYSTAL)
		{
			BallCtrl.health=0;// crystal killed by 1 hit
		}
		BallCtrl.nextHitCount=20;	// stops bit getting hurt for 20 ticks
	}
}
#endif

VECTOR nmeGlovePushVector = {0,0,0};
VECTOR nmeBallPushVector = {0,0,0};


// note - strictly speaking it should be a %ge of (enemy moves / glove moves)
void NMEPushesGlove(ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	LONG	dist;
	int rqd;
	dist = Magnitude(dvec);

	rqd = sphererad + (GloveCtrl.radius >> 12);

// If we're dead-centre of the pushing sphere, move us straight up
	if(dist == 0)
	{
		dist = rqd/2;
		dvec->vy = rqd/2;
		dvec->vx = 0;
		dvec->vz = 0;
	}
	if(rqd > dist)	// final safety check to make sure we don't get sucked into things
	{
		nmeGlovePushVector.vx -= ((rqd-dist) * dvec->vx/dist) <<12;
		nmeGlovePushVector.vy -= ((rqd-dist) * dvec->vy/dist) <<12;
		nmeGlovePushVector.vz -= ((rqd-dist) * dvec->vz/dist) <<12;
	}
}

void NMEPushedBySphere(ENEMYPOS *nme, VECTOR *spherepos, int total_rad)
{
	LONG	dist;
	VECTOR dvec;

	dvec.vx = (nme->pos.vx - spherepos->vx)>>12;
	dvec.vy = (nme->pos.vy - spherepos->vy)>>12; 
	dvec.vz = (nme->pos.vz - spherepos->vz)>>12;

	dist = Magnitude(&dvec);

	if(dist == 0)
	{
		dist = total_rad/2;
		dvec.vy = total_rad/2;
		dvec.vx = 0;
		dvec.vz = 0;
	}

	if(total_rad > dist && dist != 0)
	{
		nme->pos.vx += (((total_rad-dist)/2) * dvec.vx/dist) <<12;
		nme->pos.vy += (((total_rad-dist)/2) * dvec.vy/dist) <<12;
		nme->pos.vz += (((total_rad-dist)/2) * dvec.vz/dist) <<12;
	}
}


void NMEPushedByGlove(ENEMYPOS *nme, VECTOR *dvec, int sphererad, int amount)
{
	LONG	dist;
	VECTOR	temp;
	int rqd;
	dist = Magnitude(dvec);
	rqd = sphererad + (GloveCtrl.radius >> 12);

	if(dist == 0)
	{
		dist = rqd/2;
		dvec->vy = rqd/2;
		dvec->vx = 0;
		dvec->vz = 0;
	}
	
	if(rqd > dist)	// final safety check to make sure we don't get sucked into things
	{
		nmeGlovePushVector.vx -= ((rqd-dist) * dvec->vx/dist) <<12;
		nmeGlovePushVector.vy -= ((rqd-dist) * dvec->vy/dist) <<12;
		nmeGlovePushVector.vz -= ((rqd-dist) * dvec->vz/dist) <<12;
	}

	temp = *dvec;
	MakeUnit(&temp);
	nme->vel.vx += amount * temp.vx / 4096;
	nme->vel.vy += amount * temp.vy / 4096;
	nme->vel.vz += amount * temp.vz / 4096;
}

void NMEPushedByBall(ENEMYPOS *nme, VECTOR *dvec, int sphererad, int amount)
{
	LONG	dist;
	VECTOR	temp;
	int rqd;
	dist = Magnitude(dvec);
	rqd = sphererad + (ballColl.radius >> 12);


// For a start...
	NMEPushesBall(nme,dvec,sphererad);

/*
	if(rqd > dist)	// final safety check to make sure we don't get sucked into things
	{
		nmeBallPushVector.vx -= ((rqd-dist) * dvec->vx/dist) <<12;
		nmeBallPushVector.vy -= ((rqd-dist) * dvec->vy/dist) <<12;
		nmeBallPushVector.vz -= ((rqd-dist) * dvec->vz/dist) <<12;
	}
*/

	switch(BallCtrl.type)
	{
		case BALL_MODE_BOWLING:
			amount = amount / 2;
			break;
	}

	temp = *dvec;
	MakeUnit(&temp);
	nme->vel.vx += amount * temp.vx / 4096;
	nme->vel.vy += amount * temp.vy / 4096;
	nme->vel.vz += amount * temp.vz / 4096;
}


void NMEHurtsGloveDirect()
{
// For whatever reason, the blastring is calling here repeatedly,
// which means "glovectrl.hurtflag" is getting zeroed somewhere when it shouldn't be.
// (without this line, you get zombified by blastrings)

	if(GloveCtrl.deathType)
		return;

	if(handpower_timer!=0 && handpower_type == SPELL_ROTORBLADES)
	{
		handpower_timer = 1;
		handPowerUpdate();
	}

	InputState.aStateHoldTime=FALSE;
	InputState.aStateChange=FALSE;

	InputState.bStateHoldTime=FALSE;
	InputState.bStateChange=FALSE;

	InputState.cStateHoldTime=FALSE;
	InputState.cStateChange=FALSE;

	if(!GloveCtrl.hurtFlag)
	{

	GloveCtrl.flags=GLOVEMOVE;
	BallCtrl.ballStopMove=FALSE;
	GloveCtrl.ballWithHand=FALSE;
	InputState.throwWhenAble=FALSE;
	GloveCtrl.throwDelay=0;
//	if(!GloveCtrl.hurtFlag)
//	{
		GloveCtrl.hurtFlag=TRUE;
		GloveCtrl.deathType=HURTFALL;
	}
}
void NMEHurtsGlove(ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	NMEHurtsGloveDirect();

	if(!GloveCtrl.hurtFlag)
	{
		GloveCtrl.hurtFlag=TRUE;
		GloveCtrl.deathType=HURTFALL;
	}
	else
	{
		NMEPushesGlove(nme,dvec,sphererad);
	}
}

void nmeDismountDenis(ENEMYPOS *nme)
{
//	nme->doing = -1;
	nme->doing = DENIS_DISMOUNT;
	nme->ticker=20;
}

void nmeDismountSwish(ENEMYPOS *nme)
{
	nme->doing = SWISH_DISMOUNT;
	nme->ticker = 60;
}

// ============= "Ball/Glove <-> Spherical bit of enemy" interaction routines ============


void DenisTintRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
		if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)	// dealt with when we come to the ball interact
		{
		}
		else
		{
			if(nme->doing ==DENIS_DISMOUNT)
			{
			}
			else
			{
				if(nme->doing != DENIS_GLOVED)
				{
					handMountDenis(nme);
					nme->doing = DENIS_GLOVED;
				}
			}
		}
	}
	else
	{
		NMEPushesBall(nme,dvec,sphererad);
	}
}

void DenisBintRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
		NMEPushesGlove(nme,dvec,sphererad);
	}
	else
	{
		NMEPushesBall(nme,dvec,sphererad);
	}
}


void SwishIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
// doh! (actually,we're not calling the collision code whan you're swished at the moment
// tbd - when you're swished, you're essentially solid, or harmful when attacking
		if (nme->doing == SWISH_GLOVED)
			return;

// Commented out mountable swish, coz it ain't going to happen in a hurry
/*
//		if (mag < SWISHATTACKRADIUS && nme->doing!=SWISH_DISMOUNT)
		if (nme->doing!=SWISH_DISMOUNT)
		{
// tbd - sneak up from behind check
			{
//					printf("MOUNTING!");
				handMountSwish(nme);
				nme->doing = SWISH_GLOVED;
			}
		}

*/
		HardBastRou(with,nme,dvec,sphererad);



	}
	else
	{
		if(BallCtrl.powerwhack)
		{
			PowerWhackHandler(nme,1);
			return;
		}

		NMEPushesBall(nme,dvec,sphererad);
	}
}

void SwordIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
		if (nme->doing == SWISH_GLOVED)
			return;
//		if (mag < SWISHATTACKRADIUS && nme->doing!=SWISH_DISMOUNT)
		if (nme->doing==SWISH_DISMOUNT)
		{
			NMEPushesGlove(nme,dvec,sphererad);
			return;
		}
	}
	HardBastRou(with,nme,dvec,sphererad);
}



// Going to the next level, dealing with level-opening flags...
// Set the dropout flag yourself
void NextLevelBasics()
{
	gameCtrl.nextLevel = LevelInfo[level].nextlevel;
	if(gameCtrl.nextLevel >= HUB1 && gameCtrl.nextLevel <= HUB8)
	{
		gameCtrl.nextLevel = gameCtrl.hub_to_use;
	}



// Deal with opening up portals

	if(level >= ATLANTIS1 && level <= SPACEBOSS2)
	{
		int sub;
		sub = level - ATLANTIS1;
		sub = sub - 5 * (world-ATLANTIS);

		if(world == SPACE && sub > 3)
			sub -= 1;	// spaceboss2, spacebonus

		if(sub < 4)	// 0,1,2,3 open the next one
		{
			GameState.levels_open[world-1] |= (1<<(sub+1));	// Flag the NEXT level of (1,2,3,boss,bonus) as being open
		}

// Note that we've beaten the boss, so we've got a ball to take to the hub
// If we just replayed an already-delivered boss, then the current gamestate is fine.
// Otherwise, we need to find any bosses whos crystals we haven't delivered, and flag them as unbeaten
		if (sub == 3 && !(GameState.levels_open[world-1] & LEVOPEN_CRYSTAL_DELIVERED))
		{
			int i;

			DB("Checking for Boss disable\n");
// If there are any OTHER worlds with undelivered crystals, flag them as needing their boss beating again
			for(i = 0; i < 6; i++)
			{
				if( (GameState.levels_open[i] & (LEVOPEN_BOSS_BEATEN + LEVOPEN_CRYSTAL_DELIVERED)) ==LEVOPEN_BOSS_BEATEN)
				{
					GameState.levels_open[i] &= ~ (LEVOPEN_BOSS_BEATEN + LEVOPEN_CRYSTAL_DELIVERED);
					DB("Disabling the boss-beaten state for world %d\n",i);
				}
			}
			GameState.levels_open[world-1] |= LEVOPEN_BOSS_BEATEN;
		}
	}
}







void PortIntRou(int with, ENEMYPOS *enemy, VECTOR *dpos, int sphererad)
{
	if(gameCtrl.dropOutFlag)
		return;

	if(with == 1 || (world == HUB && with == 0))
	{
// if the ball's in collision with it, check that the hand's connected to it

// (the other ball-level (prehistoric bonus) is dealy with in bosses.c)
		if(level == FEARBONUS)
		{
			if(!gameCtrl.dropOutFlag)
			{
				NextLevelBasics();
				gameCtrl.dropOutFlag=GAME_COMPLETED;
				MenuFadeOut();	// immediate fade
			}
		}

// note the hub check - "just the ball" is no good for the hub, coz it causes bugs

		if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand || (world == HUB && with == 0))
		{
			gameCtrl.nextLevel = TITLE;

			NextLevelBasics();


			switch(level)
			{
//			case WAYROOM:
// wayroom is a special case, with no portals, and is dealt with in bosses.c

			case HUB8:
			case HUB7:
			case HUB6:
			case HUB5:
			case HUB4:
			case HUB3:
			case HUB2:
			case HUB1:
				switch(enemy->tag)
				{
					case 99:	// doesn't exist at the mo
						gameCtrl.nextLevel = CAVE;
						break;

// note - these should go to the wayroom
					case 100:
//						gameCtrl.nextLevel = ATLANTIS1;
						gameCtrl.wayroom_world = ATLANTIS;
						gameCtrl.nextLevel = WAYROOM;	// tbd - sort out WHICH wayroom!
						break;
					case 101:
//						gameCtrl.nextLevel = PIRATES1;
						gameCtrl.wayroom_world = PIRATES;
						gameCtrl.nextLevel = WAYROOM;
						break;
					case 102:
//						gameCtrl.nextLevel = CARNIVAL1;
						gameCtrl.wayroom_world = CARNIVAL;
						gameCtrl.nextLevel = WAYROOM;
						break;
					case 103:	// 104 = sp
//						gameCtrl.nextLevel = PREHISTORIC1;
						gameCtrl.wayroom_world = PREHISTORIC;
						gameCtrl.nextLevel = WAYROOM;
						break;
					case 104:
//						gameCtrl.nextLevel = FEAR1;
						gameCtrl.wayroom_world = FORTRESS;
						gameCtrl.nextLevel = WAYROOM;
						break;
					case 105:
//						gameCtrl.nextLevel = SPACE2A;
						gameCtrl.wayroom_world = SPACE;
						gameCtrl.nextLevel = WAYROOM;
						break;
				}
				DB("Leaving hub:- Wayroom world set to %d\n",gameCtrl.wayroom_world);
				break;
			default:
				gameCtrl.nextLevel = LevelInfo[level].nextlevel;
				break;
			}

			if(gameCtrl.nextLevel >= HUB1 && gameCtrl.nextLevel <= HUB8)
			{
				gameCtrl.nextLevel = gameCtrl.hub_to_use;
//				gameCtrl.dropOutFlag=GAME_COMPLETED;
//				MenuFadeOut();	// immediate fade

// eg - portal on piboss,
// (the wayrooms are dealt with elsewhere
				if (!GloveCtrl.levelFinished)
					GloveCtrl.levelFinished=START;

			}
			else if(gameCtrl.nextLevel == WAYROOM)
			{
// tbd - we need to shove glover into a teleporting anim
//				GloveCtrl.padDisableTimer = 50;
				gameCtrl.dropOutFlag=GAME_COMPLETED;
				MenuFadeOut();	// immediate fade
			}
			else
			{
// glove spins ball. Ball vanishes in stars
// glove does woohoo anim
			//gameCtrl.dropOutFlag=GAME_COMPLETED;	// Hang around for the glove animation
				if (!GloveCtrl.levelFinished)
					GloveCtrl.levelFinished=START;
			}
		}
	}
}


/*
#define TELEPORT_BALL			(1<<0)
#define TELEPORT_HAND			(1<<1)
#define TELEPORT_NME			(1<<2)
#define TELEPORT_JOINED			(1<<3)
#define TELEPORT_ZEROVEL		(1<<4)
#define TELEPORT_GOTOPLATFORM	(1<<5)
#define TELEPORT_SWAP			(1<<6)
*/
void TeleIntRou(int with, ENEMYPOS *enemy, VECTOR *dpos, int sphererad)
{
	int flags;

	flags = enemy->n_points;

	if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
	{
		if(!(flags & TELEPORT_JOINED))
			return;

		enemy->doing = TELEPORT_WORKING_GLOVE;
		enemy->ticker = 0;
	}

	if(with == 0)
	{
		if(!(flags & TELEPORT_HAND))
			return;

		enemy->doing = TELEPORT_WORKING_GLOVE;
		enemy->ticker = 0;

	}
	else
	{
		if(!(flags & TELEPORT_BALL))
			return;

		enemy->doing = TELEPORT_WORKING_BALL;
		enemy->ticker = 0;
	}
}



// ok, sphere check's happened, but we need to check that the centre of the ball's crossed the plane of
// the hoop

void HoopIntRou(int with, ENEMYPOS *enemy, VECTOR *dpos, int sphererad)
{
	int c;
	ENEMYPOS *test;

	if(with == 1 && enemy->doing == HOOP_WAITING)
	{
		if(
//			   ((ballPos.vy < enemy->pos.vy) && (ballPos.vy - ballVel.vy >= enemy->pos.vy))	// up
//			||
//			   ((ballPos.vy >= enemy->pos.vy) && (ballPos.vy - ballVel.vy < enemy->pos.vy))	// down

			   ((ballPos.vy >= enemy->pos.vy) && (ballPos.vy + ballVel.vy < enemy->pos.vy))	// corrected for backwards ball y-vel
		  )
		{
			sfxPlay3D(globalFX, SFX_COLLECT_BONUS_3,&ballPos);
			enemy->doing = HOOP_COLLECTED;
			New_StarRingDebris(&enemy->pos, 15);

			c = 0;
			for(test=nme_list; test; test = test->next)
			{
				if(test == enemy)
				{
					lasthoopused = c;
					break;
				}
				if(test->type == HOOP_ENEMY)
				{
					c++;
				}
			}
		}
	}
}




// Bovva
void BovIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
		// if the glove is a fist, kill the bad guy
		if(GloveCtrl.action == HAND_SLAM|| GloveCtrl.action == HAND_SLAM2ST) // && Glover.animInfo->frame>=4)	// the frame check's a tad messy, but WTF?
		{
			EnemyKill(nme);
		}
		else if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
			if(!IsBallShield(dvec))
				DisconnectGloveFromBall(DISCONNECT_WHEN_JOINED | DISCONNECT_WHEN_BOUNCING | DISCONNECT_WHEN_WALKINGON);
			NMEShovesBall(nme,dvec,sphererad,12);
		}
		else
		{
			if(!IsBallShield(dvec))
				NMEHurtsGlove(nme,dvec,sphererad);
		}
	}
	else
	{
		if(BallCtrl.powerwhack)
		{
			PowerWhackHandler(nme,1);
			return;
		}
		if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
			DisconnectGloveFromBall(DISCONNECT_WHEN_JOINED | DISCONNECT_WHEN_BOUNCING | DISCONNECT_WHEN_WALKINGON);
		}
		NMEShovesBall(nme,dvec,sphererad,12);
	}
}
// Bovva's sting
void StingIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(nme->doing != 1)
		return;

	if(with == 0)
	{
		NMEHurtsGlove(nme, dvec, sphererad);
	}
	else
	{
		NMEHurtsBall(nme, dvec, sphererad);
	}

	nme->doing = 2;
	nme->ticker = 0;
}

void EggIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
		if(!IsBallShield(dvec))
			NMEHurtsGlove(nme, dvec, sphererad);
	}
	else
	{
		NMEHurtsBall(nme, dvec, sphererad);
	}
	nme->flags &= ~(NMEFLAG_ACTIVE + NMEFLAG_ENABLED);
}

void FBallIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
		if(!IsBallShield(dvec))
			NMEHurtsGlove(nme, dvec, sphererad);
	}
	else
	{
		NMEPushesBall(nme, dvec, sphererad);	// very light push
	}
}

void UZapIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
		if(!IsBallShield(dvec))
			NMEHurtsGlove(nme, dvec, sphererad);
	}
	else
	{
		NMEHurtsBall(nme, dvec, sphererad);
	}
}


// Judgement calls - Enemy-v-ball interaction based on ball type & relative velocity
/*
int EnemyAffectedByBall(ENEMYPOS *nme, VECTOR *dvec)
{
//	"Normal",
//	"Bowling",
//	"Power",
//	"Bearing",
//	"Beach",
//	"Snow",
//	"Crystal"
	static int powers[7] = {4096,8192,4096,5000,2048,8192,2048};

	VECTOR ivel;
	VECTOR temp;
	int dot;

	ivel = ballVel;
	ivel.vy = -ivel.vy;

	ivel.vy = ivel.vy >> 1;	// a bit messy, but the vertical outweighs the horizontal by quite a way

	temp = *dvec;
	MakeUnit(&temp);
	dot = ((ivel.vx>>12) * temp.vx) + ((ivel.vy>>12) * temp.vy) + ((ivel.vz>>12) * temp.vz);
	if(dot < 0)
		return 0;
	dot = (dot * powers[BallCtrl.type]) >>12;

	return(dot >> 12);	// returns roughly "7" for rubber ball, flat out
}
*/

// eg - Hub Critters
// (note - on the n64, they were immune to the ball

// ** Only call from the HAND interaction, not the BALL **
int IsBallShield(VECTOR *dvec)	// dvec = hand-nmesphere
{
	VECTOR dvec2;
	int eya;
	int dx,dz;

	dvec2.vx = ballPos.vx - glovePos.vx;
	dvec2.vy = ballPos.vy - glovePos.vy;
	dvec2.vz = ballPos.vz - glovePos.vz;

	if(!GloveCtrl.handOnBall &&!GloveCtrl.ballWithHand)
	{
//		printf("ibs - not with ball\n");
		return FALSE;
	}

	if(GloveCtrl.handOnBall)	// not shielded when "on" seems better
		return TRUE;

// do it with angles ?

	dx = (-dvec->vx)>>8;
	dz = (-dvec->vz)>>8;
	eya = (calc_angle( dx, dz) & 4095);
	eya = (eya - GloveCtrl.direction) & 4095;	// 0/0x1000 = same dirn, and safe

	if(eya < 0x400 || eya > 0x1000 - 0x400)
		return TRUE;
	else
		return FALSE;

}

int PowerWhackHandler(ENEMYPOS *nme, int stunfirst)
{
	if(!BallCtrl.powerwhack)
		return 0;

	if(nme->flags & NMEFLAG_STUNNED || !stunfirst)
	{
		EnemyKill(nme);
		ballPowerWhackReturn();
	}
	else
	{
		if(BallCtrl.type == BALL_MODE_BOWLING)
		{
			EnemyKill(nme);
		}
		else
		{
			EnemyStun(nme);
		}
		ballPowerWhackReturn();
	}
	return 1;
}


void FeebleRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{

	if(with == 0)
	{
		if(GloveCtrl.action == HAND_SLAM || GloveCtrl.action == HAND_SLAM2ST )
		{
			EnemyKill(nme);
		}
	}
	else
	{
		if(BallCtrl.type == BALL_MODE_BOWLING || (BallCtrl.powerwhack))
		{
			EnemyKill(nme);
		}
		else
		{
			EnemyStun(nme);
			NMEPushesBall(nme,dvec,sphererad);
		}
	}
}


// eg - mallet
void HarmlessRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
// if the glove is a fist, kill the bad guy
		if(GloveCtrl.action == HAND_SLAM|| GloveCtrl.action == HAND_SLAM2ST) // && Glover.animInfo->frame>=4)	// the frame check's a tad messy, but WTF?
		{
			EnemyKill(nme);
		}
		else
		{
			NMEPushesGlove(nme,dvec,sphererad);
		}
	}
	else
	{
		if(BallCtrl.powerwhack)
		{
			PowerWhackHandler(nme,1);
			return;
		}
		{
			NMEPushesBall(nme,dvec,sphererad);
		}
		return;
	}
}


// eg - mallet
void SplatRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
// if the glove is a fist, kill the bad guy
		if(GloveCtrl.action == HAND_SLAM|| GloveCtrl.action == HAND_SLAM2ST) // && Glover.animInfo->frame>=4)	// the frame check's a tad messy, but WTF?
		{
			EnemyKill(nme);
		}
		else if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
			if(!IsBallShield(dvec))
				DisconnectGloveFromBall(DISCONNECT_WHEN_JOINED | DISCONNECT_WHEN_BOUNCING | DISCONNECT_WHEN_WALKINGON);
			NMEPushesBall(nme,dvec,sphererad);
		}
		else
		{
			if(nme->flags & NMEFLAG_STUNNED)
			{
				NMEPushesGlove(nme,dvec,sphererad);
			}
			else
			{
	// otherwise, hurt the glove & push it out
				if(!IsBallShield(dvec))
					NMEHurtsGlove(nme,dvec,sphererad);

			}
		}
	}
	else
	{
		if(BallCtrl.powerwhack)
		{
			PowerWhackHandler(nme,1);
			return;
		}
		{
			VECTOR temp;
			temp = ballPos;
			if (BallCtrl.type==BALL_MODE_BOWLING && !GloveCtrl.handOnBall && !GloveCtrl.ballWithHand)
			{
	// if the ball's moving towards us, stun us
				NMEPushesBall(nme,dvec,sphererad);
			}
			else
			{
				NMEHurtsBall(nme,dvec,sphererad);
			}
		}
		return;
	}
}



// eg - chester
void HardBastRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
		if(GloveCtrl.action == HAND_SLAM || GloveCtrl.action == HAND_SLAM2ST)
		{
			EnemyStun(nme);
			NMEPushesGlove(nme,dvec,sphererad);
		}
		else if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
			if(!IsBallShield(dvec))
				DisconnectGloveFromBall(DISCONNECT_WHEN_JOINED | DISCONNECT_WHEN_BOUNCING | DISCONNECT_WHEN_WALKINGON);
			NMEPushesBall(nme,dvec,sphererad);
		}
		else
		{

			if(nme->flags & NMEFLAG_STUNNED)
			{
				NMEPushesGlove(nme,dvec,sphererad);
			}
			else
			{
	// otherwise, hurt the glove & push it out
				GloveCtrl.hurtFlag=TRUE;
				GloveCtrl.deathType=HURTFALL;
			}
		}
	}
	else
	{
		if(BallCtrl.powerwhack)
		{
			PowerWhackHandler(nme,1);
		}
		else
		{
			if (BallCtrl.type==BALL_MODE_BOWLING && !GloveCtrl.handOnBall && !GloveCtrl.ballWithHand)
			{
	// if the ball's moving towards us, stun us
				NMEPushesBall(nme,dvec,sphererad);
			}
			else
			{
				NMEHurtsBall(nme,dvec,sphererad);
			}
		}
		return;
	}

}

// eg - the bottom half of GeneralWu
void PushableRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
	if(with == 0)
	{
		if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
		}
		else
		{
			NMEPushesGlove(nme,dvec,sphererad);
		}
	}
	else
	{
		if(BallCtrl.powerwhack)
		{
			PowerWhackHandler(nme,1);
			return;
		}
		{
			NMEPushesBall(nme,dvec,sphererad);
		}
	}
}

void VeryPushableRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad, int amount)
{
	if(with == 0)
	{
		if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
		}
		else
		{
			NMEPushedByGlove(nme,dvec,sphererad,amount);
		}
	}
	else
	{
		if(BallCtrl.powerwhack)
		{
			PowerWhackHandler(nme,1);
			return;
		}

		NMEPushesBall(nme,dvec,sphererad);
	}
}

// Cannonball
void CanIntRou(int with, ENEMYPOS *nme, VECTOR *dvec, int sphererad)
{
//	VeryPushableRou(with,nme,dvec,sphererad,1500);
	if(with == 0)
	{
		if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
		}
		else
		{
			NMEPushedByGlove(nme,dvec,sphererad,3000);
		}
	}
	else
	{
		if(BallCtrl.powerwhack)
		{
			PowerWhackHandler(nme,1);
			return;
		}


		if(GloveCtrl.handOnBall || GloveCtrl.ballWithHand)
		{
			NMEPushedByBall(nme,dvec,sphererad,3000);
		}
		else
		{
			NMEShovesBall(nme,dvec,sphererad,200);
		}
	}
}

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

// tbd - the tricky bit of spheres is those for GeneralWu & Swish, which move according to the anim's X angle
// question is, which bit of the nme can they check
int NmeSphereCheck(ENEMYPOS *nme, NME_SPHERE *sphere, int with)
{
	VECTOR vec;
	int r;
	int s;

/*
// xz position of sphere, adjusted for enemy's Y-angle
	if(sphere->pos.vx == 0 && sphere->pos.vz == 0)
	{
		vec.vx = 0;
		vec.vy = sphere->pos.vy;
		vec.vz = 0;
	}
	else
	{
		vec.vx = sphere->pos.vx;
		vec.vy = sphere->pos.vy;
		vec.vz = sphere->pos.vz;
	}

// full position of sphere, ajusted for enemy's X-angle
	vec.vx += (nme->pos.vx >> 12);
	vec.vy += (nme->pos.vy >> 12);
	vec.vz += (nme->pos.vz >> 12);
*/
//	printf("sphere %i\n",sphere->seg);
	EnemyRotatePoint(nme, &sphere->pos, &vec, sphere->seg);
	vec.vx += (nme->pos.vx>>12);
	vec.vy += (nme->pos.vy>>12);
	vec.vz += (nme->pos.vz>>12);

	if(with == 0)
	{
		vec.vx -= (glovePos.vx>>12);
		vec.vy -= (glovePos.vy>>12);
		vec.vz -= (glovePos.vz>>12);
		r = sphere->r + (GloveCtrl.radius >> 12);
	}
	else if(with == 1)
	{
		vec.vx -= (ballPos.vx>>12);
		vec.vy -= (ballPos.vy>>12);
		vec.vz -= (ballPos.vz>>12);
		r = sphere->r + (ballColl.radius >>12);
	}
	else
	{
		vec.vx -= (CannonCtrl.bulletPos[with-2].vx>>12);
		vec.vy -= (CannonCtrl.bulletPos[with-2].vy>>12);
		vec.vz -= (CannonCtrl.bulletPos[with-2].vz>>12);
		r = sphere->r + (ballColl.radius >>12);
	}

	if(vec.vx < -r || vec.vx > r)
		return 0;
	if(vec.vz < -r || vec.vz > r)
		return 0;
	if(vec.vy < -r || vec.vy > r)
		return 0;

	s = vec.vx * vec.vx + vec.vy * vec.vy + vec.vz * vec.vz;


//	printf("ok\n");

	if(s > r * r)
		return 0;

//	printf("calc dist = %d,%d,%d\n",s,r*r,r);

	if(sphere->interact)	// this should always be the case
	{
		sphere->interact(with,nme,&vec,sphere->r);
	}

	return 1;
}




void	CheckForNmeHitsGlove(void)
{
	ENEMYPOS *nme;
	VECTOR d;
	int r;

	nmeGlovePushVector.vx = 0;
	nmeGlovePushVector.vy = 0;
	nmeGlovePushVector.vz = 0;
	GloveCtrl.tipFlag=FALSE;


	for (nme = nme_list; nme; nme = nme->next)
	{
		if((nme->flags & NMEFLAG_ACTIVE) && !(nme->flags & NME_NO_INTER))	// && ! (nme->flags & NMEFLAG_STUNNED))
		{
// quick box test based on visible radii...
			d.vx = (glovePos.vx - nme->pos.vx)>>12;
			d.vy = (glovePos.vy - nme->pos.vy)>>12;
			d.vz = (glovePos.vz - nme->pos.vz)>>12;
			r = nmeinfo[nme->type].radius + (GloveCtrl.radius >> 12);
			if((d.vx < r && d.vx > -r) && (d.vy < r && d.vy > -r) && (d.vz < r && d.vz > -r))
			{
// time for the real thang
				if(nmeinfo[nme->type].spheres)
				{
					NME_SPHERE *sphere;
					for(sphere = nmeinfo[nme->type].spheres; sphere; sphere = sphere->next)
					{
						NmeSphereCheck(nme,sphere,0);
					}
				}
				else
				{
					static NME_SPHERE sphere = {NULL,NULL,{0,0,0},10,-1};
					sphere.r = nmeinfo[nme->type].radius;
					sphere.interact = nmeinfo[nme->type].interact;

					NmeSphereCheck(nme,&sphere,0);

				}
			}
		}
	}

	glovePos.vx += nmeGlovePushVector.vx;
	glovePos.vy += nmeGlovePushVector.vy;
	glovePos.vz += nmeGlovePushVector.vz;
}

void	CheckForNmeHitsBall(void)
{
	ENEMYPOS *nme;
	VECTOR d;
	int r;

	nmeBallPushVector.vx = 0;
	nmeBallPushVector.vy = 0;
	nmeBallPushVector.vz = 0;

	for (nme = nme_list; nme; nme = nme->next)
	{
		if((nme->flags & NMEFLAG_ACTIVE) && !(nme->flags & NME_NO_INTER))	// && ! (nme->flags & NMEFLAG_STUNNED))
		{
// quick box test based on visible radii...
			d.vx = (ballPos.vx - nme->pos.vx)>>12;
			d.vy = (ballPos.vy - nme->pos.vy)>>12;
			d.vz = (ballPos.vz - nme->pos.vz)>>12;
			r = nmeinfo[nme->type].radius + (ballColl.radius>>12);
			if((d.vx < r && d.vx > -r) && (d.vy < r && d.vy > -r) && (d.vz < r && d.vz > -r))
			{
// time for the real thang
				if(nmeinfo[nme->type].spheres)
				{
					NME_SPHERE *sphere;
					for(sphere = nmeinfo[nme->type].spheres; sphere; sphere = sphere->next)
					{
						NmeSphereCheck(nme,sphere,1);
					}
				}
				else
				{
					static NME_SPHERE sphere = {NULL,NULL,{0,0,0},10,-1};
					sphere.r = nmeinfo[nme->type].radius;
					sphere.interact = nmeinfo[nme->type].interact;
					NmeSphereCheck(nme,&sphere,1);
				}
			}
		}
	}
	if (BallCtrl.health==0)
	{
		GloveCtrl.hurtFlag=TRUE;
		GloveCtrl.deathType=DEADFROMBALLBURST;
	}

	if(BallCtrl.inside_bugle)
	{
		VECTOR temp;
		int dist;
		nme = BallCtrl.inside_bugle;

		temp.vx = nme->pos.vx - ballPos.vx;
		temp.vy = nme->pos.vy - ballPos.vy;
		temp.vz = nme->pos.vz - ballPos.vz;
		dist = Magnitude(&temp);

		if(dist > 4096 * BUGLE_SPHERE_RAD - ballColl.radius)
		{
			MakeUnit(&temp);


// bounce ball inwards

			ballVel.vx = temp.vx<<2;
			ballVel.vy = -temp.vy<<2;	// coz ballvel's upsidedown
			ballVel.vz = temp.vz<<2;


			temp.vx = temp.vx * (BUGLE_SPHERE_RAD-(ballColl.radius>>12));
			temp.vy = temp.vy * (BUGLE_SPHERE_RAD-(ballColl.radius>>12));
			temp.vz = temp.vz * (BUGLE_SPHERE_RAD-(ballColl.radius>>12));

			ballPos.vx = nme->pos.vx - temp.vx;
			ballPos.vy = nme->pos.vy - temp.vy;
			ballPos.vz = nme->pos.vz - temp.vz;

		}
	}


	ballPos.vx += nmeBallPushVector.vx;
	ballPos.vy += nmeBallPushVector.vy;
	ballPos.vz += nmeBallPushVector.vz;

}


void	CheckForNmeHitsSphere(VECTOR *pos, int radius, int id)
{
	ENEMYPOS *nme;
	VECTOR d;
	int r;


	nmeBallPushVector.vx = 0;
	nmeBallPushVector.vy = 0;
	nmeBallPushVector.vz = 0;

	for (nme = nme_list; nme; nme = nme->next)
	{
		if((nme->flags & NMEFLAG_ACTIVE) && !(nme->flags & NME_NO_INTER))	// && ! (nme->flags & NMEFLAG_STUNNED))
		{

// quick box test based on visible radii...
			d.vx = (pos->vx - nme->pos.vx)>>12;
			d.vy = (pos->vy - nme->pos.vy)>>12;
			d.vz = (pos->vz - nme->pos.vz)>>12;
			r = nmeinfo[nme->type].radius + (radius>>12);
			if((d.vx < r && d.vx > -r) && (d.vy < r && d.vy > -r) && (d.vz < r && d.vz > -r))
			{
// time for the real thang
				if(nmeinfo[nme->type].spheres)
				{
					NME_SPHERE *sphere;
					for(sphere = nmeinfo[nme->type].spheres; sphere; sphere = sphere->next)
					{
						NmeSphereCheck(nme,sphere,id);
					}
				}
				else
				{
					static NME_SPHERE sphere = {NULL,NULL,{0,0,0},10,-1};
					sphere.r = nmeinfo[nme->type].radius;
					sphere.interact = nmeinfo[nme->type].interact;
					NmeSphereCheck(nme,&sphere,id);
				}
			}
		}
	}

	pos->vx += nmeBallPushVector.vx;
	pos->vy += nmeBallPushVector.vy;
	pos->vz += nmeBallPushVector.vz;
}


/*************************************************************************************************/
// It's the enemy-stunned checker

void CheckForNmeHitsBlast(VECTOR *pos, int radius, int height)
{
	ENEMYPOS *nme;
	VECTOR check;
	int mag;
	int f;
	int nmer;
	int nmeh;

	for (nme = nme_list; nme; nme = nme->next)
	{
		if(nme->flags & NMEFLAG_ACTIVE)
		{
			f = nmeinfo[nme->type].flags;
			nmeh = nmer = (nmeinfo[nme->type].radius << 12);
			nmer += radius;
			nmeh += height;

			if(f & (BLASTRING_KILLS | BLASTRING_STUNS | BLASTRING_SPECIAL))	// even if already stunned. yes indeedy
			{
				check.vx = pos->vx - nme->pos.vx;
				check.vy = pos->vy - nme->pos.vy;
				check.vz = pos->vz - nme->pos.vz;

// spherical radiuscheck for now - should be a flattish cylinder... tbd
				if(    check.vx > -nmer && check.vx < nmer
					&& check.vz > -nmer && check.vz < nmer
					&& check.vy > -nmeh && check.vy < nmeh
					)
				{
//					printf("enemy %i withing blastbox\n",i);
					check.vy = 0;
// tbd - this shouldn't be doing square roots, dude... cf magnitude squared & all that...
					mag=Magnitude(&check);
					if(mag < nmer)
					{

//						printf("   enemy %i withing blastcylinder\n",i);

						if(f & BLASTRING_KILLS)
						{
							EnemyKill(nme);

						}
						if(f & BLASTRING_STUNS)
						{
//							printf("   enemy %i stunned\n",i);
							EnemyStun(nme);
						}

						switch(nme->type)
						{
							case CHUCK:
								CluckPushOffBall(nme);
								break;

							case FUMBLE:
								FumbleDropBall(nme);
								break;
							case SELWYN_CRAB:
								boss_FlipCancerOver(nme);
								break;
							case BABY_SPANK:
								BabyPushOffBall(nme);
								break;
							case SUCKER:
								SuckerDropsBall(nme);
								break;
							case CANNONBALL:
								StunCannonball(nme);
								break;
						}
					}
				}
			}
		}
	}
}


// (the actual "hit" is done in "CheckForFistHits", but needs moving here...





// =================================================
// used when firing froggy / death spells
ENEMYPOS * nmeFindEnemyToShootAt()
{
	ENEMYPOS *nme;
	SHORT eya;
	int dx,dz;
	ENEMYPOS *best = NULL;
	int best_d = 0x7fffffff;
	int this_d;

	for (nme = nme_list; nme; nme = nme->next)
	{
		if((nme->flags & NMEFLAG_ACTIVE) && (nmeinfo[nme->type].flags & NME_SPELLABLE))
		{
			dx = (glovePos.vx - nme->pos.vx)>>8;
			dz = (glovePos.vz - nme->pos.vz)>>8;
			eya = (calc_angle( dx, dz) & 4095);
			eya = (eya - GloveCtrl.direction) & 4095;

//fire at the nearest enemy that's roughly in front of us...

			if(eya < 512 || eya >= 4096 - 512)
			{
				this_d = dx*dx + dz*dz;
				if( this_d < best_d)
				{
					best_d = this_d;
					best = nme;
				}
			}
		}
	}
	return best;
}

