//
// Copyright(C) 1993-1996 Id Software, Inc.
// 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.
//
// DESCRIPTION:
//	Enemy thinking, AI.
//	Action Pointer Functions
//	that are associated with states/frames. 
//

#include <stdio.h>
#include <stdlib.h>

#include "m_random.h"
#include "i_system.h"
#include "doomdef.h"
#include "m_misc.h"
#include "p_local.h"
#include "s_sound.h"
#include "g_game.h"
#include "z_zone.h"     // villsa [STRIFE]

// State.
#include "doomstat.h"
#include "r_state.h"

// Data.
#include "sounds.h"

// [STRIFE] Dialog / Inventory
#include "p_dialog.h"
#include "deh_str.h"
#include "w_wad.h"
#include "f_finale.h"
#include "p_inter.h"

// Forward Declarations:
void A_RandomWalk(mobj_t *);
void A_ProgrammerAttack(mobj_t* actor);
void A_FireSigilEOffshoot(mobj_t *actor);
void A_SpectreCAttack(mobj_t *actor);
void A_SpectreDAttack(mobj_t *actor);
void A_SpectreEAttack(mobj_t *actor);

void P_ThrustMobj(mobj_t *actor, angle_t angle, fixed_t force);

typedef enum
{
    DI_EAST,
    DI_NORTHEAST,
    DI_NORTH,
    DI_NORTHWEST,
    DI_WEST,
    DI_SOUTHWEST,
    DI_SOUTH,
    DI_SOUTHEAST,
    DI_NODIR,
    NUMDIRS
    
} dirtype_t;


//
// P_NewChaseDir related LUT.
//
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 A_Fall (mobj_t *actor);


//
// ENEMY THINKING
// Enemies are allways spawned
// with targetplayer = -1, threshold = 0
// Most monsters are spawned unaware of all players,
// but some can be made preaware
//


//
// Called by P_NoiseAlert.
// Recursively traverse adjacent sectors,
// sound blocking lines cut off traversal.
//
// haleyjd 09/05/10: [STRIFE] Verified unmodified
//

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)
    {
	return;		// already flooded
    }
    
    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)
	    continue;	// closed door
	
	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);
    }
}



//
// P_NoiseAlert
// If a monster yells at a player,
// it will alert other monsters to the player.
//
// haleyjd 09/05/10: [STRIFE] Verified unmodified
//
void
P_NoiseAlert
( mobj_t*	target,
  mobj_t*	emmiter )
{
    soundtarget = target;
    validcount++;
    P_RecursiveSound (emmiter->subsector->sector, 0);
}

//
// P_WakeUpThing
//
// villsa [STRIFE] New function
// Wakes up an mobj.nearby when somebody has been punched.
//
static void P_WakeUpThing(mobj_t* puncher, mobj_t* bystander)
{
    if(!(bystander->flags & MF_NODIALOG))
    {
        bystander->target = puncher;
        if(bystander->info->seesound)
            S_StartSound(bystander, bystander->info->seesound);
        P_SetMobjState(bystander, bystander->info->seestate);
    }
}

//
// P_DoPunchAlert
//
// villsa [STRIFE] New function (by Quasar ;)
// Wake up buddies nearby when the player thinks he's gotten too clever
// with the punch dagger. Walks sector links.
//
void P_DoPunchAlert(mobj_t *puncher, mobj_t *punchee)
{   
   mobj_t *rover;
   
   // don't bother with this crap if we're already on alert
   if(punchee->subsector->sector->soundtarget)
      return;
      
   // gotta still be alive to call for help
   if(punchee->health <= 0)
      return;
      
   // has to be something you can wake up and kill too
   if(!(punchee->flags & MF_COUNTKILL) || punchee->flags & MF_NODIALOG)
      return;
   
   // make the punchee hurt - haleyjd 09/05/10: Fixed to use painstate.
   punchee->target = puncher;
   P_SetMobjState(punchee, punchee->info->painstate); 
   
   // wake up everybody nearby
   
   // scan forward on sector list
   for(rover = punchee->snext; rover; rover = rover->snext)
   {
      // we only wake up certain thing types (Acolytes and Templars?)
      if(rover->health > 0 && rover->type >= MT_GUARD1 && rover->type <= MT_PGUARD &&
         (P_CheckSight(rover, puncher) || P_CheckSight(rover, punchee)))
      {
         P_WakeUpThing(puncher, rover);
         rover->flags |= MF_NODIALOG;
      }
   }

   // scan backward on sector list
   for(rover = punchee->sprev; rover; rover = rover->sprev)
   {
      // we only wake up certain thing types (Acolytes and Templars?)
      if(rover->health > 0 && rover->type >= MT_GUARD1 && rover->type <= MT_PGUARD &&
         (P_CheckSight(rover, puncher) || P_CheckSight(rover, punchee)))
      {
         P_WakeUpThing(puncher, rover);
         rover->flags |= MF_NODIALOG;
      }
   }
}




//
// P_CheckMeleeRange
//
// [STRIFE] Minor change to meleerange.
//
boolean P_CheckMeleeRange(mobj_t* actor)
{
    mobj_t*	pl;
    fixed_t	dist;

    if(!actor->target)
        return false;

    pl = actor->target;
    if(actor->z + 3 * actor->height / 2 < pl->z) // villsa [STRIFE]
        return false;

    dist = P_AproxDistance(pl->x - actor->x, pl->y - actor->y);

    if(dist >= MELEERANGE - 20*FRACUNIT + pl->info->radius)
        return false;

    if(!P_CheckSight (actor, actor->target))
        return false;

    return true;
}

//
// P_CheckMissileRange
//
// [STRIFE]
// Changes to eliminate DOOM-specific code and to allow for
// varying attack ranges for Strife monsters, as well as a general tweak
// to considered distance for all monsters.
//
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)
        return false;	// do not attack yet

    // OPTIMIZE: get this from a global checksight
    dist = P_AproxDistance(actor->x-actor->target->x,
                           actor->y-actor->target->y) - 64*FRACUNIT;
    
    if (!actor->info->meleestate)
        dist -= 128*FRACUNIT;       // no melee attack, so fire more

    dist >>= 16;

    // villsa [STRIFE] checks for acolytes
    //  haleyjd 09/05/10: Repaired to match disassembly: Was including 
    //  SHADOWGUARD in the wrong case, was missing MT_SENTINEL entirely.
    //  Structure of ASM also indicates this was probably a switch 
    //  statement turned into a cascading if/else by the compiler.
    switch(actor->type)
    {
    case MT_GUARD1:
    case MT_GUARD2:
    case MT_GUARD3:
    case MT_GUARD4:
    case MT_GUARD5:
    case MT_GUARD6:
        // oddly, not all Acolytes are included here...
        dist >>= 4;
        break;
    case MT_SHADOWGUARD:
    case MT_CRUSADER:
    case MT_SENTINEL:
        dist >>= 1;
        break;
    default:
        break;
    }
    
    // villsa [STRIFE] changed to 150
    if (dist > 150)
        dist = 150;

    // haleyjd 20100910: Hex-Rays was leaving this out completely:
    if (actor->type == MT_CRUSADER && dist > 120)
        dist = 120;

    // haleyjd 20110224 [STRIFE]: reversed predicate
    return (dist < P_Random());
}

//
// P_CheckRobotRange
//
// villsa [STRIFE] New function
//
boolean P_CheckRobotRange(mobj_t *actor)
{
    fixed_t dist;

    if(!P_CheckSight(actor, actor->target))
        return false;

    if(actor->reactiontime)
        return false;    // do not attack yet

    dist = (P_AproxDistance(actor->x-actor->target->x,
                            actor->y-actor->target->y) - 64*FRACUNIT) >> FRACBITS;

    return (dist < 200);
}


//
// P_Move
// Move in the current direction,
// returns false if the move is blocked.
//
// [STRIFE]
// villsa/haleyjd 09/05/10: Modified for terrain types and 3D object 
// clipping. Below constants are verified to be unmodified:
//
fixed_t	xspeed[8] = {FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000};
fixed_t yspeed[8] = {0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000};

extern	line_t*	spechit[];
extern	int	numspechit;

boolean P_Move (mobj_t*	actor)
{
    fixed_t	tryx;
    fixed_t	tryy;

    line_t*	ld;

    // warning: 'catch', 'throw', and 'try'
    // are all C++ reserved words
    boolean	try_ok;
    boolean	good;

    if (actor->movedir == DI_NODIR)
        return false;

    if ((unsigned)actor->movedir >= 8)
        I_Error ("Weird actor->movedir!");

    tryx = actor->x + actor->info->speed*xspeed[actor->movedir];
    tryy = actor->y + actor->info->speed*yspeed[actor->movedir];

    try_ok = P_TryMove (actor, tryx, tryy);

    if (!try_ok)
    {
        // open any specials
        if (actor->flags & MF_FLOAT && floatok)
        {
            // must adjust height
            if (actor->z < tmfloorz)
                actor->z += FLOATSPEED; // [STRIFE] Note FLOATSPEED == 5*FRACUNIT
            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 is not a door
            // that can be opened,
            // return false
            if (P_UseSpecialLine (actor, ld,0))
                good = true;
        }
        return good;
    }
    else
    {
        actor->flags &= ~(MF_INFLOAT|MF_FEETCLIPPED);   // villsa [STRIFE]

        // villsa [STRIFE]
        if(P_GetTerrainType(actor) != FLOOR_SOLID)
            actor->flags |= MF_FEETCLIPPED;
    }


    // villsa [STRIFE] Removed pulling non-floating actors down to the ground.
    //  (haleyjd 09/05/10: Verified)
    /*if (! (actor->flags & MF_FLOAT) )	
          actor->z = actor->floorz;*/

    return true; 
}


//
// TryWalk
// Attempts to move actor on
// in its current (ob->moveangle) direction.
// If blocked by either a wall or an actor
// returns FALSE
// If move is either clear or blocked only by a door,
// returns TRUE and sets...
// If a door is in the way,
// an OpenDoor call is made to start it opening.
//
// haleyjd 09/05/10: [STRIFE] Verified unmodified.
//
boolean P_TryWalk (mobj_t* actor)
{
    if (!P_Move (actor))
    {
        return false;
    }

    actor->movecount = P_Random()&15;
    return true;
}



//
// P_NewChaseDir
//

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

    // villsa [STRIFE] don't bomb out and instead set spawnstate
    if(!actor->target)
    {
        //I_Error("P_NewChaseDir: called with no target");
        P_SetMobjState(actor, actor->info->spawnstate);
        return;
    }

    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 != (int) 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))
        {
            // either moved forward or attacked
            return;
        }
    }

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

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

                if ( P_TryWalk(actor) )
                    return;
            }
        }
    }
    else
    {
        for ( tdir=DI_SOUTHEAST;
              tdir != (DI_EAST-1);
              tdir-- )
        {
            if (tdir != (int) turnaround)
            {
                actor->movedir = tdir;

                if ( P_TryWalk(actor) )
                    return;
            }
        }
    }

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

    actor->movedir = DI_NODIR;	// can not move
}

//
// P_NewRandomDir
//
// villsa [STRIFE] new function
//
// haleyjd: Almost identical to the tail-end of P_NewChaseDir, this function 
// finds a purely random direction for an object to walk. Called from 
// A_RandomWalk.
//
// Shockingly similar to the RandomWalk pointer in Eternity :)
//
void P_NewRandomDir(mobj_t* actor)
{
    int dir = 0;
    int omovedir = opposite[actor->movedir]; // haleyjd 20110223: nerfed this...

    // randomly determine direction of search
    if(P_Random() & 1)
    {
        // Try all non-reversal directions forward, first
        for(dir = 0; dir < DI_NODIR; dir++)
        {
            if(dir != omovedir)
            {
                actor->movedir = dir;
                if(P_Random() & 1)
                {
                    if(P_TryWalk(actor))
                        break;
                }
            }
        }

        // haleyjd 20110223: logic missing entirely:
        // failed all non-reversal directions? try reversing
        if(dir > DI_SOUTHEAST) 
        {
            if(omovedir == DI_NODIR)
            {
                actor->movedir = DI_NODIR;
                return;
            }
            actor->movedir = omovedir;
            if(P_TryWalk(actor))
                return;
            else
            {
                actor->movedir = DI_NODIR;
                return;
            }
        }
    }
    else
    {
        // Try directions one at a time in backward order
        dir = DI_SOUTHEAST;
        while(1)
        {
            // haleyjd 09/05/10: missing random code.
            if(dir != omovedir)
            {
                actor->movedir = dir;

                // villsa 09/06/10: un-inlined code
                if(P_TryWalk(actor))
                    return;
            }

            // Ran out of non-reversal directions to try? Reverse.
            if(--dir == -1)
            {
                if(omovedir == DI_NODIR)
                {
                    actor->movedir = DI_NODIR;
                    return;
                }
                actor->movedir = omovedir;
                // villsa 09/06/10: un-inlined code
                if(P_TryWalk(actor))
                    return;
                else
                {
                    actor->movedir = DI_NODIR;
                    return;
                }
            } // end if(--dir == -1)
        } // end while(1)
    } // end else
}

// haleyjd 09/05/10: Needed below.
extern void P_BulletSlope (mobj_t *mo);

//
// P_LookForPlayers
//
// If allaround is false, only look 180 degrees in front.
// Returns true if a player is targeted.
//
// [STRIFE]
// haleyjd 09/05/10: Modifications to support friendly units.
//
boolean
P_LookForPlayers
( mobj_t*	actor,
  boolean	allaround )
{
    int         c;
    int         stop;
    player_t*   player;
    angle_t     an;
    fixed_t     dist;
    mobj_t  *   master = players[actor->miscdata].mo;

    // haleyjd 09/05/10: handle Allies
    if(actor->flags & MF_ALLY)
    {
        // Deathmatch: support team behavior for Rebels.
        if(netgame)
        {
            // Rebels adopt the allied player's target if it is not of the same
            // allegiance. Other allies do it unconditionally.
            if(master && master->target && 
               (master->target->type != MT_REBEL1 ||
                master->target->miscdata != actor->miscdata))
            {
                actor->target = master->target;
            }
            else
            {
                // haleyjd 09/06/10: Note that this sets actor->target in Strife!
                P_BulletSlope(actor);

                // Clear target if nothing is visible, or if the target is a
                // friendly Rebel or the allied player.
                if (linetarget == NULL
                 || (actor->target->type == MT_REBEL1
                  && actor->target->miscdata == actor->miscdata)
                 || actor->target == master)
                {
                    actor->target = NULL;
                    return false;
                }
            }
        }
        else
        {
            // Single-player: Adopt any non-allied player target.
            if(master && master->target && !(master->target->flags & MF_ALLY))
            {
                actor->target = master->target;
                return true;
            }

            // haleyjd 09/06/10: Note that this sets actor->target in Strife!
            P_BulletSlope(actor);

            // Clear target if nothing is visible, or if the target is an ally.
            if(!linetarget || actor->target->flags & MF_ALLY)
            {
                actor->target = NULL;
                return false;
            }
        }

        return true;
    }

    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)
        {
            // done looking
            return false;	
        }

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

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

    return false;
}

// haleyjd 09/05/10: [STRIFE] Removed A_KeenDie

//
// ACTION ROUTINES
//

//
// A_Look
// Stay in state until a player is sighted.
//
// [STRIFE]
// haleyjd 09/05/10: Adjusted for allies, Inquisitors, etc.
//
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) )
    {
        // [STRIFE] Allies wander when they call this.
        if(actor->flags & MF_ALLY)
            A_RandomWalk(actor);
        else
        {
            actor->target = targ;

            if ( actor->flags & MF_AMBUSH )
            {
                if (P_CheckSight (actor, actor->target))
                    goto seeyou;
            }
            else
                goto seeyou;
        }
    }

    // haleyjd 09/05/10: This is bizarre, as Rogue keeps using the GIVEQUEST flag
    // as a parameter to control allaround look behavior. Did they just run out of
    // flags, or what? 
    // STRIFE-TODO: Needs serious verification.
    if (!P_LookForPlayers(actor, (actor->flags & MF_GIVEQUEST) != 0))
        return;

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

        // [STRIFE] Removed DOOM random sounds.

        // [STRIFE] Only Inquisitors roar loudly here.
        if (actor->type == MT_INQUISITOR)
            emitter = NULL;

        S_StartSound (emitter, sound);
    }

    // [STRIFE] Set threshold (kinda odd as it's still set to 0 above...)
    actor->threshold = 20;

    P_SetMobjState (actor, actor->info->seestate);
}

//
// A_RandomWalk
//
// [STRIFE] New function.
// haleyjd 09/05/10: Action routine used to meander about.
//
void A_RandomWalk(mobj_t* actor)
{
    // Standing actors do not wander.
    if(actor->flags & MF_STAND)
        return;

    if(actor->reactiontime)
        actor->reactiontime--; // count down reaction time
    else
    {
        // turn to a new angle
        if(actor->movedir < DI_NODIR)
        {
            int delta;

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

        // try moving
        if(--actor->movecount < 0 || !P_Move(actor))
        {
            P_NewRandomDir(actor);
            actor->movecount += 5;
        }
    }
}

//
// A_FriendLook
//
// [STRIFE] New function
// haleyjd 09/05/10: Action function used mostly by mundane characters such as
// peasants.
//
void A_FriendLook(mobj_t* actor)
{
    mobj_t *soundtarget = actor->subsector->sector->soundtarget;

    actor->threshold = 0;

    if(soundtarget && soundtarget->flags & MF_SHOOTABLE)
    {
        // Handle allies, except on maps 3 and 34 (Front Base/Movement Base)
        if((actor->flags & MF_ALLY) == (soundtarget->flags & MF_ALLY) &&
            gamemap != 3 && gamemap != 34)
        {
            // STRIFE-TODO: Needs serious verification.
            if(P_LookForPlayers(actor, (actor->flags & MF_GIVEQUEST) != 0))
            {
                P_SetMobjState(actor, actor->info->seestate);
                actor->flags |= MF_NODIALOG;
                return;
            }
        }
        else
        {
            actor->target = soundtarget;

            if(!(actor->flags & MF_AMBUSH) || P_CheckSight(actor, actor->target))
            {
                actor->threshold = 10;
                P_SetMobjState(actor, actor->info->seestate);
                return;
            }
        }
    }

    // do some idle animation
    if(P_Random() < 30)
    {
        int t = P_Random();
        P_SetMobjState(actor, (t & 1) + actor->info->spawnstate + 1);
    }

    // wander around a bit
    if(!(actor->flags & MF_STAND) && P_Random() < 40)
        P_SetMobjState(actor, actor->info->spawnstate + 3);
}

//
// A_Listen
//
// [STRIFE] New function
// haleyjd 09/05/10: Action routine used to strictly listen for a target.
//
void A_Listen(mobj_t* actor)
{
    mobj_t *soundtarget;

    actor->threshold = 0;

    soundtarget = actor->subsector->sector->soundtarget;

    if(soundtarget && (soundtarget->flags & MF_SHOOTABLE))
    {
        if((actor->flags & MF_ALLY) != (soundtarget->flags & MF_ALLY))
        {
            actor->target = soundtarget;

            if(!(actor->flags & MF_AMBUSH) || P_CheckSight(actor, actor->target))
            {
                if(actor->info->seesound)
                    S_StartSound(actor, actor->info->seesound);

                actor->threshold = 10;

                P_SetMobjState(actor, actor->info->seestate);
            }
        }
    }
}


//
// A_Chase
// Actor has a melee attack,
// so it tries to close as fast as possible
//
// haleyjd 09/05/10: [STRIFE] Various minor changes
//
void A_Chase (mobj_t*	actor)
{
    int         delta;

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

    // modify target threshold
    if  (actor->threshold)
    {
        // haleyjd 20110204 [STRIFE]: No health <= 0 check here!
        if (actor->target)
            actor->threshold--;
        else
            actor->threshold = 0;
    }
    
    // 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))
            return; 	// got a new target

        P_SetMobjState (actor, actor->info->spawnstate);
        return;
    }
    
    // do not attack twice in a row
    if (actor->flags & MF_JUSTATTACKED)
    {
        actor->flags &= ~MF_JUSTATTACKED;
        // [STRIFE] Checks only against fastparm, not gameskill == 5
        if (!fastparm)
            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)
    {
        // [STRIFE] Checks only fastparm.
        if (!fastparm && actor->movecount)
        {
            goto nomissile;
        }

        if (!P_CheckMissileRange (actor))
            goto nomissile;

        P_SetMobjState (actor, actor->info->missilestate);

        // [STRIFE] Add NODIALOG flag to disable dialog
        actor->flags |= (MF_NODIALOG|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);
    }

    // [STRIFE] Changes to active sound behavior:
    // * Significantly more frequent
    // * Acolytes have randomized wandering sounds

    // make active sound
    if (actor->info->activesound && P_Random () < 38)
    {
        if(actor->info->activesound >= sfx_agrac1 &&
           actor->info->activesound <= sfx_agrac4)
        {
            S_StartSound(actor, sfx_agrac1 + P_Random() % 4);
        }
        else
            S_StartSound(actor, actor->info->activesound);
    }
}


//
// A_FaceTarget
//
// [STRIFE]
// haleyjd 09/05/10: Handling for visibility-modifying flags.
//
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)
    {
        // [STRIFE] increased SHADOW inaccuracy by a power of 2
        int t = P_Random();
        actor->angle += (t - P_Random()) << 22;
    }
    else if(actor->target->flags & MF_MVIS)
    {
        // [STRIFE] MVIS gives even worse aiming!
        int t = P_Random();
        actor->angle += (t - P_Random()) << 23;
    }
}

//
// A_PeasantPunch
//
// [STRIFE] New function
// haleyjd 09/05/10: Attack used by Peasants as a one-time retaliation
// when the player or a monster injures them. Weak doesn't begin to
// describe it :P
//
void A_PeasantPunch(mobj_t* actor)
{
    if(!actor->target)
        return;

    A_FaceTarget(actor);
    if(P_CheckMeleeRange(actor))
        P_DamageMobj(actor->target, actor, actor, 2 * (P_Random() % 5) + 2);
}

//
// A_ReaverAttack
//
// [STRIFE] New function
// haleyjd 09/06/10: Action routine used by Reavers to fire bullets.
// Also apparently used by Inquistors, though they don't seem to use
// it too often, as they're content to blow your face off with their
// HE grenades instead.
//
void A_ReaverAttack(mobj_t* actor)
{
    int i = 0;
    fixed_t slope;

    if(!actor->target)
        return;

    S_StartSound(actor, sfx_reavat);
    A_FaceTarget(actor);

    slope = P_AimLineAttack(actor, actor->angle, 2048*FRACUNIT);

    do
    {
        int     t          = P_Random();
        angle_t shootangle = actor->angle + ((t - P_Random()) << 20);
        int     damage     = 3*((P_Random() & 7) + 1);

        P_LineAttack(actor, shootangle, 2048*FRACUNIT, slope, damage);
        ++i;
    }
    while(i < 3);
}

//
// A_BulletAttack
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for generic bullet attacks. Used by
// a lot of different characters, including Acolytes, Rebels, and Macil.
//
void A_BulletAttack(mobj_t* actor)
{
    int t, damage;
    fixed_t slope;
    angle_t shootangle;

    if(!actor->target)
        return;

    S_StartSound(actor, sfx_rifle);
    A_FaceTarget(actor);
    
    slope = P_AimLineAttack(actor, actor->angle, 2048*FRACUNIT);
    t = P_Random();
    shootangle = ((t - P_Random()) << 19) + actor->angle;
    damage = 3 * (P_Random() % 5 + 1);

    P_LineAttack(actor, shootangle, 2048*FRACUNIT, slope, damage);
}

//
// A_CheckTargetVisible
//
// [STRIFE] New function
// haleyjd 09/06/10: Action routine which sets a thing back to its
// seestate at random, or if it cannot see its target, or its target
// is dead. Used by diverse actors.
//
void A_CheckTargetVisible(mobj_t* actor)
{
    A_FaceTarget(actor);

    if(P_Random() >= 30)
    {
        mobj_t *target = actor->target;
        
        if(!target || target->health <= 0 || !P_CheckSight(actor, target) ||
            P_Random() < 40)
        {
            P_SetMobjState(actor, actor->info->seestate);
        }
    }
}

//
// A_SentinelAttack
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function implementing the Sentinel's laser attack
// villsa 09/06/10 implemented
//
void A_SentinelAttack(mobj_t* actor)
{
    mobj_t* mo;
    mobj_t* mo2;
    fixed_t x;
    fixed_t y;
    fixed_t z;
    angle_t an;
    int i;

    mo = P_SpawnFacingMissile(actor, actor->target, MT_L_LASER);
    an = actor->angle >> ANGLETOFINESHIFT;

    if(mo->momy | mo->momx) // villsa - fixed typo (yes, they actually used '|' instead of'||')
    {
        for(i = 8; i > 1; i--)
        {
            x = mo->x + FixedMul(mobjinfo[MT_L_LASER].radius * i, finecosine[an]);
            y = mo->y + FixedMul(mobjinfo[MT_L_LASER].radius * i, finesine[an]);
            z = mo->z + i * (mo->momz >> 2);
            mo2 = P_SpawnMobj(x, y, z, MT_R_LASER);
            mo2->target = actor;
            mo2->momx = mo->momx;
            mo2->momy = mo->momy;
            mo2->momz = mo->momz;
            P_CheckMissileSpawn(mo2);
        }
    }

    mo->z += mo->momz >> 2;
}

//
// A_StalkerThink
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function to drive Stalker logic.
//
void A_StalkerThink(mobj_t* actor)
{
    statenum_t statenum;

    if(actor->flags & MF_NOGRAVITY)
    {
        if(actor->ceilingz - actor->info->height <= actor->z)
            return;
        statenum = S_SPID_11; // 1020
    }
    else
        statenum = S_SPID_18; // 1027

    P_SetMobjState(actor, statenum);
}

//
// A_StalkerSetLook
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function to marshall transitions to the
// Stalker's spawnstate.
//
void A_StalkerSetLook(mobj_t* actor)
{
    statenum_t statenum;

    if(!actor) // weird; totally unnecessary.
        return;

    if(actor->flags & MF_NOGRAVITY)
    {
        if(actor->state->nextstate == S_SPID_01) // 1010
            return;
        statenum = S_SPID_01; // 1010
    }
    else
    {
        if(actor->state->nextstate == S_SPID_02) // 1011
            return;
        statenum = S_SPID_02; // 1011
    }

    P_SetMobjState(actor, statenum);
}

//
// A_StalkerDrop
//
// [STRIFE] New function
// haleyjd 09/06/10: Dead simple: removes NOGRAVITY status.
//
void A_StalkerDrop(mobj_t* actor)
{
    actor->flags &= ~MF_NOGRAVITY;
}

//
// A_StalkerScratch
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for Stalker's attack.
//
void A_StalkerScratch(mobj_t* actor)
{
    if(actor->flags & MF_NOGRAVITY)
    {
        // Drop him down before he can attack
        P_SetMobjState(actor, S_SPID_11); // 1020
        return;
    }

    if(!actor->target)
        return;

    A_FaceTarget(actor);
    if(P_CheckMeleeRange(actor))
        P_DamageMobj(actor->target, actor, actor, 2 * (P_Random() % 8) + 2);
}

//
// A_FloatWeave
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function which is responsible for floating
// actors' constant upward and downward movement. Probably a really bad
// idea in retrospect given how dodgy the 3D clipping implementation is.
//
void A_FloatWeave(mobj_t* actor)
{
    fixed_t height;
    fixed_t z;

    if(actor->threshold)
        return;

    if(actor->flags & MF_INFLOAT)
        return;

    height = actor->info->height;         // v2
    z      = actor->floorz + 96*FRACUNIT; // v1

    if ( z > actor->ceilingz - height - 16*FRACUNIT )
        z = actor->ceilingz - height - 16*FRACUNIT;

    if ( z >= actor->z )
        actor->momz += FRACUNIT;
    else
        actor->momz -= FRACUNIT;

    if ( z == actor->z )
        actor->threshold = 4;
    else
        actor->threshold = 8;
}

//
// A_RobotMelee
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for Reaver and Templar melee attacks.
//
void A_RobotMelee(mobj_t* actor)
{
    if(!actor->target)
        return;

    A_FaceTarget(actor);
    if(P_CheckMeleeRange(actor))
    {
        S_StartSound(actor, sfx_revbld);
        P_DamageMobj(actor->target, actor, actor, 3 * (P_Random() % 8 + 1));
    }
}

//
// A_TemplarMauler
//
// [STRIFE] New function
// haleyjd 09/06/10: Exactly what it sounds like. Kicks your ass.
//
void A_TemplarMauler(mobj_t* actor)
{
    int i, t;
    int angle;
    int bangle;
    int damage;
    int slope;

    if(!actor->target)
        return;

    S_StartSound(actor, sfx_pgrdat);
    A_FaceTarget(actor);
    bangle = actor->angle;
    slope = P_AimLineAttack(actor, bangle, 2048*FRACUNIT);

    for(i = 0; i < 10; i++)
    {
        // haleyjd 09/06/10: Very carefully preserved order of P_Random calls
        damage = (P_Random() & 4) * 2;
        t = P_Random();
        angle = bangle + ((t - P_Random()) << 19);
        t = P_Random();
        slope = ((t - P_Random()) << 5) + slope;
        P_LineAttack(actor, angle, 2112*FRACUNIT, slope, damage);
    }
}

//
// A_CrusaderAttack
//
// villsa [STRIFE] new codepointer
// 09/06/10: Action function for the Crusader's Flamethrower.
// Very similar to the player's flamethrower, seeing how it was ripped
// off a Crusader by the Rat People ;)
//
void A_CrusaderAttack(mobj_t* actor)
{
    if(!actor->target)
        return;

    actor->z += (8*FRACUNIT);

    if(P_CheckRobotRange(actor))
    {
        A_FaceTarget(actor);
        actor->angle -= (ANG90 / 8);
        P_SpawnFacingMissile(actor, actor->target, MT_C_FLAME);
    }
    else if(P_CheckMissileRange(actor))
    {
        A_FaceTarget(actor);
        actor->z += (16*FRACUNIT);
        P_SpawnFacingMissile(actor, actor->target, MT_C_MISSILE);

        actor->angle -= (ANG45 / 32);
        actor->z -= (16*FRACUNIT);
        P_SpawnFacingMissile(actor, actor->target, MT_C_MISSILE);

        actor->angle += (ANG45 / 16);
        P_SpawnFacingMissile(actor, actor->target, MT_C_MISSILE);

        P_SetMobjState(actor, actor->info->seestate);
        actor->reactiontime += 15;
    }
    else
        P_SetMobjState(actor, actor->info->seestate);
    
    actor->z -= (8*FRACUNIT);
}

//
// A_CrusaderLeft
//
// villsa [STRIFE] new codepointer
//
void A_CrusaderLeft(mobj_t* actor)
{
    mobj_t* mo;

    actor->angle += (ANG90 / 16);
    mo = P_SpawnFacingMissile(actor, actor->target, MT_C_FLAME);
    mo->momz = FRACUNIT;
    mo->z += (16*FRACUNIT);

}

//
// A_CrusaderRight
//
// villsa [STRIFE] new codepointer
//
void A_CrusaderRight(mobj_t* actor)
{
    mobj_t* mo;

    actor->angle -= (ANG90 / 16);
    mo = P_SpawnFacingMissile(actor, actor->target, MT_C_FLAME);
    mo->momz = FRACUNIT;
    mo->z += (16*FRACUNIT);
}

//
// A_CheckTargetVisible2
//
// [STRIFE] New function
// haleyjd 09/06/10: Mostly the same as CheckTargetVisible, except without
// the randomness.
//
void A_CheckTargetVisible2(mobj_t* actor)
{
    if(!actor->target || actor->target->health <= 0 || 
        !P_CheckSight(actor, actor->target))
    {
        P_SetMobjState(actor, actor->info->seestate);
    }
}

//
// A_InqFlyCheck
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function to check if an Inquisitor wishes
// to take to flight.
//
void A_InqFlyCheck(mobj_t* actor)
{
    if(!actor->target)
        return;

    A_FaceTarget(actor);

    // if not in "robot" range, shoot grenades.
    if(!P_CheckRobotRange(actor))
        P_SetMobjState(actor, S_ROB3_14); // 1061

    if(actor->z != actor->target->z)
    {
        // Take off all zig!
        if(actor->z + actor->height + 54*FRACUNIT < actor->ceilingz)
            P_SetMobjState(actor, S_ROB3_17); // 1064
    }
}

//
// A_InqGrenade
//
// villsa [STRIFE] new codepointer
// 09/06/10: Inquisitor grenade attack action routine.
//
void A_InqGrenade(mobj_t* actor)
{
    mobj_t* mo;

    if(!actor->target)
        return;

    A_FaceTarget(actor);

    actor->z += MAXRADIUS;

    // grenade 1
    actor->angle -= (ANG45 / 32);
    mo = P_SpawnFacingMissile(actor, actor->target, MT_INQGRENADE);
    mo->momz += (9*FRACUNIT);

    // grenade 2
    actor->angle += (ANG45 / 16);
    mo = P_SpawnFacingMissile(actor, actor->target, MT_INQGRENADE);
    mo->momz += (16*FRACUNIT);

    actor->z -= MAXRADIUS;
}

//
// A_InqTakeOff
//
// [STRIFE] New function
// haleyjd 09/06/10: Makes an Inquisitor start flying.
//
void A_InqTakeOff(mobj_t* actor)
{
    angle_t an;
    fixed_t speed = actor->info->speed * (2 * FRACUNIT / 3);
    fixed_t dist;

    if(!actor->target)
        return;

    S_StartSound(actor, sfx_inqjmp);

    actor->z += 64 * FRACUNIT;

    A_FaceTarget(actor);

    an = actor->angle >> ANGLETOFINESHIFT;

    actor->momx = FixedMul(finecosine[an], speed);
    actor->momy = FixedMul(finesine[an],   speed);

    dist = P_AproxDistance(actor->target->x - actor->x,
                           actor->target->y - actor->y);

    dist /= speed;
    if(dist < 1)
        dist = 1;

    actor->momz = (actor->target->z - actor->z) / dist;
    actor->reactiontime = 60;
    actor->flags |= MF_NOGRAVITY;
}

//
// A_InqFly
//
// [STRIFE] New function
// haleyjd 09/06/10: Handles an Inquisitor in flight.
//
void A_InqFly(mobj_t* actor)
{
    if(!(leveltime & 7))
        S_StartSound(actor, sfx_inqjmp);

    if(--actor->reactiontime < 0 || !actor->momx || !actor->momy ||
        actor->z <= actor->floorz)
    {
        // Come in for a landing.
        P_SetMobjState(actor, actor->info->seestate);
        actor->reactiontime = 0;
        actor->flags &= ~MF_NOGRAVITY;
    }
}

//
// A_FireSigilWeapon
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for the Entity's attack.
//
void A_FireSigilWeapon(mobj_t* actor)
{
    int choice = P_Random() % 5;

    // STRIFE-TODO: Needs verification. This switch is just weird.
    switch(choice)
    {
    case 0:
        A_ProgrammerAttack(actor);
        break;
    // ain't not seen no case 1, bub...
    case 2:
        A_FireSigilEOffshoot(actor);
        break;
    case 3:
        A_SpectreCAttack(actor);
        break;
    case 4:
        A_SpectreDAttack(actor);
        break;
    case 5: // BUG: never used? wtf were they thinking?
        A_SpectreEAttack(actor);
        break;
    default:
        break;
    }
}

//
// A_ProgrammerAttack
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for the Programmer's main
// attack; equivalent to the player's first Sigil.
//
void A_ProgrammerAttack(mobj_t* actor)
{
    mobj_t *mo;

    if(!actor->target)
        return;

    mo = P_SpawnMobj(actor->target->x, actor->target->y, ONFLOORZ, 
                     MT_SIGIL_A_GROUND);
    mo->threshold = 25;
    mo->target = actor;
    mo->health = -2;
    mo->tracer = actor->target;
}

//
// A_Sigil_A_Action
//
// [STRIFE] New function
// haleyjd 09/06/10: Called by MT_SIGIL_A_GROUND to zot anyone nearby with
// corny looking lightning bolts.
//
void A_Sigil_A_Action(mobj_t* actor)
{
    int t, x, y, type;
    mobj_t *mo;

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

    t = P_Random();
    actor->momx += ((t & 3) - (P_Random() & 3)) << FRACBITS;
    t = P_Random();
    actor->momy += ((t & 3) - (P_Random() & 3)) << FRACBITS;

    t = P_Random();
    x = 50*FRACUNIT * ((t & 3) - (P_Random() & 3)) + actor->x;
    t = P_Random();
    y = 50*FRACUNIT * ((t & 3) - (P_Random() & 3)) + actor->y;

    if(actor->threshold <= 25)
        type = MT_SIGIL_A_ZAP_LEFT;
    else
        type = MT_SIGIL_A_ZAP_RIGHT;

    mo = P_SpawnMobj(x, y, ONCEILINGZ, type);
    mo->momz = -18 * FRACUNIT;
    mo->target = actor->target;
    mo->health = actor->health;

    mo = P_SpawnMobj(actor->x, actor->y,  ONCEILINGZ, MT_SIGIL_A_ZAP_RIGHT);
    mo->momz = -18 * FRACUNIT;
    mo->target = actor->target;
    mo->health = actor->health;
}

//
// A_SpectreEAttack
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for the Loremaster's Spectre.
// Equivalent to the player's final Sigil attack.
//
void A_SpectreEAttack(mobj_t* actor)
{
    mobj_t *mo;

    if(!actor->target)
        return;
    
    mo = P_SpawnMissile(actor, actor->target, MT_SIGIL_SE_SHOT);
    mo->health = -2;
}

//
// A_SpectreCAttack
//
// villsa [STRIFE] new codepointer
// 09/06/10: Action routine for the Oracle's Spectre. Equivalent to the player's
// third Sigil attack.
//
void A_SpectreCAttack(mobj_t* actor)
{
    mobj_t* mo;
    int i;

    if(!actor->target)
        return;

    mo = P_SpawnMobj(actor->x, actor->y, actor->z + (32*FRACUNIT), MT_SIGIL_A_ZAP_RIGHT);
    mo->momz = -(18*FRACUNIT);
    mo->target = actor;
    mo->health = -2;
    mo->tracer = actor->target;
    
    actor->angle -= ANG90;
    for(i = 0; i < 20; i++)
    {
        actor->angle += (ANG90 / 10);
        mo = P_SpawnMortar(actor, MT_SIGIL_C_SHOT);
        mo->health = -2;
        mo->z = actor->z + (32*FRACUNIT);
    }
    actor->angle -= ANG90;
}

//
// A_AlertSpectreC
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function called by the Oracle when it is
// killed. Finds an MT_SPECTRE_C anywhere on the map and awakens it.
//
void A_AlertSpectreC(mobj_t* actor)
{
    thinker_t *th;

    for(th = thinkercap.next; th != &thinkercap; th = th->next)
    {
        if(th->function.acp1 == (actionf_p1)P_MobjThinker)
        {
            mobj_t *mo = (mobj_t *)th;

            if(mo->type == MT_SPECTRE_C)
            {
                P_SetMobjState(mo, mo->info->seestate);
                mo->target = actor->target;
                return;
            }
        }
    }
}

//
// A_Sigil_E_Action
//
// villsa [STRIFE] new codepointer
// 09/06/10: Action routine for Sigil "E" shots. Spawns the room-filling
// lightning bolts that seem to often do almost nothing.
//
void A_Sigil_E_Action(mobj_t* actor)
{
    actor->angle += ANG90;
    P_SpawnMortar(actor, MT_SIGIL_E_OFFSHOOT);

    actor->angle -= ANG180;
    P_SpawnMortar(actor, MT_SIGIL_E_OFFSHOOT);

    actor->angle += ANG90;
    P_SpawnMortar(actor, MT_SIGIL_E_OFFSHOOT);

}

//
// A_SigilTrail
//
// villsa [STRIFE] new codepointer
//
void A_SigilTrail(mobj_t* actor)
{
    mobj_t* mo;

    mo = P_SpawnMobj(actor->x - actor->momx,
                     actor->y - actor->momy,
                     actor->z, MT_SIGIL_TRAIL);

    mo->angle = actor->angle;
    mo->health = actor->health;

}

//
// A_SpectreDAttack
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for Macil's Spectre.
// Equivalent of the player's fourth Sigil attack.
//
void A_SpectreDAttack(mobj_t* actor)
{
    mobj_t *mo;

    if(!actor->target)
        return;

    mo = P_SpawnMissile(actor, actor->target, MT_SIGIL_SD_SHOT);
    mo->health = -2;
    mo->tracer = actor->target;
}

//
// A_FireSigilEOffshoot
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function to fire part of a Sigil E
// attack. Used at least by the Entity.
//
void A_FireSigilEOffshoot(mobj_t* actor)
{
    mobj_t *mo;

    if(!actor->target)
        return;

    mo = P_SpawnMissile(actor, actor->target, MT_SIGIL_E_OFFSHOOT);
    mo->health = -2;
}

//
// A_ShadowOff
//
// villsa [STRIFE] new codepointer
// 09/06/10: Disables SHADOW and MVIS flags.
//
void A_ShadowOff(mobj_t* actor)
{
    actor->flags &= ~(MF_SHADOW|MF_MVIS);
}

//
// A_ModifyVisibility
//
// villsa [STRIFE] new codepointer
// 09/06/10: Turns on SHADOW, and turns off MVIS.
//
void A_ModifyVisibility(mobj_t* actor)
{
    actor->flags |= MF_SHADOW;
    actor->flags &= ~MF_MVIS;
}

//
// A_ShadowOn
//
// villsa [STRIFE] new codepointer
// 09/06/10: Turns on SHADOW and MVIS.
//
void A_ShadowOn(mobj_t* actor)
{
    actor->flags |= (MF_SHADOW|MF_MVIS);
}

//
// A_SetTLOptions
//
// villsa [STRIFE] new codepointer
// 09/06/10: Sets SHADOW and/or MVIS based on the thing's spawnpoint options.
//
void A_SetTLOptions(mobj_t* actor)
{
    if(actor->spawnpoint.options & MTF_TRANSLUCENT)
        actor->flags |= MF_SHADOW;
    if(actor->spawnpoint.options & MTF_MVIS)
        actor->flags |= MF_MVIS;
}

//
// A_BossMeleeAtk
//
// villsa [STRIFE] new codepointer
// 09/06/10: Gratuitous melee attack used by multiple boss characters,
// just for the sake of having one. It's not like anybody in their right
// mind would get close to any of the maniacs that use this ;)
//
void A_BossMeleeAtk(mobj_t* actor)
{
    if(!actor->target)
        return;

    P_DamageMobj(actor->target, actor, actor, 10 * (P_Random() & 9));
}

//
// A_BishopAttack
//
// villsa [STRIFE] new codepointer
// 09/06/10: Bishop's homing missile attack.
//
void A_BishopAttack(mobj_t* actor)
{
    mobj_t* mo;

    if(!actor->target)
        return;

    actor->z += MAXRADIUS;

    mo = P_SpawnMissile(actor, actor->target, MT_SEEKMISSILE);
    mo->tracer = actor->target;

    actor->z -= MAXRADIUS;
}

//
// A_FireHookShot
//
// villsa [STRIFE] new codepointer
// 09/06/10: Action function for the Loremaster's hookshot attack.
//
void A_FireHookShot(mobj_t* actor)
{
    if(!actor->target)
        return;

    P_SpawnMissile(actor, actor->target, MT_HOOKSHOT);
}

//
// A_FireChainShot
//
// villsa [STRIFE] new codepointer
// 09/06/10: Action function for the hookshot projectile. Spawns echoes
// to create a chain-like appearance.
//
void A_FireChainShot(mobj_t* actor)
{
    S_StartSound(actor, sfx_tend);

    P_SpawnMobj(actor->x, actor->y, actor->z, MT_CHAINSHOT); // haleyjd: fixed type

    P_SpawnMobj(actor->x - (actor->momx >> 1),
                actor->y - (actor->momy >> 1),
                actor->z, MT_CHAINSHOT);

    P_SpawnMobj(actor->x - actor->momx,
                actor->y - actor->momy,
                actor->z, MT_CHAINSHOT);
}

//
// A_MissileSmoke
//
// villsa [STRIFE] new codepointer
//
void A_MissileSmoke(mobj_t* actor)
{
    mobj_t* mo;

    S_StartSound(actor, sfx_rflite);
    P_SpawnPuff(actor->x, actor->y, actor->z);
    mo = P_SpawnMobj(actor->x - actor->momx,
                     actor->y - actor->momy,
                     actor->z, MT_MISSILESMOKE);

    mo->momz = FRACUNIT;
}

//
// A_SpawnSparkPuff
//
// villsa [STRIFE] new codepointer
//
void A_SpawnSparkPuff(mobj_t* actor)
{
    int r;
    mobj_t* mo;
    fixed_t x;
    fixed_t y;

    r = P_Random();
    x = (10*FRACUNIT) * ((r & 3) - (P_Random() & 3)) + actor->x;
    r = P_Random();
    y = (10*FRACUNIT) * ((r & 3) - (P_Random() & 3)) + actor->y;

    mo = P_SpawnMobj(x, y, actor->z, MT_SPARKPUFF);
    P_SetMobjState(mo, S_BNG4_01); // 199
    mo->momz = FRACUNIT;
}


// haleyjd 09/05/10: [STRIFE] Removed:
// A_PosAttack, A_SPosAttack, A_CPosAttack, A_CPosRefire, A_SpidRefire, 
// A_BspiAttack, A_TroopAttack, A_SargAttack, A_HeadAttack, A_CyberAttack,
// A_BruisAttack, A_SkelMissile


int TRACEANGLE = 0xE000000; // villsa [STRIFE] changed from 0xC000000 to 0xE000000

//
// A_Tracer
//
void A_Tracer (mobj_t* actor)
{
    angle_t exact;
    fixed_t dist;
    fixed_t slope;
    mobj_t* dest;
    //mobj_t* th;

    // villsa [STRIFE] removed all randomization and puff code

    // adjust direction
    dest = actor->tracer;

    if(!dest || dest->health <= 0)
        return;

    // change angle	
    exact = R_PointToAngle2(actor->x, actor->y, dest->x, dest->y);

    if(exact != actor->angle)
    {
        // villsa [STRIFE] slightly different algorithm
        if(exact - actor->angle <= 0x80000000)
        {
            actor->angle += TRACEANGLE;
            if(exact - actor->angle > 0x80000000)
                actor->angle = exact;
        }
        else
        {
            actor->angle -= TRACEANGLE;
            if (exact - actor->angle < 0x80000000)
                actor->angle = exact;
        }
    }

    exact = actor->angle>>ANGLETOFINESHIFT;
    actor->momx = FixedMul (actor->info->speed, finecosine[exact]);
    actor->momy = FixedMul (actor->info->speed, finesine[exact]);

    // change slope
    dist = P_AproxDistance (dest->x - actor->x,
        dest->y - actor->y);

    dist = dist / actor->info->speed;

    if (dist < 1)
        dist = 1;
    slope = (dest->z+40*FRACUNIT - actor->z) / dist;

    if (slope < actor->momz)
        actor->momz -= FRACUNIT/8;
    else
        actor->momz += FRACUNIT/8;
}

//
// A_ProgrammerMelee
//
// villsa [STRIFE] new codepointer
// 09/08/10: Melee attack for the Programmer.
// haleyjd - fixed damage formula
//
void A_ProgrammerMelee(mobj_t* actor)
{
    if(!actor->target)
        return;

    A_FaceTarget(actor);
    if(P_CheckMeleeRange(actor))
    {
        int damage = 6 * (P_Random() % 10 + 1);
        
        S_StartSound(actor, sfx_mtalht);
        P_DamageMobj(actor->target, actor, actor, damage);
    }

}

// haleyjd 09/05/10: [STRIFE] Removed:
// A_SkelWhoosh, A_SkelFist, PIT_VileCheck, A_VileChase, A_VileStart, 
// A_StartFire, A_FireCrackle, A_Fire, A_VileTarget, A_VileAttack
// A_FatRaise, A_FatAttack1, A_FatAttack2, A_FatAttack3, A_SkullAttack, 
// A_PainShootSkull, A_PainAttack, A_PainDie

//
// A_Scream
//
// villsa [STRIFE] 
// * Has no random death sounds, so play deathsound directly
// * Full-volume roars for the Entity and Inquisitor.
//
void A_Scream(mobj_t* actor)
{
    if(!actor->info->deathsound)
        return;

    // Check for bosses.
    if(actor->type == MT_ENTITY || actor->type == MT_INQUISITOR)
        S_StartSound(NULL, actor->info->deathsound);   // full volume
    else
        S_StartSound(actor, actor->info->deathsound);
}

//
// A_XScream
//
// villsa [STRIFE]
// * Robots will play deathsound while non-robots play the slop sfx
//
void A_XScream(mobj_t* actor)
{
    int sound;

    if(actor->flags & MF_NOBLOOD && actor->info->deathsound)
        sound = actor->info->deathsound;
    else
        sound = sfx_slop;

    S_StartSound(actor, sound);
}

//
// A_Pain
//
// villsa [STRIFE] 
// * Play random peasant sounds; otherwise play painsound directly
//
void A_Pain(mobj_t* actor)
{
    int sound = actor->info->painsound;

    if(sound)
    {
        if(sound >= sfx_pespna && sound <= sfx_pespnd)
            sound = sfx_pespna + (P_Random() % 4);
        
        S_StartSound(actor, sound);
    }
}

//
// A_PeasantCrash
//
// villsa [STRIFE] new codepointer
// 09/08/10: Called from Peasant's "crash" state (not to be confused with
// Heretic crash states), which is invoked when the Peasant has taken
// critical but sub-fatal damage. It will "bleed out" the rest of its
// health by calling this function repeatedly.
//
void A_PeasantCrash(mobj_t* actor)
{
    // Set NODIALOG, because you probably wouldn't feel like talking either
    // if somebody just stabbed you in the gut with a punch dagger...
    actor->flags |= MF_NODIALOG;

    if(!(P_Random() % 5))
    {
        A_Pain(actor);  // inlined in asm
        actor->health--;
    }

    if(actor->health <= 0)
        P_KillMobj(actor->target, actor);
}

//
// A_Fall
//
// [STRIFE]
// * Set NODIALOG, and clear NOGRAVITY and SHADOW
//
void A_Fall (mobj_t *actor)
{
    // villsa [STRIFE] set NODIALOG flag to stop dialog
    actor->flags |= MF_NODIALOG;

    // actor is on ground, it can be walked over
    // villsa [STRIFE] remove nogravity/shadow flags as well
    actor->flags &= ~(MF_SOLID|MF_NOGRAVITY|MF_SHADOW);
}

//
// A_HideZombie
//
// villsa [STRIFE] new codepointer
// Used by the "Becoming" Acolytes on the Loremaster's level.
//
void A_HideZombie(mobj_t* actor)
{
    line_t junk;

    junk.tag = 999;
    EV_DoDoor(&junk, vld_blazeClose);

    if(actor->target && actor->target->player)
        P_NoiseAlert(actor->target, actor); // inlined in asm
}

//
// A_MerchantPain
//
// villsa [STRIFE] new codepointer
// 09/08/10: Pain pointer for merchant characters. They close up shop for
// a while and set off the alarm.
//
void A_MerchantPain(mobj_t* actor)
{
    line_t junk;

    junk.tag = 999;
    EV_DoDoor(&junk, vld_shopClose);

    if(actor->target && actor->target->player)
        P_NoiseAlert(actor->target, actor); // inlined in asm
}

// haleyjd 09/05/10: Removed unused CheckBossEnd Choco routine.

// haleyjd 09/05/10: [STRIFE] Removed:
// A_Hoof, A_Metal, A_BabyMetal, A_OpenShotgun2, A_LoadShotgun2, 
// A_CloseShotgun2, A_BrainAwake, A_BrainPain, A_BrainScream, A_BrainExplode,
// A_BrainDie, A_BrainSpit, A_SpawnSound, A_SpawnFly

//
// A_ProgrammerDie
//
// villsa [STRIFE] new codepointer
// 09/08/10: Action routine for the Programmer's grisly death. Spawns the 
// separate mechanical base object and sends it flying off in some random 
// direction.
//
void A_ProgrammerDie(mobj_t* actor)
{
    int r;
    angle_t an;
    mobj_t* mo;

    mo = P_SpawnMobj(actor->x, actor->y, actor->z + 24*FRACUNIT, MT_PROGRAMMERBASE);

    // haleyjd 20110223: fix add w/ANG180
    r = P_Random();
    an = ((r - P_Random()) << 22) + actor->angle + ANG180;
    mo->angle = an;

    P_ThrustMobj(mo, an, mo->info->speed);  // inlined in asm

    mo->momz = P_Random() << 9;
}

//
// A_InqTossArm
//
// villsa [STRIFE] new codepointer
// 09/08/10: Inquisitor death action. Spawns an arm and tosses it.
//
void A_InqTossArm(mobj_t* actor)
{
    int r;
    angle_t an;
    mobj_t* mo;

    mo = P_SpawnMobj(actor->x, actor->y, actor->z + (24*FRACUNIT), MT_INQARM);

    r = P_Random();
    an = ((r - P_Random()) << 22) + actor->angle - ANG90;
    mo->angle = an;

    P_ThrustMobj(mo, an, mo->info->speed);  // inlined in asm

    mo->momz = P_Random() << 10;
}

//
// A_SpawnSpectreA
//
// villsa [STRIFE] new codepointer (unused)
// 09/08/10: Spawns Spectre A. Or would, if anything actually used this.
// This is evidence that the Programmer's spectre, which appears in the 
// Catacombs in the final version, was originally meant to be spawned
// after his death.
//
void A_SpawnSpectreA(mobj_t* actor)
{
    mobj_t* mo;

    mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SPECTRE_A);
    mo->momz = P_Random() << 9;
}

//
// A_SpawnSpectreB
//
// villsa [STRIFE] new codepointer
// 09/08/10: Action function to spawn the Bishop's spectre.
//
void A_SpawnSpectreB(mobj_t* actor)
{
    mobj_t* mo;

    mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SPECTRE_B);
    mo->momz = P_Random() << 9;
}

//
// A_SpawnSpectreC
//
// villsa [STRIFE] new codepointer (unused)
// 09/08/10: Action function to spawn the Oracle's spectre. Also
// unused, because the Oracle's spectre is already present on the
// map and is awakened on his death. Also left over from the 
// unreleased beta (and demo) versions.
//
void A_SpawnSpectreC(mobj_t* actor)
{
    mobj_t* mo;

    mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SPECTRE_C);
    mo->momz = P_Random() << 9;
}

//
// A_SpawnSpectreD
//
// villsa [STRIFE] new codepointer
// 09/08/10: Action function to spawn Macil's Spectre.
//
void A_SpawnSpectreD(mobj_t* actor)
{
    mobj_t* mo;

    mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SPECTRE_D);
    mo->momz = P_Random() << 9;
}

//
// A_SpawnSpectreE
//
// villsa [STRIFE] new codepointer
// 09/08/10: Action function to spawn the Loremaster's Spectre.
//
void A_SpawnSpectreE(mobj_t* actor)
{
    mobj_t* mo;

    mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SPECTRE_E);
    mo->momz = P_Random() << 9;
}

// [STRIFE] New statics - Remember the Entity's spawning position.
static fixed_t entity_pos_x = 0;
static fixed_t entity_pos_y = 0;
static fixed_t entity_pos_z = 0;

//
// A_SpawnEntity
//
// villsa [STRIFE] new codepointer
// 09/08/10: You will fall on your knees before the True God, the One Light.
//
void A_SpawnEntity(mobj_t* actor)
{
    mobj_t* mo;

    mo = P_SpawnMobj(actor->x, actor->y, actor->z + 70*FRACUNIT, MT_ENTITY);
    mo->momz = 5*FRACUNIT;

    entity_pos_x = mo->x;
    entity_pos_y = mo->y;
    entity_pos_z = mo->z;
}

//
// P_ThrustMobj
//
// villsa [STRIFE] new function
// Thrusts an thing in a specified force/direction
// Beware! This is inlined everywhere in the asm
//
void P_ThrustMobj(mobj_t *actor, angle_t angle, fixed_t force)
{
    angle_t an = angle >> ANGLETOFINESHIFT;
    actor->momx += FixedMul(finecosine[an], force);
    actor->momy += FixedMul(finesine[an],   force);
}

//
// A_EntityDeath
//
// [STRIFE]
// haleyjd 09/08/10: The death of the Entity's spectre brings forth
// three subentities, which are significantly less dangerous on their
// own but threatening together.
//
void A_EntityDeath(mobj_t* actor)
{
    mobj_t *subentity;
    angle_t an;
    fixed_t dist;

    dist = 2 * mobjinfo[MT_SUBENTITY].radius;

    // Subentity One
    an = actor->angle >> ANGLETOFINESHIFT;
    subentity = P_SpawnMobj(FixedMul(finecosine[an], dist) + entity_pos_x,
                            FixedMul(finesine[an],   dist) + entity_pos_y,
                            entity_pos_z, MT_SUBENTITY);
    subentity->target = actor->target;
    A_FaceTarget(subentity);
    P_ThrustMobj(subentity, subentity->angle, 625 << 13);

    // Subentity Two
    an = (actor->angle + ANG90) >> ANGLETOFINESHIFT;
    subentity = P_SpawnMobj(FixedMul(finecosine[an], dist) + entity_pos_x, 
                            FixedMul(finesine[an],   dist) + entity_pos_y,
                            entity_pos_z, MT_SUBENTITY);
    subentity->target = actor->target;
    P_ThrustMobj(subentity, actor->angle + ANG90, 4);
    A_FaceTarget(subentity);

    // Subentity Three
    an = (actor->angle - ANG90) >> ANGLETOFINESHIFT;
    subentity = P_SpawnMobj(FixedMul(finecosine[an], dist) + entity_pos_x, 
                            FixedMul(finesine[an],   dist) + entity_pos_y,
                            entity_pos_z, MT_SUBENTITY);
    subentity->target = actor->target;
    P_ThrustMobj(subentity, actor->angle - ANG90, 4);
    A_FaceTarget(subentity);
}

//
// A_SpawnZombie
//
// villsa [STRIFE] new codepointer
//
void A_SpawnZombie(mobj_t* actor)
{
    P_SpawnMobj(actor->x, actor->y, actor->z, MT_ZOMBIE);
}

//
// A_ZombieInSpecialSector
//
// villsa [STRIFE] new codepointer
//
void A_ZombieInSpecialSector(mobj_t* actor)
{
    sector_t* sector;
    fixed_t force;
    angle_t angle;
    int tagval;

    sector = actor->subsector->sector;
    if(actor->z != sector->floorheight)
        return;

    if(sector->special <= 15)
        P_DamageMobj(actor, NULL, NULL, 999);
    else if(sector->special == 18)
    {
        tagval = sector->tag - 100;
        force = (tagval % 10) << 12;
        angle = (tagval / 10) << 29;
        P_ThrustMobj(actor, angle, force);  // inlined in asm
    }
}

//
// A_CrystalExplode
//
// villsa [STRIFE] new codepointer
// Throws out debris from the Power Crystal and sets its sector floorheight
// to the lowest surrounding floor (this is maybe the only time a direct
// level-changing action is done by an object in this fashion in any of
// the DOOM engine games... they usually call a line special instead)
//
void A_CrystalExplode(mobj_t* actor)
{
    sector_t* sector;
    mobj_t* rubble;
    int i;
    int r;

    sector = actor->subsector->sector;
    sector->lightlevel = 0;
    sector->floorheight = P_FindLowestFloorSurrounding(sector);

    // spawn rubble
    for(i = 0; i < 8; i++)
    {
        rubble = P_SpawnMobj(actor->x, actor->y, actor->z, MT_RUBBLE1 + i);
        r = P_Random();
        rubble->momx = ((r & 0x0f) - (P_Random() & 7)) << FRACBITS;
        r = P_Random();
        rubble->momy = ((r & 7) - (P_Random() & 7)) << FRACBITS;
        rubble->momz = ((P_Random() & 3) << FRACBITS) + (7*FRACUNIT);
    }
}

// [STRIFE] New static global - buffer used for various player messages.
static char pmsgbuffer[80];

// 
// P_FreePrisoners
//
// haleyjd 09/08/10: [STRIFE] New function
// * Called when the prisoners get freed, obviously. Gives a
//   message and awards quest token 13.
//
void P_FreePrisoners(void)
{
    int i;

    DEH_snprintf(pmsgbuffer, sizeof(pmsgbuffer), "You've freed the prisoners!");

    for(i = 0; i < MAXPLAYERS; i++)
    {
        P_GiveItemToPlayer(&players[i], SPR_TOKN, MT_TOKEN_QUEST13);
        players[i].message = pmsgbuffer;
    }
}

//
// P_DestroyConverter
//
// haleyjd 09/08/10: [STRIFE] New function
// * Called when the converter is shut down in the factory.
//   Gives several items and a message.
//
void P_DestroyConverter(void)
{
    int i;

    DEH_snprintf(pmsgbuffer, sizeof(pmsgbuffer), "You've destroyed the Converter!");

    for(i = 0; i < MAXPLAYERS; i++)
    {
        P_GiveItemToPlayer(&players[i], SPR_TOKN, MT_TOKEN_QUEST25);
        P_GiveItemToPlayer(&players[i], SPR_TOKN, MT_TOKEN_STAMINA);
        P_GiveItemToPlayer(&players[i], SPR_TOKN, MT_TOKEN_NEW_ACCURACY);
        players[i].message = pmsgbuffer;
    }
}

//
// A_QuestMsg
//
// villsa [STRIFE] new codepointer
// Displays text based on quest item's name
// Quest item is based on actor's speed
//
void A_QuestMsg(mobj_t* actor)
{
    const char *name;
    int quest;
    int i;

    // get name
    name = DEH_String(mobjinfo[(MT_TOKEN_QUEST1 - 1) + actor->info->speed].name);
    M_StringCopy(pmsgbuffer, name, sizeof(pmsgbuffer));   // inlined in asm

    // give quest and display message to players
    for(i = 0; i < MAXPLAYERS; i++)
    {
        quest = 1 << (actor->info->speed - 1);
        players[i].message = pmsgbuffer;
        players[i].questflags |= quest;
    }
}

//
// A_ExtraLightOff
//
// villsa [STRIFE] new codepointer
// 09/08/10: Called by the Power Crystal to turn off the extended
// flash of light caused by its explosion.
//
void A_ExtraLightOff(mobj_t* actor)
{
    if(!actor->target)
        return;

    if(!actor->target->player)
        return;

    actor->target->player->extralight = 0;
}

//
// A_CrystalRadiusAtk
//
// villsa [STRIFE] new codepointer
// 09/08/10: Called by the power crystal when it dies.
//
void A_CrystalRadiusAtk(mobj_t* actor)
{
    P_RadiusAttack(actor, actor->target, 512);

    if(!(actor->target && actor->target->player))
        return;

    // set extralight to 5 for near full-bright
    actor->target->player->extralight = 5;
}

//
// A_DeathExplode5
//
// villsa [STRIFE] new codepointer
//
void A_DeathExplode5(mobj_t* actor)
{
    P_RadiusAttack(actor, actor->target, 192);
    if(actor->target && actor->target->player)
        P_NoiseAlert(actor->target, actor); // inlined in asm
}

//
// A_DeathExplode1
//
// villsa [STRIFE] new codepointer
//
void A_DeathExplode1(mobj_t* actor)
{
    P_RadiusAttack(actor, actor->target, 128);
    if(actor->target && actor->target->player)
        P_NoiseAlert(actor->target, actor); // inlined in asm
}

//
// A_DeathExplode2
//
// villsa [STRIFE] new codepointer
//
void A_DeathExplode2(mobj_t* actor)
{
    P_RadiusAttack(actor, actor->target, 64);
    if(actor->target && actor->target->player)
        P_NoiseAlert(actor->target, actor); // inlined in asm
}

//
// A_DeathExplode3
//
// villsa [STRIFE] new codepointer
//
void A_DeathExplode3(mobj_t* actor)
{
    P_RadiusAttack(actor, actor->target, 32);
    if(actor->target && actor->target->player)
        P_NoiseAlert(actor->target, actor); // inlined in asm
}

//
// A_RaiseAlarm
//
// villsa [STRIFE] new codepointer
// 09/08/10: Set off the infamous alarm. This is just a noise alert.
//
void A_RaiseAlarm(mobj_t* actor)
{
    if(actor->target && actor->target->player)
        P_NoiseAlert(actor->target, actor); // inlined in asm
}

//
// A_MissileTick
// villsa [STRIFE] - new codepointer
//
void A_MissileTick(mobj_t* actor)
{
    if(--actor->reactiontime <= 0)
    {
        P_ExplodeMissile(actor);
        actor->flags &= ~MF_MISSILE;
    }
}

//
// A_SpawnGrenadeFire
// villsa [STRIFE] - new codepointer
//
void A_SpawnGrenadeFire(mobj_t* actor)
{
    P_SpawnMobj(actor->x, actor->y, actor->z, MT_PFLAME);
}

//
// A_NodeChunk
//
// villsa [STRIFE] - new codepointer
// Throw out "nodes" from a spectral entity
//
void A_NodeChunk(mobj_t* actor)
{
    int r;
    mobj_t* mo;

    mo = P_SpawnMobj(actor->x, actor->y, actor->z + 10*FRACUNIT, MT_NODE);
    r = P_Random();
    mo->momx = ((r & 0x0f) - (P_Random() & 7)) << FRACBITS;
    r = P_Random();
    mo->momy = ((r & 7) - (P_Random() & 0x0f)) << FRACBITS;
    mo->momz = (P_Random() & 0x0f) << FRACBITS;
}

//
// A_HeadChunk
//
// villsa [STRIFE] - new codepointer
// Throw out the little "eye"-like object from a spectral entity when it dies.
//
void A_HeadChunk(mobj_t* actor)
{
    int r;
    mobj_t* mo;

    mo = P_SpawnMobj(actor->x, actor->y, actor->z + 10*FRACUNIT, MT_SPECTREHEAD);
    r = P_Random();
    mo->momx = ((r & 7) - (P_Random() & 0x0f)) << FRACBITS;
    r = P_Random();
    mo->momy = ((r & 0x0f) - (P_Random() & 7)) << FRACBITS;
    mo->momz = (P_Random() & 7) << FRACBITS;
}

//
// A_BurnSpread
// villsa [STRIFE] - new codepointer
//
void A_BurnSpread(mobj_t* actor)
{
    int t;
    mobj_t* mo;
    fixed_t x;
    fixed_t y;

    actor->momz -= (8*FRACUNIT);

    t = P_Random();
    actor->momx += ((t & 3) - (P_Random() & 3)) << FRACBITS;
    t = P_Random();
    actor->momy += ((t & 3) - (P_Random() & 3)) << FRACBITS;

    S_StartSound(actor, sfx_lgfire);

    if(actor->flags & MF_DROPPED)
        return; // not the parent

    // haleyjd 20110223: match order of calls in binary
    y = actor->y + (((P_Random() + 12) & 31) << FRACBITS);
    x = actor->x + (((P_Random() + 12) & 31) << FRACBITS);

    // spawn child
    mo = P_SpawnMobj(x, y, actor->z + (4*FRACUNIT), MT_PFLAME);

    t = P_Random();
    mo->momx += ((t & 7) - (P_Random() & 7)) << FRACBITS;
    t = P_Random();
    mo->momy += ((t & 7) - (P_Random() & 7)) << FRACBITS;
    mo->momz -= FRACUNIT;
    mo->flags |= MF_DROPPED;
    mo->reactiontime = (P_Random() & 3) + 2;
}

//
// A_BossDeath
//
// Possibly trigger special effects
// if on first boss level
//
// haleyjd 09/17/10: [STRIFE]
// * Modified to handle all Strife bosses.
//
void A_BossDeath (mobj_t* actor)
{
    int i;
    thinker_t *th;
    line_t junk;

    // only the following types can be a boss:
    switch(actor->type)
    {
    case MT_CRUSADER:
    case MT_SPECTRE_A:
    case MT_SPECTRE_B:
    case MT_SPECTRE_C:
    case MT_SPECTRE_D:
    case MT_SPECTRE_E:
    case MT_SUBENTITY:
    case MT_PROGRAMMER:
        break;
    default:
        return;
    }

    // check for a living player
    for(i = 0; i < MAXPLAYERS; i++)
    {
        if(playeringame[i] && players[i].health > 0)
            break;
    }
    if(i == MAXPLAYERS)
        return; // everybody's dead.

    // check for a still living boss
    for(th = thinkercap.next; th != &thinkercap; th = th->next)
    {
        if(th->function.acp1 == (actionf_p1) P_MobjThinker)
        {
            mobj_t *mo = (mobj_t *)th;

            if(mo != actor && mo->type == actor->type && mo->health > 0)
                return; // one is still alive.
        }
    }

    // Victory!
    switch(actor->type)
    {
    case MT_CRUSADER:
        junk.tag = 667;
        EV_DoFloor(&junk, lowerFloorToLowest);
        break;

    case MT_SPECTRE_A:
        GiveVoiceObjective("VOC95", "LOG95", 0);
        junk.tag = 999;
        EV_DoFloor(&junk, lowerFloorToLowest);
        break;

    case MT_SPECTRE_B:
        P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_BISHOP);
        GiveVoiceObjective("VOC74", "LOG74", 0);
        break;

    case MT_SPECTRE_C:
        // Look for an MT_ORACLE - this is for in case the player awakened the 
        // Oracle's spectre without killing the Oracle, which is possible by 
        // looking up to max and firing the Sigil at it. If this were not done,
        // a serious sequence break possibility would arise where one could 
        // kill both the Oracle AND Macil, possibly throwing the game out of
        // sorts entirely. Too bad they thought of it ;)  However this also
        // causes a bug sometimes! The Oracle, in its death state, sets the
        // Spectre C back to its seestate. If the Spectre C is already dead,
        // it becomes an undead ghost monster. Then it's a REAL spectre ;)
        for(th = thinkercap.next; th != &thinkercap; th = th->next)
        {
            if(th->function.acp1 == (actionf_p1) P_MobjThinker)
            {
                mobj_t *mo = (mobj_t *)th;

                // KILL ALL ORACLES! RAWWR!
                if(mo != actor && mo->type == MT_ORACLE && mo->health > 0)
                    P_KillMobj(actor, mo);
            }
        }
        P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_ORACLE);
        
        // Bishop is dead? - verify.
        if(players[0].questflags & QF_QUEST21) 
            P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_QUEST22);

        // Macil is dead?
        if(players[0].questflags & QF_QUEST24)
        {
            // Loremaster is dead?
            if(players[0].questflags & QF_QUEST26)
            {
                // We wield the complete sigil, blahblah
                GiveVoiceObjective("VOC85", "LOG85", 0);
            }
        }
        else
        {
            // So much for prognostication!
            GiveVoiceObjective("VOC87", "LOG87", 0);
        }
        junk.tag = 222;         // Open the exit door again;
        EV_DoDoor(&junk, vld_open); // Note this is NOT the Loremaster door...
        break;

    case MT_SPECTRE_D:
        P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_MACIL);
        if(players[0].questflags & QF_QUEST25) // Destroyed converter?
            GiveVoiceObjective("VOC106", "LOG106", 0);
        else
            GiveVoiceObjective("VOC79", "LOG79", 0);
        break;

    case MT_SPECTRE_E:
        P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_LOREMASTER);
        if(!netgame)
        {
            P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_STAMINA);
            P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_NEW_ACCURACY);
        }
        if(players[0].sigiltype == 4)
            GiveVoiceObjective("VOC85", "LOG85", 0);
        else
            GiveVoiceObjective("VOC83", "LOG83", 0);
        junk.tag = 666;
        EV_DoFloor(&junk, lowerFloorToLowest);
        break;

    case MT_SUBENTITY:
        F_StartFinale();
        break;

    case MT_PROGRAMMER:
        F_StartFinale();
        G_ExitLevel(0);
        break;

    default:
        // Real classy, Rogue.
        if(actor->type)
            I_Error("Error: Unconnected BossDeath id %d", actor->type);
        break;
    }
}

//
// A_AcolyteSpecial
//
// villsa [STRIFE] - new codepointer
// Awards quest #7 when all the Blue Acolytes are killed in Tarnhill
//
void A_AcolyteSpecial(mobj_t* actor)
{
    int i;
    thinker_t* th;

    if(actor->type != MT_GUARD8)
        return; // must be MT_GUARD8

    for(i = 0; i < MAXPLAYERS; i++)
    {
        if(playeringame[i] && players[i].health > 0)
            break;
    }

    if(i == 8)
        return;

    for(th = thinkercap.next; th != &thinkercap; th = th->next)
    {
        if(th->function.acp1 == (actionf_p1) P_MobjThinker)
        {
            mobj_t *mo = (mobj_t *)th;

            // Found a living MT_GUARD8?
            if(mo != actor && mo->type == actor->type && mo->health > 0)
                return;
        }
    }

    // All MT_GUARD8 are dead, give quest token #7 to all players
    for(i = 0; i < MAXPLAYERS; i++)
        P_GiveItemToPlayer(&players[i], SPR_TOKN, MT_TOKEN_QUEST7);

    // play voice, give objective
    GiveVoiceObjective("VOC14", "LOG14", 0);
}

//
// A_InqChase
// villsa [STRIFE] - new codepointer
//
void A_InqChase(mobj_t* actor)
{
    S_StartSound(actor, sfx_inqact);
    A_Chase(actor);
}

//
// A_StalkerChase
// villsa [STRIFE] - new codepointer
//
void A_StalkerChase(mobj_t* actor)
{
    S_StartSound(actor, sfx_spdwlk);
    A_Chase(actor);
}

//
// A_PlayerScream
//
// [STRIFE]
// * Modified to eliminate gamemode check and to use Strife sound.
//
void A_PlayerScream (mobj_t* mo)
{
    // Default death sound.
    int sound = sfx_pldeth;

    // villsa [STRIFE] don't check for gamemode
    if(mo->health < -50)
    {
        // IF THE PLAYER DIES
        // LESS THAN -50% WITHOUT GIBBING
        sound = sfx_plxdth;  // villsa [STRIFE] different sound
    }

    S_StartSound (mo, sound);
}

//
// A_TeleportBeacon
//
// villsa [STRIFE] - new codepointer
//
void A_TeleportBeacon(mobj_t* actor)
{
    mobj_t* mobj;
    mobj_t* fog;
    fixed_t fog_x;
    fixed_t fog_y;

    if(actor->target != players[actor->miscdata].mo)
        actor->target = players[actor->miscdata].mo;

    mobj = P_SpawnMobj(actor->x, actor->y, ONFLOORZ, MT_REBEL1);

    // haleyjd 20141024: missing code from disassembly; transfer allegiance
    // originally from master player to the rebel.
    mobj->miscdata = actor->miscdata;

    if(!P_TryMove(mobj, mobj->x, mobj->y))
    {
        // Rebel is probably stuck in something.. too bad
        P_RemoveMobj(mobj);
        return;
    }

    // beacon no longer special
    actor->flags &= ~MF_SPECIAL;

    // 20160306: set rebel threshold
    mobj->threshold = 100;

    // set rebel color and flags
    mobj->flags |= ((actor->miscdata << MF_TRANSSHIFT) | MF_NODIALOG);
    mobj->target = NULL;

    // double Rebel's health in deathmatch mode
    if(deathmatch)
        mobj->health <<= 1;

    if(actor->target)
    {
        mobj_t* targ = actor->target->target;

        if(targ)
        {
            if(targ->type != MT_REBEL1 || targ->miscdata != mobj->miscdata)
                mobj->target = targ;
        }
    }

    P_SetMobjState(mobj, mobj->info->seestate);
    mobj->angle = actor->angle;

    fog_x = mobj->x + FixedMul(20*FRACUNIT, finecosine[actor->angle>>ANGLETOFINESHIFT]);
    fog_y = mobj->y + FixedMul(20*FRACUNIT, finesine[actor->angle>>ANGLETOFINESHIFT]);

    fog = P_SpawnMobj(fog_x, fog_y, mobj->z, MT_TFOG);
    S_StartSound(fog, sfx_telept);

    if(--actor->health < 0)
        P_RemoveMobj(actor);
}

//
// A_BodyParts
//
// villsa [STRIFE] new codepointer
// 09/06/10: Spawns gibs when organic actors get splattered, or junk
// when robots explode.
//
void A_BodyParts(mobj_t* actor)
{
    mobjtype_t type;
    mobj_t* mo;
    angle_t an;

    if(actor->flags & MF_NOBLOOD) // Robots are flagged NOBLOOD
        type = MT_JUNK;
    else
        type = MT_MEAT;

    mo = P_SpawnMobj(actor->x, actor->y, actor->z + (24*FRACUNIT), type);
    P_SetMobjState(mo, mo->info->spawnstate + (P_Random() % 19));

    an = (P_Random() << 13) / 255;
    mo->angle = an << ANGLETOFINESHIFT;

    mo->momx = FixedMul(finecosine[an], (P_Random() & 0x0f) << FRACBITS);
    mo->momy = FixedMul(finesine[an], (P_Random() & 0x0f) << FRACBITS);
    mo->momz = (P_Random() & 0x0f) << FRACBITS;
}

//
// A_ClaxonBlare
//
// [STRIFE] New function
// haleyjd 09/08/10: The ever-dreadful Strife alarm!
//
void A_ClaxonBlare(mobj_t* actor)
{
    // Timer ran down?
    if(--actor->reactiontime < 0)
    {
        // reset to initial state
        actor->target = NULL;
        actor->reactiontime = actor->info->reactiontime;

        // listen for more noise
        A_Listen(actor);
        
        // If we heard something, stay on for a while,
        // otherwise return to spawnstate.
        if(actor->target)
            actor->reactiontime = 50;
        else
            P_SetMobjState(actor, actor->info->spawnstate);
    }

    // When almost ran down, clear the soundtarget so it doesn't
    // retrigger the alarm.
    // Also, play the harsh, grating claxon.
    if(actor->reactiontime == 2)
        actor->subsector->sector->soundtarget = NULL;
    else if(actor->reactiontime > 50)
        S_StartSound(actor, sfx_alarm);
}

//
// A_ActiveSound
//
// villsa [STRIFE] new codepointer
// 09/06/10: Plays an object's active sound periodically.
//
void A_ActiveSound(mobj_t* actor)
{
    if(actor->info->activesound)
    {
        if(!(leveltime & 7)) // haleyjd: added parens
            S_StartSound(actor, actor->info->activesound);
    }
}

//
// A_ClearSoundTarget
//
// villsa [STRIFE] new codepointer
// 09/06/10: Clears the actor's sector soundtarget, so that the actor
// will not be continually alerted/awakened ad infinitum. Used by
// shopkeepers.
//
void A_ClearSoundTarget(mobj_t* actor)
{
    actor->subsector->sector->soundtarget = NULL;
}

//
// A_DropBurnFlesh
//
// villsa [STRIFE] new codepointer
//
void A_DropBurnFlesh(mobj_t* actor)
{
    mobj_t* mo;
    mobjtype_t type;

    type = actor->type;

    mo = P_SpawnMobj(actor->x, actor->y, actor->z + (24*FRACUNIT), MT_BURNDROP);
    mo->momz = -FRACUNIT;

    actor->type = MT_SFIREBALL;
    P_RadiusAttack(actor, actor, 64);
    actor->type = type;
}

//
// A_FlameDeath
//
// villsa [STRIFE] new codepointer
// 09/06/10: Death animation for flamethrower fireballs.
//
void A_FlameDeath(mobj_t* actor)
{
    actor->flags |= MF_NOGRAVITY;
    actor->momz = (P_Random() & 3) << FRACBITS;
}

//
// A_ClearForceField
//
// villsa [STRIFE] new codepointer
// check for all matching lines in the sector
// and disable blocking/midtextures
//
void A_ClearForceField(mobj_t* actor)
{
    int i;
    sector_t *sec;
    line_t *secline;

    actor->flags &= ~(MF_SOLID|MF_SPECIAL);
    sec = actor->subsector->sector;

    if(!sec->linecount)
        return;

    for(i = 0; i < sec->linecount; i++)
    {
        secline = sec->lines[i];
        // BUG: will crash if 1S line has TWOSIDED flag!
        if(!(secline->flags & ML_TWOSIDED))
            continue;
        if(secline->special != 148)
            continue;

        secline->flags &= ~ML_BLOCKING;
        secline->special = 0;
        sides[secline->sidenum[0]].midtexture = 0;
        sides[secline->sidenum[1]].midtexture = 0;
    }
}

