/****************************************************************************
*
* QUADBIKE.C
*
* PROGRAMMER : Chris
*    VERSION : 00.00
*    CREATED : 13/07/98
*   MODIFIED : 13/07/98
*	   TABS : 5
*
* DESCRIPTION
*	Quad-bike controller
*
*****************************************************************************/

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

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

#include "objects.h"
#include "lara.h"
#include "laraanim.h"
#include "laraswim.h"
#include "control.h"
#include "input.h"
#include "camera.h"
#include "sound.h"
#include "sphere.h"
#include "traps.h"
#include "laraflar.h"
#include "lot.h"
#include "effect2.h"
#include "text.h"
#include "quadbike.h"
#include "savegame.h"
#include "title.h"

static void TriggerExhaustSmoke(long x, long y, long z, sint16 angle, long speed, long moving);

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

//#define DEBUG_SKIDOO

#define IN_ACCELERATE	(IN_ACTION)
#define IN_BRAKE		(IN_JUMP)
#define IN_DISMOUNT		(IN_ROLL)
#define IN_HANDBRAKE	(IN_DASH|IN_DUCK)

#define WADE_DEPTH (STEP_L)
#define SWIM_DEPTH 730

#define REV_PITCH1S -0x2000
#define REV_PITCH1E 0x6800

#define REV_PITCH2S -0x2800
#define REV_PITCH2E 0x7000

#define REV_INAIR 	0xa000

/* -------- dynamics (8bit precision) */

//#define ACCELERATION_1	0x2800
//#define ACCELERATION_2	0x5800
#define ACCELERATION_1	0x4000
#define ACCELERATION_2	0x7000
#define MAX_SPEED		0xa000
#define MIN_HANDBRAKE_SPEED	0x3000

#define FRICTION		0x0100
#define BRAKE			0x0280

#define REVERSE_ACC		-0x0300
#define MAX_BACK		-0x3000

#define MAX_REVS		0xa000

#define TERMINAL_FALLSPEED 240

#define SKIDOO_SLIP 100
#define SKIDOO_SLIP_SIDE 50

#define SKIDOO_MAX_KICK		-80

#define QUAD_FRONT	550
#define QUAD_BACK  -550
#define QUAD_SIDE	260
#define QUAD_RADIUS	500
#define QUAD_HEIGHT	512


#define SMAN_SHOT_DAMAGE		10
#define SMAN_LARA_DAMAGE		50

#define DAMAGE_START  140
#define DAMAGE_LENGTH 14

#define SKIDOO_GETON_ANIM 1
#define SKIDOO_GETONL_ANIM 18
#define SKIDOO_FALL_ANIM  8
#define SKIDOO_DEAD_ANIM  15

#define SKIDOO_HIT_LEFT  11
#define SKIDOO_HIT_RIGHT 12
#define SKIDOO_HIT_FRONT 13
#define SKIDOO_HIT_BACK  14


#define GETOFF_DIST 512		// minimum collision distance

#define SKIDOO_UNDO_TURN	(ONE_DEGREE*2)
#define SKIDOO_TURN			((ONE_DEGREE/2) + SKIDOO_UNDO_TURN)
#define SKIDOO_MAX_TURN		(ONE_DEGREE*5)
#define SKIDOO_HTURN		(((ONE_DEGREE*3)/4) + SKIDOO_UNDO_TURN)
#define SKIDOO_MAX_HTURN		(ONE_DEGREE*8)

#define MIN_MOMENTUM_TURN	(ONE_DEGREE*3)
#define MAX_MOMENTUM_TURN	(ONE_DEGREE+(ONE_DEGREE/2))
#define SKIDOO_MAX_MOM_TURN (ONE_DEGREE*150)

#define SKIDOO_FRONT 550
#define SKIDOO_SIDE	260
#define SKIDOO_RADIUS 500
#define SKIDOO_SNOW 500

#define SKIDOO_MAX_HEIGHT (STEP_L)

//#define SKIDOO_MIN_BOUNCE 15
#define SKIDOO_MIN_BOUNCE ((MAX_SPEED/2)>>8)


/*** SKIDOO BAD GUY ***/

enum skidman_anims {SMAN_EMPTY, SMAN_WAIT, SMAN_MOVING, SMAN_STARTLEFT, SMAN_STARTRIGHT, SMAN_LEFT, SMAN_RIGHT, SMAN_DEATH};

#define SMAN_MIN_TURN (SKIDOO_MAX_TURN/3)

#define SMAN_TARGET_ANGLE (ONE_DEGREE*15)
#define SMAN_WAIT_RANGE SQUARE(WALL_L*4)

#define SMAN_DEATH_ANIM 10

enum skidoo_anims {SKID_SIT, SKID_GETON, SKID_LEFT, SKID_RIGHT, SKID_FALL, SKID_HIT, SKID_GETONL, SKID_GETOFFL,
	SKID_STILL, SKID_GETOFF, SKID_LETGO, SKID_DEATH, SKID_FALLOFF};


enum {
	QS_0,
	QS_DRIVE,
	QS_TURNL, QS_3, QS_4,
	QS_SLOW,
	QS_BRAKE,
	QS_BIKEDEATH,
	QS_FALL,
	QS_GETONR,
	QS_GETOFFR,		// 10
	QS_HITBACK,
	QS_HITFRONT,
	QS_HITLEFT,
	QS_HITRIGHT,
	QS_STOP, QS_16,
	QS_LAND,
	QS_STOPSLOWLY,
	QS_FALLDEATH,
	QS_FALLOFF,		// 20
	QS_WHEELIE,
	QS_TURNR,
	QS_GETONL,
	QS_GETOFFL,
};

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

#define QUADBIKE_TURNL_A		3
#define QUADBIKE_TURNL_F		GF2(QUADBIKE_TURNL_A)
#define QUADBIKE_TURNR_A		20
#define QUADBIKE_TURNR_F		GF2(QUADBIKE_TURNR_A)

#define QUADBIKE_FALLSTART_A	6
#define QUADBIKE_FALLSTART_F	GF2(QUADBIKE_FALLSTART_A)
#define QUADBIKE_FALL_A		7
#define QUADBIKE_FALL_F		GF2(QUADBIKE_FALL_A)
#define QUADBIKE_GETONR_A	9
#define QUADBIKE_GETONR_F	GF2(QUADBIKE_GETONR_A)
#define Q_HITB_A			11
#define Q_HITF_A			12
#define Q_HITL_A			14
#define Q_HITR_A			13
#define QUADBIKE_GETONL_A	23
#define QUADBIKE_GETONL_F	GF2(QUADBIKE_GETONL_A)
#define QUADBIKE_FALLSTART2_A	25

enum { 	EXHAUST_LEFT = 0,
		EXHAUST_RIGHT,
		BACKLEFT_TYRE,
		BACKRIGHT_TYRE,
		FRONTRIGHT_TYRE,
		FRONTLEFT_TYRE
};

BITE_INFO	quad_bites[6] =	{	{	-56,-32,-380,0	},	// Exhaust left.
							{	56,-32,-380,0	},	// Exhaust right.
							{	-8,180,-48,3	},	// Back left.
							{	8,180,-48,4	},	// Back right.
							{	90,180,-32,6	},	// Front right.
							{	-90,180,-32,7	}	// Front left.
						};

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

extern char exit_message[];

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

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

void InitialiseQuadBike(sint16 item_number);
int QuadBikeControl();
void QuadBikeCollision(sint16 item_number, ITEM_INFO *litem, COLL_INFO *coll);
void QuadBikeDraw(ITEM_INFO *item);

void *func[] __attribute__((section(".header"))) = {
	InitialiseQuadBike,
	QuadBikeControl,
	QuadBikeCollision,
	QuadBikeDraw
};

#endif

/*---------------------------------------------------------------------------
 * Locals
\*--------------------------------------------------------------------------*/

static uchar HandbrakeStarting,CanHandbrakeStart,ExhaustStart,NoGetOff;

/*---------------------------------------------------------------------------
 * Macros
\*--------------------------------------------------------------------------*/

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

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

static void QuadbikeExplode(ITEM_INFO *v)
{
	long lp;
	if (room[v->room_number].flags & UNDERWATER)
		TriggerUnderwaterExplosion(v);
	else
	{
		TriggerExplosionSparks(v->pos.x_pos, v->pos.y_pos, v->pos.z_pos,3,-2,0,v->room_number);	// -2 = Set off a dynamic light controller.
		for (lp=0;lp<3;lp++)
			TriggerExplosionSparks(v->pos.x_pos, v->pos.y_pos, v->pos.z_pos,3,-1,0, v->room_number);
	}

	ExplodingDeath(lara.skidoo, 0xfffffffe, 1);
	KillItem( lara.skidoo);
	v->status = DEACTIVATED;
	SoundEffect(105, NULL, 0);
	SoundEffect(106, NULL, 0);
	lara.skidoo = NO_ITEM;
}

/*
 * Check if getting off skidoo here is possible in the direction required by
 * player
 */
static int CanGetOff(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;
}

/*
 * Check if Lara is still under skidoo control. Return 0 if she is in that
 * limbo state of the skidoo still needing control (it is falling) and her
 * needing normal control (so is she)
 */
static int SkidooCheckGetOff()
{
	ITEM_INFO *v, *l;

	v = &items[lara.skidoo];
	l = lara_item;

/* -------- Lara wants to get off vehicle */

	if (((l->current_anim_state == QS_GETOFFR)
	||   (l->current_anim_state == QS_GETOFFL))
	&&   (l->frame_number == anims[l->anim_number].frame_end))
	{
		if (l->current_anim_state == QS_GETOFFL)
			l->pos.y_rot += 0x4000;
		else
			l->pos.y_rot -= 0x4000;

		l->anim_number = STOP_A;
		l->frame_number = STOP_F;
		l->current_anim_state = l->goal_anim_state = AS_STOP;
		l->pos.x_pos -= GETOFF_DIST * phd_sin(l->pos.y_rot) >> W2V_SHIFT;
		l->pos.z_pos -= GETOFF_DIST * phd_cos(l->pos.y_rot) >> W2V_SHIFT;
		l->pos.x_rot = l->pos.z_rot = 0;
		lara.skidoo = NO_ITEM;
		lara.gun_status = LG_ARMLESS;
	}

	else if (l->frame_number == anims[l->anim_number].frame_end)
	{
		QUADINFO *bike;

		bike = (QUADINFO *)v->data;

	/* -------- Lara has been thrown off vehicle in water */

		if (l->current_anim_state == QS_FALLOFF)
		{
			PHD_VECTOR vec = { 0, 0, 0};

			l->anim_number = FASTFALL_A;
			l->frame_number = FASTFALL_F;
			l->current_anim_state = AS_FASTFALL;

			GetLaraHandAbsPosition(&vec, LARA_HIPS);

			l->pos.x_pos = vec.x;
			l->pos.y_pos = vec.y;
			l->pos.z_pos = vec.z;
			l->fallspeed = v->fallspeed;
			l->gravity_status = 1;
			l->pos.x_rot = l->pos.z_rot = 0;
			l->hit_points = 0;
			lara.gun_status = LG_ARMLESS;
//			bike->Flags |= QF_FALLING;
			v->flags |= ONESHOT;
			return 0;
		}

	/* -------- Lara has been thrown off vehicle as she's dead */

		else if (l->current_anim_state == QS_FALLDEATH)
		{
			l->goal_anim_state = AS_DEATH;
			l->fallspeed = DAMAGE_START+DAMAGE_LENGTH;
			l->speed = 0;
			bike->Flags |= QF_DEAD;
			return 0;
		}
	}

	return 1;
}

static int GetOnQuadBike(sint16 item_number, COLL_INFO *coll)
{
	int dist;
	sint32 x, z;
	ITEM_INFO *v, *l;
	FLOOR_INFO *floor;
	sint16 room_number,ang;
	uint16 tempang;

	l = lara_item;
	v = &items[item_number];

	if ((!(input & IN_ACTION))
	||  (v->flags & ONESHOT)
	||  (lara.gun_status != LG_ARMLESS)
	||  (l->gravity_status)
	|| ((ABS(v->pos.y_pos - l->pos.y_pos)) > 256))
		return 0;

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

	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 > 170000)
		return 0;

	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;
	else
	{
		ang = phd_atan(v->pos.z_pos-l->pos.z_pos, v->pos.x_pos-l->pos.x_pos);
		ang -= v->pos.y_rot;

		if ((ang > -(ONE_DEGREE*45)) && (ang < (ONE_DEGREE*135)))
		{

			// left.

			tempang = l->pos.y_rot-v->pos.y_rot;
			if (tempang > (45*ONE_DEGREE) && tempang < (135*ONE_DEGREE))
				return 1;
			else
				return 0;
		}
		else
		{
			tempang = l->pos.y_rot-v->pos.y_rot;
			// right.
			if (tempang > (225*ONE_DEGREE) && tempang < (315*ONE_DEGREE))
				return 1;
			else
				return 0;
		}
	}
	return 0;
}

static void SkidooBaddieCollision(ITEM_INFO *skidoo)
{
#define TARGET_DIST		(WALL_L*2) // dist for more complex checks
	// copy of LaraBaddieCollision()
	int			i,x,y,z;
	ITEM_INFO	*item;
	OBJECT_INFO *object;
	sint16		roomies[16],numroom,*door;
	sint16		item_num;

	numroom = 1;
	roomies[0] = skidoo->room_number;
	door = room[skidoo->room_number].door;		// Get rooms around skidoo room

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

	for ( i=0 ; i<numroom ; i++ )
	{
		item_num = room[ roomies[i] ].item_number;						// Only do collision with Items on Draw list
		while ( item_num!=NO_ITEM )
		{
			item = &items[ item_num ];
			if ( item->collidable && item->status!=INVISIBLE && item!=lara_item && item!=skidoo) // Object Isnt Collidable so Exit now
			{
				object = &objects[item->object_number];
				if ( object->collision && (object->intelligent || item->object_number==AVALANCHE))
				{
					x = skidoo->pos.x_pos - item->pos.x_pos;			// Get Vector from Baddie to Lara
					y = skidoo->pos.y_pos - item->pos.y_pos;
					z = skidoo->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, skidoo, SKIDOO_RADIUS))
						{
//							if (item->object_number == BANDIT3)
//								SkidmanPush(item, skidoo, SKIDOO_RADIUS);
//							else if (item->object_number == AVALANCHE)
							if (item->object_number == AVALANCHE)
							{
								if (item->current_anim_state==TRAP_ACTIVATE)
								{
									lara_item->hit_status = 1;
									lara_item->hit_points -= 100;
								}
							}
							else
							{
								DoLotsOfBlood(item->pos.x_pos, skidoo->pos.y_pos-STEP_L, item->pos.z_pos, skidoo->speed, skidoo->pos.y_rot, item->room_number, 3);
								item->hit_points = 0; // die motherfucker!
							}
						}
					}
				}
			}
			item_num = item->next_item;
		}
	}
#undef TARGET_DIST
}

static int GetCollisionAnim(ITEM_INFO *v, PHD_VECTOR *moved)
{
	moved->x = v->pos.x_pos - moved->x;
	moved->z = v->pos.z_pos - moved->z;

/* -------- determine direction of travel relative to rotation */

	if ((moved->x) || (moved->z))
	{
		sint32 c, s, front, side;

		c = phd_cos(v->pos.y_rot);
		s = phd_sin(v->pos.y_rot);
		front = ((moved->z * c) + (moved->x * s)) >> W2V_SHIFT;
		side = ((-moved->z * s) + (moved->x * c)) >> W2V_SHIFT;

		if (ABS(front) > ABS(side))
		{
			if (front > 0)
				return SKIDOO_HIT_BACK;
			else
				return SKIDOO_HIT_FRONT;
		}
		else
		{
			if (side > 0)
				return SKIDOO_HIT_LEFT;
			else
				return SKIDOO_HIT_RIGHT;
		}
	}

	return 0;
}

static sint32 TestHeight(ITEM_INFO *item, sint32 z_off, sint32 x_off, PHD_VECTOR *pos)
{
	/* Get height at a position offset from the origin.
		Moves the vector in 'pos' to the required test position too */
	FLOOR_INFO *floor;
	sint32 s, c;
	sint16 room_number;

	/* Get y pos correctly, but don't bother changing z_off and x_off using x_rot and z_rot */
	pos->y = item->pos.y_pos -	(z_off * phd_sin(item->pos.x_rot) >> W2V_SHIFT) +
		(x_off * phd_sin(item->pos.z_rot) >> W2V_SHIFT);

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

	pos->z = item->pos.z_pos + ((z_off * c - x_off * s) >> W2V_SHIFT);
	pos->x = item->pos.x_pos + ((z_off * s + x_off * c) >> W2V_SHIFT);

	room_number = item->room_number;
	floor = GetFloor(pos->x, pos->y, pos->z, &room_number);
	c = GetCeiling(floor,pos->x, pos->y, pos->z);
	if (pos->y < c || c == NO_HEIGHT)
		return (NO_HEIGHT);
	return GetHeight(floor, pos->x, pos->y, pos->z);
}

/*
 * Work out grid shift for skidoo based on old position v new position (given
 * that new position is known to be bad). Return rotation to be applied.
 *
 */
static int DoShift(ITEM_INFO *skidoo, PHD_VECTOR *pos, PHD_VECTOR *old)
{
	int x, z;
	int x_old, z_old;
	int shift_x, shift_z;

	x = pos->x >> WALL_SHIFT;
	z = pos->z >> WALL_SHIFT;

	x_old = old->x >> WALL_SHIFT;
	z_old = old->z >> WALL_SHIFT;

	shift_x = pos->x & (WALL_L-1);
	shift_z = pos->z & (WALL_L-1);

	if (x == x_old)
	{
		if (z == z_old)
		{
			/* Neither shift; may have hit a very steep slope, so need to push back to old position */
			skidoo->pos.z_pos += (old->z - pos->z);
			skidoo->pos.x_pos += (old->x - pos->x);
		}
		else if (z > z_old)
		{
			/* Z shift left */
			skidoo->pos.z_pos -= shift_z+1;
			return (pos->x - skidoo->pos.x_pos);
		}
		else
		{
			/* Z shift right */
			skidoo->pos.z_pos += WALL_L-shift_z;
			return (skidoo->pos.x_pos - pos->x);
		}
	}
	else if (z == z_old)
	{
		if (x > x_old)
		{
			/* X shift up */
			skidoo->pos.x_pos -= shift_x+1;
			return (skidoo->pos.z_pos - pos->z);
		}
		else
		{
			/* X shift down */
			skidoo->pos.x_pos += WALL_L-shift_x;
			return (pos->z - skidoo->pos.z_pos);
		}
	}
	else
	{
		/* A diagonal hit; means a barrage of tests needed to determine best shift */
		sint16 room_number;
		FLOOR_INFO *floor;
		sint32 height;

		x = z = 0;

		room_number = skidoo->room_number;
		floor = GetFloor(old->x, pos->y, pos->z, &room_number);
		height = GetHeight(floor, old->x, pos->y, pos->z);
		if (height < old->y - STEP_L)
		{
			if (pos->z > old->z)
				z = -shift_z-1;
			else
				z = WALL_L-shift_z;
		}

		room_number = skidoo->room_number;
		floor = GetFloor(pos->x, pos->y, old->z, &room_number);
		height = GetHeight(floor, pos->x, pos->y, old->z);
		if (height < old->y - STEP_L)
		{
			if (pos->x > old->x)
				x = -shift_x-1;
			else
				x = WALL_L-shift_x;
		}

		if (x && z)
		{
			/* Corner or side collision */
			skidoo->pos.z_pos += z;
			skidoo->pos.x_pos += x;
		}
		else if (z)
		{
			skidoo->pos.z_pos += z;
			if (z > 0)
				return (skidoo->pos.x_pos - pos->x);
			else
				return (pos->x - skidoo->pos.x_pos);
		}
		else if (x)
		{
			skidoo->pos.x_pos += x;
			if (x > 0)
				return (pos->z - skidoo->pos.z_pos);
			else
				return (skidoo->pos.z_pos - pos->z);
		}
		else
		{
			/* Pure diagonal collision */
			skidoo->pos.z_pos += (old->z - pos->z);
			skidoo->pos.x_pos += (old->x - pos->x);
		}
	}

	return 0;
}

static int DoDynamics(int height, int fallspeed, int *y)
{
	int kick;

	if (height > *y)
	{
		/* In air */
		*y += fallspeed;
		if (*y > height - SKIDOO_MIN_BOUNCE)
		{
			*y = height;
			fallspeed = 0;
		}
		else
			fallspeed += GRAVITY;
	}
	else
	{
		/* On ground: get up push from height change */
		kick = (height - *y) << 2;

		if (kick < SKIDOO_MAX_KICK)
			kick = SKIDOO_MAX_KICK;

		fallspeed += ((kick - fallspeed) >> 3);

		if (*y > height)
			*y = height;
	}

	return fallspeed;
}

static int SkidooDynamics(ITEM_INFO *v)
{
	/* Does all skidoo movement and collision and returns if collide value */
	QUADINFO *bike;
	PHD_VECTOR moved, fl, fr, br, bl, mtl, mbl, mtr, mbr, mml, mmr;
	PHD_VECTOR old, fl_old, fr_old, bl_old, br_old, mtl_old, mbl_old, mtr_old, mbr_old, mml_old, mmr_old;
	sint32 hfl, hfr, hbr, hbl, hmtl, hmbl, hmtr, hmbr, hmml, hmmr;
	sint32 hfr_old, hfl_old, hbr_old, hbl_old, hmtl_old, hmbl_old, hmtr_old, hmbr_old, hmml_old, hmmr_old;
	FLOOR_INFO *floor;
	sint32 height, slip, collide,speed;
	sint16 room_number, rot,rotadd;
	int newspeed;

	NoGetOff = 0;

	bike = (QUADINFO *)v->data;

/* First get positions and heights of skidoo's corners + centre */
	hfl_old = TestHeight(v,  SKIDOO_FRONT, -SKIDOO_SIDE, &fl_old);
	hfr_old = TestHeight(v,  SKIDOO_FRONT,  SKIDOO_SIDE, &fr_old);
	hbl_old = TestHeight(v, -SKIDOO_FRONT, -SKIDOO_SIDE, &bl_old);
	hbr_old = TestHeight(v, -SKIDOO_FRONT,  SKIDOO_SIDE, &br_old);
	hmml_old = TestHeight(v,  0, -SKIDOO_SIDE, &mml_old);
	hmmr_old = TestHeight(v,  0,  SKIDOO_SIDE, &mmr_old);
	hmtl_old = TestHeight(v,  SKIDOO_FRONT>>1, -SKIDOO_SIDE, &mtl_old);
	hmtr_old = TestHeight(v,  SKIDOO_FRONT>>1,  SKIDOO_SIDE, &mtr_old);
	hmbl_old = TestHeight(v, -SKIDOO_FRONT>>1, -SKIDOO_SIDE, &mbl_old);
	hmbr_old = TestHeight(v, -SKIDOO_FRONT>>1,  SKIDOO_SIDE, &mbr_old);

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

/* Back left/right may be slightly below ground, so correct for this */
	if (bl_old.y > hbl_old)
		bl_old.y = hbl_old;
	if (br_old.y > hbr_old)
		br_old.y = hbr_old;
	if (fl_old.y > hfl_old)
		fl_old.y = hfl_old;
	if (fr_old.y > hfr_old)
		fr_old.y = hfr_old;
	if (mbl_old.y > hmbl_old)
		mbl_old.y = hmbl_old;
	if (mbr_old.y > hmbr_old)
		mbr_old.y = hmbr_old;
	if (mtl_old.y > hmtl_old)
		mtl_old.y = hmtl_old;
	if (mtr_old.y > hmtr_old)
		mtr_old.y = hmtr_old;
	if (mml_old.y > hmml_old)
		mml_old.y = hmml_old;
	if (mmr_old.y > hmmr_old)
		mmr_old.y = hmmr_old;


/* First undo any turn the skidoo may have applied */
	if (v->pos.y_pos > (v->floor - STEP_L))
	{
		sint16 momentum;

		if (bike->skidoo_turn < -SKIDOO_UNDO_TURN)
			bike->skidoo_turn += SKIDOO_UNDO_TURN;

		else if (bike->skidoo_turn > SKIDOO_UNDO_TURN)
			bike->skidoo_turn -= SKIDOO_UNDO_TURN;

		else
			bike->skidoo_turn = 0;

		v->pos.y_rot += bike->skidoo_turn + bike->extra_rotation;

	/* Deal with momentum; do it with an angle that tracks direction of travel, but slower than turn */
		rot = v->pos.y_rot - bike->momentum_angle;

		momentum = MIN_MOMENTUM_TURN - (((((MIN_MOMENTUM_TURN - MAX_MOMENTUM_TURN) << 8) / MAX_SPEED) * bike->Velocity) >> 8);
		if (!(input & IN_ACCELERATE) && bike->Velocity > 0)
			momentum += momentum>>2;

		if (rot < -MAX_MOMENTUM_TURN)
		{
			if (rot < -SKIDOO_MAX_MOM_TURN)
			{
				rot = -SKIDOO_MAX_MOM_TURN;
				bike->momentum_angle = v->pos.y_rot - rot;
			}
			else
//				bike->momentum_angle -= SKIDOO_MOMENTUM_TURN;
				bike->momentum_angle -= momentum;
		}
		else if (rot > MAX_MOMENTUM_TURN)
		{
			if (rot > SKIDOO_MAX_MOM_TURN)
			{
				rot = SKIDOO_MAX_MOM_TURN;
				bike->momentum_angle = v->pos.y_rot - rot;
			}
			else
//				bike->momentum_angle += SKIDOO_MOMENTUM_TURN;
				bike->momentum_angle += momentum;
		}
		else
			bike->momentum_angle = v->pos.y_rot;
	}

	else
		v->pos.y_rot += bike->skidoo_turn + bike->extra_rotation;

/* -------- move vehicle according to speed */

	room_number = v->room_number;
	floor = GetFloor(v->pos.x_pos, v->pos.y_pos, v->pos.z_pos, &room_number);
	height = GetHeight(floor, v->pos.x_pos, v->pos.y_pos, v->pos.z_pos);
	if (v->pos.y_pos >= height)
	{
#ifdef DEBUG_SKIDOO
		PrintDbug(1, 6, "On ground");
#endif
		speed = (v->speed * phd_cos(v->pos.x_rot)) >> W2V_SHIFT;
	}
	else
		speed = v->speed;

	v->pos.z_pos += (speed * phd_cos(bike->momentum_angle)) >> W2V_SHIFT;
	v->pos.x_pos += (speed * phd_sin(bike->momentum_angle)) >> W2V_SHIFT;

/* -------- slide vehicle back down tilts */

	slip = SKIDOO_SLIP * phd_sin(v->pos.x_rot) >> W2V_SHIFT;
	if (ABS(slip) > SKIDOO_SLIP/2)
	{
		if (slip > 0)
			slip -= 10;
		else
			slip += 10;
		v->pos.z_pos -= slip * phd_cos(v->pos.y_rot) >> W2V_SHIFT;
		v->pos.x_pos -= slip * phd_sin(v->pos.y_rot) >> W2V_SHIFT;
	}

	slip = SKIDOO_SLIP_SIDE * phd_sin(v->pos.z_rot) >> W2V_SHIFT;
	if (ABS(slip) > SKIDOO_SLIP_SIDE/2)
	{
		v->pos.z_pos -= slip * phd_sin(v->pos.y_rot) >> W2V_SHIFT;
		v->pos.x_pos += slip * phd_cos(v->pos.y_rot) >> W2V_SHIFT;
	}


/*	slip = (SKIDOO_SLIP * phd_sin(v->pos.x_rot)) >> W2V_SHIFT;
//	if (ABS(slip) > SKIDOO_SLIP/2)
	if (ABS(slip) > 16)
	{
		NoGetOff = 1;
//		v->pos.z_pos -= (slip * phd_cos(v->pos.y_rot)) >> W2V_SHIFT;
//		v->pos.x_pos -= (slip * phd_sin(v->pos.y_rot)) >> W2V_SHIFT;
		if (slip < -FRICTION || slip > FRICTION)
			bike->Velocity -= slip;
	}

	if (bike->Velocity > (MAX_SPEED))
		bike->Velocity = (MAX_SPEED);
	else if (bike->Velocity < (MAX_BACK))
		bike->Velocity = (MAX_BACK);

	slip = (SKIDOO_SLIP_SIDE * phd_sin(v->pos.z_rot)) >> W2V_SHIFT;
	if (ABS(slip) > 15)
	{
		NoGetOff = 1;
		if  (slip < 0)
		{
			slip = abs(slip);
			v->pos.x_pos += (slip * phd_sin(v->pos.y_rot-0x4000)) >> W2V_SHIFT;
			v->pos.z_pos += (slip * phd_cos(v->pos.y_rot-0x4000)) >> W2V_SHIFT;
		}
		else
		{
			slip = abs(slip);
			v->pos.x_pos += (slip * phd_sin(v->pos.y_rot+0x4000)) >> W2V_SHIFT;
			v->pos.z_pos += (slip * phd_cos(v->pos.y_rot+0x4000)) >> W2V_SHIFT;
		}
//		bike->skidoo_turn += slip<<8;
//		v->pos.y_rot += slip<<4;
	}
*/
#ifdef DEBUG_SKIDOO
	sprintf(exit_message, "Angle:%d FS:%d",v->pos.y_rot, v->fallspeed);
	PrintDbug(1, 7, exit_message);
#endif

/* Remember desired position in case of collisions moving us about */
	moved.x = v->pos.x_pos;
	moved.z = v->pos.z_pos;

/* Test against bad guys too */
	if (!(v->flags & ONESHOT)) // ONESHOT flag set if skidoo no longer travelling with Lara
		SkidooBaddieCollision(v);

/* -------- collide against background */

	rot = 0;

	hfl = TestHeight(v, SKIDOO_FRONT, -SKIDOO_SIDE, &fl);
	if (hfl < fl_old.y - STEP_L)
		rot = DoShift(v, &fl, &fl_old);

	hmtl = TestHeight(v, SKIDOO_FRONT>>1, -SKIDOO_SIDE, &mtl);
	if (hmtl < mtl_old.y - STEP_L)
		DoShift(v, &mtl, &mtl_old);

	hmml = TestHeight(v, 0, -SKIDOO_SIDE, &mml);
	if (hmml < mml_old.y - STEP_L)
		DoShift(v, &mml, &mml_old);

	hmbl = TestHeight(v, -SKIDOO_FRONT>>1, -SKIDOO_SIDE, &mbl);
	if (hmbl < mbl_old.y - STEP_L)
		DoShift(v, &mbl, &mbl_old);

	hbl = TestHeight(v, -SKIDOO_FRONT, -SKIDOO_SIDE, &bl);
	if (hbl < bl_old.y - STEP_L)
	{
		rotadd = DoShift(v, &bl, &bl_old);
		if ((rotadd > 0 && rot >= 0) || (rotadd < 0 && rot <= 0))
			rot+=rotadd;
	}

	hfr = TestHeight(v, SKIDOO_FRONT, SKIDOO_SIDE, &fr);
	if (hfr < fr_old.y - STEP_L)
	{
		rotadd = DoShift(v, &fr, &fr_old);
		if ((rotadd > 0 && rot >= 0) || (rotadd < 0 && rot <= 0))
			rot+=rotadd;
	}

	hmtr = TestHeight(v, SKIDOO_FRONT>>1, SKIDOO_SIDE, &mtr);
	if (hmtr < mtr_old.y - STEP_L)
		DoShift(v, &mtr, &mtr_old);

	hmmr = TestHeight(v, 0, SKIDOO_SIDE, &mmr);
	if (hmmr < mmr_old.y - STEP_L)
		DoShift(v, &mmr, &mmr_old);

	hmbr = TestHeight(v, -SKIDOO_FRONT>>1, SKIDOO_SIDE, &mbr);
	if (hmbr < mbr_old.y - STEP_L)
		DoShift(v, &mbr, &mbr_old);

	hbr = TestHeight(v, -SKIDOO_FRONT, SKIDOO_SIDE, &br);
	if (hbr < br_old.y - STEP_L)
	{
		rotadd = DoShift(v, &br, &br_old);
		if ((rotadd > 0 && rot >= 0) || (rotadd < 0 && rot <= 0))
			rot+=rotadd;
	}

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

	height = GetHeight(floor, v->pos.x_pos, v->pos.y_pos, v->pos.z_pos);
	if (height < v->pos.y_pos - STEP_L)
		DoShift(v, (PHD_VECTOR *)&v->pos, &old);

	bike->extra_rotation = rot;

/* Get collision anim if skidoo has been moved from desired position by collisions */
	collide = GetCollisionAnim(v, &moved);

/* Check final actual movement; if speed is more than halved then reduce to zero */

	if (collide)
	{
		newspeed = ((v->pos.z_pos - old.z) * phd_cos(bike->momentum_angle) + (v->pos.x_pos - old.x) * phd_sin(bike->momentum_angle)) >> W2V_SHIFT;

		newspeed <<= 8;

		if ((&items[lara.skidoo] == v)
		&&  (bike->Velocity == MAX_SPEED)
		&&  (newspeed < (bike->Velocity - 10)))
		{
			lara_item->hit_points -= (bike->Velocity - newspeed) >> 7;
			lara_item->hit_status = 1;
		}

		if (bike->Velocity > 0 && newspeed < bike->Velocity)
			bike->Velocity = (newspeed<0)? 0 : newspeed;

		else if (bike->Velocity < 0 && newspeed > bike->Velocity)
			bike->Velocity = (newspeed>0)? 0 : newspeed;

		if (bike->Velocity < MAX_BACK)
			bike->Velocity = MAX_BACK;
	}

	return collide;
}
/****************************************************************************
* AnimateQuadBike
*
* SYNOPSIS
*	void AnimateQuadBike(ITEM_INFO *v, int collide, int dead)
*
* PURPOSE
*	Updates the quadbike animations
*
* INPUTS
*	v			= ptr to vehicle structure
*	collide		= flag if vehicle has collided against the background
*	dead			= flag if Lara has died
*
* RETURNS
*	Nothing
*/
static void AnimateQuadBike(ITEM_INFO *v, int collide, int dead)
{
	ITEM_INFO *l;
	QUADINFO *bike;
	static char slowdamp;

	l = lara_item;
	bike = (QUADINFO *)v->data;

/* -------- initiate jump/fall anim */

	if ((v->pos.y_pos != v->floor)
	&&  (l->current_anim_state != QS_FALL)
	&&  (l->current_anim_state != QS_LAND)
	&&  (l->current_anim_state != QS_FALLOFF)
	&&  (!dead))
	{
		if (bike->Velocity < 0)
			l->anim_number = objects[VEHICLE_ANIM].anim_index + QUADBIKE_FALLSTART_A;
		else
			l->anim_number = objects[VEHICLE_ANIM].anim_index + QUADBIKE_FALLSTART2_A;

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

/* -------- initiate hit anims */

	else if ((collide)
		&&  (l->current_anim_state != QS_HITFRONT)
		&&  (l->current_anim_state != QS_HITBACK)
		&&  (l->current_anim_state != QS_HITLEFT)
		&&  (l->current_anim_state != QS_HITRIGHT)
		&&  (l->current_anim_state != QS_FALLOFF)
		&&  (bike->Velocity > (MAX_SPEED/3))
		&&  (!dead))
	{
		if (collide == SKIDOO_HIT_FRONT)
		{
			l->anim_number = objects[VEHICLE_ANIM].anim_index + Q_HITF_A;
			l->current_anim_state = l->goal_anim_state = QS_HITFRONT;
		}

		else if (collide == SKIDOO_HIT_BACK)
		{
			l->anim_number = objects[VEHICLE_ANIM].anim_index + Q_HITB_A;
			l->current_anim_state = l->goal_anim_state = QS_HITBACK;
		}

		else if (collide == SKIDOO_HIT_LEFT)
		{
			l->anim_number = objects[VEHICLE_ANIM].anim_index + Q_HITL_A;
			l->current_anim_state = l->goal_anim_state = QS_HITLEFT;
		}

		else
		{
			l->anim_number = objects[VEHICLE_ANIM].anim_index + Q_HITR_A;
			l->current_anim_state = l->goal_anim_state = QS_HITRIGHT;
		}

		l->frame_number = anims[l->anim_number].frame_base;
		SoundEffect(202, &v->pos, 0);
	}

/* -------- initiate anims through state changes */

	else switch (l->current_anim_state)
	{
	/* --------------------- */
		case QS_STOP:
	/* --------------------- */
			if (dead)
				l->goal_anim_state = QS_BIKEDEATH;

			else if (input & IN_DISMOUNT && bike->Velocity == 0 && NoGetOff == 0 )
			{
				if ((input & IN_RIGHT) && (CanGetOff(1)))
					l->goal_anim_state = QS_GETOFFR;
				else if ((input & IN_LEFT) && (CanGetOff(-1)))
					l->goal_anim_state = QS_GETOFFL;
			}

			else if (input & (IN_ACCELERATE|IN_BRAKE))
			{
				l->goal_anim_state = QS_DRIVE;
				slowdamp = 0;
			}

			break;
	/* --------------------- */
		case QS_DRIVE:
	/* --------------------- */
			if (dead)
			{
				if (bike->Velocity > (MAX_SPEED/2))
			 		l->goal_anim_state = QS_FALLDEATH;
				else
					l->goal_anim_state = QS_BIKEDEATH;
			}

			else if (((bike->Velocity >> 8) == 0)
				&& (!(input & (IN_ACCELERATE|IN_BRAKE))))
					l->goal_anim_state = QS_STOP;

			else if (input & IN_LEFT && !HandbrakeStarting)
				l->goal_anim_state = QS_TURNL;

			else if (input & IN_RIGHT && !HandbrakeStarting)
				l->goal_anim_state = QS_TURNR;

			else if (input & IN_BRAKE)
			{
				if (bike->Velocity > ((MAX_SPEED/3)*2))
					l->goal_anim_state = QS_BRAKE;
				else
					l->goal_anim_state = QS_SLOW;
			}

//			else if (!(input & IN_FORWARD))
//			{
//				if (slowdamp++ > 5)
//					l->goal_anim_state = QS_SLOW;
//			}

			break;
	/* --------------------- */
		case QS_BRAKE:
		case QS_SLOW:
		case	QS_STOPSLOWLY:
	/* --------------------- */
			if ((bike->Velocity >> 8) == 0)
				l->goal_anim_state = QS_STOP;

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

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

			break;
	/* --------------------- */
		case QS_TURNL:
	/* --------------------- */
			if ((bike->Velocity >> 8) == 0)
				l->goal_anim_state = QS_STOP;
			else if (input & IN_RIGHT)
			{
				l->anim_number = objects[VEHICLE_ANIM].anim_index + QUADBIKE_TURNR_A;
				l->frame_number = anims[l->anim_number].frame_base;
				l->current_anim_state = l->goal_anim_state = QS_TURNR;
			}
			else if (!(input & IN_LEFT))
			{
				l->goal_anim_state = QS_DRIVE;
				slowdamp = 0;
			}
			break;
	/* --------------------- */
		case QS_TURNR:
	/* --------------------- */
			if ((bike->Velocity >> 8) == 0)
				l->goal_anim_state = QS_STOP;
			else if (input & IN_LEFT)
			{
				l->anim_number = objects[VEHICLE_ANIM].anim_index + QUADBIKE_TURNL_A;
				l->frame_number = anims[l->anim_number].frame_base;
				l->current_anim_state = l->goal_anim_state = QS_TURNL;
			}
			else if (!(input & IN_RIGHT))
			{
				l->goal_anim_state = QS_DRIVE;
				slowdamp = 0;
			}
			break;
	/* --------------------- */
		case QS_FALL:
	/* --------------------- */
			if (v->pos.y_pos == v->floor)
				l->goal_anim_state = QS_LAND;

			else if (v->fallspeed > TERMINAL_FALLSPEED && CurrentLevel != LV_GYM)
				bike->Flags |= QF_FALLING;
//##				l->goal_anim_state = QS_FALLOFF;

/*			else if (room[v->room_number].flags & (UNDERWATER|SWAMP))
			{
				int wh, wd, hfw;

				wd = GetWaterDepth(v->pos.x_pos, v->pos.y_pos, v->pos.z_pos, v->room_number);
				wh = GetWaterHeight(v->pos.x_pos, v->pos.y_pos, v->pos.z_pos, v->room_number);

  				if (wh != NO_HEIGHT)
					hfw = v->pos.y_pos - wh;
				else
					hfw = NO_HEIGHT;

				if ((hfw != NO_HEIGHT)
				&&  (hfw >= WADE_DEPTH)
				&&  (wd > (SWIM_DEPTH - STEP_L)))
					l->goal_anim_state = QS_FALLOFF;
				l->goal_anim_state = QS_FALLOFF;
				l->hit_points = 0;
				QuadbikeExplode(v);
			}                                         */
			break;
	/* --------------------- */
		case QS_FALLOFF:
	/* --------------------- */
			break;
	/* --------------------- */
		case QS_HITFRONT:
		case QS_HITBACK:
		case QS_HITLEFT:
		case QS_HITRIGHT:
	/* --------------------- */
			if (input & (IN_ACCELERATE|IN_BRAKE))
				l->goal_anim_state = QS_DRIVE;
			break;
	}

	if (room[v->room_number].flags & (UNDERWATER|SWAMP))
	{
		l->goal_anim_state = QS_FALLOFF;
		l->hit_points = 0;
		QuadbikeExplode(v);
	}

#ifdef DEBUG_SKIDOO
	sprintf(exit_message, "Cur:%d  Req:%d", l->current_anim_state, l->goal_anim_state);
	PrintDbug(1, 8, exit_message);

	sprintf(exit_message, "Speed:%x", (int)bike->Velocity);
	PrintDbug(1, 9, exit_message);

	sprintf(exit_message, "Momentum:%x", ABS(v->pos.y_rot - bike->momentum_angle));
	PrintDbug(1, 10, exit_message);
#endif
}
/****************************************************************************
* UserControl
*
* SYNOPSIS
*	int UserControl(ITEM_INFO *v, sint32 height, int *pitch)
*
* PURPOSE
*	Alter dynamics according to user's input
*
* INPUTS
*	v			= ptr to vehicle structure
*	height		=
*	pitch		= ptr to engine's pitch variable
*
* RETURNS
*	!0 if user controlled
*/

static int UserControl(ITEM_INFO *v, sint32 height, int *pitch)
{
	int drive=0,revs;
	QUADINFO *bike;

	bike = (QUADINFO *)v->data;

	if (!bike->Velocity && !(input & IN_HANDBRAKE) && !CanHandbrakeStart)
		CanHandbrakeStart = 1;
	else if (bike->Velocity)
		CanHandbrakeStart = 0;

	if (!(input & IN_HANDBRAKE))
		HandbrakeStarting = 0;

	if (!HandbrakeStarting)
	{
		if (bike->Revs > 0x10)
		{
			bike->Velocity += bike->Revs>>4;
			bike->Revs -= bike->Revs>>3;
		}
		else
			bike->Revs = 0;
	}

/* -------- vehicle is on the ground */

	if (v->pos.y_pos >= (height - STEP_L))
	{
		if ((!bike->Velocity) && (input & IN_LOOK))
			LookUpDown();

	/* -------- turn vehicle */

		if (bike->Velocity > 0)  	// Forward.
		{
			if ((input & IN_HANDBRAKE) && !HandbrakeStarting && bike->Velocity > MIN_HANDBRAKE_SPEED)
			{
				if (input & IN_LEFT)
				{
					bike->skidoo_turn -= SKIDOO_HTURN;
					if (bike->skidoo_turn < -SKIDOO_MAX_HTURN)
						bike->skidoo_turn = -SKIDOO_MAX_HTURN;
				}
				else if (input & IN_RIGHT)
				{
					bike->skidoo_turn += SKIDOO_HTURN;
					if (bike->skidoo_turn > SKIDOO_MAX_HTURN)
						bike->skidoo_turn = SKIDOO_MAX_HTURN;
				}
			}
			else
			{
				if (input & IN_LEFT)
				{
					bike->skidoo_turn -= SKIDOO_TURN;
					if (bike->skidoo_turn < -SKIDOO_MAX_TURN)
						bike->skidoo_turn = -SKIDOO_MAX_TURN;
				}
				else if (input & IN_RIGHT)
				{
					bike->skidoo_turn += SKIDOO_TURN;
					if (bike->skidoo_turn > SKIDOO_MAX_TURN)
						bike->skidoo_turn = SKIDOO_MAX_TURN;
				}
			}
		}
		else if (bike->Velocity < 0)
		{
			if ((input & IN_HANDBRAKE) && !HandbrakeStarting && bike->Velocity < -MIN_HANDBRAKE_SPEED+0x800)
			{
				if (input & IN_RIGHT)
				{
					bike->skidoo_turn -= SKIDOO_HTURN;
					if (bike->skidoo_turn < -SKIDOO_MAX_HTURN)
						bike->skidoo_turn = -SKIDOO_MAX_HTURN;
				}
				else if (input & IN_LEFT)
				{
					bike->skidoo_turn += SKIDOO_HTURN;
					if (bike->skidoo_turn > SKIDOO_MAX_HTURN)
						bike->skidoo_turn = SKIDOO_MAX_HTURN;
				}
			}
			else
			{
				if (input & IN_RIGHT)
				{
					bike->skidoo_turn -= SKIDOO_TURN;
					if (bike->skidoo_turn < -SKIDOO_MAX_TURN)
						bike->skidoo_turn = -SKIDOO_MAX_TURN;
				}
				else if (input & IN_LEFT)
				{
					bike->skidoo_turn += SKIDOO_TURN;
					if (bike->skidoo_turn > SKIDOO_MAX_TURN)
						bike->skidoo_turn = SKIDOO_MAX_TURN;
				}
			}
		}

	/* -------- brake/reverse */

		if (input & IN_BRAKE)
		{
			if ((input & IN_HANDBRAKE) && (CanHandbrakeStart || HandbrakeStarting))
			{
				HandbrakeStarting = 1;
				bike->Revs-=0x200;
				if (bike->Revs < MAX_BACK)
					bike->Revs = MAX_BACK;
			}
			else if (bike->Velocity > 0)
				bike->Velocity -= BRAKE;
			else
			{
				if (bike->Velocity > MAX_BACK)
					bike->Velocity += REVERSE_ACC;
//				drive = 1;
			}
		}

	/* -------- accelerate */

		else if (input & IN_ACCELERATE)
		{
			if ((input & IN_HANDBRAKE) && (CanHandbrakeStart || HandbrakeStarting))
			{
				HandbrakeStarting = 1;
				bike->Revs+=0x200;
				if (bike->Revs >= MAX_SPEED)
					bike->Revs = MAX_SPEED;
			}
			else if (bike->Velocity < MAX_SPEED)
			{
				if (bike->Velocity < ACCELERATION_1)
					bike->Velocity += 8 + ((ACCELERATION_1+0x800 - bike->Velocity) >> 3);
				else if (bike->Velocity < ACCELERATION_2)
					bike->Velocity += 4 + ((ACCELERATION_2+0x800 - bike->Velocity) >> 4);
				else if (bike->Velocity < MAX_SPEED)
					bike->Velocity += 2 + ((MAX_SPEED - bike->Velocity) >> 3);
			}
			else
				bike->Velocity = MAX_SPEED;

		/* -------- apply friction according to turn */

			bike->Velocity -= (ABS(v->pos.y_rot - bike->momentum_angle)) >> 6;
//			drive = 1;
		}

	/* -------- slow with friction */

		else if (bike->Velocity > FRICTION)
		{
			bike->Velocity -= FRICTION;
//			if ((GetRandomDraw() & 0x7f) < bike->Velocity)
//				drive = 1;
		}
		else if (bike->Velocity < -FRICTION)
		{
				bike->Velocity += FRICTION;
//			if ((GetRandomDraw() & 0x7f) < -bike->Velocity)
//				drive = 1;
		}
		else
		{
			bike->Velocity = 0;
		}

		if (HandbrakeStarting && bike->Revs && !(input & (IN_ACCELERATE|IN_BRAKE)))
		{
			if (bike->Revs > 0x8)
				bike->Revs -= bike->Revs>>3;
			else
				bike->Revs = 0;
		}

		v->speed = bike->Velocity >> 8;

	/* -------- determine engine note */

		if (bike->EngineRevs > REV_PITCH2E)
			bike->EngineRevs = REV_PITCH1S;

		if (bike->Velocity < 0)
			revs = abs(bike->Velocity>>1);
		else if (bike->Velocity < ACCELERATION_2)
			revs = REV_PITCH1S + ((bike->Velocity * (REV_PITCH1E-REV_PITCH1S))/ACCELERATION_2);
		else if (bike->Velocity <= MAX_SPEED)
			revs = REV_PITCH2S + (((bike->Velocity-ACCELERATION_2) * (REV_PITCH2E-REV_PITCH2S))/(MAX_SPEED-ACCELERATION_2));

		revs += abs(bike->Revs);	// Add on handbrake start revs.
		bike->EngineRevs += (revs - bike->EngineRevs)>>3;
	}

/* -------- vehicle is above ground */

	else
	{
//		if (input & (IN_ACCELERATE|IN_BRAKE))
//			drive = 1;
		if (bike->EngineRevs < REV_INAIR)
			bike->EngineRevs += (REV_INAIR-bike->EngineRevs)>>3;
	}

	*pitch = bike->EngineRevs;

#ifdef DEBUG_SKIDOO
//	if (input & IN_ROLL)
//		lara_item->hit_points = 0;
#endif

	return drive;
}

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

/****************************************************************************
* InitialiseQuadBike
*
* SYNOPSIS
*	void InitialiseQuadBike(sint16 item_number)
*
* PURPOSE
*	Initialises the quadbike
*
* INPUTS
*	item_number	= vehicle item #
*
* RETURNS
*	Nothing
*/
void InitialiseQuadBike(sint16 item_number)
{
	ITEM_INFO *v;
	QUADINFO *bike;

	v = &items[item_number];
	bike = (QUADINFO *)v->data = game_malloc(sizeof(QUADINFO), 0);

	bike->Velocity = 0;

	bike->skidoo_turn = 0;
	bike->left_fallspeed = bike->right_fallspeed = 0;
	bike->momentum_angle = v->pos.y_rot;
	bike->extra_rotation = 0;
	bike->track_mesh = 0;
	bike->pitch = 0;
	bike->Flags = 0;
}
/****************************************************************************
* QuadBikeCollision
*
* SYNOPSIS
*	void QuadBikeCollision(sint16 item_number, ITEM_INFO *litem, COLL_INFO *coll)
*
* PURPOSE
*	Determines whether Lara wants to get on the quadbike
*
* INPUTS
*	item_number	= vehicle item #
*	litem		= ptr to Lara's item structure
*	coll			= ptr to Lara's collision structure
*
* RETURNS
*	Nothing
*/
void QuadBikeCollision(sint16 item_number, ITEM_INFO *l, COLL_INFO *coll)
{
	int geton;

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

	if ((geton = GetOnQuadBike(item_number, coll)))
	{
		sint16 ang;
		QUADINFO 	*bike;
		ITEM_INFO *v;
		static char tunes[] = { 9, 12, 4, 12 };

		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;

	/* -------- determine animation */

		v = &items[item_number];

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

		if ((ang > -(ONE_DEGREE*45)) && (ang < (ONE_DEGREE*135)))
		{
			l->anim_number = objects[VEHICLE_ANIM].anim_index + QUADBIKE_GETONL_A;
			l->current_anim_state = l->goal_anim_state = QS_GETONL;
		}
		else
		{
			l->anim_number = objects[VEHICLE_ANIM].anim_index + QUADBIKE_GETONR_A;
			l->current_anim_state = l->goal_anim_state = QS_GETONR;
		}

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

		v->hit_points = 1;				// enable collision
		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.y_rot = v->pos.y_rot;
		lara.head_x_rot = lara.head_y_rot = 0;
		lara.torso_x_rot = lara.torso_y_rot = 0;
		lara.hit_direction = -1;

		AnimateItem(l);

		if (CurrentLevel == LV_QUADBIKE)
		{
			int track;

			track = tunes[GetRandomControl()&3];

			if ((track != cdtrack)
			&&  (IsAtmospherePlaying))
			{
				cdtrack = track;
				S_CDPlay(track, 0);
			}
		}

		bike = (QUADINFO *)v->data;
		bike->Revs = 0;
	}
	else
		ObjectCollision(item_number, l, coll);
}
/****************************************************************************
* QuadBikeDraw
*
* SYNOPSIS
*	void QuadBikeDraw(ITEM_INFO *item)
*
* PURPOSE
*	Draws the quad-bike
*
* INPUTS
*	item		= ptr to the quad-bike item
*
* RETURNS
*	Nothing
*/
void QuadBikeDraw(ITEM_INFO *item)
{
	sint16 *frmptr[2];
	int clip, rate, frac;

	frac = GetFrames(item, frmptr, &rate);

	phd_PushMatrix();
	phd_TranslateAbs(item->pos.x_pos, item->pos.y_pos, item->pos.z_pos);
	phd_RotYXZ(item->pos.y_rot, item->pos.x_rot, item->pos.z_rot);

	if ((clip = S_GetObjectBounds(frmptr[0])))
	{
		sint32 *bone;
		QUADINFO *bike;
		sint16 **meshpp;
		sint16 *rotation;
		OBJECT_INFO *object;

		CalculateObjectLighting(item, frmptr[0]);

		object = &objects[item->object_number];
		meshpp = &meshes[object->mesh_index];
		bone = bones + object->bone_index;

		bike = (QUADINFO *)item->data;

	/* -------- draw chassis */

		phd_TranslateRel((sint32)*(frmptr[0]+6), (sint32)*(frmptr[0]+7), (sint32)*(frmptr[0]+8));
		rotation = frmptr[0] + 9;
		gar_RotYXZsuperpack(&rotation, 0);

		phd_PutPolygons(*meshpp++, clip);

	/* -------- draw handlebars */

		phd_PushMatrix();
		phd_TranslateRel(*(bone+1), *(bone+2), *(bone+3));
		gar_RotYXZsuperpack(&rotation, 0);
		phd_PutPolygons(*meshpp++, clip);
		phd_PopMatrix();

	/* -------- draw rear axle */

		phd_PushMatrix();
		phd_TranslateRel(*(bone+1+4), *(bone+2+4), *(bone+3+4));
		gar_RotYXZsuperpack(&rotation, 0);
		phd_PutPolygons(*meshpp++, clip);

	/* -------- draw rear wheels */

		phd_PushMatrix();
		phd_TranslateRel(*(bone+1+8), *(bone+2+8), *(bone+3+8));
		gar_RotYXZsuperpack(&rotation, 0);
		phd_RotX(bike->RearRot);
		phd_PutPolygons(*meshpp++, clip);
		phd_PopMatrix();

		phd_PushMatrix();
		phd_TranslateRel(*(bone+1+12), *(bone+2+12), *(bone+3+12));
		gar_RotYXZsuperpack(&rotation, 0);
		phd_RotX(bike->RearRot);
		phd_PutPolygons(*meshpp++, clip);
		phd_PopMatrix();

		phd_PopMatrix();

	/* -------- draw front axle */

		phd_PushMatrix();
		phd_TranslateRel(*(bone+1+16), *(bone+2+16), *(bone+3+16));
		gar_RotYXZsuperpack(&rotation, 0);
		phd_PutPolygons(*meshpp++, clip);

	/* -------- draw front wheels */

		phd_PushMatrix();
		phd_TranslateRel(*(bone+1+20), *(bone+2+20), *(bone+3+20));
		gar_RotYXZsuperpack(&rotation, 0);
		phd_RotX(bike->FrontRot);
		phd_PutPolygons(*meshpp++, clip);
		phd_PopMatrix();

		phd_PushMatrix();
		phd_TranslateRel(*(bone+1+24), *(bone+2+24), *(bone+3+24));
		gar_RotYXZsuperpack(&rotation, 0);
		phd_RotX(bike->FrontRot);
		phd_PutPolygons(*meshpp++, clip);
		phd_PopMatrix();
		phd_PopMatrix();
	}

	phd_PopMatrix();
}
/****************************************************************************
* QuadBikeControl
*
* SYNOPSIS
*	int QuadBikeControl()
*
* PURPOSE
*	Quadbike controller
*
* INPUTS
*	None
*
* RETURNS
*	0 if not under Quadbike control
*/
int QuadBikeControl()
{
	QUADINFO *bike;
	ITEM_INFO *v, *l;
	GAME_VECTOR	oldpos;
	PHD_VECTOR fl, fr;
	sint32 hfl, hfr;
	FLOOR_INFO *floor;
	sint32 height, collide, drive, ceiling;
	sint16 room_number, x_rot, z_rot, rotadd;
	int pitch, dead=0;

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

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

	collide = SkidooDynamics(v);

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

/*	if (v->pos.y_pos-height < 32 && v->pos.y_pos > ceiling+64 && ceiling != NO_HEIGHT)
		v->pos.y_pos = height;
	else if (v->pos.y_pos > height || height == NO_HEIGHT || ceiling == NO_HEIGHT || ceiling >= height || v->pos.y_pos < ceiling)
	{
		printf("?");
		CameraCollisionBounds(&oldpos, 256, 0);
		v->pos.x_pos = oldpos.x;
		v->pos.y_pos = oldpos.y;
		v->pos.z_pos = oldpos.z;
		v->room_number = oldpos.room_number;
	}*/

/*
 * Now got final position, so get heights under middle and corners (will only
 * have changed from above if collision occurred, but recalc anyway as hardly
 * big maths)
 */
	hfl = TestHeight(v, SKIDOO_FRONT, -SKIDOO_SIDE, &fl);
	hfr = TestHeight(v, SKIDOO_FRONT,  SKIDOO_SIDE, &fr);

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

	TestTriggers(trigger_index, 0 );

/* Need to know what status Lara has w.r.t. the skidoo; has she died or fallen off? */

	if (lara_item->hit_points <= 0)
	{
		input &= ~(IN_LEFT|IN_RIGHT|IN_BACK|IN_FORWARD);
		dead = 1;
	}

/* Deal with user input (if allowed) */

	if (bike->Flags)
	{
//		drive = 0;
		collide = 0;
	}
	else
	{
		switch (lara_item->current_anim_state)
		{
			case QS_GETONL:
			case QS_GETONR:
			case QS_GETOFFL:
			case QS_GETOFFR:
				drive = -1;
				collide = 0;
				break;

			default:
				drive = UserControl(v, height, &pitch);
				break;
		}
	}

	if (bike->Velocity || bike->Revs)
	{
		long absvel = abs(bike->Velocity)+1;
		/* Do engine noise */
//##		bike->pitch += (pitch - bike->pitch) >> 2;
//##		SoundEffect(155, &v->pos, PITCH_SHIFT + (0x10000 - (MAX_SPEED - bike->pitch)*100 <<8));
		bike->pitch = pitch;
		if (bike->pitch < -0x8000)
			bike->pitch = -0x8000;
		else if (bike->pitch > 0xa000)
			bike->pitch = 0xa000;
		SoundEffect(155, &v->pos, PITCH_SHIFT + ((0x10000 + bike->pitch) << 8));
	}
	else
	{
		if (drive!=-1)
			SoundEffect(153, &v->pos, 0);
		bike->pitch = 0;
	}

	v->floor = height;

	rotadd = bike->Velocity>>2;
	bike->RearRot -= rotadd;
	bike->RearRot -= (bike->Revs >> 3);
	bike->FrontRot -= rotadd;

/* Do fallspeed effects on skidoo */
	bike->left_fallspeed = DoDynamics(hfl, bike->left_fallspeed, (int *)&fl.y);
	bike->right_fallspeed = DoDynamics(hfr, bike->right_fallspeed, (int *)&fr.y);
	v->fallspeed = DoDynamics(height, v->fallspeed, (int *)&v->pos.y_pos);

/* Rotate skidoo to match these heights */
	height = (fl.y + fr.y) >> 1;
	x_rot = phd_atan(SKIDOO_FRONT, v->pos.y_pos - height);
	z_rot = phd_atan(SKIDOO_SIDE, height - fl.y);

	v->pos.x_rot += (x_rot - v->pos.x_rot) >> 1;
	v->pos.z_rot += (z_rot - v->pos.z_rot) >> 1;

/* -------- update vehicle without Lara */

/*	if (bike->Flags & QF_FALLING)
	{
		if (room_number != v->room_number)
			ItemNewRoom(lara.skidoo, room_number);

		if (v->pos.y_pos == v->floor)
		{
			ExplodingDeath(lara.item_number, 0xffffffff, 1);
			lara_item->hit_points = 0;
			lara_item->flags |= ONESHOT; // flag Lara as invisible
			QuadbikeExplode(v);
			return 0;
		}

		v->anim_number = objects[QUADBIKE].anim_index + QUADBIKE_FALL_A;
		v->frame_number = QUADBIKE_FALL_F;
	}*/

	if (!(bike->Flags & QF_DEAD))
//	{
//		printf("+");
//	}

/* -------- update vehicle & Lara */

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

		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.y_rot = v->pos.y_rot;

//		if (drive >= 0)
//		{
			l->pos.x_rot = v->pos.x_rot;
			l->pos.z_rot = v->pos.z_rot;
//		}
//		else
//			l->pos.x_rot = l->pos.z_rot = 0;

	/* -------- animate Lara then vehicle */

		AnimateQuadBike(v, collide, dead);
		AnimateItem(l);

		v->anim_number = objects[QUADBIKE].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);

		camera.target_elevation = -30*ONE_DEGREE;

		if (bike->Flags & QF_FALLING)
		{
			if (v->pos.y_pos == v->floor)
			{
				ExplodingDeath(lara.item_number, 0xffffffff, 1);
				lara_item->hit_points = 0;
				lara_item->flags |= ONESHOT; // flag Lara as invisible
				QuadbikeExplode(v);
				return 0;
			}
		}
	}

// Do exhaust smoke.

	if (	l->current_anim_state != QS_GETONR && l->current_anim_state != QS_GETONL &&
		l->current_anim_state != QS_GETOFFR && l->current_anim_state != QS_GETOFFL)
	{
		PHD_VECTOR	pos;
		long		lp,speed;
		sint16 	angle;

		for (lp=0;lp<2;lp++)
		{
			pos.x = quad_bites[lp].x;
			pos.y = quad_bites[lp].y;
			pos.z = quad_bites[lp].z;
			GetJointAbsPosition(v,&pos,quad_bites[lp].mesh_num);
			angle = v->pos.y_rot + ((lp==0) ? 0x9000 : 0x7000);
			if (v->speed > 32)
			{
				if (v->speed < 64)
				{
					speed = 64-v->speed;
					TriggerExhaustSmoke(pos.x,pos.y,pos.z,angle,speed,1);
				}
			}
			else
			{
				if (ExhaustStart < 16)
				{
					speed = ((ExhaustStart<<1) + (GetRandomControl()&7) + (GetRandomControl()&16)) << 7;
					ExhaustStart++;
				}
				else if (HandbrakeStarting)
					speed = (abs(bike->Revs)>>2)+((GetRandomControl()&7)<<7);
				else if ((GetRandomControl()&3)==0)
					speed = ((GetRandomControl()&15) + (GetRandomControl()&16)) << 7;
				else
					speed = 0;
				TriggerExhaustSmoke(pos.x,pos.y,pos.z,angle,speed,0);
			}
		}
	}
	else	// Reset clock and remove if getting off bike.
	{
		if (CurrentLevel == LV_GYM)
		{
			savegame.timer = 0;
			assault_timer_active = 0;
			assault_timer_display = 0;
		}
		ExhaustStart=0;
	}


//	if (dead)
//	{
//		v->anim_number = objects[QUADBIKE].anim_index + SKIDOO_DEAD_ANIM;
//		v->frame_number = anims[v->anim_number].frame_base;
//	}

	/* If skidoo is moving, then set off a snow spray sprite */
//##	if (v->speed && v->floor == v->pos.y_pos)
//##	{
//##		DoSnowEffect(v);
//##		if (v->speed < 50)
//##			DoSnowEffect(v);
//##	}

	return SkidooCheckGetOff();
}

static void TriggerExhaustSmoke(long x, long y, long z, sint16 angle, long speed, long moving)
{
	long		size,xv,zv;
	SPARKS	*sptr;

	sptr = &spark[GetFreeSpark()];

	sptr->On = 1;
	sptr->sR = 0;
	sptr->sG = 0;
	sptr->sB = 0;

	sptr->dR = 96;
	sptr->dG = 96;
	sptr->dB = 128;

	if (moving)
	{
		sptr->dR = (sptr->dR * speed)>>5;
		sptr->dG = (sptr->dG * speed)>>5;
		sptr->dB = (sptr->dB * speed)>>5;
	}

	sptr->ColFadeSpeed = 4;
	sptr->FadeToBlack = 4;
	sptr->sLife = sptr->Life = (GetRandomControl()&3)+20-(speed>>12);
	if (sptr->sLife < 9)
		sptr->sLife = sptr->Life = 9;

	sptr->TransType = COLADD;

	sptr->extras = 0;
	sptr->Dynamic = -1;

	sptr->x = x + ((GetRandomControl()&15)-8);
	sptr->y = y + ((GetRandomControl()&15)-8);
	sptr->z = z + ((GetRandomControl()&15)-8);
	zv = (speed * phd_cos(angle)) >> (W2V_SHIFT+2);
	xv = (speed * phd_sin(angle)) >> (W2V_SHIFT+2);
	sptr->Xvel = xv+((GetRandomControl()&255)-128);
	sptr->Yvel = -(GetRandomControl()&7)-8;
	sptr->Zvel = zv+((GetRandomControl()&255)-128);
	sptr->Friction = 4;

	if (GetRandomControl()&1)
	{
		sptr->Flags = SP_SCALE|SP_DEF|SP_ROTATE|SP_EXPDEF;
		sptr->RotAng = GetRandomControl()&4095;
		if (GetRandomControl()&1)
			sptr->RotAdd = -(GetRandomControl()&7)-24;
		else
			sptr->RotAdd = (GetRandomControl()&7)+24;
	}
	else
		sptr->Flags = SP_SCALE|SP_DEF|SP_EXPDEF;

	sptr->Def = objects[EXPLOSION1].mesh_index;
	sptr->Scalar = 2;
	sptr->Gravity = -(GetRandomControl()&3)-4;
	sptr->MaxYvel = -(GetRandomControl()&7)-8;
	size = (GetRandomControl()&7)+32 + (speed>>7);
	sptr->Width = sptr->sWidth = size>>1;
	sptr->dWidth = size;
 	sptr->Height = sptr->sHeight = size>>1;
	sptr->dHeight = size;
}

