text: 
  //**************************************************************************
  //**
  //** p_spec.c : Heretic 2 : Raven Software, Corp.
  //**
  //** $RCSfile: p_spec.c,v $
  //** $Revision: 1.67 $
  //** $Date: 96/01/06 18:37:33 $
  //** $Author: bgokey $
  //**
  //**************************************************************************
  
  // HEADER FILES ------------------------------------------------------------
  
  #include "h2def.h"
  #include "p_local.h"
  #include "soundst.h"
  
  // MACROS ------------------------------------------------------------------
  
  #define MAX_TAGGED_LINES 64
  
  // TYPES -------------------------------------------------------------------
  
  // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
  
  // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
  
  // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
  
  static dt_bool CheckedLockedDoor(mobj_t *mo, byte lock);
  
  // EXTERNAL DATA DECLARATIONS ----------------------------------------------
  
  // PUBLIC DATA DEFINITIONS -------------------------------------------------
  
  int *TerrainTypes;
  struct
  {
  	char *name;
  	int type;
  } TerrainTypeDefs[] =
  {
  	{ "X_005", FLOOR_WATER },
  	{ "X_001", FLOOR_LAVA },
  	{ "X_009", FLOOR_SLUDGE },
  	{ "F_033", FLOOR_ICE },
  	{ "END", -1 }
  };
  
  // PRIVATE DATA DEFINITIONS ------------------------------------------------
  
  static struct
  {
  	line_t *line;
  	int lineTag;
  } TaggedLines[MAX_TAGGED_LINES];
  static int TaggedLineCount;
  
  mobj_t LavaInflictor;
  
  // 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 = Z_Malloc(size, PU_STATIC, 0);
  	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
  //
  //==================================================================
  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[20];         // 20 adjoining sectors max!
  
  	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;
  	}
  
  	//
  	// Find lowest height in list
  	//
  	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 = MAXINT;
  
  	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
  //
  //=========================================================================
  
  dt_bool EV_SectorSoundChange(byte *args)
  {
  	int secNum;
  	dt_bool 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 dt_bool CheckedLockedDoor(mobj_t *mo, byte lock)
  {
  	extern char *TextKeyMessages[11];
  	char LockedBuffer[80];
  
  	if(!mo->player)
  	{
  		return false;
  	}
  	if(!lock)
  	{	
  		return true;
  	}
  	if(!(mo->player->keys&(1<<(lock-1))))
  	{
  		sprintf(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
  //
  //==========================================================================
  
  dt_bool EV_LineSearchForPuzzleItem(line_t *line, byte *args, mobj_t *mo)
  {
  	player_t *player;
  	int i;
  //	artitype_t type,arti;
  	int type,arti;
  
  	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;
  		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
  //
  //============================================================================
  
  dt_bool P_ExecuteLineSpecial(int special, byte *args, line_t *line, int side,
  	mobj_t *mo)
  {
  	dt_bool 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
  				if(!(mo && mo->player && mo->player->playerstate
  					== PST_DEAD)) // Players must be alive to teleport
  				{
  					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
  				if(!(mo && mo->player && mo->player->playerstate
  					== PST_DEAD)) // Players must be alive to teleport
  				{
  					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
  //
  //============================================================================
  
  dt_bool P_ActivateLine(line_t *line, mobj_t *mo, int side, int activationType)
  {
  	int lineActivation;
  	dt_bool repeat;
  	dt_bool 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;
  	buttonSuccess = false;
  
  	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((mobj_t *)&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;
  }
  