/* Emacs style mode select   -*- C++ -*- */
/*----------------------------------------------------------------------------- */
/* */
/* $Id:$ */
/* */
/* Copyright (C) 1993-1996 by id Software, Inc. */
/* */
/* 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. */
/* */
/* $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(); */
}
