/******************************************************************************************
	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 AXE_SPEED (160*4096)		// Speed of the axe...
#if PALMODE==YES
#define CLIMB_DELAY (100)		// Frames delay between climbing attempts
#else
#define CLIMB_DELAY (120)
#endif



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
	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				

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;						// 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
	VECTOR linkoffset;						// Distance on the rope from her current link
	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
	int currentlink;						// The link she's holding on to
	int climbcounter;						// Delay between climbing attempts
	int distancecounter;					// distance from the link she's on - 0 to 10
} 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
	int counter;
	unsigned char active;					// Whether or not the axe is currently airborne
} 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;

unsigned long crateFrameCounter;

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

extern int pause_levelplaying;

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;
	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);

	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]);

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

	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, MRSTWEEDY_HANGING, 1, 0, 256, 1, 0);
	actorInitAnim(axe.actor);										
	actorAnimate(axe.actor, 0, 1, 0, 512, 1, 0);

	mrstweedy.actor->position.vx = 0;
	mrstweedy.actor->position.vy = -300;
	mrstweedy.actor->position.vz = -35000;
	mrstweedy.state = MRSTWEEDY_HANGING;
	mrstweedy.currentanimsegment = 0;
	mrstweedy.desiredanimsegment = 0;
	mrstweedy.currentlink = (CRATE_NUMLINKS - (CRATE_NUMLINKS/2));		// Start off on the middle link...
	mrstweedy.climbcounter = 0;
	mrstweedy.linkoffset.vx = 0;
	mrstweedy.linkoffset.vy = 0;
	mrstweedy.linkoffset.vz = 0;
	
	crate.desiredangle.vy = 0;
	crate.desiredangle.vx = 0;
	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;

	soundPlaySample(cr_windy, 128, 1300);
}






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

static void crateCycle(void)
{
	dsEvent_Update();


	// Increase the framecounter

	crateFrameCounter ++;

	// Animate the Scenics

	scenicCycle();

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

	switch(crate.state)
	{
		case CRATE_STARTING:
			crateMoveCrate();
			crateMoveCamera();
			crateMoveTweedy();
			crateMoveRope();
			break;	
		case CRATE_FLYING:
			if(pReadPad->debounce[0] & PAD_START)
			{
				pause_levelplaying = LEVNUMBER_A4_FLYMACHINE;
				pauseStart();
				return;
			}

			crateCollision();
			crateMoveCrate();
			crateMoveCamera();
			crateMoveTweedy();
			crateMoveAxe();
			crateMoveRope();
			crateUpdatePartExplosion();
			break;

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


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

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

		default:
			break;
	}
}






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

static void crateDraw(void)
{
	crateDrawStars();
	crateDrawMap();

	scenicDrawAll();
	crateDrawSprites();
	crateDrawModels();
	
	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)
{
	crateFree();
	optionsStart(0);
}




//----------------------------------------------------------------------------------
//	Function 	: void crateFree(void)
//	Purpose 	: Free all memory used
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------



void crateFree(void)		// Cant be static'd as we need to call it from pause.c
{
	crateStopSamples();
	scenicEnd();
	actorFree(mrstweedy.actor);
	actorFree(axe.actor);
	actorFree(crate.actor);
	mapviewReleaseMap();
	enemyDeleteAll();
	psiDestroy();
}





//----------------------------------------------------------------------------------
//	Function 	: void crateEnd
//	Purpose 	: Free all memory used and go to the end-of-game sequence
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateEndSuccess(void)
{
	crateFree();
	levflowCrateSuccess();
}





//----------------------------------------------------------------------------------
//	Function 	: void crateEndRestart
//	Purpose 	: Free all memory used and restart the Flying machine game
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateEndRestart(void)
{
	crateFree();
	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(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((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 crateDrawStars(void)
//	Purpose 	: Draw the sky - A gouraud quad for the sky and some triangles for stars
//	Parameters 	: None
//	Returns 	: None
//	Info 		:
//----------------------------------------------------------------------------------

static void crateDrawStars(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);
	}
}


//----------------------------------------------------------------------------------
//	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;
	int angle;

//	debugPrintf("State: %d   \n",mrstweedy.state);
	if(crate.state!=CRATE_WON)
	{
		switch(mrstweedy.state)
		{
			case MRSTWEEDY_HANGING:
				
				// Check to see if we want to throw a new axe
	
//				if(!(crateFrameCounter%CRATE_AXEFREQUENCY))
//				{
//					mrstweedy.state = MRSTWEEDY_THROWING;
//					mrstweedy.desiredanimsegment = MRSTWEEDY_THROWING;
//				}

				// See if we want to climb now
				if(mrstweedy.climbcounter)
					mrstweedy.climbcounter--;
				else
				{
					mrstweedy.distancecounter = 40;
					mrstweedy.linkoffset.vx = 0;
					mrstweedy.linkoffset.vy = 0;
					mrstweedy.state = MRSTWEEDY_CLIMBING;
					mrstweedy.climbcounter = CLIMB_DELAY;
					mrstweedy.desiredanimsegment = MRSTWEEDY_CLIMBING;
				}
				break;

			case MRSTWEEDY_THROWING:
				if(!axe.active)
				{
					mrstweedy.desiredanimsegment = 0;
					mrstweedy.state = MRSTWEEDY_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;

					axe.currentangle.vy = (mrstweedy.actor->psiData.object->rotate.vy)+2048;
				
					// Give the Axe the same (but slightly higher) velocities as the crate
					axe.velocity.vz = (crate.speed.vz/3840)*4096;
					axe.velocity.vx = (crate.speed.vx/3840)*4096;
					//debugPrintf("Axe Speed X: %d   Y: %d   Z: %d\n",axe.velocity.vx,axe.velocity.vy,axe.velocity.vz);
				}

				break;
						
			case MRSTWEEDY_BOUNCING:
			
				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 = MRSTWEEDY_HANGING;
					mrstweedy.desiredanimsegment = MRSTWEEDY_HANGING;
				}
				
				break;

			case MRSTWEEDY_HITBYEGG:
				
				break;

			case MRSTWEEDY_CLIMBING:
				{
					VECTOR myvec;
					VECTOR myothervec;

					int mag;


					if(mrstweedy.distancecounter>0)
						mrstweedy.distancecounter--;

					// Ok, get the distance from her current link to the link shes going to
					myvec.vx = ropelinks[mrstweedy.currentlink-1].position.vx - ropelinks[mrstweedy.currentlink].position.vx;
					myvec.vy = ropelinks[mrstweedy.currentlink-1].position.vy - ropelinks[mrstweedy.currentlink].position.vy;
					myvec.vz = ropelinks[mrstweedy.currentlink-1].position.vz - ropelinks[mrstweedy.currentlink].position.vz;

					myothervec.vx = myvec.vx;
					myothervec.vy = myvec.vy;
					myothervec.vz = myvec.vz;

					mag = Magnitude(&myvec);

				//	debugPrintf("Distance from her next link: %d",mag);

					// Scale this vector by whatever, to get the tweedy's offset from her link... she moves in ten steps

					SCALEVECTOR(&myvec,(4096/40)*mrstweedy.distancecounter);

					mrstweedy.linkoffset.vx = myothervec.vx - myvec.vx;
					mrstweedy.linkoffset.vy = myothervec.vy - myvec.vy;
					mrstweedy.linkoffset.vz = myothervec.vz - myvec.vz;


				//	debugPrintf("Mrs T Link offset: x:%d y:%d z:%d\n",mrstweedy.linkoffset.vx,mrstweedy.linkoffset.vy,mrstweedy.linkoffset.vz); 
					mag = Magnitude(&myvec);


				//	debugPrintf("Distance from her next link after: %d",mag);


					if(mag==0)
					{
						mrstweedy.currentlink--;
						mrstweedy.linkoffset.vx = 0;
						mrstweedy.linkoffset.vy = 0;
						mrstweedy.linkoffset.vz = 0;
						mrstweedy.state = MRSTWEEDY_HANGING;
						mrstweedy.desiredanimsegment = MRSTWEEDY_HANGING;
					}
				}

				break;

			default:
				
				break;
				
		}
		
		// Put Mrs Tweedy at the position of the her link in the rope, plus her offset...

		mrstweedy.actor->position.vx = ropelinks[mrstweedy.currentlink+1].position.vx + (mrstweedy.linkoffset.vx);
		mrstweedy.actor->position.vy = ropelinks[mrstweedy.currentlink+1].position.vy + (mrstweedy.linkoffset.vy);
		mrstweedy.actor->position.vz = ropelinks[mrstweedy.currentlink+1].position.vz + (mrstweedy.linkoffset.vz);

		// Set Mrs Tweedy's rotation about X and Z according to how far left / right / above / below she is of 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;

		if(mrstweedy.state!=MRSTWEEDY_BOUNCING)
			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));


		// Update the Tweedy's animation 

		if (mrstweedy.actor->animation.animData[0].segment!=mrstweedy.desiredanimsegment)	
		{
			if(mrstweedy.desiredanimsegment==MRSTWEEDY_CLIMBING)
				actorAnimate(mrstweedy.actor, mrstweedy.desiredanimsegment, 1, 0, 256, 1, 512);
			else
				actorAnimate(mrstweedy.actor, mrstweedy.desiredanimsegment, 1, 0, 512, 1, 512);
		}
	}
}






//----------------------------------------------------------------------------------
//	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;

	axe.actor->psiData.object->rotate.vy = axe.currentangle.vy;
	
	axe.actor->position.vx += axe.velocity.vx/64;
	axe.actor->position.vy += axe.velocity.vy/64;
	axe.actor->position.vz += axe.velocity.vz/64;

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

		soundPlaySample(cr_axe1, 255,12000);		
		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);

	// 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();
}






//----------------------------------------------------------------------------------
//	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!=MRSTWEEDY_BOUNCING)
	{
		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
				{
					// Make her drop down a link...
					mrstweedy.currentlink++;
					if(mrstweedy.currentlink==CRATE_NUMLINKS)
					{
						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;
					}

					soundPlaySample(cr_ouch, 128, 1300);
					dsEvent_Add(0, 0, 1, 180, 0, 7, NULL);
					mrstweedy.state = MRSTWEEDY_BOUNCING;
					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 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 - 3600;

			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*3);
		else
			ropelinks[i].position.vy-=(i*3);
	}

	// Put Mrs Tweedy at the position of the last link in the rope
	mrstweedy.actor->position.vx = ropelinks[mrstweedy.currentlink].position.vx + (mrstweedy.linkoffset.vx);
	mrstweedy.actor->position.vy = ropelinks[mrstweedy.currentlink].position.vy + (mrstweedy.linkoffset.vy);
	mrstweedy.actor->position.vz = ropelinks[mrstweedy.currentlink].position.vz + (mrstweedy.linkoffset.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);
}
