/****************************************************************************
*
* MINECART.C
*
* PROGRAMMER : Chris
*    VERSION : 00.00
*    CREATED : 11/08/98
*   MODIFIED : 11/08/98
*	   TABS : 5
*
* DESCRIPTION
*	Mine-cart controller
*
*****************************************************************************/

/*---------------------------------------------------------------------------
 * Import Headers
\*--------------------------------------------------------------------------*/

#ifdef PSX_VERSION
#include <stdio.h>
#include "../spec_psx/typedefs.h"
#include "../spec_psx/psxinput.h"
#include "../spec_psx/maths.h"
#include "../spec_psx/malloc.h"
#include "../spec_psx/specific.h"
#define CARTINFO_ONLY
#else
#include "../specific/standard.h"
#include "../specific/stypes.h"
#include "../specific/input.h"
#include "../specific/global.h"
#endif

#include "objects.h"
#include "lara.h"
#include "laraanim.h"
#include "laraflar.h"
#include "sphere.h"
#include "control.h"
#include "input.h"
#include "camera.h"
#include "effect2.h"
#include "minecart.h"
#include "text.h"
#include "sound.h"

/*---------------------------------------------------------------------------
 * Constants
\*--------------------------------------------------------------------------*/

//#define DEBUG_MINECART

#define CART_RADIUS	100
#define CART_HEIGHT 768
#define CART_NHITS	25

#define CART_TO_BADDIE_RADIUS	256

/* -------- dynamics */

#define CART_DEC		-0x600
#define CART_MIN_SPEED	0xa00
#define CART_MIN_VEL	32

#define TURN_DEATH_VEL	128

#define CART_FWD_GRAD	-128
#define CART_BACK_GRAD	128

#define CART_JUMP_VEL	0xfc00
#define CART_GRAVITY	0x401
#define MAX_CART_YVEL	0x3f00

#define TERMINAL_ANGLE	4096

/* -------- animation states */

enum {
	CART_GETIN,
	CART_GETOUT,
	CART_GETOUTL,
	CART_GETOUTR,
	CART_STILL,
	CART_DUCK,
	CART_MOVE,
	CART_RIGHT,
	CART_HARDLEFT,
	CART_LEFT,
	CART_HARDRIGHT,
	CART_BRAKE,
	CART_FWD,
	CART_BACK,
	CART_TURNDEATH,
	CART_FALLDEATH,
	CART_WALLDEATH,
	CART_HIT,
	CART_USE,
	CART_BRAKING
};

/* -------- animations */

#define GF2(a)	(anims[objects[VEHICLE_ANIM].anim_index+a].frame_base)

#define CART_GETINL_A	0
#define CART_OUTL_A		1
#define CART_PICK_A		5
#define CART_PICK_F		GF2(CART_PICK_A)
#define CART_USE_A		6
#define CART_DROP_A		7
#define CART_DROP_F		GF2(CART_DROP_A)
#define CART_WALLDEATH_A	23
#define CART_TURNDEATH_B	30
#define CART_TURNDEATH_A	31
#define CART_TURNDEATH_F	GF2(30)
#define CART_HIT_A		34
#define CART_HIT_F 		GF2(CART_HIT_A)
#define CART_GETINR_A	46
#define CART_OUTR_A		47

#define GETOFF_DIST 330		// minimum collision distance

/*---------------------------------------------------------------------------
 * Type Definitions
\*--------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------
 * Externals
\*--------------------------------------------------------------------------*/

extern int InfoFlag;
extern char exit_message[];

/*---------------------------------------------------------------------------
 * Globals
\*--------------------------------------------------------------------------*/

#if defined(PSX_VERSION) && defined(RELOC)

void MineCartInitialise(sint16 item_number);
void MineCartCollision(sint16 item_number, ITEM_INFO *l, COLL_INFO *coll);
int MineCartControl();

void *func[] __attribute__((section(".header"))) = {
	MineCartInitialise,
	MineCartCollision,
	MineCartControl
};

#endif

/*---------------------------------------------------------------------------
 * Local Functions
\*--------------------------------------------------------------------------*/

/****************************************************************************
* TestHeight
*
* SYNOPSIS
*	sint32 TestHeight(ITEM_INFO *item, sint32 x, sint32 z)
*
* PURPOSE
*	Returns the height at an offset relative to the origin.
*
* INPUTS
*	item		= ptr to vehicle item
* 	x		= x offset
*	z		= z offset
*	pos		= ptr to destination vector
*
* RETURNS
*	Height at required co-ordinate
*/
static sint32 TestHeight(ITEM_INFO *v, sint32 x, sint32 z)
{
	sint32 s, c;
	PHD_VECTOR pos;
	FLOOR_INFO *floor;
	sint16 room_number;

	c = phd_cos(v->pos.y_rot);
	s = phd_sin(v->pos.y_rot);

	pos.x = v->pos.x_pos + (((z * s) + (x * c)) >> W2V_SHIFT);
	pos.y = v->pos.y_pos - (z * phd_sin(v->pos.x_rot) >> W2V_SHIFT)
					 + (x * phd_sin(v->pos.z_rot) >> W2V_SHIFT);
	pos.z = v->pos.z_pos + (((z * c) - (x * s)) >> W2V_SHIFT);

	room_number = v->room_number;
	floor = GetFloor(pos.x, pos.y, pos.z, &room_number);
	return GetHeight(floor, pos.x, pos.y, pos.z);
}

sint16 GetCollision(ITEM_INFO *v, sint16 ang, sint32 dist, sint16 *ceiling)
{
	sint16 room;
	FLOOR_INFO *floor;
	sint32 x, y, z, height, cheight;

	x = v->pos.x_pos + ((phd_sin(ang) * dist) >> W2V_SHIFT);
	y = v->pos.y_pos - LARA_HITE;
	z = v->pos.z_pos + ((phd_cos(ang) * dist) >> W2V_SHIFT);

	room = v->room_number;
	floor = GetFloor(x, y, z, &room);
	height = GetHeight(floor, x, y, z);
	cheight = GetCeiling(floor, x, y, z);

	*ceiling = ((sint16)cheight);

	if (height != NO_HEIGHT)
		height -= v->pos.y_pos;

	return ((sint16)height);
}

/****************************************************************************
* GetInMineCart
*
* SYNOPSIS
*	int GetInMineCart(sint16 item_number, COLL_INFO *coll)
*
* PURPOSE
*	Determines whether Lara can get into the minecart
*
* INPUTS
*	item_number	= vehicle item #
*	coll			= ptr to Lara's collision structure
*
* RETURNS
*	!0 = can use vehicle
*/
static int GetInMineCart(ITEM_INFO *v, ITEM_INFO *l, COLL_INFO *coll)
{
	int dist;
	sint32 x, z;
	FLOOR_INFO *floor;
	sint16 room_number;

	if ((!(input & IN_ACTION))
	||  (lara.gun_status != LG_ARMLESS)
	||  (l->gravity_status))
		return 0;

/* -------- is Lara close enough to use the vehicle */

	if (!TestBoundsCollide(v, l, coll->radius))
		return 0;

	if (!TestCollision(v, l))
		return 0;

	x = l->pos.x_pos - v->pos.x_pos;
	z = l->pos.z_pos - v->pos.z_pos;

	dist = (x * x) + (z * z);

	if (dist > 200000)
		return 0;

/*
 * SONY BUG FIX 10000001: If player stops in exactly right place and buries
 * skidoo in avalanche, then they can get on it and crash the game
 *
 */
	room_number = v->room_number;
	floor = GetFloor(v->pos.x_pos, v->pos.y_pos, v->pos.z_pos, &room_number);
	if (GetHeight(floor, v->pos.x_pos, v->pos.y_pos, v->pos.z_pos) < -32000)
		return 0;

	return 1;
}


/*
 * Check if getting off skidoo here is possible in the direction required by
 * player
 */
static int CanGetOut(int direction)
{
	ITEM_INFO *v;
	FLOOR_INFO *floor;
	sint16 room_number, angle;
	int x, y, z, height, ceiling;

	v = &items[lara.skidoo];

	if (direction < 0)
		angle = v->pos.y_rot + 0x4000;
	else
		angle = v->pos.y_rot - 0x4000;


	x = v->pos.x_pos - (GETOFF_DIST * phd_sin(angle) >> W2V_SHIFT);
	y = v->pos.y_pos;
	z = v->pos.z_pos - (GETOFF_DIST * phd_cos(angle) >> W2V_SHIFT);

	room_number = v->room_number;
	floor = GetFloor(x, y, z, &room_number);

	height = GetHeight(floor, x, y, z);

	if ((height_type == BIG_SLOPE)
	||  (height_type == DIAGONAL)
	||  (height == NO_HEIGHT))
		return 0;

	if (ABS(height - v->pos.y_pos) > WALL_L/2)
		return 0;

	ceiling = GetCeiling(floor, x, y, z);

	if ((ceiling - v->pos.y_pos > -LARA_HITE)
	||  (height - ceiling < LARA_HITE))
		return 0;

	return 1;
}

/*************************************************************************************
 *			Handle Collision Of Lara with Nearby Items
 ************************************************************************************/
static void CartToBaddieCollision(ITEM_INFO *v)
{
	int i;
	sint16 roomies[20], numroom, *door;

#define TARGET_DIST		(WALL_L*2)              // Up to this Distance more Complicated checks are made

/* -------- get nearby rooms */

	numroom = 1;
	roomies[0] = v->room_number;

	if ((door = room[v->room_number].door))
	{
		for ( i=(int)*(door++) ; i>0 ; i-- )
		{
			roomies[numroom++] = *(door);
			door += 16;
		}
	}

/* -------- collide with all baddies in these rooms */

	for ( i=0 ; i<numroom ; i++ )
	{
		sint16 item_num;

		item_num = room[roomies[i]].item_number;	// Only do collision with Items on Draw list

		while (item_num != NO_ITEM)
		{
			sint16 nex;
			ITEM_INFO	*item;

			item = &items[item_num];
			nex = item->next_item;				// Store next Item in list as Current may be deleted!!!

			if ((item->collidable)
			&&  (item->status != INVISIBLE))
			{
				OBJECT_INFO *object;

				object = &objects[item->object_number];

				if ((object->collision)
				&& ((object->intelligent)
				||  (item->object_number == ANIMATING2)))
				{
					int x, y, z;

					x = v->pos.x_pos - item->pos.x_pos;
					y = v->pos.y_pos - item->pos.y_pos;
					z = v->pos.z_pos - item->pos.z_pos;

					if ((x > -TARGET_DIST)
					&&  (x < TARGET_DIST)
					&&  (z > -TARGET_DIST)
					&&  (z < TARGET_DIST)
					&&  (y > -TARGET_DIST)
					&&  (y < TARGET_DIST))
					{
						if (TestBoundsCollide(item, v, CART_TO_BADDIE_RADIUS))
						{
						/* -------- trigger points */

							if (item->object_number == ANIMATING2)
							{
								if ((item->frame_number == anims[item->anim_number].frame_base)
								&&  (lara_item->current_anim_state == CART_USE)
								&&  (lara_item->anim_number == objects[VEHICLE_ANIM].anim_index + CART_USE_A))
								{
									sint16 frame;
									FLOOR_INFO *floor;
									sint16 room_number;

									frame = lara_item->frame_number - anims[lara_item->anim_number].frame_base;

									if ((frame >= 12) && (frame <= 22))
									{
										SoundEffect(220, &item->pos, SFX_ALWAYS);

										room_number = item->room_number;
										floor = GetFloor(item->pos.x_pos, item->pos.y_pos, item->pos.z_pos, &room_number);
										GetHeight(floor, item->pos.x_pos, item->pos.y_pos, item->pos.z_pos);
										TestTriggers(trigger_index, 1);

										item->frame_number++;
									}
								}
							}

						/* -------- kill any baddies on track */

							else
							{
								DoLotsOfBlood(item->pos.x_pos, v->pos.y_pos-STEP_L, item->pos.z_pos, v->speed, v->pos.y_rot, item->room_number, 3);
								item->hit_points = 0;
							}
						}
					}
				}
			}
			item_num = nex;
		}
	}

#undef TARGET_DIST
}

/****************************************************************************
* MoveCart
*
* SYNOPSIS
*	void MoveCart(ITEM_INFO *v, ITEM_INFO *l, CARTINFO *cart)
*
* PURPOSE
*	Updates Lara's animations according to the player's actions
*
* INPUTS
*	v		= ptr to vehicle's item
*	l		= ptr to Lara's item
*	cart		= ptr to vehicle's specifc data
*
* RETURNS
*	Nothing
*/
static void MoveCart(ITEM_INFO *v, ITEM_INFO *l, CARTINFO *cart)
{
	sint16 val;

/* -------- check for end of track */

	if (cart->StopDelay)
		cart->StopDelay--;

	if (((lara.MineL)
	&&   (lara.MineR)
	&&   (!cart->StopDelay))
	&& (((v->pos.x_pos & 0x380) == 512)
	||  ((v->pos.z_pos & 0x380) == 512)))
	{
		if (cart->Speed < 0xf000)	// can't do this - bastard
		{
			cart->Flags |= CF_STOPPED|CF_CONTROL;
			cart->Speed = v->speed = 0;
			return;
		}
		else
			cart->StopDelay = 16;
	}

/* -------- initiate turns */

	if ((lara.MineL || lara.MineR)
	&&  (!(lara.MineL && lara.MineR))
	&&  (!cart->StopDelay)
	&&  (!(cart->Flags & (CF_TURNINGL|CF_TURNINGR))))
	{
		sint16 ang;
		uint16 rot = (((uint16)v->pos.y_rot) >> 14) | (lara.MineL << 2);

		switch (rot)
		{
			case 0:
				cart->TurnX = (v->pos.x_pos + 4096) & ~1023;
				cart->TurnZ = v->pos.z_pos & ~1023;
				break;
			case 1:
				cart->TurnX = v->pos.x_pos & ~1023;
				cart->TurnZ = (v->pos.z_pos - 4096) | 1023;
				break;
			case 2:
				cart->TurnX = (v->pos.x_pos - 4096) | 1023;
				cart->TurnZ = v->pos.z_pos | 1023;
				break;
			case 3:
				cart->TurnX = v->pos.x_pos | 1023;
				cart->TurnZ = (v->pos.z_pos + 4096) & ~1023;
				break;

			case 4:
				cart->TurnX = (v->pos.x_pos - 4096) | 1023;
				cart->TurnZ = v->pos.z_pos & ~1023;
				break;
			case 5:
				cart->TurnX = v->pos.x_pos & ~1023;
				cart->TurnZ = (v->pos.z_pos + 4096) & ~1023;
				break;
			case 6:
				cart->TurnX = (v->pos.x_pos + 4096) & ~1023;
				cart->TurnZ = v->pos.z_pos | 1023;
				break;
			case 7:
				cart->TurnX = v->pos.x_pos | 1023;
				cart->TurnZ = (v->pos.z_pos - 4096) | 1023;
				break;
		}

		ang = mGetAngle(v->pos.x_pos, v->pos.z_pos, cart->TurnX, cart->TurnZ) & 0x3fff;

		if (rot < 4)
		{
			cart->TurnRot = v->pos.y_rot;
			cart->TurnLen = ang;
		}
		else
		{
			cart->TurnRot = v->pos.y_rot;

			if (ang)
				ang = 16384 - ang;

			cart->TurnLen = ang;
		}

		cart->Flags |= (lara.MineL) ? CF_TURNINGL : CF_TURNINGR;
	}

/* -------- move vehicle */

//	if (input & IN_BACK)
//		cart->Speed += CART_DEC;

	if (cart->Speed < CART_MIN_SPEED)
		cart->Speed = CART_MIN_SPEED;

	cart->Speed += (-cart->Gradient << 2);

	if ((v->speed = cart->Speed >> 8) < CART_MIN_VEL)
	{
		v->speed = CART_MIN_VEL;

		StopSoundEffect(209);

		if (cart->YVel)
			StopSoundEffect(210);
		else
			SoundEffect(210, &v->pos, SFX_ALWAYS);
	}
	else
	{
		StopSoundEffect(210);

		if (cart->YVel)
			StopSoundEffect(209);
		else
			SoundEffect(209, &v->pos, (SFX_ALWAYS|PITCH_SHIFT)+0x1000000+(v->speed<<15));
	}

#ifdef DEBUG_MINECART
	sprintf(exit_message, "Gradient: %d", cart->Gradient);
	PrintDbug(1, 10, exit_message);
	sprintf(exit_message, "Speed: %x", cart->Speed);
	PrintDbug(1, 11, exit_message);
	sprintf(exit_message, "Velocity: %d", v->speed);
	PrintDbug(1, 12, exit_message);
#endif

/* -------- move cart around corners */

	if (cart->Flags & (CF_TURNINGL|CF_TURNINGR))
	{
		sint32 x, z;
		uint16 quad, deg;

		if ((cart->TurnLen += (v->speed*3)) > (ONE_DEGREE*90))
		{
			if (cart->Flags & CF_TURNINGL)
				v->pos.y_rot = cart->TurnRot - 16384;
			else
				v->pos.y_rot = cart->TurnRot + 16384;

			cart->Flags &= ~(CF_TURNINGL|CF_TURNINGR);
		}
		else
		{
			if (cart->Flags & CF_TURNINGL)
				v->pos.y_rot = cart->TurnRot - cart->TurnLen;
			else
				v->pos.y_rot = cart->TurnRot + cart->TurnLen;
		}

		if (cart->Flags & (CF_TURNINGL|CF_TURNINGR))
		{
			quad = ((uint16)v->pos.y_rot) >> 14;
			deg = v->pos.y_rot & 16383;

			switch (quad)
			{
				case 0:
					x = -phd_cos(deg);
					z = phd_sin(deg);
					break;
				case 1:
					x = phd_sin(deg);
					z = phd_cos(deg);
					break;
				case 2:
					x = phd_cos(deg);
					z = -phd_sin(deg);
					break;
				default:
					x = -phd_sin(deg);
					z = -phd_cos(deg);
					break;
			}

			if (cart->Flags & CF_TURNINGL)
			{
				x = -x;
				z = -z;
			}

			v->pos.x_pos = cart->TurnX + ((x * 3584) >> W2V_SHIFT);
			v->pos.z_pos = cart->TurnZ + ((z * 3584) >> W2V_SHIFT);
		}
	}

/* -------- move cart normally */

	else
	{
		v->pos.x_pos += (v->speed * phd_sin(v->pos.y_rot)) >> W2V_SHIFT;
		v->pos.z_pos += (v->speed * phd_cos(v->pos.y_rot)) >> W2V_SHIFT;
	}

/* -------- tilt cart on slopes */

	cart->MidPos   = TestHeight(v, 0,   0);

	if (!cart->YVel)
	{
		cart->FrontPos = TestHeight(v, 0, 256);
		cart->Gradient = cart->MidPos - cart->FrontPos;
		v->pos.y_pos = cart->MidPos;
	}
	else
	{
		if (v->pos.y_pos > cart->MidPos)
		{
			if (cart->YVel > 0)
				SoundEffect(202, &v->pos, SFX_ALWAYS);

			v->pos.y_pos = cart->MidPos;
			cart->YVel = 0;
		}
		else
		{
			cart->YVel += CART_GRAVITY;

			if (cart->YVel > MAX_CART_YVEL)
				cart->YVel = MAX_CART_YVEL;

			v->pos.y_pos += cart->YVel >> 8;
		}
	}

#ifdef DEBUG_MINECART
	sprintf(exit_message, "YVel: %d", cart->YVel);
	PrintDbug(1, 13, exit_message);
#endif

	v->pos.x_rot = cart->Gradient << 5;

/* -------- tilt cart around corners */

//## if zrot exceeds +- 4096 then crash

	val = v->pos.y_rot & 16383;

	if (cart->Flags & (CF_TURNINGL|CF_TURNINGR))
	{
		if (cart->Flags & CF_TURNINGR)
			v->pos.z_rot = -(val * v->speed) >> 9;
		else
			v->pos.z_rot = ((16384-val) * v->speed) >> 9;
	}
	else
		v->pos.z_rot -= v->pos.z_rot >> 3;

//	v->pos.z_rot = 0;
}
/****************************************************************************
* DoUserInput
*
* SYNOPSIS
*	void DoUserInput(ITEM_INFO *v, ITEM_INFO *l, CARTINFO *cart)
*
* PURPOSE
*	Updates Lara's animations according to the player's actions
*
* INPUTS
*	v		= ptr to vehicle's item
*	l		= ptr to Lara's item
*	cart		= ptr to vehicle's specifc data
*
* RETURNS
*	Nothing
*/
static void DoUserInput(ITEM_INFO *v, ITEM_INFO *l, CARTINFO *cart)
{
	sint16 fh, ch;

	switch (l->current_anim_state)
	{
	/* -------------------- */
		case CART_MOVE:
	/* -------------------- */
			if (input & IN_ACTION)
				l->goal_anim_state = CART_USE;

			else if (input & IN_DUCK)
				l->goal_anim_state = CART_DUCK;

			else if (input & IN_JUMP)
				l->goal_anim_state = CART_BRAKE;

		/* -------- determine anim according to cart dynamics */

			else if ((cart->Speed == CART_MIN_VEL)
				||  (cart->Flags & CF_STOPPED))
					l->goal_anim_state = CART_STILL;

			else if (cart->Gradient < CART_FWD_GRAD)
				l->goal_anim_state = CART_FWD;

			else if (cart->Gradient > CART_BACK_GRAD)
				l->goal_anim_state = CART_BACK;

			else if (input & IN_LEFT)
				l->goal_anim_state = CART_LEFT;

			else if (input & IN_RIGHT)
				l->goal_anim_state = CART_RIGHT;

			break;
	/* -------------------- */
		case CART_FWD:
	/* -------------------- */
			if (input & IN_ACTION)
				l->goal_anim_state = CART_USE;

			else if (input & IN_DUCK)
				l->goal_anim_state = CART_DUCK;

			else if (input & IN_JUMP)
				l->goal_anim_state = CART_BRAKE;

		/* -------- determine anim according to cart dynamics */

			else if (cart->Gradient > CART_FWD_GRAD)
				l->goal_anim_state = CART_MOVE;

			break;
	/* -------------------- */
		case CART_BACK:
	/* -------------------- */
			if (input & IN_ACTION)
				l->goal_anim_state = CART_USE;

			else if (input & IN_DUCK)
				l->goal_anim_state = CART_DUCK;

			else if (input & IN_JUMP)
				l->goal_anim_state = CART_BRAKE;

		/* -------- determine anim according to cart dynamics */

			else if (cart->Gradient < CART_BACK_GRAD)
				l->goal_anim_state = CART_MOVE;

			break;
	/* -------------------- */
		case CART_LEFT:
	/* -------------------- */
			if (input & IN_ACTION)
				l->goal_anim_state = CART_USE;

			else if (input & IN_DUCK)
				l->goal_anim_state = CART_DUCK;

			else if (input & IN_JUMP)
				l->goal_anim_state = CART_BRAKE;

		/* -------- determine anim according to cart dynamics */

			if (!(input & IN_LEFT))
				l->goal_anim_state = CART_MOVE;

			break;
	/* -------------------- */
		case CART_RIGHT:
	/* -------------------- */
			if (input & IN_ACTION)
				l->goal_anim_state = CART_USE;

			else if (input & IN_DUCK)
				l->goal_anim_state = CART_DUCK;

			else if (input & IN_JUMP)
				l->goal_anim_state = CART_BRAKE;

		/* -------- determine anim according to cart dynamics */

			if (!(input & IN_RIGHT))
				l->goal_anim_state = CART_MOVE;

			break;
	/* -------------------- */
		case CART_STILL:
	/* -------------------- */
			if (!(cart->Flags & CF_CONTROL))
			{
				SoundEffect(211, &v->pos, SFX_ALWAYS);
				cart->Flags |= CF_CONTROL;
				cart->StopDelay = 64;
			}

			if ((input & IN_ROLL) && (cart->Flags & CF_STOPPED))
			{
				if ((input & IN_LEFT) && (CanGetOut(-1)))
				{
					l->goal_anim_state = CART_GETOUT;
					cart->Flags &= ~CF_RDIR;
				}

				else if ((input & IN_RIGHT) && (CanGetOut(1)))
				{
					l->goal_anim_state = CART_GETOUT;
					cart->Flags |= CF_RDIR;
				}
			}

			if (input & IN_DUCK)
				l->goal_anim_state = CART_DUCK;

		/* -------- determine anim according to cart dynamics */

			else if (cart->Speed > CART_MIN_VEL)
				l->goal_anim_state = CART_MOVE;

			break;
	/* -------------------- */
		case CART_DUCK:
	/* -------------------- */
			if (input & IN_ACTION)
				l->goal_anim_state = CART_USE;

			else if (input & IN_JUMP)
				l->goal_anim_state = CART_BRAKE;

			else if (!(input & IN_DUCK))
				l->goal_anim_state = CART_STILL;
			break;
	/* -------------------- */
		case CART_USE:
	/* -------------------- */
			l->goal_anim_state = CART_MOVE;
			break;
	/* -------------------- */
		case CART_BRAKING:
	/* -------------------- */
			if (input & IN_DUCK)
			{
				l->goal_anim_state = CART_DUCK;
				StopSoundEffect(219);
			}

			else if ((!(input & IN_JUMP))
				|| (cart->Flags & CF_STOPPED))
				{
					l->goal_anim_state = CART_MOVE;
					StopSoundEffect(219);
				}

			else
			{
				cart->Speed += CART_DEC;
				SoundEffect(219, &l->pos, SFX_ALWAYS);
			}
			break;
	/* -------------------- */
		case CART_BRAKE:
	/* -------------------- */
			l->goal_anim_state = CART_BRAKING;
			break;
	/* -------------------- */
		case CART_GETOUT:
	/* -------------------- */
			if (l->anim_number == objects[VEHICLE_ANIM].anim_index + CART_DROP_A)
			{
				if ((l->frame_number == CART_DROP_F+20)
				&&  (cart->Flags & CF_MESH))
				{
					sint16 *tmp;

					tmp = lara.mesh_ptrs[HAND_R];

					lara.mesh_ptrs[HAND_R] = meshes[objects[VEHICLE_ANIM].mesh_index + HAND_R];
					meshes[objects[VEHICLE_ANIM].mesh_index + HAND_R] = tmp;

					cart->Flags &= ~CF_MESH;
				}

				if (cart->Flags & CF_RDIR)
					l->goal_anim_state = CART_GETOUTR;
				else
					l->goal_anim_state = CART_GETOUTL;
			}

			break;
	/* -------------------- */
		case CART_GETOUTL:
	/* -------------------- */
			if ((l->anim_number == objects[VEHICLE_ANIM].anim_index + CART_OUTL_A)
			&&  (l->frame_number == anims[l->anim_number].frame_end))
			{
				PHD_VECTOR vec = { 0, 640, 0};

				GetLaraHandAbsPosition(&vec, LARA_HIPS);
				l->pos.x_pos = vec.x;
				l->pos.y_pos = vec.y;
				l->pos.z_pos = vec.z;
				l->pos.x_rot = 0;
				l->pos.y_rot = v->pos.y_rot + 0x4000;
				l->pos.z_rot = 0;

				l->anim_number = STOP_A;
				l->frame_number = STOP_F;
				l->current_anim_state = l->goal_anim_state = AS_STOP;
				lara.skidoo = NO_ITEM;
				lara.gun_status = LG_ARMLESS;
			}

			break;
	/* -------------------- */
		case CART_GETOUTR:
	/* -------------------- */
			if ((l->anim_number == objects[VEHICLE_ANIM].anim_index + CART_OUTR_A)
			&&  (l->frame_number == anims[l->anim_number].frame_end))
			{
				PHD_VECTOR vec = { 0, 640, 0};

				GetLaraHandAbsPosition(&vec, LARA_HIPS);
				l->pos.x_pos = vec.x;
				l->pos.y_pos = vec.y;
				l->pos.z_pos = vec.z;
				l->pos.x_rot = 0;
				l->pos.y_rot = v->pos.y_rot - 0x4000;
				l->pos.z_rot = 0;

				l->anim_number = STOP_A;
				l->frame_number = STOP_F;
				l->current_anim_state = l->goal_anim_state = AS_STOP;
				lara.skidoo = NO_ITEM;
				lara.gun_status = LG_ARMLESS;
			}

			break;
	/* -------------------- */
		case CART_GETIN:
	/* -------------------- */
			if ((l->anim_number == objects[VEHICLE_ANIM].anim_index + CART_PICK_A)
			&&  (l->frame_number == CART_PICK_F+20)
			&&  (!cart->Flags & CF_MESH))
			{
				sint16 *tmp;

				tmp = lara.mesh_ptrs[HAND_R];

				lara.mesh_ptrs[HAND_R] = meshes[objects[VEHICLE_ANIM].mesh_index + HAND_R];
				meshes[objects[VEHICLE_ANIM].mesh_index + HAND_R] = tmp;

				cart->Flags |= CF_MESH;
			}
			break;
	/* -------------------- */
		case CART_WALLDEATH:
	/* -------------------- */
			camera.target_elevation = -25*ONE_DEGREE;
			camera.target_distance = WALL_L*4;
			break;
	/* -------------------- */
		case CART_TURNDEATH:
	/* -------------------- */
			camera.target_elevation = -45*ONE_DEGREE;
			camera.target_distance = WALL_L*2;

			fh = GetCollision(v, v->pos.y_rot, 512, &ch);

			if ((fh > -STEP_L) && (fh < STEP_L))
			{
				if ((wibble & 7) == 0)
					SoundEffect(202, &v->pos, SFX_ALWAYS);

				v->pos.x_pos += (TURN_DEATH_VEL * phd_sin(v->pos.y_rot)) >> W2V_SHIFT;
				v->pos.z_pos += (TURN_DEATH_VEL * phd_cos(v->pos.y_rot)) >> W2V_SHIFT;
			}
			else
			{
				if (l->anim_number == objects[VEHICLE_ANIM].anim_index + CART_TURNDEATH_B)
				{
					cart->Flags |= CF_NOANIM;
					l->hit_points = -1;
				}
			}
			break;
	/* -------------------- */
		case CART_HIT:
	/* -------------------- */
			if ((l->hit_points <= 0)
			&&  (l->frame_number == CART_HIT_F+28))
			{
				l->frame_number = CART_HIT_F+28;
				cart->Flags = (cart->Flags & ~CF_CONTROL) | (CF_NOANIM);
				cart->Speed = v->speed = 0;
			}
			break;
	}

/* -------- sync vehicle's anims with Lara */

	if ((lara.skidoo != NO_ITEM) && (!(cart->Flags & CF_NOANIM)))
	{
		AnimateItem(l);

		v->anim_number = objects[MINECART].anim_index + (l->anim_number - objects[VEHICLE_ANIM].anim_index);
		v->frame_number = anims[v->anim_number].frame_base + (l->frame_number - anims[l->anim_number].frame_base);
	}

/* -------- initiate animations according to collision */

	if ((l->current_anim_state != CART_TURNDEATH)
	&&  (l->current_anim_state != CART_WALLDEATH)
	&&  (l->hit_points > 0))
	{

	/* -------- fall off corners */

		if ((v->pos.z_rot > TERMINAL_ANGLE)
		||  (v->pos.z_rot < -TERMINAL_ANGLE))
		{
			l->anim_number = objects[VEHICLE_ANIM].anim_index + CART_TURNDEATH_A;
			l->frame_number = anims[l->anim_number].frame_base;
			l->current_anim_state = l->goal_anim_state = CART_TURNDEATH;
			cart->Flags = (cart->Flags & ~CF_CONTROL) | CF_STOPPED|CF_DEAD;
			cart->Speed = v->speed = 0;
			return;
		}

	/* -------- smack into walls */

		fh = GetCollision(v, v->pos.y_rot, 512, &ch);

		if (fh < -(STEP_L*2))
		{
			l->anim_number = objects[VEHICLE_ANIM].anim_index + CART_WALLDEATH_A;
			l->frame_number = anims[l->anim_number].frame_base;
			l->current_anim_state = l->goal_anim_state = CART_WALLDEATH;
			cart->Flags = (cart->Flags & ~CF_CONTROL) | (CF_STOPPED|CF_DEAD);
			cart->Speed = v->speed = 0;
			l->hit_points = -1;
			return;
		}

	/* -------- smack into ceilings */

		if ((l->current_anim_state != CART_DUCK)
		&&  (l->current_anim_state != CART_HIT))
		{
			COLL_INFO coll;

			coll.quadrant = (uint16)(v->pos.y_rot+0x2000)/0x4000;
			coll.radius = CART_RADIUS;

			if (CollideStaticObjects(&coll, v->pos.x_pos, v->pos.y_pos, v->pos.z_pos, v->room_number, CART_HEIGHT))
			{
				int hits;

				l->anim_number = objects[VEHICLE_ANIM].anim_index + CART_HIT_A;
				l->frame_number = anims[l->anim_number].frame_base;
				l->current_anim_state = l->goal_anim_state = CART_HIT;
				DoLotsOfBlood(l->pos.x_pos, l->pos.y_pos-768, l->pos.z_pos, v->speed, v->pos.y_rot, l->room_number, 3);

				if ((hits = (CART_NHITS * ((((uint16)cart->Speed)) >> 11))) < 20)
					hits = 20;

				l->hit_points -= hits;

				return;
			}
		}

	/* -------- initiate jumps */

		if ((fh > 512+64)
		&&  (!cart->YVel))
			cart->YVel = CART_JUMP_VEL;

	/* -------- collide with baddies */

		CartToBaddieCollision(v);
	}
}

/*---------------------------------------------------------------------------
 * Public Functions
\*--------------------------------------------------------------------------*/

/****************************************************************************
* MineCartInitialise
*
* SYNOPSIS
*	void MineCartInitialise(sint16 item_number)
*
* PURPOSE
*	Initialises the minecart
*
* INPUTS
*	item_number	= vehicle item #
*
* RETURNS
*	Nothing
*/
void MineCartInitialise(sint16 item_number)
{
	ITEM_INFO *v;
	CARTINFO *cart;

	v = &items[item_number];
	cart = (CARTINFO *)v->data = game_malloc(sizeof(CARTINFO), 0);
}
/****************************************************************************
* MineCartCollision
*
* SYNOPSIS
*	void MineCartCollision(sint16 item_number, ITEM_INFO *litem, COLL_INFO *coll)
*
* PURPOSE
*	Determines whether Lara wants to get into the minecart
*
* INPUTS
*	item_number	= vehicle item #
*	l			= ptr to Lara's item structure
*	coll			= ptr to Lara's collision structure
*
* RETURNS
*	Nothing
*/
void MineCartCollision(sint16 item_number, ITEM_INFO *l, COLL_INFO *coll)
{
	int geton;
	ITEM_INFO *v;

	if ((l->hit_points < 0)
	||  (lara.skidoo != NO_ITEM))
		return;

	v = &items[item_number];

	if ((geton = GetInMineCart(v, l, coll)))
	{
		sint16 ang;
		CARTINFO *cart;

		lara.skidoo = item_number;

	/* -------- throw flare away if using */

		if (lara.gun_type == LG_FLARE)
		{
			CreateFlare(0);
			undraw_flare_meshes();
			lara.flare_control_left = 0;
			lara.request_gun_type = lara.gun_type = LG_ARMLESS;
		}

		lara.gun_status = LG_HANDSBUSY;

	/* -------- initiate animation */

		ang = ((sint16)mGetAngle(v->pos.x_pos, v->pos.z_pos, l->pos.x_pos, l->pos.z_pos)) - v->pos.y_rot;

		if ((ang > -(ONE_DEGREE*45)) && (ang < (ONE_DEGREE*135)))
			l->anim_number = objects[VEHICLE_ANIM].anim_index + CART_GETINR_A;
		else
			l->anim_number = objects[VEHICLE_ANIM].anim_index + CART_GETINL_A;

		l->frame_number = anims[l->anim_number].frame_base;
		l->current_anim_state = l->goal_anim_state = CART_GETIN;

		l->pos.x_pos = v->pos.x_pos;
		l->pos.y_pos = v->pos.y_pos;
		l->pos.z_pos = v->pos.z_pos;
		l->pos.x_rot = v->pos.x_rot;
		l->pos.y_rot = v->pos.y_rot;
		l->pos.z_rot = v->pos.z_rot;

		cart = (CARTINFO *)v->data;
		cart->Flags = 0;
		cart->Speed = 0;
		cart->YVel = 0;
		cart->Gradient = 0;

		S_CDPlay(12, 0);
	}
	else
		ObjectCollision(item_number, l, coll);
}
/****************************************************************************
* MineCartControl
*
* SYNOPSIS
*	int MineCartControl()
*
* PURPOSE
*	Controls Lara in the minecart
*
* INPUTS
*	None
*
* RETURNS
*	!0 if Lara is under minecart control
*/
int MineCartControl()
{
	CARTINFO *cart;
	ITEM_INFO *v, *l;
	FLOOR_INFO *floor;
	sint16 room_number;

	l = lara_item;
	v = &items[lara.skidoo];
	cart = (CARTINFO *)v->data;

	DoUserInput(v, l, cart);

	if (cart->Flags & CF_CONTROL)
		MoveCart(v, l, cart);

/* -------- move Lara to vehicle pos */

	if (lara.skidoo != NO_ITEM)
	{
		l->pos.x_pos = v->pos.x_pos;
		l->pos.y_pos = v->pos.y_pos;
		l->pos.z_pos = v->pos.z_pos;
		l->pos.x_rot = v->pos.x_rot;
		l->pos.y_rot = v->pos.y_rot;
		l->pos.z_rot = v->pos.z_rot;
	}

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

	room_number = v->room_number;
	floor = GetFloor(v->pos.x_pos, v->pos.y_pos, v->pos.z_pos, &room_number);
	GetHeight(floor, v->pos.x_pos, v->pos.y_pos, v->pos.z_pos);

	if (room_number != v->room_number)
	{
		ItemNewRoom(lara.skidoo, room_number);
		ItemNewRoom(lara.item_number, room_number);
	}

	TestTriggers(trigger_index, 0);
//	TestTriggers(trigger_index, 1);

	if (!(cart->Flags & CF_DEAD))
	{
		camera.target_elevation = -45*ONE_DEGREE;
		camera.target_distance = WALL_L*2;
	}

	return (lara.skidoo == NO_ITEM) ? 0 : 1;
}

