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

#include "game.h"

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

ITEM_INFO *items;

#define DEATH_WAIT	(10*30)

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

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

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

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

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

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

/* What type of thing has Set current height in GetHeight */
int height_type = WALL;

/* 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
/*********************************** 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 */
	uint32		return_val;
	sint16 		item_num,nex;
	static int	framecount = 0;
#ifdef PSX_VERSION
	extern		TEXTSTRING	*demotxt;
#endif
	/* Limit number of frames to ensure playability */
	if (nframes > MAX_FRAMES)
		nframes = MAX_FRAMES;

//#define PAL_VERSION
#ifdef PAL_VERSION
	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
				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);
			}
		}

		/* 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)
				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)
#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)
		{
			S_DEL_Pause();
			return(0);
		}
#endif

/*#ifdef PSX_VERSION
		if (input & IN_PAUSE)
		{
			S_DEL_Pause();
			return(0);
		}
#endif*/

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

		/* Call control routines for active items on linked list (can create/destroy items) */
		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;
		}

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

		LaraControl(0);
		HairControl(0);

		update_dynamic_lights();

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

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

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

	return(0);
}


/*****************************************************************************
 *			Do Physical Movement/Animation of Item
 ****************************************************************************/
void	AnimateItem(ITEM_INFO *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 */
	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:
					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) )
					{
//						type = (*(command+1)&0xc000)>>14;
//						if ( type==SFX_LANDANDWATER ||
//							 type==SFX_LANDONLY ||
//							 type==SFX_WATERONLY )
//						{
							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;
}


FLOOR_INFO *GetFloor(sint32 x, sint32 y, sint32 z, sint16 *room_number)
{
	/* Get floor allowing for changes in room through pits/skylights */
	sint32 x_floor, y_floor;
	ROOM_INFO *r;
	FLOOR_INFO *floor;
	sint16 data;

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

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

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


/******************************************************************************
 *				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,8,"Error in GetWaterHeight()");
	}
*/
#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)
	{
		/* Upwards until leave water */
		while (floor->sky_room != NO_ROOM)
		{
			r = &room[floor->sky_room];
			if (!(r->flags & UNDERWATER))
				return (floor->ceiling << 8);
			floor = &r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size];
		}
		return( floor->ceiling<<8 );
	}
	else
	{
		/* Go down until hit water */
		while (floor->pit_room != NO_ROOM)
		{
			r = &room[floor->pit_room];
			if (r->flags & UNDERWATER)
				return (floor->floor << 8);
			floor = &r->floor[((z - r->z) >> WALL_SHIFT) + ((x - r->x) >> WALL_SHIFT) * r->x_size];
		}
	}
	return (NO_HEIGHT);
}


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;
	ROOM_INFO 	*r;
	ITEM_INFO 	*item;
	sint16 		*data, type, trigger;
	sint16		tilts,dx,dz,t0,t1,t2,t3,t4,t5,pflag,hadj;

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

	/* If over a pit, then go to bottom to find appropriate floor */
	height_type = WALL;
	while (floor->pit_room != NO_ROOM)
	{
		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 (GF_NoFloor && GF_NoFloor==height)
		height = 0x4000;

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

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

	/* 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:
				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&DATA_TYPE) == SPLIT1)
				{
					if (dx <= (1024-dz))	// In top triangle?
					{
						hadj = (type>>9)&0x1f;
						if (hadj&0x10)	// negative?
							hadj|=0xfff0;
						height += hadj<<8;
						xoff = t2-t1;
						yoff = t0-t1;
					}
					else
					{
						hadj = (type>>4)&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>>9)&0x1f;
						if (hadj&0x10)	// negative?
							hadj|=0xfff0;
						height += hadj<<8;
						xoff = t2-t1;
						yoff = t3-t2;
					}
					else
					{
						hadj = (type>>4)&0x1f;
						if (hadj&0x10)	// negative?
							hadj|=0xfff0;
						height += hadj<<8;
						xoff = t3-t0;
						yoff = t0-t1;

					}
				}

/*			case SPLIT1:
			case SPLIT2:
			case SPLIT3:
			case SPLIT4:

				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&DATA_TYPE) == SPLIT1)
				{
//					if (pflag && (input & IN_ACTION))
//			    			printf("Split 1: %d, %d, %d, %d",t0,t1,t2,t3);

					if (dx <= (1024-dz))	// In top triangle?
					{
//						if (pflag && (input & IN_ACTION))
//							printf(" TOP\n");
						if (t3 > t0 && t3 > t1 && t3 > t2)
						{
							if (!(t0 < t1 || t0 < t2))	// t0 biggest?
							{
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t3-t0);
								height+=((t3-t0)<<8);
							}
							else if (!(t1 < t0 || t1 < t2)) // t1 biggest ?
							{
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t3-t1);
								height+=((t3-t1)<<8);
							}
							else
							{
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t3-t2);
								height+=((t3-t2)<<8);	// t2 biggest !
							}
						}

						xoff = t2-t1;
						yoff = t0-t1;
					}
					else
					{
//						if (pflag && (input & IN_ACTION))
//							printf(" BOTTOM\n");
						if (t1 > t0 && t1 > t2 && t1 > t3)
						{
							if (!(t0 < t2 || t0 < t3))	// t0 biggest?
							{
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t1-t0);
								height+=((t1-t0)<<8);
							}
							else if (!(t2 < t0 || t2 < t3)) // t2 biggest ?
							{
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t1-t2);
								height+=((t1-t2)<<8);
							}
							else
							{
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t1-t3);
								height+=((t1-t3)<<8);	// t3 biggest !
							}
						}

						xoff = t3-t0;
						yoff = t3-t2;
					}
				}
				else	if ((type&DATA_TYPE) == SPLIT2)
				{
//					if (pflag && (input & IN_ACTION))
//						printf("Split 2: %d, %d, %d, %d",t0,t1,t2,t3);

					if (dx <= dz)	// In top triangle?
					{
//						if (pflag && (input & IN_ACTION))
//							printf(" TOP\n");
						if (t0 > t1 && t0 > t2 && t0 > t3)
						{
							if (!(t2 < t1 || t2 < t3))	// t2 biggest?
							{
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t0-t2);
								height+=((t0-t2)<<8);
							}
							else if (!(t1 < t2 || t1 < t3)) // t1 biggest ?
							{
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t0-t1);
								height+=((t0-t1)<<8);
							}
							else
							{
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t0-t3);
								height+=((t0-t3)<<8);	// t3 biggest !
							}
						}

						xoff = t2-t1;
						yoff = t3-t2;
					}
					else
					{
//						if (pflag && (input & IN_ACTION))
//							printf(" BOTTOM\n");
						if (t2 > t0 && t2 > t1 && t2 > t3)
						{
							if (!(t0 < t1 || t0 < t3))	// t0 biggest?
							{
								height+=((t2-t0)<<8);
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t2-t0);
							}
							else if (!(t1 < t0 || t1 < t3)) // t1 biggest ?
							{
								height+=((t2-t1)<<8);
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t2-t1);
							}
							else
							{
								height+=((t2-t3)<<8);	// t3 biggest !
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t2-t3);
							}
						}

						xoff = t3-t0;
						yoff = t0-t1;

					}
				}
				else if ((type&DATA_TYPE) == SPLIT3)
				{
//					if (pflag && (input & IN_ACTION))
//						printf("Split 3: %d, %d, %d, %d",t0,t1,t2,t3);

					if (dx <= (1024-dz))	// In top triangle?
					{
//						if (pflag && (input & IN_ACTION))
//							printf(" TOP\n");
						if (t0 == t1 && t1 == t2)	// no tilt ?
							xoff = yoff = 0;
						else if (t0==t1 || t1 == t2)
						{
							xoff = t2-t1;
							yoff = t0-t1;
						}
						else
  						{
							t4 = t2+(t0-t1);
							t5 = t0;
							if (t1>t5)
								t5 = t1;
							if (t2>t5)
								t5 = t2;
							if (t3>t5)
								t5 = t3;
							if (t5 < t4 || t5 == t3)
							{
						    		height+=((t5-t4)<<8);
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t5-t4);
							}
							xoff = t2-t1;
							yoff = t0-t1;
						}

					}
					else
					{
//						if (pflag && (input & IN_ACTION))
//							printf(" BOTTOM\n");
						if (t0 == t2 && t2 == t3)	// no tilt ?
							xoff = yoff = 0;
						else if (t0==t3 || t3 == t2)
						{
							xoff = t3-t0;
							yoff = t3-t2;
						}
						else
						{
							t4 = t0+(t2-t3);
							t5 = t0;
							if (t1>t5)
								t5 = t1;
							if (t2>t5)
								t5 = t2;
							if (t3>t5)
								t5 = t3;
							if (t5 < t4 || t5 == t1)
							{
						    		height+=((t5-t4)<<8);
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t5-t4);
							}
							xoff = t3-t0;
							yoff = t3-t2;
						}
					}
				}
				else	if ((type&DATA_TYPE) == SPLIT4)
				{
//					if (pflag && (input & IN_ACTION))
//						printf("Split 4: %d, %d, %d, %d",t0,t1,t2,t3);

					if (dx <= dz)	// In top triangle?
					{
//						if (pflag && (input & IN_ACTION))
//							printf(" TOP\n");

						if (t1 == t2 && t2 == t3)	// no tilt ?
							xoff = yoff = 0;
						else if (t1==t2 || t2 == t3)
						{
							xoff = t2-t1;
							yoff = t3-t2;
						}
						else
  						{
							t4 = t3+(t1-t2);
							t5 = t0;
							if (t1>t5)
								t5 = t1;
							if (t2>t5)
								t5 = t2;
							if (t3>t5)
								t5 = t3;
							if (t5 < t4 || t5 == t0)
							{
						    		height+=((t5-t4)<<8);
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t5-t4);
							}
							xoff = t2-t1;
							yoff = t3-t2;
						}

					}
					else
					{
//						if (pflag && (input & IN_ACTION))
//							printf(" BOTTOM\n");

						if (t0 == t1 && t1 == t3)	// no tilt ?
							xoff = yoff = 0;
						else if (t0==t1 || t0 == t3)
						{
							xoff = t3-t0;
							yoff = t0-t1;
						}
						else
  						{
							t4 = t1+(t3-t0);
							t5 = t0;
							if (t1>t5)
								t5 = t1;
							if (t2>t5)
								t5 = t2;
							if (t3>t5)
								t5 = t3;
							if (t5 < t4 || t5 == t2)
							{
						    		height+=((t5-t4)<<8);
								if (pflag && (input & IN_ACTION))
									printf(" ADJ %d\n",t5-t4);
							}
							xoff = t3-t0;
							yoff = t0-t1;
						}
					}
				}
*/

				/* 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 = DIAGONAL;
					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 TILT_TYPE:
				xoff = (*data) >> 8;
				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 ROOF_TYPE:
			case DOOR_TYPE:
				data++;
				break;

			case LAVA_TYPE:
				trigger_index = data-1;
				break;

			case CLIMB_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);
}


void RefreshCamera(sint16 type, sint16 *data)
{
	/* Check triggers to see if same camera as last time is on this space */
	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)
				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;
	OBJECT_VECTOR 	*obvector;
	sint16 			type, trigger, value, timer, flags;
	sint16 			camera_flags, camera_timer;
	int switch_off=0, flip=0, flip_available=0, neweffect=-1;
	LOT_INFO *LOT;

	if (!heavy)
		lara.climb_status = 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++;
	}

	/* 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 (!SwitchTrigger(value, timer))
					return;
				switch_off = (items[value].current_anim_state == 1);
				break;

			case PAD:
			case ANTIPAD:
				if (lara_item->pos.y_pos != lara_item->floor)
					return;
				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 */
			if (item->flags & ONESHOT)
				break;

			/* 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);
			else if (type == ANTIPAD || type == ANTITRIGGER)
			{
				item->flags &= (0xffffffff - (flags & CODE_BITS));
				// SPECIAL: ONESHOT only usually works when things switched on, but antipads switch things off
				if (flags & ONESHOT)
					item->flags |= ONESHOT;
			}
			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);
						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;
				}
			}
			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;

		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;

		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:
			// no longer used
			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;
	}
}


int TriggerActive(ITEM_INFO *item)
{
	/* Returns 1 if ON, 0 if OFF. Deals with timer countdown and code bit changes,
		OR absolute opposite if REVERSE flag is set */
	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;

	/* Need to go up through rooms to find ceiling, and back down again to find triggers */
	f = floor;
	while (f->sky_room != NO_ROOM)
	{
		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;

	/* 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];
		type = *(data++) & DATA_TYPE;


		if (type == TILT_TYPE || type == SPLIT1 || type == SPLIT2)	// gibby
		{
			data++;
			type = *(data++) & DATA_TYPE;
		}

		if (type == ROOF_TYPE)
		{
			/* Equivalent of TILT_TYPE but for ceilings */
			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)
	{
		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 DOOR_TYPE:
			case TILT_TYPE:
			case ROOF_TYPE:
				data++;
				break;

			case LAVA_TYPE:
			case CLIMB_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);
}


sint16 GetDoor(FLOOR_INFO *floor)
{
	/* Search for DOOR_TYPE in floor data: only TILT_TYPE+ROOF_TYPE come before */
	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))
	{
		if (type & END_BIT)
			return (NO_ROOM);

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

	/* Second may be ROOF_TYPE: skip it */
	if ((type & DATA_TYPE) == ROOF_TYPE)
	{
		if (type & END_BIT)
			return (NO_ROOM);

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

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

	return (NO_ROOM);
}

int LOS(GAME_VECTOR *start, GAME_VECTOR *target)
{
	/* 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 los1, los2;
	FLOOR_INFO *floor;

	/* 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 */
	floor = GetFloor(target->x, target->y, target->z, &target->room_number);

	if (!ClipTarget(start, target, floor) || los1 != 1 || los2 != 1)
		return (0);
	else
		return (1);
}


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


int ClipTarget(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);
}


/******************************************************************************
* 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 != BELL)
				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;

	switch ( value )
	{
	}
	TriggerNormalCDTrack( value,flags,type );
}

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

/*
	if (cd_flags[value] & ONESHOT)
		return;
	if (type == SWITCH)
		cd_flags[value] ^= (flags & CODE_BITS);
	else if (type == ANTIPAD || type == ANTITRIGGER)
		cd_flags[value] &= (0xffffffff - (flags & CODE_BITS));
	else if (flags & CODE_BITS)
		cd_flags[value] |= (flags & CODE_BITS);

	if ((cd_flags[value] & CODE_BITS) == CODE_BITS)
	{
		if (flags & ONESHOT)
			cd_flags[value] |= ONESHOT;
*/

	/* Switch doesn't touch flags at all */
	if (type != SWITCH)
	{
		/* Otherwise, if CODE_BITS flag is already set, then don't play track */
		code = flags & CODE_BITS;
		if (cd_flags[value] & code)
			return;

		/* If ONESHOT, then set CODE_BITS so not used again */
		if (flags & ONESHOT)
			cd_flags[value] |= code;
	}

	if (value!=cdtrack)
	{
		timer = flags & 0xff;
		if (timer)
		{
			cd_flags[value] = (cd_flags[value]&0xff00) + ((timer*30)&0xff); // WARNING: timer better be less than 256/30
			cdtrack = value;
		}
		else
			S_CDPlay(value, 0);
	}
	else // may be on a timer?
	{
		timer = cd_flags[value] & 0xff;
		if (timer)
		{
			timer--;
			if (!timer)
			{
				cdtrack = -1;
				S_CDPlay(value, 0);
			}
			cd_flags[value] = (cd_flags[value]&0xff00) + timer;
		}
	}
}
