// Emacs style mode select   -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id:$
//
// Copyright (C) 1993-1996 by id Software, Inc.
//
// This source is available for distribution and/or modification
// only under the terms of the DOOM Source Code License as
// published by id Software. All rights reserved.
//
// The source is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
// for more details.
//
// $Log:$
//
// DESCRIPTION:
//      Implements special effects:
//      Texture animation, height or lighting changes
//       according to adjacent sectors, respective
//       utility functions, etc.
//      Line Tag handling. Line and Sector triggers.
//
//-----------------------------------------------------------------------------

static const char
rcsid[] = "$Id: p_spec.c,v 1.6 1997/02/03 22:45:12 b1 Exp $";

#include <stdlib.h>

#include "doomdef.h"
#include "doomstat.h"

#include "i_system.h"
#include "z_zone.h"
#include "m_argv.h"
#include "m_random.h"
#include "w_wad.h"

#include "r_local.h"
#include "p_local.h"

#include "g_game.h"

#include "s_sound.h"

// State.
#include "r_state.h"

// Data.
#include "sounds.h"


//
// Animating textures and planes
// There is another anim_t used in wi_stuff, unrelated.
//
typedef struct
{
    boolean     istexture;
    int         picnum;
    int         basepic;
    int         numpics;
    int         speed;

} anim_t;

//
//      source animation definition
//
typedef struct
{
    boolean     istexture;      // if false, it is a flat
    char        endname[9];
    char        startname[9];
    int         speed;
} animdef_t;



#define MAXANIMS                32

extern anim_t   anims[MAXANIMS];
extern anim_t*  lastanim;

//
// P_InitPicAnims
//

// Floor/ceiling animation sequences,
//  defined by first and last frame,
//  i.e. the flat (64x64 tile) name to
//  be used.
// The full animation sequence is given
//  using all the flats between the start
//  and end entry, in the order found in
//  the WAD file.
//
animdef_t               animdefs[] =
{
    {false,     "NUKAGE3",      "NUKAGE1",      8},
    {false,     "FWATER4",      "FWATER1",      8},
    {false,     "SWATER4",      "SWATER1",      8},
    {false,     "LAVA4",        "LAVA1",        8},
    {false,     "BLOOD3",       "BLOOD1",       8},

    // DOOM II flat animations.
    {false,     "RROCK08",      "RROCK05",      8},
    {false,     "SLIME04",      "SLIME01",      8},
    {false,     "SLIME08",      "SLIME05",      8},
    {false,     "SLIME12",      "SLIME09",      8},

    {true,      "BLODGR4",      "BLODGR1",      8},
    {true,      "SLADRIP3",     "SLADRIP1",     8},

    {true,      "BLODRIP4",     "BLODRIP1",     8},
    {true,      "FIREWALL",     "FIREWALA",     8},
    {true,      "GSTFONT3",     "GSTFONT1",     8},
    {true,      "FIRELAVA",     "FIRELAV3",     8},
    {true,      "FIREMAG3",     "FIREMAG1",     8},
    {true,      "FIREBLU2",     "FIREBLU1",     8},
    {true,      "ROCKRED3",     "ROCKRED1",     8},

    {true,      "BFALL4",       "BFALL1",       8},
    {true,      "SFALL4",       "SFALL1",       8},
    {true,      "WFALL4",       "WFALL1",       8},
    {true,      "DBRAIN4",      "DBRAIN1",      8},

    {-1}
};

anim_t          anims[MAXANIMS];
anim_t*         lastanim;


//
//      Animating line specials
//
#define MAXLINEANIMS            64

extern  short   numlinespecials;
extern  line_t* linespeciallist[MAXLINEANIMS];



void P_InitPicAnims (void)
{
    int         i;


    //  Init animation
    lastanim = anims;

    for (i=0 ; animdefs[i].istexture != -1 ; i++)
    {
        if (animdefs[i].istexture)
        {
            // different episode ?
            if (R_CheckTextureNumForName(animdefs[i].startname) == -1)
                continue;

            lastanim->picnum = R_TextureNumForName (animdefs[i].endname);
            lastanim->basepic = R_TextureNumForName (animdefs[i].startname);
        }
        else
        {
            if (W_CheckNumForName(animdefs[i].startname) == -1)
                continue;

            lastanim->picnum = R_FlatNumForName (animdefs[i].endname);
            lastanim->basepic = R_FlatNumForName (animdefs[i].startname);
        }

        lastanim->istexture = animdefs[i].istexture;
        lastanim->numpics = lastanim->picnum - lastanim->basepic + 1;

        if (lastanim->numpics < 2)
            I_Error ("P_InitPicAnims: bad cycle from %s to %s",
                     animdefs[i].startname,
                     animdefs[i].endname);

        lastanim->speed = animdefs[i].speed;
        lastanim++;
    }

}



//
// UTILITIES
//



//
// 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,
//  it 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;
}




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



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



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



//
// P_FindNextHighestFloor
// FIND NEXT HIGHEST FLOOR IN SURROUNDING SECTORS
// Note: this should be doable w/o a fixed array.

// 20 adjoining sectors max!
#define MAX_ADJOINING_SECTORS           20

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;

        // Check for overflow. Exit.
        if ( h >= MAX_ADJOINING_SECTORS )
        {
            fprintf( stderr,
                     "Sector with more than 20 adjoining sectors\n" );
            break;
        }
    }

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

    min = heightlist[0];

    // Range checking?
    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->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;
}



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

//
// P_CrossSpecialLine - TRIGGER
// Called every time a thing origin is about
//  to cross a line with a non 0 special.
//
void
P_CrossSpecialLine
( int           linenum,
  int           side,
  mobj_t*       thing )
{
    line_t*     line;
    int         ok;

    line = &lines[linenum];

    //  Triggers that other things can activate
    if (!thing->player)
    {
        // Things that should NOT trigger specials...
        switch(thing->type)
        {
          case MT_ROCKET:
          case MT_PLASMA:
          case MT_BFG:
          case MT_TROOPSHOT:
          case MT_HEADSHOT:
          case MT_BRUISERSHOT:
            return;
            break;

          default: break;
        }

        ok = 0;
        switch(line->special)
        {
          case 39:      // TELEPORT TRIGGER
          case 97:      // TELEPORT RETRIGGER
          case 125:     // TELEPORT MONSTERONLY TRIGGER
          case 126:     // TELEPORT MONSTERONLY RETRIGGER
          case 4:       // RAISE DOOR
          case 10:      // PLAT DOWN-WAIT-UP-STAY TRIGGER
          case 88:      // PLAT DOWN-WAIT-UP-STAY RETRIGGER
            ok = 1;
            break;
        }
        if (!ok)
            return;
    }


    // Note: could use some const's here.
    switch (line->special)
    {
        // TRIGGERS.
        // All from here to RETRIGGERS.
      case 2:
        // Open Door
        EV_DoDoor(line,open);
        line->special = 0;
        break;

      case 3:
        // Close Door
        EV_DoDoor(line,close);
        line->special = 0;
        break;

      case 4:
        // Raise Door
        EV_DoDoor(line,normal);
        line->special = 0;
        break;

      case 5:
        // Raise Floor
        EV_DoFloor(line,raiseFloor);
        line->special = 0;
        break;

      case 6:
        // Fast Ceiling Crush & Raise
        EV_DoCeiling(line,fastCrushAndRaise);
        line->special = 0;
        break;

      case 8:
        // Build Stairs
        EV_BuildStairs(line,build8);
        line->special = 0;
        break;

      case 10:
        // PlatDownWaitUp
        EV_DoPlat(line,downWaitUpStay,0);
        line->special = 0;
        break;

      case 12:
        // Light Turn On - brightest near
        EV_LightTurnOn(line,0);
        line->special = 0;
        break;

      case 13:
        // Light Turn On 255
        EV_LightTurnOn(line,255);
        line->special = 0;
        break;

      case 16:
        // Close Door 30
        EV_DoDoor(line,close30ThenOpen);
        line->special = 0;
        break;

      case 17:
        // Start Light Strobing
        EV_StartLightStrobing(line);
        line->special = 0;
        break;

      case 19:
        // Lower Floor
        EV_DoFloor(line,lowerFloor);
        line->special = 0;
        break;

      case 22:
        // Raise floor to nearest height and change texture
        EV_DoPlat(line,raiseToNearestAndChange,0);
        line->special = 0;
        break;

      case 25:
        // Ceiling Crush and Raise
        EV_DoCeiling(line,crushAndRaise);
        line->special = 0;
        break;

      case 30:
        // Raise floor to shortest texture height
        //  on either side of lines.
        EV_DoFloor(line,raiseToTexture);
        line->special = 0;
        break;

      case 35:
        // Lights Very Dark
        EV_LightTurnOn(line,35);
        line->special = 0;
        break;

      case 36:
        // Lower Floor (TURBO)
        EV_DoFloor(line,turboLower);
        line->special = 0;
        break;

      case 37:
        // LowerAndChange
        EV_DoFloor(line,lowerAndChange);
        line->special = 0;
        break;

      case 38:
        // Lower Floor To Lowest
        EV_DoFloor( line, lowerFloorToLowest );
        line->special = 0;
        break;

      case 39:
        // TELEPORT!
        EV_Teleport( line, side, thing );
        line->special = 0;
        break;

      case 40:
        // RaiseCeilingLowerFloor
        EV_DoCeiling( line, raiseToHighest );
        EV_DoFloor( line, lowerFloorToLowest );
        line->special = 0;
        break;

      case 44:
        // Ceiling Crush
        EV_DoCeiling( line, lowerAndCrush );
        line->special = 0;
        break;

      case 52:
        // EXIT!
        G_ExitLevel ();
        break;

      case 53:
        // Perpetual Platform Raise
        EV_DoPlat(line,perpetualRaise,0);
        line->special = 0;
        break;

      case 54:
        // Platform Stop
        EV_StopPlat(line);
        line->special = 0;
        break;

      case 56:
        // Raise Floor Crush
        EV_DoFloor(line,raiseFloorCrush);
        line->special = 0;
        break;

      case 57:
        // Ceiling Crush Stop
        EV_CeilingCrushStop(line);
        line->special = 0;
        break;

      case 58:
        // Raise Floor 24
        EV_DoFloor(line,raiseFloor24);
        line->special = 0;
        break;

      case 59:
        // Raise Floor 24 And Change
        EV_DoFloor(line,raiseFloor24AndChange);
        line->special = 0;
        break;

      case 104:
        // Turn lights off in sector(tag)
        EV_TurnTagLightsOff(line);
        line->special = 0;
        break;

      case 108:
        // Blazing Door Raise (faster than TURBO!)
        EV_DoDoor (line,blazeRaise);
        line->special = 0;
        break;

      case 109:
        // Blazing Door Open (faster than TURBO!)
        EV_DoDoor (line,blazeOpen);
        line->special = 0;
        break;

      case 100:
        // Build Stairs Turbo 16
        EV_BuildStairs(line,turbo16);
        line->special = 0;
        break;

      case 110:
        // Blazing Door Close (faster than TURBO!)
        EV_DoDoor (line,blazeClose);
        line->special = 0;
        break;

      case 119:
        // Raise floor to nearest surr. floor
        EV_DoFloor(line,raiseFloorToNearest);
        line->special = 0;
        break;

      case 121:
        // Blazing PlatDownWaitUpStay
        EV_DoPlat(line,blazeDWUS,0);
        line->special = 0;
        break;

      case 124:
        // Secret EXIT
        G_SecretExitLevel ();
        break;

      case 125:
        // TELEPORT MonsterONLY
        if (!thing->player)
        {
            EV_Teleport( line, side, thing );
            line->special = 0;
        }
        break;

      case 130:
        // Raise Floor Turbo
        EV_DoFloor(line,raiseFloorTurbo);
        line->special = 0;
        break;

      case 141:
        // Silent Ceiling Crush & Raise
        EV_DoCeiling(line,silentCrushAndRaise);
        line->special = 0;
        break;

        // RETRIGGERS.  All from here till end.
      case 72:
        // Ceiling Crush
        EV_DoCeiling( line, lowerAndCrush );
        break;

      case 73:
        // Ceiling Crush and Raise
        EV_DoCeiling(line,crushAndRaise);
        break;

      case 74:
        // Ceiling Crush Stop
        EV_CeilingCrushStop(line);
        break;

      case 75:
        // Close Door
        EV_DoDoor(line,close);
        break;

      case 76:
        // Close Door 30
        EV_DoDoor(line,close30ThenOpen);
        break;

      case 77:
        // Fast Ceiling Crush & Raise
        EV_DoCeiling(line,fastCrushAndRaise);
        break;

      case 79:
        // Lights Very Dark
        EV_LightTurnOn(line,35);
        break;

      case 80:
        // Light Turn On - brightest near
        EV_LightTurnOn(line,0);
        break;

      case 81:
        // Light Turn On 255
        EV_LightTurnOn(line,255);
        break;

      case 82:
        // Lower Floor To Lowest
        EV_DoFloor( line, lowerFloorToLowest );
        break;

      case 83:
        // Lower Floor
        EV_DoFloor(line,lowerFloor);
        break;

      case 84:
        // LowerAndChange
        EV_DoFloor(line,lowerAndChange);
        break;

      case 86:
        // Open Door
        EV_DoDoor(line,open);
        break;

      case 87:
        // Perpetual Platform Raise
        EV_DoPlat(line,perpetualRaise,0);
        break;

      case 88:
        // PlatDownWaitUp
        EV_DoPlat(line,downWaitUpStay,0);
        break;

      case 89:
        // Platform Stop
        EV_StopPlat(line);
        break;

      case 90:
        // Raise Door
        EV_DoDoor(line,normal);
        break;

      case 91:
        // Raise Floor
        EV_DoFloor(line,raiseFloor);
        break;

      case 92:
        // Raise Floor 24
        EV_DoFloor(line,raiseFloor24);
        break;

      case 93:
        // Raise Floor 24 And Change
        EV_DoFloor(line,raiseFloor24AndChange);
        break;

      case 94:
        // Raise Floor Crush
        EV_DoFloor(line,raiseFloorCrush);
        break;

      case 95:
        // Raise floor to nearest height
        // and change texture.
        EV_DoPlat(line,raiseToNearestAndChange,0);
        break;

      case 96:
        // Raise floor to shortest texture height
        // on either side of lines.
        EV_DoFloor(line,raiseToTexture);
        break;

      case 97:
        // TELEPORT!
        EV_Teleport( line, side, thing );
        break;

      case 98:
        // Lower Floor (TURBO)
        EV_DoFloor(line,turboLower);
        break;

      case 105:
        // Blazing Door Raise (faster than TURBO!)
        EV_DoDoor (line,blazeRaise);
        break;

      case 106:
        // Blazing Door Open (faster than TURBO!)
        EV_DoDoor (line,blazeOpen);
        break;

      case 107:
        // Blazing Door Close (faster than TURBO!)
        EV_DoDoor (line,blazeClose);
        break;

      case 120:
        // Blazing PlatDownWaitUpStay.
        EV_DoPlat(line,blazeDWUS,0);
        break;

      case 126:
        // TELEPORT MonsterONLY.
        if (!thing->player)
            EV_Teleport( line, side, thing );
        break;

      case 128:
        // Raise To Nearest Floor
        EV_DoFloor(line,raiseFloorToNearest);
        break;

      case 129:
        // Raise Floor Turbo
        EV_DoFloor(line,raiseFloorTurbo);
        break;
    }
}



//
// P_ShootSpecialLine - IMPACT SPECIALS
// Called when a thing shoots a special line.
//
void
P_ShootSpecialLine
( mobj_t*       thing,
  line_t*       line )
{
    int         ok;

    //  Impacts that other things can activate.
    if (!thing->player)
    {
        ok = 0;
        switch(line->special)
        {
          case 46:
            // OPEN DOOR IMPACT
            ok = 1;
            break;
        }
        if (!ok)
            return;
    }

    switch(line->special)
    {
      case 24:
        // RAISE FLOOR
        EV_DoFloor(line,raiseFloor);
        P_ChangeSwitchTexture(line,0);
        break;

      case 46:
        // OPEN DOOR
        EV_DoDoor(line,open);
        P_ChangeSwitchTexture(line,1);
        break;

      case 47:
        // RAISE FLOOR NEAR AND CHANGE
        EV_DoPlat(line,raiseToNearestAndChange,0);
        P_ChangeSwitchTexture(line,0);
        break;
    }
}



//
// P_PlayerInSpecialSector
// Called every tic frame
//  that the player origin is in a special sector
//
void P_PlayerInSpecialSector (player_t* player)
{
    sector_t*   sector;

    sector = player->mo->subsector->sector;

    // Falling, not all the way down yet?
    if (player->mo->z != sector->floorheight)
        return;

    // Has hitten ground.
    switch (sector->special)
    {
      case 5:
        // HELLSLIME DAMAGE
        if (!player->powers[pw_ironfeet])
            if (!(leveltime&0x1f))
                P_DamageMobj (player->mo, NULL, NULL, 10);
        break;

      case 7:
        // NUKAGE DAMAGE
        if (!player->powers[pw_ironfeet])
            if (!(leveltime&0x1f))
                P_DamageMobj (player->mo, NULL, NULL, 5);
        break;

      case 16:
        // SUPER HELLSLIME DAMAGE
      case 4:
        // STROBE HURT
        if (!player->powers[pw_ironfeet]
            || (P_Random()<5) )
        {
            if (!(leveltime&0x1f))
                P_DamageMobj (player->mo, NULL, NULL, 20);
        }
        break;

      case 9:
        // SECRET SECTOR
        player->secretcount++;
        sector->special = 0;
        break;

      case 11:
        // EXIT SUPER DAMAGE! (for E1M8 finale)
        player->cheats &= ~CF_GODMODE;

        if (!(leveltime&0x1f))
            P_DamageMobj (player->mo, NULL, NULL, 20);

        if (player->health <= 10)
            G_ExitLevel();
        break;

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




//
// P_UpdateSpecials
// Animate planes, scroll walls, etc.
//
boolean         levelTimer;
int             levelTimeCount;

void P_UpdateSpecials (void)
{
    anim_t*     anim;
    int         pic;
    int         i;
    line_t*     line;


    //  LEVEL TIMER
    if (levelTimer == true)
    {
        levelTimeCount--;
        if (!levelTimeCount)
            G_ExitLevel();
    }

    //  ANIMATE FLATS AND TEXTURES GLOBALLY
    for (anim = anims ; anim < lastanim ; anim++)
    {
        for (i=anim->basepic ; i<anim->basepic+anim->numpics ; i++)
        {
            pic = anim->basepic + ( (leveltime/anim->speed + i)%anim->numpics );
            if (anim->istexture)
                texturetranslation[i] = pic;
            else
                flattranslation[i] = pic;
        }
    }


    //  ANIMATE LINE SPECIALS
    for (i = 0; i < numlinespecials; i++)
    {
        line = linespeciallist[i];
        switch(line->special)
        {
          case 48:
            // EFFECT FIRSTCOL SCROLL +
            sides[line->sidenum[0]].textureoffset += FRACUNIT;
            break;
        }
    }


    //  DO BUTTONS
    for (i = 0; i < MAXBUTTONS; i++)
        if (buttonlist[i].btimer)
        {
            buttonlist[i].btimer--;
            if (!buttonlist[i].btimer)
            {
                switch(buttonlist[i].where)
                {
                  case top:
                    sides[buttonlist[i].line->sidenum[0]].toptexture =
                        buttonlist[i].btexture;
                    break;

                  case middle:
                    sides[buttonlist[i].line->sidenum[0]].midtexture =
                        buttonlist[i].btexture;
                    break;

                  case bottom:
                    sides[buttonlist[i].line->sidenum[0]].bottomtexture =
                        buttonlist[i].btexture;
                    break;
                }
                S_StartSound((mobj_t *)&buttonlist[i].soundorg,sfx_swtchn);
                memset(&buttonlist[i],0,sizeof(button_t));
            }
        }

}



//
// Special Stuff that can not be categorized
//
int EV_DoDonut(line_t*  line)
{
    sector_t*           s1;
    sector_t*           s2;
    sector_t*           s3;
    int                 secnum;
    int                 rtn;
    int                 i;
    floormove_t*        floor;

    secnum = -1;
    rtn = 0;
    while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
    {
        s1 = &sectors[secnum];

        // ALREADY MOVING?  IF SO, KEEP GOING...
        if (s1->specialdata)
            continue;

        rtn = 1;
        s2 = getNextSector(s1->lines[0],s1);
        for (i = 0;i < s2->linecount;i++)
        {
            if ((!s2->lines[i]->flags & ML_TWOSIDED) ||
                (s2->lines[i]->backsector == s1))
                continue;
            s3 = s2->lines[i]->backsector;

            //  Spawn rising slime
            floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0);
            P_AddThinker (&floor->thinker);
            s2->specialdata = floor;
            floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor;
            floor->type = donutRaise;
            floor->crush = false;
            floor->direction = 1;
            floor->sector = s2;
            floor->speed = FLOORSPEED / 2;
            floor->texture = s3->floorpic;
            floor->newspecial = 0;
            floor->floordestheight = s3->floorheight;

            //  Spawn lowering donut-hole
            floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0);
            P_AddThinker (&floor->thinker);
            s1->specialdata = floor;
            floor->thinker.function.acp1 = (actionf_p1) T_MoveFloor;
            floor->type = lowerFloor;
            floor->crush = false;
            floor->direction = -1;
            floor->sector = s1;
            floor->speed = FLOORSPEED / 2;
            floor->floordestheight = s3->floorheight;
            break;
        }
    }
    return rtn;
}



//
// SPECIAL SPAWNING
//

//
// P_SpawnSpecials
// After the map has been loaded, scan for specials
//  that spawn thinkers
//
short           numlinespecials;
line_t*         linespeciallist[MAXLINEANIMS];


// Parses command line parameters.
void P_SpawnSpecials (void)
{
    sector_t*   sector;
    int         i;
    int         episode;

    episode = 1;
    if (W_CheckNumForName("texture2") >= 0)
        episode = 2;


    // See if -TIMER needs to be used.
    levelTimer = false;

    i = M_CheckParm("-avg");
    if (i && deathmatch)
    {
        levelTimer = true;
        levelTimeCount = 20 * 60 * 35;
    }

    i = M_CheckParm("-timer");
    if (i && deathmatch)
    {
        int     time;
        time = atoi(myargv[i+1]) * 60 * 35;
        levelTimer = true;
        levelTimeCount = time;
    }

    //  Init special SECTORs.
    sector = sectors;
    for (i=0 ; i<numsectors ; i++, sector++)
    {
        if (!sector->special)
            continue;

        switch (sector->special)
        {
          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;

          case 17:
            P_SpawnFireFlicker(sector);
            break;
        }
    }


    //  Init line EFFECTs
    numlinespecials = 0;
    for (i = 0;i < numlines; i++)
    {
        switch(lines[i].special)
        {
          case 48:
            // EFFECT FIRSTCOL SCROLL+
            linespeciallist[numlinespecials] = &lines[i];
            numlinespecials++;
            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));

    // UNUSED: no horizonal sliders.
    //  P_InitSlidingDoorFrames();
}
