/*********************************************************************************************/
/*                                                                                           */
/* Lasers for Area 51.														   */
/*                                                                                           */
/*********************************************************************************************/

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

#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"
#else
#include "../specific/standard.h"
#include "../specific/stypes.h"
#include "../specific/input.h"
#include "../specific/global.h"
#endif

#include "objects.h"
#include "lara.h"
#include "laraanim.h"
#include "control.h"

void	S_DrawLaserBeam( GAME_VECTOR *src, GAME_VECTOR *dest, uchar r, uchar g, uchar b);

#define SIGN_BIT 0x80000000

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

extern short rcossin_tbl[];
extern int los_rooms[], number_los_rooms;

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

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


void LaserControl(sint16 item_number);
void	S_DrawLaser( ITEM_INFO *item );

void *func[] = {
	&LaserControl,
	&S_DrawLaser,
	&S_DrawLaserBeam
};

#endif

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

#ifdef PSX_VERSION
	static uchar LaserShades[32];
#else
	uchar LaserShades[32];
#endif


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

static int LaraOnLOS(GAME_VECTOR *start, GAME_VECTOR *target)
{
	int j, failure;
	sint32 x, y, z, dx, dy, dz, distance;
	sint32 direction;
	sint16 *bounds, *zextent, *xextent;
	ITEM_INFO *item;

	dx = target->x - start->x;
	dy = target->y - start->y;
	dz = target->z - start->z;

	item = lara_item;

	/* Get bounds of object */
	bounds = GetBoundsAccurate(item);

	/* Need to allow for rotation of object (assume aligned to axis) */
	direction = (uint16)(item->pos.y_rot+0x2000)/0x4000;
	if (direction & 1)
	{
		zextent = &bounds[0];
		xextent = &bounds[4];
	}
	else
	{
		zextent = &bounds[4];
		xextent = &bounds[0];
	}

	/* If dz is longest axis, then check x axis faces, else vice versa */
	failure = 0;
	if (ABS(dz) > ABS(dx))
	{
		/* Check z min */
		distance = item->pos.z_pos + zextent[0] - start->z;
		for (j=0; j<2; j++) // cunning loop to do this check twice (once for each distance)
		{
			if ((distance & SIGN_BIT) == (dz & SIGN_BIT))
			{
				y = dy * distance / dz;
				if (y > item->pos.y_pos + bounds[2] - start->y && y < item->pos.y_pos + bounds[3] - start->y)
				{
					x = dx * distance / dz;
					if (x < item->pos.x_pos + xextent[0] - start->x)
						failure |= 0x1;
					else if (x > item->pos.x_pos + xextent[1] - start->x)
						failure |= 0x2;
					else
						return (1);
				}
			}

			/* Check z max */
			distance = item->pos.z_pos + zextent[1] - start->z;
		}

		/* If failed on both a greater than and less than count, actually will have passed through object */
		if (failure == 0x3)
			return (1);
	}
	else
	{
		/* Check x min */
		distance = item->pos.x_pos + xextent[0] - start->x;
		for (j=0; j<2; j++) // cunning loop to do this check twice (once for each distance)
		{
			if ((distance & SIGN_BIT) == (dx & SIGN_BIT))
			{
				y = dy * distance / dx;
				if (y > item->pos.y_pos + bounds[2] - start->y && y < item->pos.y_pos + bounds[3] - start->y)
				{
					z = dz * distance / dx;
					if (z < item->pos.z_pos + zextent[0] - start->z)
						failure |= 0x1;
					else if (z > item->pos.z_pos + zextent[1] - start->z)
						failure |= 0x2;
					else
						return (1);	/* Hit object! */
				}
			}

			/* Check x max */
			distance = item->pos.x_pos + xextent[1] - start->x;
		}

		/* If failed on both left/right then passed through object, so a hit really */
		if (failure == 0x3)
			return (1);
	}

	/* Nothing smashable has been hit */
	return (0);
}

#ifdef PSX_VERSION
static void UpdateLaserShades()
#else
void UpdateLaserShades()
#endif
{
	long		lp,rnd;
	uchar 	*shptr,val;

	shptr = &LaserShades[0];

	for (lp=0;lp<32;lp++)
	{
		val = *shptr;

		rnd = GetRandomDraw();

		if (rnd < 0x400)
			rnd = (rnd&15)+16;
		else if (rnd < 0x1000)
			rnd &= 7;
		else if (!(rnd & 0x70))
			rnd &= 3;
		else
			rnd = 0;

		if (rnd)
		{
			val += rnd;
			if (val > 127)
				val = 127;
		}
		else
		{
			if (val > 16)
				val -= val >> 3;
//			else	if (val > 4)
//			    	val -= val >> 2;
//			else
//				val = 0;
			else
				val = 16;
		}
		*shptr++ = val;
	}
}

static void LaserSplitterToggle(ITEM_INFO *item)
{

	sint16 room_number;
	sint32 x, z, dx, dz;
	FLOOR_INFO *floor;
	int active;

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

	if (!(boxes[floor->box].overlap_index & BLOCKABLE)) //If there's no splitters on this square forget it
		return;

	active = TriggerActive(item);
	if (active == ((boxes[floor->box].overlap_index & BLOCKED) == BLOCKED)) //The lasers are on and the squares are blocked, or they're not and they're not
		return;

	switch (item->pos.y_rot)
	{
		case 0:
			dz = -WALL_L;
			dx = 0;
			break;
		case 16384:
			dz = 0;
			dx = -WALL_L;
			break;
		case -32768:
			dz = WALL_L;
			dx = 0;
			break;
		default:
			dz = 0;
			dx = WALL_L;
			break;
	}

	x = item->pos.x_pos;
	z = item->pos.z_pos;
	while (floor->box != NO_BOX && (boxes[floor->box].overlap_index & BLOCKABLE))
	{
		if (active)
			boxes[floor->box].overlap_index |= BLOCKED;
		else
			boxes[floor->box].overlap_index &= ~BLOCKED;
		x += dx;
		z += dz;
		floor = GetFloor(x, item->pos.y_pos, z, &room_number);
	}

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


void LaserControl(sint16 item_number)
{
	ITEM_INFO *item;

	item = &items[item_number];

	LaserSplitterToggle(item);
}


void	S_DrawLaser(ITEM_INFO *item)
{
	GAME_VECTOR	src,dest;
	long			x,z,lp,y;

	if (!TriggerActive(item))
		return;

	y = 0;

	switch (item->pos.y_rot)
	{
		case 0:
			z = 511;
			x = 0;
			break;
		case 16384:
			z = 0;
			x = 511;
			break;
		case -32768:
			z = -511;
			x = 0;
			break;
		default:
			z = 0;
			x = -511;
			break;
	}

	for (lp=0;lp<item->hit_points;lp++)
	{
		src.room_number = item->room_number;
		src.x = item->pos.x_pos + x;
		src.y = item->pos.y_pos + y;
		src.z = item->pos.z_pos + z;

		dest.x = item->pos.x_pos - (x<<5);
		dest.y = item->pos.y_pos + y;
		dest.z = item->pos.z_pos - (z<<5);

		LOS(&src,&dest);	// Get target hit point.... assumes it will hit a wall!

		if (LaraOnLOS(&src,&dest))
		{
			long	lp;
			ITEM_INFO *target;

			if (item->object_number != SECURITY_LASER_ALARM)
			{
				if (item->object_number == SECURITY_LASER_KILLER)
					lara_item->hit_points = 0;
				else
					lara_item->hit_points-=10;
				DoLotsOfBloodD( lara_item->pos.x_pos, item->pos.y_pos+y, lara_item->pos.z_pos, (GetRandomDraw()&127)+128, GetRandomDraw()<<1, lara_item->room_number , 1);
			}

			if ( item->pos.y_rot == 0 || item->pos.y_rot == -32768)
				dest.z = lara_item->pos.z_pos;
			else
				dest.x = lara_item->pos.x_pos;

			{
				for (lp=0, target=&items[0]; lp<level_items; lp++, target++)
				{
					if (target->object_number == STROBE_LIGHT || target->object_number == ROBOT_SENTRY_GUN)	// Set off all alarms.
					{
						if (TriggerActive(target))
							target->really_active = 1;
					}

/*					if (target->object_number == STROBE_LIGHT)	// Set off all alarms.
					{
						AddActiveItem(lp);
						target->flags &= ~REVERSE;
						target->flags |= CODE_BITS;
					}
					else if (target->object_number == ROBOT_SENTRY_GUN)	// Set off all alarms.
					{
						target->status = ACTIVE;
						AddActiveItem(lp);
					}*/
				}
			}

		}

	// Colours - Shift values to affect shade. i.e. 16 = No R,G or B / 1 = Half R,G or B / 0 = Full R,G or B

		if (item->object_number == SECURITY_LASER_ALARM)
			S_DrawLaserBeam( &src, &dest, 16, 0, 16);	// Green (sets off alarms).

		else if (item->object_number == SECURITY_LASER_DEADLY)
			S_DrawLaserBeam( &src, &dest, 0, 0, 16);	// Amber (hurts).

		else
			S_DrawLaserBeam( &src, &dest, 0, 2, 16);	// Red (kills).

		y-=256;
	}
}


#ifdef PSX_VERSION

extern VECTOR3D CamPos;

void	S_DrawLaserBeam( GAME_VECTOR *src, GAME_VECTOR *dest, uchar r, uchar g, uchar b)
{
	LINE_G2 	*lineg2;
	long		x1,x2,y1,y2,z1,z2,r1,g1,b1,r2,g2,b2;
	long		wx,wy,wz,dx,dy,dz,lp,segments;
	short	*scrxy;
	uchar	*rgbs;	// X,Y, RGBs
	long		*scrz;	// Z
	short	*TempMesh;

	UpdateLaserShades();

	Matrix++;
	mCopyMatrix(&MatrixStack[0]);
	mTranslateAbsXYZ(src->x, src->y, src->z);

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

	dx = (src->x - dest->x);
	dz = (src->z - dest->z);

	dx = mSqrt(SQUARE(dx)+SQUARE(dz));
	segments = dx>>9;	// In half blocks.

	if (segments < 8)
		segments = 8;
	else if (segments > 32)
		segments = 32;

	dx = (dest->x - src->x) / segments;
	dy = (dest->y - src->y) / segments;
	dz = (dest->z - src->z) / segments;

	wx = wy = wz = 0;

	for (lp=0;lp<segments+1;lp++)
	{
		TempMesh[0] = wx;
		TempMesh[1] = wy;
		TempMesh[2] = wz;

		gte_ldv0(TempMesh);
		gte_rtps();
		wx += dx;
		wy += dy;
		wz += dz;
		gte_stsxy(scrxy);
		gte_stsz(scrz);
		scrxy+=2;

		if (lp == 0 || lp == segments || *scrz >= 0x5000)
		{
			*rgbs++ = 0;
			*rgbs++ = 0;
			*rgbs++ = 0;
		}
		else
		{
			long rnd,tr,tg,tb,tz;
			tz = *scrz;

			rnd = LaserShades[lp];

			if (r==255)
				tr = 32+rnd;
			else
				tr = rnd>>r;
			if (g==255)
				tg = 32+rnd;
			else
				tg = rnd>>g;
			if (b==255)
				tb = 32+rnd;
			else
				tb = rnd>>b;

			if (tz > 0x4000)
			{
				tr -= (tr * (0x5000-tz)) >> 10;
				tg -= (tr * (0x5000-tz)) >> 10;
				tb -= (tr * (0x5000-tz)) >> 10;
			}

			*rgbs++ = tr;
			*rgbs++ = tg;
			*rgbs++ = tb;
		}
		scrz++;
	}

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

	x1 = *scrxy++;
	y1 = *scrxy++;
	z1 = (*scrz++)>>3;
	r1 = *rgbs++;
	g1 = *rgbs++;
	b1 = *rgbs++;

	for (lp=0;lp<segments;lp++)        	// Electricity on head.
	{
		x2 = *scrxy++;
		y2 = *(scrxy++);
		z2 = (*scrz++)>>3;
		r2 = *rgbs++;
		g2 = *rgbs++;
		b2 = *rgbs++;

		z1 = (z1+z2)>>1;

		if (z1>32 && z1 < 0xa00)
		{
			lineg2 = (LINE_G2 *)db.polyptr;

			setlen(lineg2, 4);
			setcode(lineg2, 0x52);
//			setRGB0(lineg2,255,255,255);
//			setRGB1(lineg2,255,255,255);
			setRGB0(lineg2,r1,g1,b1);
			setRGB1(lineg2,r2,g2,b2);
			setXY2(lineg2,x1,y1,x2,y2);
			MyaddPrim(db.ot, z1, lineg2);
			db.polyptr += sizeof(LINE_G2);

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

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

		x1 = x2;
		y1 = y2;
		z1 = z2;
		r1 = r2;
		g1 = g2;
		b1 = b2;
	}

	mPopMatrix();
}


#endif


