/* Emacs style mode select   -*- C++ -*- */
/*-----------------------------------------------------------------------------*/

/* $Id:$*/

/* Copyright (C) 1993-1996 by id Software, Inc.*/

/* This source is available for distribution and/or modification*/
/* only under the terms of the DOOM Source Code License as*/
/* published by id Software. All rights reserved.*/

/* The source is distributed in the hope that it will be useful,*/
/* but WITHOUT ANY WARRANTY; without even the implied warranty of*/
/* FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License*/
/* for more details.*/

/* $Log:$*/

/* DESCRIPTION:*/
/*	Enemy thinking, AI.*/
/*	Action Pointer Functions*/
/*	that are associated with states/frames. */

/*-----------------------------------------------------------------------------*/

static const char
rcsid[] = "$Id: p_enemy.c,v 1.5 1997/02/03 22:45:11 b1 Exp $";

#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"
#include "z_zone.h"

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

/* Data.*/
#include "sounds.h"


#ifdef DIYINLINE
#include "inl_btitr.c"
#endif




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

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

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





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


static mobj_t*		soundtarget;

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

    /* wake up all monsters in this sector*/
    if (sec->validcount == validcount
	&& sec->soundtraversed <= soundblocks+1)
    {
	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.*/

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





/* P_CheckMeleeRange*/

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

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

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

    if ( actor->flags & MF_JUSTHIT )
    {
	/* the target just hit the enemy,*/
	/* so fight back!*/
	actor->flags &= ~MF_JUSTHIT;
	return true;
    }

    if (actor->reactiontime)
	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;

    if (actor->type == MT_VILE)
    {
	if (dist > 14*64)
	    return false;	/* too far away*/
    }


    if (actor->type == MT_UNDEAD)
    {
	if (dist < 196)
	    return false;	/* close for fist attack*/
	dist >>= 1;
    }


    if (actor->type == MT_CYBORG
	|| actor->type == MT_SPIDER
	|| actor->type == MT_SKULL)
    {
	dist >>= 1;
    }

    if (dist > 200)
	dist = 200;

    if (actor->type == MT_CYBORG && dist > 160)
	dist = 160;

    if (P_Random () < dist)
	return false;

    return true;
}



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

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

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

    if (!try_ok)
    {
	/* open any specials*/
	if (actor->flags & MF_FLOAT && floatok)
	{
	    /* must adjust height*/
	    if (actor->z < tmfloorz)
		actor->z += FLOATSPEED;
	    else
		actor->z -= FLOATSPEED;

	    actor->flags |= MF_INFLOAT;
	    return true;
	}

	if (!numspechit)
	    return false;

	actor->movedir = DI_NODIR;
	good = false;
	while (numspechit--)
	{
	    ld = spechit[numspechit];
	    /* if the special is not a door*/
	    /* that can be opened,*/
	    /* return false*/
	    if (P_UseSpecialLine (actor, ld,0))
		good = true;
	}
	BOOMSTATEMENT(if (good && !compatibility) good = (P_Random() & 3);)
	return good;
    }
    else
    {
	actor->flags &= ~MF_INFLOAT;
    }


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

static boolean P_TryWalk (mobj_t* actor)
{
    if (!P_Move (actor))
    {
	return false;
    }

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




static void P_NewChaseDir (mobj_t* actor)
{
    fixed_t	deltax;
    fixed_t	deltay;

    dirtype_t	d[3];

    int		tdir;
    dirtype_t	olddir;

    dirtype_t	turnaround;

    if (!actor->target)
    {
	fprintf (logfile, "P_NewChaseDir: called with no target");
        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_LookForPlayers*/
/* If allaround is false, only look 180 degrees in front.*/
/* Returns true if a player is targeted.*/

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

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



/* A_KeenDie*/
/* DOOM II special, map 32.*/
/* Uses special tag 666.*/

void A_KeenDie (mobj_t* mo)
{
    thinker_t*		th;
    mobj_t*		mo2;
    static line_t	junk;	/* don't put this on the stack! */

    A_Fall (mo);

    /* scan the remaining thinkers*/
    /* to see if all Keens 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 Keen not dead*/
	    return;
	}
    }

    junk.tag = 666;
    EV_DoDoor(&junk,openDoor);
}



/* ACTION ROUTINES*/



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

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

    actor->threshold = 0;	/* any shot will wake up*/
    targ = actor->subsector->sector->soundtarget;

    if (targ
	&& (targ->flags & MF_SHOOTABLE) )
    {
	actor->target = targ;

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


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

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

	switch (actor->info->seesound)
	{
	  case sfx_posit1:
	  case sfx_posit2:
	  case sfx_posit3:
	    sound = sfx_posit1+P_Random()%3;
	    break;

	  case sfx_bgsit1:
	  case sfx_bgsit2:
	    sound = sfx_bgsit1+P_Random()%2;
	    break;

	  default:
	    sound = actor->info->seesound;
	    break;
	}

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

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



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

void A_Chase (mobj_t*	actor)
{
    int		delta;

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


    /* modify target threshold*/
    if  (actor->threshold)
    {
	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;
	if (gameskill != sk_nightmare && !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)
    {
	if (gameskill < sk_nightmare
	    && !fastparm && actor->movecount)
	{
	    goto nomissile;
	}

	if (!P_CheckMissileRange (actor))
	    goto nomissile;

	P_SetMobjState (actor, actor->info->missilestate);
	actor->flags |= MF_JUSTATTACKED;
	return;
    }

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

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

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



/* 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)
    {
        int t = P_Random();	/* remove dependence on order of evaluation */
	actor->angle += (t-P_Random())<<21;
    }
}



/* A_PosAttack*/

void A_PosAttack (mobj_t* actor)
{
    int		angle;
    int		damage;
    int		slope;
    int		t;

    if (!actor->target)
	return;

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

    S_StartSound (actor, sfx_pistol);
    t = P_Random();	/* remove dependence on order of evaluation */
    angle += (t-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++)
    {
        int t;
        t = P_Random();	/* remove dependence on order of evaluation */
	angle = bangle + ((t-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;
    int		t;

    if (!actor->target)
	return;

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

    t = P_Random();	/* remove dependence on order or evaluation */
    angle = bangle + ((t-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;

    /* sync fix (see Boom source) */
    if ((gametic-levelstarttic) & 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.*/

static mobj_t*	corpsehit;
static mobj_t*	vileobj;
static fixed_t	viletryx;
static fixed_t	viletryy;

static 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;
    BOOMSTATEMENT(if (compatibility) {)
    corpsehit->height <<= 2;
    check = P_CheckPosition (corpsehit, corpsehit->x, corpsehit->y);
    corpsehit->height >>= 2;
    BOOMSTATEMENT(})
#ifdef DIYBOOM
    else
    {
        int height, radius;
        height = corpsehit->height;
        radius = corpsehit->radius;
        corpsehit->height = corpsehit->info->height;
        corpsehit->radius = corpsehit->info->radius;
        corpsehit->flags |= MF_SOLID;
        check = P_CheckPosition(corpsehit,corpsehit->x,corpsehit->y);
        corpsehit->height = height;
        corpsehit->radius = radius;
        corpsehit->flags &= ~MF_SOLID;
    }
#endif
    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);
		    BOOMSTATEMENT(if (compatibility))
		    corpsehit->height <<= 2;
#ifdef DIYBOOM
                    else
                    {
                        corpsehit->height = info->height;
                        corpsehit->radius = info->radius;
                    }
#endif
		    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) || (!actor->target))
	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;

    if (!actor->target)
        return;

    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;

    if (!actor->target)
        return;

    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;

    if (!actor->target)
        return;

    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;

    BOOMSTATEMENT(if (compatibility) {)
    /* 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;

    BOOMSTATEMENT(})

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

    BOOMSTATEMENT(if (compatibility))
    newmobj = P_SpawnMobj (x , y, z, MT_SKULL);
#ifdef DIYBOOM
    else
    {
	/* Check whether the Lost Soul is being fired through a 1-sided wall
	 * or an impassible line, or a "monsters can't cross" line. If it is,
	 * then we don't allow the spawn. This is a bug fix, but it should be
	 * considered an enhancement, since it may disturb existing demos, so
	 * don't do it in compatibility mode. */

	if (Check_Sides(actor,x,y))
            return;
	newmobj = P_SpawnMobj(x, y, z, MT_SKULL);

	/* Check to see if the new Lost Soul's z value is above the ceiling
	 * of its new sector, or below the floor. If so, kill it. */
	if ((newmobj->z > (newmobj->subsector->sector->ceilingheight - newmobj->height)) || (newmobj->z < newmobj->subsector->sector->floorheight))
	{
          P_DamageMobj(newmobj,actor,actor,10000);
          return;
	}
    }
#endif

    /* Check for movements.*/
    if (!P_TryMove (newmobj, newmobj->x, newmobj->y, false))
    {
	/* 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;
    static line_t	junk;	/* don't put this on the stack! */
    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);
}



static mobj_t**	braintargets=NULL;
static int	numbraintargets;
static int	braintargeton;

/* shared by game loading code and A_BrainAwake() */
int P_BrainInit(void)
{
    thinker_t*	thinker;
    mobj_t*	m;

    if (braintargets != NULL)
    {
        Z_Free(braintargets);
        braintargets = NULL;
    }

    /* find all the target spots*/
    braintargeton = 0;
    numbraintargets = 0;
    for (thinker = thinkercap.next; thinker != &thinkercap; thinker = thinker->next)
    {
        if (thinker->function.acp1 != (actionf_p1)P_MobjThinker)
            continue;
        m = (mobj_t*)thinker;
        if (m->type == MT_BOSSTARGET) numbraintargets++;
    }

    if (numbraintargets == 0) /* should only happen when loading games */
        return 0;

    braintargets = (mobj_t**)Z_Malloc(numbraintargets * sizeof(mobj_t*), PU_STATIC, NULL);

    numbraintargets = 0;
    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++;
	}
    }
    return numbraintargets;
}

void A_BrainAwake (mobj_t* mo)
{
    P_BrainInit();
    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;
    int		t;

    t = P_Random();	/* remove dependence on order of evaluation */
    x = mo->x + (t - 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);
}
