/*********************************************************************************************/
/*                                                                                           */
/* Doors/bridges etc                                                                         */
/*                                                                                           */
/*********************************************************************************************/

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

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

#include "objects.h"
#include "lara.h"
#include "laraanim.h"
#include "camera.h"
#include "control.h"
#include "input.h"
#include "sound.h"
#include "savegame.h"
#include "cinema.h"
#include "sphere.h"
#include "traps.h"
#include "boat.h"
#include "boatinfo.h"
#include "lot.h"
#include "effect2.h"

void TriggerFenceSparks(long x, long y, long z, long kill);

extern short rcossin_tbl[];
extern long wibble;

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

#define LIFT_WAIT_TIME 90
#define	LOW_RUMBLE	20
#define	BIG_RUMBLE	100

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

typedef struct {
	FLOOR_INFO *floor;
	FLOOR_INFO data;
	sint16 block;
}DOORPOS_DATA;

typedef struct {
	DOORPOS_DATA d1, d1flip, d2, d2flip;
}DOOR_DATA;

typedef struct {
	int start_height;
	int wait_time;
}LIFT_INFO;

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

extern int level_complete;

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

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

/*********************************** FUNCTION CODE *******************************************/
void EarthQuake(sint16 item_number)
{
	sint16 earth_item;
	int want,pitch;
	ITEM_INFO *item;

	item = &items[item_number];

	if (!TriggerActive(item))
		return;

	if (item->item_flags[1] == 0)
		item->item_flags[1] = BIG_RUMBLE;

	if (item->item_flags[2] == 0 && abs(item->item_flags[0]-item->item_flags[1]) < 16)	// Change destination rumble.
	{
		if (item->item_flags[1] == LOW_RUMBLE)
		{
			item->item_flags[1] = BIG_RUMBLE;
			item->item_flags[2] = (GetRandomControl()&127)+90;	// Max 7 seconds

		}
		else
		{
			item->item_flags[1] = LOW_RUMBLE;
			item->item_flags[2] = (GetRandomControl()&127)+30;	// Max 4 seconds.
		}
	}

	if (item->item_flags[2])
		item->item_flags[2]--;

	if (item->item_flags[0] > item->item_flags[1])
		item->item_flags[0] -= (GetRandomControl()&7)+2;
	else
		item->item_flags[0] += (GetRandomControl()&7)+2;

	pitch = (0x10000 + (item->item_flags[0]<<8)) << 8;
	SoundEffect(107, NULL, PITCH_SHIFT+pitch);
	camera.bounce = -item->item_flags[0];

	want = GetRandomControl();
	if (want < 0x400)
	{
		want = (want < 0x200)? FLAME_EMITTER : FALLING_CEILING1;

		/* Find a flame emitter or a falling ceiling in this room to activate */
		for (earth_item = room[item->room_number].item_number; earth_item!=NO_ITEM; earth_item=items[earth_item].next_item)
		{
			item = &items[earth_item];
			if (item->object_number != want)
				continue;

			if (item->status == ACTIVE || item->status == DEACTIVATED)
				continue;

			/* Activate the next one */
			AddActiveItem(earth_item);
			item->status = ACTIVE;
			item->flags = CODE_BITS;
			item->timer = 0;

			return;
		}
	}
}


#define FINAL_COUNTDOWN (30*5)

sint16 final_level_count, final_boss_count, final_boss_active;
sint16 final_boss[5];

void ControlCutShotgun(sint16 item_number)
{
	ITEM_INFO *item;

	item = &items[item_number];

	if (final_boss_active >= FINAL_COUNTDOWN)
	{
		item->status = ACTIVE;
		AnimateItem(item);
	}
	else
		item->status = INVISIBLE;
}

void InitialiseFinalLevel(void)
{
	/* Go through all the items looking for a) bad guys (alive) and b) cult bosses */
	int i;
	ITEM_INFO *item;

	final_level_count = final_boss_count = final_boss_active = 0;

	for (i=0, item=items; i<level_items; i++, item++)
	{
		switch (item->object_number)
		{
//		case CULT1:
//		case WORKER3:
		case DOG:
			final_level_count++;
			break;

//		case CULT3:
//			final_boss[final_boss_count++] = i;
//			if (item->status == ACTIVE)
//				final_boss_active = 1;
//			else if (item->status == DEACTIVATED)
//				final_boss_active = 2;
//			break;
		}
	}
}


void FinalLevelCounter(sint16 item_number)
{
	sint16 best;
	ITEM_INFO *item;
	GAME_VECTOR start, target;
	int i, best_distance, distance, x, y, z;

#ifdef GAMEDEBUG
	if (input & IN_A)
	{
		savegame.kills = final_level_count+1;
		final_boss_active = FINAL_COUNTDOWN-5;
	}
#endif

	if (savegame.kills == final_level_count && !final_boss_active)
	{
		/* Time to activate final boss */
		best_distance = 0x7fffffff;
		best = final_boss[0];
		for (i=0; i<final_boss_count; i++)
		{
			item = &items[final_boss[i]];

			start.x = lara_item->pos.x_pos;
			start.y = lara_item->pos.y_pos - STEP_L*2;
			start.z = lara_item->pos.z_pos;
			start.room_number = lara_item->room_number;

			target.x = item->pos.x_pos;
			target.y = item->pos.y_pos - STEP_L*2;
			target.z = item->pos.z_pos;
			if (!LOS(&start, &target))
			{
				/* Choose closest version of boss not in LOS */
				x = (lara_item->pos.x_pos - item->pos.x_pos) >> 6;
				y = (lara_item->pos.y_pos - item->pos.y_pos) >> 6;
				z = (lara_item->pos.z_pos - item->pos.z_pos) >> 6;
				distance = x*x + y*y + z*z;
				if (distance < best_distance)
				{
					best_distance = distance;
					best = final_boss[i];
				}
			}
		}

		/* Initialise final boss */
		item = &items[best];
		item->touch_bits = 0;
		item->status = ACTIVE;
		AddActiveItem(best);
		EnableBaddieAI(best, 1);
		item->mesh_bits = 0xffff1fff;

		final_boss_active = 1;
	}
	else if (savegame.kills > final_level_count)
	{
		final_boss_active++;
		if (final_boss_active == FINAL_COUNTDOWN)
		{
			/* Final anim */
			cine_frame = 428;		// FIX: for when you load on home sweet home
			item = &items[item_number];
			CreatureKill(item, 0, 0, EXTRA_FINALANIM);
			lara.mesh_ptrs[HAND_R]  = meshes[objects[LARA].mesh_index + HAND_R];

			camera.type = CINEMATIC_CAMERA; // carry on new cinematic cam?
			S_MemCpy(&cinematic_pos, &item->pos, sizeof(PHD_3DPOS));
		}
	}
}

void MiniCopterControl(sint16 item_number)
{
	ITEM_INFO *item;
	sint16 room_number;
	PHD_3DPOS pos;

	item = &items[item_number];

	item->pos.z_pos += 100;

	pos.x_pos = lara_item->pos.x_pos + ((item->pos.x_pos - lara_item->pos.x_pos) >> 2);
	pos.y_pos = lara_item->pos.y_pos + ((item->pos.y_pos - lara_item->pos.y_pos) >> 2);
	pos.z_pos = lara_item->pos.z_pos + ((item->pos.z_pos - lara_item->pos.z_pos) >> 2);
	SoundEffect(297, &pos, 0);

	if (ABS(item->pos.z_pos-lara_item->pos.z_pos) > WALL_L*30)
		KillItem(item_number);

	AnimateItem(item);

	room_number = item->room_number;
	GetFloor(item->pos.x_pos, item->pos.y_pos, item->pos.z_pos, &room_number);
	if (room_number != item->room_number)
		ItemNewRoom(item_number, room_number);
}

void InitialiseWindow(sint16 item_number)
{
	ITEM_INFO *item;
	ROOM_INFO *r;
	FLOOR_INFO *floor;

	item = &items[item_number];
	item->flags = 0;

	/* Turn off shards */
	item->mesh_bits = 0x1;

	/* Then block blockable blocks */
	r = &room[item->room_number];
	floor = &r->floor[((item->pos.z_pos - r->z) >> WALL_SHIFT) + ((item->pos.x_pos - r->x) >> WALL_SHIFT) * r->x_size];
	if (boxes[floor->box].overlap_index & BLOCKABLE)
		boxes[floor->box].overlap_index |= BLOCKED;
}


void SmashWindow(sint16 item_number)
{
	ROOM_INFO *r;
	FLOOR_INFO *floor;
	ITEM_INFO *item;

	item = &items[item_number];

	/* Unblock blocked spaces */
	r = &room[item->room_number];
	floor = &r->floor[((item->pos.z_pos - r->z) >> WALL_SHIFT) + ((item->pos.x_pos - r->x) >> WALL_SHIFT) * r->x_size];
	if (boxes[floor->box].overlap_index & BLOCKABLE)
		boxes[floor->box].overlap_index &= (~BLOCKED);

	if (item->object_number == SMASH_WINDOW)
		SoundEffect(214, &item->pos, 0);
	else
	{
		SoundEffect(105, &item->pos, 0);
		SoundEffect(106, &item->pos, 0);
	}

	item->mesh_bits = 0x0fffe;
	item->collidable = 0;
	ExplodingDeath(item_number, 0x0fefe, 0);

	item->flags |= ONESHOT;
	if (item->status == ACTIVE)
		RemoveActiveItem(item_number);
	item->status = DEACTIVATED;
}


void WindowControl(sint16 item_number)
{
	ITEM_INFO *item;
	sint32 speed;

	item = &items[item_number];
	if (item->flags & ONESHOT)
		return;

	/* Window has an orientation; need to get velocity in direction of window */
	if (lara.skidoo == NO_ITEM)
	{
		if (!item->touch_bits)
			return;
		item->touch_bits = 0;
		speed = lara_item->speed * phd_cos((sint16)(lara_item->pos.y_rot - item->pos.y_rot)) >> W2V_SHIFT;
		if (ABS(speed) < 50)
			return;
	}
	else if (!ItemNearLara(&item->pos, WALL_L/2))
		return;

	SmashWindow(item_number);
}


void ShutThatDoor(DOORPOS_DATA *d)
{
	FLOOR_INFO *floor;

	if (d->floor)
	{
		floor = d->floor;
		floor->index = 0;
		floor->box = NO_BOX;
		floor->floor = floor->ceiling = NO_HEIGHT>>8;
		floor->pit_room = floor->sky_room = NO_ROOM;

		if (d->block != NO_BOX)
			boxes[d->block].overlap_index |= BLOCKED;
	}
}

void OpenThatDoor(DOORPOS_DATA *d)
{
	if (d->floor)
	{
		S_MemCpy(d->floor, &d->data, sizeof(FLOOR_INFO));

		if (d->block != NO_BOX)
			boxes[d->block].overlap_index &= (~BLOCKED);
	}
}

/*
 * Door is closed initially, so alter floor data to remove doorway status and
 * remember for later when door is reopened. Flip maps make this doubly
 * complicated
*/
void InitialiseDoor(sint16 item_number)
{
	int dx, dy;
	ITEM_INFO *item;
	ROOM_INFO *r, *b;
	DOOR_DATA *door;
	sint16 two_room, box_number, room_number;

	item = &items[item_number];

	/* Get and fill in a door data structure to remember changes in floor data as door opens/closes */
	door = item->data = (DOOR_DATA *)game_malloc(sizeof(DOOR_DATA),EXTRA_DOOR_STUFF );

	/* Which direction is the door? */
	dx = dy = 0;
	if (item->pos.y_rot == 0)
		dx--;
	else if (item->pos.y_rot == -0x8000)
		dx++;
	else if (item->pos.y_rot == 0x4000)
		dy--;
	else
		dy++;

	r = &room[item->room_number];

	/* Then remember floor space information */
	door->d1.floor = &r->floor[(((item->pos.z_pos - r->z) >> WALL_SHIFT) + dx) + (((item->pos.x_pos - r->x) >> WALL_SHIFT) + dy) * r->x_size];
	room_number = GetDoor(door->d1.floor);
	if (room_number == NO_ROOM)
		box_number = door->d1.floor->box;
	else
	{
		b = &room[room_number];
		box_number = b->floor[(((item->pos.z_pos - b->z) >> WALL_SHIFT) + dx) + (((item->pos.x_pos - b->x) >> WALL_SHIFT) + dy) * b->x_size].box;
	}
	door->d1.block = (boxes[box_number].overlap_index & BLOCKABLE)? box_number : NO_BOX;

	S_MemCpy(&door->d1.data, door->d1.floor, sizeof(FLOOR_INFO));

	if (r->flipped_room != -1)
	{
		r = &room[r->flipped_room];

		door->d1flip.floor = &r->floor[(((item->pos.z_pos - r->z) >> WALL_SHIFT) + dx) +	(((item->pos.x_pos - r->x) >> WALL_SHIFT) + dy) * r->x_size];
		room_number = GetDoor(door->d1flip.floor);
		if (room_number == NO_ROOM)
			box_number = door->d1flip.floor->box;
		else
		{
			b = &room[room_number];
			box_number = b->floor[(((item->pos.z_pos - b->z) >> WALL_SHIFT) + dx) +	(((item->pos.x_pos - b->x) >> WALL_SHIFT) + dy) * b->x_size].box;
		}
		door->d1flip.block = (boxes[box_number].overlap_index & BLOCKABLE)? box_number : NO_BOX;

		S_MemCpy(&door->d1flip.data, door->d1flip.floor, sizeof(FLOOR_INFO));
	}
	else
		door->d1flip.floor = NULL;

	/* Door between two rooms can be approached from both directions, whereas one in the
		middle of a corridor will have an entire space of wall beside it */
	two_room = GetDoor(door->d1.floor);

	ShutThatDoor(&door->d1);
	ShutThatDoor(&door->d1flip);

	if (two_room == NO_ROOM)
	{
		door->d2.floor = NULL;
		door->d2flip.floor = NULL;
	}
	else
	{
		r = &room[two_room];

		door->d2.floor = &r->floor[((item->pos.z_pos - r->z) >> WALL_SHIFT) + ((item->pos.x_pos - r->x) >> WALL_SHIFT) * r->x_size];
		room_number = GetDoor(door->d2.floor);
		if (room_number == NO_ROOM)
			box_number = door->d2.floor->box;
		else
		{
			b = &room[room_number];
			box_number = b->floor[((item->pos.z_pos - b->z) >> WALL_SHIFT) + ((item->pos.x_pos - b->x) >> WALL_SHIFT) * b->x_size].box;
		}
		door->d2.block = (boxes[box_number].overlap_index & BLOCKABLE)? box_number : NO_BOX;

		S_MemCpy(&door->d2.data, door->d2.floor, sizeof(FLOOR_INFO));

		if (r->flipped_room != -1)
		{
			r = &room[r->flipped_room];

			door->d2flip.floor = &r->floor[((item->pos.z_pos - r->z) >> WALL_SHIFT) + ((item->pos.x_pos - r->x) >> WALL_SHIFT) * r->x_size];
			room_number = GetDoor(door->d2flip.floor);
			if (room_number == NO_ROOM)
				box_number = door->d2flip.floor->box;
			else
			{
				b = &room[room_number];
				box_number = b->floor[((item->pos.z_pos - b->z) >> WALL_SHIFT) + ((item->pos.x_pos - b->x) >> WALL_SHIFT) * b->x_size].box;
			}
			door->d2flip.block = (boxes[box_number].overlap_index & BLOCKABLE)? box_number : NO_BOX;

			S_MemCpy(&door->d2flip.data, door->d2flip.floor, sizeof(FLOOR_INFO));
		}
		else
			door->d2flip.floor = NULL;

		ShutThatDoor(&door->d2);
		ShutThatDoor(&door->d2flip);

		// 13/8/97: 'cos doors open into the other room, better if they are in that room for draw purposes
		room_number = item->room_number;
		ItemNewRoom(item_number, two_room);
		item->room_number = room_number;
	}
}

void DoorControl(sint16 item_number)
{
	ITEM_INFO *item;
	DOOR_DATA *door;
#ifdef PSX_VERSION
	int		keepitopen=0;	// MAJOR FRIG X 1,000,000

	// SONY AMERICA DIRECTIVE NO.A23
	//	It is written: Everyone, given the chance, will jump off the boat, just
	//	before they are about to completed the level, in order to play an extra couple of
	//	minutes from their last save game. (They love it).  Add all such people together,
	//  counting each person as a brain cell, and you will have a brain about the size of
	//	a Sony play tester.

	// Sorry Gav, but this had to be done!
	// Check if the boat has gone thru the door without lara,
	// if it has then keep the door open.
	if (objects[BOAT].loaded && lara_inboat==0 &&
		boatitem->pos.x_pos>24500 && boatitem->pos.x_pos<26500 &&
		boatitem->pos.z_pos>75500 && boatitem->pos.z_pos<83000)
		keepitopen = 1;
#endif

	item = &items[item_number];
	door = (DOOR_DATA *)item->data;

	if (TriggerActive(item))
	{
		if (item->current_anim_state == DOOR_CLOSED)
			item->goal_anim_state = DOOR_OPEN;
		else
		{
			/* Copy back open door stuff */
			OpenThatDoor(&door->d1);
			OpenThatDoor(&door->d2);
			OpenThatDoor(&door->d1flip);
			OpenThatDoor(&door->d2flip);
		}
	}
	else
	{
		if (item->current_anim_state == DOOR_OPEN)
		{
#ifdef PSX_VERSION	// Jason: you forgot this...  - Dude
			if (keepitopen)
				item->goal_anim_state = DOOR_OPEN;
			else
#endif
				item->goal_anim_state = DOOR_CLOSED;
		}
		else
		{
			/* Close door spaces */
			ShutThatDoor(&door->d1);
			ShutThatDoor(&door->d2);
			ShutThatDoor(&door->d1flip);
			ShutThatDoor(&door->d2flip);
		}
	}

	AnimateItem(item);
}


int OnDrawBridge(ITEM_INFO *item, sint32 x, sint32 y)
{
	int ix, iy;

	ix = item->pos.z_pos >> WALL_SHIFT;
	iy = item->pos.x_pos >> WALL_SHIFT;

	x >>= WALL_SHIFT;
	y >>= WALL_SHIFT;

	if (item->pos.y_rot == 0 && y==iy && (x==ix-1 || x==ix-2))
		return (1);
	else if (item->pos.y_rot == -0x8000 && y==iy && (x==ix+1 || x==ix+2))
		return (1);
	else if (item->pos.y_rot == 0x4000 && x==ix && (y==iy-1 || y==iy-2))
		return (1);
	else if (item->pos.y_rot == -0x4000 && x==ix && (y==iy+1 || y==iy+2))
		return (1);

	return (0);
}


void DrawBridgeFloor(ITEM_INFO *item, sint32 x, sint32 y, sint32 z, sint32 *height)
{
	if (item->current_anim_state == DOOR_OPEN)
	{
		if (!OnDrawBridge(item, z, x))
			return;

		if (y <= item->pos.y_pos)
		{
			OnObject = 1;
			*height = item->pos.y_pos;
			if (item == lara_item)
				lara_item->item_flags[0] = 1;
		}
	}
}

void DrawBridgeCeiling(ITEM_INFO *item, sint32 x, sint32 y, sint32 z, sint32 *height)
{
	if (item->current_anim_state == DOOR_OPEN)
	{
		if (!OnDrawBridge(item, z, x))
			return;

		if (y > item->pos.y_pos)
			*height = item->pos.y_pos + STEP_L;
	}
}

void DrawBridgeCollision(sint16 item_num, ITEM_INFO *laraitem, COLL_INFO *coll)
{
	ITEM_INFO *item;

	item = &items[item_num];
	if (item->current_anim_state == DOOR_CLOSED)
		DoorCollision(item_num, laraitem, coll);
}


void InitialiseLift(sint16 item_number)
{
	LIFT_INFO *lift;

	// store start height
	items[item_number].data = lift = (LIFT_INFO *)game_malloc(sizeof(LIFT_INFO), 0);

	lift->start_height = items[item_number].pos.y_pos;
	lift->wait_time = 0;
}

void LiftControl(sint16 item_number)
{
	ITEM_INFO *item;
	LIFT_INFO *lift;
	sint16 room_number;

	item = &items[item_number];
	lift = (LIFT_INFO *)item->data;

	if (TriggerActive(item))
	{
		if (item->pos.y_pos < lift->start_height+STEP_L*22-16)
		{
			if (lift->wait_time < LIFT_WAIT_TIME)
			{
				item->goal_anim_state = DOOR_OPEN;
				lift->wait_time++;
			}
			else
			{
				item->goal_anim_state = DOOR_CLOSED;
				item->pos.y_pos += 16;
			}
		}
		else
		{
			item->goal_anim_state = DOOR_OPEN;
			lift->wait_time = 0;
		}
	}
	else
	{
		if (item->pos.y_pos > lift->start_height+16)
		{
			if (lift->wait_time < LIFT_WAIT_TIME)
			{
				item->goal_anim_state = DOOR_OPEN;
				lift->wait_time++;
			}
			else
			{
				item->goal_anim_state = DOOR_CLOSED;
				item->pos.y_pos -= 16;
			}
		}
		else
		{
			item->goal_anim_state = DOOR_OPEN;
			lift->wait_time = 0;
		}
	}

	AnimateItem(item);

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

#define LIFT_HEIGHT (STEP_L*5)

void LiftFloorCeiling(ITEM_INFO *item, sint32 x, sint32 y, sint32 z, sint32 *floor, sint32 *ceiling)
{
	int ix, iz, lx, lz, point_in_shaft;
	int lift_floor, lift_ceiling;

	ix = item->pos.x_pos >> WALL_SHIFT;
	iz = item->pos.z_pos >> WALL_SHIFT;

	lx = lara_item->pos.x_pos >> WALL_SHIFT;
	lz = lara_item->pos.z_pos >> WALL_SHIFT;

	x >>= WALL_SHIFT;
	z >>= WALL_SHIFT;

	point_in_shaft = ((x==ix || x+1==ix) && (z==iz || z-1==iz));

	lift_floor = item->pos.y_pos;
	lift_ceiling = item->pos.y_pos - LIFT_HEIGHT;

	*floor = 0x7fff;
	*ceiling = -0x7fff;

	if ((lx==ix || lx+1==ix) && (lz==iz || lz-1==iz)) // lara in lift shaft
	{
		if (item->current_anim_state == DOOR_CLOSED &&
			lara_item->pos.y_pos < lift_floor+STEP_L && lara_item->pos.y_pos > lift_ceiling+STEP_L) // lara in closed lift
		{
			if (point_in_shaft) // point in lift too, so constrain height
			{
				*floor = lift_floor;
				*ceiling = lift_ceiling+STEP_L;
			}
			else // point outside lift; while closed this is all wall
			{
				*floor = NO_HEIGHT;
				*ceiling = 0x7fff;
			}
		}
		else if (point_in_shaft) // point in shaft with lara, so depends on lara's position relative to lift
		{
			if (lara_item->pos.y_pos < lift_ceiling+STEP_L)
				*floor = lift_ceiling;
			else if (lara_item->pos.y_pos < lift_floor+STEP_L)
			{
				*floor = lift_floor;
				*ceiling = lift_ceiling+STEP_L;
			}
			else
				*ceiling = lift_floor+STEP_L;
		}
		// else point outside shaft, so not effected by lift at all
	}
	else if (point_in_shaft) // lara not in lift shaft, so only matters if point is
	{
		if (y <= lift_ceiling)
			*floor = lift_ceiling;
		else if (y < lift_floor+STEP_L)
		{
			if (item->current_anim_state == DOOR_CLOSED) // blocked if door closed
			{
				*floor = NO_HEIGHT;
				*ceiling = 0x7fff;
			}
			else
			{
				*floor = lift_floor;
				*ceiling = lift_ceiling+STEP_L;
			}
		}
		else
			*ceiling = lift_floor+STEP_L;
	}
}

void LiftFloor(ITEM_INFO *item, sint32 x, sint32 y, sint32 z, sint32 *height)
{
	sint32 floor, ceiling;

	LiftFloorCeiling(item, x, y, z, &floor, &ceiling);

	if (floor < *height)
	{
		OnObject = 1;
		*height = floor;
	}
}

void LiftCeiling(ITEM_INFO *item, sint32 x, sint32 y, sint32 z, sint32 *height)
{
	sint32 floor, ceiling;

	LiftFloorCeiling(item, x, y, z, &floor, &ceiling);

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


void BridgeFlatFloor(ITEM_INFO *item, sint32 x, sint32 y, sint32 z, sint32 *height)
{
	if (y <= item->pos.y_pos)
	{
		*height = item->pos.y_pos;
		height_type = WALL;
		OnObject = 1;

		if (item == lara_item)
			lara_item->item_flags[0] = 1;
	}
}

void BridgeFlatCeiling(ITEM_INFO *item, sint32 x, sint32 y, sint32 z, sint32 *height)
{
	if (y > item->pos.y_pos)
		*height = item->pos.y_pos + STEP_L;
}


sint32 GetOffset(ITEM_INFO *item, sint32 x, sint32 z)
{
	/* Get tilt offset for sloping bridge sections */
	if (item->pos.y_rot == 0)
		return( (WALL_L - x) & (WALL_L-1) );
	else if (item->pos.y_rot == -0x8000)
		return( x & (WALL_L-1) );
	else if (item->pos.y_rot == 0x4000)
		return( z & (WALL_L-1) );
	return( (WALL_L - z) & (WALL_L-1) );
}


void BridgeTilt1Floor(ITEM_INFO *item, sint32 x, sint32 y, sint32 z, sint32 *height)
{
	sint32 level;

	level = item->pos.y_pos + (GetOffset(item, x, z) >> 2);
	if (y <= level)
	{
		*height = level;
		height_type = WALL;
		OnObject = 1;
		if (item == lara_item)
			lara_item->item_flags[0] = 1;
	}
}

void BridgeTilt1Ceiling(ITEM_INFO *item, sint32 x, sint32 y, sint32 z, sint32 *height)
{
	sint32 level;

	level = item->pos.y_pos + (GetOffset(item, x, z) >> 2);
	if (y > level)
		*height = level + STEP_L;
}

void BridgeTilt2Floor(ITEM_INFO *item, sint32 x, sint32 y, sint32 z, sint32 *height)
{
	sint32 level;

	level = item->pos.y_pos + (GetOffset(item, x, z) >> 1);
	if (y <= level)
	{
		*height = level;
		height_type = WALL;
		OnObject = 1;
		if (item == lara_item)
			lara_item->item_flags[0] = 1;
	}
}

void BridgeTilt2Ceiling(ITEM_INFO *item, sint32 x, sint32 y, sint32 z, sint32 *height)
{
	sint32 level;

	level = item->pos.y_pos + (GetOffset(item, x, z) >> 1);
	if (y > level)
		*height = level + STEP_L;
}


enum copter_anim {COPTER_EMPTY, COPTER_SPIN, COPTER_TAKEOFF};

void CopterControl(sint16 item_number)
{
	/* GeneralControl is used for anything that just gets activated, moves and stops again */
	ITEM_INFO *item;
	sint16 room_number, *bounds;
	PHD_VECTOR pos;


	item = &items[item_number];
	if (item->current_anim_state == COPTER_SPIN)
	{
		if (item->flags & ONESHOT)
			item->goal_anim_state = COPTER_TAKEOFF;
	}

	AnimateItem(item);

	room_number = item->room_number;
	GetFloor(item->pos.x_pos, item->pos.y_pos, item->pos.z_pos, &room_number);
	if (room_number != item->room_number)
		ItemNewRoom(item_number, room_number);

	bounds = GetBoundsAccurate(item);
	pos.x = (bounds[0]+bounds[1]) >> 1;
	pos.y = (bounds[2]+bounds[3]) >> 1;
	pos.z = (bounds[4]+bounds[5]) >> 1;
	pos.x = lara_item->pos.x_pos + ((pos.x - lara_item->pos.x_pos) >> 2);
	pos.y = lara_item->pos.y_pos + ((pos.y - lara_item->pos.y_pos) >> 2);
	pos.z = lara_item->pos.z_pos + ((pos.z - lara_item->pos.z_pos) >> 2);
	SoundEffect(297, (PHD_3DPOS *)&pos, 0);

	if (item->status == DEACTIVATED)
		KillItem(item_number);
}

void GeneralControl(sint16 item_number)
{
	/* GeneralControl is used for anything that just gets activated, moves and stops again */
	ITEM_INFO *item;
	sint16 room_number;

	item = &items[item_number];

	if (TriggerActive(item))
		item->goal_anim_state = DOOR_OPEN;
	else
		item->goal_anim_state = DOOR_CLOSED;

	AnimateItem(item);

	room_number = item->room_number;
	GetFloor(item->pos.x_pos, item->pos.y_pos, item->pos.z_pos, &room_number);
	if (room_number != item->room_number)
		ItemNewRoom(item_number, room_number);

	if (item->status == DEACTIVATED)
	{
		RemoveActiveItem(item_number);
		item->flags |= ONESHOT;
	}
}


void DetonatorControl(sint16 item_number)
{
	int frame_number;
	ITEM_INFO *item;

	item = &items[item_number];

	AnimateItem(item);

	// frame 67 is when the plunger is fully down
	frame_number = item->frame_number - anims[item->anim_number].frame_base;

//	if (frame_number > 75 && frame_number < 100)
//		AddDynamicLight(item->pos.x_pos, item->pos.y_pos, item->pos.z_pos, 13, 11);

	if (frame_number == 80)
	{
		camera.bounce = -150;
		SoundEffect(105, NULL, 0);
	}

	if (item->status == DEACTIVATED)
		RemoveActiveItem(item_number);
}

void ControlAnimating_1_4(sint16 item_number)
{
	ITEM_INFO *item;

	item = &items[item_number];

	if (TriggerActive(item))
		AnimateItem(item);
}

