/* Emacs style mode select   -*- C++ -*-
 *-----------------------------------------------------------------------------
 *
 *
 *  PrBoom a Doom port merged with LxDoom and LSDLDoom
 *  based on BOOM, a modified and improved DOOM engine
 *  Copyright (C) 1999 by
 *  id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
 *  Copyright (C) 1999-2000 by
 *  Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze
 *
 *  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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 *  02111-1307, USA.
 *
 * DESCRIPTION:
 *  Generalized linedef type handlers
 *  Floors, Ceilings, Doors, Locked Doors, Lifts, Stairs, Crushers
 *
 *-----------------------------------------------------------------------------*/

#include "doomstat.h" //jff 6/19/98 for demo_compatibility
#include "r_main.h"
#include "p_spec.h"
#include "p_tick.h"
#include "m_random.h"
#include "s_sound.h"
#include "sounds.h"

//////////////////////////////////////////////////////////
//
// Generalized Linedef Type handlers
//
//////////////////////////////////////////////////////////

//
// EV_DoGenFloor()
//
// Handle generalized floor types
//
// Passed the line activating the generalized floor function
// Returns true if a thinker is created
//
// jff 02/04/98 Added this routine (and file) to handle generalized
// floor movers using bit fields in the line special type.
//
int EV_DoGenFloor
( line_t*       line )
{
   int                   secnum;
   int                   rtn;
   boolean               manual;
   sector_t*             sec;
   floormove_t*          floor;
   unsigned              value = (unsigned)line->special - GenFloorBase;

   // parse the bit fields in the line's special type

   int Crsh = (value & FloorCrush) >> FloorCrushShift;
   int ChgT = (value & FloorChange) >> FloorChangeShift;
   int Targ = (value & FloorTarget) >> FloorTargetShift;
   int Dirn = (value & FloorDirection) >> FloorDirectionShift;
   int ChgM = (value & FloorModel) >> FloorModelShift;
   int Sped = (value & FloorSpeed) >> FloorSpeedShift;
   int Trig = (value & TriggerType) >> TriggerTypeShift;

   rtn = 0;

   // check if a manual trigger, if so do just the sector on the backside
   manual = false;
   if (Trig==PushOnce || Trig==PushMany)
   {
      if (!(sec = line->backsector))
         return rtn;
      secnum = sec-sectors;
      manual = true;
      goto manual_floor;
   }

   secnum = -1;
   // if not manual do all sectors tagged the same as the line
   while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
   {
      sec = &sectors[secnum];

manual_floor:
      // Do not start another function if floor already moving
      if (P_SectorActive(floor_special,sec))
      {
         if (!manual)
            continue;
         else
            return rtn;
      }

      // new floor thinker
      rtn = 1;
      floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0);
      P_AddThinker (&floor->thinker);
      sec->floordata = floor;
      floor->thinker.function = T_MoveFloor;
      floor->crush = Crsh;
      floor->direction = Dirn? 1 : -1;
      floor->sector = sec;
      floor->texture = sec->floorpic;
      floor->newspecial = sec->special;
      //jff 3/14/98 transfer old special field too
      floor->oldspecial = sec->oldspecial;
      floor->type = genFloor;

      // set the speed of motion
      switch (Sped)
      {
      case SpeedSlow:
         floor->speed = FLOORSPEED;
         break;
      case SpeedNormal:
         floor->speed = FLOORSPEED*2;
         break;
      case SpeedFast:
         floor->speed = FLOORSPEED*4;
         break;
      case SpeedTurbo:
         floor->speed = FLOORSPEED*8;
         break;
      default:
         break;
      }

      // set the destination height
      switch(Targ)
      {
      case FtoHnF:
         floor->floordestheight = P_FindHighestFloorSurrounding(sec);
         break;
      case FtoLnF:
         floor->floordestheight = P_FindLowestFloorSurrounding(sec);
         break;
      case FtoNnF:
         floor->floordestheight = Dirn?
                                  P_FindNextHighestFloor(sec,sec->floorheight) :
                                  P_FindNextLowestFloor(sec,sec->floorheight);
         break;
      case FtoLnC:
         floor->floordestheight = P_FindLowestCeilingSurrounding(sec);
         break;
      case FtoC:
         floor->floordestheight = sec->ceilingheight;
         break;
      case FbyST:
         floor->floordestheight = (floor->sector->floorheight>>FRACBITS) +
                                  floor->direction * (P_FindShortestTextureAround(secnum)>>FRACBITS);
         if (floor->floordestheight>32000)  //jff 3/13/98 prevent overflow
            floor->floordestheight=32000;    // wraparound in floor height
         if (floor->floordestheight<-32000)
            floor->floordestheight=-32000;
         floor->floordestheight<<=FRACBITS;
         break;
      case Fby24:
         floor->floordestheight = floor->sector->floorheight +
                                  floor->direction * 24*FRACUNIT;
         break;
      case Fby32:
         floor->floordestheight = floor->sector->floorheight +
                                  floor->direction * 32*FRACUNIT;
         break;
      default:
         break;
      }

      // set texture/type change properties
      if (ChgT)   // if a texture change is indicated
      {
         if (ChgM) // if a numeric model change
         {
            sector_t *sec;

            //jff 5/23/98 find model with ceiling at target height if target
            //is a ceiling type
            sec = (Targ==FtoLnC || Targ==FtoC)?
                  P_FindModelCeilingSector(floor->floordestheight,secnum) :
                  P_FindModelFloorSector(floor->floordestheight,secnum);
            if (sec)
            {
               floor->texture = sec->floorpic;
               switch(ChgT)
               {
               case FChgZero:  // zero type
                  floor->newspecial = 0;
                  //jff 3/14/98 change old field too
                  floor->oldspecial = 0;
                  floor->type = genFloorChg0;
                  break;
               case FChgTyp:   // copy type
                  floor->newspecial = sec->special;
                  //jff 3/14/98 change old field too
                  floor->oldspecial = sec->oldspecial;
                  floor->type = genFloorChgT;
                  break;
               case FChgTxt:   // leave type be
                  floor->type = genFloorChg;
                  break;
               default:
                  break;
               }
            }
         }
         else     // else if a trigger model change
         {
            floor->texture = line->frontsector->floorpic;
            switch (ChgT)
            {
            case FChgZero:    // zero type
               floor->newspecial = 0;
               //jff 3/14/98 change old field too
               floor->oldspecial = 0;
               floor->type = genFloorChg0;
               break;
            case FChgTyp:     // copy type
               floor->newspecial = line->frontsector->special;
               //jff 3/14/98 change old field too
               floor->oldspecial = line->frontsector->oldspecial;
               floor->type = genFloorChgT;
               break;
            case FChgTxt:     // leave type be
               floor->type = genFloorChg;
            default:
               break;
            }
         }
      }
      if (manual) return rtn;
   }
   return rtn;
}


//
// EV_DoGenCeiling()
//
// Handle generalized ceiling types
//
// Passed the linedef activating the ceiling function
// Returns true if a thinker created
//
// jff 02/04/98 Added this routine (and file) to handle generalized
// floor movers using bit fields in the line special type.
//
int EV_DoGenCeiling
( line_t*       line )
{
   int                   secnum;
   int                   rtn;
   boolean               manual;
   fixed_t               targheight;
   sector_t*             sec;
   ceiling_t*            ceiling;
   unsigned              value = (unsigned)line->special - GenCeilingBase;

   // parse the bit fields in the line's special type

   int Crsh = (value & CeilingCrush) >> CeilingCrushShift;
   int ChgT = (value & CeilingChange) >> CeilingChangeShift;
   int Targ = (value & CeilingTarget) >> CeilingTargetShift;
   int Dirn = (value & CeilingDirection) >> CeilingDirectionShift;
   int ChgM = (value & CeilingModel) >> CeilingModelShift;
   int Sped = (value & CeilingSpeed) >> CeilingSpeedShift;
   int Trig = (value & TriggerType) >> TriggerTypeShift;

   rtn = 0;

   // check if a manual trigger, if so do just the sector on the backside
   manual = false;
   if (Trig==PushOnce || Trig==PushMany)
   {
      if (!(sec = line->backsector))
         return rtn;
      secnum = sec-sectors;
      manual = true;
      goto manual_ceiling;
   }

   secnum = -1;
   // if not manual do all sectors tagged the same as the line
   while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
   {
      sec = &sectors[secnum];

manual_ceiling:
      // Do not start another function if ceiling already moving
      if (P_SectorActive(ceiling_special,sec)) //jff 2/22/98
      {
         if (!manual)
            continue;
         else
            return rtn;
      }

      // new ceiling thinker
      rtn = 1;
      ceiling = Z_Malloc (sizeof(*ceiling), PU_LEVSPEC, 0);
      P_AddThinker (&ceiling->thinker);
      sec->ceilingdata = ceiling; //jff 2/22/98
      ceiling->thinker.function = T_MoveCeiling;
      ceiling->crush = Crsh;
      ceiling->direction = Dirn? 1 : -1;
      ceiling->sector = sec;
      ceiling->texture = sec->ceilingpic;
      ceiling->newspecial = sec->special;
      //jff 3/14/98 change old field too
      ceiling->oldspecial = sec->oldspecial;
      ceiling->tag = sec->tag;
      ceiling->type = genCeiling;

      // set speed of motion
      switch (Sped)
      {
      case SpeedSlow:
         ceiling->speed = CEILSPEED;
         break;
      case SpeedNormal:
         ceiling->speed = CEILSPEED*2;
         break;
      case SpeedFast:
         ceiling->speed = CEILSPEED*4;
         break;
      case SpeedTurbo:
         ceiling->speed = CEILSPEED*8;
         break;
      default:
         break;
      }

      // set destination target height
      targheight = sec->ceilingheight;
      switch(Targ)
      {
      case CtoHnC:
         targheight = P_FindHighestCeilingSurrounding(sec);
         break;
      case CtoLnC:
         targheight = P_FindLowestCeilingSurrounding(sec);
         break;
      case CtoNnC:
         targheight = Dirn?
                      P_FindNextHighestCeiling(sec,sec->ceilingheight) :
                      P_FindNextLowestCeiling(sec,sec->ceilingheight);
         break;
      case CtoHnF:
         targheight = P_FindHighestFloorSurrounding(sec);
         break;
      case CtoF:
         targheight = sec->floorheight;
         break;
      case CbyST:
         targheight = (ceiling->sector->ceilingheight>>FRACBITS) +
                      ceiling->direction * (P_FindShortestUpperAround(secnum)>>FRACBITS);
         if (targheight>32000)  //jff 3/13/98 prevent overflow
            targheight=32000;    // wraparound in ceiling height
         if (targheight<-32000)
            targheight=-32000;
         targheight<<=FRACBITS;
         break;
      case Cby24:
         targheight = ceiling->sector->ceilingheight +
                      ceiling->direction * 24*FRACUNIT;
         break;
      case Cby32:
         targheight = ceiling->sector->ceilingheight +
                      ceiling->direction * 32*FRACUNIT;
         break;
      default:
         break;
      }
      if (Dirn) ceiling->topheight = targheight;
      else ceiling->bottomheight = targheight;

      // set texture/type change properties
      if (ChgT)     // if a texture change is indicated
      {
         if (ChgM)   // if a numeric model change
         {
            sector_t *sec;

            //jff 5/23/98 find model with floor at target height if target
            //is a floor type
            sec = (Targ==CtoHnF || Targ==CtoF)?
                  P_FindModelFloorSector(targheight,secnum) :
                  P_FindModelCeilingSector(targheight,secnum);
            if (sec)
            {
               ceiling->texture = sec->ceilingpic;
               switch (ChgT)
               {
               case CChgZero:  // type is zeroed
                  ceiling->newspecial = 0;
                  //jff 3/14/98 change old field too
                  ceiling->oldspecial = 0;
                  ceiling->type = genCeilingChg0;
                  break;
               case CChgTyp:   // type is copied
                  ceiling->newspecial = sec->special;
                  //jff 3/14/98 change old field too
                  ceiling->oldspecial = sec->oldspecial;
                  ceiling->type = genCeilingChgT;
                  break;
               case CChgTxt:   // type is left alone
                  ceiling->type = genCeilingChg;
                  break;
               default:
                  break;
               }
            }
         }
         else        // else if a trigger model change
         {
            ceiling->texture = line->frontsector->ceilingpic;
            switch (ChgT)
            {
            case CChgZero:    // type is zeroed
               ceiling->newspecial = 0;
               //jff 3/14/98 change old field too
               ceiling->oldspecial = 0;
               ceiling->type = genCeilingChg0;
               break;
            case CChgTyp:     // type is copied
               ceiling->newspecial = line->frontsector->special;
               //jff 3/14/98 change old field too
               ceiling->oldspecial = line->frontsector->oldspecial;
               ceiling->type = genCeilingChgT;
               break;
            case CChgTxt:     // type is left alone
               ceiling->type = genCeilingChg;
               break;
            default:
               break;
            }
         }
      }
      P_AddActiveCeiling(ceiling);  // add this ceiling to the active list
      if (manual) return rtn;
   }
   return rtn;
}

//
// EV_DoGenLift()
//
// Handle generalized lift types
//
// Passed the linedef activating the lift
// Returns true if a thinker is created
//
int EV_DoGenLift
( line_t*       line )
{
   plat_t*         plat;
   int             secnum;
   int             rtn;
   boolean         manual;
   sector_t*       sec;
   unsigned        value = (unsigned)line->special - GenLiftBase;

   // parse the bit fields in the line's special type

   int Targ = (value & LiftTarget) >> LiftTargetShift;
   int Dely = (value & LiftDelay) >> LiftDelayShift;
   int Sped = (value & LiftSpeed) >> LiftSpeedShift;
   int Trig = (value & TriggerType) >> TriggerTypeShift;

   secnum = -1;
   rtn = 0;

   // Activate all <type> plats that are in_stasis

   if (Targ==LnF2HnF)
      P_ActivateInStasis(line->tag);

   // check if a manual trigger, if so do just the sector on the backside
   manual = false;
   if (Trig==PushOnce || Trig==PushMany)
   {
      if (!(sec = line->backsector))
         return rtn;
      secnum = sec-sectors;
      manual = true;
      goto manual_lift;
   }

   // if not manual do all sectors tagged the same as the line
   while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
   {
      sec = &sectors[secnum];

manual_lift:
      // Do not start another function if floor already moving
      if (P_SectorActive(floor_special,sec))
      {
         if (!manual)
            continue;
         else
            return rtn;
      }

      // Setup the plat thinker
      rtn = 1;
      plat = Z_Malloc( sizeof(*plat), PU_LEVSPEC, 0);
      P_AddThinker(&plat->thinker);

      plat->sector = sec;
      plat->sector->floordata = plat;
      plat->thinker.function = T_PlatRaise;
      plat->crush = false;
      plat->tag = line->tag;

      plat->type = genLift;
      plat->high = sec->floorheight;
      plat->status = down;

      // setup the target destination height
      switch(Targ)
      {
      case F2LnF:
         plat->low = P_FindLowestFloorSurrounding(sec);
         if (plat->low > sec->floorheight)
            plat->low = sec->floorheight;
         break;
      case F2NnF:
         plat->low = P_FindNextLowestFloor(sec,sec->floorheight);
         break;
      case F2LnC:
         plat->low = P_FindLowestCeilingSurrounding(sec);
         if (plat->low > sec->floorheight)
            plat->low = sec->floorheight;
         break;
      case LnF2HnF:
         plat->type = genPerpetual;
         plat->low = P_FindLowestFloorSurrounding(sec);
         if (plat->low > sec->floorheight)
            plat->low = sec->floorheight;
         plat->high = P_FindHighestFloorSurrounding(sec);
         if (plat->high < sec->floorheight)
            plat->high = sec->floorheight;
         plat->status = P_Random(pr_genlift)&1;
         break;
      default:
         break;
      }

      // setup the speed of motion
      switch(Sped)
      {
      case SpeedSlow:
         plat->speed = PLATSPEED * 2;
         break;
      case SpeedNormal:
         plat->speed = PLATSPEED * 4;
         break;
      case SpeedFast:
         plat->speed = PLATSPEED * 8;
         break;
      case SpeedTurbo:
         plat->speed = PLATSPEED * 16;
         break;
      default:
         break;
      }

      // setup the delay time before the floor returns
      switch(Dely)
      {
      case 0:
         plat->wait = 1*35;
         break;
      case 1:
         plat->wait = PLATWAIT*35;
         break;
      case 2:
         plat->wait = 5*35;
         break;
      case 3:
         plat->wait = 10*35;
         break;
      }

      S_StartSound((mobj_t *)&sec->soundorg,sfx_pstart);
      P_AddActivePlat(plat); // add this plat to the list of active plats

      if (manual)
         return rtn;
   }
   return rtn;
}

//
// EV_DoGenStairs()
//
// Handle generalized stair building
//
// Passed the linedef activating the stairs
// Returns true if a thinker is created
//
int EV_DoGenStairs
( line_t*       line )
{
   int                   secnum;
   int                   osecnum; //jff 3/4/98 preserve loop index
   int                   height;
   int                   i;
   int                   newsecnum;
   int                   texture;
   int                   ok;
   int                   rtn;
   boolean               manual;

   sector_t*             sec;
   sector_t*             tsec;

   floormove_t*  floor;

   fixed_t               stairsize;
   fixed_t               speed;

   unsigned              value = (unsigned)line->special - GenStairsBase;

   // parse the bit fields in the line's special type

   int Igno = (value & StairIgnore) >> StairIgnoreShift;
   int Dirn = (value & StairDirection) >> StairDirectionShift;
   int Step = (value & StairStep) >> StairStepShift;
   int Sped = (value & StairSpeed) >> StairSpeedShift;
   int Trig = (value & TriggerType) >> TriggerTypeShift;

   rtn = 0;

   // check if a manual trigger, if so do just the sector on the backside
   manual = false;
   if (Trig==PushOnce || Trig==PushMany)
   {
      if (!(sec = line->backsector))
         return rtn;
      secnum = sec-sectors;
      manual = true;
      goto manual_stair;
   }

   secnum = -1;
   // if not manual do all sectors tagged the same as the line
   while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
   {
      sec = &sectors[secnum];

manual_stair:
      //Do not start another function if floor already moving
      //jff 2/26/98 add special lockout condition to wait for entire
      //staircase to build before retriggering
      if (P_SectorActive(floor_special,sec) || sec->stairlock)
      {
         if (!manual)
            continue;
         else
            return rtn;
      }

      // new floor thinker
      rtn = 1;
      floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0);
      P_AddThinker (&floor->thinker);
      sec->floordata = floor;
      floor->thinker.function = T_MoveFloor;
      floor->direction = Dirn? 1 : -1;
      floor->sector = sec;

      // setup speed of stair building
      switch(Sped)
      {
      default:
      case SpeedSlow:
         floor->speed = FLOORSPEED/4;
         break;
      case SpeedNormal:
         floor->speed = FLOORSPEED/2;
         break;
      case SpeedFast:
         floor->speed = FLOORSPEED*2;
         break;
      case SpeedTurbo:
         floor->speed = FLOORSPEED*4;
         break;
      }

      // setup stepsize for stairs
      switch(Step)
      {
      default:
      case 0:
         stairsize = 4*FRACUNIT;
         break;
      case 1:
         stairsize = 8*FRACUNIT;
         break;
      case 2:
         stairsize = 16*FRACUNIT;
         break;
      case 3:
         stairsize = 24*FRACUNIT;
         break;
      }

      speed = floor->speed;
      height = sec->floorheight + floor->direction * stairsize;
      floor->floordestheight = height;
      texture = sec->floorpic;
      floor->crush = false;
      floor->type = genBuildStair; // jff 3/31/98 do not leave uninited

      sec->stairlock = -2;         // jff 2/26/98 set up lock on current sector
      sec->nextsec = -1;
      sec->prevsec = -1;

      osecnum = secnum;            //jff 3/4/98 preserve loop index
      // Find next sector to raise
      // 1.     Find 2-sided line with same sector side[0]
      // 2.     Other side is the next sector to raise
      do
      {
         ok = 0;
         for (i = 0;i < sec->linecount;i++)
         {
            if ( !((sec->lines[i])->backsector) )
               continue;

            tsec = (sec->lines[i])->frontsector;
            newsecnum = tsec-sectors;

            if (secnum != newsecnum)
               continue;

            tsec = (sec->lines[i])->backsector;
            newsecnum = tsec - sectors;

            if (!Igno && tsec->floorpic != texture)
               continue;

            /* jff 6/19/98 prevent double stepsize */
            if (compatibility_level < boom_202_compatibility)
               height += floor->direction * stairsize;

            //jff 2/26/98 special lockout condition for retriggering
            if (P_SectorActive(floor_special,tsec) || tsec->stairlock)
               continue;

            /* jff 6/19/98 increase height AFTER continue */
            if (compatibility_level >= boom_202_compatibility)
               height += floor->direction * stairsize;

            // jff 2/26/98
            // link the stair chain in both directions
            // lock the stair sector until building complete
            sec->nextsec = newsecnum; // link step to next
            tsec->prevsec = secnum;   // link next back
            tsec->nextsec = -1;       // set next forward link as end
            tsec->stairlock = -2;     // lock the step

            sec = tsec;
            secnum = newsecnum;
            floor = Z_Malloc (sizeof(*floor), PU_LEVSPEC, 0);

            P_AddThinker (&floor->thinker);

            sec->floordata = floor;
            floor->thinker.function = T_MoveFloor;
            floor->direction = Dirn? 1 : -1;
            floor->sector = sec;
            floor->speed = speed;
            floor->floordestheight = height;
            floor->crush = false;
            floor->type = genBuildStair; // jff 3/31/98 do not leave uninited

            ok = 1;
            break;
         }
      } while(ok);
      if (manual)
         return rtn;
      secnum = osecnum; //jff 3/4/98 restore old loop index
   }
   // retriggerable generalized stairs build up or down alternately
   if (rtn)
      line->special ^= StairDirection; // alternate dir on succ activations
   return rtn;
}

//
// EV_DoGenCrusher()
//
// Handle generalized crusher types
//
// Passed the linedef activating the crusher
// Returns true if a thinker created
//
int EV_DoGenCrusher
( line_t*       line )
{
   int                   secnum;
   int                   rtn;
   boolean               manual;
   sector_t*             sec;
   ceiling_t*            ceiling;
   unsigned              value = (unsigned)line->special - GenCrusherBase;

   // parse the bit fields in the line's special type

   int Slnt = (value & CrusherSilent) >> CrusherSilentShift;
   int Sped = (value & CrusherSpeed) >> CrusherSpeedShift;
   int Trig = (value & TriggerType) >> TriggerTypeShift;

   //jff 2/22/98  Reactivate in-stasis ceilings...for certain types.
   //jff 4/5/98 return if activated
   rtn = P_ActivateInStasisCeiling(line);

   // check if a manual trigger, if so do just the sector on the backside
   manual = false;
   if (Trig==PushOnce || Trig==PushMany)
   {
      if (!(sec = line->backsector))
         return rtn;
      secnum = sec-sectors;
      manual = true;
      goto manual_crusher;
   }

   secnum = -1;
   // if not manual do all sectors tagged the same as the line
   while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
   {
      sec = &sectors[secnum];

manual_crusher:
      // Do not start another function if ceiling already moving
      if (P_SectorActive(ceiling_special,sec)) //jff 2/22/98
      {
         if (!manual)
            continue;
         else
            return rtn;
      }

      // new ceiling thinker
      rtn = 1;
      ceiling = Z_Malloc (sizeof(*ceiling), PU_LEVSPEC, 0);
      P_AddThinker (&ceiling->thinker);
      sec->ceilingdata = ceiling; //jff 2/22/98
      ceiling->thinker.function = T_MoveCeiling;
      ceiling->crush = true;
      ceiling->direction = -1;
      ceiling->sector = sec;
      ceiling->texture = sec->ceilingpic;
      ceiling->newspecial = sec->special;
      ceiling->tag = sec->tag;
      ceiling->type = Slnt? genSilentCrusher : genCrusher;
      ceiling->topheight = sec->ceilingheight;
      ceiling->bottomheight = sec->floorheight + (8*FRACUNIT);

      // setup ceiling motion speed
      switch (Sped)
      {
      case SpeedSlow:
         ceiling->speed = CEILSPEED;
         break;
      case SpeedNormal:
         ceiling->speed = CEILSPEED*2;
         break;
      case SpeedFast:
         ceiling->speed = CEILSPEED*4;
         break;
      case SpeedTurbo:
         ceiling->speed = CEILSPEED*8;
         break;
      default:
         break;
      }
      ceiling->oldspeed=ceiling->speed;

      P_AddActiveCeiling(ceiling);  // add to list of active ceilings
      if (manual) return rtn;
   }
   return rtn;
}

//
// EV_DoGenLockedDoor()
//
// Handle generalized locked door types
//
// Passed the linedef activating the generalized locked door
// Returns true if a thinker created
//
int EV_DoGenLockedDoor
( line_t* line )
{
   int   secnum,rtn;
   sector_t* sec;
   vldoor_t* door;
   boolean manual;
   unsigned  value = (unsigned)line->special - GenLockedBase;

   // parse the bit fields in the line's special type

   int Kind = (value & LockedKind) >> LockedKindShift;
   int Sped = (value & LockedSpeed) >> LockedSpeedShift;
   int Trig = (value & TriggerType) >> TriggerTypeShift;

   rtn = 0;

   // check if a manual trigger, if so do just the sector on the backside
   manual = false;
   if (Trig==PushOnce || Trig==PushMany)
   {
      if (!(sec = line->backsector))
         return rtn;
      secnum = sec-sectors;
      manual = true;
      goto manual_locked;
   }

   secnum = -1;
   rtn = 0;

   // if not manual do all sectors tagged the same as the line
   while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
   {
      sec = &sectors[secnum];
manual_locked:
      // Do not start another function if ceiling already moving
      if (P_SectorActive(ceiling_special,sec)) //jff 2/22/98
      {
         if (!manual)
            continue;
         else
            return rtn;
      }

      // new door thinker
      rtn = 1;
      door = Z_Malloc (sizeof(*door), PU_LEVSPEC, 0);
      P_AddThinker (&door->thinker);
      sec->ceilingdata = door; //jff 2/22/98

      door->thinker.function = T_VerticalDoor;
      door->sector = sec;
      door->topwait = VDOORWAIT;
      door->line = line;
      door->topheight = P_FindLowestCeilingSurrounding(sec);
      door->topheight -= 4*FRACUNIT;
      door->direction = 1;

      /* killough 10/98: implement gradual lighting */
      door->lighttag = !comp[comp_doorlight] &&
                       (line->special&6) == 6 &&
                       line->special > GenLockedBase ? line->tag : 0;

      // setup speed of door motion
      switch(Sped)
      {
      default:
      case SpeedSlow:
         door->type = Kind? genOpen : genRaise;
         door->speed = VDOORSPEED;
         break;
      case SpeedNormal:
         door->type = Kind? genOpen : genRaise;
         door->speed = VDOORSPEED*2;
         break;
      case SpeedFast:
         door->type = Kind? genBlazeOpen : genBlazeRaise;
         door->speed = VDOORSPEED*4;
         break;
      case SpeedTurbo:
         door->type = Kind? genBlazeOpen : genBlazeRaise;
         door->speed = VDOORSPEED*8;

         break;
      }

      // killough 4/15/98: fix generalized door opening sounds
      // (previously they always had the blazing door close sound)

      S_StartSound((mobj_t *)&door->sector->soundorg,   // killough 4/15/98
                   door->speed >= VDOORSPEED*4 ? sfx_bdopn : sfx_doropn);

      if (manual)
         return rtn;
   }
   return rtn;
}

//
// EV_DoGenDoor()
//
// Handle generalized door types
//
// Passed the linedef activating the generalized door
// Returns true if a thinker created
//
int EV_DoGenDoor
( line_t* line )
{
   int   secnum,rtn;
   sector_t* sec;
   boolean   manual;
   vldoor_t* door;
   unsigned  value = (unsigned)line->special - GenDoorBase;

   // parse the bit fields in the line's special type

   int Dely = (value & DoorDelay) >> DoorDelayShift;
   int Kind = (value & DoorKind) >> DoorKindShift;
   int Sped = (value & DoorSpeed) >> DoorSpeedShift;
   int Trig = (value & TriggerType) >> TriggerTypeShift;

   rtn = 0;

   // check if a manual trigger, if so do just the sector on the backside
   manual = false;
   if (Trig==PushOnce || Trig==PushMany)
   {
      if (!(sec = line->backsector))
         return rtn;
      secnum = sec-sectors;
      manual = true;
      goto manual_door;
   }


   secnum = -1;
   rtn = 0;

   // if not manual do all sectors tagged the same as the line
   while ((secnum = P_FindSectorFromLineTag(line,secnum)) >= 0)
   {
      sec = &sectors[secnum];
manual_door:
      // Do not start another function if ceiling already moving
      if (P_SectorActive(ceiling_special,sec)) //jff 2/22/98
      {
         if (!manual)
            continue;
         else
            return rtn;
      }

      // new door thinker
      rtn = 1;
      door = Z_Malloc (sizeof(*door), PU_LEVSPEC, 0);
      P_AddThinker (&door->thinker);
      sec->ceilingdata = door; //jff 2/22/98

      door->thinker.function = T_VerticalDoor;
      door->sector = sec;
      // setup delay for door remaining open/closed
      switch(Dely)
      {
      default:
      case 0:
         door->topwait = 35;
         break;
      case 1:
         door->topwait = VDOORWAIT;
         break;
      case 2:
         door->topwait = 2*VDOORWAIT;
         break;
      case 3:
         door->topwait = 7*VDOORWAIT;
         break;
      }

      // setup speed of door motion
      switch(Sped)
      {
      default:
      case SpeedSlow:
         door->speed = VDOORSPEED;
         break;
      case SpeedNormal:
         door->speed = VDOORSPEED*2;
         break;
      case SpeedFast:
         door->speed = VDOORSPEED*4;
         break;
      case SpeedTurbo:
         door->speed = VDOORSPEED*8;
         break;
      }
      door->line = line; // jff 1/31/98 remember line that triggered us

      /* killough 10/98: implement gradual lighting */
      door->lighttag = !comp[comp_doorlight] &&
                       (line->special&6) == 6 &&
                       line->special > GenLockedBase ? line->tag : 0;

      // set kind of door, whether it opens then close, opens, closes etc.
      // assign target heights accordingly
      switch(Kind)
      {
      case OdCDoor:
         door->direction = 1;
         door->topheight = P_FindLowestCeilingSurrounding(sec);
         door->topheight -= 4*FRACUNIT;
         if (door->topheight != sec->ceilingheight)
            S_StartSound((mobj_t *)&door->sector->soundorg,Sped>=SpeedFast || comp[comp_sound] ? sfx_bdopn : sfx_doropn);
         door->type = Sped>=SpeedFast? genBlazeRaise : genRaise;
         break;
      case ODoor:
         door->direction = 1;
         door->topheight = P_FindLowestCeilingSurrounding(sec);
         door->topheight -= 4*FRACUNIT;
         if (door->topheight != sec->ceilingheight)
            S_StartSound((mobj_t *)&door->sector->soundorg,Sped>=SpeedFast || comp[comp_sound] ? sfx_bdopn : sfx_doropn);
         door->type = Sped>=SpeedFast? genBlazeOpen : genOpen;
         break;
      case CdODoor:
         door->topheight = sec->ceilingheight;
         door->direction = -1;
         S_StartSound((mobj_t *)&door->sector->soundorg,Sped>=SpeedFast && !comp[comp_sound] ? sfx_bdcls : sfx_dorcls);
         door->type = Sped>=SpeedFast? genBlazeCdO : genCdO;
         break;
      case CDoor:
         door->topheight = P_FindLowestCeilingSurrounding(sec);
         door->topheight -= 4*FRACUNIT;
         door->direction = -1;
         S_StartSound((mobj_t *)&door->sector->soundorg,Sped>=SpeedFast && !comp[comp_sound] ? sfx_bdcls : sfx_dorcls);
         door->type = Sped>=SpeedFast? genBlazeClose : genClose;
         break;
      default:
         break;
      }
      if (manual)
         return rtn;
   }
   return rtn;
}
