/******************************************************************************************
	CRTEST PS   (c) 1999-2001 ISL

	crate.c:			Chase sequence featuring Mrs Tweedy hanging from the airplane
******************************************************************************************/

#include "global.h"
#include "main.h"
#include "menu.h"
#include "map_draw.h"
#include "bff_load.h"
#include "debris.h"
#include "lights.h"
#include "timer.h"
#include "maths.h"
#include "overlays.h"
#include "map_view.h"
#include "global.h"
#include "overlays.h"
#include "collide.h"
#include "enemies.h"
#include "camera.h"
#include "dualshock.h"
#include "ranges.h"
#include "crate.h"
#include "dogchase.h"
#include "scenics.h"
#include "demo.h"
#include "loadsnd.h"
#include "gametext.h"
#include "credits.h"
#include "options.h"
#include "deth.h"
#include "sound.h"
#include "startup.h"
#include "lev_flow.h"

extern void levflowCrateSuccess(void);

#define SUBGAME_OVERLAYS

#undef NULL
#define NULL 0

FMA_WORLD2 *crate_fma_world;

#define CRATE_MINX -3000
#define CRATE_MAXX 4000
#define CRATE_ROPELENGTH 1350				// The total length of the rope
#define CRATE_SEGMENTLENGTH 75				// The length of each rope segment
#define CRATE_NUMLINKS (CRATE_ROPELENGTH / CRATE_SEGMENTLENGTH)	// The number of individual links in the rope
#define CRATE_AXEFREQUENCY 100				// The number of frames between axe throws
#define CRATE_MAXDAMAGE 193					// Once damage goes above this, you're dead

int cratetransp;

int bouncedata[21] = {48,72,128,200,270,290,294,292,286,278,266,252,236,218,198,174,148,120,90,58,24};

#if PALMODE==1
#define CRATE_PANEL_Y (-120)
#else
#define CRATE_PANEL_Y (-110)
#endif


// Lots of structs

struct {
	unsigned char state;
	PSIMODEL *model;						// Pointer to the model
	ACTOR *actor;							// Pointer to the actor structure
	int damage;								// The level of damage
	VECTOR desiredangle;					// The direction we want to be facing
	ENEMY enemyStruc;						// Enemy structure used for collision and rail-cam
	VECTOR position;						// Position relative to the rail (-800 to 800 horizontal and -600 to 600 vertical)
	VECTOR speed;							// Speed on X, Y and Z
	char turning;							// Set to 1 if we've hit a boundary and are turning
} crate;

#define CRATE_MAXSPEED 14000				

#if(PALMODE==YES)
#define EGG_WAIT_TIME 100					// How many frames to wait for before we can throw a new egg
#else
#define EGG_WAIT_TIME 120
#endif

struct {
	unsigned char active;					// Whether or not the explosion is currently active
	VECTOR partpositions[35];				// Positions of all the particles in the explosion
	VECTOR velocity[35];					// Speed of each particle
	int life[35];							// Life of each particle
	unsigned long colour;					// Rate of transparency
	TextureType *texture;					// Pointer to the wood texture
	int scale[35];
} explosion;


struct {
	PSIMODEL *model;
	ACTOR *actor;
	unsigned char active;
	VECTOR speed;
	int timer;								// Number of frames before we can throw a new egg
} egg;

struct {
	PSIMODEL *model;						// Pointer to the model
	ACTOR *actor;							// Pointer to the actor structure
	unsigned char state;					// What she's doing? (hanging, throwing axe, rebounding from collision etc...)
	unsigned char currentanimsegment;		// The animation we're currently playing
	unsigned char desiredanimsegment;		// The animation we want to be playing
	VECTOR distance;						// Distance from the crate
	ENEMY enemyStruc;						// Enemy structure used for collision and rail-cam
	COLLDATA collision;						// Collision data
	int collision_radius;					// The radius of the collision sphere
	int bouncecounter;						// How many frames we wait after rebounding before we drop back down
} mrstweedy;


struct {
	PSIMODEL *model;						// Pointer to the model
	ACTOR *actor;							// Pointer to the actor structure
	VECTOR velocity;						// Speed in 3 directions
	VECTOR currentangle;					// The direction we're currently facing
	VECTOR desiredangle;					// The direction we want to be facing
	int xstep;								// Speed in X
	int ystep;								// Speed in Y
	int zstep;								// Speed in Z
	int xdist;								// X distance from target
	int ydist;								// Y distance from target
	int zdist;								// Z distance from target
	unsigned char active;					// Whether or not the axe is currently airborne
	unsigned char framestotarget;			// How many frames are left 'til we hit our target
} axe;


struct {
	TextureType *sprites[4];				// Pointer to the sprite frames
	int counter;							// Counter for animation and killing
	VERT position;							// Position before camera taken into account
	int scale;								// Size compared to original texture
	int active;								// Whether or not it needs to be drawn
} dust;


struct {
	TextureType *sprite;					// Pointer to the sprite
	VERT position;							// Position in actual screen coordinates (this is a 2d sprite that i drew myself)
} moon;

typedef struct {
	VECTOR position;						// Position of this link relative to the crate
	VERT velocity;							// Velocity of this link
	long angle;
	VERT desiredvelocity;					// The velocity this link wants to be at
} link;


typedef struct {							// Lights for the rope
	TextureType *sprite;
	VECTOR position;
	ULONG rgb;
} light;

light xmaslights[CRATE_NUMLINKS];

link ropelinks[CRATE_NUMLINKS];				

int cloudposition;

SfxSampleType *cr_axe1;
SfxSampleType *cr_axe2;
SfxSampleType *cr_hit1;
SfxSampleType *cr_aargh;
SfxSampleType *cr_ouch;
SfxSampleType *cr_windy;
SfxSampleType *cr_wings;

// This is to move link 0 up and down in time with the animation of the crate so that it's anchored at the back door
// Y axis offset from the flying machine's origin for animation frames 0 to 48

static int poslist2[49] = {193, 194, 196, 201, 206, 213, 220, 228, 236, 244, 252, 258, 264, 269, 274, 279, 284, 289, 293, 299, 305, 310, 315, 311, 308, 301, 294, 289, 284, 279, 274, 269, 264, 258, 252, 248, 240, 232, 228, 224, 216, 212, 210, 208, 207,205,205,204,204};

int camangle;			// used for tracking shots at the start and end
int camdistance;
char cratepaused;

unsigned long crateFrameCounter;

enum{	// States for the tweedy
	TWEEDY_HANGING,
	TWEEDY_STARTINGTOTHROW,
	TWEEDY_THROWING,
	TWEEDY_REBOUNDING,
	TWEEDY_EGGED
};

enum	// States for the flying machine
{
	CRATE_STARTING,
	CRATE_FLYING,					// Flying along
	CRATE_CRASHING,					// Flying towards the ground
	CRATE_CRASHED,					// Has reached the ground
	CRATE_WON,						// Has successfully reached the farm - flyby shot
	CRATE_FINISHED					// Fading out and quitting to FMV
};

int randomx[130];							// X positions for all the stars

int randomy[130];							// Y positions for all the stars




TextureType *crate_bar1;
TextureType *crate_bar2;
TextureType *crate_bar3;
TextureType *crate_egg;
TextureBankType *crate_bar_textures = NULL;
TextureType *moon_glow;
TextureType *cloud;
TextureType *cratesky1;
TextureType *cratesky2;

void crateOverlayAlpha(int x, int y, int w, int h , unsigned long rgb, int colour)
{
	POLY_F4 *f4;
	DR_TPAGE *tp;

#ifdef _WINDOWS
	BEGINPRIM(tp, DR_TPAGE);
	setDrawTPage(tp,1,1,((colour-1)<<5));
	ENDPRIM(tp,0,DR_TPAGE);
#endif

	BEGINPRIM(f4, POLY_F4);
	setPolyF4(f4);
	f4->code = f4->code | 2;

	f4->x0=f4->x2=x;
	f4->x1=f4->x3=x+w;
	f4->y0=f4->y1=y;
	f4->y2=f4->y3=y+h;
	setRGB0(f4, rgb>>16 & 0xff, rgb>>8 & 0xff, rgb & 0xff);

	ENDPRIM(f4, 0, POLY_F4);


#ifndef _WINDOWS
	BEGINPRIM(tp, DR_TPAGE);
	setDrawTPage(tp,1,1,((colour-1)<<5));
	ENDPRIM(tp,0,DR_TPAGE);
#endif
}




//----------------------------------------------------------------------------------
//	Function 	: void crateStart(void)
//	Purpose 	: Initialise everything, load models and level, allocate memory
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

void crateStart(void)
{
	int i;

	loadsndLoadBank("A4_FLYMACHINE");

	actorInitialise();
	PSImodelctrl.farclip = 1024;
	scenicInit();
	dsEvent_Init();

	// Load and get a pointer the map

	mapviewLoadMap("A4_FLYMACHINE");								
	setupCollision("A4_FLYMACHINE");

	crate_fma_world = (void *)BFF_FindObject(BFF_FMA_WORLD2_ID,utilStr2CRC("LNDMAP"));
	ASSERT(crate_fma_world);

	cr_axe1 = sfxFindSampleInAllBanks("axe1");
	ASSERT(cr_axe1);

	cr_axe2 = sfxFindSampleInAllBanks("axe2");
	cr_hit1 = sfxFindSampleInAllBanks("hitscene1");
	cr_aargh = sfxFindSampleInAllBanks("mrst_aaaaargh");
	cr_ouch = sfxFindSampleInAllBanks("mrst_ouch");
	cr_windy = sfxFindSampleInAllBanks("windy1");
	cr_wings = sfxFindSampleInAllBanks("wingflap1");

	// Set the cycles and starting state

	cloudposition = 256;
	cratepaused = 0;
	draw_routine = crateDraw;
	cycle_routine = crateCycle;
	crate.state = CRATE_STARTING;

	camangle = -1024;
	camdistance = -2048;
	cratetransp = 255;


	// Load and get pointers to the models and create the actors from them
	
	crate.model = BFF_IsModelLoaded("CRATE");
	ASSERT(crate.model);

	crate.actor = actorCreate(crate.model);
	ASSERT(crate.actor);

	mrstweedy.model = BFF_IsModelLoaded("MRSTWEEDYBOSS");

	ASSERT(mrstweedy.model);

	mrstweedy.actor = actorCreate(mrstweedy.model);
	ASSERT(mrstweedy.actor);

	axe.model = BFF_IsModelLoaded("AXE");
	ASSERT(axe.model);

	axe.actor = actorCreate(axe.model);
	ASSERT(axe.actor);

	egg.model = BFF_IsModelLoaded("EGG");
	ASSERT(egg.model);

	egg.actor = actorCreate(egg.model);
	ASSERT(egg.actor);

	// Get pointers to the sprites
	crate_egg = textureFindInAllBanks("TWEEDYEGG");
	ASSERT(crate_egg);

	crate_bar1 = textureFindInAllBanks("BAR1");
	ASSERT(crate_bar1);

	crate_bar2 = textureFindInAllBanks("BAR2");
	ASSERT(crate_bar2);

	crate_bar3 = textureFindInAllBanks("BAR3");
	ASSERT(crate_bar3);
	
	dust.sprites[0] = textureFindInAllBanks("DUST1");
	ASSERT(dust.sprites[0]);

	dust.sprites[1] = textureFindInAllBanks("DUST2");
	ASSERT(dust.sprites[1]);

	dust.sprites[2] = textureFindInAllBanks("DUST3");
	ASSERT(dust.sprites[2]);

	dust.sprites[3] = textureFindInAllBanks("DUST4");
	ASSERT(dust.sprites[3]);

//	moon.sprite = textureFindInAllBanks("MOON");
//	ASSERT(moon.sprite);

	explosion.texture = textureFindInAllBanks("WOOD");
	ASSERT(explosion.texture);

//	moon_glow = textureFindInAllBanks("MOONGLOW");
//	ASSERT(moon_glow);

//	cloud = textureFindInAllBanks("CLOUDS");
//	ASSERT(cloud);

	cratesky1 = textureFindInAllBanks("BSKY01");
	ASSERT(cratesky1);

	cratesky2 = textureFindInAllBanks("BSKY02");
	ASSERT(cratesky2);

	for(i=0;i<CRATE_NUMLINKS;i+=4)
	{
		xmaslights[i].sprite = textureFindInAllBanks("RED");
		ASSERT(xmaslights[i].sprite);
		xmaslights[i+1].sprite = textureFindInAllBanks("ORANGE");
		ASSERT(xmaslights[i+1].sprite);
		xmaslights[i+2].sprite = textureFindInAllBanks("BLUE");
		ASSERT(xmaslights[i+1].sprite);
		xmaslights[i+3].sprite = textureFindInAllBanks("GREEN");
		ASSERT(xmaslights[i+3].sprite);
	}

	// Set starting positions for the models / sprites and initialise the animations

	actorInitAnim(crate.actor);										
	actorAnimate(crate.actor, 0, 1, 0, 512, 1, 0);
	actorInitAnim(mrstweedy.actor);										
	actorAnimate(mrstweedy.actor, 0, 1, 0, 256, 1, 0);
	actorInitAnim(axe.actor);										
	actorAnimate(axe.actor, 0, 1, 0, 512, 1, 0);
	actorInitAnim(egg.actor);
	actorAnimate(egg.actor, 0, 1, 0, 512, 1, 0);

	mrstweedy.actor->position.vx = 0;
	mrstweedy.actor->position.vy = -300;
	mrstweedy.actor->position.vz = -35000;
	mrstweedy.actor->psiData.object->rotate.vy = 2048;	
	mrstweedy.state = TWEEDY_HANGING;
	mrstweedy.currentanimsegment = 0;
	mrstweedy.desiredanimsegment = 0;
	
	crate.desiredangle.vy = 0;
	crate.desiredangle.vx = 0;
	crate.damage = 3;
	crate.actor->position.vx = 0;
	crate.actor->position.vy = -200;
	crate.actor->position.vz = -35000;
	crate.actor->psiData.object->rotate.vx = 0;	
	crate.actor->psiData.object->rotate.vy = 0;	
	crate.actor->psiData.object->rotate.vz = 0;	

	axe.active = 0;
	axe.velocity.vx = 0;
	axe.velocity.vy = 0;
	axe.velocity.vz = 0;

	dust.active = 0;
	dust.position.vx = 0;
	dust.position.vy = 0;
	dust.position.vz = 0;
	dust.counter = 0;
	dust.scale = 0;

	moon.position.vx = 100;
	moon.position.vy = -120;
	moon.position.vz = 0;

	mycamera.vrx = crate.actor->position.vx;
	mycamera.vry = crate.actor->position.vy;
	mycamera.vrz = crate.actor->position.vz;

	mycamera.vpy = 0;
	mycamera.vpx = -1000;
	mycamera.vpz = -6000;

	mycamera.vpx+=crate.actor->position.vx;
	mycamera.vpy+=crate.actor->position.vy;
	mycamera.vpz+=crate.actor->position.vz;
	

	// Get some random positions for the stars

	for(i = 0; i < 130; i++)
	{
		randomx[i] = (utilRandomInt(1024))-512;				
		randomy[i] = (utilRandomInt(160))-128;	
	}


	// Set the framecounter to 0

	crateFrameCounter = 0;

	crate.turning = 0;

	// Set the starting positions for all of the segments in the rope
	// Relative to the origin of the crate

	ropelinks[0].position.vx = crate.actor->position.vx;			// The first link is positioned on the crate but slightly below and behind the origin
	ropelinks[0].position.vy = crate.actor->position.vy + 300;
	ropelinks[0].position.vz = crate.actor->position.vz - 390;

	for(i=1;i<CRATE_NUMLINKS; i++)									// the other links are positioned at the same X, but behind the first one and slightly lower
	{
		ropelinks[i].position.vx = 0;
		ropelinks[i].position.vy = 300;
		ropelinks[i].position.vz = ropelinks[i-1].position.vz - CRATE_SEGMENTLENGTH;
		ropelinks[i].velocity.vx = 0;
		ropelinks[i].velocity.vy = 0;
		ropelinks[i].velocity.vz = 40;
	}
	
	explosion.active = 0;
	egg.timer = EGG_WAIT_TIME;

	soundPlaySample(cr_windy, 128, 1300);
}






//----------------------------------------------------------------------------------
//	Function 	: void crateCycle(void)
//	Purpose 	: Main loop
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateCycle(void)
{
//	debugPrintf("%d\n",egg.timer);
	if(egg.timer)
		if(!(crateFrameCounter%3))
			egg.timer--;

	// Check to see if we need to move to a new state

	dsEvent_Update();


	// Increase the framecounter

	crateFrameCounter ++;

	// Animate the Scenics

	scenicCycle();

	if(crate.state==CRATE_FLYING)
	{
		if(pReadPad->debounce[0] & PAD_START)
		{
			if(!cratepaused)
			{
				dsEvent_Init();		// So we don't have continuous rumblings while paused
				actorSetAnimSpeed(crate.actor,0);
				actorSetAnimSpeed(mrstweedy.actor,0);
				cratepaused = 1;
			}
			else
			{
				actorSetAnimSpeed(crate.actor,512);
				actorSetAnimSpeed(mrstweedy.actor,256);
				cratepaused = 0;
			}
		}
	}

	actorMaintainAnimation(mrstweedy.actor);
	actorMaintainAnimation(crate.actor);
	actorMaintainAnimation(axe.actor);

	if(!cratepaused)
	{

		switch(crate.state)
		{
			case CRATE_STARTING:
				crateMoveCrate();
				crateMoveCamera();
				crateMoveTweedy();
				crateMoveRope();
				break;	

			case CRATE_FLYING:
				crateCollision();
				crateMoveCrate();
				crateMoveCamera();
				crateMoveTweedy();
				crateMoveAxe();
				crateMoveRope();
				crateMoveEgg();
				crateUpdatePartExplosion();
				break;

			case CRATE_CRASHING:
				crateMoveCrate();
				crateMoveCamera();
				crateMoveTweedy();
				crateMoveAxe();
				crateMoveRope();
				crateMoveEgg();
				crateUpdatePartExplosion();
				break;


			case CRATE_WON:
				crateEndSuccess();
				// Do success FMA
				break;	

			case CRATE_CRASHED:
				// Do failure FMA
				crateEndFail();
				break;

			default:
				break;
		}
	}

	else if(pReadPad->debounce[0] & PAD_SELECT)
		crateEndFail();
}






//----------------------------------------------------------------------------------
//	Function 	: void crateDraw(void)
//	Purpose 	: Draw everything
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateDraw(void)
{
	crateDrawSky();
	crateDrawOverlays();
	crateDrawMap();

	scenicDrawAll();
	crateDrawSprites();
	crateDrawModels();
	
	if(cratepaused == 1)
	{
		fontPrintCentred(font,0,-16, TEXTSTR(TEXTSTR_PAUSED1),128,128,128);
		fontPrintCentred(font,0,0,TEXTSTR(TEXTSTR_PAUSED_PRESS_START),128,128,128);
		fontPrintCentred(font,0,16,TEXTSTR(TEXTSTR_PAUSED_PRESS_SELECT),128,128,128);
	}
	
	if(crate.state==CRATE_STARTING)
	{
		if(cratetransp>0)
		{
			cratetransp-=8;
			if(cratetransp<0)
				cratetransp = 0;
			deathScreenAdd(cratetransp,cratetransp,cratetransp, SEMITRANS_SUB, 0);
		}
		crateDrawBriefing();
	}

	if((crate.state==CRATE_CRASHING)||(crate.state==CRATE_WON))
	{
		if((mycamera.vpz-6000)<crate.actor->position.vz)
		{
			if(cratetransp>0)
			{
				cratetransp-=3;
				if(cratetransp<0) cratetransp = 0;
			}

			deathScreenAdd(255-cratetransp,255-cratetransp,255-cratetransp, SEMITRANS_SUB, 0);
		}
	}
}






//----------------------------------------------------------------------------------
//	Function 	: void crateEnd
//	Purpose 	: Free all memory used and return to the menu
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateEndFail(void)
{
	crateStopSamples();
	scenicEnd();
	actorFree(mrstweedy.actor);
	actorFree(axe.actor);
	actorFree(crate.actor);
	actorFree(egg.actor);
	mapviewReleaseMap();
	enemyDeleteAll();
	psiDestroy();

	optionsStart(0);

	// Do some sort of aaarrgh!! all the chickens are dead... try again Y/N?-type thing here
}






//----------------------------------------------------------------------------------
//	Function 	: void crateEnd
//	Purpose 	: Free all memory used and return to the menu
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateEndSuccess(void)
{
	crateStopSamples();
	scenicEnd();
	actorFree(mrstweedy.actor);
	actorFree(axe.actor);
	actorFree(crate.actor);
	actorFree(egg.actor);
	mapviewReleaseMap();
	enemyDeleteAll();
	psiDestroy();
	levflowCrateSuccess();
}





//----------------------------------------------------------------------------------
//	Function 	: void crateEnd
//	Purpose 	: Free all memory used and return to the menu
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateEndRestart(void)
{
	crateStopSamples();
	scenicEnd();
	actorFree(mrstweedy.actor);
	actorFree(axe.actor);
	actorFree(crate.actor);
	actorFree(egg.actor);
	mapviewReleaseMap();
	enemyDeleteAll();
	psiDestroy();

	levflowCrateSuccess();
}

//----------------------------------------------------------------------------------
//	Function 	: void crateMoveCrate(void)
//	Purpose 	: Move the crate around with the analog stick
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateMoveCrate(void)
{
	int i;
	VECTOR v;
	int angle;
	
	switch(crate.state)
	{
		case CRATE_FLYING:
			crate.desiredangle.vy+=(pReadPad->analogXS[0] >> 6);
	
			// or of course the digital pad option for mad people
			if(pReadPad->digital[0] & PAD_LEFT)
			{
				crate.desiredangle.vy-=64;
			}
			else if(pReadPad->digital[0] & PAD_RIGHT)
			{
				crate.desiredangle.vy+=64;
			}

			if(crate.desiredangle.vy>512)
				crate.desiredangle.vy = 512;	

			if(crate.desiredangle.vy<-512)
				crate.desiredangle.vy = -512;
			
			if(crate.turning)
				crate.desiredangle.vy = 0;

			// Set the rotation about z (roll)

			crate.desiredangle.vz = pReadPad->analogXS[0] >> 4;
			if(crate.actor->psiData.object->rotate.vz != crate.desiredangle.vz)
				crate.actor->psiData.object->rotate.vz = enemyTendTowards(crate.desiredangle.vz, crate.actor->psiData.object->rotate.vz, 30);	

			if(crate.desiredangle.vx>300)
				crate.desiredangle.vx = 300;

			if(crate.desiredangle.vx<-300)
				crate.desiredangle.vx = -300;

			// Do the Min / Max X position check

			if(crate.actor->position.vx<CRATE_MINX)
			{
				crate.actor->position.vx = CRATE_MINX;
				crate.turning = 1;
			}

			else if(crate.actor->position.vx>CRATE_MAXX)
			{
				crate.actor->position.vx = CRATE_MAXX;
				crate.turning = 1;
			}

			// Set the rotation about y (yaw)
		
			if(crate.actor->psiData.object->rotate.vy!=crate.desiredangle.vy)
			{
				if(crate.turning)
					crate.actor->psiData.object->rotate.vy=enemyTendTowards(crate.desiredangle.vy, crate.actor->psiData.object->rotate.vy, 40);
				else
					crate.actor->psiData.object->rotate.vy=enemyTendTowards(crate.desiredangle.vy, crate.actor->psiData.object->rotate.vy, 200);
			}

			if((crate.turning) && (crate.actor->psiData.object->rotate.vy==0))
			{
				crate.turning = 0;
			}

			// Move the plane on X & Z according to the direction it's facing

			if(crate.actor->psiData.object->rotate.vy<1024)
			{
				angle = 1024-crate.actor->psiData.object->rotate.vy;
				crate.speed.vz = (CRATE_MAXSPEED/1024) * angle;
				crate.speed.vx = CRATE_MAXSPEED - crate.speed.vz;
			}

			else if(crate.actor->psiData.object->rotate.vy>3172)
			{
				angle = crate.actor->psiData.object->rotate.vy-2048;	
	
				angle = 1024-angle;
				crate.speed.vz = -((CRATE_MAXSPEED/1024) * angle);
				crate.speed.vx = -(CRATE_MAXSPEED - crate.speed.vz);
			}
	
			crate.speed.vy = -((crate.desiredangle.vx*128)/96);
			
			crate.actor->position.vz+=crate.speed.vz/64;
			crate.actor->position.vx+=crate.speed.vx/64;

			if(pReadPad->debounce[0] & PAD_CROSS)
				crateGenerateNewEgg();

			if(crate.damage>=CRATE_MAXDAMAGE)
			{
				crate.damage = CRATE_MAXDAMAGE;
				crate.state = CRATE_CRASHING;

				mycamera.vpx = crate.actor->position.vx;
				mycamera.vpy = crate.actor->position.vy +200;
				mycamera.vpz = crate.actor->position.vz+12000;
				cratetransp = 255;
			}

			else if(crate.actor->position.vz>325000)
			{


				crate.state = CRATE_WON;
				mycamera.vpx = 1500;
				mycamera.vpy = crate.actor->position.vy + 300;
				mycamera.vpz = 335000;
				cratetransp = 255;
			}

#ifdef DEBUG
			if(SwimCheatCycle())
			{
				crate.state = CRATE_WON;
				mycamera.vpx = 1500;
				mycamera.vpy = crate.actor->position.vy + 300;
				mycamera.vpz = 335000;
				cratetransp = 255;
			}
#endif

			break;

		case CRATE_CRASHING:
			crate.speed.vx = 0;
			crate.speed.vz = CRATE_MAXSPEED;

			crate.actor->position.vz+=crate.speed.vz/96;
			crate.actor->position.vy+=10;
			crate.actor->position.vx+=crate.speed.vx/96;

			crate.desiredangle.vy = 0;
			
			if(crate.actor->psiData.object->rotate.vy!=crate.desiredangle.vy)
				crate.actor->psiData.object->rotate.vy=enemyTendTowards(crate.desiredangle.vy, crate.actor->psiData.object->rotate.vy, 38);

			crate.desiredangle.vx = -256;
		
			if(crate.actor->psiData.object->rotate.vx!=crate.desiredangle.vx)
				crate.actor->psiData.object->rotate.vx=enemyTendTowards(crate.desiredangle.vx, crate.actor->psiData.object->rotate.vx, 8);
			break;

		case CRATE_WON:
			crate.speed.vx = 0;
			crate.speed.vz = CRATE_MAXSPEED;

			crate.actor->position.vz+=crate.speed.vz/128;
			crate.actor->position.vx+=crate.speed.vx/128;
			crate.actor->position.vy+=3;

			crate.desiredangle.vy = 0;
			if(crate.actor->psiData.object->rotate.vy!=crate.desiredangle.vy)
				crate.actor->psiData.object->rotate.vy=enemyTendTowards(crate.desiredangle.vy, crate.actor->psiData.object->rotate.vy, 38);

			crate.desiredangle.vx = -96;
			if(crate.actor->psiData.object->rotate.vx!=crate.desiredangle.vx)
				crate.actor->psiData.object->rotate.vx=enemyTendTowards(crate.desiredangle.vx, crate.actor->psiData.object->rotate.vx, 8);
			break;

		case CRATE_STARTING:
			crate.speed.vx = 0;
			crate.speed.vz = CRATE_MAXSPEED;
			crate.actor->position.vz+=crate.speed.vz/64;
			crate.actor->position.vx+=crate.speed.vx/64;
			crate.desiredangle.vy = 0;
			if(crate.actor->psiData.object->rotate.vy!=crate.desiredangle.vy)
				crate.actor->psiData.object->rotate.vy=enemyTendTowards(crate.desiredangle.vy, crate.actor->psiData.object->rotate.vy, 38);

			crate.desiredangle.vx = 0;
			if(crate.actor->psiData.object->rotate.vx!=crate.desiredangle.vx)
				crate.actor->psiData.object->rotate.vx=enemyTendTowards(crate.desiredangle.vx, crate.actor->psiData.object->rotate.vx, 8);

			if(crateFrameCounter==200)
				crate.state = CRATE_FLYING;		
			break;

		default:
			break;
	}

	if(pReadPad->debounce[0] & PAD_TRIANGLE)
		crate.damage+=10;

//	debugPrintf("%d ",crate.actor->animation.animData[0].actualFrame);
	if((crate.actor->animation.animData[0].actualFrame==31)||(crate.actor->animation.animData[0].actualFrame==32))
		soundPlaySample(cr_wings, 255,1300);		
}





//----------------------------------------------------------------------------------
//	Function 	: void crateDrawMap(void)
//	Purpose 	: Draw the level map
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateDrawMap(void)
{
	mapCamSetRefView2L(&mycamera);

	GsSetProjection(500);

	MapDraw_DrawFMA_World(crate_fma_world);
}






//----------------------------------------------------------------------------------
//	Function 	: void crateDrawModels(void)
//	Purpose 	: Draw the models
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateDrawModels(void)
{	
	// Set a fixed light

	psiSetLight(0, 96, 96, 128, -3172 , 4095, 0);

	psiSetAmbient(872,872,1024);

	actorDraw(mrstweedy.actor);
	
	actorDraw(crate.actor);
	
	if(axe.active)
	{
		actorDraw(axe.actor);
	}
}






//----------------------------------------------------------------------------------
//	Function 	: void crateDrawSky(void)
//	Purpose 	: Draw the sky - A gouraud quad for the sky and some triangles for stars
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateDrawSky(void)
{
	int i;

	POLY_F3 *star;				// A flat-shaded tri for each star
	for(i=0;i<130;i++)
	{
		BEGINPRIM(star, POLY_F3);
		setPolyF3(star);				
		star->x0 = randomx[i];				
		star->y0 = randomy[i];	
		star->x1 = star->x0+1;
		star->y1 = star->y0;
		star->x2 = star->x1;
		star->y2 = star->y1+1;

		star->r0 = abs(utilRandomInt(128) +127);
		star->g0 = star->r0;
		star->b0 = star->r0;

		ENDPRIM(star, 999, POLY_F3);
	}

	/*return;
	{
		POLY_G4 *sky;				// A quad gouraud for the sky

		BEGINPRIM(sky, POLY_G4);
		setPolyG4(sky);				
		sky->x0 = -256;				
		sky->y0 = -128;	
		sky->x1 = 256;
		sky->y1 = -128;
		sky->x2 = -256;
		sky->y2 = -50;
		sky->x3 = 256;
		sky->y3 = -50;

		sky->r0 = 16;
		sky->g0 = 0;
		sky->b0 = 0;
		sky->r1 = 16;
		sky->g1 = 0;
		sky->b1 = 0;
		sky->r2 = 0;
		sky->g2 = 0;
		sky->b2 = 126;
		sky->r3 = 0;
		sky->g3 = 0;
		sky->b3 = 126;
		
		ENDPRIM(sky, 1023, POLY_G4);
	}*/
	{
		POLY_G4 *bottomsky;

		BEGINPRIM (bottomsky, POLY_G4);
		setPolyG4(bottomsky);

		bottomsky->x0 = -256;				
		bottomsky->y0 = -32;
		
		bottomsky->x1 = 256;
		bottomsky->y1 = -32;

		bottomsky->x2 = -256;
		bottomsky->y2 = 128;

		bottomsky->x3 = 256;
		bottomsky->y3 = 128;

		bottomsky->r0 = 1;
		bottomsky->g0 = 1;
		bottomsky->b0 = 107;

		bottomsky->r1 = 1;
		bottomsky->g1 = 1;
		bottomsky->b1 = 107;

		bottomsky->r2 = 128;
		bottomsky->g2 = 0;
		bottomsky->b2 = 0;

		bottomsky->r3 = 128;
		bottomsky->g3 = 0;
		bottomsky->b3 = 0;

		ENDPRIM(bottomsky, 1023, POLY_G4);
	}
}





//----------------------------------------------------------------------------------
//	Function 	: void crateDrawOverlays(void)
//	Purpose 	: Draw the overlays - Damage meter
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateDrawOverlays(void)
{
	int i;
	POLY_G4 *speedbar;
	POLY_G4 *eggbar;

	unsigned long bordercolour;
	if(crate.state==CRATE_FLYING)
		fontPrint(font,-38,CRATE_PANEL_Y + 28,TEXTSTR(TEXTSTR_SUB_CRATE_DAMAGE),96,96,64);

#ifdef SUBGAME_OVERLAYS
	overlaySprite(crate_bar3, -75, CRATE_PANEL_Y + 20, 20, 0x808080);
#else
	overlaySprite(bar3, -75, -100, 20, 0x808080);
#endif

	BEGINPRIM(speedbar, POLY_G4);
	setPolyG4(speedbar);				
	
	speedbar->x0 = -83;				
	speedbar->y0 = CRATE_PANEL_Y + 6;	//-114;	
	
	speedbar->x1 = -83+crate.damage;
	speedbar->y1 = CRATE_PANEL_Y + 6;	//-114;

	speedbar->x2 = -83;
	speedbar->y2 = CRATE_PANEL_Y + 20;	//-100;
	
	speedbar->x3 = -83+crate.damage;
	speedbar->y3 = CRATE_PANEL_Y + 20;	//-100;

	speedbar->r0 = 32;
	speedbar->g0 = 32;
	speedbar->b0 = 48;
	speedbar->r1 = 32;
	speedbar->g1 = 32;
	speedbar->b1 = 48;

	speedbar->r2 = 32;
	speedbar->g2 = 32;
	speedbar->b2 = 48;
	speedbar->r3 = 32;
	speedbar->g3 = 32;
	speedbar->b3 = 48;
		
	ENDPRIM(speedbar,20, POLY_G4);

#ifdef SUBGAME_OVERLAYS
	overlaySprite(crate_bar1, -110, CRATE_PANEL_Y - 8, 20, 0x808080);
#else
	overlaySprite(bar1, -110, CRATE_PANEL_Y - 8, 20, 0x808080);
#endif
	{
		BEGINPRIM(eggbar, POLY_G4);
		setPolyG4(eggbar);				
	
		eggbar->x0 = -231;				
		eggbar->y0 = CRATE_PANEL_Y + 6;	
	
		eggbar->x1 = -231 + 67-((egg.timer*2744)/4096);
		eggbar->y1 = CRATE_PANEL_Y + 6;	

		eggbar->x2 = -231;
		eggbar->y2 = CRATE_PANEL_Y + 20;	
	
		eggbar->x3 = -231+ 67-((egg.timer*2744)/4096);//-164;
		eggbar->y3 = CRATE_PANEL_Y + 20;

		eggbar->r0 = 0;
		eggbar->g0 = 128;
		eggbar->b0 = 128;
		eggbar->r1 = 128;
		eggbar->g1 = 0;
		eggbar->b1 = 0;

		eggbar->r2 = 0;
		eggbar->g2 = eggbar->g0;
		eggbar->b2 = eggbar->b0;
		eggbar->r3 = eggbar->r1;
		eggbar->g3 = eggbar->g1;
		eggbar->b3 = eggbar->b1;
	
		ENDPRIM(eggbar,20, POLY_G4);
	}

	overlaySprite(crate_egg, -150, CRATE_PANEL_Y+2, 20, 0x808080);

#ifdef SUBGAME_OVERLAYS
	overlaySprite(crate_bar2,-240,CRATE_PANEL_Y + 2,20,0x808080);
#else
	overlaySprite(bar2,-240,CRATE_PANEL_Y + 2,20,0x808080);
#endif
}






//----------------------------------------------------------------------------------
//	Function 	: void crateMoveTweedy(void)
//	Purpose 	: Move Mrs Tweedy at the end of the rope
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateMoveTweedy(void)
{
	int i;
	VECTOR v;

	if(crate.state!=CRATE_WON)
	{

	//	debugPrintf("%d\n",mrstweedy.bouncecounter);
		if(mrstweedy.state == TWEEDY_REBOUNDING)
		{
			if(mrstweedy.bouncecounter>14)
				mrstweedy.actor->psiData.object->rotate.vx+=120;
			else 
				mrstweedy.actor->psiData.object->rotate.vx-=20;

			ropelinks[CRATE_NUMLINKS-1].position.vy-=(bouncedata[21-mrstweedy.bouncecounter])*2;

			// Run through the list of links backwards

			for(i = CRATE_NUMLINKS-2; i>0; i--)
			{
				// Get the distance between this link and the next link
			
				v.vx = ropelinks[i].position.vx - ropelinks[i+1].position.vx;

				v.vy = ropelinks[i].position.vy - ropelinks[i+1].position.vy;

				v.vz = ropelinks[i].position.vz - ropelinks[i+1].position.vz;

				crateKeepPointWithinSphere(&v, CRATE_SEGMENTLENGTH);
	
				ropelinks[i].position.vx = v.vx + ropelinks[i+1].position.vx;
	
				ropelinks[i].position.vy = v.vy + ropelinks[i+1].position.vy;
	
				ropelinks[i].position.vz = v.vz + ropelinks[i+1].position.vz;
			}

			mrstweedy.bouncecounter--;

			if(!mrstweedy.bouncecounter)
			{
				mrstweedy.state = TWEEDY_HANGING;
				mrstweedy.desiredanimsegment = MRSTWEEDY_HANGING;
			}
		}


		// Update the animation segment
	
		if(mrstweedy.currentanimsegment!=mrstweedy.desiredanimsegment)
		{
			if(mrstweedy.desiredanimsegment==MRSTWEEDY_BOUNCING)
				actorAnimate(mrstweedy.actor, mrstweedy.desiredanimsegment, 1, 0, 256, 1, 512);
			else if(mrstweedy.desiredanimsegment==MRSTWEEDY_HITBYEGG)
				actorAnimate(mrstweedy.actor, mrstweedy.desiredanimsegment, 0, 0, 384, 1, 1024);
			else
				actorAnimate(mrstweedy.actor, mrstweedy.desiredanimsegment, 1, 0, 384, 1, 512);

			mrstweedy.currentanimsegment = mrstweedy.desiredanimsegment;

		}
		
		if(mrstweedy.currentanimsegment==MRSTWEEDY_HITBYEGG)
		{
			if(actorNearEndOfAnimation(mrstweedy.actor, MRSTWEEDY_HITBYEGG, 2))
			{
				mrstweedy.desiredanimsegment = MRSTWEEDY_HANGING;
				mrstweedy.state = TWEEDY_HANGING;
			}
		}

		// Check to see if we want to throw a new axe
	
		if((mrstweedy.state==TWEEDY_HANGING) && (crate.state==CRATE_FLYING))
		{
			if(!(crateFrameCounter%CRATE_AXEFREQUENCY))
			{
				mrstweedy.state = TWEEDY_STARTINGTOTHROW;
				mrstweedy.desiredanimsegment = MRSTWEEDY_THROWING;
			}
		}


		// If she's on the starting to throw animation, see if a new axe needs to be generated
		
		else if((mrstweedy.state==TWEEDY_STARTINGTOTHROW) && (!axe.active))
		{
			if((crateFrameCounter%CRATE_AXEFREQUENCY) == 18)		// If she's been there for 15 frames, start the throw
			{
				mrstweedy.desiredanimsegment = 0;
				mrstweedy.state = TWEEDY_HANGING;
				axe.active = 1;


				// Set starting position for the axe
			
				axe.actor->position.vx = mrstweedy.actor->position.vx;
				axe.actor->position.vy = mrstweedy.actor->position.vy;
				axe.actor->position.vz = mrstweedy.actor->position.vz + 340;


				// Get a distance in X from the axe to the crate
			
				axe.xdist = crate.actor->position.vx - axe.actor->position.vx;


				// Get distance in Y from the axe to the crate
			
				axe.ydist = crate.actor->position.vy - axe.actor->position.vy;


				// Get distance in Z from the axe to the crate
		
				axe.zdist = crate.actor->position.vz - axe.actor->position.vz;
			

				// Work out the X,Y and Z speed required in order for the axe to reach the crate in 40 frames
		
				axe.xstep = (axe.xdist << 8) / 20;
				axe.ystep = (axe.ydist << 8) / 20;
				axe.zstep = (axe.zdist << 8) / 20;
				axe.framestotarget = 20;
			}
		}


		// Put Mrs Tweedy at the position of the last link in the rope

		mrstweedy.actor->position.vx = ropelinks[CRATE_NUMLINKS-1].position.vx;//+140;
		mrstweedy.actor->position.vy = ropelinks[CRATE_NUMLINKS-1].position.vy;//+100;
		mrstweedy.actor->position.vz = ropelinks[CRATE_NUMLINKS-1].position.vz;// + 250;


		// Set Mrs Tweedy's rotation about X and Z according to how far left / right / above / below she is of the crate

		// Get her distance in X from the crate

		mrstweedy.distance.vx = crate.actor->position.vx - mrstweedy.actor->position.vx;
		mrstweedy.distance.vy = (crate.actor->position.vy + 800) - mrstweedy.actor->position.vy;


		// Set the rotation based on that distance

		if(mrstweedy.state!=TWEEDY_REBOUNDING)
			mrstweedy.actor->psiData.object->rotate.vx = mrstweedy.distance.vy / 3;
		
		mrstweedy.actor->psiData.object->rotate.vz = -(mrstweedy.distance.vx / 2);

		mrstweedy.actor->psiData.object->rotate.vy = -(2048-(crate.actor->psiData.object->rotate.vy));
	}
}






//----------------------------------------------------------------------------------
//	Function 	: void crateMoveAxe(void)
//	Purpose 	: Move the axe so that it follows the crate and always hits
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateMoveAxe(void)
{
	if(!axe.active) return;


	// Rotate the axe about the X axis

	axe.actor->psiData.object->rotate.vx -= 512;

	if(axe.actor->psiData.object->rotate.vx <= 0)
		axe.actor->psiData.object->rotate.vx = 4095;


	// Decrease the target counter

	axe.framestotarget--;


	// Work out the path again in case the crate moved since the axe was thrown
	// Get a distance in X from the axe to the crate
	
	axe.xdist = crate.actor->position.vx - axe.actor->position.vx;


	// Get distance in Y from the axe to the crate
	
	axe.ydist = crate.actor->position.vy - axe.actor->position.vy;


	// Get distance in Z from the axe to the crate
	
	axe.zdist = crate.actor->position.vz - axe.actor->position.vz;
		
	
	// Work out the X,Y and Z speed required in order for the axe to reach the crate in time
	
	axe.xstep = (axe.xdist << 8)/axe.framestotarget;
	axe.ystep = (axe.ydist << 8)/axe.framestotarget;
	axe.zstep = (axe.zdist << 8)/axe.framestotarget;
	

	// Work out the angle of rotation about the y axis for this heading
	
	axe.currentangle.vy = axe.actor->psiData.object->rotate.vy;


	// Work out the desired angle

	axe.desiredangle.vy = (int)((calc_angle( (long) axe.xdist, (long) axe.zdist)) & 0xffff);

	if(axe.currentangle.vy != axe.desiredangle.vy)
	axe.currentangle.vy = enemyTendTowards(axe.desiredangle.vy, axe.currentangle.vy, 40);
		
	axe.actor->psiData.object->rotate.vy = axe.currentangle.vy;
	
	axe.actor->position.vx += axe.xstep >> 8;
	axe.actor->position.vy += axe.ystep >> 8;
	axe.actor->position.vz += axe.zstep >> 8;

	if((axe.actor->position.vz >= crate.actor->position.vz+100)||(axe.framestotarget==1))
	{
		axe.active = 0;
		crate.damage += 18;
		dsEvent_Add(0, 0, 1, 1200, 0, 7, NULL);

		soundPlaySample(cr_axe1, 255,12000);		// 24000 seems to be about the right frequency?
		crateGeneratePartExplosion(&crate.actor->position);

		dust.active = 1;
		dust.position.vx = (crate.actor->position.vx - mycamera.vpx);
		dust.position.vy = crate.actor->position.vy - mycamera.vpy + 200;
		dust.position.vz = (crate.actor->position.vz - mycamera.vpz) - 750;
		dust.counter = 14;
		dust.scale = 512;
	}
}



//----------------------------------------------------------------------------------
//	Function 	: void crateDrawSprites(void)
//	Purpose 	: Draw all the sprites - the dust and the rope
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateDrawSprites(void)
{
	int i;
	VERT v;
	ULONG lightalpha;

	if(dust.active)
	{
		dust.counter--;
		
		if(dust.counter==0)
			dust.active = 0;

		dust.position.vx -= crate.speed.vx/128;
		dust.position.vz -= crate.speed.vz/192;

		v.vx = dust.position.vx;
		v.vy = dust.position.vy;
		v.vz = dust.position.vz;

		Print3DSelfAlphaSprite(dust.sprites[3-(dust.counter/4)], &v, dust.scale, 0x505050);
	}

	overlaySprite(cratesky1,112,-128,1000,0x808080);
	overlaySprite(cratesky2,48,-128,1000,0x808080);
	overlaySprite(cratesky2,-16,-128,1000,0x808080);
	overlaySprite(cratesky2,-80,-128,1000,0x808080);
	overlaySprite(cratesky2,-144,-128,1000,0x808080);
	overlaySprite(cratesky2,-208,-128,1000,0x808080);
	overlaySprite(cratesky2,-272,-128,1000,0x808080);


//	overlayAlphaSprite(moon.sprite, moon.position.vx, moon.position.vy, 1000, 0x00606060);
//	overlayAlphaSprite(moon_glow, moon.position.vx-15, moon.position.vy-9, 1002, 0x404040);
//	overlayAlphaSprite(cloud, cloudposition, -120, 999, 0x00404040);
//	cloudposition--;


	// Draw the rope - just lines right now.. erm... what else could they be?

	for(i=0;i<CRATE_NUMLINKS;i++)
	{
		if(i<CRATE_NUMLINKS-1)
			overlayShowDebugLine(&ropelinks[i].position, &ropelinks[i+1].position, 0x333300, DEBUGPOLY_USEDEPTH);
		
		v.vx = ropelinks[i].position.vx - mycamera.vpx;
		v.vy = ropelinks[i].position.vy - mycamera.vpy;
		v.vz = ropelinks[i].position.vz - mycamera.vpz;
		lightalpha = utilRandomInt(255);
		Print3DSelfAlphaSprite(xmaslights[i].sprite, &v, 256, ((lightalpha<<8) + (lightalpha<<16) + (lightalpha)));
	}


	// Draw the particle explosion

	if(explosion.active)
		crateDrawPartExplosion();

	if(egg.active)
		crateDrawEgg();
}






//----------------------------------------------------------------------------------
//	Function 	: void crateCollision(void)
//	Purpose 	: Perform a spherical collision check on Mrs Tweedy and the Crate and set the flag accordingly
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateCollision(void)
{
	int items_hit;
	VECTOR v;
	int i;

	// Czech to see if Mrs tweedy has collided with something

	mrstweedy.collision.pPos = &mrstweedy.actor->position;
	mrstweedy.collision.pVel = &v;	
	mrstweedy.collision.bounce = 0;
	mrstweedy.collision.radius = 4096 * 20;
	mrstweedy.collision.nBoxes = numCollBoxes;

	mrstweedy.actor->position.vy <<= 9;						// Scale the positions up by 512 
	mrstweedy.actor->position.vx <<= 9;						// For the collision stuff to work
	mrstweedy.actor->position.vz <<= 9;		

	ASSERT(&mrstweedy.collision);
	items_hit = collboxCheckSphere(&mrstweedy.collision);

	mrstweedy.actor->position.vy >>= 9;						// Scale the positions back down again
	mrstweedy.actor->position.vx >>= 9; 
	mrstweedy.actor->position.vz >>= 9;	
	
	// Only set her bouncing again if she's in normal flying mode

	if(mrstweedy.state!=TWEEDY_REBOUNDING)
	{
		if(items_hit>0)
		{
			for(i=0;i<items_hit;i++)
			{
				if(!((pHitData[i].pCollBox->flags) & 1))	// If bit 1 isn't set, it's a box
				{
					soundPlaySample(cr_ouch, 128, 1300);
					dsEvent_Add(0, 0, 1, 180, 0, 7, NULL);
					mrstweedy.state = TWEEDY_REBOUNDING;
					mrstweedy.desiredanimsegment  = MRSTWEEDY_BOUNCING;
					mrstweedy.bouncecounter = 21;			// Make it bounce up to -600 before coming down again
				}
			}
		}
	}
}






//----------------------------------------------------------------------------------
//	Function 	: void crateGeneratePartExplosion(void)
//	Purpose 	: Generate a new particle explosion at v
//	Parameters 	: Pointer to a vector describing the origin of the explosion
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateGeneratePartExplosion(VECTOR *v)
{
	int i;

	if(explosion.active) 
		return;

	explosion.active = 1;

	// Set the colour to full

	explosion.colour = 0xffffff;

	for(i=0;i<35;i++)
	{	
		// Set starting positions for all the polygons to the origin of the explosion

		explosion.partpositions[i].vx = v->vx;
		explosion.partpositions[i].vy = v->vy;
		explosion.partpositions[i].vz = v->vz;


		// Get the correct world space co-ordinates by subtracting the camera position

		explosion.partpositions[i].vx -= mycamera.vpx+20;
		explosion.partpositions[i].vy -= mycamera.vpy-180;
		explosion.partpositions[i].vz -= mycamera.vpz+510;

		// Get random starting velocitys for all the particles
		
		explosion.velocity[i].vx = utilRandomInt(20)-10;
		explosion.velocity[i].vy = utilRandomInt(25)-15;
		explosion.velocity[i].vz = utilRandomInt(20)-10;
	

		// Make sure we haven't got any particles with a zero X velocity

		if(explosion.velocity[i].vx == 0)
			explosion.velocity[i].vx = 1;


		// Get a random lifespan for each particle between 10 and 60 frames

		explosion.life[i] = utilRandomInt(50)+10;


		// Get a random scale for this particle between 32 and 96

		explosion.scale[i] = utilRandomInt(32) + 64;
	}
}





//----------------------------------------------------------------------------------
//	Function 	: void crateGenerateNewEgg(void)
//	Purpose 	: Set a new egg off from the back of the crate
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateGenerateNewEgg(void)
{
	if((egg.active) ||(egg.timer>0))
		return;

	// Set starting positions for the egg to just behind the crate

	egg.active = 1;
	egg.actor->position.vx = crate.actor->position.vx;
	egg.actor->position.vy = crate.actor->position.vy;
	egg.actor->position.vz = crate.actor->position.vz - 100;

	egg.actor->psiData.object->rotate.vy = 0;	

	egg.speed.vx = crate.speed.vx/256;
	egg.speed.vy = 60;
	egg.speed.vz = crate.speed.vz/256;
	egg.timer = EGG_WAIT_TIME;
}





//----------------------------------------------------------------------------------
//	Function 	: void crateMoveEgg(void)
//	Purpose 	: Move the egg
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateMoveEgg(void)
{
	if(!egg.active)
		return;

	egg.actor->position.vx += egg.speed.vx;
	egg.actor->position.vy += egg.speed.vy;
	egg.actor->position.vz += egg.speed.vz;

	egg.actor->psiData.object->rotate.vx+=256;

	if(egg.actor->psiData.object->rotate.vx>=4095)
		egg.actor->psiData.object->rotate.vx = 0;	

	if(egg.actor->position.vz < mrstweedy.actor->position.vz-2000)
		egg.active = 0;

	// Psuedo-collision - box style
	if(egg.actor->position.vz<mrstweedy.actor->position.vz)
	{
		if((ABS(mrstweedy.actor->position.vx - egg.actor->position.vx))<150)
		{
			if(mrstweedy.state != TWEEDY_REBOUNDING)
			{
				mrstweedy.desiredanimsegment = MRSTWEEDY_HITBYEGG;
				mrstweedy.state = TWEEDY_EGGED;
				egg.active = 0;

				soundPlaySample(cr_ouch,128,1300);		
				dsEvent_Add(0, 0, 1, 180, 0, 7, NULL);
			}
		}
	}
}





//----------------------------------------------------------------------------------
//	Function 	: void crateDrawEgg(void)
//	Purpose 	: Draw the egg
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateDrawEgg(void)
{
	if(!egg.active)
		return;

	actorMaintainAnimation(egg.actor);

	psiSetAmbient(2024,2024,2024);
	actorDraw(egg.actor);
}





//----------------------------------------------------------------------------------
//	Function 	: void crateUpdatePartExplosion(void)
//	Purpose 	: Update the particle explosion
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateUpdatePartExplosion(void)
{
	int i;

	if(!explosion.active)
		return;

	explosion.active = 0;

	if(explosion.colour>0)
		explosion.colour -= 0x040404;

	for(i=0;i<35;i++)
	{
		if(explosion.life[i])
		{
			explosion.life[i] --;
			// Move the particles about according to the speed
			explosion.partpositions[i].vx += explosion.velocity[i].vx;
			explosion.partpositions[i].vy += explosion.velocity[i].vy;
			explosion.partpositions[i].vz += explosion.velocity[i].vz;

			explosion.partpositions[i].vx -= crate.speed.vx/128;
			explosion.partpositions[i].vz -= crate.speed.vz/256;

			// Increase the Y velocity so it falls towards the ground

			explosion.velocity[i].vy ++;
			
			explosion.active = 1;
		}
	}
}






//----------------------------------------------------------------------------------
//	Function 	: void crateDrawPartExplosion(void)
//	Purpose 	: Draw the particle explosion
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateDrawPartExplosion(void)
{
	VERT v;
	int i;

	if(!explosion.active) return;

	for(i = 0;i < 35;i ++)
	{
		if(explosion.life[i])
		{
			v.vx = explosion.partpositions[i].vx;
			v.vy = explosion.partpositions[i].vy;
			v.vz = explosion.partpositions[i].vz;

			Print3DSprite(explosion.texture,&v,explosion.scale[i],explosion.colour);
		}
	}
}





//----------------------------------------------------------------------------------
//	Function 	: void crateMoveCamera(void)
//	Purpose 	: Keep the camera directly behind the crate
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateMoveCamera(void)
{
	VECTOR campos;
	VECTOR campos2;

	switch(crate.state)
	{
		case CRATE_STARTING:
			if(camdistance<0)
				camdistance +=16;
			campos.vx = 0;
			campos.vy = - 500;
			campos.vz = - 4100 + camdistance;
			
			if(crateFrameCounter>75)
			{
				if(camangle<0)
				{
					if(camangle<-896)
						camangle+=4;
					else if(camangle<-832)
						camangle+=6;
					else if(camangle<-768)
						camangle+=8;
					else if(camangle<-640)
						camangle+=12;
					else if(camangle<-512)
						camangle+=16;
					else if(camangle<-384)
						camangle+=20;
					else if(camangle<-256)
						camangle+=24;
					else if(camangle<0)
						camangle+=28;

					if(camangle>0)
						camangle = 0;
				}
				else
					crate.state = CRATE_FLYING;
			}

			RotateVector2DXYZ(&campos2, &campos, camangle, 1);
			
			mycamera.vrx = crate.actor->position.vx ;
			mycamera.vry = crate.actor->position.vy + 600;
			mycamera.vrz = crate.actor->position.vz;
			mycamera.vpx = crate.actor->position.vx + campos2.vx;
			mycamera.vpy = crate.actor->position.vy + campos2.vy;
			mycamera.vpz = crate.actor->position.vz + campos2.vz;
			break;

		case CRATE_FLYING:
			mycamera.vrx = crate.actor->position.vx ;
			mycamera.vry = crate.actor->position.vy + 600;
			mycamera.vrz = crate.actor->position.vz;
			mycamera.vpx = crate.actor->position.vx;
			mycamera.vpy = crate.actor->position.vy - 500;
			mycamera.vpz = crate.actor->position.vz - 4100;
			break;

		case CRATE_CRASHING:
			mycamera.vrx = crate.actor->position.vx;
			mycamera.vry = crate.actor->position.vy;
			mycamera.vrz = crate.actor->position.vz;
		
			if((mycamera.vpz)<crate.actor->position.vz)
			{
				crate.state = CRATE_CRASHED;
			}
			
			break;
		
		case CRATE_WON:
			mycamera.vrx = crate.actor->position.vx;
			mycamera.vry = crate.actor->position.vy;
			mycamera.vrz = crate.actor->position.vz;
		
			if(crate.actor->position.vz > 340000)
			{
				crate.state = CRATE_FINISHED;
			}

			break;
	}
}






//----------------------------------------------------------------------------------
//	Function 	: void crateKeepPointWithinSphere(void)
//	Purpose 	: Keep a vector within a sphere of radius
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateKeepPointWithinSphere(VECTOR *v, int radius)
{
	int mag;

	mag = Magnitude(v);

	if(mag>radius)
		SCALEVECTOR(v,(4096/mag)*radius);
}






//----------------------------------------------------------------------------------
//	Function 	: void crateMoveRope(void)
//	Purpose 	: Move the rope behind the crate
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateMoveRope(void)
{
	int i;
	VECTOR v;

	// Anchor link 0 to the crate
	// Set the position of link 0 relative to the origin of the crate

	ropelinks[0].position.vx = -30;
	ropelinks[0].position.vy = (int) poslist2[crate.actor->animation.animData[0].actualFrame];
	ropelinks[0].position.vz = -390;


	// Rotate the position of link 0 according to the rotation of the crate
	
	RotateVector2DXYZ(&ropelinks[0].position, &ropelinks[0].position, -crate.actor->psiData.object->rotate.vx, 0);
	RotateVector2DXYZ(&ropelinks[0].position, &ropelinks[0].position, -crate.actor->psiData.object->rotate.vy, 1);
	RotateVector2DXYZ(&ropelinks[0].position, &ropelinks[0].position, -crate.actor->psiData.object->rotate.vz, 2);
	
	ropelinks[0].position.vx += crate.actor->position.vx;
	ropelinks[0].position.vy += crate.actor->position.vy;
	ropelinks[0].position.vz += crate.actor->position.vz;


	// Add something to the Y position of link 0 to compensate for the y rotation

	if(crate.actor->psiData.object->rotate.vy<1024)
		ropelinks[0].position.vy += abs(crate.actor->psiData.object->rotate.vy / 4);
	else
		ropelinks[0].position.vy += abs((4096-crate.actor->psiData.object->rotate.vy) / 4);

	ropelinks[0].velocity.vx = crate.speed.vx/64;
	ropelinks[0].velocity.vz = crate.speed.vz/64;

	// Now move the rest of the links in the rope
	// Give the link a speed depending on the speed of the link before it in the chain - it wants to try and hit that speed!
	for(i = 1; i<CRATE_NUMLINKS; i++)
	{
		ropelinks[i].desiredvelocity.vx = (crate.position.vx - ropelinks[i].position.vx)*2;
		if(ropelinks[i].velocity.vx>ropelinks[i].desiredvelocity.vx)
			ropelinks[i].velocity.vx--;

		else if(ropelinks[i].velocity.vx<ropelinks[i].desiredvelocity.vx)
			ropelinks[i].velocity.vx++;
	}


	// Ok, now keep em all within a sphere of the previous link
	for(i = 1; i<CRATE_NUMLINKS; i++)
	{
		// Get the distance between this link and the previous link
		
		v.vx = ropelinks[i].position.vx - ropelinks[i-1].position.vx;

		v.vy = ropelinks[i].position.vy - ropelinks[i-1].position.vy;

		v.vz = ropelinks[i].position.vz - ropelinks[i-1].position.vz;
		
		crateKeepPointWithinSphere(&v, CRATE_SEGMENTLENGTH);
	
		ropelinks[i].position.vx = v.vx + ropelinks[i-1].position.vx;
	
		ropelinks[i].position.vy = v.vy + ropelinks[i-1].position.vy;
	
		ropelinks[i].position.vz = v.vz + ropelinks[i-1].position.vz;

		if((crate.state==CRATE_FLYING)||(crate.state==CRATE_WON) ||(crate.state==CRATE_STARTING))
			ropelinks[i].position.vy+=(i*2);
		else
			ropelinks[i].position.vy-=(i*2);
	}

	// Put Mrs Tweedy at the position of the last link in the rope
	mrstweedy.actor->position.vx = ropelinks[CRATE_NUMLINKS-1].position.vx;
	mrstweedy.actor->position.vy = ropelinks[CRATE_NUMLINKS-1].position.vy;
	mrstweedy.actor->position.vz = ropelinks[CRATE_NUMLINKS-1].position.vz;
}






//----------------------------------------------------------------------------------
//	Function 	: static void crateDrawBriefing(void)
//	Purpose 	: Draw a short briefing screen at the start of the flymachine
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateDrawBriefing(void)
{
	fontCentredMultiLine(font,0,-94,318,TEXTSTR(TEXTSTR_SUB_CRATE_BRIEFING1),128,128,128);
	fontCentredMultiLine(font,0,-58,318,TEXTSTR(TEXTSTR_SUB_CRATE_BRIEFING2),128,128,128);
	fontCentredMultiLine(font,0,-16,318,TEXTSTR(TEXTSTR_SUB_CRATE_BRIEFING3),128,128,128);

	overlayLine(-160,-96,160,-96,0xffffff);			// top
	overlayLine(-160,44,160,44,0x888888);			// bottom
	overlayLine(-160,-96,-160,44,0xffffff);			// left
	overlayLine(160,-96,160,44,0x888888);			// right
	crateOverlayAlpha(-160, -96, 320, 140, 0x000088, 0x20202020);
}





//----------------------------------------------------------------------------------
//	Function 	: static void crateStopSamples(void)
//	Purpose 	: Stop all samples from playing
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------


static void crateStopSamples(void)
{
	sfxStopSample(cr_axe1,-1);
	sfxStopSample(cr_axe2,-1);
	sfxStopSample(cr_hit1,-1);
	sfxStopSample(cr_aargh,-1);
	sfxStopSample(cr_ouch,-1);
	sfxStopSample(cr_windy,-1);
	sfxStopSample(cr_wings,-1);
}
