/*********************************************************************************************/
/*                                                                                           */
/* Control Phase                                                                             */
/*                                                                                           */
/*********************************************************************************************/

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

#ifdef PSX_VERSION
#include <sys\types.h>
#include <stdio.h>
#include <libsn.h>
#include "../spec_psx/typedefs.h"
#include "../spec_psx/psxinput.h"
#include "../spec_psx/memory.h"
#include "../spec_psx/specific.h"
#include "../spec_psx/gpu.h"
extern void CheckCheatMode();
extern void VibratePad();
extern int InfoFlag;
#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 "camera.h"
#include "control.h"
#include "input.h"
#include "sound.h"
#include "savegame.h"
#include "gameflow.h"
#include "inventry.h"
#include "hair.h"
#include "title.h"
#include "laramisc.h"
#include "effect2.h"
#include "pickup.h"
#include "demo.h"
#include "moveblok.h"
#include "traps.h"
#include "lot.h"
#include "health.h"
#include "fish.h"

#define LOOK_CHEAT

extern int assault_penalties, assault_penalty_display_timer;
extern int QuadbikeLapTimeDisplayTimer;

extern void LaraBreath(ITEM_INFO *);
extern void AlertNearbyGuards(ITEM_INFO *item);

uchar	HeavyTriggered;
uchar 	LaraOnPad;		// For House level.

#ifdef GAMEDEBUG
short itactive,fxactive,spactive;
#endif

int ClipTargetC(GAME_VECTOR *start, GAME_VECTOR *target, FLOOR_INFO *floor);

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

#define DEATH_WAIT	(10*30)

#ifdef PSX_VERSION
#define MAX_FRAMES 10
#endif

extern int level_complete;
extern char exit_message[];
extern char PoisonFlag;

extern void RemoveAmmoText();

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

uchar CurrentAtmosphere;
uchar IsAtmospherePlaying;
int compy_scared_timer;
int compys_attack_lara;
sint16 CarcassItem;

/* -------- outside room vars */
char *OutsideRoomTable;
short OutsideRoomOffsets[27*27];	// -1 = no rooms
short IsRoomOutsideNo;

sint16 FXType;

ITEM_INFO *items;

/* Global variable set by GetHeight() */
sint16 *trigger_index;

/* Map flipping stuff */
int flipmap[MAX_FLIPMAPS], flip_status;

/* CD flags stuff (99 tracks max) */
sint16 cdtrack=-1;
char cd_flags[MAX_CD_TRACKS];

/* Flag set by camera to change behaviour of GetHeight/Ceiling */
int chunky_flag;

/* What type of thing has Set current height in GetHeight */
int height_type;
int tiltxoff,tiltyoff;
int OnObject;
ITEM_INFO *CeilingObject;

/* Counts amount of time User hasnt pressed input */
int		noinput_count=0;

/* Flag to show User has pressed reset Or a fatal error has occured */
int		reset_flag=0;

/* Animation playback rate Calculated as (32768*60)/(MONITOR_RATE)
 	using different values from 32768 produces Jerky movement though */
int animation_rate = 32768;

/* When map is flipped, may set off an effect which lasts some time */
int flipeffect = -1, fliptimer=0;

/* LOS routine keeps a list of which rooms LOS passes through for ObjectOnLOS() */
int los_rooms[20], number_los_rooms=0;

#ifdef PSX_VERSION
int	psx_demo_mode=0;
#else
void CD_Audio(void);
int game_closedown=0;
#endif

long	GH_count,GC_count,GD_count,GF_count;

/*---------------------------------------------------------------------------
 *	Macros
\*--------------------------------------------------------------------------*/

/* Macro for LOS routines */
#define RETURN_TARGET  {target->x=x; target->y=y; target->z=z; target->room_number=last_room;   return(0);}
#define RETURN_TARGET2 {target->x=x; target->y=y; target->z=z; target->room_number=room_number; return(-1);}

#define TRIG_BITS(T) ((T & 0x3fff) >> 10)

/********************************** GLOBAL VARIABLES *****************************************/

long	CheckNoColCeilingTriangle(FLOOR_INFO *floor, long x, long z);
long	CheckNoColFloorTriangle(FLOOR_INFO *floor, long x, long z);

#ifdef PSX_VERSION
extern TEXTSTRING *demotxt;
#endif

extern sint32 rand_1;
/*********************************** FUNCTION CODE *******************************************/

sint32 ControlPhase(sint32 nframes, int demo_mode)
{
	/* Do the game control calculations (+ time off for inventory screen).
		Returns non-zero if game-over */
	int i;
	uint32		return_val;
	sint16 		item_num,nex;
	static int	framecount = 0;

/* Limit number of frames to ensure playability */

	if (nframes > MAX_FRAMES)
		nframes = MAX_FRAMES;

#ifdef PAL
	framecount += nframes * 6;
	for ( ; framecount>=0; framecount-=10 )
#else
	framecount += nframes;
	for ( ; framecount>0 ; framecount-=2 )
#endif
	{
#ifdef PC_VERSION
		if (cdtrack > 0)
			S_CDLoop();
#endif
		if (!gameflow.cheatmodecheck_disabled)
			CheckCheatMode();

	/* If level complete return success */
		if (level_complete)
			return(1);

	/* Get user input and place in 'input' sint32 */
		S_UpdateInput();

		if (reset_flag)
			return (EXIT_TO_TITLE);

	/* Demo mode overrides this input */
		if (demo_mode)
		{
			// TODO: PC version used to exit demo on ANY key press
			if (input)
				return (gameflow.on_demo_interrupt);
#ifdef PSX_VERSION
			if ( PadConnected && psx_demo_mode==2 && demotxt )
			{
				T_ChangeText( demotxt, GF_PSXStrings[PSSTR_DEMOTEXT]);
				psx_demo_mode = 1;
			}
			if ( !PadConnected && psx_demo_mode==1 && demotxt )
			{
				T_ChangeText( demotxt, GF_PSXStrings[PSSTR_NOJOY_TEXT] );
				psx_demo_mode = 2;
			}
#endif
			GetDemoInput();

			if (input == -1)
			{
				input=0; // input = -1 is very bad
#ifdef PSX_VERSION
				Motors[0] = Motors[1] = 0;
#endif
				return (gameflow.on_demo_end); // TODO: do we want repeat demo play?
			}
		}
		else if (gameflow.noinput_timeout)
		{
			if (input)
				noinput_count = 0;
			else
			{
				/* If no input for a while, reset */
				noinput_count++;

				if (noinput_count > gameflow.noinput_time)
					return (STARTDEMO);
			}
		}

#ifdef LOOK_CHEAT
		if (input & IN_LOOK)
		{
			Inv_AddItem( M16_ITEM );
			Inv_AddItem( SHOTGUN_ITEM );
			Inv_AddItem( UZI_ITEM );
			Inv_AddItem( MAGNUM_ITEM );
			Inv_AddItem( GUN_ITEM );
			Inv_AddItem( ROCKET_GUN_ITEM );
			Inv_AddItem( GRENADE_GUN_ITEM );
			Inv_AddItem( HARPOON_ITEM );
			lara.magnums.ammo = 1000;
			lara.uzis.ammo = 1000;
			lara.shotgun.ammo = 1000;
			lara.harpoon.ammo = 1000;
			lara.rocket.ammo = 1000;
			lara.grenade.ammo = 1000;
			lara.m16.ammo = 1000;
			savegame.QuadbikeKeyFlag = 1;

			Inv_AddItem(SAVEGAME_CRYSTAL_ITEM);
		}
#endif

	/* Has lara Died if so Exit after a while Or Allow user to bring up Inventory*/

		if ((lara.death_count > DEATH_WAIT)
		|| ((lara.death_count > 2*30) && (input))
		||  (overlay_flag == 2))
		{
			if (demo_mode)
			{
#ifdef PSX_VERSION
				Motors[0] = Motors[1] = 0;
#endif
				return (gameflow.ondeath_demo_mode); // TODO: do we want repeat demo play?
			}
			else
			{
				if (CurrentLevel == LV_GYM)
					return(EXIT_TO_TITLE);

   				if (gameflow.ondeath_ingame)
   					return gameflow.ondeath_ingame;

				if (overlay_flag == 2)
				{
					overlay_flag = 1;
					return_val = Display_Inventory(INV_DEATH_MODE);

					if (return_val)
						return return_val;
				}
				else
					overlay_flag = 2;
			}
		}

	/* Option/inventory selected */

#ifdef PC_VERSION
		if ( (input&IN_OPTION || input&IN_LOAD || input&IN_SAVE || overlay_flag<=0) && !lara.death_count && !lara.extra_anim)
		{
#else
		if (((input & IN_OPTION) || (!overlay_flag))
		&&   (!lara.death_count)
		&&   (!lara.extra_anim))
		{
			Motors[0] = Motors[1] = 0;
#endif
			if (overlay_flag <= 0)
			{
				if (overlay_flag == -1)
					return_val = Display_Inventory(INV_LOAD_MODE);

				else if (overlay_flag == -2)
					return_val = Display_Inventory(INV_SAVE_MODE);

				else
					return_val = Display_Inventory(INV_GAME_MODE);

				overlay_flag = 1;

				if (return_val)
#ifdef PC_VERSION
				{
					/* Save game in slot (or in gym start new game) */
					if (Inventory_ExtraData[0] == 1)
					{
						if (CurrentLevel == LV_GYM)
							return (STARTGAME|LV_FIRSTLEVEL);
						else
						{
							CreateSaveGameInfo();
							S_SaveGame(&savegame, sizeof(SAVEGAME_INFO), Inventory_ExtraData[1]);
							S_SaveSettings();
						}
					}
					else
						return(return_val);
				}
#else
				// PSX RESTART LEVEL CHOSEN
				return(return_val);
#endif
			}
			else
			{
#ifdef PC_VERSION
				if (!gameflow.loadsave_disabled)
				{
					if (input & IN_LOAD)
						overlay_flag = -1;
					else if (input & IN_SAVE)
						overlay_flag = -2;
					else
						overlay_flag = 0;
				}
				else
#endif
				overlay_flag = 0;
			}
		}

#ifdef PSX_VERSION
		if ( (input&IN_PAUSE || (!PadConnected && demo_mode==0)) && !lara.death_count && !lara.extra_anim)
		{
			Motors[0] = Motors[1] = 0;
			RemoveAmmoText();
			S_Pause();
			return(0);
		}
#endif

	// Dynamic lights are created by objects each frame (so list must be cleared at start of each frame)
		ClearDynamics();

	/* Call control routines for active items on linked list (can create/destroy items) */
#ifdef	GAMEDEBUG
		itactive = fxactive = spactive = 0;
#endif

		item_num = next_item_active;
		while ( item_num!=NO_ITEM )
		{
			nex = items[item_num].next_active;
			if ( objects[items[item_num].object_number].control )
				(*objects[items[item_num].object_number].control)(item_num);
			item_num = nex;
#ifdef	GAMEDEBUG
	 		itactive++;
#endif
		}

	/* Call control routines for active effects on linked list (can create/destroy items) */
		item_num = next_fx_active;
		while (item_num != NO_ITEM)
		{
			nex = effects[item_num].next_active;
			if ( objects[ effects[item_num].object_number ].control )
				(*objects[ effects[item_num].object_number ].control)(item_num);
			item_num = nex;
#ifdef	GAMEDEBUG
	 		fxactive++;
#endif
		}

		{
			FLOOR_INFO	*floor;
			sint32	height,ceiling;
			sint16 	room_number;

			room_number = lara_item->room_number;
			floor = GetFloor(lara_item->pos.x_pos, lara_item->pos.y_pos-(STEP_L*2), lara_item->pos.z_pos, &room_number);
		}

		if (KillEverythingFlag)
			KillEverything();

		if (SmokeCountL)
			SmokeCountL--;
		if (SmokeCountR)
			SmokeCountR--;
		if (SplashCount)
			SplashCount--;

		if (lara.has_fired && ((wibble & 127) == 0))
		{
			AlertNearbyGuards(lara_item);
			lara.has_fired = 0;
		}

		if ((lara.poisoned>=16) && ((wibble & 255) == 0))
		{
			if (lara.poisoned > 0x100) 	// Maximum hit point reduction from poisoning is currently 16.
				lara.poisoned = 0x100;
			lara_item->hit_points -= lara.poisoned>>4;

			PoisonFlag = 16;
		}
	/* -------- reset joint position table */
		for ( i=0 ; i<15 ; i++ )
			GotJointPos[i] = 0;

		lara_item->item_flags[0] = 0;	// Clear 'On Trapdoor floor'

		LaraControl(0);

		HairControl(0);

		if (lara.electric)
		{
			if (lara.electric >= 12)
				TriggerDynamic(lara_item->pos.x_pos, lara_item->pos.y_pos, lara_item->pos.z_pos, (GetRandomControl()&3)+8, 0, (GetRandomControl()&7)+8, (GetRandomControl()&7)+16);
			else
				TriggerDynamic(lara_item->pos.x_pos, lara_item->pos.y_pos, lara_item->pos.z_pos, 25-(lara.electric<<1)+(GetRandomControl()&1), (16-lara.electric)+(GetRandomControl()&7), 32-lara.electric, 31);
			SoundEffect( 127,&lara_item->pos,0);	// Electric loop.
			SoundEffect( 128,&lara_item->pos,0);	// Crackle.
		}

	/* Nicer if camera position is calculated every frame too */
		CalculateCamera();

		wibble += 4;
		wibble &= 0xfc;

		if ((CurrentLevel == LV_ANTARC)
		||  (CurrentLevel == LV_MINES))
			LaraBreath(lara_item);

		UpdateSparks();
		UpdateSplashes();
		UpdateBats();

#ifdef GAMEDEBUG
		if (InfoFlag)
		{
			char buf[80];
			SPARKS	*sptr;
			sptr = &spark[0];
			for (i=0;i<MAX_SPARKS;i++)
			{
				if (sptr->On)
					spactive++;
				sptr++;
			}
			sprintf(buf,"IT %d FX %d SP %d",itactive,fxactive,spactive);
			PrintDbug(2, 10,buf);
		}
#endif

	/* Do global sound effects too */
		SoundEffects();

		/* Increment/Decrement Timers */
		health_bar_timer--;
		if (CurrentLevel != LV_GYM || assault_timer_active)
			savegame.timer++;
		if (assault_penalty_display_timer)
			assault_penalty_display_timer--;
		if (QuadbikeLapTimeDisplayTimer)
			QuadbikeLapTimeDisplayTimer--;

#ifdef PSX_VERSION
		VibratePad();
#endif
	}
	return(0);
}


/*****************************************************************************
 *			Do Physical Movement/Animation of Item
 ****************************************************************************/
/*
 * Work out what animation frame this thing wants to be in. Also work out new
 * position of object and return this in 'pos' as may turn out to be an
 * illegal position
 *
 */
void	AnimateItem(ITEM_INFO *item)
{
	int 		i;
	ANIM_STRUCT *anim;
	sint16 		*command;
	sint32 		speed;
	uint16		num;
	uint16		type;

	/* Clear flags */
	item->touch_bits = 0;
	item->hit_status = 0;

	/* Get pointer to animation data */
	anim = &anims[item->anim_number];

	/* Increase the frame number */
	item->frame_number++;

	/* Check for animation transitions */
	if (anim->number_changes > 0)
	{
		if (GetChange(item, anim))
		{
			anim = &anims[item->anim_number];
			item->current_anim_state = anim->current_anim_state;

			/* Clear required state if it has been reached */
			if (item->required_anim_state == item->current_anim_state)
				item->required_anim_state = 0;
		}
	}

	/* Check if at end of animation */
	if (item->frame_number > anim->frame_end)
	{
		/* Do any commands (ignoring sound FX) */
		if (anim->number_commands > 0)
		{
			command = &commands[anim->command_index];
			for (i=anim->number_commands; i>0; i--)
			{
				switch (*(command++))
				{
				case COMMAND_MOVE_ORIGIN:
					/* Translate (allowing for y-axis rotation) */
					TranslateItem(item, (sint32)*(command), (sint32)*(command+1), (sint32)*(command+2));
					command += 3;
					break;

				case COMMAND_JUMP_VELOCITY:
					item->fallspeed = *(command++);		// Get Upward Velocity
					item->speed = *(command++);         // Get Forward Velocity
					item->gravity_status = 1;           // turn gravity ON
					break;

				case COMMAND_ATTACK_READY:
					break;

				case COMMAND_DEACTIVATE:
					if (objects[item->object_number].intelligent)
						item->after_death = 1;
					else
						item->after_death = 64;	// Don't let non-intelligent baddies bleed when anim finished!
					item->status = DEACTIVATED;
					break;

				case COMMAND_SOUND_FX:
				case COMMAND_EFFECT:
					command += 2;
					break;
				}
			}
		}

		/* Update animation information */
		item->anim_number = anim->jump_anim_num;
		item->frame_number = anim->jump_frame_num;

		anim = &anims[item->anim_number];
		if (item->current_anim_state != anim->current_anim_state)
		{
			item->current_anim_state = anim->current_anim_state;

			/* Need to reset goal anim if gone to new state, else it will be used in the new state */
			item->goal_anim_state = item->current_anim_state;
		}

		/* Clear required state when arrived at */
		if (item->required_anim_state == item->current_anim_state)
			item->required_anim_state = 0;
	}

	/* Do any sound FX commands */
	if (anim->number_commands > 0)
	{
		command = &commands[anim->command_index];
		for (i=anim->number_commands; i>0; i--)
		{
			switch ( *(command++) )
			{
				case COMMAND_MOVE_ORIGIN:
					command += 3;
				break;

				case COMMAND_JUMP_VELOCITY:
					command += 2;
				break;

				case COMMAND_ATTACK_READY:
				break;

				case COMMAND_DEACTIVATE:
				break;

				case COMMAND_SOUND_FX:
					if ( item->frame_number==*(command) )
					{
						type = *(command+1)&0xc000;
						num = *(command+1)&0x3fff;

						// if this is water_creature, then always UNDERWATER
						if (objects[item->object_number].water_creature)
							SoundEffect((int)num, &item->pos, UNDERWATER);
						else if (item->room_number == NO_ROOM)
						{
							// assume this is the new weapons; so above water (apart from harpoon which plays if either)
							item->pos.x_pos = lara_item->pos.x_pos;
							item->pos.y_pos = lara_item->pos.y_pos - LARA_HITE;
							item->pos.z_pos = lara_item->pos.z_pos;
							if (item->object_number == HARPOON_GUN)
								SoundEffect((int)num, &item->pos, SFX_ALWAYS);
							else
								SoundEffect((int)num, &item->pos, 0);
						}
						else if (room[item->room_number].flags & UNDERWATER)
						{
							// must be shallow water if in water but not water_creature
							if (type == SFX_LANDANDWATER || type == SFX_WATERONLY)
								SoundEffect((int)num, &item->pos, 0);
						}
						else if (type == SFX_LANDANDWATER || type == SFX_LANDONLY) // else not in the water
							SoundEffect((int)num, &item->pos, 0);
					}
					command += 2;
				break;

				case COMMAND_EFFECT:
					if (item->frame_number == *(command))
					{
						FXType = command[1] & 0xc000;
						num = *(command+1)&0x3fff;
						(*effect_routines[(int)num])( item );
					}
					command += 2;
				break;
			}
		}
	}

	if ( !item->gravity_status ) 					// Only Calculate new velocities
	{                                               // if on the Ground...
		speed = anim->velocity;
		if (anim->acceleration != 0)
			speed += anim->acceleration * (item->frame_number - anim->frame_base);
		item->speed = (sint16)(speed >> 16);
	}
	else                                            // If gravity ON
	{                                               // do Up/down movement
		item->fallspeed += (item->fallspeed<FASTFALL_SPEED) ? GRAVITY : 1;
		item->pos.y_pos += item->fallspeed;
	}
	item->pos.x_pos += (phd_sin(item->pos.y_rot) * item->speed) >> W2V_SHIFT;
	item->pos.z_pos += (phd_cos(item->pos.y_rot) * item->speed) >> W2V_SHIFT;
}


int GetChange(ITEM_INFO *item, ANIM_STRUCT *anim)
{
	/* Go through changes to find one that matches required goal animation state, then see
		if have reached point in current animation where transition can occur */
	int i, j;
	CHANGE_STRUCT *change;
	RANGE_STRUCT *range;

	/* Don't bother if already in required state */
	if (item->current_anim_state == item->goal_anim_state)
		return (0);

	change = &changes[anim->change_index];
	for (i=0; i<anim->number_changes; i++, change++)
	{
		if (change->goal_anim_state == item->goal_anim_state)
		{
			range = &ranges[change->range_index];
			for (j=0; j<change->number_ranges; j++, range++)
			{
				if (item->frame_number >= range->start_frame && item->frame_number <= range->end_frame)
				{
					item->anim_number	= range->link_anim_num;
					item->frame_number = range->link_frame_num;

					/* Success */
					return (1);
				}
			}
		}
	}

	/* Failure */
	return (0);
}


void TranslateItem(ITEM_INFO *item, sint32 x, sint32 y, sint32 z)
{
	/* Translate item allowing for y-axis rotation */
	sint32 cosy, siny;

	cosy = phd_cos(item->pos.y_rot);
	siny = phd_sin(item->pos.y_rot);

	item->pos.x_pos += ( cosy * x + siny * z) >> W2V_SHIFT;
	item->pos.y_pos += y;
	item->pos.z_pos += (-siny * x + cosy * z) >> W2V_SHIFT;
}

/*
 * Get floor allowing for changes in room through pits/skylights
 *
 */

//#ifdef PC_VERSION
#if 1
FLOOR_INFO *GetFloor(sint32 x, sint32 y, sint32 z, sint16 *room_number)
{
	sint16 data;
	ROOM_INFO *r;
	FLOOR_INFO *floor;
	sint32 x_floor, y_floor, retval;

#ifdef GAMEDEBUG
	// DEBUG code: should never be needed in working code
	if (*room_number < 0 || *room_number >= number_rooms)
	{
		wsprintf(exit_message, "GetFloor(): Invalid room number %d", *room_number);
		return;
//		S_ExitSystem(exit_message);
	}
#endif

	r = &room[*room_number];

	do {
	/* Clip position to edge of room (that way doorways are detected) */
	  	x_floor = (z - r->z) >> WALL_SHIFT;
		y_floor = (x - r->x) >> WALL_SHIFT;
	/* Gav 19/9/96: this more complex code is just to ensure we don't test
	 * the corner of a room's floor data, as this can cause problems when 4
	 * rooms join at a point */
		if (x_floor <= 0)
		{
			x_floor = 0;
			if (y_floor < 1)
				y_floor = 1;
			else if (y_floor > r->y_size-2)
				y_floor = r->y_size-2;
		}
		else if (x_floor >= r->x_size-1)
		{
			x_floor = r->x_size - 1;
			if (y_floor < 1)
				y_floor = 1;
			else if (y_floor > r->y_size-2)
				y_floor = r->y_size-2;
		}
		else if (y_floor < 0)
			y_floor = 0;
		else if (y_floor >= r->y_size)
			y_floor = r->y_size - 1;

	/* If doorway, go through and retest, else move onto next stage */
		floor = &r->floor[x_floor + (y_floor * r->x_size)];
		data = GetDoor(floor);
		if (data != NO_ROOM)
		{
			*room_number = data;
			r = &room[data];
		}

	} while (data != NO_ROOM);

/* If below floor and pit, go down */
	if (y >= ((sint32)floor->floor << 8))
	{
		do {
			if (floor->pit_room == NO_ROOM)
				return (floor);

		// Check to see if on a no collision triangle.

			if ((retval = CheckNoColFloorTriangle(floor, x, z)) == 1)	// If on collision side of a split quad then stop.
				break;

			if (retval == -1)	// Hit a no collision triangle so floor->floor is incorrect... recheck.
			{
				//if (y < (r->y+r->minfloor))
				if (y < r->minfloor)	// < minfloor
					break;
			}

			*room_number = floor->pit_room;
			r = &room[floor->pit_room];
			floor = &r->floor[((z - r->z) >> WALL_SHIFT) + (((x - r->x) >> WALL_SHIFT) * r->x_size)];
		}
		while (y >= ((sint32)floor->floor << 8));
	}
	else if (y < ((sint32)floor->ceiling << 8))
	{
	/* If above ceiling and skylight, go up */
		do {
			if (floor->sky_room == NO_ROOM)
				return (floor);

		// Check to see if on a no collision triangle.

			if ((retval = CheckNoColCeilingTriangle(floor, x, z)) == 1)	// If on collision side of a split quad then stop.
				break;

			if (retval == -1)	// Hit a no collision triangle so floor->ceiling is incorrect... recheck.
			{
#ifdef	PC_VERSION
				if (y >= r->maxceiling)
#else
				if (y >= r->y)
#endif
					break;
			}

			*room_number = floor->sky_room;
			r = &room[floor->sky_room];
			floor = &r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size];
		}
		while (y < ((sint32)floor->ceiling << 8));
	}

	return (floor);
}
#endif


//#ifdef PC_VERSION
#if	1
/******************************************************************************
 *				Return Height of Water Surface
 *****************************************************************************/
sint32 GetWaterHeight(sint32 x, sint32 y, sint32 z, sint16 room_number)
{
	/* Returns water surface or NO_HEIGHT if none found */
	sint32 x_floor, y_floor;
	ROOM_INFO *r;
	FLOOR_INFO *floor;
	sint16 data;

//	r = &room[room_number];
//	floor = &r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size];

#ifdef GAMEDEBUG
/*
  	if ( !((x>=(room[room_number].x) && x<room[room_number].x+(room[room_number].y_size)*WALL_L) &&
		  (z>=(room[room_number].z) && z<room[room_number].z+(room[room_number].x_size)*WALL_L)) )
	{
		PrintDbug(2, 6,"Error in GetWaterHeight()");
		return NO_HEIGHT;
	}
*/
#endif

	// BUG FIX 25/10/97: Possible evil bug? x and y could be outside room boundaries
	r = &room[room_number];

	do {
		/* Clip position to edge of room (that way doorways are detected) */
	  	x_floor = (z - r->z) >> WALL_SHIFT;
		y_floor = (x - r->x) >> WALL_SHIFT;

		/* Gav 19/9/96: this more complex code is just to ensure we don't test the corner of a room's floor data,
							 as this can cause problems when 4 rooms join at a point */
		if (x_floor <= 0)
		{
			x_floor = 0;
			if (y_floor < 1)
				y_floor = 1;
			else if (y_floor > r->y_size-2)
				y_floor = r->y_size-2;
		}
		else if (x_floor >= r->x_size-1)
		{
			x_floor = r->x_size - 1;
			if (y_floor < 1)
				y_floor = 1;
			else if (y_floor > r->y_size-2)
				y_floor = r->y_size-2;
		}
		else if (y_floor < 0)
			y_floor = 0;
		else if (y_floor >= r->y_size)
			y_floor = r->y_size - 1;

		/* If doorway, go through and retest, else move onto next stage */
		floor = &r->floor[x_floor + y_floor * r->x_size];
		data = GetDoor(floor);
		if (data != NO_ROOM)
		{
			room_number = data;
			r = &room[data];
		}
	}
	while (data != NO_ROOM);

//	if (r->flags & UNDERWATER)
	if (r->flags & (UNDERWATER|SWAMP))
	{
		/* Upwards until leave water */
		while (floor->sky_room != NO_ROOM)
		{
			if (CheckNoColCeilingTriangle(floor, x, z) == 1)
				break;
			r = &room[floor->sky_room];
			//if (!(r->flags & UNDERWATER))
			if (!(r->flags & (UNDERWATER|SWAMP)))
				return (r->minfloor);
//				return (floor->ceiling << 8);
			floor = &r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size];
		}
#ifdef	PC_VERSION
		return (r->maxceiling);
#else
		return (r->y);
#endif
//		return( floor->ceiling<<8 );
	}
	else
	{
		/* Go down until hit water */
		while (floor->pit_room != NO_ROOM)
		{
			if (CheckNoColFloorTriangle(floor, x, z) == 1)
				break;
			r = &room[floor->pit_room];
//			if (r->flags & UNDERWATER)
			if (r->flags & (UNDERWATER|SWAMP))
			{
#ifdef	PC_VERSION
				return (r->maxceiling);
#else
				return (r->y);
#endif
//				floor = &r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size];
//				return (floor->ceiling << 8);
//				return (floor->floor << 8);
			}
			floor = &r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size];
		}
	}

	return (NO_HEIGHT);
}
#endif

//#ifdef PC_VERSION
#if 1

sint32 GetHeight(FLOOR_INFO *floor, sint32 x, sint32 y, sint32 z)
{
	/* Get floor height allowing for floor type. Also allows triggers to be activated */
	sint32 		xoff, yoff,	height, height2;
	ROOM_INFO 	*r;
	ITEM_INFO 	*item;
	sint16 		*data, type, trigger;
	sint16		tilts,dx,dz,t0,t1,t2,t3,hadj;

	OnObject = 0;
	height_type = WALL;

/* -------- if over a pit, then go to bottom to find appropriate floor */

	while (floor->pit_room != NO_ROOM)
	{
		if (CheckNoColFloorTriangle(floor, x, z) == 1)
			break;

		r = &room[floor->pit_room];
		floor = &r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size];
	}

	height = floor->floor << 8;
	if (height == NO_HEIGHT)
		return (height);

	if (GF_NoFloor && GF_NoFloor==height)
		height = 0x4000;

	height2 = height;

	/* Set global 'trigger_index' for use by TestTriggers() */
	trigger_index = NULL;

	if (!floor->index)
		return (height);

	tiltxoff = tiltyoff = 0;

	/* If data exists, may be TILT_TYPE or TRIGGER_TYPE or whatever */
	data = &floor_data[floor->index];

	do {
		type = *(data++);

		switch (type & DATA_TYPE)
		{
			case SPLIT1:
			case SPLIT2:
			case NOCOLF1T:
			case NOCOLF1B:
			case NOCOLF2T:
			case NOCOLF2B:
				tilts = *data;
				t0 = tilts&15;
				t1 = (tilts>>4)&15;
				t2 = (tilts>>8)&15;
				t3 = (tilts>>12)&15;

				dx = x&1023;
				dz = z&1023;

				xoff = yoff = 0;

				height_type = SPLIT_TRI;

				if (	(type&DATA_TYPE) == SPLIT1 ||
					(type&DATA_TYPE) == NOCOLF1T ||
					(type&DATA_TYPE) == NOCOLF1B)
				{
					if (dx <= (1024-dz))	// In top triangle?
					{
						hadj = (type>>10)&0x1f;
						if (hadj&0x10)	// negative?
							hadj|=0xfff0;
						height += hadj<<8;
						xoff = t2-t1;
						yoff = t0-t1;
					}
					else
					{
						hadj = (type>>5)&0x1f;
						if (hadj&0x10)	// negative?
							hadj|=0xfff0;
						height += hadj<<8;
						xoff = t3-t0;
						yoff = t3-t2;
					}
				}
				else		// SPLIT2
				{
					if (dx <= dz)	// In top triangle?
					{
						hadj = (type>>10)&0x1f;
						if (hadj&0x10)	// negative?
							hadj|=0xfff0;
						height += hadj<<8;
						xoff = t2-t1;
						yoff = t3-t2;
					}
					else
					{
						hadj = (type>>5)&0x1f;
						if (hadj&0x10)	// negative?
							hadj|=0xfff0;
						height += hadj<<8;
						xoff = t3-t0;
						yoff = t0-t1;

					}
				}

				/* Camera system ignores steep slopes and treats them as solid blocks */
				tiltxoff = xoff;
				tiltyoff = yoff;

				if (chunky_flag)
				{
					long	h1,h2;

					hadj = (hadj>>10)&0x1f;
					if (hadj&0x10)	// negative?
						hadj|=0xfff0;
					h1 = h2 = height2;
					h1 += hadj<<8;

					hadj = (hadj>>5)&0x1f;
					if (hadj&0x10)	// negative?
						hadj|=0xfff0;
					h2 += hadj<<8;

					if (h1 < h2)
						height = h1;
					else
						height = h2;
				}
				else
				{
					if ( (ABS(xoff))>2 || (ABS(yoff))>2 )
						height_type = DIAGONAL;
					else if (height_type != SPLIT_TRI)
						height_type = SMALL_SLOPE;

					if (xoff < 0)
						height -= (z & (WALL_L-1)) * xoff >> 2;
					else
						height += ((WALL_L-1-z) & (WALL_L-1)) * xoff >> 2;

					if (yoff < 0)
						height -= (x & (WALL_L-1)) * yoff >> 2;
					else
						height += ((WALL_L-1-x) & (WALL_L-1)) * yoff >> 2;
				}

				data++;
				break;

			case TILT_TYPE:
				tiltxoff = xoff = (*data) >> 8;
				tiltyoff = yoff = (signed char)(*data);

				/* Camera system ignores steep slopes and treats them as solid blocks */
				if ( !chunky_flag || ((ABS(xoff)) <= 2 && (ABS(yoff)) <= 2) )
				{
					if ( (ABS(xoff))>2 || (ABS(yoff))>2 )
						height_type = BIG_SLOPE;
					else
						height_type = SMALL_SLOPE;

					if (xoff < 0)
						height -= (z & (WALL_L-1)) * xoff >> 2;
					else
						height += ((WALL_L-1-z) & (WALL_L-1)) * xoff >> 2;

					if (yoff < 0)
						height -= (x & (WALL_L-1)) * yoff >> 2;
					else
						height += ((WALL_L-1-x) & (WALL_L-1)) * yoff >> 2;
				}

				data++;
				break;

			case SPLIT3:
			case SPLIT4:
			case NOCOLC1T:
			case NOCOLC1B:
			case NOCOLC2T:
			case NOCOLC2B:
			case ROOF_TYPE:
			case DOOR_TYPE:
				data++;
				break;

			case LAVA_TYPE:
				trigger_index = data-1;
				break;

			case CLIMB_TYPE:
			case MONKEY_TYPE:
			case MINEL_TYPE:
			case MINER_TYPE:
				if (!trigger_index)
					trigger_index = data-1;
				break;

			case TRIGGER_TYPE:
				/* Some triggers also have object associated and so effect floor height */
				if (!trigger_index)
					trigger_index = data-1;

				data++;
				do
				{
					trigger = *(data++);
					if (TRIG_BITS(trigger) != TO_OBJECT)
					{
						if (TRIG_BITS(trigger) == TO_CAMERA)
							trigger = *(data++);
						continue;
					}

					/* Call height determining routine if any exists */
					item = &items[trigger & VALUE_BITS];
					if (objects[item->object_number].floor)
						(*objects[item->object_number].floor)(item, x, y, z, &height);
				}
				while (!(trigger & END_BIT));
				break;

			default:
				S_ExitSystem("GetHeight(): Unknown type");
		}
	}
	while (!(type & END_BIT));

	return (height);
}

#endif


/*
 * Check triggers to see if same camera as last time is on this space
 *
 */

void RefreshCamera(sint16 type, sint16 *data)
{
	sint16 trigger, value, target_ok;

	/* See if triggers activated */
	target_ok = 2;
	do
	{
		trigger = *(data++);
		value = trigger & VALUE_BITS;

		switch (TRIG_BITS(trigger))
		{
			case TO_CAMERA:
			/* Cameras store their flags/timers etc in the next sint16 */
				data++;

				if (value == camera.last)
				{
					camera.number = value;

				/* If same fixed camera as last time but timer has run out, don't refresh it */
					if ((camera.timer < 0)
					||  (camera.type == LOOK_CAMERA)
					||  (camera.type == COMBAT_CAMERA))
					{
						camera.timer = -1;
						target_ok = 0;
						break;
					}
					camera.type = FIXED_CAMERA;
					target_ok = 1;
				}
				else
					target_ok = 0;
				break;

		case TO_TARGET:
			/* LOOK_CAMERA overrides special cameras */
			if (camera.type == LOOK_CAMERA || camera.type == COMBAT_CAMERA)
//			if (input & IN_LOOK)
				break;

			camera.item = &items[value];
			break;
		}
	}
	while (!(trigger & END_BIT));

	/* Cancel target if controlled by inactive fixed camera or looked at by Lara before */
	if (camera.item)
		if (!target_ok || (target_ok == 2 && camera.item->looked_at && camera.item != camera.last_item))
			camera.item = NULL;

	/* If no camera, then reset timer (else gets reactivated when return to trigger) */
	if (camera.number == NO_CAMERA && camera.timer>0)
		camera.timer = -1;
}


/**************************************************************************************************
 * Test triggers to see if cameras, traps etc are activated. 'heavy' of 1 implies this is an object
 * 			like a rolling ball trying to activate stuff and not Lara
 *************************************************************************************************/
void TestTriggers(sint16 *data, int heavy)
{
	ITEM_INFO *item,*camera_item;
	sint16 camera_flags, camera_timer;
	sint16 type, trigger, value, timer, flags;
	int switch_off=0, flip=0, flip_available=0, neweffect=-1;

	HeavyTriggered = 0;

	if (!heavy)
	{
		lara.climb_status = 0;
		lara.CanMonkeySwing = 0;
		lara.MineL = 0;
		lara.MineR = 0;
	}

	/* If no triggers, then need to reset fixed cameras */
	if (data == NULL)
		return;

	/* LAVA_TYPE can come before other triggers */
	if ((*data & DATA_TYPE) == LAVA_TYPE)
	{
		if (!heavy && (lara_item->pos.y_pos == lara_item->floor || lara.water_status != LARA_ABOVEWATER))
			LavaBurn(lara_item);

		/* May only be a LAVA_TYPE */
		if (*data & END_BIT)
			return;

		data++;
	}

	/* CLIMB_TYPE can come next */
	if ((*data & DATA_TYPE) == CLIMB_TYPE)
	{
		if (!heavy)
		{
			int quad;

			quad = (uint16)(lara_item->pos.y_rot + 0x2000)/0x4000;
			if ((1<<(quad+8)) & *data)
				lara.climb_status = 1;
		}

		if (*data & END_BIT)
			return;

		data++;
	}

/* -------- MONKEY_TYPE */

	if ((*data & DATA_TYPE) == MONKEY_TYPE)
	{
		if (!heavy)
			lara.CanMonkeySwing = 1;

		if (*data & END_BIT)
			return;

		data++;
	}

/* -------- MINEL_TYPE */

	if ((*data & DATA_TYPE) == MINEL_TYPE)
	{
		if (!heavy)
			lara.MineL = 1;

		if (*data & END_BIT)
			return;

		data++;
	}

/* -------- MINER_TYPE */

	if ((*data & DATA_TYPE) == MINER_TYPE)
	{
		if (!heavy)
			lara.MineR = 1;

		if (*data & END_BIT)
			return;

		data++;
	}


	/* TRIGGER TYPE Top bits of 'type' contain trigger type */
	type = (*(data++) >> 8) & 0x3f;

	/* First chunk of data contains 'timer + (flags<<8)' */
	flags = *(data++);
	timer = flags & 0xff;

	if (camera.type != HEAVY_CAMERA)
		RefreshCamera(type, data);

	/* Heavy items only activate HEAVY triggers */
	if (heavy)
	{
		if (type != HEAVY)
			return;
	}
	else
	{
		switch (type)
		{
			case SWITCH:
				value = *(data++)&VALUE_BITS;
				if (flags & ONESHOT)
					items[value].item_flags[0] = 1;
				if (!SwitchTrigger(value, timer))
					return;
				switch_off = (items[value].current_anim_state == 1);
				break;

			case PAD:
			case ANTIPAD:
				LaraOnPad = 1;
				if (lara_item->pos.y_pos != lara_item->floor || lara_item->item_flags[0])
					return;
				if (CurrentLevel == LV_GYM)
				{
					if (lara_item->item_flags[1] == 1)
						return;
					lara_item->item_flags[1] = 1;
				}
				break;

			case KEY:
				/* Lara needs to use the appropriate item from her inventory */
				value = *(data++)&VALUE_BITS;
				if (!KeyTrigger(value))
					return;
				break;

			case PICKUP:
				value = *(data++)&VALUE_BITS;
				if (!PickupTrigger(value))
					return;
				break;

			case HEAVY:
				/* Lara cannot activate heavy triggers */
				return;

			case COMBAT:
				/* Lara must have her guns drawn for this to count */
				if (lara.gun_status != LG_READY)
					return;
				break;

			case DUMMY:
				/* Does nothing whatsoever: meant for fixed cameras that want refreshing but are
					triggered on another space, or trapdoors etc that just want to effect height but
					not get activated */
				return;
		}
	}

	/* See if triggers activated */
	camera_item = NULL;
	do
	{
		trigger = *(data++);
		value = trigger & VALUE_BITS;

		switch (TRIG_BITS(trigger))
		{
		case TO_OBJECT:
			item = &items[value];

			/* Don't proceed if oneshot bit already set and its a trigger type. */
	    		if ((type == ANTIPAD || type == ANTITRIGGER) && (item->flags & ATONESHOT))
				break;

			if (type == SWITCH && (item->flags & SWONESHOT))
				break;

			if (type != SWITCH && type != ANTIPAD && type != ANTITRIGGER && (item->flags & ONESHOT))
				break;

			if (type != ANTIPAD && type != ANTITRIGGER)
			{
				if ((item->object_number == DART_EMITTER || item->object_number == HOMING_DART_EMITTER) && item->active)	// Bodge by Gibby to stop re-triggering !!
					break;

				// Intercept FISH and LIGHT_TYPE to use timer as flags (nice).

				if (item->object_number == TROPICAL_FISH || item->object_number == PIRAHNAS)
				{
					item->hit_points = timer&7; 	// Shoal number;
					SetupShoal(item->hit_points);
					timer = 0;
				}
				else if (item->object_number >= SECURITY_LASER_ALARM && item->object_number <= SECURITY_LASER_KILLER)
				{
					timer &= 7;
					if (timer == 0)
						timer = 1;
					item->hit_points = timer; 	// Number of lasers.
					timer = 0;
				}
				else if (item->object_number == CEILING_SPIKES || item->object_number == PENDULUM
					 || item->object_number == FIREHEAD)
				{
					item->item_flags[0] = timer; 	// Animating and faster.
					if (item->object_number == FIREHEAD)
						item->hit_points = timer; 	// Number of blow-loops.
					timer = 0;
				}
			}

			/* Timer gets set to start value until leave trigger space. 1 second timers count as
				instant switch offs */

			item->timer = timer;
			if (timer != 1)
				item->timer *= 30;

			/* Set code bits: need all five for activation */
			if (type == SWITCH)
			{
				item->flags ^= (flags & CODE_BITS);
				if (flags & ONESHOT)
					item->flags |= SWONESHOT;
			}
			else if (type == ANTIPAD || type == ANTITRIGGER)
			{
				// GIBBY, TURN OFF FISH HERE !!!
				if (item->object_number == TROPICAL_FISH || item->object_number == PIRAHNAS)
				{
					long	leader;
					leader = item->hit_points;
					lead_info[leader].on = 0;
				}

				if (item->object_number == EARTHQUAKE)
				{
					item->item_flags[0] = 0;		// Current rumble value.
					item->item_flags[1] = 100;	// Destination rumble value.
				}

				item->flags &= ~(CODE_BITS|REVERSE);

				if (flags & ONESHOT)
					item->flags |= ATONESHOT;
			}
			else if (flags & CODE_BITS)
				item->flags |= (flags & CODE_BITS);

			if ((item->flags & CODE_BITS) != CODE_BITS)
				break;

	 		if (flags & ONESHOT)
	 			item->flags |= ONESHOT;

			/* Add to active item list if not already there */
			if ( !item->active )
			{
				if ( objects[item->object_number].intelligent ) 	 // If Baddie Intelligent
				{
					if ( item->status==NOT_ACTIVE )                  // If flagged as NOT_ACTIVE
					{                                                // then we make sure
						item->touch_bits=0;
						item->status=ACTIVE;
						AddActiveItem(value);
						if (item->object_number != TARGETS)
							EnableBaddieAI(value,1);
					}
					else if ( item->status==INVISIBLE )
					{
						item->touch_bits=0;
						if ( EnableBaddieAI(value, 0) )              // we can initialise if flagged as Invisible
							item->status = ACTIVE;                   // doesnt matter if Baddie AI
						else                                         // is initialised now
							item->status = INVISIBLE;
						AddActiveItem(value);
					}
				}
				else
				{
					item->touch_bits=0;
					AddActiveItem(value);
					item->status = ACTIVE;
					HeavyTriggered = heavy;
				}
			}
			break;

		case TO_CAMERA:
			/* Cameras store their flags/timers etc in the next sint16 */
			trigger = *(data++);
			camera_flags = trigger;
			camera_timer = trigger & 0xff;

			if (camera.fixed[value].flags & ONESHOT)
				break;

			camera.number = value;

			/* LOOK_CAMERA overrides special cameras */
			if (camera.type == LOOK_CAMERA || camera.type == COMBAT_CAMERA)
				break;

			/* COMBAT triggered cameras are dealt with exclusively by RefreshCamera() */
			if (type == COMBAT)
				break;

			/* Switches with timers need to toggle else camera activated when switch goes off */
			if (type == SWITCH && timer && switch_off)
				break;

			/* Setup camera when first activated */
			if (camera.number != camera.last || type == SWITCH)
			{
				camera.timer = camera_timer * 30;

				/* Set box to NO_BOX when one shot camera */
				if (camera_flags & ONESHOT)
					camera.fixed[camera.number].flags |= ONESHOT;

				camera.speed = ((camera_flags & CODE_BITS) >> 6) + 1;
				camera.type = (heavy)? HEAVY_CAMERA : FIXED_CAMERA;
			}
			break;

		case TO_TARGET:
			/* If fixed camera is triggered, may have associated target too */
			camera_item = &items[value];
			break;

#if	0
		case TO_SINK:
			/* There's a current towards this sink (the position of which is stored as fixed camera) */
			obvector = &camera.fixed[value];

			if (!lara.creature)
				EnableBaddieAI(lara.item_number, 1);

			LOT = &lara.creature->LOT;
			LOT->target.x = obvector->x;
			LOT->target.y = obvector->y;
			LOT->target.z = obvector->z;
			LOT->required_box = obvector->flags;

			/* 'data' stores sink strength */
			lara.current_active = obvector->data * 6;
			break;
#else
		case TO_SINK:
			/* There's a current towards this sink (the position of which is stored as fixed camera) */
//			obvector = &camera.fixed[value];
			lara.current_active = value+1;	// Store data where current sink is.
			break;
#endif

		case TO_FLIPMAP:
			flip_available = 1;

			/* Flip the map for the first time (if not already done) */
			if (flipmap[value] & ONESHOT)
				break;

			if (type == SWITCH)
				flipmap[value] ^= (flags & CODE_BITS);
			else if (flags & CODE_BITS)
				flipmap[value] |= (flags & CODE_BITS);

			if ((flipmap[value] & CODE_BITS) == CODE_BITS)
			{
				if (flags & ONESHOT)
					flipmap[value] |= ONESHOT;
				if (!flip_status)
					flip = 1;
			}
			else if (flip_status)
				flip = 1;

			break;

		case TO_FLIPON:
			flip_available = 1;

			/* Once map has been flipped, this will switch map back on if it has been switched
				off */
			if ((flipmap[value] & CODE_BITS) == CODE_BITS && !flip_status)
				flip = 1;
			break;

		case TO_FLIPOFF:
			flip_available = 1;

			/* Once map has been flipped, this will switch off those areas (to allow multiple
				map flips in different regions) */
			if ((flipmap[value] & CODE_BITS) == CODE_BITS && flip_status)
				flip = 1;
			break;

		case TO_FLIPEFFECT:
			/* Flip effects are only activated if a FlipMap is activated on this space */
			/* OR if not on space with a flipmap trigger (!flip_available) */
			neweffect = value;
			break;

		case TO_FINISH:
			level_complete = 1;
#if defined(PC_VERSION) && defined(GAMEDEBUG)
			// log stats for level
			Log("LEVEL COMPLETE STATS:");
			Log("  Baddie hits   %d", level.hit_points);
			Log("  Ammo          %d used %d", level.ammo, level.ammo_used);
			Log("  Health        %d used %d", level.health, level.health_used);
#endif
			break;

		case TO_CD:
			TriggerCDTrack( value, flags, type );
			break;

		case TO_SECRET:
			if (!(savegame.secrets & (1 << value)))
			{
				S_CDPlay(122, 0);
				savegame.secrets |= (1 << value);
			}
			break;

		case TO_BODYBAG:
			/* Clear away bodies in the body bag */
			ClearBodyBag();
			break;
		}
	}
	while (!(trigger & END_BIT));

	/* If triggered fixed camera has target, set that up */
	if (camera_item && (camera.type == FIXED_CAMERA || camera.type == HEAVY_CAMERA))
		camera.item = camera_item;

	/* If flipmap triggered, do that + any required effect */
	if (flip)
		FlipMap();

	if (neweffect != -1 && (flip || !flip_available))
	{
		flipeffect = neweffect;
		fliptimer = 0;
	}
}


/*
 * Returns 1 if ON, 0 if OFF. Deals with timer countdown and code bit changes,
 * OR absolute opposite if REVERSE flag is set
 *
 */
int TriggerActive(ITEM_INFO *item)
{
	int ok;

	ok = (item->flags & REVERSE)? 0 : 1;

/* Check that code bits are still all set */
	if ((item->flags & CODE_BITS) != CODE_BITS)
		return (!ok);

	if (!item->timer)
		return (ok);

/* Timer triggers get set to -1 when run out to distinguish from those that never had timers */
	if (item->timer == -1)
		return (!ok);

	item->timer--;

	if (!item->timer)
		item->timer = -1;

	return (ok);
}


sint32 GetCeiling(FLOOR_INFO *floor, sint32 x, sint32 y, sint32 z)
{
	/* Get ceiling height allowing for skylights and floor type */
	sint32 xoff, yoff, height;
	ROOM_INFO *r;
	ITEM_INFO *item;
	FLOOR_INFO *f;
	sint16	*data, type, trigger;
	sint16	tilts,dx,dz,t0,t1,t2,t3,hadj,ended;

	CeilingObject = NULL;

//	GC_count++;
//	if (x == lara_item->pos.x_pos && z == lara_item->pos.z_pos)
//		pflag = 1;
//	else
//		pflag = 0;

	/* Need to go up through rooms to find ceiling, and back down again to find triggers */
	f = floor;
	while (f->sky_room != NO_ROOM)
	{
		if (CheckNoColCeilingTriangle(floor, x, z) == 1)
			break;
		r = &room[f->sky_room];
		f = &r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size];
	}

	height = f->ceiling << 8;
	if (height == NO_HEIGHT)
		return (height);

	/* Check out if sloping ceiling */
	if (f->index)
	{
		/* First item may be TILT_TYPE: skip it. Second may be ROOF_TYPE though */
		data = &floor_data[f->index];
		hadj = *(data++);
		type = hadj & DATA_TYPE;

		ended = 0;

		if (type == TILT_TYPE || type == SPLIT1 || type == SPLIT2 || type == NOCOLF1T || type == NOCOLF1B || type == NOCOLF2T || type == NOCOLF2B)	// gibby
		{
			ended = hadj & END_BIT;
			data++;
			hadj = *(data++);
			type = hadj & DATA_TYPE;
			//type = *(data++) & DATA_TYPE;
		}

		if (ended == 0 && (type == ROOF_TYPE || type == SPLIT3 || type == SPLIT4 || type == NOCOLC1T || type == NOCOLC1B || type == NOCOLC2T || type == NOCOLC2B))
		{
			/* Equivalent of TILT_TYPE but for ceilings */

			if (type != ROOF_TYPE) 	// Must be a split of some kind.
			{
				tilts = *data;
				t0 = -(tilts&15);
				t1 = -((tilts>>4)&15);
				t2 = -((tilts>>8)&15);
				t3 = -((tilts>>12)&15);

				dx = x&1023;
				dz = z&1023;

				xoff = yoff = 0;

				if (type == SPLIT3 || type == NOCOLC1T || type == NOCOLC1B)
				{
					if (dx <= (1024-dz))	// In top triangle?
					{
						hadj = (hadj>>10)&0x1f;
						if (hadj&0x10)	// negative?
							hadj|=0xfff0;
						height += hadj<<8;
						xoff = t2-t1;
						yoff = t3-t2;
//						if (pflag && (input & IN_ACTION))
//	    						printf("Split 3 TOP: %d, %d, %d, %d, Adj %d : %d,%d\n",t0,t1,t2,t3,hadj,xoff,yoff);
					}
					else
					{
						hadj = (hadj>>5)&0x1f;
						if (hadj&0x10)	// negative?
							hadj|=0xfff0;
						height += hadj<<8;
						xoff = t3-t0;
						yoff = t0-t1;
//						if (pflag && (input & IN_ACTION))
//	    						printf("Split 3 BOTTOM: %d, %d, %d, %d, Adj %d : %d,%d \n",t0,t1,t2,t3,hadj,xoff,yoff);
					}
				}
				else		// SPLIT4
				{

					if (dx <= dz)	// In top triangle?
					{
						hadj = (hadj>>10)&0x1f;
						if (hadj&0x10)	// negative?
							hadj|=0xfff0;
						height += hadj<<8;
						xoff = t2-t1;
						yoff = t0-t1;
//						if (pflag && (input & IN_ACTION))
//	    						printf("Split 4 TOP: %d, %d, %d, %d, Adj %d\n",t0,t1,t2,t3,hadj);
					}
					else
					{
						hadj = (hadj>>5)&0x1f;
						if (hadj&0x10)	// negative?
							hadj|=0xfff0;
						height += hadj<<8;
						xoff = t3-t0;
						yoff = t3-t2;
//						if (pflag && (input & IN_ACTION))
//	    						printf("Split 4 BOTTOM: %d, %d, %d, %d, Adj %d\n",t0,t1,t2,t3,hadj);
					}
				}

				if (chunky_flag)
				{
					long	h1,h2;

					hadj = (hadj>>10)&0x1f;
					if (hadj&0x10)	// negative?
						hadj|=0xfff0;
					h1 = h2 = f->ceiling << 8;
					h1 += hadj<<8;

					hadj = (hadj>>5)&0x1f;
					if (hadj&0x10)	// negative?
						hadj|=0xfff0;
					h2 += hadj<<8;

					if (h1 > h2)
						height = h1;
					else
						height = h2;
				}
			}
			else
			{
				xoff = (*data) >> 8;
				yoff = (signed char)(*data);
			}

			if (!chunky_flag)// || (xoff >= -2 && xoff <= 2 && yoff >= -2 && yoff <= 2))
			{
				if (xoff < 0)
					height += (z & (WALL_L-1)) * xoff >> 2;
				else
					height -= ((WALL_L-1-z) & (WALL_L-1)) * xoff >> 2;

				if (yoff >= 0)
					height -= (x & (WALL_L-1)) * yoff >> 2;
				else
					height += ((WALL_L-1-x) & (WALL_L-1)) * yoff >> 2;
			}
		}
	}

	/* Also need to check triggered objects which may have height associated with them;
		their triggers will be on the ground floor, so need to find that */
	while (floor->pit_room != NO_ROOM)
	{
		if (CheckNoColFloorTriangle(floor, x, z) == 1)	// If on collision side of a split quad then stop.
			break;
		r = &room[floor->pit_room];
		floor = &r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size];
	}

	if (!floor->index)
		return (height);

	/* Check out on square data to see if it effects ceiling height */
	data = &floor_data[floor->index];

	do
	{
		type = *(data++);

		switch (type & DATA_TYPE)
		{
			case SPLIT1:   // gibby
			case SPLIT2:	// gibby
			case SPLIT3:   // gibby
			case SPLIT4:	// gibby
			case NOCOLF1T:	// gibby
			case NOCOLF1B:	// gibby
			case NOCOLF2T:	// gibby
			case NOCOLF2B:	// gibby
			case NOCOLC1T:	// gibby
			case NOCOLC1B:	// gibby
			case NOCOLC2T:	// gibby
			case NOCOLC2B:	// gibby
			case DOOR_TYPE:
			case TILT_TYPE:
			case ROOF_TYPE:
				data++;
				break;

			case LAVA_TYPE:
			case CLIMB_TYPE:
			case MONKEY_TYPE:
			case MINEL_TYPE:
			case MINER_TYPE:
				break;

			case TRIGGER_TYPE:
				/* Some triggers also have object associated and so effect ceiling height */
				data++;
				do
				{
					trigger = *(data++);
					if (TRIG_BITS(trigger) != TO_OBJECT)
					{
						if (TRIG_BITS(trigger) == TO_CAMERA)
							trigger = *(data++);
						continue;
					}

					/* Call ceiling determining routine if any exists */
					item = &items[trigger & VALUE_BITS];
					if (objects[item->object_number].ceiling)
						(*objects[item->object_number].ceiling)(item, x, y, z, &height);
				}
				while (!(trigger & END_BIT));
				break;

			default:
				S_ExitSystem("GetCeiling(): Unknown type");
		}
	}
	while (!(type & END_BIT));

	return (height);
}


//#ifdef PC_VERSION
#if 1
sint16 GetDoor(FLOOR_INFO *floor)
{
	sint16 *data, type;

	if (!floor->index)
		return (NO_ROOM);

	data = &floor_data[floor->index];
	type = *(data++);

	/* First item may be TILT_TYPE: skip it */
	if (((type & DATA_TYPE) == TILT_TYPE)
	||  ((type & DATA_TYPE) == SPLIT1)
	||  ((type & DATA_TYPE) == SPLIT2)
	||  ((type & DATA_TYPE) == NOCOLF1B)
	||  ((type & DATA_TYPE) == NOCOLF1T)
	||  ((type & DATA_TYPE) == NOCOLF2B)
	||  ((type & DATA_TYPE) == NOCOLF2T))
	{
		if (type & END_BIT)
			return (NO_ROOM);

		data++;
		type = *(data++);
	}

	/* Second may be ROOF_TYPE: skip it */
	if (((type & DATA_TYPE) == ROOF_TYPE)
	||  ((type & DATA_TYPE) == SPLIT3)
	||  ((type & DATA_TYPE) == SPLIT4)
	||  ((type & DATA_TYPE) == NOCOLC1B)
	||  ((type & DATA_TYPE) == NOCOLC1T)
	||  ((type & DATA_TYPE) == NOCOLC2B)
	||  ((type & DATA_TYPE) == NOCOLC2T))
	{
		if (type & END_BIT)
			return (NO_ROOM);

		data++;
		type = *(data++);
	}

	if ((type & DATA_TYPE) == DOOR_TYPE)
		return (*data);

	return (NO_ROOM);
}
#endif

/*
 * Check LOS. Does both z and x directions to determine free path. Gets
 * 'target' room and last free LOS target pos if LOS blocked. This version
 * is very new (12/9/96) and doesn't make any of the assumptions of the old
 * version, as severe tilts mish-mashed together can cause almost anything.
 *
 * In particular:
 *
 * 	a) shortest axis LOS can fail 'cleanly' (return 0), but longest axis can
 *      ALSO fail but closer to start point; thus must do both tests to get
 *	   shortest LOS
 *
 * 	b) shortest axis LOS can fail, but longest can then pass (target has
 *      been clipped by shortest test), so must remember result of both
 *
 * 	c) either/both LOS tests can fail but ClipTarget can still pass, so need
 *	   result of all 3 tests to decide if LOS has failed
 *
 */

int LOS(GAME_VECTOR *start, GAME_VECTOR *target)
{
	int los1, los2;

	/* Do shortest axis first (to make sure we get correct room number) */
	if ( (ABS(target->z - start->z)) > (ABS(target->x - start->x)))
	{
		los1 = xLOS(start, target);
		los2 = zLOS(start, target);
	}
	else
	{
		los1 = zLOS(start, target);
		los2 = xLOS(start, target);
	}

	/* Longest axis failure is fine and dandy and most common clip */
	if (!los2)
		return (0);

	/* Get room number */
#if 0
	floor = GetFloor(target->x, target->y, target->z, &target->room_number);
	if (!ClipTargetC(start, target, floor) || los1 != 1 || los2 != 1)
		return (0);
	else
		return (1);
#else
	GetFloor(target->x, target->y, target->z, &target->room_number);
	if (!ClipTarget(start, target) || los1 != 1 || los2 != 1)
		return (0);
	else
		return (1);
#endif
}

int zLOS(GAME_VECTOR *start, GAME_VECTOR *target)
{
	/* Check LOS along z-axis. Returns 1 if LOS exists (-1 if broken on leading edge, 0 if
		broken on following edge. Former implies that xLOS will give better 'pos' for where
		LOS broken), latter results in 'target' being adjusted to store last LOS position. */
	sint32 x, y, z;
	sint32 dx, dy, dz;
	sint16 room_number, last_room;
	FLOOR_INFO *floor;

	dz = target->z - start->z;
	if (dz == 0)
		return (1);

	dx = ((target->x - start->x) << WALL_SHIFT)/dz;
	dy = ((target->y - start->y) << WALL_SHIFT)/dz;

	los_rooms[0] = room_number = last_room = start->room_number;
	number_los_rooms = 1;

	if (dz < 0)
	{
		z = start->z & ~(WALL_L-1);
		x = start->x + (dx * (z - start->z) >> WALL_SHIFT);
		y = start->y + (dy * (z - start->z) >> WALL_SHIFT);

		while (z > target->z)
		{
			/* Check that floor position is not a doorway/pitway/skyway */
			floor = GetFloor(x, y, z, &room_number);
			if (y > (GetHeight(floor, x, y, z)) || y < (GetCeiling(floor, x, y, z)))
				RETURN_TARGET2;

			/* Keep a list of which rooms LOS passes through (for ObjectOnLOS) */
			if (room_number != last_room)
			{
				los_rooms[number_los_rooms++] = room_number;
				last_room = room_number;
			}

			/* Cross edge and check floor doesn't lead through doorway */
			floor = GetFloor(x, y, z-1, &room_number);
			if (y > GetHeight(floor, x, y, z-1) || y < GetCeiling(floor, x, y, z-1))
				RETURN_TARGET;

			/* Move to next position */
			z -= WALL_L;
			x -= dx;
			y -= dy;
		}
	}
	else
	{
		z = start->z | (WALL_L-1);
		x = start->x + (dx * (z - start->z) >> WALL_SHIFT);
		y = start->y + (dy * (z - start->z) >> WALL_SHIFT);

		while (z < target->z)
		{
			/* Check that floor position is not a doorway/pitway/skyway */
			floor = GetFloor(x, y, z, &room_number);
			if (y > GetHeight(floor, x, y, z) || y < GetCeiling(floor, x, y, z))
				RETURN_TARGET2;

			if (room_number != last_room)
			{
				los_rooms[number_los_rooms++] = room_number;
				last_room = room_number;
			}

			/* Cross edge and check floor doesn't lead through doorway */
			floor = GetFloor(x, y, z+1, &room_number);
			if (y > GetHeight(floor, x, y, z+1) || y < GetCeiling(floor, x, y, z+1))
				RETURN_TARGET;

			/* Move to next position */
			z += WALL_L;
			x += dx;
			y += dy;
		}
	}

	target->room_number = room_number;

	return (1);
}


int xLOS(GAME_VECTOR *start, GAME_VECTOR *target)
{
	/* Check LOS along x-axis. Returns 1 if LOS exists (-1 if broken on leading edge, 0 if
		broken on following edge. Former implies that xLOS will give better 'pos' for where
		LOS broken) */
	sint32 x, y, z;
	sint32 dx, dy, dz;
	sint16 room_number, last_room;
	FLOOR_INFO *floor;

	dx = target->x - start->x;
	if (dx == 0)
		return (1);

	dy = ((target->y - start->y) << WALL_SHIFT)/dx;
	dz = ((target->z - start->z) << WALL_SHIFT)/dx;

	los_rooms[0] = room_number = last_room = start->room_number;
	number_los_rooms = 1;

	if (dx < 0)
	{
		x = start->x & ~(WALL_L-1);
		y = start->y + (dy * (x - start->x) >> WALL_SHIFT);
		z = start->z + (dz * (x - start->x) >> WALL_SHIFT);

		while (x > target->x)
		{
			/* Check that floor position is not a doorway/pitway/skyway */
			floor = GetFloor(x, y, z, &room_number);
			if (y > (GetHeight(floor, x, y, z)) || y < (GetCeiling(floor, x, y, z)))
				RETURN_TARGET2;

			if (room_number != last_room)
			{
				los_rooms[number_los_rooms++] = room_number;
				last_room = room_number;
			}

			/* Cross edge and check floor doesn't lead through doorway */
			floor = GetFloor(x-1, y, z, &room_number);
			if (y > GetHeight(floor, x-1, y, z) || y < GetCeiling(floor, x-1, y, z))
				RETURN_TARGET;

			/* Move to next position */
			x -= WALL_L;
			y -= dy;
			z -= dz;
		}
	}
	else
	{
		x = start->x | (WALL_L-1);
		y = start->y + (dy * (x - start->x) >> WALL_SHIFT);
		z = start->z + (dz * (x - start->x) >> WALL_SHIFT);

		while (x < target->x)
		{
			/* Check that floor position is not a doorway/pitway/skyway */
			floor = GetFloor(x, y, z, &room_number);
			if (y > GetHeight(floor, x, y, z) || y < GetCeiling(floor, x, y, z))
				RETURN_TARGET2;

			if (room_number != last_room)
			{
				los_rooms[number_los_rooms++] = room_number;
				last_room = room_number;
			}

			/* Cross edge and check floor doesn't lead through doorway */
			floor = GetFloor(x+1, y, z, &room_number);
			if (y > GetHeight(floor, x+1, y, z) || y < GetCeiling(floor, x+1, y, z))
 				RETURN_TARGET;

			/* Move to next position */
			x += WALL_L;
			y += dy;
			z += dz;
		}
	}

	target->room_number = room_number;

	/* LOS is clear */
	return (1);
}

#if 0
int ClipTargetC(GAME_VECTOR *start, GAME_VECTOR *target, FLOOR_INFO *floor)
{
	/* Clip target point to ceiling/floor height */
	sint32 dx, dy, dz, height;

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

	/* Look for surface clips: x,z assumes flat face, so inaccurate for tilting ones */
	height = GetHeight(floor, target->x, target->y, target->z);
	if (target->y > height && start->y < height)
	{
		target->y = height;
		target->x = start->x + dx * (height - start->y) / dy;
		target->z = start->z + dz * (height - start->y) / dy;
		return (0);
	}

	height = GetCeiling(floor, target->x, target->y, target->z);
	if (target->y < height && start->y > height)
	{
		target->y = height;
		target->x = start->x + dx * (height - start->y) / dy;
		target->z = start->z + dz * (height - start->y) / dy;
		return (0);
	}

	return (1);
}

#else

int ClipTarget(GAME_VECTOR *start, GAME_VECTOR *target)
{
	// Clip target point to ceiling/floor height.

	sint32 	dx, dy, dz, height,lp;
	sint16	room_no;
	FLOOR_INFO *floor;
	GAME_VECTOR src;

	room_no = target->room_number;
	floor = GetFloor(target->x, target->y, target->z,&room_no);
	height = GetHeight(floor, target->x, target->y, target->z);

	if (target->y > height)
	{
		src.x = start->x + (((target->x - start->x)*7)>>3);
		src.y = start->y + (((target->y - start->y)*7)>>3);
		src.z = start->z + (((target->z - start->z)*7)>>3);

		for (lp=3;lp>0;lp--)
		{
			dx = src.x + (((target->x - src.x)*lp)>>2);
			dy = src.y + (((target->y - src.y)*lp)>>2);
			dz = src.z + (((target->z - src.z)*lp)>>2);
			floor = GetFloor(dx, dy, dz,&room_no);
			height = GetHeight(floor, dx, dy, dz);
			if (dy < height)
			{
				target->x = dx;
				target->y = dy;
				target->z = dz;
				target->room_number = room_no;
				return(0);
			}
		}

		target->x = dx;
		target->y = dy;
		target->z = dz;
		target->room_number = room_no;
		return(0);
	}

	room_no = target->room_number;
	floor = GetFloor(target->x, target->y, target->z,&room_no);
	height = GetCeiling(floor, target->x, target->y, target->z);
	if (target->y < height)
	{
		src.x = start->x + (((target->x - start->x)*7)>>3);
		src.y = start->y + (((target->y - start->y)*7)>>3);
		src.z = start->z + (((target->z - start->z)*7)>>3);

		for (lp=3;lp>0;lp--)
		{
			dx = src.x + (((target->x - src.x)*lp)>>2);
			dy = src.y + (((target->y - src.y)*lp)>>2);
			dz = src.z + (((target->z - src.z)*lp)>>2);
			floor = GetFloor(dx, dy, dz,&room_no);
			height = GetCeiling(floor, dx, dy, dz);
			if (dy > height)
			{
				target->x = dx;
				target->y = dy;
				target->z = dz;
				target->room_number = room_no;
				return(0);
			}
		}
		target->x = dx;
		target->y = dy;
		target->z = dz;
		target->room_number = room_no;
		return(0);
	}

	return (1);
}

#endif

/******************************************************************************
* Test for any objects that lie on the line between two points
******************************************************************************/
#define SIGN_BIT 0x80000000

int ObjectOnLOS(GAME_VECTOR *start, GAME_VECTOR *target)
{
	/* Checks for smashable objects on LOS. Currently doesn't deal with fact that LOS to
		smashable object may be broken by unsmashable object, but don't really see this as
		a great worry (and would mean the routine would have to test EVERY item, not just
		smashable ones, so much more expensive) as doors and blocks break LOS already */
	int i, j, failure;
	sint32 x, y, z, dx, dy, dz, distance;
	sint32 direction;
	sint16 item_number;
	sint16 *bounds, *zextent, *xextent;
	ITEM_INFO *item;

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

	for (i=0; i<number_los_rooms; i++)
	{
		/* Check what items are in this room */
		for (item_number=room[los_rooms[i]].item_number; item_number!=NO_ITEM; item_number=items[item_number].next_item)
		{
			/* Check item hasn't been smashed already */
			item = &items[item_number];
			if (item->status == DEACTIVATED)
				continue;

			/* Check item is a smashable one */
			// TODO: need 'smashable' flag in object definitions? or will it be a small subset of objects?
			if (	item->object_number != SMASH_WINDOW &&
				item->object_number != SMASH_OBJECT1 &&
				item->object_number != SMASH_OBJECT2 &&
				item->object_number != SMASH_OBJECT3 &&
				item->object_number != CARCASS &&
				item->object_number != EXTRAFX6)
				continue;

			/* 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 item_number;
						}
					}

					/* 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 item_number;
			}
			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
								/* Hit object! */
								return item_number;
						}
					}

					/* 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 item_number;
			}
		}
	}

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



/******************************************************************************
 *						Flip the Map
 *****************************************************************************/
void	FlipMap(void)
{
	// Flip to the alternative version of the map
	int i;
	ROOM_INFO *r, *flipped, temp;

	/* Stop all sound effects (may get restarted if still exist) */
	//mn_stop_ambient_samples();

	for (i=0, r=room; i<number_rooms; i++, r++)
	{
		if (r->flipped_room < 0)
			continue;

		RemoveRoomFlipItems( r );

		flipped = &room[r->flipped_room];

		S_MemCpy(&temp, r, sizeof(ROOM_INFO));
		S_MemCpy(r, flipped, sizeof(ROOM_INFO));
		S_MemCpy(flipped, &temp, sizeof(ROOM_INFO));

		// Flip back a couple of things that don't want flipping
		r->flipped_room = flipped->flipped_room;
		flipped->flipped_room = -1;

		// Item flipping will be a nightmare!! Need to check out new floor heights and all sorts
		r->item_number = flipped->item_number;
		r->fx_number = flipped->fx_number;
		AddRoomFlipItems( r );
	}
	// Toggle flipmap status
	flip_status = !flip_status;
}

/******************************************************************************
 *			Remove Items from A Flipped Room
 *****************************************************************************/
void	RemoveRoomFlipItems( ROOM_INFO *r )
{
	ITEM_INFO	*item;
	sint16		item_num;

	for ( item_num=r->item_number ;item_num!=NO_ITEM; item_num=items[item_num].next_item )
	{
		item = &items[item_num];

		if (objects[item->object_number].control == MovableBlock)
			AlterFloorHeight( item,1024 );		// Lower floor height in old room
		else if ((item->flags&ONESHOT) && objects[item->object_number].intelligent && item->hit_points<=0)
		{
			RemoveDrawnItem(item_num);				// remove dead baddies from flipping rooms
			item->flags |= KILLED_ITEM;
		}
	}
}

/******************************************************************************
 *				Add Items to a Flipped Room
 *****************************************************************************/
void	AddRoomFlipItems( ROOM_INFO *r )
{
	ITEM_INFO	*item;
	sint16		item_num;

	for ( item_num=r->item_number ;item_num!=NO_ITEM; item_num=items[item_num].next_item )
	{
		item = &items[item_num];

		if (objects[item->object_number].control == MovableBlock)
			AlterFloorHeight( item,-1024 );		// Increase floor height for new room
	}
}


/******************************************************************************
 *			Handle triggering of CD audio tracks
 *		includes FRIGS for Gym level
 *****************************************************************************/
void	TriggerCDTrack( sint16 value, sint16 flags, sint16 type )
{
	if ( value<=1 || value>=MAX_CD_TRACKS )
		return;

	TriggerNormalCDTrack( value,flags,type );
}

/****************************************************************************
 *			Trigger off normal Audio CD track
 ***************************************************************************/
void	TriggerNormalCDTrack( sint16 value, sint16 flags, sint16 type )
{
	int timer, code;

/* -------- trigger an atmosphere */

	if (((value >= 26) && (value <= 36))
	||  ((value >= 73) && (value <= 78))
	||   (value == 117))
	{
		if (CurrentAtmosphere != value)
		{
			CurrentAtmosphere = value;

			if (IsAtmospherePlaying)
				S_CDPlay(value, 1);

			printf("Got new atmosphere %d\n", value);
		}
	}

/* -------- trigger a normal CD track */

	else
	{
		if (value != 2)
		{
			code = (flags & (CODE_BITS|ONESHOT)) >> 8;

			if ((cd_flags[value] & code) == code)
				return;

			cd_flags[value] |= code;
		}

		S_CDPlay(value, 0);

		IsAtmospherePlaying = 0;
	}
}


//#ifdef	PC_VERSION
#if 1
long	CheckNoColFloorTriangle(FLOOR_INFO *floor, long x, long z)
{
	sint16 		*data, type, dx, dz;

	if (!floor->index)
		return (0);	// No 'No collision' set so return ok.

// Search for a no collision triangle and see if player is on it.

/*
 * -------
 * |    /|
 * | T / |     	Split type 1
 * |  /  |
 * | / B |
 * |/    |
 * -------
 */
	data = &floor_data[floor->index];
	type = *(data) & DATA_TYPE;

	if (type == NOCOLF1T || type == NOCOLF1B || type == NOCOLF2T || type == NOCOLF2B)
	{
		// Shouldn't (in theory) be any other than these types because there is a pit room on this block.

		dx = x&1023;
		dz = z&1023;

		if (type == NOCOLF1T && dx <= (1024-dz))
		{
//			if (input & IN_ACTION)
//				printf ("F type = F1T\n");
			return(-1);
		}
		else if (type == NOCOLF1B && dx > (1024-dz))
		{
//			if (input & IN_ACTION)
//				printf ("F type = F1B\n");
			return(-1);
		}
		else if (type == NOCOLF2T && dx <= dz)
		{
//			if (input & IN_ACTION)
//				printf ("F type = F2T\n");
			return(-1);
		}
		else if (type == NOCOLF2B && dx > dz)
		{
//			if (input & IN_ACTION)
//				printf ("F type = F2B\n");
			return(-1);
		}
		else
			return(1);
	}

	return(0);
}

long	CheckNoColCeilingTriangle(FLOOR_INFO *floor, long x, long z)
{
	sint16 		*data, type, dx, dz;

	if (!floor->index)
		return (0);	// No 'No collision' set so return ok.

// Search for a no collision triangle and see if player is on it.

/*
 * -------
 * |    /|
 * | T / |     	Split type 3
 * |  /  |
 * | / B |
 * |/    |
 * -------
 */
	data = &floor_data[floor->index];

// Search for floor tilts first.

	type = *(data) & DATA_TYPE;

	if (type == TILT_TYPE || type == SPLIT1 || type == SPLIT2 || type == NOCOLF1T || type == NOCOLF1B || type == NOCOLF2T || type == NOCOLF2B)	// gibby
	{
		if (*(data) & END_BIT)
			return(0);
		type = *(data+2) & DATA_TYPE;
	}

	if (type == NOCOLC1T || type == NOCOLC1B || type == NOCOLC2T || type == NOCOLC2B)
	{
		// Shouldn't (in theory) be any other than these types because there is a sky room on this block.

		dx = x&1023;
		dz = z&1023;

		if (		type == NOCOLC1T && dx <= (1024-dz))
		{
//			if (input & IN_ACTION)
//				printf ("C type = C1T\n");
			return(-1);
		}
		else if (	type == NOCOLC1B && dx > (1024-dz))
		{
//			if (input & IN_ACTION)
//				printf ("C type = C1B\n");
			return(-1);
		}
		else if (	type == NOCOLC2T && dx <= dz)
		{
//			if (input & IN_ACTION)
//				printf ("C type = C2T\n");
			return(-1);
		}
		else if (	type == NOCOLC2B && dx > dz)
		{
//			if (input & IN_ACTION)
//				printf ("C type = C2B\n");
			return(-1);
		}
		else
			return(1);
	}
	//else
	//	printf("Error in ceiling %d\n",type);

	return(0);
}
#endif

#ifdef PSX_VERSION
/******************************************************************************
 *				Pauls Most Excellent Random routine....
 *****************************************************************************/
sint32 rand_1 = 0xd371f947;
static sint32 rand_2 = 0xd371f947;

sint32 GetRandomControl()
{
	rand_1 = rand_1 * 0x41c64e6d + 0x3039;
	return( ( rand_1 >> 10 )&0x7fff );
}

/*******************************************************************************
 *					Seed Random Number routine....
 ******************************************************************************/
void	SeedRandomControl( sint32 seed )
{
	rand_1 = seed;
}

/*******************************************************************************
 *				Get Random number for Draw Routines
 *		  This is seperate from GetRandomControl due to Demo Playback
 ******************************************************************************/
sint32 GetRandomDraw()
{
	rand_2 = rand_2 * 0x41c64e6d + 0x3039;
	return( ( rand_2 >> 10 )&0x7fff );
}

/*******************************************************************************
 *			Seed Random Number routine....
 ******************************************************************************/
void	SeedRandomDraw( sint32 seed )
{
	rand_2 = seed;
}
#endif


/****************************************************************************
* IsRoomOutside
*
* SYNOPSIS
*	LONG IsRoomOutside(LONG x, LONG y, LONG z)
*
* PURPOSE
*	Determines if a room, at a given position, is outside
*
* INPUTS
*	x	= world x
*	y	= world y
*	z	= world z
*
* RETURNS
*	-2 if no room or in wall,under floor or in ceiling.
*	-3 if room is not outside and not underwater
*	1 if room is outside/underwater + room number in IsRoomOutsideNo.
*/

sint32 IsRoomOutside(sint32 x, sint32 y, sint32 z)
{
	WORD offset;
	ROOM_INFO *r;
	FLOOR_INFO	*floor;
	sint16 room_number;
	long	height;

	if ((offset = OutsideRoomOffsets[((x>>12)*27)+(z>>12)]) == -1)
		return -2;

/* -------- handle single entry */

	if (offset < 0)
	{
		r = &room[(offset&0x7fff)];

		if ((y > r->maxceiling) && (y < r->minfloor)
		&&  ( (z > (r->z+1024)) && (z < (r->z+((r->x_size-1)*1024))) )
		&&  ( (x > (r->x+1024)) && (x < (r->x+((r->y_size-1)*1024))) ))
		{
			room_number = offset&0x7fff;
			floor = GetFloor(x,y,z, &room_number);
			height = GetHeight(floor,x,y,z);
			if (height == NO_HEIGHT || y > height)
				return -2;
			height = GetCeiling(floor,x,y,z);
			if (y < height)
			 	return -2;

			if (!(r->flags & (NOT_INSIDE|UNDERWATER)))
				return -3;

			IsRoomOutsideNo = offset&0x7fff;
			return 1;
		}
		else
			return -2;
	}

/* -------- handle multiple entries */

	else
	{
		unsigned char *s = &OutsideRoomTable[offset];

		while (*s != 0xff)
		{
			r = &room[*s];

			if (( y > r->maxceiling && y < r->minfloor )
			&&  ( (z > (r->z+1024)) && (z < (r->z+((r->x_size-1)*1024))) )
			&&  ( (x > (r->x+1024)) && (x < (r->x+((r->y_size-1)*1024))) ))
			{
				room_number = *s;

				floor = GetFloor(x,y,z, &room_number);
				height = GetHeight(floor,x,y,z);
				if (height == NO_HEIGHT || y > height)
					return -2;
				height = GetCeiling(floor,x,y,z);
				if (y < height)
					return -2;

				if (!(r->flags & (NOT_INSIDE|UNDERWATER)))
					return -3;

				IsRoomOutsideNo = *s;
				return 1;
			}
			s++;
		}
		return -2;
	}
}

