
/***********************************************************************************************************/
/*                                                                                                         */
/* Boat Stuff                                                                               G.Rummery 1997 */
/*                                                                                                         */
/***********************************************************************************************************/

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

#ifdef PSX_VERSION
#include <sys\types.h>
#include <libgte.h>
#include <libgpu.h>
#include <gtemac.h>
#include <inline_c.h>
#include <stdio.h>
#include "../spec_psx/typedefs.h"
#include "../spec_psx/psxinput.h"
#include "../spec_psx/3d_gen.h"
#include "../spec_psx/malloc.h"
#include "../spec_psx/specific.h"
#include "..\spec_psx\maths.h"
#else
#include "../specific/stypes.h"
#include "../specific/init.h"
#include "../specific/input.h"
#include "../3dsystem/3d_gen.h"

#endif

#include "objects.h"
#include "lara.h"
#include "laraanim.h"
#include "camera.h"
#include "control.h"
#include "input.h"
#include "sound.h"
#include "sphere.h"
#include "boatinfo.h"
#include "effect2.h"

static void BoatSplash(ITEM_INFO *item, long fallspeed, long water);
static void TriggerBoatMist(long x, long y, long z, long speed, sint16 angle, long snow);

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

enum boat_anims {
	BOAT_GETON,
	BOAT_STILL,
	BOAT_MOVING,
	BOAT_JUMPR,
	BOAT_JUMPL,
	BOAT_HIT,
	BOAT_FALL,
	BOAT_TURNR,
	BOAT_DEATH,
	BOAT_TURNL
};

#define	IN_ACCELERATE	IN_ACTION
#define	IN_REVERSE	IN_JUMP
#define	IN_DISMOUNT	IN_ROLL
#define	IN_TURBO		(IN_DASH|IN_DUCK)
#define	IN_TURNL		(IN_LEFT|IN_STEPL)
#define	IN_TURNR		(IN_RIGHT|IN_STEPR)

#define BOAT_GETONLW_ANIM	0
#define BOAT_GETONRW_ANIM	8
#define BOAT_GETONJ_ANIM		6
#define BOAT_GETON_START		1
#define BOAT_FALL_ANIM		15
#define BOAT_DEATH_ANIM		18

#define BOAT_UNDO_TURN	(ONE_DEGREE/4)
#define BOAT_TURN		(ONE_DEGREE/8)
#define BOAT_MAX_TURN	(ONE_DEGREE*4)

#define BOAT_MAX_SPEED		110
#define BOAT_SLOW_SPEED		(BOAT_MAX_SPEED/3)
#define BOAT_FAST_SPEED		(BOAT_MAX_SPEED+75)
#define BOAT_MIN_SPEED		20
#define BOAT_ACCELERATION	5
#define BOAT_BRAKE			5
#define BOAT_SLOWDOWN		1
#define BOAT_REVERSE		-2	// -5
#define BOAT_MAX_BACK		-20
#define BOAT_MAX_KICK		-80

#define BOAT_SLIP		10
#define BOAT_SIDE_SLIP	30

#define BOAT_FRONT	750
#define BOAT_SIDE	300
#define BOAT_RADIUS 500
#define BOAT_SNOW	500

#define BOAT_MAX_HEIGHT (STEP_L)

#define GETOFF_DIST (1024)

#define BOAT_WAKE 700
#define BOAT_SOUND_CEILING (WALL_L*5)

#define BOAT_TIP (BOAT_FRONT+250)

#define NUM_WAKE_SPRITES	32
#define WAKE_SIZE 		32
#define WAKE_SPEED 		4

//#ifdef PSX_VERSION
#define SKIDOO_HIT_LEFT  11
#define SKIDOO_HIT_RIGHT 12
#define SKIDOO_HIT_FRONT 13
#define SKIDOO_HIT_BACK  14
//#endif

typedef struct {
	sint32 	x[2];
	sint32 	y;
	sint32	z[2];
	sint16	xvel[2];
	sint16	zvel[2];
	uchar 	life;
	uchar	pad[3];
} WAKE_PTS;

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

#ifdef PSX_VERSION
extern ITEM_INFO *boatitem;
extern int lara_inboat;
#endif

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

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

void InitialiseBoat(sint16 item_number);
void BoatControl(sint16 item_number);
void BoatCollision(sint16 item_number, ITEM_INFO *litem, COLL_INFO *coll);
void DrawBoat(ITEM_INFO *item);

void *func[] = {
	&InitialiseBoat,		// initialise
	&BoatControl,			// control
	&BoatCollision, 		// collision
	&DrawBoat				// draw routine
};

#endif

static WAKE_PTS WakePts[NUM_WAKE_SPRITES][2];	// Left and right wake.
static uchar CurrentStartWake = 0;
static uchar WakeShade = 0;

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

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 - v->pos.y_pos) < -WALL_L/2)
		return 0;

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

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

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

	return 1;
}

/****************************************************************************
* S_DrawWakeFX
*
* SYNOPSIS
*	void S_DrawWakeFX()
*
* PURPOSE
*	Draws any active wake effects
*
* INPUTS
* 	None
*
* RETURNS
*	Nothing
*/
#ifdef PSX_VERSION

static void S_DrawWakeFX(ITEM_INFO *v)
{

	WAKE_PTS 	*w;
	POLY_G4 	*polyg4;
	short	*scrxy;	// X,Y
	long		*scrz;	// Z
	short	*TempMesh;
	uchar	*rgbs;
	long		lp,lp2,current;
	long		x1,x2,x3,x4,y1,y2,y3,y4,z1,z2,z3,z4,c,s,rgb12,rgb34;
	long		offsets[2][2] =	{	{	-256-128,0	},
								{	256+128,0	}
							};

	mPushMatrix();
//	SetRotMatrix((MATRIX*) Matrix);
//	SetTransMatrix((MATRIX*) Matrix);
//	mTranslateXYZ(lara_item->pos.x_pos,lara_item->pos.y_pos,lara_item->pos.z_pos);
	mTranslateAbsXYZ(lara_item->pos.x_pos,lara_item->pos.y_pos,lara_item->pos.z_pos);

	for (lp2=0;lp2<2;lp2++)
	{
		scrxy = (short *) 0x1f800000;	// Set pointer to scratch pad.
		scrz = (long *) 0x1f800100;	// Set pointer to scratch pad.
		rgbs = (uchar *) 0x1f800200;	// Set pointer to scratch pad.
		TempMesh = (short *) 0x1f800300;	// Set pointer to scratch pad.

	// Get first 2 points from trigger point on kayak.

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

		x1 = v->pos.x_pos + (((offsets[lp2][1] * s) + (offsets[lp2][0] * c)) >> W2V_SHIFT);
		y1 = v->pos.y_pos;
		z1 = v->pos.z_pos + (((offsets[lp2][1] * c) - (offsets[lp2][0] * s)) >> W2V_SHIFT);

		x1 -= lara_item->pos.x_pos;
		y1 -= lara_item->pos.y_pos;
		z1 -= lara_item->pos.z_pos;

		TempMesh[0] = x1;
		TempMesh[1] = y1;
		TempMesh[2] = z1;

		gte_ldv0(TempMesh);
		gte_rtps();
		gte_stsxy(scrxy);
		gte_stsz(scrz);
		scrxy+=2;
		scrz++;
		gte_stsxy(scrxy);
		gte_stsz(scrz);
		scrxy+=2;
		scrz++;

		*rgbs++ = 64;

	//--------------------

		current = (CurrentStartWake-1) & (NUM_WAKE_SPRITES-1);

		for ( lp=0 ; lp<NUM_WAKE_SPRITES ; lp++)
		{
			w = &WakePts[current][lp2];

			if (w->life)
			{
				ulong 	lp3;

				for (lp3=0;lp3<2;lp3++)
				{
					TempMesh[0] = w->x[lp3] - lara_item->pos.x_pos;
					TempMesh[1] = w->y - lara_item->pos.y_pos;
					TempMesh[2] = w->z[lp3] - lara_item->pos.z_pos;
					gte_ldv0(TempMesh);
					gte_rtps();
					gte_stsxy(scrxy);
					gte_stsz(scrz);
					scrxy+=2;
					scrz++;
				}

				*rgbs++ = w->life;
			}
			else
			{
				*scrz = -5555;	// Limiter.
    				break;
			}

			current--;
			current &= NUM_WAKE_SPRITES-1;
		}

		if (lp==0)
		{
			mPopMatrix();
			return;	// Nothing to draw.
		}

		scrxy = (short *) 0x1f800000;	// Set pointer to scratch pad.
		scrz = (long *) 0x1f800100;	// Set pointer to scratch pad.
		rgbs = (uchar *) 0x1f800200;	// Set pointer to scratch pad.

		x1 = *scrxy++;
		y1 = *scrxy++;
		x2 = *scrxy++;
		y2 = *scrxy++;
		z1 = *scrz++;
		z2 = *scrz++;
		rgb12 = *rgbs++;
		if (WakeShade < 16)
			rgb12 = (rgb12 * WakeShade) >> 4;

		for (;;)
		{
			z3 = *scrz++;
			if (z3 == -5555)
				break;

			x3 = *scrxy++;
			y3 = *scrxy++;
			x4 = *scrxy++;
			y4 = *scrxy++;
			z4 = *scrz++;
			rgb34 = *rgbs++;

			if (WakeShade < 16)
				rgb34 = (rgb34 * WakeShade) >> 4;

			z1 = (z1+z2+z3+z4)>>2;

			if ((z1 > 256) && (z1 < 0x5000) &&
				x1 > -128 && x2 > -128 && x3 > -128 && x4 > -128 &&
				x1 < 512+128 && x2 < 512+128 && x3 < 512+128 && x4 < 512+128 &&
				y1 > -128 && y2 > -128 && y3 > -128 && y4 > -128 &&
				y1 < 224+128 && y2 < 224+128 && y3 < 224+128 && y4 < 224+128)
			{
				if (db.polyptr >= db.polybuf_limit)
				{
					mPopMatrix();
					return;
				}

				polyg4 = (POLY_G4 *)db.polyptr;
				setlen(polyg4, 8);
				setcode(polyg4, 0x3a);
				setRGB0(polyg4,rgb12>>1,rgb12>>1,rgb12>>1);
				setRGB1(polyg4,rgb12<<1,rgb12<<1,rgb12<<1);
				setRGB2(polyg4,rgb34>>1,rgb34>>1,rgb34>>1);
				setRGB3(polyg4,rgb34<<1,rgb34<<1,rgb34<<1);
				setXY4(polyg4,x1,y1,x2,y2,x3,y3,x4,y4);

				MyaddPrim(db.ot, z1>>3, polyg4);
    				db.polyptr += sizeof(POLY_G4);

				if (db.polyptr >= db.polybuf_limit)
				{
					mPopMatrix();
					return;
				}

				setDrawTPage((DR_TPAGE *)db.polyptr, 0, 1, getTPage(0, 1, 0, 0));
				MyaddPrim( db.ot,z1>>3,(DR_TPAGE *)db.polyptr);
				db.polyptr += sizeof(DR_TPAGE);
			}

			x1 = x3;
			y1 = y3;
			z1 = z3;
			x2 = x4;
			y2 = y4;
			z2 = z4;
			rgb12 = rgb34;
		}
	}

	mPopMatrix();
}

#endif

/****************************************************************************
* DoWake
*
* SYNOPSIS
*	void DoWake(ITEM_INFO *v, sint16 xoff, sint16 zoff)
*
* PURPOSE
*	Initialises a wake effect
*
* INPUTS
*	v 			= ptr to vehicle's item
*	xoff			= ripple x offset
*	zoff			= ripple z offset
*
* RETURNS
*	Nothing
*/

static void DoWake(ITEM_INFO *v, sint16 xoff, sint16 zoff, sint16 rotate)
{
	FLOOR_INFO *floor;

	sint32 s, c, x, z, xv[2], zv[2], lp, h;
	sint16 room_number,angle1,angle2;

	if (WakePts[CurrentStartWake][rotate].life)
		return;

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

	x = v->pos.x_pos + (((zoff * s) + (xoff * c)) >> W2V_SHIFT);
	z = v->pos.z_pos + (((zoff * c) - (xoff * s)) >> W2V_SHIFT);

	room_number = v->room_number;
	floor = GetFloor(x, v->pos.y_pos+128, z, &room_number);
	h = GetWaterHeight(x, v->pos.y_pos+128, z, room_number);

//	if (h > v->pos.y_pos+32 || h == NO_HEIGHT)
	{
		if (v->speed < 0)
		{
			if (!rotate)
			{
				angle1 = v->pos.y_rot - (ONE_DEGREE*20);
				angle2 = v->pos.y_rot - (ONE_DEGREE*40);
			}
			else
			{
				angle1 = v->pos.y_rot + (ONE_DEGREE*20);
				angle2 = v->pos.y_rot + (ONE_DEGREE*40);
			}
		}
		else
		{
			if (!rotate)
			{
				angle1 = v->pos.y_rot - (ONE_DEGREE*160);
				angle2 = v->pos.y_rot - (ONE_DEGREE*140);
			}
			else
			{
				angle1 = v->pos.y_rot + (ONE_DEGREE*160);
				angle2 = v->pos.y_rot + (ONE_DEGREE*140);
			}
		}

		xv[0] = (WAKE_SPEED * phd_sin(angle1)) >> W2V_SHIFT;
		zv[0] = (WAKE_SPEED * phd_cos(angle1)) >> W2V_SHIFT;
		xv[1] = ((WAKE_SPEED+6) * phd_sin(angle2)) >> W2V_SHIFT;
		zv[1] = ((WAKE_SPEED+6) * phd_cos(angle2)) >> W2V_SHIFT;

		WakePts[CurrentStartWake][rotate].y = v->pos.y_pos + 32;
		WakePts[CurrentStartWake][rotate].life = 0x40;
		for (lp=0;lp<2;lp++)
		{
			WakePts[CurrentStartWake][rotate].x[lp] = x;
			WakePts[CurrentStartWake][rotate].z[lp] = z;
			WakePts[CurrentStartWake][rotate].xvel[lp] = xv[lp];
			WakePts[CurrentStartWake][rotate].zvel[lp] = zv[lp];
		}

		if (rotate == 1)
		{
			CurrentStartWake++;
			CurrentStartWake &= (NUM_WAKE_SPRITES-1);
		}
	}
}

/****************************************************************************
* UpdateWakeFX
*
* SYNOPSIS
*	void UpdateWakeFX()
*
* PURPOSE
*	Updates any active wake effects
*
* INPUTS
* 	None
*
* RETURNS
*	Nothing
*/
static void UpdateWakeFX()
{
	int lp,lp2;

	for ( lp=0 ; lp<2 ; lp++ )
	{
		for ( lp2=0 ; lp2<NUM_WAKE_SPRITES ; lp2++ )
		{
			if (WakePts[lp2][lp].life)
			{
				WakePts[lp2][lp].life--;
				WakePts[lp2][lp].x[0] += WakePts[lp2][lp].xvel[0] ;
				WakePts[lp2][lp].z[0] += WakePts[lp2][lp].zvel[0] ;
				WakePts[lp2][lp].x[1] += WakePts[lp2][lp].xvel[1] ;
				WakePts[lp2][lp].z[1] += WakePts[lp2][lp].zvel[1] ;
			}
		}
	}
}

//#ifdef PSX_VERSION

/*
 * 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 GetCollisionAnim(ITEM_INFO *skidoo, PHD_VECTOR *moved)
{
	sint32 c, s, front, side;

	moved->x = skidoo->pos.x_pos - moved->x;
	moved->z = skidoo->pos.z_pos - moved->z;

	if (moved->x || moved->z)
	{
		/* Get direction of movement relative to facing */
		c = phd_cos(skidoo->pos.y_rot);
		s = phd_sin(skidoo->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;
}

//#else

//extern int DoShift(ITEM_INFO *skidoo, PHD_VECTOR *pos, PHD_VECTOR *old);
//extern int GetCollisionAnim(ITEM_INFO *skidoo, PHD_VECTOR *moved);

//#endif

static sint32 TestWaterHeight(ITEM_INFO *item, sint32 z_off, sint32 x_off, PHD_VECTOR *pos)
{
	/* Get water 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, height;
	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);

	/* Try to get water height; if none get ground height instead */
	room_number = item->room_number;
	GetFloor(pos->x, pos->y, pos->z, &room_number); // get correct room (as GetWaterHeight doesn't)
	height = GetWaterHeight(pos->x, pos->y, pos->z, room_number);
	if (height == NO_HEIGHT)
	{
		floor = GetFloor(pos->x, pos->y, pos->z, &room_number);
		height = GetHeight(floor, pos->x, pos->y, pos->z);
		if (height == NO_HEIGHT)
			return height;
	}

	return height - 5; // make sure boat is above water line else all sorts of weirdness results
}

static void DoBoatShift(int boat_number)
{
	ITEM_INFO *item, *boat;
	int item_number, distance, x, z;

	boat = &items[boat_number];

	/* Check if hit something in the water */
	item_number = room[boat->room_number].item_number;
	while (item_number != NO_ITEM)
	{
		item = &items[item_number];

		if (item->object_number == BOAT && item_number != boat_number && lara.skidoo != item_number)
		{
			// other boat
			x = item->pos.x_pos - boat->pos.x_pos;
			z = item->pos.z_pos - boat->pos.z_pos;
			distance = x*x + z*z;
			if (distance < SQUARE(BOAT_RADIUS*2))
			{
				boat->pos.x_pos = item->pos.x_pos - x * SQUARE(BOAT_RADIUS*2) / distance;
				boat->pos.z_pos = item->pos.z_pos - z * SQUARE(BOAT_RADIUS*2) / distance;
			}
			return;
		}

		item_number = item->next_item;
	}
}


/****************************************************************************
*/
static int DoBoatDynamics(int height, int fallspeed, int *y)
{
	if (height > *y)
	{
		/* In air */
		*y += fallspeed;
		if (*y > height)
		{
			*y = height;
			fallspeed = 0;
		}
		else
			fallspeed += GRAVITY;
	}
	else
	{
		/* On ground: get up push from height change (if not a closed door and so NO_HEIGHT) */
		fallspeed += ((height - *y - fallspeed) >> 3);
		if (fallspeed < -20)
			fallspeed = -20;

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

	return fallspeed;
}

/****************************************************************************
*/
static int BoatDynamics(sint16 boat_number)
{
	ITEM_INFO *boat;
	BOAT_INFO *binfo;
	PHD_VECTOR moved, fl, fr, br, bl, f;
	PHD_VECTOR old, fl_old, fr_old, bl_old, br_old, f_old;
	sint32 hfl, hfr, hbr, hbl, hf;
	sint32 hfr_old, hfl_old, hbr_old, hbl_old, hf_old;
	FLOOR_INFO *floor;
	sint32 height, slip, collide;
	sint16 room_number, rot;
	int newspeed;

	boat = &items[boat_number];
	binfo = (BOAT_INFO *)boat->data;

	/* Remove tilt angle (and add again at the end) as effects control */
	boat->pos.z_rot -= binfo->tilt_angle;

	/* First get positions and heights of boat's corners + centre */
	hfl_old = TestWaterHeight(boat, BOAT_FRONT, -BOAT_SIDE, &fl_old);
	hfr_old = TestWaterHeight(boat, BOAT_FRONT, BOAT_SIDE, &fr_old);
	hbl_old = TestWaterHeight(boat, -BOAT_FRONT, -BOAT_SIDE, &bl_old);
	hbr_old = TestWaterHeight(boat, -BOAT_FRONT, BOAT_SIDE, &br_old);
	hf_old = TestWaterHeight(boat, BOAT_TIP, 0, &f_old);
	old.x = boat->pos.x_pos;
	old.y = boat->pos.y_pos;
	old.z = boat->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 (f_old.y > hf_old)
		f_old.y = hf_old;

	boat->pos.y_rot += binfo->boat_turn + binfo->extra_rotation;
	binfo->tilt_angle = binfo->boat_turn * 6;

	/* Move boat according to speed */
	boat->pos.z_pos += boat->speed * phd_cos(boat->pos.y_rot) >> W2V_SHIFT;
	boat->pos.x_pos += boat->speed * phd_sin(boat->pos.y_rot) >> W2V_SHIFT;
	if (boat->speed >= 0)
		binfo->prop_rot += (boat->speed * (ONE_DEGREE*3)) + (ONE_DEGREE<<1);
	else
		binfo->prop_rot += ONE_DEGREE*33;

	/* Slide boat according to tilts (to avoid getting stuck on slopes) */
	slip = BOAT_SIDE_SLIP * phd_sin(boat->pos.z_rot) >> W2V_SHIFT;
	if (!slip && boat->pos.z_rot)
		slip = (boat->pos.z_rot>0)? 1 : -1;
	boat->pos.z_pos -= slip * phd_sin(boat->pos.y_rot) >> W2V_SHIFT;
	boat->pos.x_pos += slip * phd_cos(boat->pos.y_rot) >> W2V_SHIFT;

	slip = BOAT_SLIP * phd_sin(boat->pos.x_rot) >> W2V_SHIFT;
	if (!slip && boat->pos.x_rot)
		slip = (boat->pos.x_rot>0)? 1 : -1;
	boat->pos.z_pos -= slip * phd_cos(boat->pos.y_rot) >> W2V_SHIFT;
	boat->pos.x_pos -= slip * phd_sin(boat->pos.y_rot) >> W2V_SHIFT;

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

	/* Collision with other boat? */
	DoBoatShift(boat_number);

	/* Test new positions of points (one at a time) and shift boat accordingly */
	rot = 0;
	hbl = TestWaterHeight(boat, -BOAT_FRONT, -BOAT_SIDE, &bl);
	if (hbl < bl_old.y - STEP_L/2)
		rot = DoShift(boat, &bl, &bl_old);

	hbr = TestWaterHeight(boat, -BOAT_FRONT, BOAT_SIDE, &br);
	if (hbr < br_old.y - STEP_L/2)
		rot += DoShift(boat, &br, &br_old);

	hfl = TestWaterHeight(boat, BOAT_FRONT, -BOAT_SIDE, &fl);
	if (hfl < fl_old.y - STEP_L/2)
		rot += DoShift(boat, &fl, &fl_old);

	hfr = TestWaterHeight(boat, BOAT_FRONT, BOAT_SIDE, &fr);
	if (hfr < fr_old.y - STEP_L/2)
		rot += DoShift(boat, &fr, &fr_old);

	if (!slip)
	{
		hf = TestWaterHeight(boat, BOAT_TIP, 0, &f);
		if (hf < f_old.y - STEP_L/2)
			DoShift(boat, &f, &f_old);
	}

	room_number = boat->room_number;
	floor = GetFloor(boat->pos.x_pos, boat->pos.y_pos, boat->pos.z_pos, &room_number);
	height = GetWaterHeight(boat->pos.x_pos, boat->pos.y_pos, boat->pos.z_pos, room_number);
	if (height == NO_HEIGHT)
		height = GetHeight(floor, boat->pos.x_pos, boat->pos.y_pos, boat->pos.z_pos);
	if (height < boat->pos.y_pos - STEP_L/2)
		DoShift(boat, (PHD_VECTOR *)&boat->pos, &old);

	binfo->extra_rotation = rot;

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

	/* Check final movement if slipped or collided and adjust speed */
	if (slip || collide)
	{
//##		newspeed = (boat->pos.z_pos - old.z) * phd_cos(boat->pos.y_rot) + (boat->pos.x_pos - old.x) * phd_sin(boat->pos.y_rot) >> W2V_SHIFT;
		newspeed = ((boat->pos.z_pos - old.z) * phd_cos(boat->pos.y_rot)
			    +  (boat->pos.x_pos - old.x) * phd_sin(boat->pos.y_rot))
			    >> W2V_SHIFT;

		if (lara.skidoo == boat_number && boat->speed > BOAT_MAX_SPEED+BOAT_ACCELERATION && newspeed < boat->speed-10)
		{
			lara_item->hit_points -= boat->speed;
			lara_item->hit_status = 1;
			SoundEffect(31, &lara_item->pos, 0);
			newspeed >>= 1;
			boat->speed >>= 1;
		}

		/* Adjust speed if serious change */
		if (slip)
		{
			/* Only if slip is above certain amount, and boat is not in FAST speed range */
			if (boat->speed <= BOAT_MAX_SPEED+10)
				boat->speed = newspeed;
		}
		else
		{
			if (boat->speed > 0 && newspeed < boat->speed)
				boat->speed = newspeed;
			else if (boat->speed < 0 && newspeed > boat->speed)
				boat->speed = newspeed;
		}

		if (boat->speed < BOAT_MAX_BACK)
			boat->speed = BOAT_MAX_BACK;
	}

	return collide;
}




/************************************ FUNCTION CODE ********************************************************/

void InitialiseBoat(sint16 item_number)
{
	ITEM_INFO *boat;
	BOAT_INFO *binfo;
	int	i;

	/* Allocate extra data to store boat specific information */
	boat = &items[item_number];
	binfo = (BOAT_INFO *)boat->data = game_malloc(sizeof(BOAT_INFO), 0);
	binfo->boat_turn = 0;
	binfo->left_fallspeed = binfo->right_fallspeed = 0;
	binfo->tilt_angle = 0;
	binfo->extra_rotation = 0;
	binfo->water = 0;
	binfo->pitch = 0;
#ifdef PSX_VERSION
	lara_inboat = 0;
#endif

	for ( i=0 ; i<NUM_WAKE_SPRITES ; i++ )
	{
		WakePts[i][0].life = 0;
		WakePts[i][1].life = 0;
	}

}


int BoatCheckGeton(sint16 item_number, COLL_INFO *coll)
{
	/* Returns 0 if no get on, 1 if right get on and 2 if left get on and 3 if jump geton */
	int geton, dist;
	sint16 rot;
	ITEM_INFO *boat;

	if (lara.gun_status!=LG_ARMLESS)
		return 0;

	boat = &items[item_number];

	dist = ((lara_item->pos.z_pos - boat->pos.z_pos) * phd_cos(-boat->pos.y_rot) -
			  (lara_item->pos.x_pos - boat->pos.x_pos) * phd_sin(-boat->pos.y_rot)) >> W2V_SHIFT;

	if (dist > 512)
		return 0;

	rot = boat->pos.y_rot - lara_item->pos.y_rot;

	/* Check if Lara is close enough and in right position to get onto boat */
	geton = 0;
	if (lara.water_status == LARA_SURFACE || lara.water_status == LARA_WADE)
	{
		if (!(input & IN_ACTION) || lara_item->gravity_status || boat->speed)
			return 0;

		if (rot > 0x2000 && rot < 0x6000)
			geton = 1; //right
		else if (rot > -0x6000 && rot < -0x2000)
			geton = 2; //left
	}
	else if (lara.water_status == LARA_ABOVEWATER)
	{
		if (lara_item->fallspeed > 0)
		{
//##			if (rot > -0x6000 && rot < 0x6000 && lara_item->pos.y_pos > boat->pos.y_pos)
			if (lara_item->pos.y_pos+512 > boat->pos.y_pos)
				geton = 3; //jump
		}
		else if (lara_item->fallspeed == 0)
		{
			if (rot > -0x6000 && rot < 0x6000)
			{
				if (lara_item->pos.x_pos == boat->pos.x_pos && lara_item->pos.y_pos == boat->pos.y_pos && lara_item->pos.z_pos == boat->pos.z_pos)
					geton = 4; // must have started on same spot as boat
				else
					geton = 3; //jump
			}
		}
	}

	if (!geton)
		return 0;

	/* Is Lara actually close enough to get on the thing? */
	if (!TestBoundsCollide(boat, lara_item, coll->radius))
		return 0;

	if (!TestCollision(boat, lara_item))
		return 0;

	return geton;
}


void BoatCollision(sint16 item_number, ITEM_INFO *litem, COLL_INFO *coll)
{
	/* This routine is only for when Lara is not on the boat and she would like to be */
	int geton;
	ITEM_INFO *item;

	/* If Lara dead or already on the boat, then no collision */
	if (litem->hit_points < 0 || lara.skidoo != NO_ITEM)
		return;

	/* If player isn't pressing control or Lara is busy, then do normal object collision */
	geton = BoatCheckGeton(item_number, coll);
	if (!geton)
	{
		coll->enable_baddie_push = 1;
		ObjectCollision(item_number, litem, coll);
		return;
	}

	/* Yeeha! Get in that boat girly */
	lara.skidoo = item_number;

	if (geton == 2)
		litem->anim_number = objects[VEHICLE_ANIM].anim_index + BOAT_GETONLW_ANIM;
	else if (geton == 1)
		litem->anim_number = objects[VEHICLE_ANIM].anim_index + BOAT_GETONRW_ANIM;
	else if (geton == 3)
		litem->anim_number = objects[VEHICLE_ANIM].anim_index + BOAT_GETONJ_ANIM;
	else
		litem->anim_number = objects[VEHICLE_ANIM].anim_index + BOAT_GETON_START;

	lara.water_status = LARA_ABOVEWATER;
	item = &items[item_number];
	litem->pos.x_pos = item->pos.x_pos;
	litem->pos.y_pos = item->pos.y_pos-5;
	litem->pos.z_pos = item->pos.z_pos;
	litem->pos.y_rot = item->pos.y_rot;
	litem->pos.x_rot = litem->pos.z_rot = 0;
	litem->gravity_status = 0;
	litem->speed = 0;
	litem->fallspeed = 0;
	litem->frame_number = anims[litem->anim_number].frame_base;
	litem->current_anim_state = litem->goal_anim_state = BOAT_GETON;

	// BUG FIX 25/10/97: Sony bug
	if (litem->room_number != item->room_number)
		ItemNewRoom(lara.item_number, item->room_number);

	AnimateItem(litem);

	/* Add to active item list from this point onward */
	if (items[item_number].status != ACTIVE)
	{
		AddActiveItem(item_number);
		items[item_number].status = ACTIVE;
	}

	S_CDPlay(12, 0);
}


// if we want Boat to kill people
void SkidooBaddieCollision(ITEM_INFO *skidoo);

int BoatUserControl(ITEM_INFO *boat)
{
	/* Return whether to straighten up or not */
	int no_turn=1, max_speed;
	BOAT_INFO *binfo;

	binfo = (BOAT_INFO *)boat->data;

	if (boat->pos.y_pos >= binfo->water - STEP_L/2 && binfo->water!=NO_HEIGHT)
	{
		/* If on the water surface, user has control; allow for reversing! */
		if ((!(input & IN_DISMOUNT) && !(input & IN_LOOK)) || boat->speed)
		{
			if ( ((input & IN_TURNL) && !(input & IN_REVERSE)) || ((input & IN_TURNR) && (input & IN_REVERSE)) )
			{
				if (binfo->boat_turn > 0)
					binfo->boat_turn -= BOAT_UNDO_TURN;
				else
				{
					binfo->boat_turn -= BOAT_TURN;
					if (binfo->boat_turn < -BOAT_MAX_TURN)
						binfo->boat_turn = -BOAT_MAX_TURN;
				}
				no_turn = 0;
			}
			else if ( ((input & IN_TURNR) && !(input & IN_REVERSE)) || ((input & IN_TURNL) && (input & IN_REVERSE)) )
			{
				if (binfo->boat_turn < 0)
					binfo->boat_turn += BOAT_UNDO_TURN;
				else
				{
					binfo->boat_turn += BOAT_TURN;
					if (binfo->boat_turn > BOAT_MAX_TURN)
						binfo->boat_turn = BOAT_MAX_TURN;
				}
				no_turn = 0;
			}

			if (input & IN_REVERSE)
			{
				if (boat->speed > 0)
					boat->speed -= BOAT_BRAKE;
				else if (boat->speed > BOAT_MAX_BACK)
					boat->speed += BOAT_REVERSE;
			}
			else if (input & IN_ACCELERATE)
			{
				if (input & IN_TURBO)
					max_speed = BOAT_FAST_SPEED;
				else
					max_speed = (input & IN_SLOW)? BOAT_SLOW_SPEED : BOAT_MAX_SPEED;

				if (boat->speed < max_speed)
					boat->speed += BOAT_ACCELERATION/2 + BOAT_ACCELERATION * boat->speed / (2*max_speed);
				else if (boat->speed > max_speed + BOAT_SLOWDOWN)
					boat->speed -= BOAT_SLOWDOWN;
			}
			else if (boat->speed>=0 && boat->speed<BOAT_MIN_SPEED && (input & (IN_TURNL|IN_TURNR)))
			{
				if (boat->speed == 0 && !(input & IN_DISMOUNT))
					boat->speed = BOAT_MIN_SPEED; // If user wants to turn, boat will move forward
			}
			else if (boat->speed > BOAT_SLOWDOWN)
				boat->speed -= BOAT_SLOWDOWN;
			else
				boat->speed = 0;
		}
		else
		{
			if (boat->speed>=0 && boat->speed<BOAT_MIN_SPEED && (input & (IN_TURNL|IN_TURNR)))
			{
				if (boat->speed == 0 && !(input & IN_DISMOUNT))
			 		boat->speed = BOAT_MIN_SPEED; // If user wants to turn, boat will move forward
			}
			else if (boat->speed > BOAT_SLOWDOWN)
				boat->speed -= BOAT_SLOWDOWN;
			else
				boat->speed = 0;

			if ((input & IN_LOOK) && boat->speed == 0)
				LookUpDown();
		}
	}

	return no_turn;
}


void BoatAnimation(ITEM_INFO *boat, int collide)
{
	BOAT_INFO *binfo;

	binfo = (BOAT_INFO *)boat->data;

	/* Do animation stuff */
	if (lara_item->hit_points <= 0)
	{
		if (lara_item->current_anim_state!=BOAT_DEATH)
		{
			lara_item->anim_number = objects[VEHICLE_ANIM].anim_index + BOAT_DEATH_ANIM;
			lara_item->frame_number = anims[lara_item->anim_number].frame_base;
			lara_item->current_anim_state = lara_item->goal_anim_state = BOAT_DEATH;
		}
	}
	else if (boat->pos.y_pos < binfo->water-STEP_L/2 && boat->fallspeed > 0)
	{
		if (lara_item->current_anim_state!=BOAT_FALL)
		{
			lara_item->anim_number = objects[VEHICLE_ANIM].anim_index + BOAT_FALL_ANIM;
			lara_item->frame_number = anims[lara_item->anim_number].frame_base;
			lara_item->current_anim_state = lara_item->goal_anim_state = BOAT_FALL;
		}
	}
	else if (collide)
	{
		if (lara_item->current_anim_state != BOAT_HIT)
		{
			lara_item->anim_number = (sint16)(objects[VEHICLE_ANIM].anim_index + collide);
			lara_item->frame_number = anims[lara_item->anim_number].frame_base;
			lara_item->current_anim_state = lara_item->goal_anim_state = BOAT_HIT;
		}
	}
	else
	{
		switch (lara_item->current_anim_state)
		{
		/* -------------------- */
			case BOAT_STILL:
		/* -------------------- */
				if (input & IN_DISMOUNT)
				{
					if (boat->speed == 0)
					{
						if ((input & IN_TURNR) && CanGetOff(boat->pos.y_rot+0x4000))
							lara_item->goal_anim_state = BOAT_JUMPR;
						else if (input & IN_TURNL && CanGetOff(boat->pos.y_rot-0x4000))
							lara_item->goal_anim_state = BOAT_JUMPL;
					}
				}

				if (boat->speed > 0)
					lara_item->goal_anim_state = BOAT_MOVING;

				break;
		/* -------------------- */
			case BOAT_MOVING:
		/* -------------------- */
				if (boat->speed <= 0)
					lara_item->goal_anim_state = BOAT_STILL;

				else if (input & IN_TURNR)
					lara_item->goal_anim_state = BOAT_TURNR;

				else if (input & IN_TURNL)
					lara_item->goal_anim_state = BOAT_TURNL;

				break;
		/* -------------------- */
			case BOAT_FALL:
		/* -------------------- */
				lara_item->goal_anim_state = BOAT_MOVING;

				break;
		/* -------------------- */
			case BOAT_TURNR:
		/* -------------------- */
				if (boat->speed <= 0)
					lara_item->goal_anim_state = BOAT_STILL;

				else if (!(input & IN_TURNR))
					lara_item->goal_anim_state = BOAT_MOVING;

				break;
		/* -------------------- */
			case BOAT_TURNL:
		/* -------------------- */
				if (boat->speed <= 0)
					lara_item->goal_anim_state = BOAT_STILL;

				else if (!(input & IN_TURNL))
					lara_item->goal_anim_state = BOAT_MOVING;

				break;
		}
	}
}


void BoatControl(sint16 item_number)
{
	ITEM_INFO *boat;
	BOAT_INFO *binfo;
	PHD_VECTOR fl, fr, prop;
	sint32 hfl, hfr, no_turn=1, drive=0;
	FLOOR_INFO *floor;
	sint32 height, collide, water, ceiling, pitch, h, ofs, nowake;
	sint16 room_number, x_rot, z_rot;

	boat = &items[item_number];
#ifdef PSX_VERSION
	boatitem = boat;	//Part of major FRIG to fix lonely boat exit
#endif
	binfo = (BOAT_INFO *)boat->data;

	collide = BoatDynamics(item_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 = TestWaterHeight(boat, BOAT_FRONT, -BOAT_SIDE, &fl);
	hfr = TestWaterHeight(boat, BOAT_FRONT, BOAT_SIDE, &fr);

	room_number = boat->room_number;
	floor = GetFloor(boat->pos.x_pos, boat->pos.y_pos, boat->pos.z_pos, &room_number);
	height = GetHeight(floor, boat->pos.x_pos, boat->pos.y_pos, boat->pos.z_pos);
	ceiling = GetCeiling(floor, boat->pos.x_pos, boat->pos.y_pos, boat->pos.z_pos);
	if (lara.skidoo == item_number)
	{
		TestTriggers(trigger_index, 0);
		TestTriggers(trigger_index, 1); // HEAVY too
	}

	binfo->water = water = GetWaterHeight(boat->pos.x_pos, boat->pos.y_pos, boat->pos.z_pos, room_number);

	/* Deal with user input; will effect next frame, but relies on info calced this frame */
	if (lara.skidoo == item_number && lara_item->hit_points > 0)
	{
		switch (lara_item->current_anim_state)
		{
		case BOAT_GETON:
		case BOAT_JUMPR:
		case BOAT_JUMPL:
			break;

		default:
			drive = 1;
			no_turn = BoatUserControl(boat);
		}
	}
	else
	{
		/* Boat has no driver */
		if (boat->speed > BOAT_SLOWDOWN)
			boat->speed -= BOAT_SLOWDOWN;
		else
			boat->speed = 0;
	}

	if (no_turn)
	{
		/* Straighten up if not under control */
		if (binfo->boat_turn < -BOAT_UNDO_TURN)
			binfo->boat_turn += BOAT_UNDO_TURN;
		else if (binfo->boat_turn > BOAT_UNDO_TURN)
			binfo->boat_turn -= BOAT_UNDO_TURN;
		else
			binfo->boat_turn = 0;
	}


	/* Get floor height (keep slightly above water surface) */
	boat->floor = height - 5;
	if (binfo->water == NO_HEIGHT)
		binfo->water = height;
	else
		binfo->water -= 5;

	/* Do fallspeed effects on boat */
	binfo->left_fallspeed = DoBoatDynamics(hfl, binfo->left_fallspeed, (int *)&fl.y);
	binfo->right_fallspeed = DoBoatDynamics(hfr, binfo->right_fallspeed, (int *)&fr.y);
	ofs = boat->fallspeed;
	boat->fallspeed = DoBoatDynamics(binfo->water, boat->fallspeed, (int *)&boat->pos.y_pos);
	if (ofs-boat->fallspeed > 32 && boat->fallspeed == 0 && water != NO_HEIGHT)
		BoatSplash(boat,ofs-boat->fallspeed,water);

	/* Rotate boat to match these heights */
	height = (fl.y + fr.y);
	if (height < 0)
		height = -(abs(height)>>1);
	else
		height = height>>1;

	x_rot = phd_atan(BOAT_FRONT, boat->pos.y_pos - height);
	z_rot = phd_atan(BOAT_SIDE, height - fl.y);

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

	/* Auto level the boat on flat water (to stop evil shifts) */
	if (!x_rot && ABS(boat->pos.x_rot) < 4)
		boat->pos.x_rot = 0;
	if (!z_rot && ABS(boat->pos.z_rot) < 4)
		boat->pos.z_rot = 0;

	/* If Lara is on the boat, do all her animation and move her to same position as boat, etc */
	if (lara.skidoo == item_number)
	{
		BoatAnimation(boat, collide);

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

		/* Move Lara to the boat position */
		boat->pos.z_rot += binfo->tilt_angle;
		lara_item->pos.x_pos = boat->pos.x_pos;
		lara_item->pos.y_pos = boat->pos.y_pos;
		lara_item->pos.z_pos = boat->pos.z_pos;
		lara_item->pos.x_rot = boat->pos.x_rot;
		lara_item->pos.y_rot = boat->pos.y_rot;
		lara_item->pos.z_rot = boat->pos.z_rot;

		AnimateItem(lara_item);

		/* Set boat on the exact same anim frame */
		if (lara_item->hit_points > 0)
		{
			boat->anim_number = objects[BOAT].anim_index + (lara_item->anim_number - objects[VEHICLE_ANIM].anim_index);
			boat->frame_number = anims[boat->anim_number].frame_base + (lara_item->frame_number - anims[lara_item->anim_number].frame_base);
		}

		/* Set camera */
		camera.target_elevation = -20*ONE_DEGREE;
		camera.target_distance = WALL_L*2;

#ifdef PSX_VERSION
		lara_inboat = 1;
#endif
	}
	else
	{
		/* If Lara isn't in the boat, then just move the boat */
		if (room_number != boat->room_number)
			ItemNewRoom(item_number, room_number);
		boat->pos.z_rot += binfo->tilt_angle;
#ifdef PSX_VERSION
		lara_inboat = 0;
#endif
	}


	/* Do sound effect */
//	ceiling = water - ceiling;
//	if (ceiling < BOAT_SOUND_CEILING)
//		pitch = boat->speed * ceiling / BOAT_SOUND_CEILING;
//	else

	pitch = boat->speed;
	binfo->pitch += (pitch - binfo->pitch) >> 2;

//	if (boat->speed && water-5 != boat->pos.y_pos)
//		SoundEffect(336, &boat->pos, 0);
	if (boat->speed > 8)
		SoundEffect(197, &boat->pos, PITCH_SHIFT + ((0x10000 - (BOAT_MAX_SPEED - binfo->pitch)*100) <<8));
	else if (drive)
		SoundEffect(195, &boat->pos, PITCH_SHIFT + ((0x10000 - (BOAT_MAX_SPEED - binfo->pitch)*100) <<8));

//	/* If boat is moving, then do wake */
//	if (boat->speed && water-5 == boat->pos.y_pos)
//		DoWakeEffect(boat);

	if (lara.skidoo != item_number)
		return;

	/* Wait for last frame of getoff anims before returning to normal Lara control */
	if ((lara_item->current_anim_state == BOAT_JUMPR || lara_item->current_anim_state == BOAT_JUMPL) &&
		  lara_item->frame_number == anims[lara_item->anim_number].frame_end)
	{
		sint16 room_number;
		int x, y, z;
		FLOOR_INFO *floor;

		if (lara_item->current_anim_state == BOAT_JUMPL)
			lara_item->pos.y_rot -= 0x4000;
		else
			lara_item->pos.y_rot += 0x4000;

		lara_item->anim_number = 77;
		lara_item->frame_number = anims[lara_item->anim_number].frame_base;
		lara_item->current_anim_state = lara_item->goal_anim_state = AS_FORWARDJUMP;
		lara_item->gravity_status = 1;
		lara_item->fallspeed = -40;
		lara_item->speed = 20;
		lara_item->pos.x_rot = lara_item->pos.z_rot = 0;
		lara.skidoo = NO_ITEM;
//		lara.gun_status = LG_ARMLESS; // SONY BUG FIX A18 - This line broke Lara's flare anims

		room_number = lara_item->room_number;
		x = lara_item->pos.x_pos + (360 * phd_sin(lara_item->pos.y_rot) >> W2V_SHIFT);
		y = lara_item->pos.y_pos - 90;
		z = lara_item->pos.z_pos + (360 * phd_cos(lara_item->pos.y_rot) >> W2V_SHIFT);
		floor = GetFloor(x, y, z, &room_number);
		if (GetHeight(floor, x, y, z) >= y-STEP_L)
		{
			lara_item->pos.x_pos = x;
			lara_item->pos.z_pos = z;
			if (room_number != lara_item->room_number)
				ItemNewRoom(lara.item_number, room_number);
		}
		lara_item->pos.y_pos = y;

		/* Set boat to still anim */
		boat->anim_number = objects[BOAT].anim_index + 0;
		boat->frame_number = anims[boat->anim_number].frame_base;
	}

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

	h = GetWaterHeight(boat->pos.x_pos, boat->pos.y_pos+128, boat->pos.z_pos, room_number);

	if (h > boat->pos.y_pos+32 || h == NO_HEIGHT)
		h = 0;
	else
		h = 1;

	if (lara_item->current_anim_state == BOAT_JUMPR || lara_item->current_anim_state == BOAT_JUMPL)
		nowake = 1;
	else
		nowake = 0;

	if (!(wibble & 15) && h && !nowake)
	{
		DoWake(boat, -256-128, 0, 0);
 		DoWake(boat,  256+128, 0, 1);
	}

	{
		if (boat->speed == 0 || !h || nowake)
		{
			if (WakeShade)
				WakeShade--;
		}
		else
		{
			if (WakeShade < 16)
				WakeShade++;
		}
	}

	prop.x = prop.y = 0;
	prop.z = -80;
	GetJointAbsPosition(boat,&prop,2);	// Get position of propeller.
	room_number = boat->room_number;
	floor = GetFloor(prop.x, prop.y, prop.z, &room_number);
	h = GetWaterHeight(prop.x, prop.y, prop.z, room_number);

	if (boat->speed && h < prop.y && h != NO_HEIGHT)
	{
		TriggerBoatMist(prop.x,prop.y,prop.z,abs(boat->speed),boat->pos.y_rot+0x8000,0);
		if ((GetRandomControl()&1)==0)
		{
			PHD_3DPOS pos;
			sint16 	room_number;

			pos.x_pos = prop.x+(GetRandomControl()&63)-32;
			pos.y_pos = prop.y+(GetRandomControl()&15);
			pos.z_pos = prop.z+(GetRandomControl()&63)-32;
			room_number = boat->room_number;
			GetFloor(pos.x_pos,pos.y_pos,pos.z_pos,&room_number);
			CreateBubble((PHD_3DPOS *)&pos, room_number, 16, 8);
		}
	}
	else
	{
		GAME_VECTOR pos;
		long		cnt;

		h = GetHeight(floor, prop.x, prop.y, prop.z);
		if (prop.y > h && !(room[room_number].flags & UNDERWATER))
		{
			pos.x = prop.x;
			pos.y = prop.y;
			pos.z = prop.z;

			cnt = (GetRandomControl()&3)+3;
			for (;cnt>0;cnt--)
				TriggerBoatMist(prop.x,prop.y,prop.z,((GetRandomControl()&15)+96)<<4,boat->pos.y_rot+0x4000+GetRandomControl(),1);
		}
	}

	UpdateWakeFX();
}

void DrawBoat(ITEM_INFO *item)
{
	BOAT_INFO	*b;

	b = (BOAT_INFO	*)item->data;
	item->data = &b->prop_rot;
	DrawAnimatingItem(item);
	item->data = (void *) b;

	S_DrawWakeFX(item);
}


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

	sptr = &spark[GetFreeSpark()];

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

	if (snow)
	{
		sptr->dR = 255;
		sptr->dG = 255;
		sptr->dB = 255;
	}
	else
	{
		sptr->dR = 64;
		sptr->dG = 64;
		sptr->dB = 64;
	}



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

	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()&127)-64);
	sptr->Yvel = (speed<<3)+(speed<<2);	// *12
	sptr->Zvel = zv + ((GetRandomControl()&127)-64);
	sptr->Friction = 3;

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

	sptr->Def = objects[EXPLOSION1].mesh_index;
	if (!snow)
	{
		sptr->Scalar = 4;
		sptr->Gravity = sptr->MaxYvel = 0;
		size = (GetRandomControl()&7) + (speed>>1) + 16;
		sptr->Width = sptr->sWidth = size>>2;
 		sptr->Height = sptr->sHeight = size>>2;
	}
	else
	{
		sptr->Friction = 0;
		sptr->Scalar = 3;
		sptr->Yvel = -sptr->Yvel>>5;
		sptr->Gravity = (GetRandomControl()&31)+32;
		sptr->MaxYvel = 0;
		size = (GetRandomControl()&7) + 16;
		sptr->Width = sptr->sWidth = size;
 		sptr->Height = sptr->sHeight = size;
	}
	sptr->dWidth = size;
	sptr->dHeight = size;
}

static void BoatSplash(ITEM_INFO *item, long fallspeed, long water)
{
	splash_setup.x = item->pos.x_pos;
	splash_setup.y = water;
	splash_setup.z = item->pos.z_pos;
	splash_setup.InnerXZoff = 16<<2;
	splash_setup.InnerXZsize = 12<<2;
	splash_setup.InnerYsize = -96<<2;
	splash_setup.InnerXZvel = 0xa0;
	splash_setup.InnerYvel = -fallspeed<<7;
	splash_setup.InnerGravity = 0x80;
	splash_setup.InnerFriction = 7;
	splash_setup.MiddleXZoff = 24<<2;
	splash_setup.MiddleXZsize = 24<<2;
	splash_setup.MiddleYsize = -64<<2;
	splash_setup.MiddleXZvel = 0xe0;
	splash_setup.MiddleYvel = -fallspeed<<6;
	splash_setup.MiddleGravity = 0x48;
	splash_setup.MiddleFriction = 8;
	splash_setup.OuterXZoff = 32<<2;
	splash_setup.OuterXZsize = 32<<2;
	splash_setup.OuterXZvel = 0x110;
	splash_setup.OuterFriction = 9;
	SetupSplash(&splash_setup);
	SplashCount = 16;
}
