shithub: choc

ref: bd6ef57ac4785f70c58d02feb627407c73a2b909
dir: /src/hexen/p_enemy.c/

View raw version
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 1993-2008 Raven Software
// Copyright(C) 2005-2014 Simon Howard
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//


#include "h2def.h"
#include "m_random.h"
#include "i_system.h"
#include "i_swap.h"
#include "p_local.h"
#include "s_sound.h"

// Macros
// Types
// Private Data
// External Data


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

mobj_t *soundtarget;

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

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

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

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
================
*/

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


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

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

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

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

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

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
    {
        tdir = DI_SOUTHEAST;

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

            if (tdir == DI_EAST)
            {
                break;
            }

            --tdir;
        }
    }

    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

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 != actor->special1.p->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
================
*/

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;

    // NOTE: This behavior has been changed from the Vanilla behavior, where
    // an infinite loop can occur if players 0-3 all quit the game. Although
    // technically this is not what Vanilla does, fixing this is highly
    // desirable, and having the game simply lock up is not acceptable.
    // stop = (actor->lastlook - 1) & 3;
    // for (;; actor->lastlook = (actor->lastlook + 1) & 3)

    stop = (actor->lastlook + maxplayers - 1) % maxplayers;
    for (;; actor->lastlook = (actor->lastlook + 1) % maxplayers)
    {
        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 (actor->special1.p == player)
            {
                continue;       // Don't target master
            }
        }

        actor->target = player->mo;
        return (true);
    }
    return (false);
}

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

						ACTION ROUTINES

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

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

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

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


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

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

        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_SubRandom() << 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.
//
//----------------------------------------------------------------------------

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.i -= tics;
    if (actor->special1.i > 0)
    {
        return (false);
    }
    moType = actor->special2.i;
    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.i = 5 * 35;  // Next try in 5 seconds
        mo->special2.i = 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;
    }
}



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

// Check the age of the minotaur and stomp it after MAULATORTICS of time
// have passed. Returns false if killed.
static boolean CheckMinotaurAge(mobj_t *mo)
{
    unsigned int starttime;

    // The start time is stored in the mobj_t structure, but it is stored
    // in little endian format. For Vanilla savegame compatibility we must
    // swap it to the native endianness.
    memcpy(&starttime, mo->args, sizeof(unsigned int));

    if (leveltime - LONG(starttime) >= MAULATORTICS)
    {
        P_DamageMobj(mo, NULL, NULL, 10000);
        return false;
    }

    return true;
}

void A_MinotaurRoam(mobj_t * actor)
{
    actor->flags &= ~MF_SHADOW; // In case pain caused him to 
    actor->flags &= ~MF_ALTSHADOW;      // skip his fade in.

    if (!CheckMinotaurAge(actor))
    {
        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 + 1) % 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 = NULL;
    player_t *player;
    thinker_t *think;
    fixed_t dist;
    int i;
    mobj_t *master = actor->special1.m;

    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.m == actor->special1.m))
                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)
{
    actor->flags &= ~MF_SHADOW; // In case pain caused him to 
    actor->flags &= ~MF_ALTSHADOW;      // skip his fade in.

    if (!CheckMinotaurAge(actor))
    {
        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.i = 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.i == 0)
    {
        P_SetMobjState(actor, S_MNTR_ATK3_4);
        actor->special2.i = 1;
    }
}

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

void A_MntrFloorFire(mobj_t * actor)
{
    mobj_t *mo;
    int r1, r2;

    r1 = P_SubRandom();
    r2 = P_SubRandom();

    actor->z = actor->floorz;
    mo = P_SpawnMobj(actor->x + (r2 << 10),
                     actor->y + (r1 << 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->class)
                {
                    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->class)
                {
                    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->class)
                {
                    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_SubRandom()<<8;
	mo->momy = P_SubRandom()<<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_SubRandom() << 9;
    mo->momy = P_SubRandom() << 9;
    mo->momz = FRACUNIT * 2 + (P_Random() << 6);
    // Attach player mobj to bloody skull
    player = actor->player;
    actor->player = NULL;
    actor->special1.i = player->class;
    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.i == 666)
    {
        P_SetMobjState(actor, S_BLOODYSKULLX2);
    }
}

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

void A_CheckBurnGone(mobj_t * actor)
{
    if (actor->special2.i == 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
mobj_t *corpseQueue[CORPSEQUEUESIZE];
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
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;
    int r1, r2;

    r1 = P_Random();
    r2 = P_Random();
    mo = P_SpawnMobj(actor->x + ((r2 - 128) << 12),
                     actor->y + ((r1 - 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;
    }
    r1 = P_Random();
    r2 = P_Random();
    mo = P_SpawnMobj(actor->x + ((r2 - 128) << 12),
                     actor->y + ((r1 - 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;
    }
    r1 = P_Random();
    r2 = P_Random();
    mo = P_SpawnMobj(actor->x + ((r2 - 128) << 12),
                     actor->y + ((r1 - 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.i = (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.i)
    {
        actor->special1.i = 0;
        P_SetMobjState(actor, S_BISHOP_WALK1);
        return;
    }
    mo = P_SpawnMissile(actor, actor->target, MT_BISH_FX);
    if (mo)
    {
        mo->special1.m = actor->target;
        mo->special2.i = 16;      // High word == x/y, Low word == z
    }
    actor->special1.i--;
}

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

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

    weaveXY = actor->special2.i >> 16;
    weaveZ = actor->special2.i & 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.i = weaveZ + (weaveXY << 16);
}

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

void A_BishopMissileSeek(mobj_t * actor)
{
    P_SeekerMissile(actor, ANG1 * 2, ANG1 * 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.i = (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.i)
    {
        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.i] >> 1;
    actor->special2.i = (actor->special2.i + 4) & 63;
    actor->z += FloatBobOffsets[actor->special2.i] >> 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;
    int r1,r2,r3;

    if (P_Random() < 64)
    {
        P_SetMobjState(actor, S_BISHOP_BLUR1);
        return;
    }

    r1 = P_SubRandom();
    r2 = P_SubRandom();
    r3 = P_SubRandom();

    mo = P_SpawnMobj(actor->x + (r3 << 12), actor->y
                     + (r2 << 12),
                     actor->z + (r1 << 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 = actor->special1.m;
    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((int) actor->angle - (int) R_PointToAngle2(actor->x, actor->y,
                                               target->x,
                                               target->y)) < ANG45 / 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 = ANG_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((int) angleToSpot - (int) angleToTarget) < bestAngle)
                {
                    bestAngle = abs((int) angleToSpot - (int) angleToTarget);
                    bestArg = i;
                }
            }
            if (bestArg != -1)
            {
                search = -1;
                actor->special1.m =
                    P_FindMobjFromTID(target->args[bestArg], &search);
            }
        }
        else
        {
            do
            {
                i = (P_Random() >> 2) % 5;
            }
            while (!target->args[i]);
            search = -1;
            actor->special1.m =
                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.m = P_FindMobjFromTID(actor->tid, &search);
        if (search == -1)
        {
            P_SetMobjState(actor, actor->info->spawnstate);
            return;
        }
    }
    while (actor->special1.m == actor);
    P_RemoveMobjFromTIDList(actor);
}

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

void A_DragonFlight(mobj_t * actor)
{
    angle_t angle;

    DragonSeek(actor, 4 * ANG1, 8 * ANG1);
    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((int) actor->angle - (int) angle) < ANG45 / 2
            && P_CheckMeleeRange(actor))
        {
            P_DamageMobj(actor->target, actor, actor, HITDICE(8));
            S_StartSound(actor, SFX_DRAGON_ATTACK);
        }
        else if (abs((int) actor->angle - (int) angle) <= ANG1 * 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 r1,r2,r3;
    int delay;

    delay = 16 + (P_Random() >> 3);
    for (i = 1 + (P_Random() & 3); i; i--)
    {
        r1 = P_Random();
        r2 = P_Random();
        r3 = P_Random();
        mo = P_SpawnMobj(actor->x + ((r3 - 128) << 14),
                         actor->y + ((r2 - 128) << 14),
                         actor->z + ((r1 - 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.i)
    {                           // 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.i * 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.i = 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.i;
    actor->z += FloatBobOffsets[weaveindex];
    actor->special1.i = (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.i = 2;       // Number bounces
    }

    // Initialize fire demon
    actor->special2.i = 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.i;
    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.i = (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.i > 0)
    {
        actor->special2.i--;
    }
    else
    {
        actor->special2.i = 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 += ANG90;
                else
                    ang -= ANG90;
                ang >>= ANGLETOFINESHIFT;
                actor->momx = FixedMul(8 * FRACUNIT, finecosine[ang]);
                actor->momy = FixedMul(8 * FRACUNIT, finesine[ang]);
                actor->special2.i = 3;    // strafe time
            }
        }
    }

    FaceMovementDirection(actor);

    // Normal movement
    if (!actor->special2.i)
    {
        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_IceGuyDie(mobj_t * actor)
{
    void A_FreezeDeathChunks(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	(ANG_MAX/3)
#define BALL3_ANGLEOFFSET	((ANG_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.i = ANG1;
    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.i = 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, 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.i;

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

    baseangle = (angle_t) parent->special1.i;
    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");
            return;
    }
    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.i == actor->type) &&
                (parent->args[1] > SORCBALL_SPEED_ROTATIONS) &&
                (abs((int) angle - (int) (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.i = (int) (parent->angle -
                                                    BALL1_ANGLEOFFSET);
                        break;
                    case MT_SORCBALL2:
                        parent->special1.i = (int) (parent->angle -
                                                    BALL2_ANGLEOFFSET);
                        break;
                    case MT_SORCBALL3:
                        parent->special1.i = (int) (parent->angle -
                                                    BALL3_ANGLEOFFSET);
                        break;
                    default:
                        break;
                }
            }
            else
            {
                A_SorcUpdateBallAngle(actor);
            }
            break;
        case SORC_FIRESPELL:   // Casting spell
            if (parent->special2.i == 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.i = 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.i == actor->type)
            {
                if (actor->special2.i-- <= 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.i = 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.i = MT_SORCBALL2; // Blue
    }
    else if ((actor->health < (actor->info->spawnhealth >> 1)) &&
             (chance < 200))
    {
        actor->special2.i = MT_SORCBALL3; // Green
    }
    else
    {
        actor->special2.i = 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.i += ANG1 * 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 - ANG45;
            ang2 = actor->angle + ANG45;
            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 = ANG1 * 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 + ANG1 * 70;
    ang2 = actor->angle - ANG1 * 70;
    mo = P_SpawnMissileAngle(parent, MT_SORCFX1, ang1, 0);
    if (mo)
    {
        mo->target = parent;
        mo->special1.m = 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.m = 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, index;
    mobj_t *parent = actor->target;
    mobj_t *dest = parent->target;
    int dist;

    index = actor->args[4] << 5;
    actor->args[4] += 15;
    delta = (finesine[index]) * SORCFX4_SPREAD_ANGLE;
    delta = (delta >> FRACBITS) * ANG1;
    ang1 = actor->angle + delta;
    mo = P_SpawnMissileAngle(parent, MT_SORCFX4, ang1, 0);
    if (mo)
    {
        mo->special2.i = 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, ANG1 * 2, ANG1 * 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.i = 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.i = 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.i += ANG1 * 10;
        angle = ((angle_t) actor->special1.i) >> 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.i -= ANG1 * 10;
        angle = ((angle_t) actor->special1.i) >> 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.i-- <= 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.i = 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.i > 0)
    {
        actor->special2.i--;
    }
    else
    {
        target = actor->target;
        actor->special2.i = 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 += ANG90;
                else
                    ang -= ANG90;
                ang >>= ANGLETOFINESHIFT;
                actor->momx = FixedMul(13 * FRACUNIT, finecosine[ang]);
                actor->momy = FixedMul(13 * FRACUNIT, finesine[ang]);
                actor->special2.i = 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.i)
    {
        if (--actor->movecount < 0 || !P_Move(actor))
        {
            P_NewChaseDir(actor);
        }
    }
}


void A_FighterAttack(mobj_t * actor)
{
    if (!actor->target)
        return;
    A_FSwordAttack2(actor);
}


void A_ClericAttack(mobj_t * actor)
{
    if (!actor->target)
        return;
    A_CHolyAttack3(actor);
}



void A_MageAttack(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.i)
        {
            actor->health *= 5;
            actor->special1.i = 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)
{
    int r = P_Random();
    actor->tics = 75 + r + 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.i == 666)
    {
        P_SetMobjState(actor, S_ICECHUNK_HEAD2);
    }
}

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

void A_FreezeDeathChunks(mobj_t * actor)
{
    int i;
    int r1,r2,r3;
    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--)
    {
        r1 = P_Random();
        r2 = P_Random();
        r3 = P_Random();
        mo = P_SpawnMobj(actor->x +
                         (((r3 - 128) * actor->radius) >> 7),
                         actor->y +
                         (((r2 - 128) * actor->radius) >> 7),
                         actor->z + (r1 * actor->height / 255),
                         MT_ICECHUNK);
        P_SetMobjState(mo, mo->info->spawnstate + (P_Random() % 3));
        mo->momz = FixedDiv(mo->z - actor->z, actor->height) << 2;
        mo->momx = P_SubRandom() << (FRACBITS - 7);
        mo->momy = P_SubRandom() << (FRACBITS - 7);
        A_IceSetTics(mo);   // set a random tic wait
    }
    for (i = 12 + (P_Random() & 15); i >= 0; i--)
    {
        r1 = P_Random();
        r2 = P_Random();
        r3 = P_Random();
        mo = P_SpawnMobj(actor->x +
                         (((r3 - 128) * actor->radius) >> 7),
                         actor->y +
                         (((r2 - 128) * actor->radius) >> 7),
                         actor->z + (r1 * actor->height / 255),
                         MT_ICECHUNK);
        P_SetMobjState(mo, mo->info->spawnstate + (P_Random() % 3));
        mo->momz = FixedDiv(mo->z - actor->z, actor->height) << 2;
        mo->momx = P_SubRandom() << (FRACBITS - 7);
        mo->momy = P_SubRandom() << (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_SubRandom() << (FRACBITS - 7);
        mo->momy = P_SubRandom() << (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.i) &&
        (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);
        }

        CheckACSPresent(249);
        P_StartACS(249, 0, args, actor, NULL, 0);
        actor->special2.i = 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.i;
            spot = P_FindMobjFromTID(KORAX_TELEPORT_TID, &lastfound);
            actor->special1.i = 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, ANG60 * 0,
                             5 * FRACUNIT);
    if (mo)
        KSpiritInit(mo, actor);
    mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT2, ANG60 * 1,
                             5 * FRACUNIT);
    if (mo)
        KSpiritInit(mo, actor);
    mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT3, ANG60 * 2,
                             5 * FRACUNIT);
    if (mo)
        KSpiritInit(mo, actor);
    mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT4, ANG60 * 3,
                             5 * FRACUNIT);
    if (mo)
        KSpiritInit(mo, actor);
    mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT5, ANG60 * 4,
                             5 * FRACUNIT);
    if (mo)
        KSpiritInit(mo, actor);
    mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT6, ANG60 * 5,
                             5 * FRACUNIT);
    if (mo)
        KSpiritInit(mo, actor);

    CheckACSPresent(255);
    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.m = korax;     // Swarm around korax
    spirit->special2.i = 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.m = 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.m = next;
        tail = next;
    }
    tail->special1.m = NULL;         // 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 = 0;

    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 - ANG90) >> 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:
            CheckACSPresent(250);
            P_StartACS(250, 0, args, actor, NULL, 0);
            break;
        case 1:
            CheckACSPresent(251);
            P_StartACS(251, 0, args, actor, NULL, 0);
            break;
        case 2:
            CheckACSPresent(252);
            P_StartACS(252, 0, args, actor, NULL, 0);
            break;
        case 3:
            CheckACSPresent(253);
            P_StartACS(253, 0, args, actor, NULL, 0);
            break;
        case 4:
            CheckACSPresent(254);
            P_StartACS(254, 0, args, actor, NULL, 0);
            break;
    }
}


#define KORAX_DELTAANGLE			(85*ANG1)
#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.i >> 16;
    weaveZ = actor->special2.i & 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.i = 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 = actor->special1.m;
    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.m)
        {
            A_KSpiritSeeker(actor, actor->args[0] * ANG1,
                            actor->args[0] * ANG1 * 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.i-- <= 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.i = KORAX_BOLT_LIFETIME;
        }
    }
    else
    {
        // Maybe cap it off here
    }
}