//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 1993-2008 Raven Software
// Copyright(C) 2005-2014 Simon Howard
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//


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

#include "h2def.h"
#include "i_system.h"
#include "m_misc.h"
#include "p_local.h"
#include "s_sound.h"

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

#define MAX_TAGGED_LINES 64

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

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

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

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

static boolean 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!

    heightlist[0] = 0;

    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 = INT_MAX;

    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
//
//=========================================================================

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)
{
    extern char *TextKeyMessages[11];
    char LockedBuffer[80];

    if (!mo->player)
    {
        return false;
    }
    if (!lock)
    {
        return true;
    }
    if (!(mo->player->keys & (1 << (lock - 1))))
    {
        M_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
//
//==========================================================================

boolean EV_LineSearchForPuzzleItem(line_t * line, byte * args, mobj_t * mo)
{
    player_t *player;
    int i;
    int type;
    artitype_t 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
//
// Invoked when crossing a linedef. The args[] array should be at least
// 5 elements in length.
//
//============================================================================

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] * (ANG90 / 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
//
//============================================================================

boolean P_ActivateLine(line_t * line, mobj_t * mo, int side,
                       int activationType)
{
    byte args[5];
    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) != 0;
    buttonSuccess = false;

    // Construct args[] array to contain the arguments from the line, as we
    // cannot rely on struct field ordering and layout.
    args[0] = line->arg1;
    args[1] = line->arg2;
    args[2] = line->arg3;
    args[3] = line->arg4;
    args[4] = line->arg5;
    buttonSuccess = P_ExecuteLineSpecial(line->special, args, 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;
}
