/****************************************************************************/
/*                                                                          */
/* Movable Block Control                                                    */
/*                                                                          */
/****************************************************************************/

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

#ifdef PSX_VERSION
#include "../spec_psx/typedefs.h"
#include "../spec_psx/psxinput.h"
#else
#include "../specific/standard.h"
#include "../specific/input.h"
#endif

#include "lara.h"
#include "laraanim.h"
#include "control.h"
#include "input.h"
#include "sound.h"
#include "laramisc.h"
#include "moveblok.h"

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

#define MAXOFF_MB	300
#define MAXOFF_Z	(LARA_RAD+80)

enum block_states {
	STILL=1,
	PUSH,
	PULL
};

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

static sint16 MovingBlockBounds[12] = {
	-MAXOFF_MB,+MAXOFF_MB,             // X axis Limits
	0,0,                               // Y axis Limits
	-WALL_L/2-MAXOFF_Z,-WALL_L/2,      // Z axis Limits
	-10*ONE_DEGREE,+10*ONE_DEGREE,	// X Rot Limits
	-30*ONE_DEGREE,+30*ONE_DEGREE,	// Y Rot Limits
	-10*ONE_DEGREE,+10*ONE_DEGREE		// Z Rot Limits
};

/*****************************************************************************
 *			Initialise Moving Block
 *  1. Increase Floor Height at Current postion
 ****************************************************************************/
void	InitialiseMovingBlock( sint16 item_num )
{
	ITEM_INFO 	*item;

	item = &items[item_num];

	// TS - new code to unblock splitters attached to Moving Block
	ClearMovableBlockSplitters(item->pos.x_pos, item->pos.y_pos, item->pos.z_pos, item->room_number);

	if (item->status != INVISIBLE)
		AlterFloorHeight( item, -1024 );		// Increase floor height
}

/*****************************************************************************
 *		*Recursive* Routine to clear all splitters attached to a moveable block. Shittage Ahoy.
 ****************************************************************************/
void	ClearMovableBlockSplitters(sint32 x, sint32 y, sint32 z, sint16 room_number)
{
	sint16 height, base_room_number;
	FLOOR_INFO *floor;
	floor = GetFloor( x, y, z, &room_number );
	boxes[floor->box].overlap_index &= (~BLOCKED);	//Clear Box
	height = boxes[floor->box].height;
	base_room_number = room_number;

	floor = GetFloor( x+WALL_L, y, z, &room_number );
	if (floor->box != NO_BOX)
	{
		if (boxes[floor->box].height == height && (boxes[floor->box].overlap_index & BLOCKABLE) && (boxes[floor->box].overlap_index & BLOCKED))
			ClearMovableBlockSplitters(x+WALL_L, y, z, room_number);
	}

	room_number = base_room_number;
	floor = GetFloor( x-WALL_L, y, z, &room_number );
	if (floor->box != NO_BOX)
	{
		if (boxes[floor->box].height == height && (boxes[floor->box].overlap_index & BLOCKABLE) && (boxes[floor->box].overlap_index & BLOCKED))
			ClearMovableBlockSplitters(x-WALL_L, y, z, room_number);
	}

	room_number = base_room_number;
	floor = GetFloor( x, y, z+WALL_L, &room_number );
	if (floor->box != NO_BOX)
	{
		if (boxes[floor->box].height == height && (boxes[floor->box].overlap_index & BLOCKABLE) && (boxes[floor->box].overlap_index & BLOCKED))
			ClearMovableBlockSplitters(x, y, z+WALL_L, room_number);
	}

	room_number = base_room_number;
	floor = GetFloor( x, y, z-WALL_L, &room_number );
	if (floor->box != NO_BOX)
	{
		if (boxes[floor->box].height == height && (boxes[floor->box].overlap_index & BLOCKABLE) && (boxes[floor->box].overlap_index & BLOCKED))
			ClearMovableBlockSplitters(x, y, z-WALL_L, room_number);
	}
}

/*****************************************************************************
 *		Movable Block Control Routine...
 ****************************************************************************/
void	MovableBlock( sint16 item_number )
{
	ITEM_INFO	*item;
	FLOOR_INFO 	*floor;
	int			height;
	sint16		room_number;


	item = &items[item_number];

	/* Gav: new bit to make block disappear if ONESHOT bit is set */
	if (item->flags & ONESHOT)
	{
		AlterFloorHeight(item, 1024);
		KillItem(item_number);
		return;
	}

	AnimateItem(item);

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

	if ( item->pos.y_pos<height )                   // If in Mid Air make it fall
		item->gravity_status = 1;
	else if ( item->gravity_status ) 				// If Hit floor
	{                                               // then deactivate!!!
		item->gravity_status = 0;
		item->pos.y_pos = height;
		item->status = DEACTIVATED;
		floor_shake_effect(item);					// Make crashing effect...
		SoundEffect(70, &item->pos, 0);
	}

	if (item->room_number != room_number)           // Change rooms if need be
		ItemNewRoom(item_number, room_number);
	if ( item->status==DEACTIVATED ) 				// Item has Deactivated itself
	{
		item->status = NOT_ACTIVE;
		RemoveActiveItem( item_number );            // so remove from active list
		AlterFloorHeight( item,-1024 );				// ReIncrease current Floor Height
		AdjustStopperFlag(item, item->item_flags[0]+0x8000,0);	// Clear stopper flags.
		/* Gav: Test HEAVY trigger points.. GetHeight used to get TriggerIndex */
		room_number = item->room_number;
		floor = GetFloor(item->pos.x_pos, item->pos.y_pos, item->pos.z_pos, &room_number);
		GetHeight(floor, item->pos.x_pos, item->pos.y_pos, item->pos.z_pos);
		TestTriggers(trigger_index, 1);
	}
}

/******************************************************************************
 *			Test If Lara is Ready to Pull/Push A Block
 *****************************************************************************/
void	MovableBlockCollision( sint16 item_num, ITEM_INFO *laraitem, COLL_INFO *coll )
{
	uint16		quadrant;
	ITEM_INFO	*item;
	FLOOR_INFO *floor;
	sint16 room_number;

	item = &items[item_num];
	if ( !(input&IN_ACTION) ||                  				// only Push/Pull if pressing Action
		 item->status==ACTIVE ||                 				// Block is currently Not Active
		 laraitem->gravity_status ||                    		// Lara is not jumping and is at same Height!!
		 laraitem->pos.y_pos!=item->pos.y_pos )
			return;

	quadrant = (uint16)(laraitem->pos.y_rot+0x2000)/0x4000;		// Get Laras facing quadrant
	if ( laraitem->current_anim_state==AS_STOP )
	{
		if (lara.gun_status!=LG_ARMLESS)                      // except ACTION and Hands are free
			return;

		switch ( quadrant )
		{
			case NORTH:
				item->pos.y_rot = 0;
			break;

			case EAST:
				item->pos.y_rot = 16384;
			break;

			case SOUTH:
				item->pos.y_rot = -32768;
			break;

			case WEST:
				item->pos.y_rot = -16384;
			break;
		}
		if ( !TestLaraPosition( MovingBlockBounds, item, laraitem ) )
			return;

		// SONY BUG FIX 1/11/97: check block is physically reachable from this point (check block pos from Lara room)
		room_number = laraitem->room_number;
		floor = GetFloor(item->pos.x_pos, item->pos.y_pos, item->pos.z_pos, &room_number);
		if (room_number != item->room_number)
			return;
//		if (GetHeight(floor, item->pos.x_pos, item->pos.y_pos, item->pos.z_pos) == NO_HEIGHT)
//			return;

		switch ( quadrant )
		{
			case NORTH:
				laraitem->pos.z_pos &= -WALL_L;                 // Shift Lara According to Quadrant
				laraitem->pos.z_pos += WALL_L - LARA_RAD;       // She is facing.
			break;

			case SOUTH:
				laraitem->pos.z_pos &= -WALL_L;
				laraitem->pos.z_pos += LARA_RAD;
			break;

			case EAST:
				laraitem->pos.x_pos &= -WALL_L;
				laraitem->pos.x_pos += WALL_L - LARA_RAD;
			break;

			case WEST:
				laraitem->pos.x_pos &= -WALL_L;
				laraitem->pos.x_pos += LARA_RAD;
			break;
		}
		laraitem->pos.y_rot = item->pos.y_rot;
		laraitem->goal_anim_state = AS_PPREADY;				// Want to get ready to Push/Pull block
		AnimateLara( laraitem );
		if ( laraitem->current_anim_state==AS_PPREADY )     // if got to Ready status then hands now busy
			lara.gun_status=LG_HANDSBUSY;
	}
	else if ( laraitem->current_anim_state==AS_PPREADY )
	{
		if ( laraitem->frame_number!=PPREADY_F )
			return;
		if ( !TestLaraPosition( MovingBlockBounds, item, laraitem ) )
			return;
		if ( input&IN_FORWARD )
		{
			if ( !TestBlockPush( item, 1024, quadrant ) ) 			// have we got space to Push this Block
				return;
			item->goal_anim_state = PUSH;
			laraitem->goal_anim_state = AS_PUSHBLOCK;
		}
		else if ( input&IN_BACK )
		{
			if ( !TestBlockPull( item, 1024, quadrant ) )			// have we got space to Pull this Block
				return;                                         	// including space for lara to stand in
			item->goal_anim_state = PULL;
			laraitem->goal_anim_state = AS_PULLBLOCK;
		}
		else
			return;

		AddActiveItem( item_num );
		AlterFloorHeight( item,1024 );					// Bring Down current Floor Height
		AdjustStopperFlag(item,item->item_flags[0],1);			// Set stopper flag.
		item->status = ACTIVE;
		AnimateItem(item);								// Do first frame of Movement..
		AnimateLara(laraitem);                          // for Block and Lara
		lara.head_x_rot = lara.head_y_rot = 0;
		lara.torso_x_rot = lara.torso_y_rot = 0;
	}
}

static int TestBlockMovable(ITEM_INFO *item, int blokhite)
{
	sint16 room_number;
	FLOOR_INFO *floor;

	room_number = item->room_number;
	floor = GetFloor(item->pos.x_pos, item->pos.y_pos, item->pos.z_pos, &room_number);
	if (floor->floor == NO_HEIGHT/256)
		return (1);

	if (floor->floor*256 != item->pos.y_pos - blokhite)
		return (0);

	return (1);
}

/******************************************************************************
 *!!		Test If We Can Push Block into Square next to it
 *****************************************************************************/
int		TestBlockPush( ITEM_INFO *item, int blokhite, uint16 quadrant )
{
	COLL_INFO		scoll;
	FLOOR_INFO	*floor;
	ROOM_INFO 	*r;
	sint32		x,y,z,cmax;
	sint16		room_num;

	if (!TestBlockMovable(item, blokhite))
		return (0);

	x = item->pos.x_pos;
	y = item->pos.y_pos;
	z = item->pos.z_pos;
	room_num = item->room_number;
	switch ( quadrant )
	{
		case NORTH:
			z += WALL_L;
		break;

		case EAST:
			x += WALL_L;
		break;

		case SOUTH:
			z -= WALL_L;
		break;

		case WEST:
			x -= WALL_L;
		break;
	}


	floor = GetFloor( x,y,z, &room_num );   // Get height of where block is going

	r = &room[room_num];
	if (r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size].stopper)
		return(0);

	scoll.quadrant = quadrant;
	scoll.radius = 500;
	if ( CollideStaticObjects( &scoll,x,y,z, room_num, 1000 ) )		// If its going to Collide with a static
		return(0);         											// Object then dont move it

	/* Gav: check floor/ceiling height directly so as not to get confused by TRAPDOORS and the like */
	if (((sint32)floor->floor << 8) != y)
		return (0);

	GetHeight(floor, x, y, z);
	if (height_type != WALL) // no push onto sloping floor
		return (0);

	cmax = y - (blokhite-100);
	floor = GetFloor(x, cmax, z, &room_num);
	if (GetCeiling(floor, x, cmax, z) > cmax)
		return (0);
	item->item_flags[0] = lara_item->pos.y_rot;
//	if (((sint32)floor->ceiling << 8) > cmax)
//		return (0);

	return(1);
}

/******************************************************************************
 *!!		Test If We Can Pull Block into Square next to it
 *****************************************************************************/
int		TestBlockPull( ITEM_INFO *item, int blokhite, uint16 quadrant )
{
	COLL_INFO		scoll;
	FLOOR_INFO	*floor;
	ROOM_INFO 	*r;
	sint32		x,y,z,zadd,xadd,cmax;
	sint16		room_num;

	if (!TestBlockMovable(item, blokhite))
		return (0);

	xadd = zadd = 0;
	switch ( quadrant )
	{
		case NORTH:
			zadd = -WALL_L;
		break;

		case EAST:
			xadd = -WALL_L;
		break;

		case SOUTH:
			zadd = WALL_L;
		break;

		case WEST:
			xadd = WALL_L;
		break;
	}

	x = item->pos.x_pos + xadd;
	y = item->pos.y_pos;
	z = item->pos.z_pos + zadd;
	room_num = item->room_number;
	floor = GetFloor( x,y,z, &room_num );   // Get height of where block is going

	r = &room[room_num];
	if (r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size].stopper)
		return(0);

	scoll.quadrant = quadrant;
	scoll.radius = 500;
	if ( CollideStaticObjects( &scoll,x,y,z, room_num, 1000 ) )		// If its going to Collide with a static
		return(0);                                                  // Object then dont move it

	/* Gav: check floor/ceiling height directly so as not to get confused by TRAPDOORS and the like */
	if (((sint32)floor->floor << 8) != y)
		return (0);

	cmax = y - blokhite;
	floor = GetFloor(x, cmax, z, &room_num);
	if (((sint32)floor->ceiling << 8) > cmax)
		return (0);

	x += xadd;								  // Now Test if Lara Can move to her New Position safely
	z += zadd;
	room_num = item->room_number;
	floor = GetFloor( x,y,z, &room_num );     // Get height of where Lara is going

	/* Gav: check floor/ceiling height directly so as not to get confused by TRAPDOORS and the like */
	if (((sint32)floor->floor << 8) != y)
		return (0);

	cmax = y - LARA_HITE;
	floor = GetFloor(x, cmax, z, &room_num);
	if (((sint32)floor->ceiling << 8) > cmax)
		return (0);

	x = lara_item->pos.x_pos + xadd;        	// Finally Check if Lara is going to collide with a static object
	y = lara_item->pos.y_pos;
	z = lara_item->pos.z_pos + zadd;
	room_num = lara_item->room_number;
	floor = GetFloor( x,y,z, &room_num );   	// Get height of where block is going
	scoll.quadrant = (quadrant+2)&3;
	scoll.radius = LARA_RAD;
	if ( CollideStaticObjects( &scoll,x,y,z, room_num, LARA_HITE ) )  // If its going to Collide with a static
		return(0);                                                    // Object then dont move it

	item->item_flags[0] = lara_item->pos.y_rot+0x8000;

	return(1);
}


/******************************************************************************
 *			Alter Floor Height at current position
 *****************************************************************************/
void	AlterFloorHeight( ITEM_INFO *item, int height )
{
	FLOOR_INFO	*floor, *ceiling;
	sint16		room_num;

	/* Gav: rejigged this code so that blocks flush with ceiling count as walls - stops Lara
		leaping up and grabbing edge of block in these situations */
	room_num = item->room_number;
	floor = GetFloor( item->pos.x_pos, item->pos.y_pos, item->pos.z_pos, &room_num );
	ceiling = GetFloor(item->pos.x_pos, item->pos.y_pos+height-WALL_L, item->pos.z_pos, &room_num); // extra WALL_L to ensure room above
	if (floor->floor == NO_HEIGHT/256)
		floor->floor = ceiling->ceiling + height/256;
	else
	{
		floor->floor += height/256;
		if (floor->floor == ceiling->ceiling)
			floor->floor = NO_HEIGHT/256;
	}

	/* Gav: if floor height goes up then BLOCKABLE boxes are blocked (else they're cleared) */
	if (boxes[floor->box].overlap_index & BLOCKABLE)
	{
		if (height < 0)
			boxes[floor->box].overlap_index |= BLOCKED;
		else
			boxes[floor->box].overlap_index &= (~BLOCKED);
	}
}

/******************************************************************************
 *		Draw Moving Block ( resets Draw bounds to screen when animating )
 *****************************************************************************/
void	DrawMovableBlock( ITEM_INFO *item )
{
	if ( item->status!=ACTIVE )
		DrawAnimatingItem( item );
	else
		DrawUnclippedItem( item );
}

/******************************************************************************
 *		Draw Animating Item without clipping to Door Bounds
 *****************************************************************************/
void	DrawUnclippedItem( ITEM_INFO *item )
{
	int		left,right,top,bottom;

	left = phd_left;
	right = phd_right;
	top = phd_top;
	bottom = phd_bottom;
	phd_left = phd_top = 0;					// Make Bounds to Screen Limits
	phd_bottom = phd_winymax;
	phd_right = phd_winxmax;
	DrawAnimatingItem( item );
	phd_left = left;                    	// Reset bounds to what they were
	phd_right = right;
	phd_top = top;
	phd_bottom = bottom;
}

void SetupCleanerFromSavegame(ITEM_INFO *item, long block)
{
	ROOM_INFO	*r;
	long	x,z,xadd,zadd;
	sint16 room_number;

	x = item->pos.x_pos;
	z = item->pos.z_pos;

	if ( !block && (item->pos.z_pos & 0x3ff) == 512 && (item->pos.x_pos & 0x3ff) == 512)	// In the middle of a block ?
	{
		room_number = item->room_number;
		GetFloor(x,item->pos.y_pos,z,&room_number);
		r = &room[room_number];
		r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size].stopper = 1;
		return;
	}
	else	if (item->pos.y_rot == 0)    	// Z +ve
	{
		if (!block)
		{
			if ((z&1023) < 512) 	// In next block ?
			{
				z &= ~1023;
				z -= 512;
			}
			else
			{
				z &= ~1023;
				z += 512;
			}
		}

		xadd = 0;
		zadd = 1024;
	}
	else	if (item->pos.y_rot == 16384)	// X +ve
	{
		if (!block)
		{
			if ((x&1023) < 512) 	// In next block ?
			{
				x &= ~1023;
				x -= 512;
			}
			else
			{
				x &= ~1023;
				x += 512;
			}
		}

		xadd = 1024;
		zadd = 0;
	}
	else	if (item->pos.y_rot == -32768)	// Z -ve
	{
		if (!block)
		{
			if ((z&1023) > 512) 	// In next block ?
			{
				z |= 1023;
				z += 512;
			}
			else
			{
				z &= ~1023;
				z += 512;
			}
		}

		xadd = 0;
		zadd = -1024;
	}
	else						// X -ve
	{
		if (!block)
		{
			if ((x&1023) > 512) 	// In next block ?
			{
				x |= 1023;
				x += 512;
			}
			else
			{
				x &= ~1023;
				x += 512;
			}
		}

		xadd = -1024;
		zadd = 0;
	}

	room_number = item->room_number;
	GetFloor(x,item->pos.y_pos,z,&room_number);
	r = &room[room_number];
	r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size].stopper = 1;

	x += xadd;
	z += zadd;
	room_number = item->room_number;
	GetFloor(x,item->pos.y_pos,z,&room_number);
	r = &room[room_number];
	r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size].stopper = 1;
}

