shithub: heretic

ref: 7110aba73616e4f84b933a0db1ff96c4c22a9d6f
dir: heretic/p_spec.c

View raw version

// P_Spec.c

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

#include "h2stdinc.h"
#include "doomdef.h"
#include "p_local.h"
#include "soundst.h"

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

#define MAX_AMBIENT_SFX		8	/* Per level */

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

typedef enum
{
	afxcmd_play,		/* (sound) */
	afxcmd_playabsvol,	/* (sound, volume) */
	afxcmd_playrelvol,	/* (sound, volume) */
	afxcmd_delay,		/* (ticks) */
	afxcmd_delayrand,	/* (andbits) */
	afxcmd_end		/* ()		*/
} afxcmd_t;

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

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

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

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

// PUBLIC DATA DEFINITIONS -------------------------------------------------

anim_t anims[MAXANIMS];
anim_t *lastanim;
int *TerrainTypes;
mobj_t LavaInflictor;

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static int *LevelAmbientSfx[MAX_AMBIENT_SFX];
static int *AmbSfxPtr;
static int AmbSfxCount;
static int AmbSfxTics;
static int AmbSfxVolume;

static int AmbSndSeqInit[] =
{ /* Startup */
	afxcmd_end
};
static int AmbSndSeq1[] =
{ /* Scream */
	afxcmd_play, sfx_amb1,
	afxcmd_end
};
static int AmbSndSeq2[] =
{ /* Squish */
	afxcmd_play, sfx_amb2,
	afxcmd_end
};
static int AmbSndSeq3[] =
{ /* Drops */
	afxcmd_play, sfx_amb3,
	afxcmd_delay, 16,
	afxcmd_delayrand, 31,
	afxcmd_play, sfx_amb7,
	afxcmd_delay, 16,
	afxcmd_delayrand, 31,
	afxcmd_play, sfx_amb3,
	afxcmd_delay, 16,
	afxcmd_delayrand, 31,
	afxcmd_play, sfx_amb7,
	afxcmd_delay, 16,
	afxcmd_delayrand, 31,
	afxcmd_play, sfx_amb3,
	afxcmd_delay, 16,
	afxcmd_delayrand, 31,
	afxcmd_play, sfx_amb7,
	afxcmd_delay, 16,
	afxcmd_delayrand, 31,
	afxcmd_end
};
static int AmbSndSeq4[] =
{ /* SlowFootSteps */
	afxcmd_play, sfx_amb4,
	afxcmd_delay, 15,
	afxcmd_playrelvol, sfx_amb11, -3,
	afxcmd_delay, 15,
	afxcmd_playrelvol, sfx_amb4, -3,
	afxcmd_delay, 15,
	afxcmd_playrelvol, sfx_amb11, -3,
	afxcmd_delay, 15,
	afxcmd_playrelvol, sfx_amb4, -3,
	afxcmd_delay, 15,
	afxcmd_playrelvol, sfx_amb11, -3,
	afxcmd_delay, 15,
	afxcmd_playrelvol, sfx_amb4, -3,
	afxcmd_delay, 15,
	afxcmd_playrelvol, sfx_amb11, -3,
	afxcmd_end
};
static int AmbSndSeq5[] =
{ /* Heartbeat */
	afxcmd_play, sfx_amb5,
	afxcmd_delay, 35,
	afxcmd_play, sfx_amb5,
	afxcmd_delay, 35,
	afxcmd_play, sfx_amb5,
	afxcmd_delay, 35,
	afxcmd_play, sfx_amb5,
	afxcmd_end
};
static int AmbSndSeq6[] =
{ /* Bells */
	afxcmd_play, sfx_amb6,
	afxcmd_delay, 17,
	afxcmd_playrelvol, sfx_amb6, -8,
	afxcmd_delay, 17,
	afxcmd_playrelvol, sfx_amb6, -8,
	afxcmd_delay, 17,
	afxcmd_playrelvol, sfx_amb6, -8,
	afxcmd_end
};
static int AmbSndSeq7[] =
{ /* Growl */
	afxcmd_play, sfx_bstsit,
	afxcmd_end
};
static int AmbSndSeq8[] =
{ /* Magic */
	afxcmd_play, sfx_amb8,
	afxcmd_end
};
static int AmbSndSeq9[] =
{ /* Laughter */
	afxcmd_play, sfx_amb9,
	afxcmd_delay, 16,
	afxcmd_playrelvol, sfx_amb9, -4,
	afxcmd_delay, 16,
	afxcmd_playrelvol, sfx_amb9, -4,
	afxcmd_delay, 16,
	afxcmd_playrelvol, sfx_amb10, -4,
	afxcmd_delay, 16,
	afxcmd_playrelvol, sfx_amb10, -4,
	afxcmd_delay, 16,
	afxcmd_playrelvol, sfx_amb10, -4,
	afxcmd_end
};
static int AmbSndSeq10[] =
{ /* FastFootsteps */
	afxcmd_play, sfx_amb4,
	afxcmd_delay, 8,
	afxcmd_playrelvol, sfx_amb11, -3,
	afxcmd_delay, 8,
	afxcmd_playrelvol, sfx_amb4, -3,
	afxcmd_delay, 8,
	afxcmd_playrelvol, sfx_amb11, -3,
	afxcmd_delay, 8,
	afxcmd_playrelvol, sfx_amb4, -3,
	afxcmd_delay, 8,
	afxcmd_playrelvol, sfx_amb11, -3,
	afxcmd_delay, 8,
	afxcmd_playrelvol, sfx_amb4, -3,
	afxcmd_delay, 8,
	afxcmd_playrelvol, sfx_amb11, -3,
	afxcmd_end
};

static int *AmbientSfx[] =
{
	AmbSndSeq1,		/* Scream */
	AmbSndSeq2,		/* Squish */
	AmbSndSeq3,		/* Drops */
	AmbSndSeq4,		/* SlowFootsteps */
	AmbSndSeq5,		/* Heartbeat */
	AmbSndSeq6,		/* Bells */
	AmbSndSeq7,		/* Growl */
	AmbSndSeq8,		/* Magic */
	AmbSndSeq9,		/* Laughter */
	AmbSndSeq10		/* FastFootsteps */
};

static animdef_t animdefs[] =
{
	/* false = flat, true = texture */
	{ false, "FLTWAWA3", "FLTWAWA1", 8 }, /* Water */
	{ false, "FLTSLUD3", "FLTSLUD1", 8 }, /* Sludge */
	{ false, "FLTTELE4", "FLTTELE1", 6 }, /* Teleport */
	{ false, "FLTFLWW3", "FLTFLWW1", 9 }, /* River - West */
	{ false, "FLTLAVA4", "FLTLAVA1", 8 }, /* Lava */
	{ false, "FLATHUH4", "FLATHUH1", 8 }, /* Super Lava */
	{  true,  "LAVAFL3",  "LAVAFL1", 6 }, /* Texture: Lavaflow */
	{  true, "WATRWAL3", "WATRWAL1", 4 }, /* Texture: Waterfall */
	{ -1 }
};

static struct
{
	const char	*name;
	int		type;
} TerrainTypeDefs[] =
{
	{ "FLTWAWA1", FLOOR_WATER },
	{ "FLTFLWW1", FLOOR_WATER },
	{ "FLTLAVA1", FLOOR_LAVA },
	{ "FLATHUH1", FLOOR_LAVA },
	{ "FLTSLUD1", FLOOR_SLUDGE },
	{ "END", -1 }
};

// CODE --------------------------------------------------------------------

//----------------------------------------------------------------------------
//
// PROC P_InitLava
//
//----------------------------------------------------------------------------

void P_InitLava(void)
{
	memset(&LavaInflictor, 0, sizeof(mobj_t));
	LavaInflictor.type = MT_PHOENIXFX2;
	LavaInflictor.flags2 = MF2_FIREDAMAGE|MF2_NODMGTHRUST;
}

//==========================================================================
//
// PROC P_InitTerrainTypes
//
//==========================================================================

void P_InitTerrainTypes(void)
{
	int i;
	int lump;
	int size;

	size = (numflats + 1) * sizeof(int);
	TerrainTypes = (int *) Z_Malloc(size, PU_STATIC, NULL);
	memset(TerrainTypes, 0, size);
	for (i = 0; TerrainTypeDefs[i].type != -1; i++)
	{
		lump = W_CheckNumForName(TerrainTypeDefs[i].name);
		if (lump != -1)
		{
			TerrainTypes[lump - firstflat] = TerrainTypeDefs[i].type;
		}
	}
}

//----------------------------------------------------------------------------
//
// PROC P_InitPicAnims
//
//----------------------------------------------------------------------------

void P_InitPicAnims(void)
{
	int i;

	lastanim = anims;
	for (i = 0; animdefs[i].istexture != -1; i++)
	{
		if (animdefs[i].istexture)
		{ // Texture animation
			if (R_CheckTextureNumForName(animdefs[i].startname) == -1)
			{ // Texture doesn't exist
				continue;
			}
			lastanim->picnum = R_TextureNumForName(animdefs[i].endname);
			lastanim->basepic = R_TextureNumForName(animdefs[i].startname);
		}
		else
		{ // Flat animation
			if (W_CheckNumForName(animdefs[i].startname) == -1)
			{ // Flat doesn't exist
				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

==============================================================================
*/

//
// 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] ];
}

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

//
// Given the sector number and the line number, will tell you whether
// the line is two-sided or not.
//
int twoSided(int sector, int line)
{
	return (sectors[sector].lines[line])->flags & ML_TWOSIDED;
}

//==================================================================
//
// Return sector_t * of sector next to current. NULL if not two-sided line
//
//==================================================================
sector_t *getNextSector(line_t *line,sector_t *sec)
{
	if (!(line->flags & ML_TWOSIDED))
		return NULL;

	if (line->frontsector == sec)
		return line->backsector;

	return line->frontsector;
}

//==================================================================
//
// FIND LOWEST FLOOR HEIGHT IN SURROUNDING SECTORS
//
//==================================================================
fixed_t P_FindLowestFloorSurrounding(sector_t *sec)
{
	int		i;
	line_t		*check;
	sector_t	*other;
	fixed_t		floor = sec->floorheight;

	for (i = 0; i < sec->linecount; i++)
	{
		check = sec->lines[i];
		other = getNextSector(check,sec);
		if (!other)
			continue;
		if (other->floorheight < floor)
			floor = other->floorheight;
	}
	return floor;
}

//==================================================================
//
// FIND HIGHEST FLOOR HEIGHT IN SURROUNDING SECTORS
//
//==================================================================
fixed_t P_FindHighestFloorSurrounding(sector_t *sec)
{
	int		i;
	line_t		*check;
	sector_t	*other;
	fixed_t		floor = -500*FRACUNIT;

	for (i = 0; i < sec->linecount; i++)
	{
		check = sec->lines[i];
		other = getNextSector(check,sec);
		if (!other)
			continue;
		if (other->floorheight > floor)
			floor = other->floorheight;
	}
	return floor;
}

//==================================================================
//
// FIND NEXT HIGHEST FLOOR IN SURROUNDING SECTORS
//
//==================================================================

#define MAX_ADJOINING_SECTORS	20	/* 20 adjoining sectors max! */

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;
		if (h >= MAX_ADJOINING_SECTORS)
		{
			fprintf(stderr, "Sector with more than %d adjoining sectors\n",
					 MAX_ADJOINING_SECTORS);
			break;
		}
	}

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

	min = heightlist[0];
	for (i = 1; i < h; i++)
	{
		if (heightlist[i] < min)
			min = heightlist[i];
	}

	return min;
}

//==================================================================
//
// FIND LOWEST CEILING IN THE SURROUNDING SECTORS
//
//==================================================================
fixed_t P_FindLowestCeilingSurrounding(sector_t *sec)
{
	int		i;
	line_t		*check;
	sector_t	*other;
	fixed_t		height = H2MAXINT;

	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;

	line = &lines[linenum];
	if (!thing->player)
	{ // Check if trigger allowed by non-player mobj
		switch (line->special)
		{
		case 39:	// Trigger_TELEPORT
		case 97:	// Retrigger_TELEPORT
		case 4:		// Trigger_Raise_Door
		//case 10:	// PLAT DOWN-WAIT-UP-STAY TRIGGER
		//case 88:	// PLAT DOWN-WAIT-UP-STAY RETRIGGER
			break;
		default:
			return;
		}
	}
	switch (line->special)
	{
	//====================================================
	// TRIGGERS
	//====================================================
	case 2: // Open Door
		EV_DoDoor(line, vldoor_open, VDOORSPEED);
		line->special = 0;
		break;
	case 3: // Close Door
		EV_DoDoor(line, vldoor_close, VDOORSPEED);
		line->special = 0;
		break;
	case 4: // Raise Door
		EV_DoDoor(line, vldoor_normal, VDOORSPEED);
		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: // Trigger_Build_Stairs (8 pixel steps)
		EV_BuildStairs(line, 8*FRACUNIT);
		line->special = 0;
		break;
	case 106: // Trigger_Build_Stairs_16 (16 pixel steps)
		EV_BuildStairs(line, 16*FRACUNIT);
		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, VDOORSPEED);
		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 ();
		line->special = 0;
		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 105: // Trigger_SecretExit
		G_SecretExitLevel();
		line->special = 0;
		break;

	//====================================================
	// RE-DOABLE TRIGGERS
	//====================================================

	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, vldoor_close, VDOORSPEED);
		break;
	case 76: // Close Door 30
		EV_DoDoor(line, close30ThenOpen, VDOORSPEED);
		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, vldoor_open, VDOORSPEED);
		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, vldoor_normal, VDOORSPEED);
		break;
	case 100: // Retrigger_Raise_Door_Turbo
		EV_DoDoor(line, vldoor_normal, VDOORSPEED*3);
		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;
	}
}

//----------------------------------------------------------------------------
//
// PROC P_ShootSpecialLine
//
// Called when a thing shoots a special line.
//
//----------------------------------------------------------------------------

void P_ShootSpecialLine(mobj_t *thing, line_t *line)
{
	if (!thing->player)
	{ // Check if trigger allowed by non-player mobj
		switch (line->special)
		{
		case 46: // Impact_OpenDoor
			break;
		default:
			return;
		}
	}
	switch (line->special)
	{
	case 24: // Impact_RaiseFloor
		EV_DoFloor(line, raiseFloor);
		P_ChangeSwitchTexture(line, 0);
		break;
	case 46: // Impact_OpenDoor
		EV_DoDoor(line, vldoor_open, VDOORSPEED);
		P_ChangeSwitchTexture(line, 1);
		break;
	case 47: // Impact_RaiseFloorNear&Change
		EV_DoPlat(line, raiseToNearestAndChange, 0);
		P_ChangeSwitchTexture(line, 0);
		break;
	}
}

//----------------------------------------------------------------------------
//
// PROC P_PlayerInSpecialSector
//
// Called every tic frame that the player origin is in a special sector.
//
//----------------------------------------------------------------------------

void P_PlayerInSpecialSector(player_t *player)
{
	sector_t *sector;
	static int pushTab[5] =
	{
		2048*5,
		2048*10,
		2048*25,
		2048*30,
		2048*35
	};

	sector = player->mo->subsector->sector;
	if (player->mo->z != sector->floorheight)
	{ // Player is not touching the floor
		return;
	}
	switch (sector->special)
	{
	case 7: // Damage_Sludge
		if (!(leveltime & 31))
		{
			P_DamageMobj(player->mo, NULL, NULL, 4);
		}
		break;
	case 5: // Damage_LavaWimpy
		if (!(leveltime & 15))
		{
			P_DamageMobj(player->mo, &LavaInflictor, NULL, 5);
			P_HitFloor(player->mo);
		}
		break;
	case 16: // Damage_LavaHefty
		if (!(leveltime & 15))
		{
			P_DamageMobj(player->mo, &LavaInflictor, NULL, 8);
			P_HitFloor(player->mo);
		}
		break;
	case 4: // Scroll_EastLavaDamage
		P_Thrust(player, 0, 2048*28);
		if (!(leveltime & 15))
		{
			P_DamageMobj(player->mo, &LavaInflictor, NULL, 5);
			P_HitFloor(player->mo);
		}
		break;
	case 9: // SecretArea
		player->secretcount++;
		sector->special = 0;
		break;
	case 11: // Exit_SuperDamage (DOOM E1M8 finale)
		/*
		player->cheats &= ~CF_GODMODE;
		if (!(leveltime & 0x1f))
		{
			P_DamageMobj(player->mo, NULL, NULL, 20);
		}
		if (player->health <= 10)
		{
			G_ExitLevel();
		}
		*/
		break;

	case 25: case 26: case 27: case 28: case 29: // Scroll_North
		P_Thrust(player, ANG90, pushTab[sector->special-25]);
		break;
	case 20: case 21: case 22: case 23: case 24: // Scroll_East
		P_Thrust(player, 0, pushTab[sector->special-20]);
		break;
	case 30: case 31: case 32: case 33: case 34: // Scroll_South
		P_Thrust(player, ANG270, pushTab[sector->special-30]);
		break;
	case 35: case 36: case 37: case 38: case 39: // Scroll_West
		P_Thrust(player, ANG180, pushTab[sector->special-35]);
		break;

	case 40: case 41: case 42: case 43: case 44: case 45:
	case 46: case 47: case 48: case 49: case 50: case 51:
		// Wind specials are handled in (P_mobj):P_XYMovement
		break;

	case 15: // Friction_Low
		// Only used in (P_mobj):P_XYMovement and (P_user):P_Thrust
		break;

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

//----------------------------------------------------------------------------
//
// PROC P_UpdateSpecials
//
// Animate planes, scroll walls, etc.
//
//----------------------------------------------------------------------------

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

	// Animate flats and textures
	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;
			}
		}
	}
	// Update scrolling texture offsets
	for (i = 0; i < numlinespecials; i++)
	{
		line = linespeciallist[i];
		switch (line->special)
		{
		case 48: // Effect_Scroll_Left
			sides[line->sidenum[0]].textureoffset += FRACUNIT;
			break;
		case 99: // Effect_Scroll_Right
			sides[line->sidenum[0]].textureoffset -= FRACUNIT;
			break;
		}
	}
	// Handle buttons
	for (i = 0; i < MAXBUTTONS; i++)
	{
		if (buttonlist[i].btimer)
		{
			buttonlist[i].btimer--;
			if (!buttonlist[i].btimer)
			{
				switch (buttonlist[i].where)
				{
				case swtch_top:
					sides[buttonlist[i].line->sidenum[0]].toptexture =
							buttonlist[i].btexture;
					break;
				case swtch_middle:
					sides[buttonlist[i].line->sidenum[0]].midtexture =
							buttonlist[i].btexture;
						break;
				case swtch_bottom:
					sides[buttonlist[i].line->sidenum[0]].bottomtexture =
							buttonlist[i].btexture;
					break;
				}
				S_StartSound(buttonlist[i].soundorg, sfx_switch);
				memset(&buttonlist[i], 0, sizeof(button_t));
			}
		}
	}
}

//============================================================
//
//	Special Stuff that can't 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 = (floormove_t *) Z_Malloc (sizeof(*floor), PU_LEVSPEC, NULL);
			P_AddThinker (&floor->thinker);
			s2->specialdata = floor;
			floor->thinker.function = 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 = (floormove_t *) Z_Malloc (sizeof(*floor), PU_LEVSPEC, NULL);
			P_AddThinker (&floor->thinker);
			s1->specialdata = floor;
			floor->thinker.function = 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];

void P_SpawnSpecials (void)
{
	sector_t	*sector;
	int		i;
#if 0
	int		episode = 1;
	if (W_CheckNumForName("texture2") >= 0)
		episode = 2;
#endif
	//
	// 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;
		}
	}

	//
	// Init line EFFECTs
	//
	numlinespecials = 0;
	for (i = 0; i < numlines; i++)
	{
		switch(lines[i].special)
		{
			case 48: // Effect_Scroll_Left
			case 99: // Effect_Scroll_Right
				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));
}

//----------------------------------------------------------------------------
//
// PROC P_InitAmbientSound
//
//----------------------------------------------------------------------------

void P_InitAmbientSound(void)
{
	AmbSfxCount = 0;
	AmbSfxVolume = 0;
	AmbSfxTics = 10*TICSPERSEC;
	AmbSfxPtr = AmbSndSeqInit;
}

//----------------------------------------------------------------------------
//
// PROC P_AddAmbientSfx
//
// Called by (P_mobj):P_SpawnMapThing during (P_setup):P_SetupLevel.
//
//----------------------------------------------------------------------------

void P_AddAmbientSfx(int sequence)
{
	if (AmbSfxCount == MAX_AMBIENT_SFX)
	{
		I_Error("Too many ambient sound sequences");
	}
	LevelAmbientSfx[AmbSfxCount++] = AmbientSfx[sequence];
}

//----------------------------------------------------------------------------
//
// PROC P_AmbientSound
//
// Called every tic by (P_tick):P_Ticker.
//
//----------------------------------------------------------------------------

void P_AmbientSound(void)
{
	afxcmd_t cmd;
	int sound;
	boolean done;

	if (!AmbSfxCount)
	{ // No ambient sound sequences on current level
		return;
	}
	if (--AmbSfxTics)
	{
		return;
	}
	done = false;
	do
	{
		cmd = *AmbSfxPtr++;
		switch (cmd)
		{
		case afxcmd_play:
			AmbSfxVolume = P_Random()>>2;
			S_StartSoundAtVolume(NULL, *AmbSfxPtr++, AmbSfxVolume);
			break;
		case afxcmd_playabsvol:
			sound = *AmbSfxPtr++;
			AmbSfxVolume = *AmbSfxPtr++;
			S_StartSoundAtVolume(NULL, sound, AmbSfxVolume);
			break;
		case afxcmd_playrelvol:
			sound = *AmbSfxPtr++;
			AmbSfxVolume += *AmbSfxPtr++;
			if(AmbSfxVolume < 0)
			{
				AmbSfxVolume = 0;
			}
			else if(AmbSfxVolume > 127)
			{
				AmbSfxVolume = 127;
			}
			S_StartSoundAtVolume(NULL, sound, AmbSfxVolume);
			break;
		case afxcmd_delay:
			AmbSfxTics = *AmbSfxPtr++;
			done = true;
			break;
		case afxcmd_delayrand:
			AmbSfxTics = P_Random()&(*AmbSfxPtr++);
			done = true;
			break;
		case afxcmd_end:
			AmbSfxTics = 6*TICSPERSEC+P_Random();
			AmbSfxPtr = LevelAmbientSfx[P_Random() % AmbSfxCount];
			done = true;
			break;
		default:
			I_Error("P_AmbientSound: Unknown afxcmd %d", cmd);
			break;
		}
	} while (done == false);
}