shithub: hexen

ref: 8bd16fed5b196ae0d0c05e2d8412b2e7b08901ba
dir: /p_spec.c/

View raw version

//**************************************************************************
//**
//** p_spec.c : Heretic 2 : Raven Software, Corp.
//**
//** $Revision: 584 $
//** $Date: 2012-02-17 12:01:51 +0200 (Fri, 17 Feb 2012) $
//**
//**************************************************************************

// HEADER FILES ------------------------------------------------------------

#include "h2stdinc.h"
#include "h2def.h"
#include "p_local.h"
#include "soundst.h"

// MACROS ------------------------------------------------------------------

#define MAX_TAGGED_LINES	64

// TYPES -------------------------------------------------------------------

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

extern const char *TextKeyMessages[11];

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

static boolean CheckedLockedDoor(mobj_t *mo, byte lock);

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

// PUBLIC DATA DEFINITIONS -------------------------------------------------

int *TerrainTypes;
mobj_t LavaInflictor;

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static struct
{
	const char	*name;
	int		type;
} TerrainTypeDefs[] =
{
	{ "X_005", FLOOR_WATER },
	{ "X_001", FLOOR_LAVA },
	{ "X_009", FLOOR_SLUDGE },
	{ "F_033", FLOOR_ICE },
	{ "END", -1 }
};

static struct
{
	line_t *line;
	int  lineTag;
} TaggedLines[MAX_TAGGED_LINES];

static int TaggedLineCount;

// CODE --------------------------------------------------------------------

//==========================================================================
//
// P_InitLava
//
//==========================================================================

void P_InitLava(void)
{
	memset(&LavaInflictor, 0, sizeof(mobj_t));
	LavaInflictor.type = MT_CIRCLEFLAME;
	LavaInflictor.flags2 = MF2_FIREDAMAGE|MF2_NODMGTHRUST;
}

//==========================================================================
//
// P_InitTerrainTypes
//
//==========================================================================

void P_InitTerrainTypes(void)
{
	int i;
	int lump;
	int size;

	size = (numflats + 1) * sizeof(int);
	TerrainTypes = (int *) Z_Malloc(size, PU_STATIC, NULL);
	memset(TerrainTypes, 0, size);
	for (i = 0; TerrainTypeDefs[i].type != -1; i++)
	{
		lump = W_CheckNumForName(TerrainTypeDefs[i].name);
		if (lump != -1)
		{
			TerrainTypes[lump - firstflat] = TerrainTypeDefs[i].type;
		}
	}
}

//==========================================================================
//
// getSide
//
// Will return a side_t* given the number of the current sector, the
// line number, and the side (0/1) that you want.
//
//==========================================================================

/*
side_t *getSide(int currentSector, int line, int side)
{
	return &sides[ (sectors[currentSector].lines[line])->sidenum[side] ];
}
*/

//==========================================================================
//
// getSector
//
// Will return a sector_t* given the number of the current sector, the
// line number, and the side (0/1) that you want.
//
//==========================================================================

/*
sector_t *getSector(int currentSector, int line, int side)
{
	return sides[ (sectors[currentSector].lines[line])->sidenum[side] ].sector;
}
*/

//==========================================================================
//
// twoSided
//
// Given the sector number and the line number, will tell you whether
// the line is two-sided or not.
//
//==========================================================================

/*
int twoSided(int sector, int line)
{
	return (sectors[sector].lines[line])->flags & ML_TWOSIDED;
}
*/

//==================================================================
//
// Return sector_t * of sector next to current. NULL if not two-sided line
//
//==================================================================

sector_t *getNextSector(line_t *line,sector_t *sec)
{
	if (!(line->flags & ML_TWOSIDED))
		return NULL;

	if (line->frontsector == sec)
		return line->backsector;

	return line->frontsector;
}

//==================================================================
//
// FIND LOWEST FLOOR HEIGHT IN SURROUNDING SECTORS
//
//==================================================================

fixed_t P_FindLowestFloorSurrounding(sector_t *sec)
{
	int		i;
	line_t		*check;
	sector_t	*other;
	fixed_t		floor = sec->floorheight;

	for (i = 0; i < sec->linecount; i++)
	{
		check = sec->lines[i];
		other = getNextSector(check,sec);
		if (!other)
			continue;
		if (other->floorheight < floor)
			floor = other->floorheight;
	}
	return floor;
}

//==================================================================
//
// FIND HIGHEST FLOOR HEIGHT IN SURROUNDING SECTORS
//
//==================================================================

fixed_t P_FindHighestFloorSurrounding(sector_t *sec)
{
	int		i;
	line_t		*check;
	sector_t	*other;
	fixed_t		floor = -500*FRACUNIT;

	for (i = 0; i < sec->linecount; i++)
	{
		check = sec->lines[i];
		other = getNextSector(check,sec);
		if (!other)
			continue;
		if (other->floorheight > floor)
			floor = other->floorheight;
	}
	return floor;
}

//==================================================================
//
// FIND NEXT HIGHEST FLOOR IN SURROUNDING SECTORS
//
//==================================================================


#define MAX_ADJOINING_SECTORS	20	/* 20 adjoining sectors max! */

fixed_t P_FindNextHighestFloor(sector_t *sec,int currentheight)
{
	int		i;
	int		h;
	int		min;
	line_t		*check;
	sector_t	*other;
	fixed_t		height = currentheight;
	fixed_t		heightlist[MAX_ADJOINING_SECTORS];

	for (i = 0, h = 0; i < sec->linecount; i++)
	{
		check = sec->lines[i];
		other = getNextSector(check,sec);
		if (!other)
			continue;
		if (other->floorheight > height)
			heightlist[h++] = other->floorheight;
		if (h >= MAX_ADJOINING_SECTORS)
		{
			fprintf(stderr, "Sector with more than %d adjoining sectors\n",
					 MAX_ADJOINING_SECTORS);
			break;
		}
	}

	//
	// Find lowest height in list
	//
	if(!h)
		return currentheight;

	min = heightlist[0];
	for (i = 1; i < h; i++)
	{
		if (heightlist[i] < min)
			min = heightlist[i];
	}

	return min;
}

//==================================================================
//
// FIND LOWEST CEILING IN THE SURROUNDING SECTORS
//
//==================================================================

fixed_t P_FindLowestCeilingSurrounding(sector_t *sec)
{
	int		i;
	line_t		*check;
	sector_t	*other;
	fixed_t		height = H2MAXINT;

	for (i = 0; i < sec->linecount; i++)
	{
		check = sec->lines[i];
		other = getNextSector(check,sec);
		if (!other)
			continue;
		if (other->ceilingheight < height)
			height = other->ceilingheight;
	}
	return height;
}

//==================================================================
//
// FIND HIGHEST CEILING IN THE SURROUNDING SECTORS
//
//==================================================================

fixed_t P_FindHighestCeilingSurrounding(sector_t *sec)
{
	int		i;
	line_t		*check;
	sector_t	*other;
	fixed_t		height = 0;

	for (i = 0; i < sec->linecount; i++)
	{
		check = sec->lines[i];
		other = getNextSector(check,sec);
		if (!other)
			continue;
		if (other->ceilingheight > height)
			height = other->ceilingheight;
	}
	return height;
}

//==================================================================
//
// RETURN NEXT SECTOR # THAT LINE TAG REFERS TO
//
//==================================================================

/*
int P_FindSectorFromLineTag(line_t  *line,int start)
{
	int     i;

	for (i = start + 1; i < numsectors; i++)
	{
		if (sectors[i].tag == line->arg1)
			return i;
	}
	return -1;
}
*/

//=========================================================================
//
// P_FindSectorFromTag
//
//=========================================================================

int P_FindSectorFromTag(int tag, int start)
{
	int i;

	for (i = start + 1; i < numsectors; i++)
	{
		if (sectors[i].tag == tag)
		{
			return i;
		}
	}
	return -1;
}

//==================================================================
//
// Find minimum light from an adjacent sector
//
//==================================================================

/*
int P_FindMinSurroundingLight(sector_t *sector,int max)
{
	int		i;
	int		min;
	line_t		*line;
	sector_t	*check;

	min = max;
	for (i = 0; i < sector->linecount; i++)
	{
		line = sector->lines[i];
		check = getNextSector(line,sector);
		if (!check)
			continue;
		if (check->lightlevel < min)
			min = check->lightlevel;
	}
	return min;
}
*/

//=========================================================================
//
// EV_SectorSoundChange
//
//=========================================================================

static boolean EV_SectorSoundChange(byte *args)
{
	int secNum;
	boolean rtn;

	if (!args[0])
	{
		return false;
	}
	secNum = -1;
	rtn = false;
	while ((secNum = P_FindSectorFromTag(args[0], secNum)) >= 0)
	{
		sectors[secNum].seqType = args[1];
		rtn = true;
	}
	return rtn;
}

//============================================================================
//
// CheckedLockedDoor
//
//============================================================================

static boolean CheckedLockedDoor(mobj_t *mo, byte lock)
{
	char LockedBuffer[80];

	if (!mo->player)
	{
		return false;
	}
	if (!lock)
	{
		return true;
	}
	if (!(mo->player->keys & (1<<(lock-1))))
	{
		snprintf(LockedBuffer, sizeof(LockedBuffer),
			 "YOU NEED THE %s\n", TextKeyMessages[lock-1]);
		P_SetMessage(mo->player, LockedBuffer, true);
		S_StartSound(mo, SFX_DOOR_LOCKED);
		return false;
	}
	return true;
}

//==========================================================================
//
// EV_LineSearchForPuzzleItem
//
//==========================================================================

static boolean EV_LineSearchForPuzzleItem(line_t *line, byte *args, mobj_t *mo)
{
	player_t *player;
	int i;
	artitype_t type, arti;

	USED(args);
	if (!mo)
		return false;
	player = mo->player;
	if (!player)
		return false;

	// Search player's inventory for puzzle items
	for (i = 0; i < player->artifactCount; i++)
	{
		arti = player->inventory[i].type;
		if (arti < arti_firstpuzzitem)
			continue;
		type = arti - arti_firstpuzzitem;
		//if (type < 0)
		//	continue;
		if (type == line->arg1)
		{
			// A puzzle item was found for the line
			if (P_UseArtifact(player, arti))
			{
				// A puzzle item was found for the line
				P_PlayerRemoveArtifact(player, i);
				if (player == &players[consoleplayer])
				{
					if (arti < arti_firstpuzzitem)
					{
						S_StartSound(NULL, SFX_ARTIFACT_USE);
					}
					else
					{
						S_StartSound(NULL, SFX_PUZZLE_SUCCESS);
					}
					ArtifactFlash = 4;
				}
				return true;
			}
		}
	}
	return false;
}


/*
==============================================================================

EVENTS

Events are operations triggered by using, crossing, or shooting special lines,
or by timed thinkers

==============================================================================
*/

//============================================================================
//
// P_ExecuteLineSpecial
//
//============================================================================

boolean P_ExecuteLineSpecial(int special, byte *args, line_t *line, int side,
								mobj_t *mo)
{
	boolean buttonSuccess;

	buttonSuccess = false;
	switch (special)
	{
	case 1: // Poly Start Line
		break;
	case 2: // Poly Rotate Left
		buttonSuccess = EV_RotatePoly(line, args, 1, false);
		break;
	case 3: // Poly Rotate Right
		buttonSuccess = EV_RotatePoly(line, args, -1, false);
		break;
	case 4: // Poly Move
		buttonSuccess = EV_MovePoly(line, args, false, false);
		break;
	case 5: // Poly Explicit Line:  Only used in initialization
		break;
	case 6: // Poly Move Times 8
		buttonSuccess = EV_MovePoly(line, args, true, false);
		break;
	case 7: // Poly Door Swing
		buttonSuccess = EV_OpenPolyDoor(line, args, PODOOR_SWING);
		break;
	case 8: // Poly Door Slide
		buttonSuccess = EV_OpenPolyDoor(line, args, PODOOR_SLIDE);
		break;
	case 10: // Door Close
		buttonSuccess = EV_DoDoor(line, args, DREV_CLOSE);
		break;
	case 11: // Door Open
		if (!args[0])
		{
			buttonSuccess = EV_VerticalDoor(line, mo);
		}
		else
		{
			buttonSuccess = EV_DoDoor(line, args, DREV_OPEN);
		}
		break;
	case 12: // Door Raise
		if (!args[0])
		{
			buttonSuccess = EV_VerticalDoor(line, mo);
		}
		else
		{
			buttonSuccess = EV_DoDoor(line, args, DREV_NORMAL);
		}
		break;
	case 13: // Door Locked_Raise
		if (CheckedLockedDoor(mo, args[3]))
		{
			if (!args[0])
			{
				buttonSuccess = EV_VerticalDoor(line, mo);
			}
			else
			{
				buttonSuccess = EV_DoDoor(line, args, DREV_NORMAL);
			}
		}
		break;
	case 20: // Floor Lower by Value
		buttonSuccess = EV_DoFloor(line, args, FLEV_LOWERFLOORBYVALUE);
		break;
	case 21: // Floor Lower to Lowest
		buttonSuccess = EV_DoFloor(line, args, FLEV_LOWERFLOORTOLOWEST);
		break;
	case 22: // Floor Lower to Nearest
		buttonSuccess = EV_DoFloor(line, args, FLEV_LOWERFLOOR);
		break;
	case 23: // Floor Raise by Value
		buttonSuccess = EV_DoFloor(line, args, FLEV_RAISEFLOORBYVALUE);
		break;
	case 24: // Floor Raise to Highest
		buttonSuccess = EV_DoFloor(line, args, FLEV_RAISEFLOOR);
		break;
	case 25: // Floor Raise to Nearest
		buttonSuccess = EV_DoFloor(line, args, FLEV_RAISEFLOORTONEAREST);
		break;
	case 26: // Stairs Build Down Normal
		buttonSuccess = EV_BuildStairs(line, args, -1, STAIRS_NORMAL);
		break;
	case 27: // Build Stairs Up Normal
		buttonSuccess = EV_BuildStairs(line, args, 1, STAIRS_NORMAL);
		break;
	case 28: // Floor Raise and Crush
		buttonSuccess = EV_DoFloor(line, args, FLEV_RAISEFLOORCRUSH);
		break;
	case 29: // Build Pillar (no crushing)
		buttonSuccess = EV_BuildPillar(line, args, false);
		break;
	case 30: // Open Pillar
		buttonSuccess = EV_OpenPillar(line, args);
		break;
	case 31: // Stairs Build Down Sync
		buttonSuccess = EV_BuildStairs(line, args, -1, STAIRS_SYNC);
		break;
	case 32: // Build Stairs Up Sync
		buttonSuccess = EV_BuildStairs(line, args, 1, STAIRS_SYNC);
		break;
	case 35: // Raise Floor by Value Times 8
		buttonSuccess = EV_DoFloor(line, args, FLEV_RAISEBYVALUETIMES8);
		break;
	case 36: // Lower Floor by Value Times 8
		buttonSuccess = EV_DoFloor(line, args, FLEV_LOWERBYVALUETIMES8);
		break;
	case 40: // Ceiling Lower by Value
		buttonSuccess = EV_DoCeiling(line, args, CLEV_LOWERBYVALUE);
		break;
	case 41: // Ceiling Raise by Value
		buttonSuccess = EV_DoCeiling(line, args, CLEV_RAISEBYVALUE);
		break;
	case 42: // Ceiling Crush and Raise
		buttonSuccess = EV_DoCeiling(line, args, CLEV_CRUSHANDRAISE);
		break;
	case 43: // Ceiling Lower and Crush
		buttonSuccess = EV_DoCeiling(line, args, CLEV_LOWERANDCRUSH);
		break;
	case 44: // Ceiling Crush Stop
		buttonSuccess = EV_CeilingCrushStop(line, args);
		break;
	case 45: // Ceiling Crush Raise and Stay
		buttonSuccess = EV_DoCeiling(line, args, CLEV_CRUSHRAISEANDSTAY);
		break;
	case 46: // Floor Crush Stop
		buttonSuccess = EV_FloorCrushStop(line, args);
		break;
	case 60: // Plat Perpetual Raise
		buttonSuccess = EV_DoPlat(line, args, PLAT_PERPETUALRAISE, 0);
		break;
	case 61: // Plat Stop
		EV_StopPlat(line, args);
		break;
	case 62: // Plat Down-Wait-Up-Stay
		buttonSuccess = EV_DoPlat(line, args, PLAT_DOWNWAITUPSTAY, 0);
		break;
	case 63: // Plat Down-by-Value*8-Wait-Up-Stay
		buttonSuccess = EV_DoPlat(line, args, PLAT_DOWNBYVALUEWAITUPSTAY, 0);
		break;
	case 64: // Plat Up-Wait-Down-Stay
		buttonSuccess = EV_DoPlat(line, args, PLAT_UPWAITDOWNSTAY, 0);
		break;
	case 65: // Plat Up-by-Value*8-Wait-Down-Stay
		buttonSuccess = EV_DoPlat(line, args, PLAT_UPBYVALUEWAITDOWNSTAY, 0);
		break;
	case 66: // Floor Lower Instant * 8
		buttonSuccess = EV_DoFloor(line, args, FLEV_LOWERTIMES8INSTANT);
		break;
	case 67: // Floor Raise Instant * 8
		buttonSuccess = EV_DoFloor(line, args, FLEV_RAISETIMES8INSTANT);
		break;
	case 68: // Floor Move to Value * 8
		buttonSuccess = EV_DoFloor(line, args, FLEV_MOVETOVALUETIMES8);
		break;
	case 69: // Ceiling Move to Value * 8
		buttonSuccess = EV_DoCeiling(line, args, CLEV_MOVETOVALUETIMES8);
		break;
	case 70: // Teleport
		if (side == 0)
		{ // Only teleport when crossing the front side of a line
			buttonSuccess = EV_Teleport(args[0], mo, true);
		}
		break;
	case 71: // Teleport, no fog
		if (side == 0)
		{ // Only teleport when crossing the front side of a line
			buttonSuccess = EV_Teleport(args[0], mo, false);
		}
		break;
	case 72: // Thrust Mobj
		if (!side) // Only thrust on side 0
		{
			P_ThrustMobj(mo, args[0]*(ANGLE_90/64), args[1]<<FRACBITS);
			buttonSuccess = 1;
		}
		break;
	case 73: // Damage Mobj
		if (args[0])
		{
			P_DamageMobj(mo, NULL, NULL, args[0]);
		}
		else
		{ // If arg1 is zero, then guarantee a kill
			P_DamageMobj(mo, NULL, NULL, 10000);
		}
		buttonSuccess = 1;
		break;
	case 74: // Teleport_NewMap
		if (side == 0)
		{ // Only teleport when crossing the front side of a line
			// Players must be alive to teleport
			if (!(mo && mo->player && mo->player->playerstate == PST_DEAD))
			{
				G_Completed(args[0], args[1]);
				buttonSuccess = true;
			}
		}
		break;
	case 75: // Teleport_EndGame
		if (side == 0)
		{ // Only teleport when crossing the front side of a line
			// Players must be alive to teleport
			if (!(mo && mo->player && mo->player->playerstate == PST_DEAD))
			{
				buttonSuccess = true;
				if (deathmatch)
				{ // Winning in deathmatch just goes back to map 1
					G_Completed(1, 0);
				}
				else
				{ // Passing -1, -1 to G_Completed() starts the Finale
					G_Completed(-1, -1);
				}
			}
		}
		break;
	case 80: // ACS_Execute
		buttonSuccess = P_StartACS(args[0], args[1], &args[2], mo, line, side);
		break;
	case 81: // ACS_Suspend
		buttonSuccess = P_SuspendACS(args[0], args[1]);
		break;
	case 82: // ACS_Terminate
		buttonSuccess = P_TerminateACS(args[0], args[1]);
		break;
	case 83: // ACS_LockedExecute
		buttonSuccess = P_StartLockedACS(line, args, mo, side);
		break;
	case 90: // Poly Rotate Left Override
		buttonSuccess = EV_RotatePoly(line, args, 1, true);
		break;
	case 91: // Poly Rotate Right Override
		buttonSuccess = EV_RotatePoly(line, args, -1, true);
		break;
	case 92: // Poly Move Override
		buttonSuccess = EV_MovePoly(line, args, false, true);
		break;
	case 93: // Poly Move Times 8 Override
		buttonSuccess = EV_MovePoly(line, args, true, true);
		break;
	case 94: // Build Pillar Crush
		buttonSuccess = EV_BuildPillar(line, args, true);
		break;
	case 95: // Lower Floor and Ceiling
		buttonSuccess = EV_DoFloorAndCeiling(line, args, false);
		break;
	case 96: // Raise Floor and Ceiling
		buttonSuccess = EV_DoFloorAndCeiling(line, args, true);
		break;
	case 109: // Force Lightning
		buttonSuccess = true;
		P_ForceLightning();
		break;
	case 110: // Light Raise by Value
		buttonSuccess = EV_SpawnLight(line, args, LITE_RAISEBYVALUE);
		break;
	case 111: // Light Lower by Value
		buttonSuccess = EV_SpawnLight(line, args, LITE_LOWERBYVALUE);
		break;
	case 112: // Light Change to Value
		buttonSuccess = EV_SpawnLight(line, args, LITE_CHANGETOVALUE);
		break;
	case 113: // Light Fade
		buttonSuccess = EV_SpawnLight(line, args, LITE_FADE);
		break;
	case 114: // Light Glow
		buttonSuccess = EV_SpawnLight(line, args, LITE_GLOW);
		break;
	case 115: // Light Flicker
		buttonSuccess = EV_SpawnLight(line, args, LITE_FLICKER);
		break;
	case 116: // Light Strobe
		buttonSuccess = EV_SpawnLight(line, args, LITE_STROBE);
		break;
	case 120: // Quake Tremor
		buttonSuccess = A_LocalQuake(args, mo);
		break;
	case 129: // UsePuzzleItem
		buttonSuccess = EV_LineSearchForPuzzleItem(line, args, mo);
		break;
	case 130: // Thing_Activate
		buttonSuccess = EV_ThingActivate(args[0]);
		break;
	case 131: // Thing_Deactivate
		buttonSuccess = EV_ThingDeactivate(args[0]);
		break;
	case 132: // Thing_Remove
		buttonSuccess = EV_ThingRemove(args[0]);
		break;
	case 133: // Thing_Destroy
		buttonSuccess = EV_ThingDestroy(args[0]);
		break;
	case 134: // Thing_Projectile
		buttonSuccess = EV_ThingProjectile(args, 0);
		break;
	case 135: // Thing_Spawn
		buttonSuccess = EV_ThingSpawn(args, 1);
		break;
	case 136: // Thing_ProjectileGravity
		buttonSuccess = EV_ThingProjectile(args, 1);
		break;
	case 137: // Thing_SpawnNoFog
		buttonSuccess = EV_ThingSpawn(args, 0);
		break;
	case 138: // Floor_Waggle
		buttonSuccess = EV_StartFloorWaggle(args[0], args[1], args[2],
						    args[3], args[4]);
		break;
	case 140: // Sector_SoundChange
		buttonSuccess = EV_SectorSoundChange(args);
		break;

	// Line specials only processed during level initialization
	// 100: Scroll_Texture_Left
	// 101: Scroll_Texture_Right
	// 102: Scroll_Texture_Up
	// 103: Scroll_Texture_Down
	// 121: Line_SetIdentification

	// Inert Line specials
	default:
		break;
	}
	return buttonSuccess;
}

//============================================================================
//
// P_ActivateLine
//
//============================================================================

boolean P_ActivateLine(line_t *line, mobj_t *mo, int side, int activationType)
{
	int lineActivation;
	boolean repeat;
	boolean buttonSuccess;

	lineActivation = GET_SPAC(line->flags);
	if (lineActivation != activationType)
	{
		return false;
	}
	if (!mo->player && !(mo->flags&MF_MISSILE))
	{
		if (lineActivation != SPAC_MCROSS)
		{ // currently, monsters can only activate the MCROSS activation type
			return false;
		}
		if (line->flags & ML_SECRET)
			return false;		// never open secret doors
	}
	repeat = line->flags & ML_REPEAT_SPECIAL;
	USED(repeat);
	buttonSuccess = false;
	USED(buttonSuccess);

	buttonSuccess = P_ExecuteLineSpecial(line->special, &line->arg1, line, side, mo);
	if (!repeat && buttonSuccess)
	{ // clear the special on non-retriggerable lines
		line->special = 0;
	}
	if ((lineActivation == SPAC_USE || lineActivation == SPAC_IMPACT) && buttonSuccess)
	{
		P_ChangeSwitchTexture(line, repeat);
	}
	return true;
}

//----------------------------------------------------------------------------
//
// PROC P_PlayerInSpecialSector
//
// Called every tic frame that the player origin is in a special sector.
//
//----------------------------------------------------------------------------

void P_PlayerInSpecialSector(player_t *player)
{
	sector_t *sector;
	static int pushTab[3] =
	{
		2048*5,
		2048*10,
		2048*25
	};

	sector = player->mo->subsector->sector;
	if (player->mo->z != sector->floorheight)
	{ // Player is not touching the floor
		return;
	}
	switch (sector->special)
	{
	case 9: // SecretArea
		player->secretcount++;
		sector->special = 0;
		break;

	case 201: case 202: case 203: // Scroll_North_xxx
		P_Thrust(player, ANG90, pushTab[sector->special-201]);
		break;
	case 204: case 205: case 206: // Scroll_East_xxx
		P_Thrust(player, 0, pushTab[sector->special-204]);
		break;
	case 207: case 208: case 209: // Scroll_South_xxx
		P_Thrust(player, ANG270, pushTab[sector->special-207]);
		break;
	case 210: case 211: case 212: // Scroll_West_xxx
		P_Thrust(player, ANG180, pushTab[sector->special-210]);
		break;
	case 213: case 214: case 215: // Scroll_NorthWest_xxx
		P_Thrust(player, ANG90+ANG45, pushTab[sector->special-213]);
		break;
	case 216: case 217: case 218: // Scroll_NorthEast_xxx
		P_Thrust(player, ANG45, pushTab[sector->special-216]);
		break;
	case 219: case 220: case 221: // Scroll_SouthEast_xxx
		P_Thrust(player, ANG270+ANG45, pushTab[sector->special-219]);
		break;
	case 222: case 223: case 224: // Scroll_SouthWest_xxx
		P_Thrust(player, ANG180+ANG45, pushTab[sector->special-222]);
		break;

	case 40: case 41: case 42: case 43: case 44: case 45:
	case 46: case 47: case 48: case 49: case 50: case 51:
		// Wind specials are handled in (P_mobj):P_XYMovement
		break;

	case 26: // Stairs_Special1
	case 27: // Stairs_Special2
		// Used in (P_floor):ProcessStairSector
		break;

	case 198: // Lightning Special
	case 199: // Lightning Flash special
	case 200: // Sky2
		// Used in (R_plane):R_Drawplanes
		break;

	default:
		I_Error("P_PlayerInSpecialSector: "
			"unknown special %i", sector->special);
	}
}

//============================================================================
//
// P_PlayerOnSpecialFlat
//
//============================================================================

void P_PlayerOnSpecialFlat(player_t *player, int floorType)
{
	if (player->mo->z != player->mo->floorz)
	{ // Player is not touching the floor
		return;
	}
	switch (floorType)
	{
	case FLOOR_LAVA:
		if (!(leveltime & 31))
		{
			P_DamageMobj(player->mo, &LavaInflictor, NULL, 10);
			S_StartSound(player->mo, SFX_LAVA_SIZZLE);
		}
		break;
	default:
		break;
	}
}

//----------------------------------------------------------------------------
//
// PROC P_UpdateSpecials
//
//----------------------------------------------------------------------------

void P_UpdateSpecials(void)
{
	int i;

	// Handle buttons
	for (i = 0; i < MAXBUTTONS; i++)
	{
		if (buttonlist[i].btimer)
		{
			buttonlist[i].btimer--;
			if (!buttonlist[i].btimer)
			{
				switch (buttonlist[i].where)
				{
				case SWTCH_TOP:
					sides[buttonlist[i].line->sidenum[0]].toptexture =
							buttonlist[i].btexture;
					break;
				case SWTCH_MIDDLE:
					sides[buttonlist[i].line->sidenum[0]].midtexture =
							buttonlist[i].btexture;
					break;
				case SWTCH_BOTTOM:
					sides[buttonlist[i].line->sidenum[0]].bottomtexture =
							buttonlist[i].btexture;
					break;
				}
				//S_StartSound(buttonlist[i].soundorg, sfx_switch);
				memset(&buttonlist[i], 0, sizeof(button_t));
			}
		}
	}
}

/*
==============================================================================

							SPECIAL SPAWNING

==============================================================================
*/

/*
================================================================================
= P_SpawnSpecials
=
= After the map has been loaded, scan for specials that
= spawn thinkers
=
===============================================================================
*/

short	numlinespecials;
line_t	*linespeciallist[MAXLINEANIMS];

void P_SpawnSpecials (void)
{
	sector_t	*sector;
	int		i;

	//
	// Init special SECTORs
	//
	sector = sectors;
	for (i = 0; i < numsectors; i++, sector++)
	{
		if (!sector->special)
			continue;
		switch (sector->special)
		{
		case 1: // Phased light
			// Hardcoded base, use sector->lightlevel as the index
			P_SpawnPhasedLight(sector, 80, -1);
			break;
		case 2: // Phased light sequence start
			P_SpawnLightSequence(sector, 1);
			break;
		// Specials 3 & 4 are used by the phased light sequences

		/*
		case 1:		// FLICKERING LIGHTS
			P_SpawnLightFlash (sector);
			break;
		case 2:		// STROBE FAST
			P_SpawnStrobeFlash(sector, FASTDARK, 0);
			break;
		case 3:		// STROBE SLOW
			P_SpawnStrobeFlash(sector, SLOWDARK, 0);
			break;
		case 4:		// STROBE FAST/DEATH SLIME
			P_SpawnStrobeFlash(sector, FASTDARK, 0);
			sector->special = 4;
			break;
		case 8:		// GLOWING LIGHT
			P_SpawnGlowingLight(sector);
			break;
		case 9:		// SECRET SECTOR
			totalsecret++;
			break;
		case 10:	// DOOR CLOSE IN 30 SECONDS
			P_SpawnDoorCloseIn30 (sector);
			break;
		case 12:	// SYNC STROBE SLOW
			P_SpawnStrobeFlash(sector, SLOWDARK, 1);
			break;
		case 13:	// SYNC STROBE FAST
			P_SpawnStrobeFlash(sector, FASTDARK, 1);
			break;
		case 14:	// DOOR RAISE IN 5 MINUTES
			P_SpawnDoorRaiseIn5Mins (sector, i);
			break;
		*/
		}
	}

	//
	// Init line EFFECTs
	//
	numlinespecials = 0;
	TaggedLineCount = 0;
	for (i = 0; i < numlines; i++)
	{
		switch (lines[i].special)
		{
		case 100: // Scroll_Texture_Left
		case 101: // Scroll_Texture_Right
		case 102: // Scroll_Texture_Up
		case 103: // Scroll_Texture_Down
			linespeciallist[numlinespecials] = &lines[i];
			numlinespecials++;
			break;
		case 121: // Line_SetIdentification
			if (lines[i].arg1)
			{
				if (TaggedLineCount == MAX_TAGGED_LINES)
				{
					I_Error("P_SpawnSpecials: MAX_TAGGED_LINES "
						"(%d) exceeded.", MAX_TAGGED_LINES);
				}
				TaggedLines[TaggedLineCount].line = &lines[i];
				TaggedLines[TaggedLineCount++].lineTag = lines[i].arg1;
			}
			lines[i].special = 0;
			break;
		}
	}

	//
	// Init other misc stuff
	//
	for (i = 0; i < MAXCEILINGS; i++)
		activeceilings[i] = NULL;
	for (i = 0; i < MAXPLATS; i++)
		activeplats[i] = NULL;
	for (i = 0; i < MAXBUTTONS; i++)
		memset(&buttonlist[i], 0, sizeof(button_t));

	// Initialize flat and texture animations
	P_InitFTAnims();
}

//==========================================================================
//
// P_FindLine
//
//==========================================================================

line_t *P_FindLine(int lineTag, int *searchPosition)
{
	int i;

	for (i = *searchPosition + 1; i < TaggedLineCount; i++)
	{
		if (TaggedLines[i].lineTag == lineTag)
		{
			*searchPosition = i;
			return TaggedLines[i].line;
		}
	}
	*searchPosition = -1;
	return NULL;
}