/****************************************************************************
*
* KAYAK.C
*
* PROGRAMMER : Chris
*    VERSION : 00.00
*    CREATED : 17/08/98
*   MODIFIED : 17/08/98
*	   TABS : 5
*
* DESCRIPTION
*	Kayak controller
*
*****************************************************************************/

/*---------------------------------------------------------------------------
 * 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\maths.h"
#include "..\spec_psx\malloc.h"
extern sint16 rcossin_tbl[];
#else
#include "../specific/standard.h"
#include "../specific/stypes.h"
#include "../specific/input.h"
#include "../specific/global.h"
extern short rcossin_tbl[];
#endif

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

#define KAYAK_INFO_ONLY
#include "kayak.h"

static void KayakSplash(ITEM_INFO *item, long fallspeed, long water);

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

//#define TEST_COLLISION
//#define DEBUG_KAYAK

#define KAYAK_COLLIDE			64
#define GETOFF_DIST 			768		// minimum collision distance
#define KAYAK_TO_BADDIE_RADIUS	256

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

#define MAX_SPEED		0x380000

#ifdef DEBUG_KAYAK

#define KAYAK_FRICTION	KayakFric
#define KAYAK_ROT_FRIC	KayakRotFric
#define KAYAK_DFLECT_ROT	KayakRotDeflect

#define KAYAK_FWD_VEL	KayakForce
#define KAYAK_FWD_ROT	KayakRotFwd

#define KAYAK_LR_VEL	KayakForceLR
#define KAYAK_LR_ROT	KayakRotLR
#define KAYAK_MAX_LR	KayakMaxRotLR

#define KAYAK_TURN_ROT	KayakTurnRot
#define KAYAK_MAX_TURN	KayakMaxTurnRot
#define KAYAK_TURN_BRAKE	KayakTurnBrake

#define KAYAK_HARD_ROT	KayakStatRot
#define KAYAK_MAX_STAT	KayakMaxStatRot

#else

#define KAYAK_FRICTION	0x8000
#define KAYAK_ROT_FRIC	0x50000
#define KAYAK_DFLECT_ROT	0x80000

#define KAYAK_FWD_VEL	0x180000
#define KAYAK_FWD_ROT	0x800000

#define KAYAK_LR_VEL	0x100000
#define KAYAK_LR_ROT	0xc00000
#define KAYAK_MAX_LR	0xc00000

#define KAYAK_TURN_ROT	0x200000
#define KAYAK_MAX_TURN	0x1000000
#define KAYAK_TURN_BRAKE	0x8000

#define KAYAK_HARD_ROT	0x1000000
#define KAYAK_MAX_STAT	0x1000000

#endif

#define BOAT_SLIP		50
#define BOAT_SIDE_SLIP	50

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

enum {
	KS_BACK,
	KS_POSE,
	KS_LEFT,
	KS_RIGHT,
	KS_CLIMBIN,
	KS_DEATHIN,
	KS_FORWARD,
	KS_ROLL,
	KS_DROWNIN,
	KS_JUMPOUT,
	KS_TURNL,
	KS_TURNR,
	KS_CLIMBINR,
	KS_CLIMBOUTL,
	KS_CLIMBOUTR,
};

#define HIT_BACK	1
#define HIT_FRONT	2
#define HIT_LEFT	3
#define HIT_RIGHT	4

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

#define KAYAK_BACK_A		2
#define KAYAK_CLIMBIN_A		3
#define KAYAK_CLIMBIN_F		GF(KAYAK_CLIMBIN_A, 0)
#define KAYAK_CLIMBIN2_A		4
#define KAYAK_DEATHIN_A		5
#define KAYAK_FORWARD_A		8
#define KAYAK_2FORWARD_A		9
#define KAYAK_JUMPOUT1_A		14
#define KAYAK_POSE_A		16
#define KAYAK_POSE_F		GF(KAYAK_POSE_A, 0)
#define KAYAK_JUMPOUT2_A		24
#define KAYAK_DROWN_A		25
#define KAYAK_TURNL_A		26
#define KAYAK_TURNR_A		27
#define KAYAK_CLIMBINR_A		28
#define KAYAK_CLIMBINR_F		GF(KAYAK_CLIMBINR_A, 0)
#define KAYAK_JUMPOUTR_A		32

#define KAYAK_DRAW_SHIFT		32

#define LARA_LEG_BITS		((1<<HIPS)|(1<<THIGH_L)|(1<<CALF_L)|(1<<FOOT_L)|(1<<THIGH_R)|(1<<CALF_R)|(1<<FOOT_R))

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

#define KAYAK_X	128
#define KAYAK_Z	128

#define SKIDOO_MAX_KICK	 -80
#define SKIDOO_MIN_BOUNCE ((MAX_SPEED/2)>>8)

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

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

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

extern int InfoFlag;
extern char exit_message[];
extern long	CameraCollisionBounds(GAME_VECTOR *ideal, long push, long yfirst);

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

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

void KayakCollision(sint16 item_number, ITEM_INFO *litem, COLL_INFO *coll);
int KayakControl();
void KayakDraw(ITEM_INFO *v);
void KayakInitialise(sint16 item_number);
void LaraRapidsDrown();

void *func[] __attribute__((section(".header"))) = {
	&KayakCollision,
	&KayakControl,
	&KayakDraw,
	&KayakInitialise,
	&LaraRapidsDrown
};

#endif


#ifdef DEBUG_KAYAK

sint32 KayakFric  		= 0x8000;
sint32 KayakRotFric		= 0x50000;

sint32 KayakForce		= 0x180000;
sint32 KayakRotFwd		= 0x800000;
sint32 KayakRotDeflect 	= 0x80000;

sint32 KayakForceLR 	= 0x100000;

sint32 KayakRotLR 		= 0xc00000;
sint32 KayakMaxRotLR	= 0xc00000;

sint32 KayakTurnRot		= 0x200000;
sint32 KayakMaxTurnRot	= 0x1000000;
sint32 KayakTurnBrake	= 0x8000;

sint32 KayakStatRot		= 0x1000000;
sint32 KayakMaxStatRot	= 0x1000000;

#endif

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

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

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

/****************************************************************************
* 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] =	{	{	-128,0	},
								{	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;
	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, z, &room_number);

	if (GetWaterHeight(x, v->pos.y_pos, z, room_number) != NO_HEIGHT)
	{
		if (v->speed < 0)
		{
			if (!rotate)
			{
				angle1 = v->pos.y_rot - (ONE_DEGREE*10);
				angle2 = v->pos.y_rot - (ONE_DEGREE*30);
			}
			else
			{
				angle1 = v->pos.y_rot + (ONE_DEGREE*10);
				angle2 = v->pos.y_rot + (ONE_DEGREE*30);
			}
		}
		else
		{
			if (!rotate)
			{
				angle1 = v->pos.y_rot - (ONE_DEGREE*170);
				angle2 = v->pos.y_rot - (ONE_DEGREE*150);
			}
			else
			{
				angle1 = v->pos.y_rot + (ONE_DEGREE*170);
				angle2 = v->pos.y_rot + (ONE_DEGREE*150);
			}
		}

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

		WakePts[CurrentStartWake][rotate].y = v->pos.y_pos + KAYAK_DRAW_SHIFT;
		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);
		}
	}
}

/****************************************************************************
* DoRipple
*
* SYNOPSIS
*	void DoRipple(ITEM_INFO *v, sint16 xoff, sint16 zoff)
*
* PURPOSE
*	Initialises a ripple effect
*
* INPUTS
*	v 			= ptr to vehicle's item
*	xoff			= ripple x offset
*	zoff			= ripple z offset
*
* RETURNS
*	Nothing
*/
static void DoRipple(ITEM_INFO *v, sint16 xoff, sint16 zoff)
{
	RIPPLE_STRUCT *r;
	sint32 s, c, x, z;
	FLOOR_INFO *floor;
	sint16 room_number;

	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, z, &room_number);

	if (GetWaterHeight(x, v->pos.y_pos, z, room_number) != NO_HEIGHT)
	{
		r = SetupRipple(x, v->pos.y_pos, z, -2-(GetRandomControl()&1), 0);
		r->init = 0;
	}
}
/****************************************************************************
* 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] ;
			}
		}
	}
}

static void KayakSplash(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<<3;
	splash_setup.InnerXZsize = 12<<2;
	splash_setup.InnerYsize = -96<<2;
	splash_setup.InnerXZvel = 0xa0;
	splash_setup.InnerYvel = -fallspeed<<5;
	splash_setup.InnerGravity = 0x80;
	splash_setup.InnerFriction = 7;
	splash_setup.MiddleXZoff = 24<<3;
	splash_setup.MiddleXZsize = 24<<2;
	splash_setup.MiddleYsize = -64<<2;
	splash_setup.MiddleXZvel = 0xe0;
	splash_setup.MiddleYvel = -fallspeed<<4;
	splash_setup.MiddleGravity = 0x48;
	splash_setup.MiddleFriction = 8;
	splash_setup.OuterXZoff = 32<<3;
	splash_setup.OuterXZsize = 32<<2;
	splash_setup.OuterXZvel = 0x110;
	splash_setup.OuterFriction = -9;
	SetupSplash(&splash_setup);
	SplashCount = 16;
}

static void TriggerRapidsMist(long x, long y, long z)
{
	SPARKS	*sptr;
	long		xsize;

	sptr = &spark[GetFreeSpark()];

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

	sptr->dR = 192;
	sptr->dG = 192;
	sptr->dB = 192;

	sptr->ColFadeSpeed = 2;
	sptr->FadeToBlack = 4;	// 8
	sptr->sLife = sptr->Life = 6+(GetRandomControl()&3);

	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);
	sptr->Xvel = ((GetRandomControl()&255)-128);
	sptr->Zvel = ((GetRandomControl()&255)-128);
	sptr->Yvel = ((GetRandomControl()&255)-128);
	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;
	sptr->Scalar = 4;
	sptr->Gravity = 0;
	sptr->MaxYvel = 0;
	xsize = (GetRandomControl()&7)+16;
	sptr->Width = sptr->sWidth = xsize>>1;
	sptr->dWidth = xsize;
 	sptr->Height = sptr->sHeight = xsize>>1;
	sptr->dHeight = xsize;
}

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

	l = lara_item;

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

	v = &items[item_number];

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

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

		tempang = l->pos.y_rot-v->pos.y_rot;

		if ((ang > -(ONE_DEGREE*45)) && (ang < (ONE_DEGREE*135)))
		{
			if (tempang > (45*ONE_DEGREE) && tempang < (135*ONE_DEGREE))
				return -1;
		}
		else
		{
			if (tempang > (225*ONE_DEGREE) && tempang < (315*ONE_DEGREE))
				return 1;
		}
	}

	return 0;
}

/****************************************************************************
* GetCollisionAnim
*
* SYNOPSIS
*	sint32 GetCollisionAnim(ITEM_INFO *v, int xdiff, int zdiff)
*
* PURPOSE
*	Determines the required collision animation
*
* INPUTS
*	v		= ptr to vehicle item
* 	xdiff	= old x position
*	zdiff	= old z position
*
* RETURNS
*	Animation #
*/
static sint32 GetCollisionAnim(ITEM_INFO *v, int xdiff, int zdiff)
{
	xdiff = v->pos.x_pos - xdiff;
	zdiff = v->pos.z_pos - zdiff;

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

	if ((xdiff) || (zdiff))
	{
		sint32 c, s, front, side;

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

		front = ((zdiff * c) + (xdiff * s)) >> W2V_SHIFT;
		side = ((-zdiff * s) + (xdiff * c)) >> W2V_SHIFT;

		if (ABS(front) > ABS(side))
		{
			if (front > 0)
				return HIT_BACK;
			else
				return HIT_FRONT;
		}
		else
		{
			if (side > 0)
				return HIT_LEFT;
			else
				return HIT_RIGHT;
		}
	}

	return 0;
}
/****************************************************************************
* DoDynamics
*
* SYNOPSIS
*	sint32 TestHeight(ITEM_INFO *item, sint32 x, sint32 z, PHD_VECTOR *pos)
*
* PURPOSE
*	Returns the height at an offset relative to the origin.
*
* INPUTS
*	item		= ptr to vehicle item
* 	x		= x offset
*	z		= z offset
*	pos		= ptr to destination vector
*
* RETURNS
*	Height at required co-ordinate
*/
static int DoDynamics(sint32 height, int fallspeed, sint32 *y)
{
	if (height > *y)
	{
		/* In air */
		*y += fallspeed;

		if (*y > height)
//		if (*y > height - SKIDOO_MIN_BOUNCE)
		{
			*y = height;
			fallspeed = 0;
		}
		else
			fallspeed += GRAVITY;
	}
	else
	{
#if 1
		int kick;

		/* On ground: get up push from height change (if not a closed door and so NO_HEIGHT) */
		kick = (height - *y) << 2;

		if (kick < SKIDOO_MAX_KICK)
			kick = SKIDOO_MAX_KICK;

		fallspeed += ((kick - fallspeed) >> 3);
#else
		fallspeed += ((height - *y - fallspeed) >> 3);
		if (fallspeed < -20)
			fallspeed = -20;
#endif
		if (*y > height)
			*y = height;
	}

	return fallspeed;
}
/****************************************************************************
* DoCurrent
*
* SYNOPSIS
*	void DoCurrent(ITEM_INFO *v)
*
* PURPOSE
*	Pulls the Kayak towards sinks
*
* INPUTS
*	v	= ptr to Kayak's item structure
*
* RETURNS
*	Nothing
*/
static void DoCurrent(ITEM_INFO *item)
{
	ROOM_INFO *r;
	PHD_VECTOR target;

	r = &room[item->room_number];

	if (!lara.current_active)
	{
		long	shifter,absvel;

		absvel = abs(lara.current_xvel);

		if (absvel > 16)
			shifter = 4;
		else if (absvel > 8)
			shifter = 3;
		else
			shifter = 2;

		lara.current_xvel -= lara.current_xvel >> shifter;

		if (abs(lara.current_xvel) < 4)
			lara.current_xvel = 0;

		absvel = abs(lara.current_zvel);
		if (absvel > 16)
			shifter = 4;
		else if (absvel > 8)
			shifter = 3;
		else
			shifter = 2;

		lara.current_zvel -= lara.current_zvel >> shifter;
		if (abs(lara.current_zvel) < 4)
			lara.current_zvel = 0;

		if (	lara.current_xvel == 0 &&
			lara.current_zvel == 0)
			return;
	}
	else
	{
		long	angle,dx,dz,speed,sinkval;

		sinkval = lara.current_active-1;
		target.x = camera.fixed[sinkval].x;
		target.y = camera.fixed[sinkval].y;
		target.z = camera.fixed[sinkval].z;
		angle = ((mGetAngle(target.x,target.z,lara_item->pos.x_pos,lara_item->pos.z_pos)-0x4000)>>4)&4095;

		dx = target.x - lara_item->pos.x_pos;
		dz = target.z - lara_item->pos.z_pos;

		speed = camera.fixed[sinkval].data;
		dx = (((rcossin_tbl[(angle<<1)] * speed))) >> 2; // / distance)<<4;
		dz = (((rcossin_tbl[(angle<<1)+1] * speed))) >> 2;// / distance)<<4;

		lara.current_xvel += (dx - lara.current_xvel)>>4;
		lara.current_zvel += (dz - lara.current_zvel)>>4;
	}

	/* Move Lara in direction of sink. */

	item->pos.x_pos += lara.current_xvel>>8;
	item->pos.z_pos += lara.current_zvel>>8;

	/* Reset current (will get set again so long as Lara is over triggers) */
	lara.current_active = 0;
}
/****************************************************************************
* TestHeight
*
* SYNOPSIS
*	sint32 TestHeight(ITEM_INFO *item, sint32 x, sint32 z, PHD_VECTOR *pos)
*
* PURPOSE
*	Returns the height at an offset relative to the origin.
*
* INPUTS
*	item		= ptr to vehicle item
* 	x		= x offset
*	z		= z offset
*	pos		= ptr to destination vector
*
* RETURNS
*	Height at required co-ordinate
*/
static sint32 TestHeight(ITEM_INFO *item, sint32 x, sint32 z, PHD_VECTOR *pos)
{
	sint32 h;
	FLOOR_INFO *floor;
	sint16 room_number;

	phd_PushUnitMatrix();

#ifdef PSX_VERSION
	mSetTrans(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);
	phd_TranslateRel(x, 0, z);
	pos->x = Matrix->tx;
	pos->y = Matrix->ty;
	pos->z = Matrix->tz;
	phd_PopMatrix();
#else
	*(phd_mxptr+M03) = item->pos.x_pos;
	*(phd_mxptr+M13) = item->pos.y_pos;
	*(phd_mxptr+M23) = item->pos.z_pos;
	phd_RotYXZ(item->pos.y_rot, item->pos.x_rot, item->pos.z_rot);
	phd_TranslateRel(x, 0, z);
	pos->x = (*(phd_mxptr+M03)>>W2V_SHIFT);
	pos->y = (*(phd_mxptr+M13)>>W2V_SHIFT);
	pos->z = (*(phd_mxptr+M23)>>W2V_SHIFT);
#endif

	room_number = item->room_number;
	GetFloor(pos->x, pos->y, pos->z, &room_number);

	if ((h = GetWaterHeight(pos->x, pos->y, pos->z, room_number)) == NO_HEIGHT)
	{
		room_number = item->room_number;
		floor = GetFloor(pos->x, pos->y, pos->z, &room_number);
		if ((h = GetHeight(floor, pos->x, pos->y, pos->z)) == NO_HEIGHT)
			return h;
	}

	return h-5;
}

static int CanGetOut(ITEM_INFO *v, int direction)
{
	sint32 height;
	PHD_VECTOR pos;

	height = TestHeight(v, (direction < 0) ? -GETOFF_DIST : GETOFF_DIST, 0, &pos);

	if ((v->pos.y_pos - height) > 0)
		return 0;

	return 1;
}

/****************************************************************************
* DoShift
*
* SYNOPSIS
*	void DoShift(ITEM_INFO *v, PHD_VECTOR *pos, PHD_VECTOR *old)
*
* PURPOSE
*	Determines a new position for an incorrect position
*
* INPUTS
*	v	= ptr to vehicle's item structure
*	pos	= ptr to current (bad) position
*	old	= ptr to previous (ok) position
*
* RETURNS
*	Nothing
*/
static int DoShift(ITEM_INFO *v, 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)
	{
		old->x = 0;

		if (z == z_old)
		{
			v->pos.z_pos += (old->z - pos->z);
			v->pos.x_pos += (old->x - pos->x);
		}

		else if (z > z_old)
		{
			v->pos.z_pos -= shift_z+1;
			return (pos->x - v->pos.x_pos);
		}

		else
		{
			v->pos.z_pos += WALL_L-shift_z;
			return (v->pos.x_pos - pos->x);
		}
	}

	else if (z == z_old)
	{
		old->z = 0;

		if (x > x_old)
		{
			v->pos.x_pos -= shift_x+1;
			return (v->pos.z_pos - pos->z);
		}

		else
		{
			v->pos.x_pos += WALL_L-shift_x;
			return (pos->z - v->pos.z_pos);
		}
	}

	else
	{
/* A diagonal hit; means a barrage of tests needed to determine best shift */
		sint32 height;
		FLOOR_INFO *floor;
		sint16 room_number;

		x = z = 0;

		room_number = v->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 = v->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;
		}

	/* -------- corner collision */

		if (x && z)
		{
			v->pos.z_pos += z;
			v->pos.x_pos += x;
		}

	/* -------- edge collisions */

		else if (z)
		{
			v->pos.z_pos += z;

			if (z > 0)
				return (v->pos.x_pos - pos->x);
			else
				return (pos->x - v->pos.x_pos);
		}

		else if (x)
		{
			v->pos.x_pos += x;

			if (x > 0)
				return (pos->z - v->pos.z_pos);
			else
				return (v->pos.z_pos - pos->z);
		}

	/* -------- diagnal collision */

		else
		{
			v->pos.z_pos += (old->z - pos->z);
			v->pos.x_pos += (old->x - pos->x);
		}
	}
	return 0;
}
/****************************************************************************
* KayakToBackground
*
* SYNOPSIS
*	int KayakToBackground(ITEM_INFO *v, KAYAKINFO *Kayak)
*
* PURPOSE
*	Handles the Kayak colliding with the background
*
* INPUTS
*	v		= ptr to Kayak's item structure
*	Kayak	= ptr to Kayak's specific structure
*
* RETURNS
*	Nothing
*/
static void KayakToBackground(ITEM_INFO *v, KAYAKINFO *Kayak)
{
	sint32 h, slip=0, rot=0;
	PHD_VECTOR pos;
	int i, oldx, oldz;
	FLOOR_INFO *floor;
	sint16 room_number;
	sint32 height[8];
	PHD_VECTOR oldpos[9];

	sint32 x, z;
	sint32 fh, lh, rh;
	PHD_VECTOR fpos, lpos, rpos;

	Kayak->OldPos = v->pos;

/* -------- determine valid Kayak positions */

	height[0] = TestHeight(v,    0, 1024, &oldpos[0]);
	height[1] = TestHeight(v,  -96,  512, &oldpos[1]);
	height[2] = TestHeight(v,   96,  512, &oldpos[2]);
	height[3] = TestHeight(v, -128,  128, &oldpos[3]);
	height[4] = TestHeight(v,  128,  128, &oldpos[4]);
	height[5] = TestHeight(v, -128, -320, &oldpos[5]);
	height[6] = TestHeight(v,  128, -320, &oldpos[6]);
	height[7] = TestHeight(v,    0, -640, &oldpos[7]);

	for ( i=0 ; i<8 ; i++ )
	{
		if (oldpos[i].y > height[i])
			oldpos[i].y = height[i];
	}

	oldpos[8].x = v->pos.x_pos;
	oldpos[8].y = v->pos.y_pos;
	oldpos[8].z = v->pos.z_pos;

/* -------- move Kayak according to velocities & current */

	fh = TestHeight(v,        0,    1024, &fpos);
	lh = TestHeight(v, -KAYAK_X, KAYAK_Z, &lpos);
	rh = TestHeight(v,  KAYAK_X, KAYAK_Z, &rpos);

	v->pos.y_rot += (Kayak->Rot >> 16);

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

	DoCurrent(v);

/* -------- move Kayak vertically */

	Kayak->FallSpeedL = DoDynamics(lh, Kayak->FallSpeedL, &lpos.y);
	Kayak->FallSpeedR = DoDynamics(rh, Kayak->FallSpeedR, &rpos.y);
	Kayak->FallSpeedF = DoDynamics(fh, Kayak->FallSpeedF, &fpos.y);

	v->fallspeed = DoDynamics(Kayak->Water, v->fallspeed, &v->pos.y_pos);

	h = (lpos.y + rpos.y) >> 1;
	x = phd_atan(1024, v->pos.y_pos - fpos.y);
	z = phd_atan(KAYAK_X, h - lpos.y);

	v->pos.x_rot = x;
	v->pos.z_rot = z;

/* -------- slide Kayak back down slopes */

#if 0
	slip = BOAT_SIDE_SLIP * phd_sin(v->pos.z_rot) >> W2V_SHIFT;

	if (!slip && v->pos.z_rot)
		slip = (v->pos.z_rot > 0) ? 1 : -1;

	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 = BOAT_SLIP * phd_sin(v->pos.x_rot) >> W2V_SHIFT;

	if (!slip && v->pos.x_rot)
		slip = (v->pos.x_rot > 0) ? 1 : -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;
#endif

	oldx = v->pos.x_pos;
	oldz = v->pos.z_pos;

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

	if ((h = TestHeight(v, 0, -640, &pos)) < (oldpos[7].y - KAYAK_COLLIDE))
		rot = DoShift(v, &pos, &oldpos[7]);

	if ((h = TestHeight(v, 128, -320, &pos)) < (oldpos[6].y - KAYAK_COLLIDE))
		rot += DoShift(v, &pos, &oldpos[6]);

	if ((h = TestHeight(v, -128, -320, &pos)) < (oldpos[5].y - KAYAK_COLLIDE))
		rot += DoShift(v, &pos, &oldpos[5]);

	if ((h = TestHeight(v, 128, 128, &pos)) < (oldpos[4].y - KAYAK_COLLIDE))
		rot += DoShift(v, &pos, &oldpos[4]);

	if ((h = TestHeight(v, -128, 128, &pos)) < (oldpos[3].y - KAYAK_COLLIDE))
		rot += DoShift(v, &pos, &oldpos[3]);

	if ((h = TestHeight(v, 96, 512, &pos)) < (oldpos[2].y - KAYAK_COLLIDE))
		rot += DoShift(v, &pos, &oldpos[2]);

	if ((h = TestHeight(v, -96, 512, &pos)) < (oldpos[1].y - KAYAK_COLLIDE))
		rot += DoShift(v, &pos, &oldpos[1]);

	if ((h = TestHeight(v, 0, 1024, &pos)) < (oldpos[0].y - KAYAK_COLLIDE))
		rot += DoShift(v, &pos, &oldpos[0]);

	v->pos.y_rot += rot;

/* -------- update the vehicle's actual position */

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

	if (h == NO_HEIGHT)
		h = GetHeight(floor, v->pos.x_pos, v->pos.y_pos, v->pos.z_pos);

	if (h < (v->pos.y_pos - KAYAK_COLLIDE))
		DoShift(v, (PHD_VECTOR *)&v->pos, &oldpos[8]);

/* -------- if position is still invalid - find somewhere decent */

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

	if (h == NO_HEIGHT)
		h = GetHeight(floor, v->pos.x_pos, v->pos.y_pos, v->pos.z_pos);

	if (h == NO_HEIGHT)
	{
		GAME_VECTOR kpos;

		kpos.x = Kayak->OldPos.x_pos;
		kpos.y = Kayak->OldPos.y_pos;
		kpos.z = Kayak->OldPos.z_pos;
		kpos.room_number = v->room_number;

		CameraCollisionBounds(&kpos, 256, 0);
		{
			v->pos.x_pos = kpos.x;
			v->pos.y_pos = kpos.y;
			v->pos.z_pos = kpos.z;
			v->room_number = kpos.room_number;
		}
	}

/* -------- adjust speed upon collision */

	i = GetCollisionAnim(v, oldx, oldz);

#if 1

	if (slip || i)
	{
		sint32 newspeed;

		newspeed = ((v->pos.z_pos - oldpos[8].z) * phd_cos(v->pos.y_rot)
			    +  (v->pos.x_pos - oldpos[8].x) * phd_sin(v->pos.y_rot)) >> W2V_SHIFT;

		newspeed <<= 8;

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

		/* Adjust speed if serious change */

		if (slip)
		{
			/* Only if slip is above certain amount, and boat is not in FAST speed range */
			if (Kayak->Vel <= MAX_SPEED)
				Kayak->Vel = newspeed;
		}
		else
		{
			if (Kayak->Vel > 0 && newspeed < Kayak->Vel)
				Kayak->Vel = newspeed;

			else if (Kayak->Vel < 0 && newspeed > Kayak->Vel)
				Kayak->Vel = newspeed;
		}

		if (Kayak->Vel < -MAX_SPEED)
			Kayak->Vel = -MAX_SPEED;
	}
#endif
}
/****************************************************************************
* KayakUserInput
*
* SYNOPSIS
*	void KayakUserInput(ITEM_INFO *v, ITEM_INFO *l, KAYAKINFO *Kayak)
*
* PURPOSE
*	Determines animations & velocities depending upon the user's input
*
* INPUTS
*	v		= ptr to vehicle's item structure
*	l		= ptr to Lara's item structure
*	Kayak	= ptr to Kayak's specific structure
*
* RETURNS
*	Nothing
*/
static void KayakUserInput(ITEM_INFO *v, ITEM_INFO *l, KAYAKINFO *Kayak)
{
	sint16 frame;

/* -------- kill Lara in boat */

	if ((l->hit_points <= 0)
	&&  (l->current_anim_state != KS_DEATHIN))
	{
		l->anim_number = objects[VEHICLE_ANIM].anim_index + KAYAK_DEATHIN_A;
		l->frame_number = anims[l->anim_number].frame_base;
		l->current_anim_state = l->goal_anim_state = KS_DEATHIN;
	}

/* -------- control Kayak */

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

	switch (l->current_anim_state)
	{
		static char lr;

	/* --------------------- */
		case KS_POSE:
	/* --------------------- */
#ifdef TEST_COLLISION
			if (input & IN_ACTION)
				v->pos.y_rot += 4096;

			Kayak->Vel = 0;

			if (input & IN_FORWARD)
				Kayak->Vel = 0x200000;

			if (input & IN_BACK)
				Kayak->Vel = -0x200000;
#else
			if ((input & IN_ROLL)
			&&  (!lara.current_active)
			&&  (!lara.current_xvel)
			&&  (!lara.current_zvel))
			{
				if ((input & IN_LEFT) && (CanGetOut(v, -1)))
				{
					l->goal_anim_state = KS_JUMPOUT;
					l->required_anim_state = KS_CLIMBOUTL;
				}

				else if ((input & IN_RIGHT) && (CanGetOut(v, 1)))
				{
					l->goal_anim_state = KS_JUMPOUT;
					l->required_anim_state = KS_CLIMBOUTR;
				}
			}

			else if (input & IN_FORWARD)
			{
				l->goal_anim_state = KS_RIGHT;
				Kayak->Turn = 0;
				Kayak->Forward = 1;
			}

			else if (input & IN_BACK)
				l->goal_anim_state = KS_BACK;

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

				if (Kayak->Vel)
					Kayak->Turn = 0;
				else
					Kayak->Turn = 1;		// harder turn

				Kayak->Forward = 0;
			}

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

				if (Kayak->Vel)
					Kayak->Turn = 0;
				else
					Kayak->Turn = 1;		// harder turn

				Kayak->Forward = 0;
			}

			else if ((input & IN_STEPL)
				&& ((Kayak->Vel)
				||  (lara.current_xvel)
				||  (lara.current_zvel)))
					l->goal_anim_state = KS_TURNL;

			else if ((input & IN_STEPR)
				&& ((Kayak->Vel)
				||  (lara.current_xvel)
				||  (lara.current_zvel)))
					l->goal_anim_state = KS_TURNR;
#endif
			break;
	/* --------------------- */
		case KS_LEFT:
	/* --------------------- */
			if (Kayak->Forward)
			{
				if (!frame)
					lr = 0;

				if ((frame == 2) && (!(lr & 0x80)))
					lr++;

				else if (frame > 2)
					lr &= ~0x80;

				if (input & IN_FORWARD)
				{
					if (input & IN_LEFT)
					{
						if ((lr & ~0x80) >= 2)
							l->goal_anim_state = KS_RIGHT;
					}
					else
						l->goal_anim_state = KS_RIGHT;
				}
				else
					l->goal_anim_state = KS_POSE;
			}

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

		/* -------- apply velocities */

			if (frame == 7)
			{
				if (Kayak->Forward)
				{
					if ((Kayak->Rot -= KAYAK_FWD_ROT) < -KAYAK_MAX_TURN)
						Kayak->Rot = -KAYAK_MAX_TURN;

					Kayak->Vel += KAYAK_FWD_VEL;
				}

				else if (Kayak->Turn)
				{
					if ((Kayak->Rot -= KAYAK_HARD_ROT) < -KAYAK_MAX_STAT)
						Kayak->Rot = -KAYAK_MAX_STAT;
				}
				else
				{
					if ((Kayak->Rot -= KAYAK_LR_ROT) < -KAYAK_MAX_LR)
						Kayak->Rot = -KAYAK_MAX_LR;

					Kayak->Vel += KAYAK_LR_VEL;
				}
			}

		/* -------- initialise ripple effects */

			if ((frame > 6) && (frame < 24) && (frame & 1))
				DoRipple(v, -384, -64);

			break;
	/* --------------------- */
		case KS_RIGHT:
	/* --------------------- */
			if (Kayak->Forward)
			{
				if (!frame)
					lr = 0;

				if ((frame == 2) && (!(lr & 0x80)))
					lr++;

				else if (frame > 2)
					lr &= ~0x80;

				if (input & IN_FORWARD)
				{
					if (input & IN_RIGHT)
					{
						if ((lr & ~0x80) >= 2)
							l->goal_anim_state = KS_LEFT;
					}
					else
						l->goal_anim_state = KS_LEFT;
				}
				else
					l->goal_anim_state = KS_POSE;
			}

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

		/* -------- apply velocities */

			if (frame == 7)
			{
				if (Kayak->Forward)
				{
					if ((Kayak->Rot += KAYAK_FWD_ROT) > KAYAK_MAX_TURN)
						Kayak->Rot = KAYAK_MAX_TURN;

					Kayak->Vel += KAYAK_FWD_VEL;
				}

				else if (Kayak->Turn)
				{
					if ((Kayak->Rot += KAYAK_HARD_ROT) > KAYAK_MAX_STAT)
						Kayak->Rot = KAYAK_MAX_STAT;
				}
				else
				{
					if ((Kayak->Rot += KAYAK_LR_ROT) > KAYAK_MAX_LR)
						Kayak->Rot = KAYAK_MAX_LR;

					Kayak->Vel += KAYAK_LR_VEL;
				}
			}

		/* -------- initialise ripple effects */

			if ((frame > 6) && (frame < 24) && (frame & 1))
				DoRipple(v, 384, -64);

			break;
	/* --------------------- */
		case KS_BACK:
	/* --------------------- */
			if (!(input & IN_BACK))
				l->goal_anim_state = KS_POSE;

			if ((l->anim_number-objects[VEHICLE_ANIM].anim_index) == KAYAK_BACK_A)
			{
				if (frame == 8)
				{
					Kayak->Rot += KAYAK_FWD_ROT;
					Kayak->Vel -= KAYAK_FWD_VEL;
				}

				if (frame == 31)
				{
					Kayak->Rot -= KAYAK_FWD_ROT;
					Kayak->Vel -= KAYAK_FWD_VEL;
				}

				if ((frame < 15) && (frame & 1))
					DoRipple(v, 384, -128);

				else if ((frame >= 20) && (frame <= 34) && (frame & 1))
					DoRipple(v, -384, -128);
			}

			break;
	/* --------------------- */
		case KS_TURNL:
	/* --------------------- */
			if ((!(input & IN_STEPL))
			|| ((!Kayak->Vel)
			&&  (!lara.current_xvel)
			&&  (!lara.current_zvel)))
				l->goal_anim_state = KS_POSE;

			else if ((l->anim_number-objects[VEHICLE_ANIM].anim_index) == KAYAK_TURNL_A)
			{
				if (Kayak->Vel >= 0)
				{
					if ((Kayak->Rot -= KAYAK_TURN_ROT) < -KAYAK_MAX_TURN)
						Kayak->Rot = -KAYAK_MAX_TURN;

					if ((Kayak->Vel += -KAYAK_TURN_BRAKE) < 0)
						Kayak->Vel = 0;
				}
				if (Kayak->Vel < 0)
				{
					Kayak->Rot += KAYAK_TURN_ROT;

					if ((Kayak->Vel += KAYAK_TURN_BRAKE) > 0)
						Kayak->Vel = 0;
				}

				if (!(wibble & 3))
					DoRipple(v, -256, -256);
			}
			break;
	/* --------------------- */
		case KS_TURNR:
	/* --------------------- */
			if ((!(input & IN_STEPR))
			|| ((!Kayak->Vel)
			&&  (!lara.current_xvel)
			&&  (!lara.current_zvel)))
				l->goal_anim_state = KS_POSE;

			else if ((l->anim_number-objects[VEHICLE_ANIM].anim_index) == KAYAK_TURNR_A)
			{
				if (Kayak->Vel >= 0)
				{
					if ((Kayak->Rot += KAYAK_TURN_ROT) > KAYAK_MAX_TURN)
						Kayak->Rot = KAYAK_MAX_TURN;

					if ((Kayak->Vel += -KAYAK_TURN_BRAKE) < 0)
						Kayak->Vel = 0;
				}
				if (Kayak->Vel < 0)
				{
					Kayak->Rot -= KAYAK_TURN_ROT;

					if ((Kayak->Vel += KAYAK_TURN_BRAKE) > 0)
						Kayak->Vel = 0;
				}

				if (!(wibble & 3))
					DoRipple(v, 256, -256);
			}

			break;
	/* --------------------- */
		case KS_CLIMBIN:
	/* --------------------- */
			if ((l->anim_number == objects[VEHICLE_ANIM].anim_index + KAYAK_CLIMBIN2_A)
			&&  (frame == 24)
			&&  (!(Kayak->Flags & 0x80)))
			{
				sint16 *tmp;

				tmp = lara.mesh_ptrs[HAND_R];

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

				l->mesh_bits &= ~LARA_LEG_BITS;

				Kayak->Flags |= 0x80;
			}
			break;
	/* --------------------- */
		case KS_JUMPOUT:
	/* --------------------- */
			if ((l->anim_number == objects[VEHICLE_ANIM].anim_index + KAYAK_JUMPOUT1_A)
			&&  (frame == 27)
			&&  (Kayak->Flags & 0x80))
			{
				sint16 *tmp;

				tmp = lara.mesh_ptrs[HAND_R];

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

				l->mesh_bits |= LARA_LEG_BITS;

				Kayak->Flags &= ~0x80;
			}

			l->goal_anim_state = l->required_anim_state;
			break;
	/* --------------------- */
		case KS_CLIMBOUTL:
	/* --------------------- */
			if ((l->anim_number == objects[VEHICLE_ANIM].anim_index + KAYAK_JUMPOUT2_A)
			&&  (frame == 83))
			{
				PHD_VECTOR vec = { 0, 350, 500};

				GetLaraHandAbsPosition(&vec, LARA_HIPS);

				l->pos.x_pos = vec.x;
				l->pos.y_pos = vec.y;
				l->pos.z_pos = vec.z;
				l->pos.x_rot = 0;
				l->pos.y_rot = v->pos.y_rot - 0x4000;
				l->pos.z_rot = 0;

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

				l->fallspeed = 0;
				l->gravity_status = 1;
				lara.gun_status = LG_ARMLESS;
				lara.skidoo = NO_ITEM;
			}
			break;
	/* --------------------- */
		case KS_CLIMBOUTR:
	/* --------------------- */
			if ((l->anim_number == objects[VEHICLE_ANIM].anim_index + KAYAK_JUMPOUTR_A)
			&&  (frame == 83))
			{
				PHD_VECTOR vec = { 0, 350, 500};

				GetLaraHandAbsPosition(&vec, LARA_HIPS);

				l->pos.x_pos = vec.x;
				l->pos.y_pos = vec.y;
				l->pos.z_pos = vec.z;
				l->pos.x_rot = 0;
				l->pos.y_rot = v->pos.y_rot + 0x4000;
				l->pos.z_rot = 0;

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

				l->fallspeed = 0;
				l->gravity_status = 1;
				lara.gun_status = LG_ARMLESS;
				lara.skidoo = NO_ITEM;
			}
	}

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

	if (Kayak->Vel > 0)
	{
		if ((Kayak->Vel -= KAYAK_FRICTION) < 0)
			Kayak->Vel = 0;
	}
	else if (Kayak->Vel < 0)
	{
		if ((Kayak->Vel += KAYAK_FRICTION) > 0)
			Kayak->Vel = 0;
	}

	if (Kayak->Vel > MAX_SPEED)
		Kayak->Vel = MAX_SPEED;

	else if (Kayak->Vel < -MAX_SPEED)
		Kayak->Vel = -MAX_SPEED;

	v->speed = (Kayak->Vel >> 16);

/* -------- unwind rotation */

	if (Kayak->Rot >= 0)
	{
		if ((Kayak->Rot -= KAYAK_ROT_FRIC) < 0)
			Kayak->Rot = 0;
	}
	else if (Kayak->Rot < 0)
	{
		if ((Kayak->Rot += KAYAK_ROT_FRIC) > 0)
			Kayak->Rot = 0;
	}

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

	sprintf(exit_message, "Rot:%x", (int)Kayak->Rot);
	PrintDbug(1, 9, exit_message);
#endif
}

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

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

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

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

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

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

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

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

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

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

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

				object = &objects[item->object_number];

				if ((object->collision)
				&& ((item->object_number == SPIKES)
				||  (item->object_number == DARTS)
				||  (item->object_number == TEETH_TRAP)
				|| ((item->object_number == BLADE)
				&&  (item->current_anim_state != 1))			// STOP
				|| ((item->object_number == ICICLES)
				&&  (item->current_anim_state != 3))))			// LAND
				{
					int x, y, z;

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

					if ((x > -TARGET_DIST)
					&&  (x < TARGET_DIST)
					&&  (z > -TARGET_DIST)
					&&  (z < TARGET_DIST)
					&&  (y > -TARGET_DIST)
					&&  (y < TARGET_DIST))
					{
						if (TestBoundsCollide(item, v, KAYAK_TO_BADDIE_RADIUS))
						{
							DoLotsOfBlood(lara_item->pos.x_pos, lara_item->pos.y_pos-STEP_L, lara_item->pos.z_pos, v->speed, v->pos.y_rot, lara_item->room_number, 3);
							lara_item->hit_points -= 5;
						}
					}
				}
			}
			item_num = nex;
		}
	}

#undef TARGET_DIST
}
/*---------------------------------------------------------------------------
 * Public Functions
\*--------------------------------------------------------------------------*/


void LaraRapidsDrown()
{
	ITEM_INFO *l = lara_item;

	l->anim_number = objects[VEHICLE_ANIM].anim_index + KAYAK_DROWN_A;
	l->frame_number = anims[l->anim_number].frame_base;
	l->current_anim_state = EXTRA_RAPIDSDROWN;
	l->goal_anim_state = EXTRA_RAPIDSDROWN;
	l->hit_points = 0;
	l->fallspeed = 0;
	l->gravity_status = 0;
	l->speed = 0;

	AnimateItem(l);

	lara.extra_anim = 1;
	lara.gun_status = LG_HANDSBUSY;
	lara.gun_type = LG_UNARMED;
	lara.hit_direction = -1;
}

/****************************************************************************
* KayakInitialise
*
* SYNOPSIS
*	void KayakInitialise(ITEM_INFO *v)
*
* PURPOSE
*	Initialises the Kayak
*
* INPUTS
*	v = ptr to vehicle structure
*
* RETURNS
*	Nothing
*/
void KayakInitialise(sint16 item_number)
{
	int i;
	ITEM_INFO *v;
	KAYAKINFO *Kayak;

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

	Kayak->Vel = Kayak->Rot = Kayak->Flags = 0;
	Kayak->FallSpeedF = Kayak->FallSpeedL = Kayak->FallSpeedR = 0;

	Kayak->OldPos = v->pos;

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

/****************************************************************************
* KayakDraw
*
* SYNOPSIS
*	void KayakDraw(ITEM_INFO *v)
*
* PURPOSE
*	Draws the Kayak
*
* INPUTS
*	v		= ptr to vehicle structure
*
* RETURNS
*	Nothing
*/
void KayakDraw(ITEM_INFO *v)
{
	v->pos.y_pos += KAYAK_DRAW_SHIFT;

	DrawAnimatingItem(v);

	v->pos.y_pos -= KAYAK_DRAW_SHIFT;

	S_DrawWakeFX(v);
}
/****************************************************************************
* KayakCollision
*
* SYNOPSIS
*	void MineCartCollision(sint16 item_number, ITEM_INFO *litem, COLL_INFO *coll)
*
* PURPOSE
*	Determines whether Lara wants to get into the minecart
*
* INPUTS
*	item_number	= vehicle item #
*	l			= ptr to Lara's item structure
*	coll			= ptr to Lara's collision structure
*
* RETURNS
*	Nothing
*/
void KayakCollision(sint16 item_number, ITEM_INFO *l, COLL_INFO *coll)
{
	int geton;

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

	if ((geton = GetInKayak(item_number, coll)))
	{
		KAYAKINFO *Kayak;
		ITEM_INFO *v = &items[item_number];

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

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

		if (geton > 0)
			l->anim_number = objects[VEHICLE_ANIM].anim_index + KAYAK_CLIMBIN_A;
		else
			l->anim_number = objects[VEHICLE_ANIM].anim_index + KAYAK_CLIMBINR_A;

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

		lara.water_status = LARA_ABOVEWATER;
		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;
		l->pos.x_rot = l->pos.z_rot = 0;
		l->gravity_status = 0;
		l->speed = 0;
		l->fallspeed = 0;

		if (l->room_number != v->room_number)
			ItemNewRoom(lara.item_number, v->room_number);

		AnimateItem(l);

		Kayak = (KAYAKINFO *)v->data;
		Kayak->Water = v->pos.y_pos;
		Kayak->Flags = 0;
	}
	else
	{
		coll->enable_baddie_push = 1;
		ObjectCollision(item_number, l, coll);
	}
}
/****************************************************************************
* KayakControl
*
* SYNOPSIS
*	int KayakControl()
*
* PURPOSE
*
* INPUTS
*
* RETURNS
*	!0 if still under Kayak control
*/
int KayakControl()
{
	sint32 h;
	KAYAKINFO *Kayak;
	ITEM_INFO *v, *l;
	FLOOR_INFO *floor;
	sint32 ofs, water,lp;
	sint16 room_number;

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

	if (input & IN_LOOK)
		LookUpDown();

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

	ofs = v->fallspeed;

	KayakUserInput(v, l, Kayak);

	KayakToBackground(v, Kayak);

/* -------- determine water level */

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

	if ((Kayak->Water = water = GetWaterHeight(v->pos.x_pos, v->pos.y_pos, v->pos.z_pos, room_number)) == NO_HEIGHT)
	{
		Kayak->Water = water = h;
		Kayak->TrueWater = 0;
	}
	else
	{
		Kayak->Water -= 5;
		Kayak->TrueWater = 1;
	}


	if (((ofs-v->fallspeed) > 128)
	&&   (v->fallspeed == 0)
	&&   (water != NO_HEIGHT))
	{
		long damage;
		if ((damage=(ofs-v->fallspeed)) > 160)
			l->hit_points -= (damage-160)<<3;

		KayakSplash(v,ofs-v->fallspeed, water);
	}

#ifdef DEBUG_KAYAK
	sprintf(exit_message, "%d %d", v->pos.y_pos, fpos.y);
	PrintDbug(1, 10, exit_message);

	sprintf(exit_message, "RotX:%x  RotZ:%x", v->pos.x_rot, v->pos.z_rot);
	PrintDbug(1, 11, exit_message);
#endif

/* -------- move Lara to Kayak pos */

	if (lara.skidoo != NO_ITEM)
	{
		if (v->room_number != 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 + KAYAK_DRAW_SHIFT;
		l->pos.z_pos = v->pos.z_pos;
		l->pos.x_rot = v->pos.x_rot;
		l->pos.y_rot = v->pos.y_rot;
		l->pos.z_rot = v->pos.z_rot >> 1;

	/* -------- animate Lara then Kayak */

		AnimateItem(l);

		v->anim_number = objects[KAYAK].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;
		camera.target_distance = WALL_L*2;
	}

/* -------- process effects */

	if ((!(wibble & 15)) && (Kayak->TrueWater))
	{
		DoWake(v, -128, 0, 0);
 		DoWake(v,  128, 0, 1);
	}


	if ((wibble&7))
	{
		if ((!Kayak->TrueWater) && (v->fallspeed < 20))
		{
			PHD_VECTOR dest;
			static char cnt=0;
			static sint16 MistZPos[10] = { 900, 750, 600, 450, 300, 150, 0,  -150, -300, -450};
//			static sint16 MistXPos[10] = { 32,  64,  128, 192, 256, 480, 480, 350,  256,  64};
			static sint16 MistXPos[10] = { 32,  96,  170, 220, 300, 400, 400, 300,  200,  64};

			cnt ^= 1;

			for (lp=cnt;lp<10;lp+=2)
			{
				//dest.x = (GetRandomControl()%MistXPos[lp]) - (MistXPos[lp]>>1);
				if (GetRandomControl()&1)
					dest.x = (MistXPos[lp]>>1);
				else
					dest.x = -(MistXPos[lp]>>1);
				dest.y = 50;
				dest.z = MistZPos[lp];

#ifdef PSX_VERSION
				mPushUnitMatrix();
				mRotYXZ(v->pos.y_rot, v->pos.x_rot, v->pos.z_rot);
				gte_SetTransVector(&v->pos.x_pos);
				gte_ldlvl(&dest);
 				gte_rtirtr();
				mPopMatrix();
				gte_stlvnl(&dest);
				TriggerRapidsMist(dest.x, dest.y, dest.z);
				//TriggerFlareSparks(dest.x, dest.y, dest.z,0,0,0,0,0);
#else
				phd_PushUnitMatrix();
				phd_RotYXZ(v->pos.y_rot, v->pos.x_rot, v->pos.z_rot);
				dest.x = v->pos.x_pos + ((*(mptr+M00)*dest.x + *(mptr+M01)*dest.y + *(mptr+M02)*dest.z )>>W2V_SHIFT);
				dest.y = v->pos.y_pos + ((*(mptr+M10)*dest.x + *(mptr+M11)*dest.y + *(mptr+M12)*dest.z )>>W2V_SHIFT);
				dest.z = v->pos.z_pos + ((*(mptr+M20)*dest.x + *(mptr+M21)*dest.y + *(mptr+M22)*dest.z )>>W2V_SHIFT);
				TriggerRapidsMist(dest.x, dest.y, dest.z);
				phd_PopMatrix();
#endif
			}

		}
	}
	if ((v->speed == 0)
	&&  (!lara.current_xvel)
	&&  (!lara.current_zvel))
	{
		if (WakeShade)
			WakeShade--;
	}
	else
	{
		if (WakeShade < 16)
			WakeShade++;
	}

	UpdateWakeFX();

	KayakToBaddieCollision(v);

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