/*********************************************************************************************/
/*                                                                                           */
/* London Boss - TS & Gibby - 16/9/98                                                                    */
/*                                                                                           */
/*********************************************************************************************/

#ifdef PSX_VERSION
#include <sys\types.h>
#include <libgte.h>
#include <libgpu.h>
#include <gtemac.h>
#include <inline_c.h>
#include "../spec_psx/typedefs.h"
#include "../spec_psx/maths.h"
//#include "../spec_psx/psxinput.h"	//##
#else
#include "../specific/stypes.h"
#include <sys\types.h>

typedef unsigned char uchar;

#endif

#include "objects.h"
#include "lara.h"
#include "sound.h"
#include "effect2.h"
#include "lot.h"
#include "sphere.h"
#include "people.h"
#include "text.h"
#include "control.h"
#include "laraanim.h"
#include "laraelec.h"
#include "pickup.h"
#include "traps.h"
#include "setup.h"
//#include "input.h"	//##

typedef struct
{
	short 	x;
	short 	y;
	short 	z;
	uchar	rsub;
	uchar	gsub;
	uchar	bsub;
	uchar	pad[3];
	long		rgb;

} SHIELD_POINTS;

typedef struct
{
	short 	x;
	short 	z;
	long		rgb;

} EXPLOSION_VERTS;

typedef struct
{
	short on;
	short life;	// 0 - 32.
	short speed;
	short radius;	// Width is 1/4 of radius.
	short xrot;
	short zrot;
	long	x;
	long	y;
	long	z;
	EXPLOSION_VERTS	verts[16];

} EXPLOSION_RING;

#ifdef PSX_VERSION
static void DrawLondonBossShield(ITEM_INFO *item);
static void DrawExplosionRings();
static void DrawSummonRings();
static void DrawKnockBackRings();
#endif
static void TriggerPlasmaBall(ITEM_INFO *item, long type, PHD_VECTOR *pos1, sint16 room_number, sint16 angle);
static void TriggerLaserBolt(PHD_VECTOR *pos, ITEM_INFO *item, long type, sint16 yang);
static void LondonBossDie(sint16 item_number);
static void ExplodeLondonBoss(ITEM_INFO *item);
static long KnockBackCollision(EXPLOSION_RING *erptr);

extern sint32 GetRandomControl();
extern sint32 GetRandomDraw();

extern long wibble;
extern short rcossin_tbl[];
extern int LOS(GAME_VECTOR *start, GAME_VECTOR *target);
extern char exit_message[];


static char	links[20][4] = {	{	0,0,1,2	},
							{	0,0,2,3,	},
							{	0,0,3,4,	},
							{	0,0,4,1,	},

							{	1,2,5,6,	},
							{	2,3,6,7,	},
							{	3,4,7,8,	},
							{	4,1,8,5,	},

							{	1+4,2+4,5+4,6+4,	},
							{	2+4,3+4,6+4,7+4,	},
							{	3+4,4+4,7+4,8+4,	},
							{	4+4,1+4,8+4,5+4,	},

							{	1+8,2+8,5+8,6+8,	},
							{	2+8,3+8,6+8,7+8,	},
							{	3+8,4+8,7+8,8+8,	},
							{	4+8,1+8,8+8,5+8,	},

							{	1+12,2+12,5+12,5+12,	},
							{	2+12,3+12,5+12,5+12,	},
							{	3+12,4+12,5+12,5+12,	},
							{	4+12,1+12,5+12,5+12,	}
						};

static char	sumlinks[8][4] = {	{	0,0,1,2	},
							{	0,0,2,3,	},
							{	0,0,3,4,	},
							{	0,0,4,1,	},

							{	1,2,5,5,	},
							{	2,3,5,5,	},
							{	3,4,5,5,	},
							{	4,1,5,5,	},
						};


/*********************************** TYPE DEFINITIONS ****************************************/

#define	MAX_TRIGGER_RANGE	0x4000

#define	SMALL_FLASH 	10
#define	BIG_FLASH 	16
#define	BOLT_SPEED	384

enum	{	NOTHING, SUMMONING, KNOCKBACK	};
enum	{	NORMAL_BOLT, LARGE_BOLT, SUMMON_BOLT	};
enum	{	RIGHT_PRONG=0, ICONPOS, LEFT_PRONG	};
enum	{	ATTACK_HEAD, ATTACK_HAND1, ATTACK_HAND2	};

// London boss.

enum londonboss_anims {
		LONDONBOSS_EMPTY,
		LONDONBOSS_STAND,
		LONDONBOSS_WALK,
		LONDONBOSS_RUN,
		LONDONBOSS_SUMMON,
		LONDONBOSS_BIGZAP,
		LONDONBOSS_DEATH,
		LONDONBOSS_LAUGH,
		LONDONBOSS_LILZAP,
		LONDONBOSS_VAULT2,
		LONDONBOSS_VAULT3,
		LONDONBOSS_VAULT4,
		LONDONBOSS_GODOWN
};

#define	LONDONBOSS_VAULT2_ANIM 9
#define	LONDONBOSS_VAULT3_ANIM 18
#define	LONDONBOSS_VAULT4_ANIM 15
#define	LONDONBOSS_GODOWN_ANIM 21
#define	LONDONBOSS_STND2SUM_ANIM 1
#define LONDONBOSS_SUMMON_ANIM 2
#define	LONDONBOSS_GODOWN_ANIM 21

#define LONDONBOSS_VAULT_SHIFT 96
#define LONDONBOSS_AWARE_DISTANCE SQUARE(WALL_L)

#define LONDONBOSS_WALK_TURN (ONE_DEGREE*4)
#define LONDONBOSS_RUN_TURN (ONE_DEGREE*7)

#define LONDONBOSS_WALK_RANGE SQUARE(WALL_L)

#define LONDONBOSS_WALK_CHANCE 0x100
#define LONDONBOSS_LAUGH_CHANCE 0x100

#define LONDONBOSS_TURN 		(ONE_DEGREE*2)
#define LONDONBOSS_DIE_ANIM 	17

#define LONDONBOSS_FINAL_HEIGHT -11776

#define BIGZAP_TIMER 600

//#define DEBUG_LONDONBOSS

#ifdef DEBUG_LONDONBOSS
extern char exit_message[];
#endif

#ifdef DEBUG_LONDONBOSS
static char *LondonBossStrings[] = {
		"EMPTY",
		"STAND",
		"WALK",
		"RUN",
		"SUMMON",
		"BIGZAP",
		"DEATH",
		"LAUGH",
		"LILZAP",
		"VAULT2",
		"VAULT3",
		"VAULT4",
		"GODOWN"
};
#endif

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

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

void InitialiseLondonBoss(sint16 item_number);
void LondonBossControl(sint16 item_number);
void S_DrawLondonBoss(ITEM_INFO *item);
void ControlLaserBolts(sint16 item_number);
void S_DrawLaserBolts(ITEM_INFO *item);
void ControlLondBossPlasmaBall(sint16 fx_number);

void *func[] __attribute__((section(".header"))) = {
	InitialiseLondonBoss,
	LondonBossControl,
	S_DrawLondonBoss,
	ControlLaserBolts,
	S_DrawLaserBolts,
	ControlLondBossPlasmaBall,
};

#endif

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

static	long		death_radii[5];
static	long		death_heights[5];
static	long		radii[5] = {	200,400,500,500,475	};
static	long		heights[5] = {	-1536,-1280,-832,-384,0	};
static	long		dradii[5] = {	100<<4,350<<4,400<<4,350<<4,100<<4	};
static	long		dheights1[5] = {	-1536-(768<<3),-1152-(384<<3),-768,-384+(384<<3),0+(768<<3)	};
static	long		dheights2[5] = {	-1536,-1152,-768,-384,0	};


static BITE_INFO londonboss_points[3] = {
									{16,56,356, 10},	// Right prong.
									{-28,48,304, 10},	// Icon.
									{-72,48,356, 10},	// Left prong.
//									{-128,-64,136, 8},
//									{-124,64,136, 8},
//									{8,32,400, 8}
								};


static SHIELD_POINTS	LondonBossShield[40];	// x,y,z,rgb.
static EXPLOSION_RING	ExpRings[6];
static EXPLOSION_RING	KBRings[3];

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

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

void LondonBossControl(sint16 item_number)
{
	ITEM_INFO *item, *enemy;
	CREATURE_INFO *londonboss;
	sint16 angle, head, tilt,lp, torso_y, torso_x;
	sint32 lara_dx, lara_dz, lara_dy;
	AI_INFO info, lara_info;
	PHD_VECTOR	pos1,trident[3];

	if (!CreatureActive(item_number))
		return;

	item = &items[item_number];
	londonboss = (CREATURE_INFO *)item->data;
	head = angle = tilt = torso_x = torso_y = 0;
// Gibby - when item->item_flags[2] == 1, it's time for her to fry...

// Get positions of the trident prongs (or tines, for the posh among us).

	if (item->item_flags[2])
	{
		if (item->item_flags[2] == 1)
			item->hit_points = 0;

		if (item->item_flags[2] >= 12)
			TriggerDynamic(item->pos.x_pos, item->pos.y_pos, item->pos.z_pos, (GetRandomControl()&3)+8, 0, (GetRandomControl()&7)+8, (GetRandomControl()&7)+16);
		else
			TriggerDynamic(item->pos.x_pos, item->pos.y_pos, item->pos.z_pos, 25-(item->item_flags[2]<<1)+(GetRandomControl()&1), (16-item->item_flags[2])+(GetRandomControl()&7), 32-item->item_flags[2], 31);
	}

	for (lp=0;lp<3;lp++)
	{
		trident[lp].x = londonboss_points[lp].x;
		trident[lp].y = londonboss_points[lp].y;
		trident[lp].z = londonboss_points[lp].z;
		GetJointAbsPosition(item,&trident[lp],londonboss_points[lp].mesh_num);
	}

	if (item->hit_points <= 0)
	{
		if (item->current_anim_state != LONDONBOSS_DEATH)
		{
			item->anim_number = objects[item->object_number].anim_index + LONDONBOSS_DIE_ANIM;
			item->frame_number = anims[item->anim_number].frame_base;
			item->current_anim_state = LONDONBOSS_DEATH;
		}

		if (anims[item->anim_number].frame_end - item->frame_number == 1)
		{
			item->frame_number = anims[item->anim_number].frame_end - 1;
			item->mesh_bits = 0;	// Don't draw any of the boss while I'm killing it.
			if (bossdata.explode_count == 0)
			{
				bossdata.ring_count = 0;
				for (lp=0;lp<6;lp++)
				{
					ExpRings[lp].on = 0;
					ExpRings[lp].life = 32;
					ExpRings[lp].radius = 512;
					ExpRings[lp].speed = 128+(lp<<5);
					ExpRings[lp].xrot = ((GetRandomControl()&511)-256)<<4;
					ExpRings[lp].zrot = ((GetRandomControl()&511)-256)<<4;
				}

				if (bossdata.dropped_icon == 0)
				{
					BossDropIcon(item_number);
					bossdata.dropped_icon = 1;
				}
			}

			if (bossdata.explode_count < 256)
				bossdata.explode_count++;
			if (	bossdata.explode_count > 128 && bossdata.ring_count == 6 && ExpRings[5].life == 0)
			{
				LondonBossDie(item_number);
				bossdata.dead = 1;
			}
			else
				ExplodeLondonBoss(item);
			return;
		}
	}
	else
	{
		if (item->ai_bits)
			GetAITarget(londonboss);

		CreatureAIInfo(item, &info);

		if (londonboss->enemy == lara_item)
		{
			lara_info.angle = info.angle;
			lara_info.distance = info.distance;
			lara_info.x_angle = info.x_angle;
		}
		else
		{
			lara_dz = lara_item->pos.z_pos - item->pos.z_pos;
			lara_dx = lara_item->pos.x_pos - item->pos.x_pos;
			lara_dy = item->pos.y_pos - lara_item->pos.y_pos;

			lara_info.angle = phd_atan(lara_dz, lara_dx) - item->pos.y_rot; //only need to fill out the bits of lara_info that will be needed by TargetVisible
			lara_info.distance = lara_dz * lara_dz + lara_dx * lara_dx;
			if (abs(lara_dx) > abs(lara_dz))
				lara_info.x_angle = phd_atan(abs(lara_dx) + (abs(lara_dz) >> 1), lara_dy);
			else
				lara_info.x_angle = phd_atan(abs(lara_dz) + (abs(lara_dx) >> 1), lara_dy);

		}

		GetCreatureMood(item, &info, VIOLENT);

		CreatureMood(item, &info, VIOLENT);

//		if (londonboss->reached_goal)
//		{
//			londonboss->target.x = lara_item->pos.x_pos;
//			londonboss->target.z = lara_item->pos.z_pos;
//		}

		angle = CreatureTurn(item, londonboss->maximum_turn);

	#ifdef DEBUG_LONDONBOSS
//	sprintf(exit_message, "head: %d, Target Visible?: %d", londonboss->joint_rotation[1], TargetVisible(item, &info));
//	PrintDbug(2, 5, exit_message);
//	sprintf(exit_message, "x_angle: %d, Visible?: %d", info.x_angle, TargetVisible(item, &info));
//	PrintDbug(2, 5, exit_message);
	#endif


		enemy = londonboss->enemy; //TargetVisible uses enemy, so need to fill this in as lara if we're doing other things
		londonboss->enemy = lara_item;
//		if (((lara_info.distance < LONDONBOSS_AWARE_DISTANCE && abs(lara_item->pos.y_pos - item->pos.y_pos) < WALL_L)|| item->hit_status || TargetVisible(item, &lara_info)) && !(item->ai_bits & FOLLOW)) //Maybe move this into LONDSEC_LAUGH case?
//		if ((lara_info.distance < LONDONBOSS_AWARE_DISTANCE || item->hit_status || TargetVisible(item, &lara_info)) && !(item->ai_bits & FOLLOW))
		if (item->hit_status || lara_info.distance < LONDONBOSS_AWARE_DISTANCE ||  TargetVisible(item, &lara_info) || lara_item->pos.y_pos < item->pos.y_pos) // TS- TODO: take this back out after demo!!
			AlertAllGuards(item_number);
		londonboss->enemy = enemy;


		if (lara_item->pos.y_pos < item->pos.y_pos)
			londonboss->hurt_by_lara = 1;

		if (item->timer > 0)
			item->timer--;

		item->hit_points = 300;
/*
		if (item->pos.y_pos == LONDONBOSS_FINAL_HEIGHT)
		{
			londonboss->LOT.step = 0;
			londonboss->LOT.drop = 0;
		}
*/

		switch (item->current_anim_state)
		{
		case LONDONBOSS_LAUGH:
			if (abs(lara_info.angle) <  LONDONBOSS_WALK_TURN)
				item->pos.y_rot += lara_info.angle;
			else if (lara_info.angle < 0)
				item->pos.y_rot -= LONDONBOSS_WALK_TURN;
			else
				item->pos.y_rot += LONDONBOSS_WALK_TURN;

			if (londonboss->alerted)
			{
				item->goal_anim_state = LONDONBOSS_STAND;
				break;
			}

		case LONDONBOSS_STAND:

			londonboss->flags = 0;
			londonboss->maximum_turn = 0;

			if (londonboss->reached_goal)	//She's found this ambush point, so set up the next one
			{
				londonboss->reached_goal = 0;
				item->ai_bits |= AMBUSH;
				item->item_flags[3] += 0x2000;
			}

			head = lara_info.angle;

			if (item->ai_bits & GUARD)
			{
				if ((head < -0x3000 || head > 0x3000) && item->pos.y_pos > LONDONBOSS_FINAL_HEIGHT)
				{
					item->goal_anim_state = LONDONBOSS_WALK;
					londonboss->maximum_turn = LONDONBOSS_WALK_TURN;
				}
				break;

				head = AIGuard(londonboss);
				if (!(GetRandomControl() & 0xF))
						item->goal_anim_state = LONDONBOSS_LAUGH;
				break;
			}
			else if ((item->pos.y_pos <= LONDONBOSS_FINAL_HEIGHT || item->pos.y_pos < lara_item->pos.y_pos)  && !(GetRandomControl() & 0xF) && !bossdata.charged && item->timer)
				item->goal_anim_state = LONDONBOSS_LAUGH;
			else if (londonboss->reached_goal || lara_item->pos.y_pos > item->pos.y_pos || item->pos.y_pos <= LONDONBOSS_FINAL_HEIGHT)
			{
				if (bossdata.charged)
					item->goal_anim_state = LONDONBOSS_BIGZAP;
				else if (!item->timer)
					item->goal_anim_state = LONDONBOSS_SUMMON;
				else //if (!(GetRandomControl() & 0x3))
					item->goal_anim_state = LONDONBOSS_LILZAP;
			}

			else if (item->ai_bits & PATROL1)
				item->goal_anim_state = LONDONBOSS_WALK;
			else if (londonboss->mood == ESCAPE_MOOD || item->pos.y_pos > lara_item->pos.y_pos)
				item->goal_anim_state = LONDONBOSS_RUN;
			else if (londonboss->mood == BORED_MOOD || ((item->ai_bits & FOLLOW ) && (londonboss->reached_goal || lara_info.distance > SQUARE(WALL_L*2))))
			{
				if (item->required_anim_state)
					item->goal_anim_state = item->required_anim_state;
				else if (info.ahead)
					item->goal_anim_state = LONDONBOSS_STAND;
				else
					item->goal_anim_state = LONDONBOSS_RUN;
			}
			else if (info.bite && info.distance < LONDONBOSS_WALK_RANGE)
				item->goal_anim_state = LONDONBOSS_WALK;
			else
				item->goal_anim_state = LONDONBOSS_RUN;
			break;

		/* - Moved to above LONDONBOSS-STOP
		case LONDONBOSS_LAUGH:
			if (info.ahead)
				head = info.angle;
			londonboss->maximum_turn = 0;

			if (londonboss->mood != BORED_MOOD)
				item->goal_anim_state = LONDONBOSS_STAND;
			else if (GetRandomControl() < LONDONBOSS_WALK_CHANCE)
			{
				item->required_anim_state = LONDONBOSS_WALK;
				item->goal_anim_state = LONDONBOSS_STAND;
			}
			break;
		*/
		case LONDONBOSS_WALK:
			head=lara_info.angle;
			londonboss->flags = 0;

			londonboss->maximum_turn = LONDONBOSS_WALK_TURN;

			if (item->ai_bits & GUARD || (londonboss->reached_goal && !(item->ai_bits & FOLLOW )))
				item->goal_anim_state = LONDONBOSS_STAND;
			else if (item->ai_bits & PATROL1)
			{
				item->goal_anim_state = LONDONBOSS_WALK;
				head=0;
			}
			else if (londonboss->mood == ESCAPE_MOOD)
				item->goal_anim_state = LONDONBOSS_RUN;
			else if (londonboss->mood == BORED_MOOD)
			{
				if (GetRandomControl() < LONDONBOSS_LAUGH_CHANCE)
				{
					item->required_anim_state = LONDONBOSS_LAUGH;
					item->goal_anim_state = LONDONBOSS_STAND;
				}
			}
			else if (info.distance > LONDONBOSS_WALK_RANGE)
				item->goal_anim_state = LONDONBOSS_RUN;
			break;

		case LONDONBOSS_RUN:
			if (info.ahead)
				head = info.angle;

			londonboss->maximum_turn = LONDONBOSS_RUN_TURN;
			tilt = angle/2;

			if (item->ai_bits & GUARD || (londonboss->reached_goal && !(item->ai_bits & FOLLOW )))
				item->goal_anim_state = LONDONBOSS_STAND;
			else if (londonboss->mood == ESCAPE_MOOD)
				break;
			else if ((item->ai_bits & FOLLOW ) && (londonboss->reached_goal || lara_info.distance > SQUARE(WALL_L*2)))
				item->goal_anim_state = LONDONBOSS_STAND;	//Maybe LONDONBOSS_STAND
			else if (londonboss->mood == BORED_MOOD)
				item->goal_anim_state = LONDONBOSS_WALK;
			else if (info.ahead && info.distance < LONDONBOSS_WALK_RANGE)
				item->goal_anim_state = LONDONBOSS_WALK;
			break;

		case LONDONBOSS_SUMMON:
			head=lara_info.angle;

			if (londonboss->reached_goal)	//She's found this ambush point, so set up the next one
			{
				londonboss->reached_goal = 0;
				item->ai_bits = AMBUSH;
				item->item_flags[3] += 0x2000;
			}

			if (item->anim_number == objects[item->object_number].anim_index + LONDONBOSS_STND2SUM_ANIM)
			{
				if (item->frame_number == anims[item->anim_number].frame_base)
				{
					bossdata.hp_counter = item->hit_points;
					item->timer = BIGZAP_TIMER;
//					SoundEffect(70, &item->pos, 0); //Oi! - just for testing!
				}
//				else if (bossdata.hp_counter - item->hit_points > 5) //if she takes more than 5hp damage during the stand2summon anim, break off the summoning
				else if (item->hit_status && item->goal_anim_state != LONDONBOSS_STAND) //if she takes *any* damage during the stand2summon anim, break off the summoning
				{
					StopSoundEffect(352);
					SoundEffect(353, &item->pos, 0); // Take Hit
					SoundEffect(355, &item->pos, 0); // Summon wind-down noise

					item->goal_anim_state = LONDONBOSS_STAND; //Put a condition in here so she can do more than one attack per waypoint?
				}
			}
			else if (item->anim_number == objects[item->object_number].anim_index + LONDONBOSS_SUMMON_ANIM && item->frame_number == anims[item->anim_number].frame_end)
				bossdata.charged = 1;

			if (abs(lara_info.angle) <  LONDONBOSS_WALK_TURN)
				item->pos.y_rot += lara_info.angle;
			else if (lara_info.angle < 0)
				item->pos.y_rot -= LONDONBOSS_WALK_TURN;
			else
				item->pos.y_rot += LONDONBOSS_WALK_TURN;

// Summon.
			if ((wibble & 7) == 0)
			{
				pos1.x = item->pos.x_pos;
				pos1.y = (GetRandomControl()&511)-256;
				pos1.z = item->pos.z_pos;
				TriggerLaserBolt(&pos1,item,SUMMON_BOLT,0);

				for (lp=0;lp<6;lp++)
				{
					long	rnd;

					if (!ExpRings[lp].on)
					{
						ExpRings[lp].on = 1;
						ExpRings[lp].life = 64;
						ExpRings[lp].speed = (GetRandomControl()&15)+16;
						ExpRings[lp].x = item->pos.x_pos;
						ExpRings[lp].y = item->pos.y_pos + 128 - (rnd=(GetRandomControl()&1023));
						ExpRings[lp].z = item->pos.z_pos;
						ExpRings[lp].xrot = ((GetRandomControl()&511)-256)<<4;
						ExpRings[lp].zrot = ((GetRandomControl()&511)-256)<<4;
						ExpRings[lp].radius = (1024+(1024-(abs(rnd-512))));
						break;
					}
				}
			}

			londonboss->maximum_turn = 0;
			break;

		case LONDONBOSS_BIGZAP:
			if (londonboss->reached_goal)	//She's found this ambush point, so set up the next one
			{
				londonboss->reached_goal = 0;
				item->ai_bits = AMBUSH;
				item->item_flags[3] += 0x2000;
			}
			bossdata.charged = 0;
			if (abs(lara_info.angle) <  LONDONBOSS_WALK_TURN)
				item->pos.y_rot += lara_info.angle;
			else if (lara_info.angle < 0)
				item->pos.y_rot -= LONDONBOSS_WALK_TURN;
			else
				item->pos.y_rot += LONDONBOSS_WALK_TURN;

			torso_y = lara_info.angle;
			torso_x = lara_info.x_angle;

			londonboss->maximum_turn = 0;

			if (item->frame_number == anims[item->anim_number].frame_base + 36)
			{
				TriggerLaserBolt(&trident[RIGHT_PRONG],item,NORMAL_BOLT,item->pos.y_rot+0x200);
				TriggerLaserBolt(&trident[ICONPOS],item,LARGE_BOLT,item->pos.y_rot);
				TriggerLaserBolt(&trident[LEFT_PRONG],item,NORMAL_BOLT,item->pos.y_rot-0x200);
			}

			break;

		case LONDONBOSS_LILZAP:
			if (londonboss->reached_goal)	//She's found this ambush point, so set up the next one
			{
				londonboss->reached_goal = 0;
				item->ai_bits = AMBUSH;
				item->item_flags[3] += 0x2000;
			}

			if (abs(lara_info.angle) <  LONDONBOSS_WALK_TURN)
				item->pos.y_rot += lara_info.angle;
			else if (lara_info.angle < 0)
				item->pos.y_rot -= LONDONBOSS_WALK_TURN;
			else
				item->pos.y_rot += LONDONBOSS_WALK_TURN;

			torso_y = lara_info.angle;
			torso_x = lara_info.x_angle;

			londonboss->maximum_turn = 0;

			if (item->frame_number == anims[item->anim_number].frame_base + 14)
			{
				TriggerLaserBolt(&trident[RIGHT_PRONG],item,NORMAL_BOLT,item->pos.y_rot+0x200);
				TriggerLaserBolt(&trident[LEFT_PRONG],item,NORMAL_BOLT,item->pos.y_rot-0x200);
			}

			break;

		}
	}

	CreatureTilt(item, tilt);
	CreatureJoint(item, 0, torso_y);
	CreatureJoint(item, 1, torso_x);
	CreatureJoint(item, 2, head);

	#ifdef DEBUG_LONDONBOSS
//	sprintf(exit_message, "Torso_y: %d, Torso_x: %d, Head: %d", torso_y, torso_x, head);
//	PrintDbug(2, 2, exit_message);
//	sprintf(exit_message, "Max Turn: %d", londonboss->maximum_turn);
//	PrintDbug(2, 2, exit_message);
//	sprintf(exit_message, "Angle: %d", angle);
//	PrintDbug(2, 2, exit_message);
	sprintf(exit_message, "Mood:%d, AI:%d, bossdata.charged:%d, YPos:%d", londonboss->mood, item->ai_bits, bossdata.charged, item->pos.y_pos);
	PrintDbug(2, 2, exit_message);
	sprintf(exit_message, "%s", LondonBossStrings[item->current_anim_state]);
	PrintDbug(2, 3, exit_message);
	sprintf(exit_message, "%s", LondonBossStrings[item->goal_anim_state]);
	PrintDbug(2, 4, exit_message);
//	sprintf(exit_message, "%s", LondonBossStrings[item->required_anim_state]);
//	PrintDbug(2, 6, exit_message);
//	sprintf(exit_message, "Anim Number; %d", item->anim_number - objects[LON_BOSS].anim_index);
//	PrintDbug(2, 6, exit_message);
	sprintf(exit_message, "Alert:%d, Goal:%d, Rot:%d", londonboss->alerted, londonboss->reached_goal, item->item_flags[3]);
	PrintDbug(2,5, exit_message);

//	sprintf(exit_message, "AIbits:%d, bossdata.charged:%d", item->ai_bits, bossdata.charged);
//	PrintDbug(2,7, exit_message);
	#endif

	/* Actually do animation allowing for collisions */
	if ((item->current_anim_state < LONDONBOSS_VAULT2 || item->current_anim_state > LONDONBOSS_GODOWN) && item->current_anim_state != LONDONBOSS_DEATH)
	{
		switch (CreatureVault(item_number, angle, 2, LONDONBOSS_VAULT_SHIFT))
		{
		case 2:
			/* Half block jump */
			londonboss->maximum_turn = 0;
			item->anim_number = objects[LON_BOSS].anim_index + LONDONBOSS_VAULT2_ANIM;
			item->frame_number = anims[item->anim_number].frame_base;
			item->current_anim_state = LONDONBOSS_VAULT2;
			break;

		case 3:
			/* 3/4 block jump */
			londonboss->maximum_turn = 0;
			item->anim_number = objects[LON_BOSS].anim_index + LONDONBOSS_VAULT3_ANIM;
			item->frame_number = anims[item->anim_number].frame_base;
			item->current_anim_state = LONDONBOSS_VAULT3;
			break;

		case 4:
			/* Full block jump */
			londonboss->maximum_turn = 0;
			item->anim_number = objects[LON_BOSS].anim_index + LONDONBOSS_VAULT4_ANIM;
			item->frame_number = anims[item->anim_number].frame_base;
			item->current_anim_state = LONDONBOSS_VAULT4;
			break;
		case -4:
			/* Full block fall */
			londonboss->maximum_turn = 0;
			item->anim_number = objects[LON_BOSS].anim_index + LONDONBOSS_GODOWN_ANIM;
			item->frame_number = anims[item->anim_number].frame_base;
			item->current_anim_state = LONDONBOSS_GODOWN;
			break;
		}
	}
	else
	{
		londonboss->maximum_turn = 0;
		CreatureAnimation(item_number, angle, 0);
	}

// Do light.

	{
		long	g,b;

		g = abs(rcossin_tbl[item->item_flags[1]<<7]>>7);
		g += (GetRandomControl()&7);
		if (g>31)
			g = 31;
		b = g>>1;
		TriggerDynamic(trident[ICONPOS].x,trident[ICONPOS].y,trident[ICONPOS].z,10,0,g>>1,b>>1);
		item->item_flags[1]++;
		item->item_flags[1]&=63;
	}

// Knockback.
	if (item->hit_points > 0 && item->item_flags[0] != KNOCKBACK && lara_item->hit_points > 0)
	{
		long	dx,dy,dz;

		dx = lara_item->pos.x_pos-item->pos.x_pos;
		dy = lara_item->pos.y_pos-256-item->pos.y_pos;
		dz = lara_item->pos.z_pos-item->pos.z_pos;

		if (dx > 8000 || dx < -8000 || dy > 8000 || dy < -8000 || dz > 8000 || dz < -8000)
			dx = 0xFFF;
		else
		{
			dx = SQUARE(dx);
			dy = SQUARE(dy);
			dz = SQUARE(dz);
			dx = mSqrt(dx+dy+dz);
		}

		if (dx < 0xB00)
		{
			item->item_flags[0] = KNOCKBACK;
			for (lp=0;lp<3;lp++)
			{
				KBRings[lp].on = 1;
				KBRings[lp].life = 32;
				KBRings[lp].speed = 64 + ((lp==1)<<4);
				KBRings[lp].x = item->pos.x_pos;
				KBRings[lp].y = item->pos.y_pos - 384 - 128 + (lp<<7);
				KBRings[lp].z = item->pos.z_pos;
				KBRings[lp].xrot = KBRings[lp].zrot = 0;
				KBRings[lp].radius = 512 + ((lp==1)<<8);
			}
		}
	}
	else	if (KBRings[0].on == 0 && KBRings[1].on == 0 && KBRings[2].on == 0)
		item->item_flags[0] = 0;
}

void S_DrawLondonBoss(ITEM_INFO *item)
{
	DrawAnimatingItem(item);

	if (bossdata.explode_count)
	{
		DrawLondonBossShield(item);
		DrawExplosionRings();
	}
	else
	{
		DrawSummonRings();
		DrawKnockBackRings();
	}

	if (item->hit_points <= 0 && bossdata.explode_count == 0)
	{
		UpdateElectricityPoints();
		LaraElectricDeath(0,item);
		LaraElectricDeath(1,item);
	}

}

static void LondonBossDie(sint16 item_number)
{
	ITEM_INFO *item;

	item = &items[item_number];

	item->collidable = 0;
	item->hit_points = DONT_TARGET;
	KillItem(item_number);
	DisableBaddieAI(item_number);
	item->flags |= ONESHOT;	// Don't want bad guys triggered ever again
}

void InitialiseLondonBoss(sint16 item_number)
{
	SHIELD_POINTS	*shptr;
	long		lp,lp1,angle,y;

	bossdata.explode_count = bossdata.ring_count = bossdata.dead = bossdata.dropped_icon = 0;

// Initialise shield coordinates.

	shptr = &LondonBossShield[0];	// x,y,z,rgb.

	for (lp=0;lp<5;lp++)
	{
		y = heights[lp];
		angle = 0;
		for (lp1=0;lp1<8;lp1++)
		{
			shptr->x = (rcossin_tbl[angle<<1]*radii[lp])>>11;
			shptr->y = y;
			shptr->z = (rcossin_tbl[(angle<<1)+1]*radii[lp])>>11;
			shptr->rgb = 0;	// rgb.
			angle += 512;
			shptr++;
		}
	}
}



#ifdef PSX_VERSION

static void DrawLondonBossShield(ITEM_INFO *item)
{
	POLY_GT4	*polygt4;
	PSXSPRITESTRUCT* pSpriteInfo;
	SHIELD_POINTS	*shptr,*shptr2;
	short	*scrxy,*scrxy2;	// X,Y
	long		*scrz,*scrz2;	// Z
	short	*TempMesh;
	long		lp,u1,u2,v1,v2;

	mPushMatrix();

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

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

	shptr = &LondonBossShield[0];

	for (lp=0;lp<40;lp++)
	{
		long	r,g,b,rgb;

		TempMesh[0] = shptr->x;
		TempMesh[1] = shptr->y;
		TempMesh[2] = shptr->z;

		if (lp>=8 && lp<=31)
		{
			rgb = shptr->rgb;
			r = rgb & 255;
			g = (rgb >> 8) & 255;
			b = (rgb >> 16) & 255;

			if (rgb)
			{
			  	r -= shptr->rsub;
			  	g -= shptr->gsub;
			  	b -= shptr->bsub;
				if (r < 0)
					r = 0;
				if (g < 0)
					g = 0;
				if (b < 0)
					b = 0;

				shptr->rgb = r|(g<<8)|(b<<16);
			}
		}

		shptr++;

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

	shptr = &LondonBossShield[0];
	shptr2 = &LondonBossShield[8];

	scrxy = (short *) 0x1f800000;	// Set pointer to scratch pad.
	scrz = (long *) 0x1f800100;	// Set pointer to scratch pad.
	scrxy2 = (short *) 0x1f800020;	// Set pointer to scratch pad.
	scrz2 = (long *) 0x1f800120;	// Set pointer to scratch pad.

	polygt4 = (POLY_GT4 *)db.polyptr;

	for (lp=0;lp<4;lp++)
	{
	 	long		x1,x2,x3,x4,y1,y2,y3,y4,z1,z2,z3,z4,rgb1,rgb2,rgb3,rgb4,lp1;

		x1 = *scrxy++;
		y1 = *scrxy++;
		z1 = *scrz++;
		x3 = *scrxy2++;
		y3 = *scrxy2++;
		z3 = *scrz2++;
		rgb1 = shptr->rgb;
		rgb3 = shptr2->rgb;
		shptr++;
		shptr2++;

  		pSpriteInfo=psxspriteinfo+objects[EXPLOSION1].mesh_index+18+((lp+(wibble>>3))&7);
		u1 = pSpriteInfo->u1;
		u2 = pSpriteInfo->u2;
		v1 = pSpriteInfo->v1;
		v2 = pSpriteInfo->v2;

		for (lp1=0;lp1<8;lp1++)
		{
			if ((char *)polygt4 >= db.polybuf_limit)
			{
				db.polyptr = (char *)polygt4;
				mPopMatrix();
				return;
			}

			if (lp1 != 7)
			{
				x2 = *scrxy++;
				y2 = *scrxy++;
				z2 = *scrz++;
				x4 = *scrxy2++;
				y4 = *scrxy2++;
				z4 = *scrz2++;
				rgb2 = shptr->rgb;
				rgb4 = shptr2->rgb;
				shptr++;
				shptr2++;
			}
			else
			{
				x2 = *(scrxy-16);
				y2 = *(scrxy-15);
				z2 = *(scrz-8);
				x4 = *(scrxy2-16);
				y4 = *(scrxy2-15);
				z4 = *(scrz2-8);
				rgb2 = (shptr-8)->rgb;
				rgb4 = (shptr)->rgb;
			}

			z1 += z2+z3+z4;
			z1 >>= 5;

			if (z1>32 && z1 < 0xa00 && (rgb1 || rgb2 || rgb3 || rgb4))
			{
				setlen(polygt4, 12);
				setcode(polygt4, 0x3e);
				setRGB0(polygt4,rgb1&0xff,(rgb1>>8)&0xff,(rgb1>>16)&0xff);
				setRGB1(polygt4,rgb2&0xff,(rgb2>>8)&0xff,(rgb2>>16)&0xff);
				setRGB2(polygt4,rgb3&0xff,(rgb3>>8)&0xff,(rgb3>>16)&0xff);
				setRGB3(polygt4,rgb4&0xff,(rgb4>>8)&0xff,(rgb4>>16)&0xff);
				setUV4(polygt4,u1,v1,u2,v1,u1,v2,u2,v2);
				setXY4(polygt4,x1,y1,x2,y2,x3,y3,x4,y4);
				polygt4->clut = pSpriteInfo->clut;
				polygt4->tpage = (pSpriteInfo->tpage & (~96)) | (1<<5);	// Set colour addition.
				MyaddPrim(db.ot, z1, polygt4);
				polygt4++;
			}

			x1 = x2;
			y1 = y2;
			z1 = z2;
			x3 = x4;
			y3 = y4;
			z3 = z4;
			rgb1 = rgb2;
			rgb3 = rgb4;
		}
	}

	db.polyptr = (uchar *) polygt4;
	mPopMatrix();
}

#endif

static void ExplodeLondonBoss(ITEM_INFO *item)
{
	SHIELD_POINTS	*shptr;
	long		lp,lp1,angle,y,r,g,b;

	if (	bossdata.explode_count == 1 ||
		bossdata.explode_count == 15 ||
		bossdata.explode_count == 25 ||
		bossdata.explode_count == 35 ||
		bossdata.explode_count == 45 ||
		bossdata.explode_count == 55)	// Trigger explosion & ring ?
	{
		long x,y,z;

		x = item->pos.x_pos + ((GetRandomDraw()&1023)-512);
		y = item->pos.y_pos - (GetRandomDraw()&1023) - 256;
		z = item->pos.z_pos + ((GetRandomDraw()&1023)-512);

		ExpRings[bossdata.ring_count].x = x;
		ExpRings[bossdata.ring_count].y = y;
		ExpRings[bossdata.ring_count].z = z;
		ExpRings[bossdata.ring_count].on = 1;
		bossdata.ring_count++;

		TriggerExplosionSparks(x, y, z,3,-2,2,0);	// -2 = Set off a dynamic light controller.
		for (lp=0;lp<2;lp++)
			TriggerExplosionSparks(x, y, z,3,-1,2, 0);

	    	SoundEffect( 76, &item->pos,PITCH_SHIFT|0x800000);
	}

// Adjust shield coordinates.

	for (lp=0;lp<5;lp++)
	{
		if (bossdata.explode_count < 128)	// Expand.
		{
			death_radii[lp] = (dradii[lp] >> 4) + (((dradii[lp]) * bossdata.explode_count) >> 7);
			death_heights[lp] = dheights2[lp] + (((dheights1[lp]-dheights2[lp]) * bossdata.explode_count) >> 7);
		}
	}

// Setup shield coordinates.

	shptr = &LondonBossShield[0];	// x,y,z,rgb.

	for (lp=0;lp<5;lp++)
	{
		y = death_heights[lp];
		angle = (wibble<<3) & 511;
		for (lp1=0;lp1<8;lp1++)
		{
			shptr->x = (rcossin_tbl[angle<<1] * death_radii[lp])>>11;
			shptr->y = y;
			shptr->z = (rcossin_tbl[(angle<<1)+1] * death_radii[lp])>>11;

			if (lp!=0 && lp!=4 && bossdata.explode_count < 64)
			{
				g = (GetRandomDraw()&31)+224;
				b = (g>>2) + (GetRandomDraw()&63);
				r = (GetRandomDraw()&63);
				r = (r * (64 - bossdata.explode_count)) >> 6;
				g = (g * (64 - bossdata.explode_count)) >> 6;
				b = (b * (64 - bossdata.explode_count)) >> 6;
				shptr->rgb = r|(g<<8)|(b<<16);
			}
			else
				shptr->rgb = 0;
			angle += 512;
			angle &= 4095;
			shptr++;
		}
	}
}

#ifdef PSX_VERSION

static void DrawExplosionRings()
{
	PSXSPRITESTRUCT	*pSpriteInfo;
	EXPLOSION_VERTS	*evptr,*evptr2;
	EXPLOSION_RING		*erptr;
	POLY_GT4			*polygt4;
	long		rlp,lp,lp1;
	long		angle,rad,r,g,b;
	short	*scrxy,*scrxy2;	// X,Y
	long		*scrz,*scrz2;	// Z
	short	*TempMesh;
	long		u1,u2,v1,v2;

	for (rlp=0;rlp<6;rlp++)
	{
		erptr = &ExpRings[rlp];
		if (erptr->on == 0)
		{
			erptr++;
			continue;
		}

		erptr->life--;
		if (erptr->life == 0)
		{
			erptr->on = 0;
			erptr++;
			continue;
		}

		mPushMatrix();

//		SetRotMatrix((MATRIX*) Matrix);
//		SetTransMatrix((MATRIX*) Matrix);
//		mTranslateXYZ(erptr->x,erptr->y,erptr->z);
		mTranslateAbsXYZ(erptr->x,erptr->y,erptr->z);
		mRotZ(erptr->zrot);
		mRotX(erptr->xrot);
//		SetRotMatrix((MATRIX*) Matrix);
//		SetTransMatrix((MATRIX*) Matrix);

		erptr->radius += erptr->speed;
//		erptr->speed += 8;

		evptr = &erptr->verts[0];
		rad = erptr->radius;	// Outer ring.

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

		for (lp=0;lp<2;lp++)
		{
			angle = (wibble<<3) & 511;

			for (lp1=0;lp1<8;lp1++)
			{
				evptr->x = (rcossin_tbl[angle<<1] * rad) >> 12;
				evptr->z = (rcossin_tbl[(angle<<1)+1] * rad) >> 12;

				g = (GetRandomDraw()&31)+224;
				b = (g>>2) + (GetRandomDraw()&63);
				r = (GetRandomDraw()&63);
				r = (r * erptr->life) >> 5;
				g = (g * erptr->life) >> 5;
				b = (b * erptr->life) >> 5;
				evptr->rgb = r|(g<<8)|(b<<16);

				angle += 512;
				angle &= 4095;

				TempMesh[0] = evptr->x;
				TempMesh[1] = 0;
				TempMesh[2] = evptr->z;

				gte_ldv0(TempMesh);
				gte_rtps();
				gte_stsxy(scrxy);
				gte_stsz(scrz);
				scrxy+=2;
				scrz++;
				evptr++;
			}
			//rad -= rad>>2;		// Inner ring.
			rad >>= 1;		// Inner ring.
		}

		evptr = &erptr->verts[0];
		evptr2 = &erptr->verts[8];

		scrxy = (short *) 0x1f800000;		// Set pointer to scratch pad.
		scrz = (long *) 0x1f800100;		// Set pointer to scratch pad.
		scrxy2 = (short *) 0x1f800020;	// Set pointer to scratch pad.
		scrz2 = (long *) 0x1f800120;		// Set pointer to scratch pad.

		polygt4 = (POLY_GT4 *)db.polyptr;

//		for (lp=0;lp<4;lp++)
		{
	 		long		x1,x2,x3,x4,y1,y2,y3,y4,z1,z2,z3,z4,rgb1,rgb2,rgb3,rgb4;

			x1 = *scrxy++;
			y1 = *scrxy++;
			z1 = *scrz++;
			x3 = *scrxy2++;
			y3 = *scrxy2++;
			z3 = *scrz2++;
			rgb1 = evptr->rgb;
			rgb3 = evptr2->rgb;
			evptr++;
			evptr2++;

			pSpriteInfo=psxspriteinfo+objects[EXPLOSION1].mesh_index+4+((wibble>>4)&3);
			u1 = pSpriteInfo->u1;
			u2 = pSpriteInfo->u2;
			v1 = pSpriteInfo->v1;
			v2 = pSpriteInfo->v2;

			for (lp1=0;lp1<8;lp1++)
			{
				if ((char *)polygt4 >= db.polybuf_limit)
				{
					db.polyptr = (char *)polygt4;
					mPopMatrix();
					return;
				}

				if (lp1 != 7)
				{
					x2 = *scrxy++;
					y2 = *scrxy++;
					z2 = *scrz++;
					x4 = *scrxy2++;
					y4 = *scrxy2++;
					z4 = *scrz2++;
					rgb2 = evptr->rgb;
					rgb4 = evptr2->rgb;
					evptr++;
					evptr2++;
				}
				else
				{
					x2 = *(scrxy-16);
					y2 = *(scrxy-15);
					z2 = *(scrz-8);
					x4 = *(scrxy2-16);
					y4 = *(scrxy2-15);
					z4 = *(scrz2-8);
					rgb2 = (evptr-8)->rgb;
					rgb4 = (evptr)->rgb;
				}

				z1 += z2+z3+z4;
				z1 >>= 5;

				if (z1>32 && z1 < 0xa00 && (rgb1 || rgb2 || rgb3 || rgb4))
				{
					setlen(polygt4, 12);
					setcode(polygt4, 0x3e);
					setRGB0(polygt4,rgb1&0xff,(rgb1>>8)&0xff,(rgb1>>16)&0xff);
					setRGB1(polygt4,rgb2&0xff,(rgb2>>8)&0xff,(rgb2>>16)&0xff);
					setRGB2(polygt4,rgb3&0xff,(rgb3>>8)&0xff,(rgb3>>16)&0xff);
					setRGB3(polygt4,rgb4&0xff,(rgb4>>8)&0xff,(rgb4>>16)&0xff);
					setUV4(polygt4,u1,v1,u2,v1,u1,v2,u2,v2);
					setXY4(polygt4,x1,y1,x2,y2,x3,y3,x4,y4);
					polygt4->clut = pSpriteInfo->clut;
					polygt4->tpage = (pSpriteInfo->tpage & (~96)) | (1<<5);	// Set colour addition.
					MyaddPrim(db.ot, z1, polygt4);
					polygt4++;
				}

				x1 = x2;
				y1 = y2;
				z1 = z2;
				x3 = x4;
				y3 = y4;
				z3 = z4;
				rgb1 = rgb2;
				rgb3 = rgb4;
			}
		}

		db.polyptr = (uchar *) polygt4;
		mPopMatrix();
	}
}

#endif

#ifdef PSX_VERSION

static void DrawSummonRings()
{
	EXPLOSION_VERTS	*evptr,*evptr2;
	EXPLOSION_RING		*erptr;
	POLY_G4			*polyg4;
	long		rlp,lp,lp1;
	long		angle,rad,r,g,b;
	short	*scrxy,*scrxy2;	// X,Y
	long		*scrz,*scrz2;	// Z
	short	*TempMesh;
	long		fade;

	for (rlp=0;rlp<6;rlp++)
	{
		erptr = &ExpRings[rlp];
		if (erptr->on == 0)
		{
			erptr++;
			continue;
		}

		erptr->life--;
		erptr->radius -= erptr->speed;

		if (erptr->life == 0 || erptr->radius <= 0)
		{
			erptr->on = 0;
			erptr++;
			continue;
		}

		erptr->speed += 2;

		mPushMatrix();

//		SetRotMatrix((MATRIX*) Matrix);
//		SetTransMatrix((MATRIX*) Matrix);
//		mTranslateXYZ(erptr->x,erptr->y,erptr->z);
		mTranslateAbsXYZ(erptr->x,erptr->y,erptr->z);
		mRotZ(erptr->zrot);
		mRotX(erptr->xrot);
//		SetRotMatrix((MATRIX*) Matrix);
//		SetTransMatrix((MATRIX*) Matrix);

		evptr = &erptr->verts[0];
		rad = erptr->radius;	// Outer ring.

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

		if (erptr->life > 32)
			fade = (64-erptr->life)<<1;
		else if (erptr->life < 8)
			fade = erptr->life<<3;
		else
			fade = 64;

		for (lp=0;lp<2;lp++)
		{
			angle = (wibble<<3) & 511;
			for (lp1=0;lp1<8;lp1++)
			{
				evptr->x = (rcossin_tbl[angle<<1] * rad) >> 12;
				evptr->z = (rcossin_tbl[(angle<<1)+1] * rad) >> 12;

				if (lp==0)
				{
					g = (GetRandomDraw()&31)+224;
					b = (g>>2) + (GetRandomDraw()&63);
					r = (GetRandomDraw()&63);
					r = (r * fade) >> 7;
					g = (g * fade) >> 7;
					b = (b * fade) >> 7;
					evptr->rgb = r|(g<<8)|(b<<16);
				}
				else
					evptr->rgb = 0;

				angle += 512;
				angle &= 4095;

				TempMesh[0] = evptr->x;
				TempMesh[1] = 0;
				TempMesh[2] = evptr->z;

				gte_ldv0(TempMesh);
				gte_rtps();
				gte_stsxy(scrxy);
				gte_stsz(scrz);
				scrxy+=2;
				scrz++;
				evptr++;
			}
			rad >>= 1;		// Inner ring.
		}

		evptr = &erptr->verts[0];
		evptr2 = &erptr->verts[8];

		scrxy = (short *) 0x1f800000;		// Set pointer to scratch pad.
		scrz = (long *) 0x1f800100;		// Set pointer to scratch pad.
		scrxy2 = (short *) 0x1f800020;	// Set pointer to scratch pad.
		scrz2 = (long *) 0x1f800120;		// Set pointer to scratch pad.

		{
	 		long		x1,x2,x3,x4,y1,y2,y3,y4,z1,z2,z3,z4,rgb1,rgb2,rgb3,rgb4;

			x1 = *scrxy++;
			y1 = *scrxy++;
			z1 = *scrz++;
			x3 = *scrxy2++;
			y3 = *scrxy2++;
			z3 = *scrz2++;
			rgb1 = evptr->rgb;
			rgb3 = evptr2->rgb;
			evptr++;
			evptr2++;

			for (lp1=0;lp1<8;lp1++)
			{
				if ((char *)db.polyptr >= db.polybuf_limit)
				{
					mPopMatrix();
					return;
				}

				if (lp1 != 7)
				{
					x2 = *scrxy++;
					y2 = *scrxy++;
					z2 = *scrz++;
					x4 = *scrxy2++;
					y4 = *scrxy2++;
					z4 = *scrz2++;
					rgb2 = evptr->rgb;
					rgb4 = evptr2->rgb;
					evptr++;
					evptr2++;
				}
				else
				{
					x2 = *(scrxy-16);
					y2 = *(scrxy-15);
					z2 = *(scrz-8);
					x4 = *(scrxy2-16);
					y4 = *(scrxy2-15);
					z4 = *(scrz2-8);
					rgb2 = (evptr-8)->rgb;
					rgb4 = (evptr)->rgb;
				}

				z1 += z2+z3+z4;
				z1 >>= 5;

				if (z1>32 && z1 < 0xa00 && (rgb1 || rgb2 || rgb3 || rgb4))
				{
  					polyg4 = (POLY_G4 *)db.polyptr;
					setlen(polyg4, 8);
					setcode(polyg4, 0x3a);
					setRGB0(polyg4,rgb1&0xff,(rgb1>>8)&0xff,(rgb1>>16)&0xff);
					setRGB1(polyg4,rgb2&0xff,(rgb2>>8)&0xff,(rgb2>>16)&0xff);
					setRGB2(polyg4,rgb3&0xff,(rgb3>>8)&0xff,(rgb3>>16)&0xff);
					setRGB3(polyg4,rgb4&0xff,(rgb4>>8)&0xff,(rgb4>>16)&0xff);
					setXY4(polyg4,x1,y1,x2,y2,x3,y3,x4,y4);

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

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

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

				x1 = x2;
				y1 = y2;
				z1 = z2;
				x3 = x4;
				y3 = y4;
				z3 = z4;
				rgb1 = rgb2;
				rgb3 = rgb4;
			}
		}

		mPopMatrix();
	}
}

#endif

#ifdef PSX_VERSION

static void DrawKnockBackRings()
{
	PSXSPRITESTRUCT	*pSpriteInfo;
	EXPLOSION_VERTS	*evptr,*evptr2;
	EXPLOSION_RING		*erptr;
	POLY_GT4			*polygt4;
	long		rlp,lp,lp1;
	long		angle,rad,r,g,b,hitlara;
	short	*scrxy,*scrxy2;	// X,Y
	long		*scrz,*scrz2;	// Z
	short	*TempMesh;
	long		u1,u2,v1,v2,fade;

	if (KBRings[1].speed >= 0)
	{
		hitlara = KnockBackCollision(&KBRings[1]);
		if (hitlara)
		{
			for (rlp=0;rlp<3;rlp++)
				KBRings[rlp].speed = -KBRings[rlp].speed>>2;
		}
	}

	for (rlp=0;rlp<3;rlp++)
	{
		erptr = &KBRings[rlp];
		if (erptr->on == 0)
		{
			erptr++;
			continue;
		}

		erptr->life--;
		if (erptr->life == 0)
		{
			erptr->on = 0;
			erptr++;
			continue;
		}

		mPushMatrix();
//		SetRotMatrix((MATRIX*) Matrix);
//		SetTransMatrix((MATRIX*) Matrix);
		mTranslateAbsXYZ(erptr->x,erptr->y,erptr->z);
//		SetRotMatrix((MATRIX*) Matrix);
//		SetTransMatrix((MATRIX*) Matrix);

		erptr->radius += erptr->speed;
		if (erptr->speed < 0)
			erptr->speed--;
		else
			erptr->speed += (rlp==1) ? 3 : 2;

		evptr = &erptr->verts[0];
		rad = erptr->radius;	// Outer ring.

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

		if (erptr->life > 24)
			fade = (32-erptr->life)<<2;
		else if (erptr->life < 16)
			fade = erptr->life<<1;
		else
			fade = 32;

		for (lp=0;lp<2;lp++)
		{
			angle = (wibble<<3) & 511;

			for (lp1=0;lp1<8;lp1++)
			{
				evptr->x = (rcossin_tbl[angle<<1] * rad) >> 12;
				evptr->z = (rcossin_tbl[(angle<<1)+1] * rad) >> 12;

				g = (GetRandomDraw()&31)+224;
				b = (g>>2) + (GetRandomDraw()&63);
				r = (GetRandomDraw()&63);
				r = (r * fade) >> 5;
				g = (g * fade) >> 5;
				b = (b * fade) >> 5;
				evptr->rgb = r|(g<<8)|(b<<16);

				angle += 512;
				angle &= 4095;

				TempMesh[0] = evptr->x;
				TempMesh[1] = 0;
				TempMesh[2] = evptr->z;

				gte_ldv0(TempMesh);
				gte_rtps();
				gte_stsxy(scrxy);
				gte_stsz(scrz);
				scrxy+=2;
				scrz++;
				evptr++;
			}
			rad >>= 1;		// Inner ring.
			fade>>=1;
		}

		evptr = &erptr->verts[0];
		evptr2 = &erptr->verts[8];

		scrxy = (short *) 0x1f800000;		// Set pointer to scratch pad.
		scrz = (long *) 0x1f800100;		// Set pointer to scratch pad.
		scrxy2 = (short *) 0x1f800020;	// Set pointer to scratch pad.
		scrz2 = (long *) 0x1f800120;		// Set pointer to scratch pad.

		polygt4 = (POLY_GT4 *)db.polyptr;

		{
	 		long		x1,x2,x3,x4,y1,y2,y3,y4,z1,z2,z3,z4,rgb1,rgb2,rgb3,rgb4;

			x1 = *scrxy++;
			y1 = *scrxy++;
			z1 = *scrz++;
			x3 = *scrxy2++;
			y3 = *scrxy2++;
			z3 = *scrz2++;
			rgb1 = evptr->rgb;
			rgb3 = evptr2->rgb;
			evptr++;
			evptr2++;

			pSpriteInfo=psxspriteinfo+objects[EXPLOSION1].mesh_index+4+((wibble>>4)&3);
			u1 = pSpriteInfo->u1;
			u2 = pSpriteInfo->u2;
			v1 = pSpriteInfo->v1;
			v2 = pSpriteInfo->v2;

			for (lp1=0;lp1<8;lp1++)
			{
				if ((char *)polygt4 >= db.polybuf_limit)
				{
					db.polyptr = (char *)polygt4;
					mPopMatrix();
					return;
				}

				if (lp1 != 7)
				{
					x2 = *scrxy++;
					y2 = *scrxy++;
					z2 = *scrz++;
					x4 = *scrxy2++;
					y4 = *scrxy2++;
					z4 = *scrz2++;
					rgb2 = evptr->rgb;
					rgb4 = evptr2->rgb;
					evptr++;
					evptr2++;
				}
				else
				{
					x2 = *(scrxy-16);
					y2 = *(scrxy-15);
					z2 = *(scrz-8);
					x4 = *(scrxy2-16);
					y4 = *(scrxy2-15);
					z4 = *(scrz2-8);
					rgb2 = (evptr-8)->rgb;
					rgb4 = (evptr)->rgb;
				}

				z1 += z2+z3+z4;
				z1 >>= 5;

				if (z1>32 && z1 < 0xa00 && (rgb1 || rgb2 || rgb3 || rgb4))
				{
					setlen(polygt4, 12);
					setcode(polygt4, 0x3e);
					setRGB0(polygt4,rgb1&0xff,(rgb1>>8)&0xff,(rgb1>>16)&0xff);
					setRGB1(polygt4,rgb2&0xff,(rgb2>>8)&0xff,(rgb2>>16)&0xff);
					setRGB2(polygt4,rgb3&0xff,(rgb3>>8)&0xff,(rgb3>>16)&0xff);
					setRGB3(polygt4,rgb4&0xff,(rgb4>>8)&0xff,(rgb4>>16)&0xff);
					setUV4(polygt4,u1,v1,u2,v1,u1,v2,u2,v2);
					setXY4(polygt4,x1,y1,x2,y2,x3,y3,x4,y4);
					polygt4->clut = pSpriteInfo->clut;
					polygt4->tpage = (pSpriteInfo->tpage & (~96)) | (1<<5);	// Set colour addition.
					MyaddPrim(db.ot, z1, polygt4);
					polygt4++;
				}

				x1 = x2;
				y1 = y2;
				z1 = z2;
				x3 = x4;
				y3 = y4;
				z3 = z4;
				rgb1 = rgb2;
				rgb3 = rgb4;
			}
		}

		db.polyptr = (uchar *) polygt4;
		mPopMatrix();
	}
}

#endif

static void TriggerLaserBolt(PHD_VECTOR *pos, ITEM_INFO *item, long type, sint16 yang)
{
	sint16 item_number;
	ITEM_INFO *bolt_item;
	PHD_ANGLE		angles[2];

	/* Create a laser bolt and launch it on its way */
	item_number = CreateItem();
	if (item_number != NO_ITEM)
	{
		bolt_item = &items[item_number];

		bolt_item->object_number = EXTRAFX4;
		bolt_item->room_number = item->room_number;
		bolt_item->pos.x_pos = pos->x;
		bolt_item->pos.y_pos = pos->y;
		bolt_item->pos.z_pos = pos->z;

		InitialiseItem(item_number);

		if (type==SUMMON_BOLT)
		{

			bolt_item->pos.y_pos += item->pos.y_pos-384;
			bolt_item->pos.x_rot = -pos->y<<5;
			bolt_item->pos.y_rot = GetRandomControl()<<1;
		}
		else
		{
		  	phd_GetVectorAngles( lara_item->pos.x_pos-pos->x,lara_item->pos.y_pos-256-pos->y,lara_item->pos.z_pos-pos->z, angles );
			bolt_item->pos.x_rot = angles[1];
			bolt_item->pos.y_rot = yang;
			bolt_item->pos.z_rot = 0;
		}

		if (type != LARGE_BOLT)
		{
			bolt_item->speed = 16;
			bolt_item->item_flags[0] = -24;
			bolt_item->item_flags[1] = 4;
			if (type == SUMMON_BOLT)
				bolt_item->item_flags[2] = 1;	// Set the 'just draw front and back' flag.
		}
		else
		{
			bolt_item->speed = 24;
			bolt_item->item_flags[0] = 31;
	 		bolt_item->item_flags[1] = 16;
		}

		AddActiveItem(item_number);

//		SoundEffect(105, &lara_item->pos, PITCH_SHIFT|0x2000000);
	}
}

void ControlLaserBolts(sint16 item_number)
{
	FLOOR_INFO 	*floor;
	ITEM_INFO 	*item;
	PHD_VECTOR	oldpos;
	long		g,b,rad;
	sint16 	room_number,oldroom,hitlara;
	sint32 	speed;

	item = &items[item_number];

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

	speed = (item->speed * phd_cos(item->pos.x_rot)) >> W2V_SHIFT;
	item->pos.z_pos += (speed * phd_cos(item->pos.y_rot)) >> W2V_SHIFT;
	item->pos.x_pos += (speed * phd_sin(item->pos.y_rot)) >> W2V_SHIFT;
	item->pos.y_pos += -((item->speed * phd_sin(item->pos.x_rot)) >> W2V_SHIFT);
	if (item->speed < BOLT_SPEED)
		item->speed += (item->speed>>3)+2;

	if (item->item_flags[2] && item->speed > (BOLT_SPEED>>1))
	{
		item->item_flags[3]++;
		if (item->item_flags[3] >= 16)
		{
			KillItem(item_number);
			return;
		}
	}

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

	if (item->room_number != room_number)
		ItemNewRoom(item_number, room_number);

	if (!item->item_flags[2])
	{
		if (ItemNearLara(&item->pos, 400))
			hitlara = 1;
		else
			hitlara = 0;

		item->floor = GetHeight(floor, item->pos.x_pos, item->pos.y_pos, item->pos.z_pos);
		if (hitlara || item->pos.y_pos >= item->floor || item->pos.y_pos <= GetCeiling(floor, item->pos.x_pos, item->pos.y_pos, item->pos.z_pos))
		{
			SoundEffect(105, &item->pos, 0);
			if (item->item_flags[0] < 0)
				rad = 2;
			else
				rad = 3;
			TriggerExplosionSparks(oldpos.x, oldpos.y, oldpos.z, rad,-2,2,item->room_number);	// -2 = Set off a dynamic light controller.
			for (g=0;g<rad;g++)
				TriggerExplosionSparks(oldpos.x, oldpos.y, oldpos.z, 2,-1,2,item->room_number);
			rad++;
			for (g=0;g<rad;g++)
				TriggerPlasmaBall(item, 1, &oldpos, oldroom, item->pos.y_rot);

			if (hitlara)
			{
				lara_item->hit_points -= 30+((item->item_flags[0]>=0)<<9);
				lara_item->hit_status = 1;
			}
			else
			{
				long	dx,dy,dz;
				dx = SQUARE(lara_item->pos.x_pos-item->pos.x_pos);
				dy = SQUARE(lara_item->pos.y_pos-256-item->pos.y_pos);
				dz = SQUARE(lara_item->pos.z_pos-item->pos.z_pos);
				dx = mSqrt(dx+dy+dz);
				if (dx < 0x400)
				{
					lara_item->hit_points -= (0x400-dx)>>(6-((item->item_flags[0]>=0)<<1));
					lara_item->hit_status = 1;
				}
			}

			KillItem(item_number);
			return;
		}
	}
	g = 31-(GetRandomControl()&7);
	b = g>>1;

	if (item->item_flags[0] < 0)	// Small bolt?
	{
		if (item->item_flags[2])
		{
			g = (g*(16-item->item_flags[3]))>>4;
			b = (b*(16-item->item_flags[3]))>>4;
		}

		rad = -item->item_flags[0];
		if (rad > SMALL_FLASH)
		{
			item->item_flags[0]++;
			item->item_flags[1]+=2;
		}
	}
	else
	{
		rad = item->item_flags[0];
		if (rad > BIG_FLASH)
		{
			item->item_flags[0]--;
			item->item_flags[1]+=4;
		}
	}

	TriggerDynamic(item->pos.x_pos,item->pos.y_pos,item->pos.z_pos,rad,0,g,b);

//	item->pos.y_rot-=256;
}

static void TriggerPlasmaBallFlame(sint16 fx_number, long type, long xv, long yv, long zv)
{
	SPARKS	*sptr;
	long		size;
	long		dx,dz;

	dx = lara_item->pos.x_pos - effects[fx_number].pos.x_pos;
	dz = lara_item->pos.z_pos - effects[fx_number].pos.z_pos;

	if (dx < -MAX_TRIGGER_RANGE || dx > MAX_TRIGGER_RANGE || dz < -MAX_TRIGGER_RANGE || dz > MAX_TRIGGER_RANGE)
		return;

	sptr = &spark[GetFreeSpark()];

	sptr->On = 1;
	sptr->sG = 255;
	sptr->sB = 48+(GetRandomControl()&31);
	sptr->sR = 48;

	sptr->dG = 192+(GetRandomControl()&63);
	sptr->dB = 128+(GetRandomControl()&63);
	sptr->dR = 32;

	sptr->ColFadeSpeed = 12 + (GetRandomControl()&3);
	sptr->FadeToBlack = 8;
	sptr->sLife = sptr->Life = (GetRandomControl()&7)+24;

	sptr->TransType = COLADD;

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

	sptr->x = ((GetRandomControl()&15)-8);
	sptr->y = 0;
	sptr->z = ((GetRandomControl()&15)-8);

	sptr->Xvel = xv + (GetRandomControl()&255) - 128;
	sptr->Yvel = yv;
	sptr->Zvel = zv + (GetRandomControl()&255) - 128;
	sptr->Friction = 5;

	if (GetRandomControl()&1)
	{
		sptr->Flags = SP_SCALE|SP_DEF|SP_ROTATE|SP_EXPDEF|SP_FX;
		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|SP_FX;
	}

	sptr->FxObj = fx_number;

	sptr->Def = objects[EXPLOSION1].mesh_index;
	sptr->Scalar = 1;
	size = (GetRandomControl()&31)+64;
	sptr->Width = sptr->sWidth = size;
 	sptr->Height = sptr->sHeight = size;
	sptr->dWidth = size>>2;
	sptr->dHeight = size>>2;
	sptr->Gravity = sptr->MaxYvel = 0;
}

static void TriggerPlasmaBall(ITEM_INFO *item, long type, PHD_VECTOR *pos1, sint16 room_number, sint16 angle)
{
	FX_INFO 	*fx;
	PHD_VECTOR pos;
	sint16 	fx_number,angles[2];
	long		speed;

	pos.x = pos1->x;
	pos.y = pos1->y;
	pos.z = pos1->z;
	speed = (GetRandomControl()&31)+64;
	angles[0] = angle + GetRandomControl() + 0x4000;
	angles[1] = 0x2000;

	fx_number = CreateEffect(room_number);
	if (fx_number != NO_ITEM)
	{
		fx = &effects[fx_number];
		fx->pos.x_pos = pos.x;
		fx->pos.y_pos = pos.y;
		fx->pos.z_pos = pos.z;
		fx->pos.y_rot = angles[0];
		fx->pos.x_rot = angles[1];
		fx->object_number = EXTRAFX5;
		fx->speed = speed;
		fx->fallspeed = 0;
		fx->flag1 = 1;
		if (type == 2)
			fx->flag2 = 1;
		else
			fx->flag2 = 0;

	}
}

void ControlLondBossPlasmaBall(sint16 fx_number)
{
	FX_INFO	*fx;
	FLOOR_INFO	*floor;
	long		speed;
	long		rnd,r,g,b,old_y;
	uchar 	radtab[2] = {	13,7	};
	sint16 	room_number;

	fx = &effects[fx_number];

	old_y = fx->pos.y_pos;

	fx->fallspeed++;
	if (fx->speed >8)
		fx->speed -= 2;
	if (fx->pos.x_rot > -0x3c00)
		fx->pos.x_rot-=0x100;

	speed = (fx->speed * phd_cos(fx->pos.x_rot)) >> W2V_SHIFT;
	fx->pos.z_pos += (speed * phd_cos(fx->pos.y_rot)) >> W2V_SHIFT;
	fx->pos.x_pos += (speed * phd_sin(fx->pos.y_rot)) >> W2V_SHIFT;
	fx->pos.y_pos += -((fx->speed * phd_sin(fx->pos.x_rot)) >> W2V_SHIFT) + fx->fallspeed;
	if ((wibble&15) == 0)
		TriggerPlasmaBallFlame(fx_number,0,0,abs(old_y-fx->pos.y_pos)<<3,0);

	room_number = fx->room_number;
	floor = GetFloor(fx->pos.x_pos, fx->pos.y_pos, fx->pos.z_pos, &room_number);
	if (fx->pos.y_pos >= GetHeight(floor, fx->pos.x_pos, fx->pos.y_pos, fx->pos.z_pos) ||
		 fx->pos.y_pos < GetCeiling(floor, fx->pos.x_pos, fx->pos.y_pos, fx->pos.z_pos))
	{
		KillEffect(fx_number);
		return;
	}

	if (room[room_number].flags & UNDERWATER)
	{
		KillEffect(fx_number);
		return;
	}

	if (!fx->flag2 && ItemNearLara(&fx->pos, 200))
	{
		lara_item->hit_points -= 25;
		lara_item->hit_status = 1;
		KillEffect(fx_number);
		return;
	}

	if (room_number != fx->room_number)
		EffectNewRoom(fx_number, lara_item->room_number);

	rnd = GetRandomControl();
	g=31-((rnd>>4)&3);
	b=24-((rnd>>6)&3);
	r=rnd&7;
	TriggerDynamic(fx->pos.x_pos,fx->pos.y_pos,fx->pos.z_pos,radtab[fx->flag1],r,g,b);
}

#ifdef	PSX_VERSION

void S_DrawLaserBolts(ITEM_INFO *item)
{
//	PHD_VECTOR	pos;
	SVECTOR3D	rad[4],sub[4];
	POLY_G4 	*polyg4;
	short	*scrxy;	// X,Y
	long		*scrz;	// Z
	short	*TempMesh;
	uchar	*rgbs;
	long		lp,lp2,face,speed,dx,dy,dz,dx2,dy2,dz2;
	long		x1,x2,x3,x4,y1,y2,y3,y4,z1,z2,z3,z4,rgb12,rgb34;
	char		*linkptr;

	speed = (item->speed * phd_cos(item->pos.x_rot)) >> W2V_SHIFT;
	dz = (speed * phd_cos(item->pos.y_rot)) >> W2V_SHIFT;
	dx = (speed * phd_sin(item->pos.y_rot)) >> W2V_SHIFT;
	dy = -((item->speed * phd_sin(item->pos.x_rot)) >> W2V_SHIFT);

	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.

	mPushUnitMatrix();
	mRotYXZ(item->pos.y_rot, item->pos.x_rot, item->pos.z_rot);

	TempMesh[2] = 0;	// Set Z to 0.

	dx2 = (item->item_flags[2]) ? (item->item_flags[1]>>1) : item->item_flags[1];

	for (lp=0;lp<4;lp++)
	{
		if (lp == 0)
		{
			TempMesh[0] = -dx2;
			TempMesh[1] = -dx2;
		}
		else	if (lp == 1)
		{
			TempMesh[0] = dx2;
			TempMesh[1] = -dx2;
		}
		else	if (lp == 2)
		{
			TempMesh[0] = dx2;
			TempMesh[1] = dx2;
		}
		else
		{
			TempMesh[0] = -dx2;
			TempMesh[1] = dx2;
		}

		gte_ldv0(TempMesh);
		gte_rtv0();
		gte_stsv(&rad[lp]);
		sub[lp].x = rad[lp].x>>2;
		sub[lp].y = rad[lp].y>>2;
		sub[lp].z = rad[lp].z>>2;
	}

	mPopMatrix();

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

	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.

// Calc front of bolt.

	x1 = item->pos.x_pos - lara_item->pos.x_pos;
	y1 = item->pos.y_pos - lara_item->pos.y_pos;
	z1 = item->pos.z_pos - 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++;
	*rgbs++ = 128;	// Don't want to see the start of the first one.

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

	dx2 = dx<<1;
	dy2 = dy<<1;
	dz2 = dz<<1;
	for ( lp=0 ; lp < 4; lp++)
	{
		for (lp2=0;lp2<4;lp2++)
		{
			TempMesh[0] = item->pos.x_pos - lara_item->pos.x_pos + rad[lp2].x - dx2;
			TempMesh[1] = item->pos.y_pos - lara_item->pos.y_pos + rad[lp2].y - dy2;
			TempMesh[2] = item->pos.z_pos - lara_item->pos.z_pos + rad[lp2].z - dz2;
			gte_ldv0(TempMesh);
			gte_rtps();
			rad[lp2].x -= sub[lp2].x;
			rad[lp2].y -= sub[lp2].y;
			rad[lp2].z -= sub[lp2].z;
			gte_stsxy(scrxy);
			gte_stsz(scrz);
			scrxy+=2;
			scrz++;
		}
		*rgbs++ = 64-(lp<<4);
		dx2 += dx;
		dy2 += dy;
		dz2 += dz;
		if (lp==0 && item->item_flags[2])	// Summon bolt?
		{
			dx2 += dx<<1;
			dy2 += dy<<1;
			dz2 += dz<<1;
			break;
		}
	}

// Calc last point of bolt.

	TempMesh[0] = item->pos.x_pos - lara_item->pos.x_pos - dx2;
	TempMesh[1] = item->pos.y_pos - lara_item->pos.y_pos - dy2;
	TempMesh[2] = item->pos.z_pos - lara_item->pos.z_pos - dz2;

	gte_ldv0(TempMesh);
	gte_rtps();
	gte_stsxy(scrxy);
	gte_stsz(scrz);
	*rgbs++ = 0;	// Don't want to see the end.

	linkptr = (item->item_flags[2]) ? &sumlinks[0][0] : &links[0][0];

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

	face = 0;
	rgb12 = *rgbs++;

	if (item->item_flags[2])
		rgb12 = (rgb12*(16-item->item_flags[3]))>>4;

	for (lp=0;lp<5;lp++)
	{
		rgb34 = *rgbs++;
		if (item->item_flags[2])
			rgb34 = (rgb34*(16-item->item_flags[3]))>>4;

		for (lp2=0;lp2<4;lp2++)
		{
			x1 = *(scrxy+(*(linkptr+(face<<2)+1)<<1));
			y1 = *(scrxy+(*(linkptr+(face<<2)+1)<<1)+1);
			z1 = *(scrz+(*(linkptr+(face<<2)+1)));
			x2 = *(scrxy+(*(linkptr+(face<<2)+0)<<1));
			y2 = *(scrxy+(*(linkptr+(face<<2)+0)<<1)+1);
			z2 = *(scrz+(*(linkptr+(face<<2)+0)));
			x3 = *(scrxy+(*(linkptr+(face<<2)+3)<<1));
			y3 = *(scrxy+(*(linkptr+(face<<2)+3)<<1)+1);
			z3 = *(scrz+(*(linkptr+(face<<2)+3)));
			x4 = *(scrxy+(*(linkptr+(face<<2)+2)<<1));
			y4 = *(scrxy+(*(linkptr+(face<<2)+2)<<1)+1);
			z4 = *(scrz+(*(linkptr+(face<<2)+2)));

			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>>2,rgb12,rgb12>>1);
				setRGB1(polyg4,rgb12>>2,rgb12,rgb12>>1);
				setRGB2(polyg4,rgb34>>2,rgb34,rgb34>>1);
				setRGB3(polyg4,rgb34>>2,rgb34,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);
			}
			face++;
		}

		if (lp==1 && item->item_flags[2])
			break;
		rgb12 = rgb34;
	}

	mPopMatrix();
}

#endif

static long KnockBackCollision(EXPLOSION_RING *erptr)
{
	int radius, x, z, dist;
	sint16 angle, diff;

	radius = erptr->radius;
	x = lara_item->pos.x_pos - erptr->x;
	z = lara_item->pos.z_pos - erptr->z;
	if (x > 16000 || x < -16000 || z > 16000 || z < -16000)
		dist = 0x7FFF;
	else
		dist = SQUARE(x) + SQUARE(z);

	if (dist < SQUARE(radius))
	{
		/* Whump! Been hit by sphere - throw Lara through air */
		lara_item->hit_points -= 200;
		lara_item->hit_status = 1;
		angle = phd_atan(z, x);
		diff = lara_item->pos.y_rot - angle;
		if (ABS(diff) < 0x4000)
		{
			/* Facing away from blast */
			lara_item->speed = 75;
			lara_item->pos.y_rot = angle;
		}
		else
		{
			/* Facing towards blast */
			lara_item->pos.y_rot = (sint16)(angle-0x8000);
			lara_item->speed = -75;
		}
		lara_item->gravity_status = 1;
		lara_item->fallspeed = -50;
		lara_item->pos.x_rot = lara_item->pos.z_rot = 0;
		lara_item->anim_number = FALLDOWN_A;
		lara_item->frame_number = FALLDOWN_F;
		lara_item->current_anim_state = AS_FORWARDJUMP;
		lara_item->goal_anim_state = AS_FORWARDJUMP;

		TriggerExplosionSparks(lara_item->pos.x_pos, lara_item->pos.y_pos, lara_item->pos.z_pos, 3,-2,2,lara_item->room_number);	// -2 = Set off a dynamic light controller.
		for (x=0;x<3;x++)
			TriggerPlasmaBall(lara_item, 2, (PHD_VECTOR *) &lara_item->pos, lara_item->room_number, GetRandomControl()<<1);
		return(1);
	}
	return(0);
}
