// Emacs style mode select   -*- C++ -*- 
//-----------------------------------------------------------------------------
//
// Copyright (C) 1993-1996 id Software, Inc.
// Copyright (C) 1996 Rogue Software, Inc.
// Copyright (C) 2009 James Haley and Samuel Villarreal
//
// 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.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// DESCRIPTION:
//      Enemy thinking, AI.
//      Action Pointer Functions
//      that are associated with states/frames. 
//
//-----------------------------------------------------------------------------

#include <stdlib.h>

#include "m_random.h"
#include "i_system.h"

#include "doomdef.h"
#include "p_local.h"

#include "s_sound.h"

#include "g_game.h"

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

// Data.
#include "sounds.h"




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

mobj_t*         soundtarget;

//
// P_RecursiveSound
//
// haleyjd: verified unmodified in Strife
//
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: verified unmodified in Strife
//
void
P_NoiseAlert
( mobj_t*       target,
  mobj_t*       emmiter )
{
    soundtarget = target;
    validcount++;
    P_RecursiveSound (emmiter->subsector->sector, 0);
}



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



//
// P_DoPunchAlert
//
// [STRIFE]
// haleyjd: Traverses sector links to find nearby guards who can see
// the player's dagger kills.
//
void P_DoPunchAlert(mobj_t *puncher, mobj_t *punchee)
{
   mobj_t *mo;
   
   // not when the alarm is already going off.
   if(punchee->subsector->sector->soundtarget)
      return;
      
   // must be alive
   if(punchee->health <= 0)
      return;
      
   // must have proper flags
   if(!(punchee->flags & MF_COUNTKILL) || (punchee->flags & MF_INCOMBAT))
      return;
   
   // make the punched guy hurt
   punchee->target = puncher;
   P_SetMobjState(punchee, punchee->info->painstate);
   
   // run through forward sector links
   for(mo = punchee->snext; mo; mo = mo->snext)
   {
      if(mo->health >= 0 &&                                        // must be alive
         mo->type >= MT_GUARD1 && mo->type <= MT_PGUARD &&         // must be Acolyte or Templar
         (P_CheckSight(mo, puncher) || P_CheckSight(mo, punchee))) // must be able to see
      {
         P_WakeUpThing(puncher, mo);
      }
   }
   
   // run through backward sector links
   for(mo = punchee->sprev; mo; mo = mo->sprev)
   {
      if(mo->health >= 0 &&
         mo->type >= MT_GUARD1 && mo->type <= MT_PGUARD &&
         (P_CheckSight(mo, puncher) || P_CheckSight(mo, punchee)))
      {
         P_WakeUpThing(puncher, mo);
      }
   }
}



//
// P_CheckMeleeRange
//
// haleyjd: verified unmodified in Strife
//
boolean P_CheckMeleeRange (mobj_t*      actor)
{
    mobj_t*     pl;
    fixed_t     dist;
        
    if (!actor->target)
        return false;
                
    pl = actor->target;
    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]
// haleyjd: modifications made for various Strife thing types.
//
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;

    // [STRIFE] changes start here vvvv    
    
    if(actor->type < MT_SHADOWGUARD)
    {
       if(actor->type >= MT_GUARD1 && actor->type <= MT_GUARD6)
          dist >>= 4;
    }
    else
    {
       if(actor->type <= MT_SHADOWGUARD ||
          actor->type >= MT_CRUSADER && 
          (actor->type <= MT_CRUSADER || actor->type == MT_SENTINEL))
          dist >>= 1;
    }          
    
    if(dist > 150)
       dist = 150;
    
    // [STRIFE] changes end here ^^^^
                            
    if (P_Random () < dist)
        return false;
                
    return true;
}

//
// P_CheckRobotRange
//
// [STRIFE]
// haleyjd: Called from A_InqFlyCheck and A_CrusaderAttack.
//
boolean P_CheckRobotRange(mobj_t *actor)
{
   boolean result = false;
   
   if(P_CheckSight(actor, actor->target))
   {
      if(!actor->reactiontime &&
         (P_AproxDistance(actor->x - actor->target->x,
                          actor->y - actor->target->y) - 64*FRACUNIT) >> FRACBITS < 200)
         result = true; 
   }
   
   return result;
}


//
// P_Move
//
// Move in the current direction,
// returns false if the move is blocked.
//
// [STRIFE]
// haleyjd: 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};

#define MAXSPECIALCROSS 8

extern  line_t* spechit[MAXSPECIALCROSS];
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)
        {
            // [STRIFE] FIXME: FLOATSPEED == 5*FRACUNIT (p_local.h)
         
            // 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 is not a door
            // that can be opened,
            // return false
            if (P_UseSpecialLine (actor, ld,0))
                good = true;
        }
        return good;
    }
    else
    {
        // [STRIFE] changes begin here vvvv
        
        actor->flags &= ~(MF_INFLOAT|MF_FEETCLIPPED);
        
        if(P_GetTerrainType(actor) != FLOOR_SOLID)
           actor->flags |= MF_FEETCLIPPED;
        
        // [STRIFE] changes end here ^^^^
    }
            
    // [STRIFE] removed pulling down non-floaters to the floor
    // 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: verified unmodified in Strife.
//
boolean P_TryWalk (mobj_t* actor)
{       
    if (!P_Move (actor))
    {
        return false;
    }

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


//
// P_NewChaseDir
//
// [STRIFE]
// haleyjd: modified with spawnstate jump, but otherwise unchanged.
//
void P_NewChaseDir (mobj_t*     actor)
{
    fixed_t     deltax;
    fixed_t     deltay;
    
    dirtype_t   d[3];
    
    int         tdir;
    dirtype_t   olddir;
    
    dirtype_t   turnaround;

    // [STRIFE]: return to spawnstate instead of I_Error
    if(!actor->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 != 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!=turnaround)
            {
                actor->movedir =tdir;
                
                if ( P_TryWalk(actor) )
                    return;
            }
        }
    }
    else
    {
        for ( tdir=DI_SOUTHEAST;
              tdir != (DI_EAST-1);
              tdir-- )
        {
            if (tdir!=turnaround)
            {
                actor->movedir =tdir;
                
                if ( P_TryWalk(actor) )
                    return;
            }
        }
    }

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

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


//
// P_NewRandomDir
//
// [STRIFE]
// haleyjd: 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         tdir;
    dirtype_t   olddir;
    dirtype_t   turnaround;
    
    olddir = actor->movedir;
    turnaround=opposite[olddir];

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

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

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


//
// P_LookForPlayers
//
// If allaround is false, only look 180 degrees in front.
// Returns true if a player is targeted.
//
// [STRIFE]
// haleyjd: modifications to support friendly units
//
boolean
P_LookForPlayers
( mobj_t*       actor,
  boolean       allaround )
{
    int         c;
    int         stop;
    player_t*   player;
    sector_t*   sector;
    angle_t     an;
    fixed_t     dist;
    
    // [STRIFE] changes start here vvvv
    
    // haleyjd: handle allies
    if(actor->flags & MF_ALLY)
    {
        mobj_t *master = players[actor->allegiance].mo;
        
        // deathmatch: support team behavior for rebels.        
        if(netgame)
        {
            // Rebels adopt allied player's target if not of the same allegiance.
            // Other allies do it unconditionally.
            if(master && master->target && 
               (master->target->type != MT_REBEL1 || 
                master->target->allegiance != actor->allegiance))
            {
                actor->target = master->target;
            }
            else 
            {
                P_BulletSlope(actor);
                
                // clear target if nothing is visible or the target is a 
                // friendly rebel.
                if(!linetarget ||
                   actor->target->type == MT_REBEL1 && 
                   actor->target->allegiance == actor->allegiance ||
                   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;
            }
        
            P_BulletSlope(actor);
        
            // clear target if nothing is visible or the target is an ally.        
            if(!linetarget || actor->target->flags & MF_ALLY)
            {
                actor->target = NULL;
                return false;
            }
        }
        
        return true;
    }
    
    // [STRIFE] changes end here ^^^^
                
    sector = actor->subsector->sector;
        
    c = 0;
    stop = (actor->lastlook-1)&3;
        
    for ( ; ; actor->lastlook = (actor->lastlook+1)&3 )
    {
        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;
}

//
// ACTION ROUTINES
//

//
// A_Look
// Stay in state until a player is sighted.
//
// [STRIFE]
// haleyjd: 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;
        }
    }
     
    // [STRIFE]
    // haleyjd: odd use of MF_GIVEQUEST flag to control allaround-look behavior.
    // Probably because Rogue ran out of flag bits (that's my best guess anyway).  
    if (!P_LookForPlayers (actor, actor->flags & MF_GIVEQUEST) )
        return;
                
    // go into chase state
  seeyou:
    if (actor->info->seesound)
    {
        int sound;

        // [STRIFE] Inquisitors have full-volume wakeup sound                
        if (actor->type == MT_INQUISITOR)
        {
            // full volume
            S_StartSound (NULL, sound);
        }
        else
            S_StartSound (actor, sound);
    }

    // [STRIFE] Set threshold.
    actor->threshold = 20;
    P_SetMobjState (actor, actor->info->seestate);
}


//
// A_RandomWalk
//
// [STRIFE]
// haleyjd: Strife action routine used to meander around.
//
void A_RandomWalk(mobj_t *actor)
{
    // standing actors do not wander
    if(actor->flags & MF_STAND)
        return;
        
    if(actor->reactiontime)
        actor->reactiontime--; // count down reactiontime
    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]
// haleyjd: Strife action routine used mostly by mundane characters like peasants.
//
void A_FriendLook(mobj_t *actor)
{
    mobj_t *soundtarget;
    
    actor->threshold = 0;
    
    soundtarget = actor->subsector->sector->soundtarget;
    
    if(soundtarget && soundtarget->flags & MF_SHOOTABLE)
    {
        // handle allies, except on maps 3 and 34 (Front/Movement Base)
        if((actor->flags & MF_ALLY) == (soundtarget->flags & MF_ALLY) &&
           gamemap != 3 && gamemap != 34)
        {
            if(P_LookForPlayers(actor, actor->flags & MF_GIVEQUEST))
            {
                P_SetMobjState(actor, actor->info->seestate);
                actor->flags |= MF_INCOMBAT;
                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)
        P_SetMobjState(actor, actor->info->spawnstate + 1 + (P_Random() & 1));
        
    if(!(actor->flags & MF_STAND) && P_Random() < 40)
        P_SetMobjState(actor, actor->info->spawnstate + 3);
}


//
// A_Listen
//
// [STRIFE]
// haleyjd: Strife action routine used to listen for a soundtarget.
//
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
//
// [STRIFE]
// haleyjd:
//
void A_Chase (mobj_t*   actor)
{
    int         delta;

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

    // modify target threshold
    if  (actor->threshold)
    {
        if (!actor->target
            || actor->target->health <= 0)
        {
            actor->threshold = 0;
        }
        else
            actor->threshold--;
    }
    
    // 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 fastparm, not against 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 INCOMBAT flag to disable dialog
        actor->flags |= (MF_INCOMBAT|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 begin here vvvv
    
    // make active sound - haleyjd: chance increased from 3 in DOOM to 38 in Strife
    if(actor->info->activesound && P_Random() < 38)
    {
        // haleyjd: Acolytes have randomized activesounds
        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);
    }
    
    // [STRIFE] changes end here ^^^^
}


//
// 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)
        actor->angle += (P_Random()-P_Random())<<21;
}


//
// A_PosAttack
//
void A_PosAttack (mobj_t* actor)
{
    int         angle;
    int         damage;
    int         slope;
        
    if (!actor->target)
        return;
                
    A_FaceTarget (actor);
    angle = actor->angle;
    slope = P_AimLineAttack (actor, angle, MISSILERANGE);

    S_StartSound (actor, sfx_pistol);
    angle += (P_Random()-P_Random())<<20;
    damage = ((P_Random()%5)+1)*3;
    P_LineAttack (actor, angle, MISSILERANGE, slope, damage);
}

void A_SPosAttack (mobj_t* actor)
{
    int         i;
    int         angle;
    int         bangle;
    int         damage;
    int         slope;
        
    if (!actor->target)
        return;

    S_StartSound (actor, sfx_shotgn);
    A_FaceTarget (actor);
    bangle = actor->angle;
    slope = P_AimLineAttack (actor, bangle, MISSILERANGE);

    for (i=0 ; i<3 ; i++)
    {
        angle = bangle + ((P_Random()-P_Random())<<20);
        damage = ((P_Random()%5)+1)*3;
        P_LineAttack (actor, angle, MISSILERANGE, slope, damage);
    }
}

void A_CPosAttack (mobj_t* actor)
{
    int         angle;
    int         bangle;
    int         damage;
    int         slope;
        
    if (!actor->target)
        return;

    S_StartSound (actor, sfx_shotgn);
    A_FaceTarget (actor);
    bangle = actor->angle;
    slope = P_AimLineAttack (actor, bangle, MISSILERANGE);

    angle = bangle + ((P_Random()-P_Random())<<20);
    damage = ((P_Random()%5)+1)*3;
    P_LineAttack (actor, angle, MISSILERANGE, slope, damage);
}

void A_CPosRefire (mobj_t* actor)
{       
    // keep firing unless target got out of sight
    A_FaceTarget (actor);

    if (P_Random () < 40)
        return;

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


void A_SpidRefire (mobj_t* actor)
{       
    // keep firing unless target got out of sight
    A_FaceTarget (actor);

    if (P_Random () < 10)
        return;

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

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

    // launch a missile
    P_SpawnMissile (actor, actor->target, MT_ARACHPLAZ);
}


//
// A_TroopAttack
//
void A_TroopAttack (mobj_t* actor)
{
    int         damage;
        
    if (!actor->target)
        return;
                
    A_FaceTarget (actor);
    if (P_CheckMeleeRange (actor))
    {
        S_StartSound (actor, sfx_claw);
        damage = (P_Random()%8+1)*3;
        P_DamageMobj (actor->target, actor, actor, damage);
        return;
    }

    
    // launch a missile
    P_SpawnMissile (actor, actor->target, MT_TROOPSHOT);
}


void A_SargAttack (mobj_t* actor)
{
    int         damage;

    if (!actor->target)
        return;
                
    A_FaceTarget (actor);
    if (P_CheckMeleeRange (actor))
    {
        damage = ((P_Random()%10)+1)*4;
        P_DamageMobj (actor->target, actor, actor, damage);
    }
}

void A_HeadAttack (mobj_t* actor)
{
    int         damage;
        
    if (!actor->target)
        return;
                
    A_FaceTarget (actor);
    if (P_CheckMeleeRange (actor))
    {
        damage = (P_Random()%6+1)*10;
        P_DamageMobj (actor->target, actor, actor, damage);
        return;
    }
    
    // launch a missile
    P_SpawnMissile (actor, actor->target, MT_HEADSHOT);
}

void A_CyberAttack (mobj_t* actor)
{       
    if (!actor->target)
        return;
                
    A_FaceTarget (actor);
    P_SpawnMissile (actor, actor->target, MT_ROCKET);
}


void A_BruisAttack (mobj_t* actor)
{
    int         damage;
        
    if (!actor->target)
        return;
                
    if (P_CheckMeleeRange (actor))
    {
        S_StartSound (actor, sfx_claw);
        damage = (P_Random()%8+1)*10;
        P_DamageMobj (actor->target, actor, actor, damage);
        return;
    }
    
    // launch a missile
    P_SpawnMissile (actor, actor->target, MT_BRUISERSHOT);
}


//
// A_SkelMissile
//
void A_SkelMissile (mobj_t* actor)
{       
    mobj_t*     mo;
        
    if (!actor->target)
        return;
                
    A_FaceTarget (actor);
    actor->z += 16*FRACUNIT;    // so missile spawns higher
    mo = P_SpawnMissile (actor, actor->target, MT_TRACER);
    actor->z -= 16*FRACUNIT;    // back to normal

    mo->x += mo->momx;
    mo->y += mo->momy;
    mo->tracer = actor->target;
}

int     TRACEANGLE = 0xc000000;

void A_Tracer (mobj_t* actor)
{
    angle_t     exact;
    fixed_t     dist;
    fixed_t     slope;
    mobj_t*     dest;
    mobj_t*     th;
                
    if (gametic & 3)
        return;
    
    // spawn a puff of smoke behind the rocket          
    P_SpawnPuff (actor->x, actor->y, actor->z);
        
    th = P_SpawnMobj (actor->x-actor->momx,
                      actor->y-actor->momy,
                      actor->z, MT_SMOKE);
    
    th->momz = FRACUNIT;
    th->tics -= P_Random()&3;
    if (th->tics < 1)
        th->tics = 1;
    
    // 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)
    {
        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;
}


void A_SkelWhoosh (mobj_t*      actor)
{
    if (!actor->target)
        return;
    A_FaceTarget (actor);
    S_StartSound (actor,sfx_skeswg);
}

void A_SkelFist (mobj_t*        actor)
{
    int         damage;

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



//
// PIT_VileCheck
// Detect a corpse that could be raised.
//
mobj_t*         corpsehit;
mobj_t*         vileobj;
fixed_t         viletryx;
fixed_t         viletryy;

boolean PIT_VileCheck (mobj_t*  thing)
{
    int         maxdist;
    boolean     check;
        
    if (!(thing->flags & MF_CORPSE) )
        return true;    // not a monster
    
    if (thing->tics != -1)
        return true;    // not lying still yet
    
    if (thing->info->raisestate == S_NULL)
        return true;    // monster doesn't have a raise state
    
    maxdist = thing->info->radius + mobjinfo[MT_VILE].radius;
        
    if ( abs(thing->x - viletryx) > maxdist
         || abs(thing->y - viletryy) > maxdist )
        return true;            // not actually touching
                
    corpsehit = thing;
    corpsehit->momx = corpsehit->momy = 0;
    corpsehit->height <<= 2;
    check = P_CheckPosition (corpsehit, corpsehit->x, corpsehit->y);
    corpsehit->height >>= 2;

    if (!check)
        return true;            // doesn't fit here
                
    return false;               // got one, so stop checking
}



//
// A_VileChase
// Check for ressurecting a body
//
void A_VileChase (mobj_t* actor)
{
    int                 xl;
    int                 xh;
    int                 yl;
    int                 yh;
    
    int                 bx;
    int                 by;

    mobjinfo_t*         info;
    mobj_t*             temp;
        
    if (actor->movedir != DI_NODIR)
    {
        // check for corpses to raise
        viletryx =
            actor->x + actor->info->speed*xspeed[actor->movedir];
        viletryy =
            actor->y + actor->info->speed*yspeed[actor->movedir];

        xl = (viletryx - bmaporgx - MAXRADIUS*2)>>MAPBLOCKSHIFT;
        xh = (viletryx - bmaporgx + MAXRADIUS*2)>>MAPBLOCKSHIFT;
        yl = (viletryy - bmaporgy - MAXRADIUS*2)>>MAPBLOCKSHIFT;
        yh = (viletryy - bmaporgy + MAXRADIUS*2)>>MAPBLOCKSHIFT;
        
        vileobj = actor;
        for (bx=xl ; bx<=xh ; bx++)
        {
            for (by=yl ; by<=yh ; by++)
            {
                // Call PIT_VileCheck to check
                // whether object is a corpse
                // that canbe raised.
                if (!P_BlockThingsIterator(bx,by,PIT_VileCheck))
                {
                    // got one!
                    temp = actor->target;
                    actor->target = corpsehit;
                    A_FaceTarget (actor);
                    actor->target = temp;
                                        
                    P_SetMobjState (actor, S_VILE_HEAL1);
                    S_StartSound (corpsehit, sfx_slop);
                    info = corpsehit->info;
                    
                    P_SetMobjState (corpsehit,info->raisestate);
                    corpsehit->height <<= 2;
                    corpsehit->flags = info->flags;
                    corpsehit->health = info->spawnhealth;
                    corpsehit->target = NULL;

                    return;
                }
            }
        }
    }

    // Return to normal attack.
    A_Chase (actor);
}


//
// A_VileStart
//
void A_VileStart (mobj_t* actor)
{
    S_StartSound (actor, sfx_vilatk);
}


//
// A_Fire
// Keep fire in front of player unless out of sight
//
void A_Fire (mobj_t* actor);

void A_StartFire (mobj_t* actor)
{
    S_StartSound(actor,sfx_flamst);
    A_Fire(actor);
}

void A_FireCrackle (mobj_t* actor)
{
    S_StartSound(actor,sfx_flame);
    A_Fire(actor);
}

void A_Fire (mobj_t* actor)
{
    mobj_t*     dest;
    unsigned    an;
                
    dest = actor->tracer;
    if (!dest)
        return;
                
    // don't move it if the vile lost sight
    if (!P_CheckSight (actor->target, dest) )
        return;

    an = dest->angle >> ANGLETOFINESHIFT;

    P_UnsetThingPosition (actor);
    actor->x = dest->x + FixedMul (24*FRACUNIT, finecosine[an]);
    actor->y = dest->y + FixedMul (24*FRACUNIT, finesine[an]);
    actor->z = dest->z;
    P_SetThingPosition (actor);
}



//
// A_VileTarget
// Spawn the hellfire
//
void A_VileTarget (mobj_t*      actor)
{
    mobj_t*     fog;
        
    if (!actor->target)
        return;

    A_FaceTarget (actor);

    fog = P_SpawnMobj (actor->target->x,
                       actor->target->x,
                       actor->target->z, MT_FIRE);
    
    actor->tracer = fog;
    fog->target = actor;
    fog->tracer = actor->target;
    A_Fire (fog);
}




//
// A_VileAttack
//
void A_VileAttack (mobj_t* actor)
{       
    mobj_t*     fire;
    int         an;
        
    if (!actor->target)
        return;
    
    A_FaceTarget (actor);

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

    S_StartSound (actor, sfx_barexp);
    P_DamageMobj (actor->target, actor, actor, 20);
    actor->target->momz = 1000*FRACUNIT/actor->target->info->mass;
        
    an = actor->angle >> ANGLETOFINESHIFT;

    fire = actor->tracer;

    if (!fire)
        return;
                
    // move the fire between the vile and the player
    fire->x = actor->target->x - FixedMul (24*FRACUNIT, finecosine[an]);
    fire->y = actor->target->y - FixedMul (24*FRACUNIT, finesine[an]);  
    P_RadiusAttack (fire, actor, 70 );
}




//
// Mancubus attack,
// firing three missiles (bruisers)
// in three different directions?
// Doesn't look like it. 
//
#define FATSPREAD       (ANG90/8)

void A_FatRaise (mobj_t *actor)
{
    A_FaceTarget (actor);
    S_StartSound (actor, sfx_manatk);
}


void A_FatAttack1 (mobj_t* actor)
{
    mobj_t*     mo;
    int         an;
        
    A_FaceTarget (actor);
    // Change direction  to ...
    actor->angle += FATSPREAD;
    P_SpawnMissile (actor, actor->target, MT_FATSHOT);

    mo = P_SpawnMissile (actor, actor->target, MT_FATSHOT);
    mo->angle += FATSPREAD;
    an = mo->angle >> ANGLETOFINESHIFT;
    mo->momx = FixedMul (mo->info->speed, finecosine[an]);
    mo->momy = FixedMul (mo->info->speed, finesine[an]);
}

void A_FatAttack2 (mobj_t* actor)
{
    mobj_t*     mo;
    int         an;

    A_FaceTarget (actor);
    // Now here choose opposite deviation.
    actor->angle -= FATSPREAD;
    P_SpawnMissile (actor, actor->target, MT_FATSHOT);

    mo = P_SpawnMissile (actor, actor->target, MT_FATSHOT);
    mo->angle -= FATSPREAD*2;
    an = mo->angle >> ANGLETOFINESHIFT;
    mo->momx = FixedMul (mo->info->speed, finecosine[an]);
    mo->momy = FixedMul (mo->info->speed, finesine[an]);
}

void A_FatAttack3 (mobj_t*      actor)
{
    mobj_t*     mo;
    int         an;

    A_FaceTarget (actor);
    
    mo = P_SpawnMissile (actor, actor->target, MT_FATSHOT);
    mo->angle -= FATSPREAD/2;
    an = mo->angle >> ANGLETOFINESHIFT;
    mo->momx = FixedMul (mo->info->speed, finecosine[an]);
    mo->momy = FixedMul (mo->info->speed, finesine[an]);

    mo = P_SpawnMissile (actor, actor->target, MT_FATSHOT);
    mo->angle += FATSPREAD/2;
    an = mo->angle >> ANGLETOFINESHIFT;
    mo->momx = FixedMul (mo->info->speed, finecosine[an]);
    mo->momy = FixedMul (mo->info->speed, finesine[an]);
}


//
// SkullAttack
// Fly at the player like a missile.
//
#define SKULLSPEED              (20*FRACUNIT)

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

    if (!actor->target)
        return;
                
    dest = actor->target;       
    actor->flags |= MF_SKULLFLY;

    S_StartSound (actor, actor->info->attacksound);
    A_FaceTarget (actor);
    an = actor->angle >> ANGLETOFINESHIFT;
    actor->momx = FixedMul (SKULLSPEED, finecosine[an]);
    actor->momy = FixedMul (SKULLSPEED, finesine[an]);
    dist = P_AproxDistance (dest->x - actor->x, dest->y - actor->y);
    dist = dist / SKULLSPEED;
    
    if (dist < 1)
        dist = 1;
    actor->momz = (dest->z+(dest->height>>1) - actor->z) / dist;
}


//
// A_PainShootSkull
// Spawn a lost soul and launch it at the target
//
void
A_PainShootSkull
( mobj_t*       actor,
  angle_t       angle )
{
    fixed_t     x;
    fixed_t     y;
    fixed_t     z;
    
    mobj_t*     newmobj;
    angle_t     an;
    int         prestep;
    int         count;
    thinker_t*  currentthinker;

    // count total number of skull currently on the level
    count = 0;

    currentthinker = thinkercap.next;
    while (currentthinker != &thinkercap)
    {
        if (   (currentthinker->function.acp1 == (actionf_p1)P_MobjThinker)
            && ((mobj_t *)currentthinker)->type == MT_SKULL)
            count++;
        currentthinker = currentthinker->next;
    }

    // if there are allready 20 skulls on the level,
    // don't spit another one
    if (count > 20)
        return;


    // okay, there's playe for another one
    an = angle >> ANGLETOFINESHIFT;
    
    prestep =
        4*FRACUNIT
        + 3*(actor->info->radius + mobjinfo[MT_SKULL].radius)/2;
    
    x = actor->x + FixedMul (prestep, finecosine[an]);
    y = actor->y + FixedMul (prestep, finesine[an]);
    z = actor->z + 8*FRACUNIT;
                
    newmobj = P_SpawnMobj (x , y, z, MT_SKULL);

    // Check for movements.
    if (!P_TryMove (newmobj, newmobj->x, newmobj->y))
    {
        // kill it immediately
        P_DamageMobj (newmobj,actor,actor,10000);       
        return;
    }
                
    newmobj->target = actor->target;
    A_SkullAttack (newmobj);
}


//
// A_PainAttack
// Spawn a lost soul and launch it at the target
// 
void A_PainAttack (mobj_t* actor)
{
    if (!actor->target)
        return;

    A_FaceTarget (actor);
    A_PainShootSkull (actor, actor->angle);
}


void A_PainDie (mobj_t* actor)
{
    A_Fall (actor);
    A_PainShootSkull (actor, actor->angle+ANG90);
    A_PainShootSkull (actor, actor->angle+ANG180);
    A_PainShootSkull (actor, actor->angle+ANG270);
}






void A_Scream (mobj_t* actor)
{
    int         sound;
        
    switch (actor->info->deathsound)
    {
      case 0:
        return;
                
      case sfx_podth1:
      case sfx_podth2:
      case sfx_podth3:
        sound = sfx_podth1 + P_Random ()%3;
        break;
                
      case sfx_bgdth1:
      case sfx_bgdth2:
        sound = sfx_bgdth1 + P_Random ()%2;
        break;
        
      default:
        sound = actor->info->deathsound;
        break;
    }

    // Check for bosses.
    if (actor->type==MT_SPIDER
        || actor->type == MT_CYBORG)
    {
        // full volume
        S_StartSound (NULL, sound);
    }
    else
        S_StartSound (actor, sound);
}


void A_XScream (mobj_t* actor)
{
    S_StartSound (actor, sfx_slop);     
}

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



void A_Fall (mobj_t *actor)
{
    // actor is on ground, it can be walked over
    actor->flags &= ~MF_SOLID;

    // So change this if corpse objects
    // are meant to be obstacles.
}


//
// A_Explode
//
void A_Explode (mobj_t* thingy)
{
    P_RadiusAttack ( thingy, thingy->target, 128 );
}


//
// A_BossDeath
// Possibly trigger special effects
// if on first boss level
//
void A_BossDeath (mobj_t* mo)
{
    thinker_t*  th;
    mobj_t*     mo2;
    line_t      junk;
    int         i;
                
    if ( gamemode == commercial)
    {
        if (gamemap != 7)
            return;
                
        if ((mo->type != MT_FATSO)
            && (mo->type != MT_BABY))
            return;
    }
    else
    {
        switch(gameepisode)
        {
          case 1:
            if (gamemap != 8)
                return;

            if (mo->type != MT_BRUISER)
                return;
            break;
            
          case 2:
            if (gamemap != 8)
                return;

            if (mo->type != MT_CYBORG)
                return;
            break;
            
          case 3:
            if (gamemap != 8)
                return;
            
            if (mo->type != MT_SPIDER)
                return;
            
            break;
            
          case 4:
            switch(gamemap)
            {
              case 6:
                if (mo->type != MT_CYBORG)
                    return;
                break;
                
              case 8: 
                if (mo->type != MT_SPIDER)
                    return;
                break;
                
              default:
                return;
                break;
            }
            break;
            
          default:
            if (gamemap != 8)
                return;
            break;
        }
                
    }

    
    // make sure there is a player alive for victory
    for (i=0 ; i<MAXPLAYERS ; i++)
        if (playeringame[i] && players[i].health > 0)
            break;
    
    if (i==MAXPLAYERS)
        return; // no one left alive, so do not end game
    
    // scan the remaining thinkers to see
    // if all bosses are dead
    for (th = thinkercap.next ; th != &thinkercap ; th=th->next)
    {
        if (th->function.acp1 != (actionf_p1)P_MobjThinker)
            continue;
        
        mo2 = (mobj_t *)th;
        if (mo2 != mo
            && mo2->type == mo->type
            && mo2->health > 0)
        {
            // other boss not dead
            return;
        }
    }
        
    // victory!
    if ( gamemode == commercial)
    {
        if (gamemap == 7)
        {
            if (mo->type == MT_FATSO)
            {
                junk.tag = 666;
                EV_DoFloor(&junk,lowerFloorToLowest);
                return;
            }
            
            if (mo->type == MT_BABY)
            {
                junk.tag = 667;
                EV_DoFloor(&junk,raiseToTexture);
                return;
            }
        }
    }
    else
    {
        switch(gameepisode)
        {
          case 1:
            junk.tag = 666;
            EV_DoFloor (&junk, lowerFloorToLowest);
            return;
            break;
            
          case 4:
            switch(gamemap)
            {
              case 6:
                junk.tag = 666;
                EV_DoDoor (&junk, blazeOpen);
                return;
                break;
                
              case 8:
                junk.tag = 666;
                EV_DoFloor (&junk, lowerFloorToLowest);
                return;
                break;
            }
        }
    }
        
    G_ExitLevel ();
}


void A_Hoof (mobj_t* mo)
{
    S_StartSound (mo, sfx_hoof);
    A_Chase (mo);
}

void A_Metal (mobj_t* mo)
{
    S_StartSound (mo, sfx_metal);
    A_Chase (mo);
}

void A_BabyMetal (mobj_t* mo)
{
    S_StartSound (mo, sfx_bspwlk);
    A_Chase (mo);
}

void
A_OpenShotgun2
( player_t*     player,
  pspdef_t*     psp )
{
    S_StartSound (player->mo, sfx_dbopn);
}

void
A_LoadShotgun2
( player_t*     player,
  pspdef_t*     psp )
{
    S_StartSound (player->mo, sfx_dbload);
}

void
A_ReFire
( player_t*     player,
  pspdef_t*     psp );

void
A_CloseShotgun2
( player_t*     player,
  pspdef_t*     psp )
{
    S_StartSound (player->mo, sfx_dbcls);
    A_ReFire(player,psp);
}



mobj_t*         braintargets[32];
int             numbraintargets;
int             braintargeton;

void A_BrainAwake (mobj_t* mo)
{
    thinker_t*  thinker;
    mobj_t*     m;
        
    // find all the target spots
    numbraintargets = 0;
    braintargeton = 0;
        
    thinker = thinkercap.next;
    for (thinker = thinkercap.next ;
         thinker != &thinkercap ;
         thinker = thinker->next)
    {
        if (thinker->function.acp1 != (actionf_p1)P_MobjThinker)
            continue;   // not a mobj

        m = (mobj_t *)thinker;

        if (m->type == MT_BOSSTARGET )
        {
            braintargets[numbraintargets] = m;
            numbraintargets++;
        }
    }
        
    S_StartSound (NULL,sfx_bossit);
}


void A_BrainPain (mobj_t*       mo)
{
    S_StartSound (NULL,sfx_bospn);
}


void A_BrainScream (mobj_t*     mo)
{
    int         x;
    int         y;
    int         z;
    mobj_t*     th;
        
    for (x=mo->x - 196*FRACUNIT ; x< mo->x + 320*FRACUNIT ; x+= FRACUNIT*8)
    {
        y = mo->y - 320*FRACUNIT;
        z = 128 + P_Random()*2*FRACUNIT;
        th = P_SpawnMobj (x,y,z, MT_ROCKET);
        th->momz = P_Random()*512;

        P_SetMobjState (th, S_BRAINEXPLODE1);

        th->tics -= P_Random()&7;
        if (th->tics < 1)
            th->tics = 1;
    }
        
    S_StartSound (NULL,sfx_bosdth);
}



void A_BrainExplode (mobj_t* mo)
{
    int         x;
    int         y;
    int         z;
    mobj_t*     th;
        
    x = mo->x + (P_Random () - P_Random ())*2048;
    y = mo->y;
    z = 128 + P_Random()*2*FRACUNIT;
    th = P_SpawnMobj (x,y,z, MT_ROCKET);
    th->momz = P_Random()*512;

    P_SetMobjState (th, S_BRAINEXPLODE1);

    th->tics -= P_Random()&7;
    if (th->tics < 1)
        th->tics = 1;
}


void A_BrainDie (mobj_t*        mo)
{
    G_ExitLevel ();
}

void A_BrainSpit (mobj_t*       mo)
{
    mobj_t*     targ;
    mobj_t*     newmobj;
    
    static int  easy = 0;
        
    easy ^= 1;
    if (gameskill <= sk_easy && (!easy))
        return;
                
    // shoot a cube at current target
    targ = braintargets[braintargeton];
    braintargeton = (braintargeton+1)%numbraintargets;

    // spawn brain missile
    newmobj = P_SpawnMissile (mo, targ, MT_SPAWNSHOT);
    newmobj->target = targ;
    newmobj->reactiontime =
        ((targ->y - mo->y)/newmobj->momy) / newmobj->state->tics;

    S_StartSound(NULL, sfx_bospit);
}



void A_SpawnFly (mobj_t* mo);

// travelling cube sound
void A_SpawnSound (mobj_t* mo)  
{
    S_StartSound (mo,sfx_boscub);
    A_SpawnFly(mo);
}

void A_SpawnFly (mobj_t* mo)
{
    mobj_t*     newmobj;
    mobj_t*     fog;
    mobj_t*     targ;
    int         r;
    mobjtype_t  type;
        
    if (--mo->reactiontime)
        return; // still flying
        
    targ = mo->target;

    // First spawn teleport fog.
    fog = P_SpawnMobj (targ->x, targ->y, targ->z, MT_SPAWNFIRE);
    S_StartSound (fog, sfx_telept);

    // Randomly select monster to spawn.
    r = P_Random ();

    // Probability distribution (kind of :),
    // decreasing likelihood.
    if ( r<50 )
        type = MT_TROOP;
    else if (r<90)
        type = MT_SERGEANT;
    else if (r<120)
        type = MT_SHADOWS;
    else if (r<130)
        type = MT_PAIN;
    else if (r<160)
        type = MT_HEAD;
    else if (r<162)
        type = MT_VILE;
    else if (r<172)
        type = MT_UNDEAD;
    else if (r<192)
        type = MT_BABY;
    else if (r<222)
        type = MT_FATSO;
    else if (r<246)
        type = MT_KNIGHT;
    else
        type = MT_BRUISER;              

    newmobj     = P_SpawnMobj (targ->x, targ->y, targ->z, type);
    if (P_LookForPlayers (newmobj, true) )
        P_SetMobjState (newmobj, newmobj->info->seestate);
        
    // telefrag anything in this spot
    P_TeleportMove (newmobj, newmobj->x, newmobj->y);

    // remove self (i.e., cube).
    P_RemoveMobj (mo);
}



void A_PlayerScream (mobj_t* mo)
{
    // Default death sound.
    int         sound = sfx_pldeth;
        
    if ( (gamemode == commercial)
        &&      (mo->health < -50))
    {
        // IF THE PLAYER DIES
        // LESS THAN -50% WITHOUT GIBBING
        sound = sfx_pdiehi;
    }
    
    S_StartSound (mo, sound);
}