shithub: heretic

ref: 7110aba73616e4f84b933a0db1ff96c4c22a9d6f
dir: heretic/p_enemy.c

View raw version
// P_enemy.c

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

// Macros

#define MAX_BOSS_SPOTS		8

// Types

typedef struct
{
	fixed_t		x;
	fixed_t		y;
	angle_t		angle;
} BossSpot_t;

// Private Data
static mobj_t	*soundtarget;

static int	BossSpotCount;
static BossSpot_t BossSpots[MAX_BOSS_SPOTS];

//----------------------------------------------------------------------------
//
// PROC P_InitMonsters
//
// Called at level load.
//
//----------------------------------------------------------------------------

void P_InitMonsters(void)
{
	BossSpotCount = 0;
}

//----------------------------------------------------------------------------
//
// PROC P_AddBossSpot
//
//----------------------------------------------------------------------------

void P_AddBossSpot(fixed_t x, fixed_t y, angle_t angle)
{
	if (BossSpotCount == MAX_BOSS_SPOTS)
	{
		I_Error("Too many boss spots.");
	}
	BossSpots[BossSpotCount].x = x;
	BossSpots[BossSpotCount].y = y;
	BossSpots[BossSpotCount].angle = angle;
	BossSpotCount++;
}

//----------------------------------------------------------------------------
//
// PROC P_RecursiveSound
//
//----------------------------------------------------------------------------

static void P_RecursiveSound(sector_t *sec, int soundblocks)
{
	int i;
	line_t *check;
	sector_t *other;

	// Wake up all monsters in this sector
	if (sec->validcount == validcount && sec->soundtraversed <= soundblocks + 1)
	{ // Already flooded
		return;
	}
	sec->validcount = validcount;
	sec->soundtraversed = soundblocks + 1;
	sec->soundtarget = soundtarget;
	for (i = 0; i < sec->linecount; i++)
	{
		check = sec->lines[i];
		if (!(check->flags & ML_TWOSIDED))
		{
			continue;
		}
		P_LineOpening(check);
		if (openrange <= 0)
		{ // Closed door
			continue;
		}
		if (sides[check->sidenum[0]].sector == sec)
		{
			other = sides[check->sidenum[1]].sector;
		}
		else
		{
			other = sides[check->sidenum[0]].sector;
		}
		if (check->flags & ML_SOUNDBLOCK)
		{
			if (!soundblocks)
			{
				P_RecursiveSound(other, 1);
			}
		}
		else
		{
			P_RecursiveSound(other, soundblocks);
		}
	}
}

//----------------------------------------------------------------------------
//
// PROC P_NoiseAlert
//
// If a monster yells at a player, it will alert other monsters to the
// player.
//
//----------------------------------------------------------------------------

void P_NoiseAlert(mobj_t *target, mobj_t *emmiter)
{
	soundtarget = target;
	validcount++;
	P_RecursiveSound(emmiter->subsector->sector, 0);
}

//----------------------------------------------------------------------------
//
// FUNC P_CheckMeleeRange
//
//----------------------------------------------------------------------------

static boolean P_CheckMeleeRange(mobj_t *actor)
{
	mobj_t *mo;
	fixed_t dist;

	if (!actor->target)
	{
		return false;
	}
	mo = actor->target;
	dist = P_AproxDistance(mo->x - actor->x, mo->y - actor->y);
	if (dist >= MELEERANGE)
	{
		return false;
	}
	if (!P_CheckSight(actor, mo))
	{
		return false;
	}
	if (mo->z > actor->z + actor->height)
	{ // Target is higher than the attacker
		return false;
	}
	else if (actor->z > mo->z + mo->height)
	{ // Attacker is higher
		return false;
	}
	return true;
}

//----------------------------------------------------------------------------
//
// FUNC P_CheckMissileRange
//
//----------------------------------------------------------------------------

static boolean P_CheckMissileRange(mobj_t *actor)
{
	fixed_t dist;

	if (!P_CheckSight(actor, actor->target))
	{
		return false;
	}
	if (actor->flags & MF_JUSTHIT)
	{ // The target just hit the enemy, so fight back!
		actor->flags &= ~MF_JUSTHIT;
		return true;
	}
	if (actor->reactiontime)
	{ // Don't attack yet
		return false;
	}
	dist = (P_AproxDistance(actor->x - actor->target->x,
		actor->y - actor->target->y)>>FRACBITS) - 64;
	if (!actor->info->meleestate)
	{ // No melee attack, so fire more frequently
		dist -= 128;
	}
	if (actor->type == MT_IMP)
	{ // Imp's fly attack from far away
		dist >>= 1;
	}
	if (dist > 200)
	{
		dist = 200;
	}
	if (P_Random() < dist)
	{
		return false;
	}
	return true;
}

/*
================
=
= P_Move
=
= Move in the current direction
= returns false if the move is blocked
================
*/

static fixed_t xspeed[8] = {FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000};
static fixed_t yspeed[8] = {0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000};

#define MAXSPECIALCROSS		8
extern line_t	*spechit[MAXSPECIALCROSS];
extern int			numspechit;

static boolean P_Move(mobj_t *actor)
{
	fixed_t tryx, tryy;
	line_t *ld;
	boolean good;

	if (actor->movedir == DI_NODIR)
	{
		return false;
	}
	tryx = actor->x + actor->info->speed * xspeed[actor->movedir];
	tryy = actor->y + actor->info->speed * yspeed[actor->movedir];
	if (!P_TryMove(actor, tryx, tryy))
	{ // open any specials
		if (actor->flags & MF_FLOAT && floatok)
		{ // must adjust height
			if (actor->z < tmfloorz)
			{
				actor->z += FLOATSPEED;
			}
			else
			{
				actor->z -= FLOATSPEED;
			}
			actor->flags |= MF_INFLOAT;
			return true;
		}
		if (!numspechit)
		{
			return false;
		}
		actor->movedir = DI_NODIR;
		good = false;
		while (numspechit--)
		{
			ld = spechit[numspechit];
			// if the special isn't a door that can be opened, return false
			if (P_UseSpecialLine(actor, ld))
			{
				good = true;
			}
		}
		return good;
	}
	else
	{
		actor->flags &= ~MF_INFLOAT;
	}
	if (!(actor->flags & MF_FLOAT))
	{
		if (actor->z > actor->floorz)
		{
			P_HitFloor(actor);
		}
		actor->z = actor->floorz;
	}
	return true;
}

//----------------------------------------------------------------------------
//
// FUNC P_TryWalk
//
// Attempts to move actor in its current (ob->moveangle) direction.
// If blocked by either a wall or an actor returns FALSE.
// If move is either clear of block only by a door, returns TRUE and sets.
// If a door is in the way, an OpenDoor call is made to start it opening.
//
//----------------------------------------------------------------------------

static boolean P_TryWalk(mobj_t *actor)
{
	if (!P_Move(actor))
	{
		return false;
	}
	actor->movecount = P_Random() & 15;
	return true;
}

/*
================
=
= P_NewChaseDir
=
================
*/

static dirtype_t opposite[] =
{
	DI_WEST, DI_SOUTHWEST, DI_SOUTH, DI_SOUTHEAST,
	DI_EAST, DI_NORTHEAST, DI_NORTH, DI_NORTHWEST,
	DI_NODIR
};

static dirtype_t diags[] =
{
	DI_NORTHWEST,
	DI_NORTHEAST,
	DI_SOUTHWEST,
	DI_SOUTHEAST
};

static void P_NewChaseDir (mobj_t *actor)
{
	fixed_t		deltax, deltay;
	dirtype_t	d[3];
	dirtype_t	tdir, olddir, turnaround;

	if (!actor->target)
		I_Error ("P_NewChaseDir: called with no target");

	olddir = actor->movedir;
	turnaround = opposite[olddir];

	deltax = actor->target->x - actor->x;
	deltay = actor->target->y - actor->y;
	if (deltax > 10*FRACUNIT)
		d[1] = DI_EAST;
	else if (deltax < -10*FRACUNIT)
		d[1] = DI_WEST;
	else
		d[1] = DI_NODIR;
	if (deltay < -10*FRACUNIT)
		d[2] = DI_SOUTH;
	else if (deltay > 10*FRACUNIT)
		d[2] = DI_NORTH;
	else
		d[2] = DI_NODIR;

// try direct route
	if (d[1] != DI_NODIR && d[2] != DI_NODIR)
	{
		actor->movedir = diags[((deltay < 0)<<1) + (deltax > 0)];
		if (actor->movedir != turnaround && P_TryWalk(actor))
			return;
	}

// try other directions
	if (P_Random() > 200 || abs(deltay) > abs(deltax))
	{
		tdir = d[1];
		d[1] = d[2];
		d[2] = tdir;
	}

	if (d[1] == turnaround)
		d[1] = DI_NODIR;
	if (d[2] == turnaround)
		d[2] = DI_NODIR;

	if (d[1] != DI_NODIR)
	{
		actor->movedir = d[1];
		if (P_TryWalk(actor))
			return;	/* either moved forward or attacked */
	}

	if (d[2] != DI_NODIR)
	{
		actor->movedir = d[2];
		if (P_TryWalk(actor))
			return;
	}

/* there is no direct path to the player, so pick another direction */

	if (olddir != DI_NODIR)
	{
		actor->movedir = olddir;
		if (P_TryWalk(actor))
			return;
	}

	if (P_Random() & 1)	/* randomly determine direction of search */
	{
		for (tdir = DI_EAST; tdir <= DI_SOUTHEAST; tdir++)
		{
			if (tdir != turnaround)
			{
				actor->movedir = tdir;
				if (P_TryWalk(actor))
					return;
			}
		}
	}
	else
	{
		for (tdir = DI_SOUTHEAST; (int)tdir >= DI_EAST; tdir--)
		{
			if (tdir != turnaround)
			{
				actor->movedir = tdir;
				if (P_TryWalk(actor))
					return;
			}
		}
	}

	if (turnaround != DI_NODIR)
	{
		actor->movedir = turnaround;
		if (P_TryWalk(actor))
			return;
	}

	actor->movedir = DI_NODIR;		// can't move
}

//---------------------------------------------------------------------------
//
// FUNC P_LookForMonsters
//
//---------------------------------------------------------------------------

#define MONS_LOOK_RANGE		(20 * 64 * FRACUNIT)
#define MONS_LOOK_LIMIT		64

static boolean P_LookForMonsters(mobj_t *actor)
{
	int count;
	mobj_t *mo;
	thinker_t *think;

	if (!P_CheckSight(players[0].mo, actor))
	{ // Player can't see monster
		return false;
	}
	count = 0;
	for (think = thinkercap.next; think != &thinkercap; think = think->next)
	{
		if (think->function != P_MobjThinker)
		{ // Not a mobj thinker
			continue;
		}
		mo = (mobj_t *)think;
		if (!(mo->flags & MF_COUNTKILL) || (mo == actor) || (mo->health <= 0))
		{ // Not a valid monster
			continue;
		}
		if (P_AproxDistance(actor->x - mo->x, actor->y - mo->y) > MONS_LOOK_RANGE)
		{ // Out of range
			continue;
		}
		if (P_Random() < 16)
		{ // Skip
			continue;
		}
		if (count++ > MONS_LOOK_LIMIT)
		{ // Stop searching
			return false;
		}
		if (!P_CheckSight(actor, mo))
		{ // Out of sight
			continue;
		}
		// Found a target monster
		actor->target = mo;
		return true;
	}
	return false;
}

/*
================
=
= P_LookForPlayers
=
= If allaround is false, only look 180 degrees in front
= returns true if a player is targeted
================
*/

static boolean P_LookForPlayers(mobj_t *actor, boolean allaround)
{
	int c;
	int stop;
	player_t *player;
	angle_t an;
	fixed_t dist;

	player = players;
	if (!netgame && player->health <= 0)
	{ // Single player game and player is dead, look for monsters
		return(P_LookForMonsters(actor));
	}
	c = 0;
	stop = (actor->lastlook - 1) & (MAXPLAYERS - 1);
	for ( ; ; actor->lastlook = (actor->lastlook + 1) & (MAXPLAYERS - 1))
	{
		if (!playeringame[actor->lastlook])
			continue;

		if (c++ == 2 || actor->lastlook == stop)
			return false;		// done looking

		player = &players[actor->lastlook];
		if (player->health <= 0)
			continue;		// dead
		if (!P_CheckSight (actor, player->mo))
			continue;		// out of sight

		if (!allaround)
		{
			an = R_PointToAngle2 (actor->x, actor->y, player->mo->x, player->mo->y)
				- actor->angle;
			if (an > ANG90 && an < ANG270)
			{
				dist = P_AproxDistance (player->mo->x - actor->x, player->mo->y - actor->y);
				// if real close, react anyway
				if (dist > MELEERANGE)
					continue;		// behind back
			}
		}
		if (player->mo->flags & MF_SHADOW)
		{ // Player is invisible
			if ((P_AproxDistance(player->mo->x - actor->x, player->mo->y - actor->y) > 2*MELEERANGE)
				&& P_AproxDistance(player->mo->momx, player->mo->momy) < 5*FRACUNIT)
			{ // Player is sneaking - can't detect
				return false;
			}
			if (P_Random() < 225)
			{ // Player isn't sneaking, but still didn't detect
				return false;
			}
		}
		actor->target = player->mo;
		return true;
	}
	return false;
}

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

						ACTION ROUTINES

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

/*
==============
=
= A_Look
=
= Stay in state until a player is sighted
=
==============
*/

void A_Look (mobj_t *actor)
{
	mobj_t		*targ;

	actor->threshold = 0;		// any shot will wake up
	targ = actor->subsector->sector->soundtarget;
	if (targ && (targ->flags & MF_SHOOTABLE))
	{
		actor->target = targ;
		if (actor->flags & MF_AMBUSH)
		{
			if (P_CheckSight (actor, actor->target))
				goto seeyou;
		}
		else
			goto seeyou;
	}

	if (!P_LookForPlayers (actor, false))
		return;

// go into chase state
seeyou:
	if (actor->info->seesound)
	{
		int		sound;

		/*
		switch (actor->info->seesound)
		{
		case sfx_posit1:
		case sfx_posit2:
		case sfx_posit3:
			sound = sfx_posit1 + P_Random()%3;
			break;
		case sfx_bgsit1:
		case sfx_bgsit2:
			sound = sfx_bgsit1 + P_Random()%2;
			break;
		default:
			sound = actor->info->seesound;
			break;
		}
		*/
		sound = actor->info->seesound;
		if (actor->flags2 & MF2_BOSS)
		{ // Full volume
			S_StartSound(NULL, sound);
		}
		else
		{
			S_StartSound(actor, sound);
		}
	}
	P_SetMobjState(actor, actor->info->seestate);
}


/*
==============
=
= A_Chase
=
= Actor has a melee attack, so it tries to close as fast as possible
=
==============
*/

void A_Chase(mobj_t *actor)
{
	int delta;

	if (actor->reactiontime)
	{
		actor->reactiontime--;
	}

	// Modify target threshold
	if (actor->threshold)
	{
		actor->threshold--;
	}

	if (gameskill == sk_nightmare)
	{ // Monsters move faster in nightmare mode
		actor->tics -= actor->tics/2;
		if (actor->tics < 3)
		{
			actor->tics = 3;
		}
	}

//
// turn towards movement direction if not there yet
//
	if (actor->movedir < 8)
	{
		actor->angle &= (7 << 29);
		delta = actor->angle - (actor->movedir << 29);
		if (delta > 0)
		{
			actor->angle -= ANG90/2;
		}
		else if (delta < 0)
		{
			actor->angle += ANG90/2;
		}
	}

	if (!actor->target || !(actor->target->flags & MF_SHOOTABLE))
	{ // look for a new target
		if (P_LookForPlayers(actor, true))
		{ // got a new target
			return;
		}
		P_SetMobjState(actor, actor->info->spawnstate);
		return;
	}

//
// don't attack twice in a row
//
	if (actor->flags & MF_JUSTATTACKED)
	{
		actor->flags &= ~MF_JUSTATTACKED;
		if (gameskill != sk_nightmare)
			P_NewChaseDir (actor);
		return;
	}

//
// check for melee attack
//
	if (actor->info->meleestate && P_CheckMeleeRange (actor))
	{
		if (actor->info->attacksound)
		{
			S_StartSound (actor, actor->info->attacksound);
		}
		P_SetMobjState (actor, actor->info->meleestate);
		return;
	}

//
// check for missile attack
//
	if (actor->info->missilestate)
	{
		if (gameskill < sk_nightmare && actor->movecount)
			goto nomissile;
		if (!P_CheckMissileRange (actor))
			goto nomissile;
		P_SetMobjState (actor, actor->info->missilestate);
		actor->flags |= MF_JUSTATTACKED;
		return;
	}

nomissile:
//
// possibly choose another target
//
	if (netgame && !actor->threshold && !P_CheckSight(actor, actor->target))
	{
		if (P_LookForPlayers(actor, true))
			return;		// got a new target
	}

//
// chase towards player
//
	if (--actor->movecount < 0 || !P_Move(actor))
	{
		P_NewChaseDir (actor);
	}

//
// make active sound
//
	if (actor->info->activesound && P_Random() < 3)
	{
		if (actor->type == MT_WIZARD && P_Random() < 128)
		{
			S_StartSound(actor, actor->info->seesound);
		}
		else if (actor->type == MT_SORCERER2)
		{
			S_StartSound(NULL, actor->info->activesound);
		}
		else
		{
			S_StartSound(actor, actor->info->activesound);
		}
	}
}

//----------------------------------------------------------------------------
//
// PROC A_FaceTarget
//
//----------------------------------------------------------------------------

void A_FaceTarget(mobj_t *actor)
{
	if (!actor->target)
	{
		return;
	}
	actor->flags &= ~MF_AMBUSH;
	actor->angle = R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y);
	if (actor->target->flags & MF_SHADOW)
	{ // Target is a ghost
		actor->angle += (P_Random2()) << 21;
	}
}

//----------------------------------------------------------------------------
//
// PROC A_Pain
//
//----------------------------------------------------------------------------

void A_Pain(mobj_t *actor)
{
	if (actor->info->painsound)
	{
		S_StartSound(actor, actor->info->painsound);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_DripBlood
//
//----------------------------------------------------------------------------

void A_DripBlood(mobj_t *actor)
{
	mobj_t *mo;
	int a, b;

	b = ((P_Random2())<<11);
	a = ((P_Random2())<<11);

	mo = P_SpawnMobj(actor->x + a,
			 actor->y + b,
			 actor->z, MT_BLOOD);
	mo->momx = (P_Random2())<<10;
	mo->momy = (P_Random2())<<10;
	mo->flags2 |= MF2_LOGRAV;
}

//----------------------------------------------------------------------------
//
// PROC A_KnightAttack
//
//----------------------------------------------------------------------------

void A_KnightAttack(mobj_t *actor)
{
	if (!actor->target)
	{
		return;
	}
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(3));
		S_StartSound(actor, sfx_kgtat2);
		return;
	}
	// Throw axe
	S_StartSound(actor, actor->info->attacksound);
	if (actor->type == MT_KNIGHTGHOST || P_Random() < 40)
	{ // Red axe
		P_SpawnMissile(actor, actor->target, MT_REDAXE);
		return;
	}
	// Green axe
	P_SpawnMissile(actor, actor->target, MT_KNIGHTAXE);
}

//----------------------------------------------------------------------------
//
// PROC A_ImpExplode
//
//----------------------------------------------------------------------------

void A_ImpExplode(mobj_t *actor)
{
	mobj_t *mo;

	mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_IMPCHUNK1);
	mo->momx = (P_Random2 ())<<10;
	mo->momy = (P_Random2 ())<<10;
	mo->momz = 9*FRACUNIT;
	mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_IMPCHUNK2);
	mo->momx = (P_Random2 ())<<10;
	mo->momy = (P_Random2 ())<<10;
	mo->momz = 9*FRACUNIT;
	if (actor->special1 == 666)
	{ // Extreme death crash
		P_SetMobjState(actor, S_IMP_XCRASH1);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_BeastPuff
//
//----------------------------------------------------------------------------

void A_BeastPuff(mobj_t *actor)
{
	int a, b, c;

	if (P_Random() > 64)
	{
		c = ((P_Random2())<<10);
		b = ((P_Random2())<<10);
		a = ((P_Random2())<<10);
		P_SpawnMobj(actor->x + a,
			    actor->y + b,
			    actor->z + c, MT_PUFFY);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_ImpMeAttack
//
//----------------------------------------------------------------------------

void A_ImpMeAttack(mobj_t *actor)
{
	if (!actor->target)
	{
		return;
	}
	S_StartSound(actor, actor->info->attacksound);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, 5 + (P_Random() & 7));
	}
}

//----------------------------------------------------------------------------
//
// PROC A_ImpMsAttack
//
//----------------------------------------------------------------------------

void A_ImpMsAttack(mobj_t *actor)
{
	mobj_t *dest;
	angle_t an;
	int dist;

	if (!actor->target || P_Random() > 64)
	{
		P_SetMobjState(actor, actor->info->seestate);
		return;
	}
	dest = actor->target;
	actor->flags |= MF_SKULLFLY;
	S_StartSound(actor, actor->info->attacksound);
	A_FaceTarget(actor);
	an = actor->angle >> ANGLETOFINESHIFT;
	actor->momx = FixedMul(12*FRACUNIT, finecosine[an]);
	actor->momy = FixedMul(12*FRACUNIT, finesine[an]);
	dist = P_AproxDistance(dest->x-actor->x, dest->y-actor->y);
	dist = dist/(12*FRACUNIT);
	if (dist < 1)
	{
		dist = 1;
	}
	actor->momz = (dest->z + (dest->height>>1) - actor->z) / dist;
}

//----------------------------------------------------------------------------
//
// PROC A_ImpMsAttack2
//
// Fireball attack of the imp leader.
//
//----------------------------------------------------------------------------

void A_ImpMsAttack2(mobj_t *actor)
{
	if (!actor->target)
	{
		return;
	}
	S_StartSound(actor, actor->info->attacksound);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, 5 + (P_Random() & 7));
		return;
	}
	P_SpawnMissile(actor, actor->target, MT_IMPBALL);
}

//----------------------------------------------------------------------------
//
// PROC A_ImpDeath
//
//----------------------------------------------------------------------------

void A_ImpDeath(mobj_t *actor)
{
	actor->flags &= ~MF_SOLID;
	actor->flags2 |= MF2_FOOTCLIP;
	if (actor->z <= actor->floorz)
	{
		P_SetMobjState(actor, S_IMP_CRASH1);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_ImpXDeath1
//
//----------------------------------------------------------------------------

void A_ImpXDeath1(mobj_t *actor)
{
	actor->flags &= ~MF_SOLID;
	actor->flags |= MF_NOGRAVITY;
	actor->flags2 |= MF2_FOOTCLIP;
	actor->special1 = 666; // Flag the crash routine
}

//----------------------------------------------------------------------------
//
// PROC A_ImpXDeath2
//
//----------------------------------------------------------------------------

void A_ImpXDeath2(mobj_t *actor)
{
	actor->flags &= ~MF_NOGRAVITY;
	if (actor->z <= actor->floorz)
	{
		P_SetMobjState(actor, S_IMP_CRASH1);
	}
}

//----------------------------------------------------------------------------
//
// FUNC P_UpdateChicken
//
// Returns true if the chicken morphs.
//
//----------------------------------------------------------------------------

boolean P_UpdateChicken(mobj_t *actor, int tics)
{
	mobj_t *fog;
	fixed_t x;
	fixed_t y;
	fixed_t z;
	mobjtype_t moType;
	mobj_t *mo;
	mobj_t oldChicken;

	actor->special1 -= tics;
	if (actor->special1 > 0)
	{
		return false;
	}
	moType = actor->special2;
	x = actor->x;
	y = actor->y;
	z = actor->z;
	oldChicken = *actor;
	P_SetMobjState(actor, S_FREETARGMOBJ);
	mo = P_SpawnMobj(x, y, z, moType);
	if (P_TestMobjLocation(mo) == false)
	{ // Didn't fit
		P_RemoveMobj(mo);
		mo = P_SpawnMobj(x, y, z, MT_CHICKEN);
		mo->angle = oldChicken.angle;
		mo->flags = oldChicken.flags;
		mo->health = oldChicken.health;
		mo->target = oldChicken.target;
		mo->special1 = 5*35; // Next try in 5 seconds
		mo->special2 = moType;
		return false;
	}
	mo->angle = oldChicken.angle;
	mo->target = oldChicken.target;
	fog = P_SpawnMobj(x, y, z+TELEFOGHEIGHT, MT_TFOG);
	S_StartSound(fog, sfx_telept);
	return true;
}

//----------------------------------------------------------------------------
//
// PROC A_ChicAttack
//
//----------------------------------------------------------------------------

void A_ChicAttack(mobj_t *actor)
{
	if (P_UpdateChicken(actor, 18))
	{
		return;
	}
	if (!actor->target)
	{
		return;
	}
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, 1 + (P_Random() & 1));
	}
}

//----------------------------------------------------------------------------
//
// PROC A_ChicLook
//
//----------------------------------------------------------------------------

void A_ChicLook(mobj_t *actor)
{
	if (P_UpdateChicken(actor, 10))
	{
		return;
	}
	A_Look(actor);
}

//----------------------------------------------------------------------------
//
// PROC A_ChicChase
//
//----------------------------------------------------------------------------

void A_ChicChase(mobj_t *actor)
{
	if (P_UpdateChicken(actor, 3))
	{
		return;
	}
	A_Chase(actor);
}

//----------------------------------------------------------------------------
//
// PROC A_ChicPain
//
//----------------------------------------------------------------------------

void A_ChicPain(mobj_t *actor)
{
	if (P_UpdateChicken(actor, 10))
	{
		return;
	}
	S_StartSound(actor, actor->info->painsound);
}

//----------------------------------------------------------------------------
//
// PROC A_Feathers
//
//----------------------------------------------------------------------------

void A_Feathers(mobj_t *actor)
{
	int i;
	int count;
	mobj_t *mo;

	if (actor->health > 0)
	{ // Pain
		count = P_Random() < 32 ? 2 : 1;
	}
	else
	{ // Death
		count = 5 + (P_Random() & 3);
	}
	for (i = 0; i < count; i++)
	{
		mo = P_SpawnMobj(actor->x, actor->y, actor->z + 20*FRACUNIT, MT_FEATHER);
		mo->target = actor;
		mo->momx = (P_Random2())<<8;
		mo->momy = (P_Random2())<<8;
		mo->momz = FRACUNIT + (P_Random()<<9);
		P_SetMobjState(mo, S_FEATHER1 + (P_Random() & 7));
	}
}

//----------------------------------------------------------------------------
//
// PROC A_MummyAttack
//
//----------------------------------------------------------------------------

void A_MummyAttack(mobj_t *actor)
{
	if (!actor->target)
	{
		return;
	}
	S_StartSound(actor, actor->info->attacksound);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(2));
		S_StartSound(actor, sfx_mumat2);
		return;
	}
	S_StartSound(actor, sfx_mumat1);
}

//----------------------------------------------------------------------------
//
// PROC A_MummyAttack2
//
// Mummy leader missile attack.
//
//----------------------------------------------------------------------------

void A_MummyAttack2(mobj_t *actor)
{
	mobj_t *mo;

	if (!actor->target)
	{
		return;
	}
	//S_StartSound(actor, actor->info->attacksound);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(2));
		return;
	}
	mo = P_SpawnMissile(actor, actor->target, MT_MUMMYFX1);
	//mo = P_SpawnMissile(actor, actor->target, MT_EGGFX);
	if (mo != NULL)
	{
		mo->special1 = (intptr_t)actor->target;
	}
}

//----------------------------------------------------------------------------
//
// PROC A_MummyFX1Seek
//
//----------------------------------------------------------------------------

void A_MummyFX1Seek(mobj_t *actor)
{
	P_SeekerMissile(actor, ANGLE_1*10, ANGLE_1*20);
}

//----------------------------------------------------------------------------
//
// PROC A_MummySoul
//
//----------------------------------------------------------------------------

void A_MummySoul(mobj_t *mummy)
{
	mobj_t *mo;

	mo = P_SpawnMobj(mummy->x, mummy->y, mummy->z + 10*FRACUNIT, MT_MUMMYSOUL);
	mo->momz = FRACUNIT;
}

//----------------------------------------------------------------------------
//
// PROC A_Sor1Pain
//
//----------------------------------------------------------------------------

void A_Sor1Pain(mobj_t *actor)
{
	actor->special1 = 20; // Number of steps to walk fast
	A_Pain(actor);
}

//----------------------------------------------------------------------------
//
// PROC A_Sor1Chase
//
//----------------------------------------------------------------------------

void A_Sor1Chase(mobj_t *actor)
{
	if (actor->special1)
	{
		actor->special1--;
		actor->tics -= 3;
	}
	A_Chase(actor);
}

//----------------------------------------------------------------------------
//
// PROC A_Srcr1Attack
//
// Sorcerer demon attack.
//
//----------------------------------------------------------------------------

void A_Srcr1Attack(mobj_t *actor)
{
	mobj_t *mo;
	fixed_t momz;
	angle_t angle;

	if (!actor->target)
	{
		return;
	}
	S_StartSound(actor, actor->info->attacksound);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(8));
		return;
	}
	if (actor->health > (actor->info->spawnhealth/3)*2)
	{ // Spit one fireball
		P_SpawnMissile(actor, actor->target, MT_SRCRFX1);
	}
	else
	{ // Spit three fireballs
		mo = P_SpawnMissile(actor, actor->target, MT_SRCRFX1);
		if (mo)
		{
			momz = mo->momz;
			angle = mo->angle;
			P_SpawnMissileAngle(actor, MT_SRCRFX1, angle - ANGLE_1*3, momz);
			P_SpawnMissileAngle(actor, MT_SRCRFX1, angle + ANGLE_1*3, momz);
		}
		if (actor->health < actor->info->spawnhealth/3)
		{ // Maybe attack again
			if (actor->special1)
			{ // Just attacked, so don't attack again
				actor->special1 = 0;
			}
			else
			{ // Set state to attack again
				actor->special1 = 1;
				P_SetMobjState(actor, S_SRCR1_ATK4);
			}
		}
	}
}

//----------------------------------------------------------------------------
//
// PROC A_SorcererRise
//
//----------------------------------------------------------------------------

void A_SorcererRise(mobj_t *actor)
{
	mobj_t *mo;

	actor->flags &= ~MF_SOLID;
	mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SORCERER2);
	P_SetMobjState(mo, S_SOR2_RISE1);
	mo->angle = actor->angle;
	mo->target = actor->target;
}

//----------------------------------------------------------------------------
//
// PROC P_DSparilTeleport
//
//----------------------------------------------------------------------------

void P_DSparilTeleport(mobj_t *actor)
{
	int i;
	fixed_t x;
	fixed_t y;
	fixed_t prevX;
	fixed_t prevY;
	fixed_t prevZ;
	mobj_t *mo;

	if (!BossSpotCount)
	{ // No spots
		return;
	}
	i = P_Random();
	do
	{
		i++;
		x = BossSpots[i % BossSpotCount].x;
		y = BossSpots[i % BossSpotCount].y;
	} while (P_AproxDistance(actor->x - x, actor->y - y) < 128*FRACUNIT);
	prevX = actor->x;
	prevY = actor->y;
	prevZ = actor->z;
	if (P_TeleportMove(actor, x, y))
	{
		mo = P_SpawnMobj(prevX, prevY, prevZ, MT_SOR2TELEFADE);
		S_StartSound(mo, sfx_telept);
		P_SetMobjState(actor, S_SOR2_TELE1);
		S_StartSound(actor, sfx_telept);
		actor->z = actor->floorz;
		actor->angle = BossSpots[i % BossSpotCount].angle;
		actor->momx = actor->momy = actor->momz = 0;
	}
}

//----------------------------------------------------------------------------
//
// PROC A_Srcr2Decide
//
//----------------------------------------------------------------------------

void A_Srcr2Decide(mobj_t *actor)
{
	static int chance[] =
	{
		192, 120, 120, 120, 64, 64, 32, 16, 0
	};

	if (!BossSpotCount)
	{ // No spots
		return;
	}
	if (P_Random() < chance[actor->health / (actor->info->spawnhealth / 8)])
	{
		P_DSparilTeleport(actor);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_Srcr2Attack
//
//----------------------------------------------------------------------------

void A_Srcr2Attack(mobj_t *actor)
{
	int chance;

	if (!actor->target)
	{
		return;
	}
	S_StartSound(NULL, actor->info->attacksound);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(20));
		return;
	}
	chance = actor->health < actor->info->spawnhealth/2 ? 96 : 48;
	if (P_Random() < chance)
	{ // Wizard spawners
		P_SpawnMissileAngle(actor, MT_SOR2FX2, actor->angle - ANG45, FRACUNIT/2);
		P_SpawnMissileAngle(actor, MT_SOR2FX2, actor->angle + ANG45, FRACUNIT/2);
	}
	else
	{ // Blue bolt
		P_SpawnMissile(actor, actor->target, MT_SOR2FX1);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_BlueSpark
//
//----------------------------------------------------------------------------

void A_BlueSpark(mobj_t *actor)
{
	int i;
	mobj_t *mo;

	for (i = 0; i < 2; i++)
	{
		mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SOR2FXSPARK);
		mo->momx = (P_Random2())<<9;
		mo->momy = (P_Random2())<<9;
		mo->momz = FRACUNIT + (P_Random()<<8);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_GenWizard
//
//----------------------------------------------------------------------------

void A_GenWizard(mobj_t *actor)
{
	mobj_t *mo;
	mobj_t *fog;

	mo = P_SpawnMobj(actor->x, actor->y, actor->z - mobjinfo[MT_WIZARD].height/2, MT_WIZARD);
	if (P_TestMobjLocation(mo) == false)
	{ // Didn't fit
		P_RemoveMobj(mo);
		return;
	}
	actor->momx = actor->momy = actor->momz = 0;
	P_SetMobjState(actor, mobjinfo[actor->type].deathstate);
	actor->flags &= ~MF_MISSILE;
	fog = P_SpawnMobj(actor->x, actor->y, actor->z, MT_TFOG);
	S_StartSound(fog, sfx_telept);
}

//----------------------------------------------------------------------------
//
// PROC A_Sor2DthInit
//
//----------------------------------------------------------------------------

void A_Sor2DthInit(mobj_t *actor)
{
	actor->special1 = 7; // Animation loop counter
	P_Massacre(); // Kill monsters early
}

//----------------------------------------------------------------------------
//
// PROC A_Sor2DthLoop
//
//----------------------------------------------------------------------------

void A_Sor2DthLoop(mobj_t *actor)
{
	if (--actor->special1)
	{ // Need to loop
		P_SetMobjState(actor, S_SOR2_DIE4);
	}
}

//----------------------------------------------------------------------------
//
// D'Sparil Sound Routines
//
//----------------------------------------------------------------------------

void A_SorZap(mobj_t *actor)
{
	S_StartSound(NULL, sfx_sorzap);
}

void A_SorRise(mobj_t *actor)
{
	S_StartSound(NULL, sfx_sorrise);
}

void A_SorDSph(mobj_t *actor)
{
	S_StartSound(NULL, sfx_sordsph);
}

void A_SorDExp(mobj_t *actor)
{
	S_StartSound(NULL, sfx_sordexp);
}

void A_SorDBon(mobj_t *actor)
{
	S_StartSound(NULL, sfx_sordbon);
}

void A_SorSightSnd(mobj_t *actor)
{
	S_StartSound(NULL, sfx_sorsit);
}

//----------------------------------------------------------------------------
//
// PROC A_MinotaurAtk1
//
// Melee attack.
//
//----------------------------------------------------------------------------

void A_MinotaurAtk1(mobj_t *actor)
{
	player_t *player;

	if (!actor->target)
	{
		return;
	}
	S_StartSound(actor, sfx_stfpow);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(4));
		if ((player = actor->target->player) != NULL)
		{ // Squish the player
			player->deltaviewheight = -16*FRACUNIT;
		}
	}
}

//----------------------------------------------------------------------------
//
// PROC A_MinotaurDecide
//
// Choose a missile attack.
//
//----------------------------------------------------------------------------

#define MNTR_CHARGE_SPEED	(13 * FRACUNIT)

void A_MinotaurDecide(mobj_t *actor)
{
	angle_t angle;
	mobj_t *target;
	int dist;

	target = actor->target;
	if (!target)
	{
		return;
	}
	S_StartSound(actor, sfx_minsit);
	dist = P_AproxDistance(actor->x - target->x, actor->y - target->y);
	if (target->z + target->height > actor->z
		&& target->z + target->height < actor->z + actor->height
		&& dist < 8*64*FRACUNIT
		&& dist > 1*64*FRACUNIT
		&& P_Random() < 150)
	{ // Charge attack
		// Don't call the state function right away
		P_SetMobjStateNF(actor, S_MNTR_ATK4_1);
		actor->flags |= MF_SKULLFLY;
		A_FaceTarget(actor);
		angle = actor->angle>>ANGLETOFINESHIFT;
		actor->momx = FixedMul(MNTR_CHARGE_SPEED, finecosine[angle]);
		actor->momy = FixedMul(MNTR_CHARGE_SPEED, finesine[angle]);
		actor->special1 = 35/2; // Charge duration
	}
	else if (target->z == target->floorz
		&& dist < 9*64*FRACUNIT
		&& P_Random() < 220)
	{ // Floor fire attack
		P_SetMobjState(actor, S_MNTR_ATK3_1);
		actor->special2 = 0;
	}
	else
	{ // Swing attack
		A_FaceTarget(actor);
		// Don't need to call P_SetMobjState because the current state
		// falls through to the swing attack
	}
}

//----------------------------------------------------------------------------
//
// PROC A_MinotaurCharge
//
//----------------------------------------------------------------------------

void A_MinotaurCharge(mobj_t *actor)
{
	mobj_t *puff;

	if (actor->special1)
	{
		puff = P_SpawnMobj(actor->x, actor->y, actor->z, MT_PHOENIXPUFF);
		puff->momz = 2*FRACUNIT;
		actor->special1--;
	}
	else
	{
		actor->flags &= ~MF_SKULLFLY;
		P_SetMobjState(actor, actor->info->seestate);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_MinotaurAtk2
//
// Swing attack.
//
//----------------------------------------------------------------------------

void A_MinotaurAtk2(mobj_t *actor)
{
	mobj_t *mo;
	angle_t angle;
	fixed_t momz;

	if (!actor->target)
		return;

	S_StartSound(actor, sfx_minat2);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(5));
		return;
	}
	mo = P_SpawnMissile(actor, actor->target, MT_MNTRFX1);
	if (mo)
	{
		S_StartSound(mo, sfx_minat2);
		momz = mo->momz;
		angle = mo->angle;
		P_SpawnMissileAngle(actor, MT_MNTRFX1, angle - (ANG45 / 8), momz);
		P_SpawnMissileAngle(actor, MT_MNTRFX1, angle + (ANG45 / 8), momz);
		P_SpawnMissileAngle(actor, MT_MNTRFX1, angle - (ANG45 /16), momz);
		P_SpawnMissileAngle(actor, MT_MNTRFX1, angle + (ANG45 /16), momz);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_MinotaurAtk3
//
// Floor fire attack.
//
//----------------------------------------------------------------------------

void A_MinotaurAtk3(mobj_t *actor)
{
	mobj_t *mo;
	player_t *player;

	if (!actor->target)
	{
		return;
	}

	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(5));
		if ((player = actor->target->player) != NULL)
		{ // Squish the player
			player->deltaviewheight = -16*FRACUNIT;
		}
	}
	else
	{
		mo = P_SpawnMissile(actor, actor->target, MT_MNTRFX2);
		if (mo != NULL)
		{
			S_StartSound(mo, sfx_minat1);
		}
	}
	if (P_Random() < 192 && actor->special2 == 0)
	{
		P_SetMobjState(actor, S_MNTR_ATK3_4);
		actor->special2 = 1;
	}
}

//----------------------------------------------------------------------------
//
// PROC A_MntrFloorFire
//
//----------------------------------------------------------------------------

void A_MntrFloorFire(mobj_t *actor)
{
	mobj_t *mo;
	int a, b;

	actor->z = actor->floorz;
	b = ((P_Random2()) << 10);
	a = ((P_Random2()) << 10);
	mo = P_SpawnMobj(actor->x + a,
			 actor->y + b, ONFLOORZ, MT_MNTRFX3);
	mo->target = actor->target;
	mo->momx = 1; // Force block checking
	P_CheckMissileSpawn(mo);
}

//----------------------------------------------------------------------------
//
// PROC A_BeastAttack
//
//----------------------------------------------------------------------------

void A_BeastAttack(mobj_t *actor)
{
	if (!actor->target)
	{
		return;
	}
	S_StartSound(actor, actor->info->attacksound);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(3));
		return;
	}
	P_SpawnMissile(actor, actor->target, MT_BEASTBALL);
}

//----------------------------------------------------------------------------
//
// PROC A_HeadAttack
//
//----------------------------------------------------------------------------

void A_HeadAttack(mobj_t *actor)
{
	int i;
	mobj_t *fire;
	mobj_t *baseFire;
	mobj_t *mo;
	mobj_t *target;
	int randAttack;
	static int atkResolve1[] = { 50, 150 };
	static int atkResolve2[] = { 150, 200 };
	int dist;

	// Ice ball		(close 20% : far 60%)
	// Fire column	(close 40% : far 20%)
	// Whirlwind	(close 40% : far 20%)
	// Distance threshold = 8 cells

	target = actor->target;
	if (target == NULL)
	{
		return;
	}
	A_FaceTarget(actor);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(target, actor, actor, HITDICE(6));
		return;
	}
	dist = P_AproxDistance(actor->x-target->x, actor->y-target->y) > 8*64*FRACUNIT;
	randAttack = P_Random();
	if (randAttack < atkResolve1[dist])
	{ // Ice ball
		P_SpawnMissile(actor, target, MT_HEADFX1);
		S_StartSound(actor, sfx_hedat2);
	}
	else if (randAttack < atkResolve2[dist])
	{ // Fire column
		baseFire = P_SpawnMissile(actor, target, MT_HEADFX3);
		if (baseFire != NULL)
		{
			P_SetMobjState(baseFire, S_HEADFX3_4); // Don't grow
			for (i = 0; i < 5; i++)
			{
				fire = P_SpawnMobj(baseFire->x, baseFire->y,
						   baseFire->z, MT_HEADFX3);
				if (i == 0)
				{
					S_StartSound(actor, sfx_hedat1);
				}
				fire->target = baseFire->target;
				fire->angle = baseFire->angle;
				fire->momx = baseFire->momx;
				fire->momy = baseFire->momy;
				fire->momz = baseFire->momz;
				fire->damage = 0;
				fire->health = (i + 1)*2;
				P_CheckMissileSpawn(fire);
			}
		}
	}
	else
	{ // Whirlwind
		mo = P_SpawnMissile(actor, target, MT_WHIRLWIND);
		if (mo != NULL)
		{
			mo->z -= 32*FRACUNIT;
			mo->special1 = (intptr_t)target;
			mo->special2 = 50; // Timer for active sound
			mo->health = 20*TICSPERSEC; // Duration
			S_StartSound(actor, sfx_hedat3);
		}
	}
}

//----------------------------------------------------------------------------
//
// PROC A_WhirlwindSeek
//
//----------------------------------------------------------------------------

void A_WhirlwindSeek(mobj_t *actor)
{
	actor->health -= 3;
	if (actor->health < 0)
	{
		actor->momx = actor->momy = actor->momz = 0;
		P_SetMobjState(actor, mobjinfo[actor->type].deathstate);
		actor->flags &= ~MF_MISSILE;
		return;
	}
	if ((actor->special2 -= 3) < 0)
	{
		actor->special2 = 58 + (P_Random() & 31);
		S_StartSound(actor, sfx_hedat3);
	}
	if (actor->special1
		&& (((mobj_t *)(actor->special1))->flags & MF_SHADOW))
	{
		return;
	}
	P_SeekerMissile(actor, ANGLE_1*10, ANGLE_1*30);
}

//----------------------------------------------------------------------------
//
// PROC A_HeadIceImpact
//
//----------------------------------------------------------------------------

void A_HeadIceImpact(mobj_t *ice)
{
	unsigned int i;
	angle_t angle;
	mobj_t *shard;

	for (i = 0; i < 8; i++)
	{
		shard = P_SpawnMobj(ice->x, ice->y, ice->z, MT_HEADFX2);
		angle = i*ANG45;
		shard->target = ice->target;
		shard->angle = angle;
		angle >>= ANGLETOFINESHIFT;
		shard->momx = FixedMul(shard->info->speed, finecosine[angle]);
		shard->momy = FixedMul(shard->info->speed, finesine[angle]);
		shard->momz = -.6*FRACUNIT;
		P_CheckMissileSpawn(shard);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_HeadFireGrow
//
//----------------------------------------------------------------------------

void A_HeadFireGrow(mobj_t *fire)
{
	fire->health--;
	fire->z += 9*FRACUNIT;
	if (fire->health == 0)
	{
		fire->damage = fire->info->damage;
		P_SetMobjState(fire, S_HEADFX3_4);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_SnakeAttack
//
//----------------------------------------------------------------------------

void A_SnakeAttack(mobj_t *actor)
{
	if (!actor->target)
	{
		P_SetMobjState(actor, S_SNAKE_WALK1);
		return;
	}
	S_StartSound(actor, actor->info->attacksound);
	A_FaceTarget(actor);
	P_SpawnMissile(actor, actor->target, MT_SNAKEPRO_A);
}

//----------------------------------------------------------------------------
//
// PROC A_SnakeAttack2
//
//----------------------------------------------------------------------------

void A_SnakeAttack2(mobj_t *actor)
{
	if (!actor->target)
	{
		P_SetMobjState(actor, S_SNAKE_WALK1);
		return;
	}
	S_StartSound(actor, actor->info->attacksound);
	A_FaceTarget(actor);
	P_SpawnMissile(actor, actor->target, MT_SNAKEPRO_B);
}

//----------------------------------------------------------------------------
//
// PROC A_ClinkAttack
//
//----------------------------------------------------------------------------

void A_ClinkAttack(mobj_t *actor)
{
	int damage;

	if (!actor->target)
	{
		return;
	}
	S_StartSound(actor, actor->info->attacksound);
	if (P_CheckMeleeRange(actor))
	{
		damage = ((P_Random() % 7) + 3);
		P_DamageMobj(actor->target, actor, actor, damage);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_GhostOff
//
//----------------------------------------------------------------------------

void A_GhostOff(mobj_t *actor)
{
	actor->flags &= ~MF_SHADOW;
}

//----------------------------------------------------------------------------
//
// PROC A_WizAtk1
//
//----------------------------------------------------------------------------

void A_WizAtk1(mobj_t *actor)
{
	A_FaceTarget(actor);
	actor->flags &= ~MF_SHADOW;
}

//----------------------------------------------------------------------------
//
// PROC A_WizAtk2
//
//----------------------------------------------------------------------------

void A_WizAtk2(mobj_t *actor)
{
	A_FaceTarget(actor);
	actor->flags |= MF_SHADOW;
}

//----------------------------------------------------------------------------
//
// PROC A_WizAtk3
//
//----------------------------------------------------------------------------

void A_WizAtk3(mobj_t *actor)
{
	mobj_t *mo;
	angle_t angle;
	fixed_t momz;

	actor->flags &= ~MF_SHADOW;
	if (!actor->target)
	{
		return;
	}
	S_StartSound(actor, actor->info->attacksound);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(4));
		return;
	}
	mo = P_SpawnMissile(actor, actor->target, MT_WIZFX1);
	if (mo)
	{
		momz = mo->momz;
		angle = mo->angle;
		P_SpawnMissileAngle(actor, MT_WIZFX1, angle - (ANG45 / 8), momz);
		P_SpawnMissileAngle(actor, MT_WIZFX1, angle + (ANG45 / 8), momz);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_Scream
//
//----------------------------------------------------------------------------

void A_Scream(mobj_t *actor)
{
	switch (actor->type)
	{
	case MT_CHICPLAYER:
	case MT_SORCERER1:
	case MT_MINOTAUR:
		// Make boss death sounds full volume
		S_StartSound(NULL, actor->info->deathsound);
		break;
	case MT_PLAYER:
		// Handle the different player death screams
		if (actor->special1 < 10)
		{ // Wimpy death sound
			S_StartSound(actor, sfx_plrwdth);
		}
		else if (actor->health > -50)
		{ // Normal death sound
			S_StartSound(actor, actor->info->deathsound);
		}
		else if (actor->health > -100)
		{ // Crazy death sound
			S_StartSound(actor, sfx_plrcdth);
		}
		else
		{ // Extreme death sound
			S_StartSound(actor, sfx_gibdth);
		}
		break;
	default:
		S_StartSound(actor, actor->info->deathsound);
		break;
	}
}

//---------------------------------------------------------------------------
//
// PROC P_DropItem
//
//---------------------------------------------------------------------------

void P_DropItem(mobj_t *source, mobjtype_t type, int special, int chance)
{
	mobj_t *mo;

	if (P_Random() > chance)
	{
		return;
	}
	mo = P_SpawnMobj(source->x, source->y,
			 source->z + (source->height>>1), type);
	mo->momx = (P_Random2())<<8;
	mo->momy = (P_Random2())<<8;
	mo->momz = FRACUNIT*5 + (P_Random()<<10);
	mo->flags |= MF_DROPPED;
	mo->health = special;
}

//----------------------------------------------------------------------------
//
// PROC A_NoBlocking
//
//----------------------------------------------------------------------------

void A_NoBlocking(mobj_t *actor)
{
	actor->flags &= ~MF_SOLID;
	// Check for monsters dropping things
	switch (actor->type)
	{
	case MT_MUMMY:
	case MT_MUMMYLEADER:
	case MT_MUMMYGHOST:
	case MT_MUMMYLEADERGHOST:
		P_DropItem(actor, MT_AMGWNDWIMPY, 3, 84);
		break;
	case MT_KNIGHT:
	case MT_KNIGHTGHOST:
		P_DropItem(actor, MT_AMCBOWWIMPY, 5, 84);
		break;
	case MT_WIZARD:
		P_DropItem(actor, MT_AMBLSRWIMPY, 10, 84);
		P_DropItem(actor, MT_ARTITOMEOFPOWER, 0, 4);
		break;
	case MT_HEAD:
		P_DropItem(actor, MT_AMBLSRWIMPY, 10, 84);
		P_DropItem(actor, MT_ARTIEGG, 0, 51);
		break;
	case MT_BEAST:
		P_DropItem(actor, MT_AMCBOWWIMPY, 10, 84);
		break;
	case MT_CLINK:
		P_DropItem(actor, MT_AMSKRDWIMPY, 20, 84);
		break;
	case MT_SNAKE:
		P_DropItem(actor, MT_AMPHRDWIMPY, 5, 84);
		break;
	case MT_MINOTAUR:
		P_DropItem(actor, MT_ARTISUPERHEAL, 0, 51);
		P_DropItem(actor, MT_AMPHRDWIMPY, 10, 84);
		break;
	default:
		break;
	}
}

//----------------------------------------------------------------------------
//
// PROC A_Explode
//
// Handles a bunch of exploding things.
//
//----------------------------------------------------------------------------

void A_Explode(mobj_t *actor)
{
	int damage;

	damage = 128;
	switch (actor->type)
	{
	case MT_FIREBOMB: // Time Bombs
		actor->z += 32*FRACUNIT;
		actor->flags &= ~MF_SHADOW;
		break;
	case MT_MNTRFX2: // Minotaur floor fire
		damage = 24;
		break;
	case MT_SOR2FX1: // D'Sparil missile
		damage = 80 + (P_Random() & 31);
		break;
	default:
		break;
	}
	P_RadiusAttack(actor, actor->target, damage);
	P_HitFloor(actor);
}

//----------------------------------------------------------------------------
//
// PROC A_PodPain
//
//----------------------------------------------------------------------------

void A_PodPain(mobj_t *actor)
{
	int i;
	int count;
	int chance;
	mobj_t *goo;

	chance = P_Random();
	if (chance < 128)
	{
		return;
	}
	count = chance > 240 ? 2 : 1;
	for (i = 0; i < count; i++)
	{
		goo = P_SpawnMobj(actor->x, actor->y,
				  actor->z + 48*FRACUNIT, MT_PODGOO);
		goo->target = actor;
		goo->momx = (P_Random2())<<9;
		goo->momy = (P_Random2())<<9;
		goo->momz = FRACUNIT/2 + (P_Random()<<9);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_RemovePod
//
//----------------------------------------------------------------------------

void A_RemovePod(mobj_t *actor)
{
	mobj_t *mo;

	if (actor->special2)
	{
		mo = (mobj_t *)actor->special2;
		if (mo->special1 > 0)
		{
			mo->special1--;
		}
	}
}

//----------------------------------------------------------------------------
//
// PROC A_MakePod
//
//----------------------------------------------------------------------------

#define MAX_GEN_PODS		16

void A_MakePod(mobj_t *actor)
{
	mobj_t *mo;
	fixed_t x;
	fixed_t y;

	if (actor->special1 == MAX_GEN_PODS)
	{ // Too many generated pods
		return;
	}
	x = actor->x;
	y = actor->y;
	mo = P_SpawnMobj(x, y, ONFLOORZ, MT_POD);
	if (P_CheckPosition(mo, x, y) == false)
	{ // Didn't fit
		P_RemoveMobj(mo);
		return;
	}
	P_SetMobjState(mo, S_POD_GROW1);
	P_ThrustMobj(mo, P_Random()<<24, (fixed_t)(4.5*FRACUNIT));
	S_StartSound(mo, sfx_newpod);
	actor->special1++; // Increment generated pod count
	mo->special2 = (intptr_t)actor; // Link the generator to the pod
	return;
}

//----------------------------------------------------------------------------
//
// PROC P_Massacre
//
// Kills all monsters.
//
//----------------------------------------------------------------------------

void P_Massacre(void)
{
	mobj_t *mo;
	thinker_t *think;

	for (think = thinkercap.next; think != &thinkercap;
					think = think->next)
	{
		if (think->function != P_MobjThinker)
		{ // Not a mobj thinker
			continue;
		}
		mo = (mobj_t *)think;
		if ((mo->flags & MF_COUNTKILL) && (mo->health > 0))
		{
			P_DamageMobj(mo, NULL, NULL, 10000);
		}
	}
}

//----------------------------------------------------------------------------
//
// PROC A_BossDeath
//
// Trigger special effects if all bosses are dead.
//
//----------------------------------------------------------------------------

void A_BossDeath(mobj_t *actor)
{
	mobj_t *mo;
	thinker_t *think;
	line_t dummyLine;
	static mobjtype_t bossType[6] =
	{
		MT_HEAD,
		MT_MINOTAUR,
		MT_SORCERER2,
		MT_HEAD,
		MT_MINOTAUR,
		-1
	};

	if (gamemap != 8)
	{ // Not a boss level
		return;
	}
	if (actor->type != bossType[gameepisode - 1])
	{ // Not considered a boss in this episode
		return;
	}
	// Make sure all bosses are dead
	for (think = thinkercap.next; think != &thinkercap; think = think->next)
	{
		if (think->function != P_MobjThinker)
		{ // Not a mobj thinker
			continue;
		}
		mo = (mobj_t *)think;
		if ((mo != actor) && (mo->type == actor->type) && (mo->health > 0))
		{ // Found a living boss
			return;
		}
	}
	if (gameepisode > 1)
	{ // Kill any remaining monsters
		P_Massacre();
	}
	dummyLine.tag = 666;
	EV_DoFloor(&dummyLine, lowerFloor);
}

//----------------------------------------------------------------------------
//
// PROC A_ESound
//
//----------------------------------------------------------------------------

void A_ESound(mobj_t *mo)
{
	int sound = 0;

	switch (mo->type)
	{
	case MT_SOUNDWATERFALL:
		sound = sfx_waterfl;
		break;
	case MT_SOUNDWIND:
		sound = sfx_wind;
		break;
	default:
		break;
	}
	S_StartSound(mo, sound);
}

//----------------------------------------------------------------------------
//
// PROC A_SpawnTeleGlitter
//
//----------------------------------------------------------------------------

void A_SpawnTeleGlitter(mobj_t *actor)
{
	mobj_t *mo;
	int a, b;

	b = ((P_Random() & 31) - 16) * FRACUNIT;
	a = ((P_Random() & 31) - 16) * FRACUNIT;
	mo = P_SpawnMobj(actor->x + a,
			 actor->y + b,
			 actor->subsector->sector->floorheight, MT_TELEGLITTER);
	mo->momz = FRACUNIT/4;
}

//----------------------------------------------------------------------------
//
// PROC A_SpawnTeleGlitter2
//
//----------------------------------------------------------------------------

void A_SpawnTeleGlitter2(mobj_t *actor)
{
	mobj_t *mo;

	mo = P_SpawnMobj(actor->x + ((P_Random() & 31) - 16) * FRACUNIT,
			 actor->y + ((P_Random() & 31) - 16) * FRACUNIT,
			 actor->subsector->sector->floorheight, MT_TELEGLITTER2);
	mo->momz = FRACUNIT/4;
}

//----------------------------------------------------------------------------
//
// PROC A_AccTeleGlitter
//
//----------------------------------------------------------------------------

void A_AccTeleGlitter(mobj_t *actor)
{
	if (++actor->health > 35)
	{
		actor->momz += actor->momz/2;
	}
}

//----------------------------------------------------------------------------
//
// PROC A_InitKeyGizmo
//
//----------------------------------------------------------------------------

void A_InitKeyGizmo(mobj_t *gizmo)
{
	mobj_t *mo;
	statenum_t state = 0;

	switch (gizmo->type)
	{
	case MT_KEYGIZMOBLUE:
		state = S_KGZ_BLUEFLOAT1;
		break;
	case MT_KEYGIZMOGREEN:
		state = S_KGZ_GREENFLOAT1;
		break;
	case MT_KEYGIZMOYELLOW:
		state = S_KGZ_YELLOWFLOAT1;
		break;
	default:
		break;
	}
	mo = P_SpawnMobj(gizmo->x, gizmo->y, gizmo->z + 60*FRACUNIT,
						MT_KEYGIZMOFLOAT);
	P_SetMobjState(mo, state);
}

//----------------------------------------------------------------------------
//
// PROC A_VolcanoSet
//
//----------------------------------------------------------------------------

void A_VolcanoSet(mobj_t *volcano)
{
	volcano->tics = 105 + (P_Random() & 127);
}

//----------------------------------------------------------------------------
//
// PROC A_VolcanoBlast
//
//----------------------------------------------------------------------------

void A_VolcanoBlast(mobj_t *volcano)
{
	int i;
	int count;
	mobj_t *blast;
	angle_t angle;

	count = 1 + (P_Random() % 3);
	for (i = 0; i < count; i++)
	{
		blast = P_SpawnMobj(volcano->x, volcano->y,
				    volcano->z + 44*FRACUNIT,
				    MT_VOLCANOBLAST);
		blast->target = volcano;
		angle = P_Random()<<24;
		blast->angle = angle;
		angle >>= ANGLETOFINESHIFT;
		blast->momx = FixedMul(1*FRACUNIT, finecosine[angle]);
		blast->momy = FixedMul(1*FRACUNIT, finesine[angle]);
		blast->momz = (2.5*FRACUNIT)+(P_Random()<<10);
		S_StartSound(blast, sfx_volsht);
		P_CheckMissileSpawn(blast);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_VolcBallImpact
//
//----------------------------------------------------------------------------

void A_VolcBallImpact(mobj_t *ball)
{
	unsigned int i;
	mobj_t *tiny;
	angle_t angle;

	if (ball->z <= ball->floorz)
	{
		ball->flags |= MF_NOGRAVITY;
		ball->flags2 &= ~MF2_LOGRAV;
		ball->z += 28*FRACUNIT;
		//ball->momz = 3*FRACUNIT;
	}
	P_RadiusAttack(ball, ball->target, 25);
	for (i = 0; i < 4; i++)
	{
		tiny = P_SpawnMobj(ball->x, ball->y, ball->z, MT_VOLCANOTBLAST);
		tiny->target = ball;
		angle = i*ANG90;
		tiny->angle = angle;
		angle >>= ANGLETOFINESHIFT;
		tiny->momx = FixedMul(FRACUNIT*.7, finecosine[angle]);
		tiny->momy = FixedMul(FRACUNIT*.7, finesine[angle]);
		tiny->momz = FRACUNIT + (P_Random()<<9);
		P_CheckMissileSpawn(tiny);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_SkullPop
//
//----------------------------------------------------------------------------

void A_SkullPop(mobj_t *actor)
{
	mobj_t *mo;
	player_t *player;

	actor->flags &= ~MF_SOLID;
	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 48*FRACUNIT,
						MT_BLOODYSKULL);
	//mo->target = actor;
	mo->momx = (P_Random2())<<9;
	mo->momy = (P_Random2())<<9;
	mo->momz = FRACUNIT*2 + (P_Random()<<6);
	// Attach player mobj to bloody skull
	player = actor->player;
	actor->player = NULL;
	mo->player = player;
	mo->health = actor->health;
	mo->angle = actor->angle;
	player->mo = mo;
	player->lookdir = 0;
	player->damagecount = 32;
}

//----------------------------------------------------------------------------
//
// PROC A_CheckSkullFloor
//
//----------------------------------------------------------------------------

void A_CheckSkullFloor(mobj_t *actor)
{
	if (actor->z <= actor->floorz)
	{
		P_SetMobjState(actor, S_BLOODYSKULLX1);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_CheckSkullDone
//
//----------------------------------------------------------------------------

void A_CheckSkullDone(mobj_t *actor)
{
	if (actor->special2 == 666)
	{
		P_SetMobjState(actor, S_BLOODYSKULLX2);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_CheckBurnGone
//
//----------------------------------------------------------------------------

void A_CheckBurnGone(mobj_t *actor)
{
	if (actor->special2 == 666)
	{
		P_SetMobjState(actor, S_PLAY_FDTH20);
	}
}

//----------------------------------------------------------------------------
//
// PROC A_FreeTargMobj
//
//----------------------------------------------------------------------------

void A_FreeTargMobj(mobj_t *mo)
{
	mo->momx = mo->momy = mo->momz = 0;
	mo->z = mo->ceilingz+4*FRACUNIT;
	mo->flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SKULLFLY|MF_SOLID);
	mo->flags |= MF_CORPSE|MF_DROPOFF|MF_NOGRAVITY;
	mo->flags2 &= ~(MF2_PASSMOBJ|MF2_LOGRAV);
	mo->player = NULL;
}

//----------------------------------------------------------------------------
//
// PROC A_AddPlayerCorpse
//
//----------------------------------------------------------------------------

#define BODYQUESIZE		32
static mobj_t	*bodyque[BODYQUESIZE];
int			bodyqueslot;

void A_AddPlayerCorpse(mobj_t *actor)
{
	if (bodyqueslot >= BODYQUESIZE)
	{ // Too many player corpses - remove an old one
		P_RemoveMobj(bodyque[bodyqueslot % BODYQUESIZE]);
	}
	bodyque[bodyqueslot % BODYQUESIZE] = actor;
	bodyqueslot++;
}

//----------------------------------------------------------------------------
//
// PROC A_FlameSnd
//
//----------------------------------------------------------------------------

void A_FlameSnd(mobj_t *actor)
{
	S_StartSound(actor, sfx_hedat1); // Burn sound
}

//----------------------------------------------------------------------------
//
// PROC A_HideThing
//
//----------------------------------------------------------------------------

void A_HideThing(mobj_t *actor)
{
	//P_UnsetThingPosition(actor);
	actor->flags2 |= MF2_DONTDRAW;
}

//----------------------------------------------------------------------------
//
// PROC A_UnHideThing
//
//----------------------------------------------------------------------------

void A_UnHideThing(mobj_t *actor)
{
	//P_SetThingPosition(actor);
	actor->flags2 &= ~MF2_DONTDRAW;
}