shithub: hexen

ref: b29e042630023016ae6b293904ac34bf285bd19c
dir: /p_enemy.c/

View raw version

//**************************************************************************
//**
//** p_enemy.c : Heretic 2 : Raven Software, Corp.
//**
//** $Revision: 596 $
//** $Date: 2013-03-16 02:24:28 +0200 (Sat, 16 Mar 2013) $
//**
//**************************************************************************

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

// Macros
// Types
// Private Data
static mobj_t	*soundtarget;
// External Data
extern fixed_t	FloatBobOffsets[64];


//----------------------------------------------------------------------------
//
// 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_CheckMeleeRange2
//
//----------------------------------------------------------------------------

static boolean P_CheckMeleeRange2(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*2 || 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 (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->flags2 & MF2_BLASTED)
		return true;
	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_ActivateLine(ld, actor, 0, SPAC_USE))
			{
				good = true;
			}
/* Old version before use/cross/impact specials were combined
			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		(16 * 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;
		}
		if (actor->type == MT_MINOTAUR)
		{
			if ((mo->type == MT_MINOTAUR) && 
				(mo->target != ((player_t *)actor->special1)->mo))
			{
				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;

	if (!netgame && players[0].health <= 0)
	{ // Single player game and player is dead, look for monsters
		return(P_LookForMonsters(actor));
	}
	c = 0;
	// O.S. - lastlook mask was 3 here, but it should have been
	// a leftover from hexen-1.0 where MAXPLAYERS was 4..
	// also see p_mobj.c :: P_SpawnMobj() where it is set.
	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;
			}
		}
		if (actor->type == MT_MINOTAUR)
		{
			if (((player_t *)(actor->special1)) == player)
			{
				continue;			// Don't target master
			}
		}

		actor->target = player->mo;
		return true;
	}
}

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

						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;

		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_BISHOP && P_Random() < 128)
		{
			S_StartSound(actor, actor->info->seesound);
		}
		else if (actor->type == MT_PIG)
		{
			S_StartSound(actor, SFX_PIG_ACTIVE1 + (P_Random() & 1));
		}
		else if (actor->flags2 & MF2_BOSS)
		{
			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_Random() - P_Random()) << 21;
	}
}

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

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

//============================================================================
//
// A_SetInvulnerable
//
//============================================================================

void A_SetInvulnerable(mobj_t *actor)
{
	actor->flags2 |= MF2_INVULNERABLE;
}

//============================================================================
//
// A_UnSetInvulnerable
//
//============================================================================

void A_UnSetInvulnerable(mobj_t *actor)
{
	actor->flags2 &= ~MF2_INVULNERABLE;
}

//============================================================================
//
// A_SetReflective
//
//============================================================================

void A_SetReflective(mobj_t *actor)
{
	actor->flags2 |= MF2_REFLECTIVE;

	if ((actor->type == MT_CENTAUR) ||
		(actor->type == MT_CENTAURLEADER))
	{
		A_SetInvulnerable(actor);
	}
}

//============================================================================
//
// A_UnSetReflective
//
//============================================================================

void A_UnSetReflective(mobj_t *actor)
{
	actor->flags2 &= ~MF2_REFLECTIVE;

	if ((actor->type == MT_CENTAUR) ||
		(actor->type == MT_CENTAURLEADER))
	{
		A_UnSetInvulnerable(actor);
	}
}


//----------------------------------------------------------------------------
//
// FUNC P_UpdateMorphedMonster
//
// Returns true if the pig morphs.
//
//----------------------------------------------------------------------------

static boolean P_UpdateMorphedMonster(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 oldMonster;

	actor->special1 -= tics;
	if (actor->special1 > 0)
	{
		return false;
	}
	moType = actor->special2;
	switch (moType)
	{
	case MT_WRAITHB:			// These must remain morphed
	case MT_SERPENT:
	case MT_SERPENTLEADER:
	case MT_MINOTAUR:
		return false;
	default:
		break;
	}
	x = actor->x;
	y = actor->y;
	z = actor->z;
	oldMonster = *actor;			// Save pig vars

	P_RemoveMobjFromTIDList(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, oldMonster.type);
		mo->angle = oldMonster.angle;
		mo->flags = oldMonster.flags;
		mo->health = oldMonster.health;
		mo->target = oldMonster.target;
		mo->special = oldMonster.special;
		mo->special1 = 5*35; // Next try in 5 seconds
		mo->special2 = moType;
		mo->tid = oldMonster.tid;
		memcpy(mo->args, oldMonster.args, 5);
		P_InsertMobjIntoTIDList(mo, oldMonster.tid);
		return false;
	}
	mo->angle = oldMonster.angle;
	mo->target = oldMonster.target;
	mo->tid = oldMonster.tid;
	mo->special = oldMonster.special;
	memcpy(mo->args, oldMonster.args, 5);
	P_InsertMobjIntoTIDList(mo, oldMonster.tid);
	fog = P_SpawnMobj(x, y, z + TELEFOGHEIGHT, MT_TFOG);
	S_StartSound(fog, SFX_TELEPORT);
	return true;
}

//----------------------------------------------------------------------------
//
// PROC A_PigLook
//
//----------------------------------------------------------------------------

void A_PigLook(mobj_t *actor)
{
	if (P_UpdateMorphedMonster(actor, 10))
	{
		return;
	}
	A_Look(actor);
}

//----------------------------------------------------------------------------
//
// PROC A_PigChase
//
//----------------------------------------------------------------------------

void A_PigChase(mobj_t *actor)
{
	if (P_UpdateMorphedMonster(actor, 3))
	{
		return;
	}
	A_Chase(actor);
}

//============================================================================
//
// A_PigAttack
//
//============================================================================

void A_PigAttack(mobj_t *actor)
{
	if (P_UpdateMorphedMonster(actor, 18))
	{
		return;
	}
	if (!actor->target)
	{
		return;
	}
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, 2+(P_Random() & 1));
		S_StartSound(actor, SFX_PIG_ATTACK);
	}
}

//============================================================================
//
// A_PigPain
//
//============================================================================

void A_PigPain(mobj_t *actor)
{
	A_Pain(actor);
	if (actor->z <= actor->floorz)
	{
		actor->momz = 3.5*FRACUNIT;
	}
}


static void FaceMovementDirection(mobj_t *actor)
{
	switch (actor->movedir)
	{
	case DI_EAST:
		actor->angle = 0<<24;
		break;
	case DI_NORTHEAST:
		actor->angle = 32<<24;
		break;
	case DI_NORTH:
		actor->angle = 64<<24;
		break;
	case DI_NORTHWEST:
		actor->angle = 96<<24;
		break;
	case DI_WEST:
		actor->angle = 128<<24;
		break;
	case DI_SOUTHWEST:
		actor->angle = 160<<24;
		break;
	case DI_SOUTH:
		actor->angle = 192<<24;
		break;
	case DI_SOUTHEAST:
		actor->angle = 224<<24;
		break;
	}
}


//----------------------------------------------------------------------------
//
// Minotaur variables
//
//	special1		pointer to player that spawned it (mobj_t)
//	special2		internal to minotaur AI
//	args[0]			args[0]-args[3] together make up minotaur start time
//	args[1]			|
//	args[2]			|
//	args[3]			V
//	args[4]			charge duration countdown
//----------------------------------------------------------------------------

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

void A_MinotaurFade1(mobj_t *actor)
{
	// Second level of transparency
	actor->flags &= ~MF_SHADOW;
	actor->flags |= MF_ALTSHADOW;
}

void A_MinotaurFade2(mobj_t *actor)
{
	// Make fully visible
	actor->flags &= ~MF_SHADOW;
	actor->flags &= ~MF_ALTSHADOW;
}


//----------------------------------------------------------------------------
//
// A_MinotaurRoam - 
//
//
//----------------------------------------------------------------------------

void A_MinotaurLook(mobj_t *actor);

void A_MinotaurRoam(mobj_t *actor)
{
	int	summontime;

	actor->flags &= ~MF_SHADOW;	// In case pain caused him to 
	actor->flags &= ~MF_ALTSHADOW;		// skip his fade in.

	summontime = READ_INT32(actor->args);
	if ((leveltime - summontime) >= MAULATORTICS)
	{
		P_DamageMobj(actor, NULL, NULL, 10000);
		return;
	}

	if (P_Random() < 30)
		A_MinotaurLook(actor);		// adjust to closest target

	if (P_Random() < 6)
	{
		//Choose new direction
		actor->movedir = P_Random() % 8;
		FaceMovementDirection(actor);
	}
	if (!P_Move(actor))
	{
		// Turn
		if (P_Random() & 1)
		{
		//	actor->movedir = (++actor->movedir) % 8;
			++actor->movedir;
			actor->movedir = actor->movedir % 8;
		}
		else
		{
			actor->movedir = (actor->movedir + 7) % 8;
		}
		FaceMovementDirection(actor);
	}
}

//----------------------------------------------------------------------------
//
//	PROC A_MinotaurLook
//
// Look for enemy of player
//----------------------------------------------------------------------------
#define MINOTAUR_LOOK_DIST		(16 * 54 * FRACUNIT)

void A_MinotaurLook(mobj_t *actor)
{
	mobj_t *mo;
	player_t *player;
	thinker_t *think;
	fixed_t dist;
	int i;
	mobj_t *master = (mobj_t *)(actor->special1);

	actor->target = NULL;
	if (deathmatch)		// Quick search for players
	{
		for (i = 0; i < MAXPLAYERS; i++)
		{
			if (!playeringame[i])
				continue;
			player = &players[i];
			mo = player->mo;
			if (mo == master)
				continue;
			if (mo->health <= 0)
				continue;
			dist = P_AproxDistance(actor->x - mo->x, actor->y - mo->y);
			if (dist > MINOTAUR_LOOK_DIST)
				continue;
			actor->target = mo;
			break;
		}
	}

	if (!actor->target)	// Near player monster search
	{
		if (master && (master->health > 0) && (master->player))
			mo = P_RoughMonsterSearch(master, 20);
		else
			mo = P_RoughMonsterSearch(actor, 20);
		actor->target = mo;
	}

	if (!actor->target)	// Normal monster search
	{
		for (think = thinkercap.next; think != &thinkercap; think = think->next)
		{
			if (think->function != P_MobjThinker)
				continue;
			mo = (mobj_t *)think;
			if (!(mo->flags & MF_COUNTKILL))
				continue;
			if (mo->health <= 0)
				continue;
			if (!(mo->flags & MF_SHOOTABLE))
				continue;
			dist = P_AproxDistance(actor->x - mo->x, actor->y - mo->y);
			if (dist > MINOTAUR_LOOK_DIST)
				continue;
			if ((mo == master) || (mo == actor))
				continue;
			if ((mo->type == MT_MINOTAUR) && (mo->special1 == actor->special1))
				continue;
			actor->target = mo;
			break;	// Found mobj to attack
		}
	}

	if (actor->target)
	{
		P_SetMobjStateNF(actor, S_MNTR_WALK1);
	}
	else
	{
		P_SetMobjStateNF(actor, S_MNTR_ROAM1);
	}
}


void A_MinotaurChase(mobj_t *actor)
{
	int	summontime;

	actor->flags &= ~MF_SHADOW;	// In case pain caused him to 
	actor->flags &= ~MF_ALTSHADOW;		// skip his fade in.

	summontime = READ_INT32(actor->args);
	if ((leveltime - summontime) >= MAULATORTICS)
	{
		P_DamageMobj(actor, NULL, NULL, 10000);
		return;
	}

	if (P_Random() < 30)
		A_MinotaurLook(actor);		// adjust to closest target

	if (!actor->target || (actor->target->health <= 0) ||
			!(actor->target->flags&MF_SHOOTABLE))
	{ // look for a new target
		P_SetMobjState(actor, S_MNTR_LOOK1);
		return;
	}

	FaceMovementDirection(actor);
	actor->reactiontime = 0;

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

	// Missile attack
	if (actor->info->missilestate && P_CheckMissileRange(actor))
	{
		P_SetMobjState (actor, actor->info->missilestate);
		return;
	}

	// chase towards target
	if (!P_Move(actor))
	{
		P_NewChaseDir(actor);
	}

	// Active sound
	if (actor->info->activesound && P_Random() < 6)
	{
		S_StartSound(actor, actor->info->activesound);
	}
}


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

void A_MinotaurAtk1(mobj_t *actor)
{
	if (!actor->target)
		return;

	S_StartSound(actor, SFX_MAULATOR_HAMMER_SWING);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(4));
	}
}

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

#define MNTR_CHARGE_SPEED	(23 * FRACUNIT)

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

	if (!target)
		return;
	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 < 16*64*FRACUNIT
		&& dist > 1*64*FRACUNIT
		&& P_Random() < 230)
	{ // 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->args[4] = 35/2; // Charge duration
	}
	else if (target->z == target->floorz
		&& dist < 9*64*FRACUNIT
		&& P_Random() < 100)
	{ // 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->target)
		return;

	if (actor->args[4] > 0)
	{
		puff = P_SpawnMobj(actor->x, actor->y, actor->z, MT_PUNCHPUFF);
		puff->momz = 2*FRACUNIT;
		actor->args[4]--;
	}
	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_MAULATOR_HAMMER_SWING);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(3));
		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(3));
		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_MAULATOR_HAMMER_HIT);
		}
	}
	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;

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


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

void A_Scream(mobj_t *actor)
{
	int sound;

	S_StopSound(actor);
	if (actor->player)
	{
		if (actor->player->morphTics)
		{
			S_StartSound(actor, actor->info->deathsound);
		}
		else
		{
			// Handle the different player death screams
			if (actor->momz <= -39*FRACUNIT)
			{ // Falling splat
				sound = SFX_PLAYER_FALLING_SPLAT;
			}
			else if (actor->health > -50)
			{ // Normal death sound
				switch (actor->player->playerclass)
				{
				case PCLASS_FIGHTER:
					sound = SFX_PLAYER_FIGHTER_NORMAL_DEATH;
					break;
				case PCLASS_CLERIC:
					sound = SFX_PLAYER_CLERIC_NORMAL_DEATH;
					break;
				case PCLASS_MAGE:
					sound = SFX_PLAYER_MAGE_NORMAL_DEATH;
					break;
				default:
					sound = SFX_NONE;
					break;
				}
			}
			else if (actor->health > -100)
			{ // Crazy death sound
				switch (actor->player->playerclass)
				{
				case PCLASS_FIGHTER:
					sound = SFX_PLAYER_FIGHTER_CRAZY_DEATH;
					break;
				case PCLASS_CLERIC:
					sound = SFX_PLAYER_CLERIC_CRAZY_DEATH;
					break;
				case PCLASS_MAGE:
					sound = SFX_PLAYER_MAGE_CRAZY_DEATH;
					break;
				default:
					sound = SFX_NONE;
					break;
				}
			}
			else
			{ // Extreme death sound
				switch (actor->player->playerclass)
				{
				case PCLASS_FIGHTER:
					sound = SFX_PLAYER_FIGHTER_EXTREME1_DEATH;
					break;
				case PCLASS_CLERIC:
					sound = SFX_PLAYER_CLERIC_EXTREME1_DEATH;
					break;
				case PCLASS_MAGE:
					sound = SFX_PLAYER_MAGE_EXTREME1_DEATH;
					break;
				default:
					sound = SFX_NONE;
					break;
				}
				sound += P_Random() % 3; // Three different extreme deaths
			}
			S_StartSound(actor, sound);
		}
	}
	else
	{
		S_StartSound(actor, actor->info->deathsound);
	}
}

//---------------------------------------------------------------------------
//
// 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_Random() - P_Random()) << 8;
	mo->momy = (P_Random() - P_Random()) << 8;
	mo->momz = FRACUNIT*5 + (P_Random() << 10);
	mo->flags2 |= MF2_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)
	{
	// Add the monster dropped items here
	case MT_MUMMYLEADERGHOST:
		P_DropItem(actor, MT_AMGWNDWIMPY, 3, 84);
		break;
	default:
		break;
	}
*/
}

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

void A_Explode(mobj_t *actor)
{
	int damage;
	int distance;
	boolean damageSelf;

	damage = 128;
	distance = 128;
	damageSelf = true;
	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_BISHOP:		// Bishop radius death
		damage = 25 + (P_Random() & 15);
		break;
	case MT_HAMMER_MISSILE:	// Fighter Hammer
		damage = 128;
		damageSelf = false;
		break;
	case MT_FSWORD_MISSILE:	// Fighter Runesword
		damage = 64;
		damageSelf = false;
		break;
	case MT_CIRCLEFLAME:	// Cleric Flame secondary flames
		damage = 20;
		damageSelf = false;
		break;
	case MT_SORCBALL1:	// Sorcerer balls
	case MT_SORCBALL2:
	case MT_SORCBALL3:
		distance = 255;
		damage = 255;
		actor->args[0] = 1;	// don't play bounce
		break;
	case MT_SORCFX1:	// Sorcerer spell 1
		damage = 30;
		break;
	case MT_SORCFX4:	// Sorcerer spell 4
		damage = 20;
		break;
	case MT_TREEDESTRUCTIBLE:
		damage = 10;
		break;
	case MT_DRAGON_FX2:
		damage = 80;
		damageSelf = false;
		break;
	case MT_MSTAFF_FX:
		damage = 64;
		distance = 192;
		damageSelf = false;
		break;
	case MT_MSTAFF_FX2:
		damage = 80;
		distance = 192;
		damageSelf = false;
		break;
	case MT_POISONCLOUD:
		damage = 4;
		distance = 40;
		break;
	case MT_ZXMAS_TREE:
	case MT_ZSHRUB2:
		damage = 30;
		distance = 64;
		break;
	default:
		break;
	}

	P_RadiusAttack(actor, actor->target, damage, distance, damageSelf);
	if (actor->z <= actor->floorz + (distance<<FRACBITS)
				&& actor->type != MT_POISONCLOUD)
	{
 		P_HitFloor(actor);
	}
}

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

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

	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->health > 0))
		{
			mo->flags2 &= ~(MF2_NONSHOOTABLE+MF2_INVULNERABLE);
			mo->flags |= MF_SHOOTABLE;
			P_DamageMobj(mo, NULL, NULL, 10000);
			count++;
		}
	}
	return count;
}


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

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

	if (!actor->player)
	{
		return;
	}
	actor->flags &= ~MF_SOLID;
	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 48*FRACUNIT, MT_BLOODYSKULL);
	//mo->target = actor;
	mo->momx = (P_Random() - P_Random()) << 9;
	mo->momy = (P_Random() - P_Random()) << 9;
	mo->momz = FRACUNIT*2 + (P_Random() << 6);
	// Attach player mobj to bloody skull
	player = actor->player;
	actor->player = NULL;
	actor->special1 = player->playerclass;
	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);
		S_StartSound(actor, SFX_DRIP);
	}
}

//----------------------------------------------------------------------------
//
// 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|MF_COUNTKILL);
	mo->flags |= MF_CORPSE|MF_DROPOFF|MF_NOGRAVITY;
	mo->flags2 &= ~(MF2_PASSMOBJ|MF2_LOGRAV);
	mo->flags2 |= MF2_DONTDRAW;
	mo->player = NULL;
	mo->health = -1000;		// Don't resurrect
}


//----------------------------------------------------------------------------
//
// CorpseQueue Routines
//
//----------------------------------------------------------------------------

// Corpse queue for monsters - this should be saved out
#define CORPSEQUEUESIZE			64
static mobj_t	*corpseQueue[CORPSEQUEUESIZE];
static int	corpseQueueSlot;

// throw another corpse on the queue
void A_QueueCorpse(mobj_t *actor)
{
	mobj_t *corpse;

	if (corpseQueueSlot >= CORPSEQUEUESIZE)
	{ // Too many corpses - remove an old one
		corpse = corpseQueue[corpseQueueSlot % CORPSEQUEUESIZE];
		if (corpse)
			P_RemoveMobj(corpse);
	}
	corpseQueue[corpseQueueSlot % CORPSEQUEUESIZE] = actor;
	corpseQueueSlot++;
}

// Remove a mobj from the queue (for resurrection)
void A_DeQueueCorpse(mobj_t *actor)
{
	int slot;

	for (slot = 0; slot < CORPSEQUEUESIZE; slot++)
	{
		if (corpseQueue[slot] == actor)
		{
			corpseQueue[slot] = NULL;
			break;
		}
	}
}

void P_InitCreatureCorpseQueue(boolean corpseScan)
{
	thinker_t *think;
	mobj_t *mo;

	// Initialize queue
	corpseQueueSlot = 0;
	memset(corpseQueue, 0, sizeof(mobj_t *)*CORPSEQUEUESIZE);

	if (!corpseScan)
		return;

	// Search mobj list for corpses and place them in this queue
	for (think = thinkercap.next; think != &thinkercap; think = think->next)
	{
		if (think->function != P_MobjThinker)
			continue;
		mo = (mobj_t *)think;
		if (!(mo->flags & MF_CORPSE))
			continue;	// Must be a corpse
		if (mo->flags & MF_ICECORPSE)
			continue;	// Not ice corpses
		// Only corpses that call A_QueueCorpse from death routine
		switch (mo->type)
		{
		case MT_CENTAUR:
		case MT_CENTAURLEADER:
		case MT_DEMON:
		case MT_DEMON2:
		case MT_WRAITH:
		case MT_WRAITHB:
		case MT_BISHOP:
		case MT_ETTIN:
		case MT_PIG:
		case MT_CENTAUR_SHIELD:
		case MT_CENTAUR_SWORD:
		case MT_DEMONCHUNK1:
		case MT_DEMONCHUNK2:
		case MT_DEMONCHUNK3:
		case MT_DEMONCHUNK4:
		case MT_DEMONCHUNK5:
		case MT_DEMON2CHUNK1:
		case MT_DEMON2CHUNK2:
		case MT_DEMON2CHUNK3:
		case MT_DEMON2CHUNK4:
		case MT_DEMON2CHUNK5:
		case MT_FIREDEMON_SPLOTCH1:
		case MT_FIREDEMON_SPLOTCH2:
			A_QueueCorpse(mo);	// Add corpse to queue
			break;
		default:
			break;
		}
	}
}


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

//============================================================================
//
// A_SerpentUnHide
//
//============================================================================

void A_SerpentUnHide(mobj_t *actor)
{
	actor->flags2 &= ~MF2_DONTDRAW;
	actor->floorclip = 24*FRACUNIT;
}

//============================================================================
//
// A_SerpentHide
//
//============================================================================

void A_SerpentHide(mobj_t *actor)
{
	actor->flags2 |= MF2_DONTDRAW;
	actor->floorclip = 0;
}

//============================================================================
//
// A_SerpentChase
//
//============================================================================

void A_SerpentChase(mobj_t *actor)
{
	int delta;
	int oldX, oldY, oldFloor;

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

//
// 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
//
	oldX = actor->x;
	oldY = actor->y;
	oldFloor = actor->subsector->sector->floorpic;
	if (--actor->movecount < 0 || !P_Move(actor))
	{
		P_NewChaseDir (actor);
	}
	if (actor->subsector->sector->floorpic != oldFloor)
	{
		P_TryMove(actor, oldX, oldY);
		P_NewChaseDir (actor);
	}

//
// make active sound
//
	if (actor->info->activesound && P_Random() < 3)
	{
		S_StartSound(actor, actor->info->activesound);
	}
}

//============================================================================
//
// A_SerpentRaiseHump
//
// Raises the hump above the surface by raising the floorclip level
//============================================================================

void A_SerpentRaiseHump(mobj_t *actor)
{
	actor->floorclip -= 4*FRACUNIT;
}

//============================================================================
//
// A_SerpentLowerHump
//
//============================================================================

void A_SerpentLowerHump(mobj_t *actor)
{
	actor->floorclip += 4*FRACUNIT;
}

//============================================================================
//
// A_SerpentHumpDecide
//
//	Decided whether to hump up, or if the mobj is a serpent leader,
//	to missile attack
//============================================================================

void A_SerpentHumpDecide(mobj_t *actor)
{
	if (actor->type == MT_SERPENTLEADER)
	{
		if (P_Random() > 30)
		{
			return;
		}
		else if (P_Random() < 40)
		{ // Missile attack
			P_SetMobjState(actor, S_SERPENT_SURFACE1);
			return;
		}
	}
	else if (P_Random() > 3)
	{
		return;
	}
	if (!P_CheckMeleeRange(actor))
	{ // The hump shouldn't occur when within melee range
		if (actor->type == MT_SERPENTLEADER && P_Random() < 128)
		{
			P_SetMobjState(actor, S_SERPENT_SURFACE1);
		}
		else
		{
			P_SetMobjState(actor, S_SERPENT_HUMP1);
			S_StartSound(actor, SFX_SERPENT_ACTIVE);
		}
	}
}

//============================================================================
//
// A_SerpentBirthScream
//
//============================================================================

void A_SerpentBirthScream(mobj_t *actor)
{
	S_StartSound(actor, SFX_SERPENT_BIRTH);
}

//============================================================================
//
// A_SerpentDiveSound
//
//============================================================================

void A_SerpentDiveSound(mobj_t *actor)
{
	S_StartSound(actor, SFX_SERPENT_ACTIVE);
}

//============================================================================
//
// A_SerpentWalk
//
// Similar to A_Chase, only has a hardcoded entering of meleestate
//============================================================================

void A_SerpentWalk(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, S_SERPENT_ATK1);
		return;
	}
//
// 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);
	}
}

//============================================================================
//
// A_SerpentCheckForAttack
//
//============================================================================

void A_SerpentCheckForAttack(mobj_t *actor)
{
	if (!actor->target)
	{
		return;
	}
	if (actor->type == MT_SERPENTLEADER)
	{
		if (!P_CheckMeleeRange(actor))
		{
			P_SetMobjState(actor, S_SERPENT_ATK1);
			return;
		}
	}
	if (P_CheckMeleeRange2(actor))
	{
		P_SetMobjState(actor, S_SERPENT_WALK1);
	}
	else if (P_CheckMeleeRange(actor))
	{
		if (P_Random() < 32)
		{
			P_SetMobjState(actor, S_SERPENT_WALK1);
		}
		else
		{
			P_SetMobjState(actor, S_SERPENT_ATK1);
		}
	}
}

//============================================================================
//
// A_SerpentChooseAttack
//
//============================================================================

void A_SerpentChooseAttack(mobj_t *actor)
{
	if (!actor->target || P_CheckMeleeRange(actor))
	{
		return;
	}
	if (actor->type == MT_SERPENTLEADER)
	{
		P_SetMobjState(actor, S_SERPENT_MISSILE1);
	}
}

//============================================================================
//
// A_SerpentMeleeAttack
//
//============================================================================

void A_SerpentMeleeAttack(mobj_t *actor)
{
	if (!actor->target)
	{
		return;
	}
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(5));
		S_StartSound(actor, SFX_SERPENT_MELEEHIT);
	}
	if (P_Random() < 96)
	{
		A_SerpentCheckForAttack(actor);
	}
}

//============================================================================
//
// A_SerpentMissileAttack
//
//============================================================================

void A_SerpentMissileAttack(mobj_t *actor)
{
	if (!actor->target)
	{
		return;
	}
	P_SpawnMissile(actor, actor->target, MT_SERPENTFX);
}

//============================================================================
//
// A_SerpentHeadPop
//
//============================================================================

void A_SerpentHeadPop(mobj_t *actor)
{
	P_SpawnMobj(actor->x, actor->y, actor->z + 45*FRACUNIT, MT_SERPENT_HEAD);
}

//============================================================================
//
// A_SerpentSpawnGibs
//
//============================================================================

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

	mo = P_SpawnMobj(actor->x + ((P_Random() - 128) << 12),
			 actor->y + ((P_Random() - 128) << 12),
			 actor->floorz + FRACUNIT, MT_SERPENT_GIB1);
	if (mo)
	{
		mo->momx = (P_Random() - 128) << 6;
		mo->momy = (P_Random() - 128) << 6;
		mo->floorclip = 6*FRACUNIT;
	}
	mo = P_SpawnMobj(actor->x + ((P_Random() - 128) << 12),
			 actor->y + ((P_Random() - 128) << 12),
			 actor->floorz + FRACUNIT, MT_SERPENT_GIB2);
	if (mo)
	{
		mo->momx = (P_Random() - 128) << 6;
		mo->momy = (P_Random() - 128) << 6;
		mo->floorclip = 6*FRACUNIT;
	}
	mo = P_SpawnMobj(actor->x + ((P_Random() - 128) << 12),
			 actor->y + ((P_Random() - 128) << 12),
			 actor->floorz + FRACUNIT, MT_SERPENT_GIB3);
	if (mo)
	{
		mo->momx = (P_Random() - 128) << 6;
		mo->momy = (P_Random() - 128) << 6;
		mo->floorclip = 6*FRACUNIT;
	}
}

//============================================================================
//
// A_FloatGib
//
//============================================================================

void A_FloatGib(mobj_t *actor)
{
	actor->floorclip -= FRACUNIT;
}

//============================================================================
//
// A_SinkGib
//
//============================================================================

void A_SinkGib(mobj_t *actor)
{
	actor->floorclip += FRACUNIT;
}

//============================================================================
//
// A_DelayGib
//
//============================================================================

void A_DelayGib(mobj_t *actor)
{
	actor->tics -= P_Random()>>2;
}

//============================================================================
//
// A_SerpentHeadCheck
//
//============================================================================

void A_SerpentHeadCheck(mobj_t *actor)
{
	if (actor->z <= actor->floorz)
	{
		if (P_GetThingFloorType(actor) >= FLOOR_LIQUID)
		{
			P_HitFloor(actor);
			P_SetMobjState(actor, S_NULL);
		}
		else
		{
			P_SetMobjState(actor, S_SERPENT_HEAD_X1);
		}
	}
}

//============================================================================
//
// A_CentaurAttack
//
//============================================================================

void A_CentaurAttack(mobj_t *actor)
{
	if (!actor->target)
	{
		return;
	}
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, (P_Random() % 7) + 3);
	}
}

//============================================================================
//
// A_CentaurAttack2
//
//============================================================================

void A_CentaurAttack2(mobj_t *actor)
{
	if (!actor->target)
	{
		return;
	}
	P_SpawnMissile(actor, actor->target, MT_CENTAUR_FX);
	S_StartSound(actor, SFX_CENTAURLEADER_ATTACK);
}

//============================================================================
//
// A_CentaurDropStuff
//
// Spawn shield/sword sprites when the centaur pulps
//============================================================================

void A_CentaurDropStuff(mobj_t *actor)
{
	mobj_t *mo;
	angle_t angle;

	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45*FRACUNIT, MT_CENTAUR_SHIELD);
	if (mo)
	{
		angle = actor->angle + ANG90;
		mo->momz = FRACUNIT*8 + (P_Random() << 10);
		mo->momx = FixedMul(((P_Random() - 128) << 11) + FRACUNIT,
				    finecosine[angle >> ANGLETOFINESHIFT]);
		mo->momy = FixedMul(((P_Random() - 128) << 11) + FRACUNIT,
				    finesine[angle >> ANGLETOFINESHIFT]);
		mo->target = actor;
	}
	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45*FRACUNIT, MT_CENTAUR_SWORD);
	if (mo)
	{
		angle = actor->angle - ANG90;
		mo->momz = FRACUNIT*8 + (P_Random() << 10);
		mo->momx = FixedMul(((P_Random() - 128) << 11) + FRACUNIT,
				    finecosine[angle >> ANGLETOFINESHIFT]);
		mo->momy = FixedMul(((P_Random() - 128) << 11) + FRACUNIT,
				    finesine[angle>>ANGLETOFINESHIFT]);
		mo->target = actor;
	}
}

//============================================================================
//
// A_CentaurDefend
//
//============================================================================

void A_CentaurDefend(mobj_t *actor)
{
	A_FaceTarget(actor);
	if (P_CheckMeleeRange(actor) && P_Random() < 32)
	{
		A_UnSetInvulnerable(actor);
		P_SetMobjState(actor, actor->info->meleestate);
	}
}

//============================================================================
//
// A_BishopAttack
//
//============================================================================

void A_BishopAttack(mobj_t *actor)
{
	if (!actor->target)
	{
		return;
	}
	S_StartSound(actor, actor->info->attacksound);
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(4));
		return;
	}
	actor->special1 = (P_Random() & 3) + 5;
}

//============================================================================
//
// A_BishopAttack2
//
//	Spawns one of a string of bishop missiles
//============================================================================

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

	if (!actor->target || !actor->special1)
	{
		actor->special1 = 0;
		P_SetMobjState(actor, S_BISHOP_WALK1);
		return;
	}
	mo = P_SpawnMissile(actor, actor->target, MT_BISH_FX);
	if (mo)
	{
		mo->special1 = (intptr_t)actor->target;
		mo->special2 = 16; // High word == x/y, Low word == z
	}
	actor->special1--;
}

//============================================================================
//
// A_BishopMissileWeave
//
//============================================================================

void A_BishopMissileWeave(mobj_t *actor)
{
	fixed_t newX, newY;
	int weaveXY, weaveZ;
	int angle;

	weaveXY = actor->special2 >> 16;
	weaveZ = actor->special2 & 0xFFFF;
	angle = (actor->angle + ANG90) >> ANGLETOFINESHIFT;
	newX = actor->x - FixedMul(finecosine[angle], FloatBobOffsets[weaveXY]<<1);
	newY = actor->y - FixedMul(finesine[angle], FloatBobOffsets[weaveXY]<<1);
	weaveXY = (weaveXY + 2) & 63;
	newX += FixedMul(finecosine[angle], FloatBobOffsets[weaveXY]<<1);
	newY += FixedMul(finesine[angle], FloatBobOffsets[weaveXY]<<1);
	P_TryMove(actor, newX, newY);
	actor->z -= FloatBobOffsets[weaveZ];
	weaveZ = (weaveZ + 2) & 63;
	actor->z += FloatBobOffsets[weaveZ];
	actor->special2 = weaveZ + (weaveXY<<16);
}

//============================================================================
//
// A_BishopMissileSeek
//
//============================================================================

void A_BishopMissileSeek(mobj_t *actor)
{
	P_SeekerMissile(actor, ANGLE_1*2, ANGLE_1*3);
}

//============================================================================
//
// A_BishopDecide
//
//============================================================================

void A_BishopDecide(mobj_t *actor)
{
	if (P_Random() < 220)
	{
		return;
	}
	else
	{
		P_SetMobjState(actor, S_BISHOP_BLUR1);
	}
}

//============================================================================
//
// A_BishopDoBlur
//
//============================================================================

void A_BishopDoBlur(mobj_t *actor)
{
	actor->special1 = (P_Random() & 3) + 3; // Random number of blurs
	if (P_Random() < 120)
	{
		P_ThrustMobj(actor, actor->angle + ANG90, 11*FRACUNIT);
	}
	else if (P_Random() > 125)
	{
		P_ThrustMobj(actor, actor->angle - ANG90, 11*FRACUNIT);
	}
	else
	{ // Thrust forward
		P_ThrustMobj(actor, actor->angle, 11*FRACUNIT);
	}
	S_StartSound(actor, SFX_BISHOP_BLUR);
}

//============================================================================
//
// A_BishopSpawnBlur
//
//============================================================================

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

	if (!--actor->special1)
	{
		actor->momx = 0;
		actor->momy = 0;
		if (P_Random() > 96)
		{
			P_SetMobjState(actor, S_BISHOP_WALK1);
		}
		else
		{
			P_SetMobjState(actor, S_BISHOP_ATK1);
		}
	}
	mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_BISHOPBLUR);
	if (mo)
	{
		mo->angle = actor->angle;
	}
}

//============================================================================
//
// A_BishopChase
//
//============================================================================

void A_BishopChase(mobj_t *actor)
{
	actor->z -= FloatBobOffsets[actor->special2]>>1;
	actor->special2 = (actor->special2 + 4) & 63;
	actor->z += FloatBobOffsets[actor->special2]>>1;
}

//============================================================================
//
// A_BishopPuff
//
//============================================================================

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

	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 40*FRACUNIT, MT_BISHOP_PUFF);
	if (mo)
	{
		mo->momz = FRACUNIT/2;
	}
}

//============================================================================
//
// A_BishopPainBlur
//
//============================================================================

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

	if (P_Random() < 64)
	{
		P_SetMobjState(actor, S_BISHOP_BLUR1);
		return;
	}
	mo = P_SpawnMobj(actor->x + ((P_Random() - P_Random()) << 12),
			 actor->y + ((P_Random() - P_Random()) << 12),
			 actor->z + ((P_Random() - P_Random()) << 11),
			 MT_BISHOPPAINBLUR);
	if (mo)
	{
		mo->angle = actor->angle;
	}
}

//============================================================================
//
// DragonSeek
//
//============================================================================

static void DragonSeek(mobj_t *actor, angle_t thresh, angle_t turnMax)
{
	int dir;
	int dist;
	angle_t delta;
	angle_t angle;
	mobj_t *target;
	int search;
	int i;
	int bestArg;
	angle_t bestAngle;
	angle_t angleToSpot, angleToTarget;
	mobj_t *mo;

	target = (mobj_t *)actor->special1;
	if (target == NULL)
	{
		return;
	}
	dir = P_FaceMobj(actor, target, &delta);
	if (delta > thresh)
	{
		delta >>= 1;
		if (delta > turnMax)
		{
			delta = turnMax;
		}
	}
	if (dir)
	{ // Turn clockwise
		actor->angle += delta;
	}
	else
	{ // Turn counter clockwise
		actor->angle -= delta;
	}
	angle = actor->angle >> ANGLETOFINESHIFT;
	actor->momx = FixedMul(actor->info->speed, finecosine[angle]);
	actor->momy = FixedMul(actor->info->speed, finesine[angle]);
	if (actor->z + actor->height < target->z ||
	    target->z + target->height < actor->z)
	{
		dist = P_AproxDistance(target->x - actor->x, target->y - actor->y);
		dist = dist / actor->info->speed;
		if (dist < 1)
		{
			dist = 1;
		}
		actor->momz = (target->z - actor->z) / dist;
	}
	else
	{
		dist = P_AproxDistance(target->x - actor->x, target->y - actor->y);
		dist = dist / actor->info->speed;
	}
	if (target->flags & MF_SHOOTABLE && P_Random() < 64)
	{ // attack the destination mobj if it's attackable
		mobj_t *oldTarget;

		if (abs(actor->angle - R_PointToAngle2(actor->x, actor->y, target->x, target->y)) < ANGLE_45/2)
		{
			oldTarget = actor->target;
			actor->target = target;
			if (P_CheckMeleeRange(actor))
			{
				P_DamageMobj(actor->target, actor, actor, HITDICE(10));
				S_StartSound(actor, SFX_DRAGON_ATTACK);
			}
			else if (P_Random() < 128 && P_CheckMissileRange(actor))
			{
				P_SpawnMissile(actor, target, MT_DRAGON_FX);
				S_StartSound(actor, SFX_DRAGON_ATTACK);
			}
			actor->target = oldTarget;
		}
	}
	if (dist < 4)
	{ // Hit the target thing
		if (actor->target && P_Random() < 200)
		{
			bestArg = -1;
			bestAngle = ANGLE_MAX;
			angleToTarget = R_PointToAngle2(actor->x, actor->y,
							actor->target->x, actor->target->y);
			for (i = 0; i < 5; i++)
			{
				if (!target->args[i])
				{
					continue;
				}
				search = -1;
				mo = P_FindMobjFromTID(target->args[i], &search);
				angleToSpot = R_PointToAngle2(actor->x, actor->y, mo->x, mo->y);
				if (abs(angleToSpot - angleToTarget) < bestAngle)
				{
					bestAngle = abs(angleToSpot - angleToTarget);
					bestArg = i;
				}
			}
			if (bestArg != -1)
			{
				search = -1;
				actor->special1 = (intptr_t)P_FindMobjFromTID(target->args[bestArg], &search);
			}
		}
		else
		{
			do
			{
				i = (P_Random() >> 2) % 5;
			} while (!target->args[i]);
			search = -1;
			actor->special1 = (intptr_t)P_FindMobjFromTID(target->args[i], &search);
		}
	}
}

//============================================================================
//
// A_DragonInitFlight
//
//============================================================================

void A_DragonInitFlight(mobj_t *actor)
{
	int search;

	search = -1;
	do
	{ // find the first tid identical to the dragon's tid
		actor->special1 = (intptr_t)P_FindMobjFromTID(actor->tid, &search);
		if (search == -1)
		{
			P_SetMobjState(actor, actor->info->spawnstate);
			return;
		}
	} while (actor->special1 == (intptr_t)actor);
	P_RemoveMobjFromTIDList(actor);
}

//============================================================================
//
// A_DragonFlight
//
//============================================================================

void A_DragonFlight(mobj_t *actor)
{
	angle_t angle;

	DragonSeek(actor, 4*ANGLE_1, 8*ANGLE_1);
	if (actor->target)
	{
		if (!(actor->target->flags & MF_SHOOTABLE))
		{ // target died
			actor->target = NULL;
			return;
		}
		angle = R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y);
		if (abs(actor->angle - angle) < ANGLE_45/2 && P_CheckMeleeRange(actor))
		{
			P_DamageMobj(actor->target, actor, actor, HITDICE(8));
			S_StartSound(actor, SFX_DRAGON_ATTACK);
		}
		else if (abs(actor->angle - angle) <= ANGLE_1*20)
		{
			P_SetMobjState(actor, actor->info->missilestate);
			S_StartSound(actor, SFX_DRAGON_ATTACK);
		}
	}
	else
	{
		P_LookForPlayers(actor, true);
	}
}

//============================================================================
//
// A_DragonFlap
//
//============================================================================

void A_DragonFlap(mobj_t *actor)
{
	A_DragonFlight(actor);
	if (P_Random() < 240)
	{
		S_StartSound(actor, SFX_DRAGON_WINGFLAP);
	}
	else
	{
		S_StartSound(actor, actor->info->activesound);
	}
}

//============================================================================
//
// A_DragonAttack
//
//============================================================================

void A_DragonAttack(mobj_t *actor)
{
	P_SpawnMissile(actor, actor->target, MT_DRAGON_FX);
}

//============================================================================
//
// A_DragonFX2
//
//============================================================================

void A_DragonFX2(mobj_t *actor)
{
	mobj_t *mo;
	int i;
	int delay;

	delay = 16 + (P_Random() >> 3);
	for (i = 1 + (P_Random() & 3); i; i--)
	{
		mo = P_SpawnMobj(actor->x + ((P_Random() - 128) << 14),
				 actor->y + ((P_Random() - 128) << 14),
				 actor->z + ((P_Random() - 128) << 12),
				 MT_DRAGON_FX2);
		if (mo)
		{
			mo->tics = delay + (P_Random() & 3) * i * 2;
			mo->target = actor->target;
		}
	}
}

//============================================================================
//
// A_DragonPain
//
//============================================================================

void A_DragonPain(mobj_t *actor)
{
	A_Pain(actor);
	if (!actor->special1)
	{ // no destination spot yet
		P_SetMobjState(actor, S_DRAGON_INIT);
	}
}

//============================================================================
//
// A_DragonCheckCrash
//
//============================================================================

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

//============================================================================
// Demon AI
//============================================================================

//
// A_DemonAttack1 (melee)
//
void A_DemonAttack1(mobj_t *actor)
{
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(2));
	}
}

//
// A_DemonAttack2 (missile)
//
void A_DemonAttack2(mobj_t *actor)
{
	mobj_t *mo;
	int fireBall;

	if (actor->type == MT_DEMON)
	{
		fireBall = MT_DEMONFX1;
	}
	else
	{
		fireBall = MT_DEMON2FX1;
	}
	mo = P_SpawnMissile(actor, actor->target, fireBall);
	if (mo)
	{
		mo->z += 30*FRACUNIT;
		S_StartSound(actor, SFX_DEMON_MISSILE_FIRE);
	}
}

//
// A_DemonDeath
//
void A_DemonDeath(mobj_t *actor)
{
	mobj_t *mo;
	angle_t angle;

	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45*FRACUNIT, MT_DEMONCHUNK1);
	if (mo)
	{
		angle = actor->angle + ANG90;
		mo->momz = 8*FRACUNIT;
		mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
				    finecosine[angle>>ANGLETOFINESHIFT]);
		mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
				    finesine[angle>>ANGLETOFINESHIFT]);
		mo->target = actor;
	}
	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45*FRACUNIT, MT_DEMONCHUNK2);
	if (mo)
	{
		angle = actor->angle - ANG90;
		mo->momz = 8*FRACUNIT;
		mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
				    finecosine[angle>>ANGLETOFINESHIFT]);
		mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
				    finesine[angle>>ANGLETOFINESHIFT]);
		mo->target = actor;
	}
	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45*FRACUNIT, MT_DEMONCHUNK3);
	if (mo)
	{
		angle = actor->angle - ANG90;
		mo->momz = 8*FRACUNIT;
		mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
				    finecosine[angle>>ANGLETOFINESHIFT]);
		mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
				    finesine[angle>>ANGLETOFINESHIFT]);
		mo->target = actor;
	}
	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45*FRACUNIT, MT_DEMONCHUNK4);
	if (mo)
	{
		angle = actor->angle - ANG90;
		mo->momz = 8*FRACUNIT;
		mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
				    finecosine[angle>>ANGLETOFINESHIFT]);
		mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
				    finesine[angle>>ANGLETOFINESHIFT]);
		mo->target = actor;
	}
	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45*FRACUNIT, MT_DEMONCHUNK5);
	if (mo)
	{
		angle = actor->angle - ANG90;
		mo->momz = 8*FRACUNIT;
		mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
				    finecosine[angle>>ANGLETOFINESHIFT]);
		mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
				    finesine[angle>>ANGLETOFINESHIFT]);
		mo->target = actor;
	}
}

//===========================================================================
//
// A_Demon2Death
//
//===========================================================================

void A_Demon2Death(mobj_t *actor)
{
	mobj_t *mo;
	angle_t angle;

	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45*FRACUNIT, MT_DEMON2CHUNK1);
	if (mo)
	{
		angle = actor->angle + ANG90;
		mo->momz = 8*FRACUNIT;
		mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
				    finecosine[angle>>ANGLETOFINESHIFT]);
		mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
				    finesine[angle>>ANGLETOFINESHIFT]);
		mo->target = actor;
	}
	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45*FRACUNIT, MT_DEMON2CHUNK2);
	if (mo)
	{
		angle = actor->angle - ANG90;
		mo->momz = 8*FRACUNIT;
		mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
				    finecosine[angle>>ANGLETOFINESHIFT]);
		mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
				    finesine[angle>>ANGLETOFINESHIFT]);
		mo->target = actor;
	}
	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45*FRACUNIT, MT_DEMON2CHUNK3);
	if (mo)
	{
		angle = actor->angle - ANG90;
		mo->momz = 8*FRACUNIT;
		mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
				    finecosine[angle>>ANGLETOFINESHIFT]);
		mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
				    finesine[angle>>ANGLETOFINESHIFT]);
		mo->target = actor;
	}
	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45*FRACUNIT, MT_DEMON2CHUNK4);
	if (mo)
	{
		angle = actor->angle - ANG90;
		mo->momz = 8*FRACUNIT;
		mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
				    finecosine[angle>>ANGLETOFINESHIFT]);
		mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
				    finesine[angle>>ANGLETOFINESHIFT]);
		mo->target = actor;
	}
	mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45*FRACUNIT, MT_DEMON2CHUNK5);
	if (mo)
	{
		angle = actor->angle - ANG90;
		mo->momz = 8*FRACUNIT;
		mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
				    finecosine[angle>>ANGLETOFINESHIFT]);
		mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
				    finesine[angle>>ANGLETOFINESHIFT]);
		mo->target = actor;
	}
}


//
// A_SinkMobj
// Sink a mobj incrementally into the floor
//

boolean A_SinkMobj(mobj_t *actor)
{
	if (actor->floorclip <  actor->info->height)
	{
		switch (actor->type)
		{
		case MT_THRUSTFLOOR_DOWN:
		case MT_THRUSTFLOOR_UP:
			actor->floorclip += 6*FRACUNIT;
			break;
		default:
			actor->floorclip += FRACUNIT;
			break;
		}
		return false;
	}
	return true;
}

//
// A_RaiseMobj
// Raise a mobj incrementally from the floor to 
//

boolean A_RaiseMobj(mobj_t *actor)
{
	int done = true;

	// Raise a mobj from the ground
	if (actor->floorclip > 0)
	{
		switch (actor->type)
		{
		case MT_WRAITHB:
			actor->floorclip -= 2*FRACUNIT;
			break;
		case MT_THRUSTFLOOR_DOWN:
		case MT_THRUSTFLOOR_UP:
			actor->floorclip -= actor->special2*FRACUNIT;
			break;
		default:
			actor->floorclip -= 2*FRACUNIT;
			break;
		}
		if (actor->floorclip <= 0)
		{
			actor->floorclip = 0;
			done = true;
		}
		else
		{
			done = false;
		}
	}
	return done;		// Reached target height
}


//============================================================================
// Wraith Variables
//
//	special1				Internal index into floatbob
//	special2
//============================================================================

//
// A_WraithInit
//

void A_WraithInit(mobj_t *actor)
{
	actor->z += 48<<FRACBITS;
	actor->special1 = 0;			// index into floatbob
}

void A_WraithRaiseInit(mobj_t *actor)
{
	actor->flags2 &= ~MF2_DONTDRAW;
	actor->flags2 &= ~MF2_NONSHOOTABLE;
	actor->flags |= MF_SHOOTABLE|MF_SOLID;
	actor->floorclip = actor->info->height;
}

void A_WraithRaise(mobj_t *actor)
{
	if (A_RaiseMobj(actor))
	{
		// Reached it's target height
		P_SetMobjState(actor,S_WRAITH_CHASE1);
	}

	P_SpawnDirt(actor, actor->radius);
}

void A_WraithMelee(mobj_t *actor)
{
	int amount;

	// Steal health from target and give to player
	if (P_CheckMeleeRange(actor) && (P_Random() < 220))
	{
		amount = HITDICE(2);
		P_DamageMobj(actor->target, actor, actor, amount);
		actor->health += amount;
	}
}

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

	mo = P_SpawnMissile(actor, actor->target, MT_WRAITHFX1);
	if (mo)
	{
		S_StartSound(actor, SFX_WRAITH_MISSILE_FIRE);
	}
}

//
// A_WraithFX2 - spawns sparkle tail of missile
//
void A_WraithFX2(mobj_t *actor)
{
	mobj_t *mo;
	angle_t angle;
	int i;

	for (i = 0; i < 2; i++)
	{
		mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_WRAITHFX2);
		if (mo)
		{
			if (P_Random() < 128)
			{
				angle = actor->angle + (P_Random() << 22);
			}
			else
			{
				angle = actor->angle - (P_Random() << 22);
			}
			mo->momz = 0;
			mo->momx = FixedMul((P_Random() << 7) + FRACUNIT,
					    finecosine[angle>>ANGLETOFINESHIFT]);
			mo->momy = FixedMul((P_Random() << 7) + FRACUNIT,
					    finesine[angle>>ANGLETOFINESHIFT]);
			mo->target = actor;
			mo->floorclip = 10*FRACUNIT;
		}
	}
}

// Spawn an FX3 around the actor during attacks
void A_WraithFX3(mobj_t *actor)
{
	mobj_t *mo;
	int numdropped = P_Random() % 15;
	int i;

	for (i = 0; i < numdropped; i++)
	{
		mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_WRAITHFX3);
		if (mo)
		{
			mo->x += (P_Random() - 128) << 11;
			mo->y += (P_Random() - 128) << 11;
			mo->z += (P_Random() << 10);
			mo->target = actor;
		}
	}
}

// Spawn an FX4 during movement
void A_WraithFX4(mobj_t *actor)
{
	mobj_t *mo;
	int chance = P_Random();
	int spawn4, spawn5;

	if (chance < 10)
	{
		spawn4 = true;
		spawn5 = false;
	}
	else if (chance < 20)
	{
		spawn4 = false;
		spawn5 = true;
	}
	else if (chance < 25)
	{
		spawn4 = true;
		spawn5 = true;
	}
	else
	{
		spawn4 = false;
		spawn5 = false;
	}

	if (spawn4)
	{
		mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_WRAITHFX4);
		if (mo)
		{
			mo->x += (P_Random() - 128) << 12;
			mo->y += (P_Random() - 128) << 12;
			mo->z += (P_Random() << 10);
			mo->target = actor;
		}
	}
	if (spawn5)
	{
		mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_WRAITHFX5);
		if (mo)
		{
			mo->x += (P_Random() - 128) << 11;
			mo->y += (P_Random() - 128) << 11;
			mo->z += (P_Random() << 10);
			mo->target = actor;
		}
	}
}

void A_WraithLook(mobj_t *actor)
{
//	A_WraithFX4(actor);		// too expensive
	A_Look(actor);
}

void A_WraithChase(mobj_t *actor)
{
	int weaveindex = actor->special1;
	actor->z += FloatBobOffsets[weaveindex];
	actor->special1 = (weaveindex + 2) & 63;
//	if (actor->floorclip > 0)
//	{
//		P_SetMobjState(actor, S_WRAITH_RAISE2);
//		return;
//	}
	A_Chase(actor);
	A_WraithFX4(actor);
}


//============================================================================
// Ettin AI
//============================================================================

void A_EttinAttack(mobj_t *actor)
{
	if (P_CheckMeleeRange(actor))
	{
		P_DamageMobj(actor->target, actor, actor, HITDICE(2));
	}
}

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

	mo = P_SpawnMobj(actor->x, actor->y, actor->z + (actor->height>>1), MT_ETTIN_MACE);
	if (mo)
	{
		mo->momx = (P_Random() - 128) << 11;
		mo->momy = (P_Random() - 128) << 11;
		mo->momz = FRACUNIT*10 + (P_Random() << 10);
		mo->target = actor;
	}
}


//============================================================================
// Fire Demon AI
//
// special1			index into floatbob
// special2			whether strafing or not
//============================================================================

void A_FiredSpawnRock(mobj_t *actor)
{
	mobj_t *mo;
	int x, y, z;
	int rtype = 0;

	switch (P_Random() % 5)
	{
	case 0:
		rtype = MT_FIREDEMON_FX1;
		break;
	case 1:
		rtype = MT_FIREDEMON_FX2;
		break;
	case 2:
		rtype = MT_FIREDEMON_FX3;
		break;
	case 3:
		rtype = MT_FIREDEMON_FX4;
		break;
	case 4:
		rtype = MT_FIREDEMON_FX5;
		break;
	}

	x = actor->x + ((P_Random() - 128) << 12);
	y = actor->y + ((P_Random() - 128) << 12);
	z = actor->z + ((P_Random()) << 11);
	mo = P_SpawnMobj(x, y, z, rtype);
	if (mo)
	{
		mo->target = actor;
		mo->momx = (P_Random() - 128) << 10;
		mo->momy = (P_Random() - 128) << 10;
		mo->momz = (P_Random() << 10);
		mo->special1 = 2;	// Number bounces
	}

	// Initialize fire demon
	actor->special2 = 0;
	actor->flags &= ~MF_JUSTATTACKED;
}

void A_FiredRocks(mobj_t *actor)
{
	A_FiredSpawnRock(actor);
	A_FiredSpawnRock(actor);
	A_FiredSpawnRock(actor);
	A_FiredSpawnRock(actor);
	A_FiredSpawnRock(actor);
}

void A_FiredAttack(mobj_t *actor)
{
	mobj_t *mo;
	mo = P_SpawnMissile(actor, actor->target, MT_FIREDEMON_FX6);
	if (mo)
		S_StartSound(actor, SFX_FIRED_ATTACK);
}

void A_SmBounce(mobj_t *actor)
{
	// give some more momentum (x,y,&z)
	actor->z = actor->floorz + FRACUNIT;
	actor->momz = (2*FRACUNIT) + (P_Random()<<10);
	actor->momx = (P_Random() % 3) << FRACBITS;
	actor->momy = (P_Random() % 3) << FRACBITS;
}


#define FIREDEMON_ATTACK_RANGE	(64 * 8 * FRACUNIT)

void A_FiredChase(mobj_t *actor)
{
	int weaveindex = actor->special1;
	mobj_t *target = actor->target;
	angle_t ang;
	fixed_t dist;

	if (actor->reactiontime)
		actor->reactiontime--;
	if (actor->threshold)
		actor->threshold--;

	// Float up and down
	actor->z += FloatBobOffsets[weaveindex];
	actor->special1 = (weaveindex + 2) & 63;

	// Insure it stays above certain height
	if (actor->z < actor->floorz + (64*FRACUNIT))
	{
		actor->z += 2*FRACUNIT;
	}

	if (!actor->target || !(actor->target->flags & MF_SHOOTABLE))
	{	// Invalid target
		P_LookForPlayers(actor, true);
		return;
	}

	// Strafe
	if (actor->special2 > 0)
	{
		actor->special2--;
	}
	else
	{
		actor->special2 = 0;
		actor->momx = actor->momy = 0;
		dist = P_AproxDistance(actor->x - target->x, actor->y - target->y);
		if (dist < FIREDEMON_ATTACK_RANGE)
		{
			if (P_Random() < 30)
			{
				ang = R_PointToAngle2(actor->x, actor->y, target->x, target->y);
				if (P_Random() < 128)
					ang += ANGLE_90;
				else
					ang -= ANGLE_90;
				ang >>= ANGLETOFINESHIFT;
				actor->momx = FixedMul(8*FRACUNIT, finecosine[ang]);
				actor->momy = FixedMul(8*FRACUNIT, finesine[ang]);
				actor->special2 = 3;		// strafe time
			}
		}
	}

	FaceMovementDirection(actor);

	// Normal movement
	if (!actor->special2)
	{
		if (--actor->movecount < 0 || !P_Move (actor))
		{
			P_NewChaseDir (actor);
		}
	}

	// Do missile attack
	if (!(actor->flags & MF_JUSTATTACKED))
	{
		if (P_CheckMissileRange(actor) && (P_Random() < 20))
		{
			P_SetMobjState (actor, actor->info->missilestate);
			actor->flags |= MF_JUSTATTACKED;
			return;
		}
	}
	else
	{
		actor->flags &= ~MF_JUSTATTACKED;
	}

	// make active sound
	if (actor->info->activesound && P_Random() < 3)
	{
		S_StartSound(actor, actor->info->activesound);
	}
}

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

	mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_FIREDEMON_SPLOTCH1);
	if (mo)
	{
		mo->momx = (P_Random() - 128) << 11;
		mo->momy = (P_Random() - 128) << 11;
		mo->momz = FRACUNIT*3 + (P_Random() << 10);
	}
	mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_FIREDEMON_SPLOTCH2);
	if (mo)
	{
		mo->momx = (P_Random() - 128) << 11;
		mo->momy = (P_Random() - 128) << 11;
		mo->momz = FRACUNIT*3 + (P_Random() << 10);
	}
}


//============================================================================
//
// A_IceGuyLook
//
//============================================================================

void A_IceGuyLook(mobj_t *actor)
{
	fixed_t dist;
	fixed_t an;

	A_Look(actor);
	if (P_Random() < 64)
	{
		dist = ((P_Random() - 128) * actor->radius) >> 7;
		an = (actor->angle + ANG90) >> ANGLETOFINESHIFT;

		P_SpawnMobj(actor->x + FixedMul(dist, finecosine[an]),
			    actor->y + FixedMul(dist, finesine[an]),
			    actor->z + 60*FRACUNIT,
			    MT_ICEGUY_WISP1 + (P_Random() & 1));
	}
}

//============================================================================
//
// A_IceGuyChase
//
//============================================================================

void A_IceGuyChase(mobj_t *actor)
{
	fixed_t dist;
	fixed_t an;
	mobj_t *mo;

	A_Chase(actor);
	if (P_Random() < 128)
	{
		dist = ((P_Random() - 128) * actor->radius) >> 7;
		an = (actor->angle + ANG90) >> ANGLETOFINESHIFT;

		mo = P_SpawnMobj(actor->x + FixedMul(dist, finecosine[an]),
				 actor->y + FixedMul(dist, finesine[an]),
				 actor->z + 60*FRACUNIT,
				 MT_ICEGUY_WISP1 + (P_Random() & 1));
		if (mo)
		{
			mo->momx = actor->momx;
			mo->momy = actor->momy;
			mo->momz = actor->momz;
			mo->target = actor;
		}
	}
}

//============================================================================
//
// A_IceGuyAttack
//
//============================================================================

void A_IceGuyAttack(mobj_t *actor)
{
	fixed_t an;

	if (!actor->target)
	{
		return;
	}
	an = (actor->angle + ANG90) >> ANGLETOFINESHIFT;
	P_SpawnMissileXYZ(actor->x + FixedMul(actor->radius >> 1, finecosine[an]),
			  actor->y + FixedMul(actor->radius >> 1, finesine[an]),
			  actor->z + 40*FRACUNIT, actor, actor->target, MT_ICEGUY_FX);
	an = (actor->angle - ANG90) >> ANGLETOFINESHIFT;
	P_SpawnMissileXYZ(actor->x + FixedMul(actor->radius >> 1, finecosine[an]),
			  actor->y + FixedMul(actor->radius >> 1, finesine[an]),
			  actor->z + 40*FRACUNIT, actor, actor->target, MT_ICEGUY_FX);
	S_StartSound(actor, actor->info->attacksound);
}

//============================================================================
//
// A_IceGuyMissilePuff
//
//============================================================================

void A_IceGuyMissilePuff(mobj_t *actor)
{
	P_SpawnMobj(actor->x, actor->y, actor->z + 2*FRACUNIT, MT_ICEFX_PUFF);
}

//============================================================================
//
// A_IceGuyDie
//
//============================================================================

void A_FreezeDeathChunks(mobj_t *actor);

void A_IceGuyDie(mobj_t *actor)
{
	actor->momx = 0;
	actor->momy = 0;
	actor->momz = 0;
	actor->height <<= 2;
	A_FreezeDeathChunks(actor);
}

//============================================================================
//
// A_IceGuyMissileExplode
//
//============================================================================

void A_IceGuyMissileExplode(mobj_t *actor)
{
	mobj_t *mo;
	unsigned int i;

	for (i = 0; i < 8; i++)
	{
		mo = P_SpawnMissileAngle(actor, MT_ICEGUY_FX2, i*ANG45,	-0.3*FRACUNIT);
		if (mo)
		{
			mo->target = actor->target;
		}
	}
}


//============================================================================
//
//	Sorcerer stuff
//
//	Sorcerer Variables
//		special1		Angle of ball 1 (all others relative to that)
//		special2		which ball to stop at in stop mode (MT_???)
//		args[0]			Denfense time
//		args[1]			Number of full rotations since stopping mode
//		args[2]			Target orbit speed for acceleration/deceleration
//		args[3]			Movement mode (see SORC_ macros)
//		args[4]			Current ball orbit speed
//	Sorcerer Ball Variables
//		special1		Previous angle of ball (for woosh)
//		special2		Countdown of rapid fire (FX4)
//		args[0]			If set, don't play the bounce sound when bouncing
//============================================================================

#define SORCBALL_INITIAL_SPEED		7
#define SORCBALL_TERMINAL_SPEED		25
#define SORCBALL_SPEED_ROTATIONS	5

#define SORC_DEFENSE_TIME		255
#define SORC_DEFENSE_HEIGHT		45

#define BOUNCE_TIME_UNIT		(35/2)

#define SORCFX4_RAPIDFIRE_TIME		(6*3)		/* 3 seconds */
#define SORCFX4_SPREAD_ANGLE		20

#define SORC_DECELERATE		0
#define SORC_ACCELERATE		1
#define SORC_STOPPING		2
#define SORC_FIRESPELL		3
#define SORC_STOPPED		4
#define SORC_NORMAL		5
#define SORC_FIRING_SPELL	6

#define BALL1_ANGLEOFFSET	0
#define BALL2_ANGLEOFFSET	(ANGLE_MAX / 3)
#define BALL3_ANGLEOFFSET	((ANGLE_MAX / 3) * 2)

void A_SorcBallOrbit(mobj_t *actor);
void A_SorcSpinBalls(mobj_t *actor);
void A_SpeedBalls(mobj_t *actor);
void A_SlowBalls(mobj_t *actor);
void A_StopBalls(mobj_t *actor);
void A_AccelBalls(mobj_t *actor);
void A_DecelBalls(mobj_t *actor);
void A_SorcBossAttack(mobj_t *actor);
void A_SpawnFizzle(mobj_t *actor);
void A_CastSorcererSpell(mobj_t *actor);
void A_SorcUpdateBallAngle(mobj_t *actor);
void A_BounceCheck(mobj_t *actor);
void A_SorcFX1Seek(mobj_t *actor);
void A_SorcOffense1(mobj_t *actor);
void A_SorcOffense2(mobj_t *actor);

// Spawn spinning balls above head - actor is sorcerer
void A_SorcSpinBalls(mobj_t *actor)
{
	mobj_t *mo;
	fixed_t z;

	A_SlowBalls(actor);
	actor->args[0] = 0;					// Currently no defense
	actor->args[3] = SORC_NORMAL;
	actor->args[4] = SORCBALL_INITIAL_SPEED;		// Initial orbit speed
	actor->special1 = ANGLE_1;
	z = actor->z - actor->floorclip + actor->info->height;

	mo = P_SpawnMobj(actor->x, actor->y, z, MT_SORCBALL1);
	if (mo)
	{
		mo->target = actor;
		mo->special2 = SORCFX4_RAPIDFIRE_TIME;
	}
	mo = P_SpawnMobj(actor->x, actor->y, z, MT_SORCBALL2);
	if (mo)
		mo->target = actor;
	mo = P_SpawnMobj(actor->x, actor->y, z, MT_SORCBALL3);
	if (mo)
		mo->target = actor;
}

//
// A_SorcBallOrbit() ==========================================
//
void A_SorcBallOrbit(mobj_t *actor)
{
	int x,y;
	angle_t angle = 0, baseangle;
	int mode = actor->target->args[3];
	mobj_t *parent = (mobj_t *)actor->target;
	int dist = parent->radius - (actor->radius<<1);
	angle_t prevangle = actor->special1;

	if (actor->target->health <= 0)
		P_SetMobjState(actor, actor->info->painstate);

	baseangle = (angle_t)parent->special1;
	switch (actor->type)
	{
	case MT_SORCBALL1:
		angle = baseangle + BALL1_ANGLEOFFSET;
		break;
	case MT_SORCBALL2:
		angle = baseangle + BALL2_ANGLEOFFSET;
		break;
	case MT_SORCBALL3:
		angle = baseangle + BALL3_ANGLEOFFSET;
		break;
	default:
		I_Error("corrupted sorcerer");
		break;
	}
	actor->angle = angle;
	angle >>= ANGLETOFINESHIFT;

	switch (mode)
	{
	case SORC_NORMAL:		// Balls rotating normally
		A_SorcUpdateBallAngle(actor);
		break;
	case SORC_DECELERATE:		// Balls decelerating
		A_DecelBalls(actor);
		A_SorcUpdateBallAngle(actor);
		break;
	case SORC_ACCELERATE:		// Balls accelerating
		A_AccelBalls(actor);
		A_SorcUpdateBallAngle(actor);
		break;
	case SORC_STOPPING:		// Balls stopping
		if ((parent->special2 == actor->type) &&
			(parent->args[1] > SORCBALL_SPEED_ROTATIONS) &&
			(abs(angle - (parent->angle>>ANGLETOFINESHIFT)) < (30<<5)))
		{
			// Can stop now
			actor->target->args[3] = SORC_FIRESPELL;
			actor->target->args[4] = 0;
			// Set angle so ball angle == sorcerer angle
			switch (actor->type)
			{
			case MT_SORCBALL1:
				parent->special1 = (int)(parent->angle - BALL1_ANGLEOFFSET);
				break;
			case MT_SORCBALL2:
				parent->special1 = (int)(parent->angle - BALL2_ANGLEOFFSET);
				break;
			case MT_SORCBALL3:
				parent->special1 = (int)(parent->angle - BALL3_ANGLEOFFSET);
				break;
			default:
				break;
			}
		}
		else
		{
			A_SorcUpdateBallAngle(actor);
		}
		break;
	case SORC_FIRESPELL:		// Casting spell
		if (parent->special2 == actor->type)
		{
			// Put sorcerer into special throw spell anim
			if (parent->health > 0)
				P_SetMobjStateNF(parent, S_SORC_ATTACK1);

			if (actor->type == MT_SORCBALL1 && P_Random()<200)
			{
				S_StartSound(NULL, SFX_SORCERER_SPELLCAST);
				actor->special2 = SORCFX4_RAPIDFIRE_TIME;
				actor->args[4] = 128;
				parent->args[3] = SORC_FIRING_SPELL;
			}
			else
			{
				A_CastSorcererSpell(actor);
				parent->args[3] = SORC_STOPPED;
			}
		}
		break;
	case SORC_FIRING_SPELL:
		if (parent->special2 == actor->type)
		{
			if (actor->special2-- <= 0)
			{
				// Done rapid firing 
				parent->args[3] = SORC_STOPPED;
				// Back to orbit balls
				if (parent->health > 0)
					P_SetMobjStateNF(parent, S_SORC_ATTACK4);
			}
			else
			{
				// Do rapid fire spell
				A_SorcOffense2(actor);
			}
		}
		break;
	case SORC_STOPPED:		// Balls stopped
	default:
		break;
	}

	if ((angle < prevangle) && (parent->args[4] == SORCBALL_TERMINAL_SPEED))
	{
		parent->args[1]++;	// Bump rotation counter
		// Completed full rotation - make woosh sound
		S_StartSound(actor, SFX_SORCERER_BALLWOOSH);
	}
	actor->special1 = angle;	// Set previous angle
	x = parent->x + FixedMul(dist, finecosine[angle]);
	y = parent->y + FixedMul(dist, finesine[angle]);
	actor->x = x;
	actor->y = y;
	actor->z = parent->z - parent->floorclip + parent->info->height;
}

//
// Set balls to speed mode - actor is sorcerer
//
void A_SpeedBalls(mobj_t *actor)
{
	actor->args[3] = SORC_ACCELERATE;		// speed mode
	actor->args[2] = SORCBALL_TERMINAL_SPEED;	// target speed
}

//
// Set balls to slow mode - actor is sorcerer
//
void A_SlowBalls(mobj_t *actor)
{
	actor->args[3] = SORC_DECELERATE;		// slow mode
	actor->args[2] = SORCBALL_INITIAL_SPEED;	// target speed
}

//
// Instant stop when rotation gets to ball in special2
//		actor is sorcerer
//
void A_StopBalls(mobj_t *actor)
{
	int chance = P_Random();
	actor->args[3] = SORC_STOPPING;			// stopping mode
	actor->args[1] = 0;				// Reset rotation counter

	if ((actor->args[0] <= 0) && (chance < 200))
	{
		actor->special2 = MT_SORCBALL2;		// Blue
	}
	else if ((actor->health < (actor->info->spawnhealth >> 1)) &&
			(chance < 200))
	{
		actor->special2 = MT_SORCBALL3;		// Green
	}
	else
	{
		actor->special2 = MT_SORCBALL1;		// Yellow
	}
}

//
// Increase ball orbit speed - actor is ball
//
void A_AccelBalls(mobj_t *actor)
{
	mobj_t *sorc = actor->target;

	if (sorc->args[4] < sorc->args[2])
	{
		sorc->args[4]++;
	}
	else
	{
		sorc->args[3] = SORC_NORMAL;
		if (sorc->args[4] >= SORCBALL_TERMINAL_SPEED)
		{
			// Reached terminal velocity - stop balls
			A_StopBalls(sorc);
		}
	}
}

// Decrease ball orbit speed - actor is ball
void A_DecelBalls(mobj_t *actor)
{
	mobj_t *sorc = actor->target;

	if (sorc->args[4] > sorc->args[2])
	{
		sorc->args[4]--;
	}
	else
	{
		sorc->args[3] = SORC_NORMAL;
	}
}

// Update angle if first ball - actor is ball
void A_SorcUpdateBallAngle(mobj_t *actor)
{
	if (actor->type == MT_SORCBALL1)
	{
		actor->target->special1 += ANGLE_1*actor->target->args[4];
	}
}

// actor is ball
void A_CastSorcererSpell(mobj_t *actor)
{
	mobj_t *mo;
	int spell = actor->type;
	angle_t ang1, ang2;
	fixed_t z;
	mobj_t *parent = actor->target;

	S_StartSound(NULL, SFX_SORCERER_SPELLCAST);

	// Put sorcerer into throw spell animation
	if (parent->health > 0)
		P_SetMobjStateNF(parent, S_SORC_ATTACK4);

	switch (spell)
	{
	case MT_SORCBALL1:		// Offensive
		A_SorcOffense1(actor);
		break;
	case MT_SORCBALL2:		// Defensive
		z = parent->z - parent->floorclip + SORC_DEFENSE_HEIGHT*FRACUNIT;
		mo = P_SpawnMobj(actor->x, actor->y, z, MT_SORCFX2);
		parent->flags2 |= MF2_REFLECTIVE|MF2_INVULNERABLE;
		parent->args[0] = SORC_DEFENSE_TIME;
		if (mo)
			mo->target = parent;
		break;
	case MT_SORCBALL3:		// Reinforcements
		ang1 = actor->angle - ANGLE_45;
		ang2 = actor->angle + ANGLE_45;
		if (actor->health < (actor->info->spawnhealth/3))
		{	// Spawn 2 at a time
			mo = P_SpawnMissileAngle(parent, MT_SORCFX3, ang1, 4*FRACUNIT);
			if (mo)
				mo->target = parent;
			mo = P_SpawnMissileAngle(parent, MT_SORCFX3, ang2, 4*FRACUNIT);
			if (mo)
				mo->target = parent;
		}
		else
		{
			if (P_Random() < 128)
				ang1 = ang2;
			mo = P_SpawnMissileAngle(parent, MT_SORCFX3, ang1, 4*FRACUNIT);
			if (mo)
				mo->target = parent;
		}
		break;
	default:
		break;
	}
}

/*
void A_SpawnReinforcements(mobj_t *actor)
{
	mobj_t *parent = actor->target;
	mobj_t *mo;
	angle_t ang;

	ang = ANGLE_1 * P_Random();
	mo = P_SpawnMissileAngle(actor, MT_SORCFX3, ang, 5*FRACUNIT);
	if (mo)
		mo->target = parent;
}
*/

// actor is ball
void A_SorcOffense1(mobj_t *actor)
{
	mobj_t *mo;
	angle_t ang1,ang2;
	mobj_t *parent = (mobj_t *)actor->target;

	ang1 = actor->angle + ANGLE_1*70;
	ang2 = actor->angle - ANGLE_1*70;
	mo = P_SpawnMissileAngle(parent, MT_SORCFX1, ang1, 0);
	if (mo)
	{
		mo->target = parent;
		mo->special1 = (intptr_t)parent->target;
		mo->args[4] = BOUNCE_TIME_UNIT;
		mo->args[3] = 15;	// Bounce time in seconds
	}
	mo = P_SpawnMissileAngle(parent, MT_SORCFX1, ang2, 0);
	if (mo)
	{
		mo->target = parent;
		mo->special1 = (intptr_t)parent->target;
		mo->args[4] = BOUNCE_TIME_UNIT;
		mo->args[3] = 15;	// Bounce time in seconds
	}
}

// Actor is ball
void A_SorcOffense2(mobj_t *actor)
{
	angle_t ang1;
	mobj_t *mo;
	int delta, idx;
	mobj_t *parent = actor->target;
	mobj_t *dest = parent->target;
	int dist;

	idx = actor->args[4] << 5;
	actor->args[4] += 15;
	delta = (finesine[idx])*SORCFX4_SPREAD_ANGLE;
	delta = (delta>>FRACBITS)*ANGLE_1;
	ang1 = actor->angle + delta;
	mo = P_SpawnMissileAngle(parent, MT_SORCFX4, ang1, 0);
	if (mo)
	{
		mo->special2 = 35*5/2;		// 5 seconds
		dist = P_AproxDistance(dest->x - mo->x, dest->y - mo->y);
		dist = dist / mo->info->speed;
		if (dist < 1)
			dist = 1;
		mo->momz = (dest->z-mo->z) / dist;
	}
}

// Resume ball spinning
void A_SorcBossAttack(mobj_t *actor)
{
	actor->args[3] = SORC_ACCELERATE;
	actor->args[2] = SORCBALL_INITIAL_SPEED;
}

// spell cast magic fizzle
void A_SpawnFizzle(mobj_t *actor)
{
	fixed_t x, y, z;
	fixed_t dist = 5*FRACUNIT;
	angle_t angle = actor->angle >> ANGLETOFINESHIFT;
	fixed_t speed = actor->info->speed;
	angle_t rangle;
	mobj_t *mo;
	int ix;

	x = actor->x + FixedMul(dist, finecosine[angle]);
	y = actor->y + FixedMul(dist, finesine[angle]);
	z = actor->z - actor->floorclip + (actor->height>>1);
	for (ix = 0; ix < 5; ix++)
	{
		mo = P_SpawnMobj(x, y, z, MT_SORCSPARK1);
		if (mo)
		{
			rangle = angle + ((P_Random() % 5) << 1);
			mo->momx = FixedMul(P_Random() % speed, finecosine[rangle]);
			mo->momy = FixedMul(P_Random() % speed, finesine[rangle]);
			mo->momz = FRACUNIT*2;
		}
	}
}

//============================================================================
// Yellow spell - offense
//============================================================================

void A_SorcFX1Seek(mobj_t *actor)
{
	A_BounceCheck(actor);
	P_SeekerMissile(actor, ANGLE_1*2, ANGLE_1*6);
}

//============================================================================
// Blue spell - defense
//============================================================================
//
// FX2 Variables
//		special1		current angle
//		special2
//		args[0]		0 = CW,  1 = CCW
//		args[1]		
//============================================================================

// Split ball in two
void A_SorcFX2Split(mobj_t *actor)
{
	mobj_t *mo;

	mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SORCFX2);
	if (mo)
	{
		mo->target = actor->target;
		mo->args[0] = 0;			// CW
		mo->special1 = actor->angle;		// Set angle
		P_SetMobjStateNF(mo, S_SORCFX2_ORBIT1);
	}
	mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SORCFX2);
	if (mo)
	{
		mo->target = actor->target;
		mo->args[0] = 1;			// CCW
		mo->special1 = actor->angle;		// Set angle
		P_SetMobjStateNF(mo, S_SORCFX2_ORBIT1);
	}
	P_SetMobjStateNF(actor, S_NULL);
}

// Orbit FX2 about sorcerer
void A_SorcFX2Orbit(mobj_t *actor)
{
	angle_t angle;
	fixed_t x, y, z;
	mobj_t *parent = actor->target;
	fixed_t dist = parent->info->radius;

	if ((parent->health <= 0) ||		// Sorcerer is dead
		(!parent->args[0]))			// Time expired
	{
		P_SetMobjStateNF(actor, actor->info->deathstate);
		parent->args[0] = 0;
		parent->flags2 &= ~MF2_REFLECTIVE;
		parent->flags2 &= ~MF2_INVULNERABLE;
	}

	if (actor->args[0] && (parent->args[0]-- <= 0))	// Time expired
	{
		P_SetMobjStateNF(actor, actor->info->deathstate);
		parent->args[0] = 0;
		parent->flags2 &= ~MF2_REFLECTIVE;
	}

	// Move to new position based on angle
	if (actor->args[0])		// Counter clock-wise
	{
		actor->special1 += ANGLE_1*10;
		angle = ((angle_t)actor->special1) >> ANGLETOFINESHIFT;
		x = parent->x + FixedMul(dist, finecosine[angle]);
		y = parent->y + FixedMul(dist, finesine[angle]);
		z = parent->z - parent->floorclip + SORC_DEFENSE_HEIGHT*FRACUNIT;
		z += FixedMul(15*FRACUNIT, finecosine[angle]);
		// Spawn trailer
		P_SpawnMobj(x, y, z, MT_SORCFX2_T1);
	}
	else	// Clock wise
	{
		actor->special1 -= ANGLE_1*10;
		angle = ((angle_t)actor->special1) >> ANGLETOFINESHIFT;
		x = parent->x + FixedMul(dist, finecosine[angle]);
		y = parent->y + FixedMul(dist, finesine[angle]);
		z = parent->z - parent->floorclip + SORC_DEFENSE_HEIGHT*FRACUNIT;
		z += FixedMul(20*FRACUNIT, finesine[angle]);
		// Spawn trailer
		P_SpawnMobj(x, y, z, MT_SORCFX2_T1);
	}

	actor->x = x;
	actor->y = y;
	actor->z = z;
}

//============================================================================
// Green spell - spawn bishops
//============================================================================

void A_SpawnBishop(mobj_t *actor)
{
	mobj_t *mo;
	mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_BISHOP);
	if (mo)
	{
		if (!P_TestMobjLocation(mo))
		{
			P_SetMobjState(mo, S_NULL);
		}
	}
	P_SetMobjState(actor, S_NULL);
}

/*
void A_SmokePuffEntry(mobj_t *actor)
{
	P_SpawnMobj(actor->x, actor->y, actor->z, MT_MNTRSMOKE);
}
*/

void A_SmokePuffExit(mobj_t *actor)
{
	P_SpawnMobj(actor->x, actor->y, actor->z, MT_MNTRSMOKEEXIT);
}

void A_SorcererBishopEntry(mobj_t *actor)
{
	P_SpawnMobj(actor->x, actor->y, actor->z, MT_SORCFX3_EXPLOSION);
	S_StartSound(actor, actor->info->seesound);
}

//============================================================================
// FX4 - rapid fire balls
//============================================================================

void A_SorcFX4Check(mobj_t *actor)
{
	if (actor->special2-- <= 0)
	{
		P_SetMobjStateNF(actor, actor->info->deathstate);
	}
}

//============================================================================
// Ball death - spawn stuff
//============================================================================

void A_SorcBallPop(mobj_t *actor)
{
	S_StartSound(NULL, SFX_SORCERER_BALLPOP);
	actor->flags &= ~MF_NOGRAVITY;
	actor->flags2 |= MF2_LOGRAV;
	actor->momx = ((P_Random() % 10) - 5) << FRACBITS;
	actor->momy = ((P_Random() % 10) - 5) << FRACBITS;
	actor->momz = (2 + (P_Random() % 3)) << FRACBITS;
	actor->special2 = 4*FRACUNIT;		// Initial bounce factor
	actor->args[4] = BOUNCE_TIME_UNIT;	// Bounce time unit
	actor->args[3] = 5;			// Bounce time in seconds
}

void A_BounceCheck(mobj_t *actor)
{
	if (actor->args[4]-- <= 0)
	{
		if (actor->args[3]-- <= 0)
		{
			P_SetMobjState(actor, actor->info->deathstate);
			switch (actor->type)
			{
			case MT_SORCBALL1:
			case MT_SORCBALL2:
			case MT_SORCBALL3:
				S_StartSound(NULL, SFX_SORCERER_BIGBALLEXPLODE);
				break;
			case MT_SORCFX1:
				S_StartSound(NULL, SFX_SORCERER_HEADSCREAM);
				break;
			default:
				break;
			}
		}
		else
		{
			actor->args[4] = BOUNCE_TIME_UNIT;
		}
	}
}


//============================================================================
// Class Bosses
//============================================================================
#define CLASS_BOSS_STRAFE_RANGE	(64 * 10 * FRACUNIT)

void A_FastChase(mobj_t *actor)
{
	int delta;
	fixed_t dist;
	angle_t ang;
	mobj_t *target;

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

	// Strafe
	if (actor->special2 > 0)
	{
		actor->special2--;
	}
	else
	{
		target = actor->target;
		actor->special2 = 0;
		actor->momx = actor->momy = 0;
		dist = P_AproxDistance (actor->x - target->x,
					actor->y - target->y);
		if (dist < CLASS_BOSS_STRAFE_RANGE)
		{
			if (P_Random() < 100)
			{
				ang = R_PointToAngle2(actor->x, actor->y,
							target->x, target->y);
				if (P_Random() < 128)
					ang += ANGLE_90;
				else
					ang -= ANGLE_90;
				ang >>= ANGLETOFINESHIFT;
				actor->momx = FixedMul(13*FRACUNIT, finecosine[ang]);
				actor->momy = FixedMul(13*FRACUNIT, finesine[ang]);
				actor->special2 = 3;		// strafe time
			}
		}
	}

//
// 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->special2)
	{
		if (--actor->movecount < 0 || !P_Move(actor))
		{
			P_NewChaseDir (actor);
		}
	}
}

void A_FighterAttack(mobj_t *actor)
{
	extern void A_FSwordAttack2(mobj_t *actor);

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

void A_ClericAttack(mobj_t *actor)
{
	extern void A_CHolyAttack3(mobj_t *actor);

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

void A_MageAttack(mobj_t *actor)
{
	extern void A_MStaffAttack2(mobj_t *actor);

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

void A_ClassBossHealth(mobj_t *actor)
{
	if (netgame && !deathmatch)		// co-op only
	{
		if (!actor->special1)
		{
			actor->health *= 5;
			actor->special1 = true;	// has been initialized
		}
	}
}


//===========================================================================
//
// A_CheckFloor - Checks if an object hit the floor
//
//===========================================================================

void A_CheckFloor(mobj_t *actor)
{
	if (actor->z <= actor->floorz)
	{
		actor->z = actor->floorz;
		actor->flags2 &= ~MF2_LOGRAV;
		P_SetMobjState(actor, actor->info->deathstate);
	}
}

//============================================================================
//
// A_FreezeDeath
//
//============================================================================

void A_FreezeDeath(mobj_t *actor)
{
	actor->tics = 75 + P_Random() + P_Random();
	actor->flags |= MF_SOLID|MF_SHOOTABLE|MF_NOBLOOD;
	actor->flags2 |= MF2_PUSHABLE|MF2_TELESTOMP|MF2_PASSMOBJ|MF2_SLIDE;
	actor->height <<= 2;
	S_StartSound(actor, SFX_FREEZE_DEATH);

	if (actor->player)
	{
		actor->player->damagecount = 0;
		actor->player->poisoncount = 0;
		actor->player->bonuscount = 0;
		if (actor->player == &players[consoleplayer])
		{
			SB_PaletteFlash(false);
		}
	}
	else if (actor->flags&MF_COUNTKILL && actor->special)
	{ // Initiate monster death actions
		P_ExecuteLineSpecial(actor->special, actor->args, NULL, 0, actor);
	}
}

//============================================================================
//
// A_IceSetTics
//
//============================================================================

void A_IceSetTics(mobj_t *actor)
{
	int floor;

	actor->tics = 70 + (P_Random() & 63);
	floor = P_GetThingFloorType(actor);
	if (floor == FLOOR_LAVA)
	{
		actor->tics >>= 2;
	}
	else if (floor == FLOOR_ICE)
	{
		actor->tics <<= 1;
	}
}

//============================================================================
//
// A_IceCheckHeadDone
//
//============================================================================

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

//============================================================================
//
// A_FreezeDeathChunks
//
//============================================================================

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

	if (actor->momx || actor->momy || actor->momz)
	{
		actor->tics = 105;
		return;
	}
	S_StartSound(actor, SFX_FREEZE_SHATTER);

	for (i = 12 + (P_Random() & 15); i >= 0; i--)
	{
		mo = P_SpawnMobj(actor->x + (((P_Random() - 128) * actor->radius) >> 7),
				 actor->y + (((P_Random() - 128) * actor->radius) >> 7),
				 actor->z + (P_Random() * actor->height / 255), MT_ICECHUNK);
		P_SetMobjState(mo, mo->info->spawnstate + (P_Random() % 3));
		if (mo)
		{
			mo->momz = FixedDiv(mo->z - actor->z, actor->height)<<2;
			mo->momx = (P_Random() - P_Random()) << (FRACBITS - 7);
			mo->momy = (P_Random() - P_Random()) << (FRACBITS - 7);
			A_IceSetTics(mo);	// set a random tic wait
		}
	}
	for (i = 12 + (P_Random() & 15); i >= 0; i--)
	{
		mo = P_SpawnMobj(actor->x + (((P_Random() - 128) * actor->radius) >> 7),
				 actor->y + (((P_Random() - 128) * actor->radius) >> 7),
				 actor->z + (P_Random() * actor->height / 255), MT_ICECHUNK);
		P_SetMobjState(mo, mo->info->spawnstate + (P_Random() % 3));
		if (mo)
		{
			mo->momz = FixedDiv(mo->z - actor->z, actor->height)<<2;
			mo->momx = (P_Random() - P_Random()) << (FRACBITS - 7);
			mo->momy = (P_Random() - P_Random()) << (FRACBITS - 7);
			A_IceSetTics(mo);	// set a random tic wait
		}
	}
	if (actor->player)
	{ // attach the player's view to a chunk of ice
		mo = P_SpawnMobj(actor->x, actor->y, actor->z + VIEWHEIGHT, MT_ICECHUNK);
		P_SetMobjState(mo, S_ICECHUNK_HEAD);
		mo->momz = FixedDiv(mo->z - actor->z, actor->height)<<2;
		mo->momx = (P_Random() - P_Random()) << (FRACBITS - 7);
		mo->momy = (P_Random() - P_Random()) << (FRACBITS - 7);
		mo->flags2 |= MF2_ICEDAMAGE; // used to force blue palette
		mo->flags2 &= ~MF2_FLOORCLIP;
		mo->player = actor->player;
		actor->player = NULL;
		mo->health = actor->health;
		mo->angle = actor->angle;
		mo->player->mo = mo;
		mo->player->lookdir = 0;
	}
	P_RemoveMobjFromTIDList(actor);
	P_SetMobjState(actor, S_FREETARGMOBJ);
	actor->flags2 |= MF2_DONTDRAW;
}

//===========================================================================
// Korax Variables
//	special1	last teleport destination
//	special2	set if "below half" script not yet run
//
// Korax Scripts (reserved)
//	249		Tell scripts that we are below half health
//	250-254	Control scripts
//	255		Death script
//
// Korax TIDs (reserved)
//	245		Reserved for Korax himself
//	248		Initial teleport destination
//	249		Teleport destination
//	250-254	For use in respective control scripts
//	255		For use in death script (spawn spots)
//===========================================================================
#define KORAX_SPIRIT_LIFETIME	(5 * (35 / 5))	/* 5 seconds */
#define KORAX_COMMAND_HEIGHT	(120 * FRACUNIT)
#define KORAX_COMMAND_OFFSET	(27 * FRACUNIT)

void KoraxFire1(mobj_t *actor, int type);
void KoraxFire2(mobj_t *actor, int type);
void KoraxFire3(mobj_t *actor, int type);
void KoraxFire4(mobj_t *actor, int type);
void KoraxFire5(mobj_t *actor, int type);
void KoraxFire6(mobj_t *actor, int type);
void KSpiritInit(mobj_t *spirit, mobj_t *korax);

#define KORAX_TID			(245)
#define KORAX_FIRST_TELEPORT_TID	(248)
#define KORAX_TELEPORT_TID		(249)

void A_KoraxChase(mobj_t *actor)
{
	mobj_t *spot;
	int lastfound;
	byte args[3] = {0, 0, 0};

	if ((!actor->special2) &&
		(actor->health <= (actor->info->spawnhealth/2)))
	{
		lastfound = 0;
		spot = P_FindMobjFromTID(KORAX_FIRST_TELEPORT_TID, &lastfound);
		if (spot)
		{
			P_Teleport(actor, spot->x, spot->y, spot->angle, true);
		}

		P_StartACS(249, 0, args, actor, NULL, 0);
		actor->special2 = 1;	// Don't run again

		return;
	}

	if (!actor->target)
		return;
	if (P_Random() < 30)
	{
		P_SetMobjState(actor, actor->info->missilestate);
	}
	else if (P_Random() < 30)
	{
		S_StartSound(NULL, SFX_KORAX_ACTIVE);
	}

	// Teleport away
	if (actor->health < (actor->info->spawnhealth>>1))
	{
		if (P_Random() < 10)
		{
			lastfound = actor->special1;
			spot = P_FindMobjFromTID(KORAX_TELEPORT_TID, &lastfound);
			actor->special1 = lastfound;
			if (spot)
			{
				P_Teleport(actor, spot->x, spot->y, spot->angle, true);
			}
		}
	}
}

void A_KoraxStep(mobj_t *actor)
{
	A_Chase(actor);
}

void A_KoraxStep2(mobj_t *actor)
{
	S_StartSound(NULL, SFX_KORAX_STEP);
	A_Chase(actor);
}

void A_KoraxBonePop(mobj_t *actor)
{
	mobj_t *mo;
	byte args[5];

	args[0] = args[1] = args[2] = args[3] = args[4] = 0;

	// Spawn 6 spirits equalangularly
	mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT1, ANGLE_60*0, 5*FRACUNIT);
	if (mo)
		KSpiritInit(mo, actor);
	mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT2, ANGLE_60*1, 5*FRACUNIT);
	if (mo)
		KSpiritInit(mo, actor);
	mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT3, ANGLE_60*2, 5*FRACUNIT);
	if (mo)
		KSpiritInit(mo, actor);
	mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT4, ANGLE_60*3, 5*FRACUNIT);
	if (mo)
		KSpiritInit(mo, actor);
	mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT5, ANGLE_60*4, 5*FRACUNIT);
	if (mo)
		KSpiritInit(mo, actor);
	mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT6, ANGLE_60*5, 5*FRACUNIT);
	if (mo)
		KSpiritInit(mo, actor);

	P_StartACS(255, 0, args, actor, NULL, 0);		// Death script
}

void KSpiritInit(mobj_t *spirit, mobj_t *korax)
{
	int i;
	mobj_t *tail, *next;

	spirit->health = KORAX_SPIRIT_LIFETIME;

	spirit->special1 = (intptr_t)korax;	// Swarm around korax
	spirit->special2 = 32+(P_Random()&7);	// Float bob index
	spirit->args[0] = 10;			// initial turn value
	spirit->args[1] = 0;			// initial look angle

	// Spawn a tail for spirit
	tail = P_SpawnMobj(spirit->x, spirit->y, spirit->z, MT_HOLY_TAIL);
	tail->special2 = (intptr_t)spirit;	// parent
	for (i = 1; i < 3; i++)
	{
		next = P_SpawnMobj(spirit->x, spirit->y, spirit->z, MT_HOLY_TAIL);
		P_SetMobjState(next, next->info->spawnstate + 1);
		tail->special1 = (intptr_t)next;
		tail = next;
	}
	tail->special1 = 0; // last tail bit
}

void A_KoraxDecide(mobj_t *actor)
{
	if (P_Random() < 220)
	{
		P_SetMobjState(actor, S_KORAX_MISSILE1);
	}
	else
	{
		P_SetMobjState(actor, S_KORAX_COMMAND1);
	}
}

void A_KoraxMissile(mobj_t *actor)
{
	int type = P_Random() % 6;
	int sound = SFX_NONE;

	S_StartSound(actor, SFX_KORAX_ATTACK);

	switch (type)
	{
	case 0:
		type = MT_WRAITHFX1;
		sound = SFX_WRAITH_MISSILE_FIRE;
		break;
	case 1:
		type = MT_DEMONFX1;
		sound = SFX_DEMON_MISSILE_FIRE;
		break;
	case 2:
		type = MT_DEMON2FX1;
		sound = SFX_DEMON_MISSILE_FIRE;
		break;
	case 3:
		type = MT_FIREDEMON_FX6;
		sound = SFX_FIRED_ATTACK;
		break;
	case 4:
		type = MT_CENTAUR_FX;
		sound = SFX_CENTAURLEADER_ATTACK;
		break;
	case 5:
		type = MT_SERPENTFX;
		sound = SFX_CENTAURLEADER_ATTACK;
		break;
	}

	// Fire all 6 missiles at once
	S_StartSound(NULL, sound);
	KoraxFire1(actor, type);
	KoraxFire2(actor, type);
	KoraxFire3(actor, type);
	KoraxFire4(actor, type);
	KoraxFire5(actor, type);
	KoraxFire6(actor, type);
}

// Call action code scripts (250-254)
void A_KoraxCommand(mobj_t *actor)
{
	byte args[5];
	fixed_t x, y, z;
	angle_t ang;
	int numcommands;

	S_StartSound(actor, SFX_KORAX_COMMAND);

	// Shoot stream of lightning to ceiling
	ang = (actor->angle - ANGLE_90) >> ANGLETOFINESHIFT;
	x = actor->x + FixedMul(KORAX_COMMAND_OFFSET, finecosine[ang]);
	y = actor->y + FixedMul(KORAX_COMMAND_OFFSET, finesine[ang]);
	z = actor->z + KORAX_COMMAND_HEIGHT;
	P_SpawnMobj(x, y, z, MT_KORAX_BOLT);

	args[0] = args[1] = args[2] = args[3] = args[4] = 0;

	if (actor->health <= (actor->info->spawnhealth >> 1))
	{
		numcommands = 5;
	}
	else
	{
		numcommands = 4;
	}

	switch (P_Random() % numcommands)
	{
	case 0:
		P_StartACS(250, 0, args, actor, NULL, 0);
		break;
	case 1:
		P_StartACS(251, 0, args, actor, NULL, 0);
		break;
	case 2:
		P_StartACS(252, 0, args, actor, NULL, 0);
		break;
	case 3:
		P_StartACS(253, 0, args, actor, NULL, 0);
		break;
	case 4:
		P_StartACS(254, 0, args, actor, NULL, 0);
		break;
	}
}


#define KORAX_DELTAANGLE		(85 * ANGLE_1)
#define KORAX_ARM_EXTENSION_SHORT	(40 * FRACUNIT)
#define KORAX_ARM_EXTENSION_LONG	(55 * FRACUNIT)

#define KORAX_ARM1_HEIGHT		(108* FRACUNIT)
#define KORAX_ARM2_HEIGHT		(82 * FRACUNIT)
#define KORAX_ARM3_HEIGHT		(54 * FRACUNIT)
#define KORAX_ARM4_HEIGHT		(104* FRACUNIT)
#define KORAX_ARM5_HEIGHT		(86 * FRACUNIT)
#define KORAX_ARM6_HEIGHT		(53 * FRACUNIT)

// Arm projectiles
//		arm positions numbered:
//			1	top left
//			2	middle left
//			3	lower left
//			4	top right
//			5	middle right
//			6	lower right

// Arm 1 projectile
void KoraxFire1(mobj_t *actor, int type)
{
	angle_t ang;
	fixed_t x, y, z;

	ang = (actor->angle - KORAX_DELTAANGLE) >> ANGLETOFINESHIFT;
	x = actor->x + FixedMul(KORAX_ARM_EXTENSION_SHORT, finecosine[ang]);
	y = actor->y + FixedMul(KORAX_ARM_EXTENSION_SHORT, finesine[ang]);
	z = actor->z - actor->floorclip + KORAX_ARM1_HEIGHT;
	P_SpawnKoraxMissile(x, y, z, actor, actor->target, type);
}

// Arm 2 projectile
void KoraxFire2(mobj_t *actor, int type)
{
	angle_t ang;
	fixed_t x, y, z;

	ang = (actor->angle - KORAX_DELTAANGLE) >> ANGLETOFINESHIFT;
	x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine[ang]);
	y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine[ang]);
	z = actor->z - actor->floorclip + KORAX_ARM2_HEIGHT;
	P_SpawnKoraxMissile(x, y, z, actor, actor->target, type);
}

// Arm 3 projectile
void KoraxFire3(mobj_t *actor, int type)
{
	angle_t ang;
	fixed_t x, y, z;

	ang = (actor->angle - KORAX_DELTAANGLE) >> ANGLETOFINESHIFT;
	x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine[ang]);
	y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine[ang]);
	z = actor->z - actor->floorclip + KORAX_ARM3_HEIGHT;
	P_SpawnKoraxMissile(x, y, z, actor, actor->target, type);
}

// Arm 4 projectile
void KoraxFire4(mobj_t *actor, int type)
{
	angle_t ang;
	fixed_t x, y, z;

	ang = (actor->angle + KORAX_DELTAANGLE) >> ANGLETOFINESHIFT;
	x = actor->x + FixedMul(KORAX_ARM_EXTENSION_SHORT, finecosine[ang]);
	y = actor->y + FixedMul(KORAX_ARM_EXTENSION_SHORT, finesine[ang]);
	z = actor->z - actor->floorclip + KORAX_ARM4_HEIGHT;
	P_SpawnKoraxMissile(x, y, z, actor, actor->target, type);
}

// Arm 5 projectile
void KoraxFire5(mobj_t *actor, int type)
{
	angle_t ang;
	fixed_t x, y, z;

	ang = (actor->angle + KORAX_DELTAANGLE) >> ANGLETOFINESHIFT;
	x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine[ang]);
	y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine[ang]);
	z = actor->z - actor->floorclip + KORAX_ARM5_HEIGHT;
	P_SpawnKoraxMissile(x, y, z, actor, actor->target, type);
}

// Arm 6 projectile
void KoraxFire6(mobj_t *actor, int type)
{
	angle_t ang;
	fixed_t x, y, z;

	ang = (actor->angle + KORAX_DELTAANGLE) >> ANGLETOFINESHIFT;
	x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine[ang]);
	y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine[ang]);
	z = actor->z - actor->floorclip + KORAX_ARM6_HEIGHT;
	P_SpawnKoraxMissile(x, y, z, actor, actor->target, type);
}


void A_KSpiritWeave(mobj_t *actor)
{
	fixed_t newX, newY;
	int weaveXY, weaveZ;
	int angle;

	weaveXY = actor->special2 >> 16;
	weaveZ = actor->special2 & 0xFFFF;
	angle = (actor->angle + ANG90) >> ANGLETOFINESHIFT;
	newX = actor->x - FixedMul(finecosine[angle], FloatBobOffsets[weaveXY] << 2);
	newY = actor->y - FixedMul(finesine[angle], FloatBobOffsets[weaveXY] << 2);
	weaveXY = (weaveXY + (P_Random() % 5)) & 63;
	newX += FixedMul(finecosine[angle], FloatBobOffsets[weaveXY] << 2);
	newY += FixedMul(finesine[angle], FloatBobOffsets[weaveXY] << 2);
	P_TryMove(actor, newX, newY);
	actor->z -= FloatBobOffsets[weaveZ] << 1;
	weaveZ = (weaveZ + (P_Random() % 5)) & 63;
	actor->z += FloatBobOffsets[weaveZ] << 1;
	actor->special2 = weaveZ + (weaveXY << 16);
}

void A_KSpiritSeeker(mobj_t *actor, angle_t thresh, angle_t turnMax)
{
	int dir;
	int dist;
	angle_t delta;
	angle_t angle;
	mobj_t *target;
	fixed_t newZ;
	fixed_t deltaZ;

	target = (mobj_t *)actor->special1;
	if (target == NULL)
	{
		return;
	}
	dir = P_FaceMobj(actor, target, &delta);
	if (delta > thresh)
	{
		delta >>= 1;
		if (delta > turnMax)
		{
			delta = turnMax;
		}
	}
	if (dir)
	{ // Turn clockwise
		actor->angle += delta;
	}
	else
	{ // Turn counter clockwise
		actor->angle -= delta;
	}
	angle = actor->angle >> ANGLETOFINESHIFT;
	actor->momx = FixedMul(actor->info->speed, finecosine[angle]);
	actor->momy = FixedMul(actor->info->speed, finesine[angle]);

	if (!(leveltime & 15)
		|| actor->z > target->z + (target->info->height)
		|| actor->z + actor->height < target->z)
	{
		newZ = target->z + ((P_Random() * target->info->height) >> 8);
		deltaZ = newZ - actor->z;
		if (abs(deltaZ) > 15*FRACUNIT)
		{
			if (deltaZ > 0)
			{
				deltaZ = 15*FRACUNIT;
			}
			else
			{
				deltaZ = -15*FRACUNIT;
			}
		}
		dist = P_AproxDistance(target->x - actor->x, target->y - actor->y);
		dist = dist / actor->info->speed;
		if (dist < 1)
		{
			dist = 1;
		}
		actor->momz = deltaZ / dist;
	}
	return;
}

void A_KSpiritRoam(mobj_t *actor)
{
	if (actor->health-- <= 0)
	{
		S_StartSound(actor, SFX_SPIRIT_DIE);
		P_SetMobjState(actor, S_KSPIRIT_DEATH1);
	}
	else
	{
		if (actor->special1)
		{
			A_KSpiritSeeker(actor, actor->args[0]*ANGLE_1,
						actor->args[0]*ANGLE_1*2);
		}
		A_KSpiritWeave(actor);
		if (P_Random() < 50)
		{
			S_StartSound(NULL, SFX_SPIRIT_ACTIVE);
		}
	}
}

void A_KBolt(mobj_t *actor)
{
	// Countdown lifetime
	if (actor->special1-- <= 0)
	{
		P_SetMobjState(actor, S_NULL);
	}
}


#define KORAX_BOLT_HEIGHT		48*FRACUNIT
#define KORAX_BOLT_LIFETIME		3

void A_KBoltRaise(mobj_t *actor)
{
	mobj_t *mo;
	fixed_t z;

	// Spawn a child upward
	z = actor->z + KORAX_BOLT_HEIGHT;

	if ((z + KORAX_BOLT_HEIGHT) < actor->ceilingz)
	{
		mo = P_SpawnMobj(actor->x, actor->y, z, MT_KORAX_BOLT);
		if (mo)
		{
			mo->special1 = KORAX_BOLT_LIFETIME;
		}
	}
	else
	{
		// Maybe cap it off here
	}
}