0
0
Fork 0
mirror of https://github.com/id-Software/DOOM-3-BFG.git synced 2025-03-16 15:41:16 +00:00
doom3-bfg/doomclassic/doom/p_enemy.cpp
2022-09-05 22:25:33 +02:00

2306 lines
40 KiB
C++

/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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 3 of the License, or
(at your option) any later version.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "Precompiled.h"
#include "globaldata.h"
#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"
extern bool globalNetworking;
//
// P_NewChaseDir related LUT.
//
const dirtype_t opposite[] =
{
DI_WEST, DI_SOUTHWEST, DI_SOUTH, DI_SOUTHEAST,
DI_EAST, DI_NORTHEAST, DI_NORTH, DI_NORTHWEST, DI_NODIR
};
const dirtype_t diags[] =
{
DI_NORTHWEST, DI_NORTHEAST, DI_SOUTHWEST, DI_SOUTHEAST
};
extern "C" void A_Fall( mobj_t* actor, void* );
//
// ENEMY THINKING
// Enemies are allways spawned
// with targetplayer = -1, threshold = 0
// Most monsters are spawned unaware of all ::g->players,
// but some can be made preaware
//
//
// Called by P_NoiseAlert.
// Recursively traverse adjacent ::g->sectors,
// sound blocking ::g->lines cut off traversal.
//
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 == ::g->validcount
&& sec->soundtraversed <= soundblocks + 1 )
{
return; // already flooded
}
sec->validcount = ::g->validcount;
sec->soundtraversed = soundblocks + 1;
sec->soundtarget = ::g->soundtarget;
for( i = 0 ; i < sec->linecount ; i++ )
{
check = sec->lines[i];
if( !( check->flags & ML_TWOSIDED ) )
{
continue;
}
P_LineOpening( check );
if( ::g->openrange <= 0 )
{
continue; // closed door
}
if( ::g->sides[ check->sidenum[0] ].sector == sec )
{
other = ::g->sides[ check->sidenum[1] ] .sector;
}
else
{
other = ::g->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 )
{
::g->soundtarget = target;
::g->validcount++;
P_RecursiveSound( emmiter->subsector->sector, 0 );
}
//
// P_CheckMeleeRange
//
qboolean 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
//
qboolean 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.
//
const fixed_t xspeed[8] = {FRACUNIT, 47000, 0, -47000, -FRACUNIT, -47000, 0, 47000};
const fixed_t yspeed[8] = {0, 47000, FRACUNIT, 47000, 0, -47000, -FRACUNIT, -47000};
qboolean P_Move( mobj_t* actor )
{
fixed_t tryx;
fixed_t tryy;
line_t* ld;
// warning: 'catch', 'throw', and 'try'
// are all C++ reserved words
qboolean try_ok;
qboolean 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 && ::g->floatok )
{
// must adjust height
if( actor->z < ::g->tmfloorz )
{
actor->z += FLOATSPEED;
}
else
{
actor->z -= FLOATSPEED;
}
actor->flags |= MF_INFLOAT;
return true;
}
if( !::g->numspechit )
{
return false;
}
actor->movedir = DI_NODIR;
good = false;
while( ::g->numspechit-- )
{
ld = ::g->spechit[::g->numspechit];
// if the special is not a door
// that can be opened,
// return false
if( P_UseSpecialLine( actor, ld, 0 ) )
{
good = true;
}
}
return good;
}
else
{
actor->flags &= ~MF_INFLOAT;
}
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.
//
qboolean P_TryWalk( mobj_t* actor )
{
if( !P_Move( actor ) )
{
return false;
}
actor->movecount = P_Random() & 15;
return true;
}
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 )
{
I_Error( "P_NewChaseDir: called with no target" );
}
olddir = ( dirtype_t )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] = ( dirtype_t )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.
//
qboolean
P_LookForPlayers
( mobj_t* actor,
qboolean 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( !::g->playeringame[actor->lastlook] )
{
continue;
}
if( c++ == 2
|| actor->lastlook == stop )
{
// done looking
return false;
}
player = &::g->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;
}
extern "C" {
//
// A_KeenDie
// DOOM II special, map 32.
// Uses special tag 666.
//
void A_KeenDie( mobj_t* mo, void* )
{
thinker_t* th;
mobj_t* mo2;
line_t junk;
A_Fall( mo, 0 );
// scan the remaining thinkers
// to see if all Keens are dead
for( th = ::g->thinkercap.next ; th != &::g->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, opened );
}
//
// ACTION ROUTINES
//
//
// A_Look
// Stay in state until a player is sighted.
//
void A_Look( mobj_t* actor, void* )
{
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, ( statenum_t )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, void* )
{
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, ( statenum_t )actor->info->spawnstate );
return;
}
// do not attack twice in a row
if( actor->flags & MF_JUSTATTACKED )
{
actor->flags &= ~MF_JUSTATTACKED;
if( ::g->gameskill != sk_nightmare && !::g->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, ( statenum_t )actor->info->meleestate );
return;
}
// check for missile attack
if( actor->info->missilestate )
{
if( ::g->gameskill < sk_nightmare
&& !::g->fastparm && actor->movecount )
{
goto nomissile;
}
if( !P_CheckMissileRange( actor ) )
{
goto nomissile;
}
P_SetMobjState( actor, ( statenum_t )actor->info->missilestate );
actor->flags |= MF_JUSTATTACKED;
return;
}
// ?
nomissile:
// possibly choose another target
if( ::g->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, void* )
{
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, void* )
{
int angle;
int damage;
int slope;
if( !actor->target )
{
return;
}
A_FaceTarget( actor, 0 );
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, void* )
{
int i;
int angle;
int bangle;
int damage;
int slope;
if( !actor->target )
{
return;
}
S_StartSound( actor, sfx_shotgn );
A_FaceTarget( actor, 0 );
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, void* )
{
int angle;
int bangle;
int damage;
int slope;
if( !actor->target )
{
return;
}
S_StartSound( actor, sfx_shotgn );
A_FaceTarget( actor, 0 );
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, void* )
{
// keep firing unless target got out of sight
A_FaceTarget( actor, 0 );
if( P_Random() < 40 )
{
return;
}
if( !actor->target
|| actor->target->health <= 0
|| !P_CheckSight( actor, actor->target ) )
{
P_SetMobjState( actor, ( statenum_t )actor->info->seestate );
}
}
void A_SpidRefire( mobj_t* actor, void* )
{
// keep firing unless target got out of sight
A_FaceTarget( actor, 0 );
if( P_Random() < 10 )
{
return;
}
if( !actor->target
|| actor->target->health <= 0
|| !P_CheckSight( actor, actor->target ) )
{
P_SetMobjState( actor, ( statenum_t )actor->info->seestate );
}
}
void A_BspiAttack( mobj_t* actor, void* )
{
if( !actor->target )
{
return;
}
A_FaceTarget( actor, 0 );
// launch a missile
P_SpawnMissile( actor, actor->target, MT_ARACHPLAZ );
}
//
// A_TroopAttack
//
void A_TroopAttack( mobj_t* actor, void* )
{
int damage;
if( !actor->target )
{
return;
}
A_FaceTarget( actor, 0 );
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, void* )
{
int damage;
if( !actor->target )
{
return;
}
A_FaceTarget( actor, 0 );
if( P_CheckMeleeRange( actor ) )
{
damage = ( ( P_Random() % 10 ) + 1 ) * 4;
P_DamageMobj( actor->target, actor, actor, damage );
}
}
void A_HeadAttack( mobj_t* actor, void* )
{
int damage;
if( !actor->target )
{
return;
}
A_FaceTarget( actor, 0 );
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, void* )
{
if( !actor->target )
{
return;
}
A_FaceTarget( actor, 0 );
P_SpawnMissile( actor, actor->target, MT_ROCKET );
}
void A_BruisAttack( mobj_t* actor, void* )
{
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, void* )
{
mobj_t* mo;
if( !actor->target )
{
return;
}
A_FaceTarget( actor, 0 );
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;
}
void A_Tracer( mobj_t* actor, void* )
{
angle_t exact;
fixed_t dist;
fixed_t slope;
mobj_t* dest;
mobj_t* th;
//if (::g->gametic & 3)
//return;
// DHM - Nerve :: Demo fix - Keep the game state deterministic!!!
if( ::g->leveltime & 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 -= ::g->TRACEANGLE;
if( exact - actor->angle < 0x80000000 )
{
actor->angle = exact;
}
}
else
{
actor->angle += ::g->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, void* )
{
if( !actor->target )
{
return;
}
A_FaceTarget( actor, 0 );
S_StartSound( actor, sfx_skeswg );
}
void A_SkelFist( mobj_t* actor, void* )
{
int damage;
if( !actor->target )
{
return;
}
A_FaceTarget( actor, 0 );
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.
//
qboolean PIT_VileCheck( mobj_t* thing )
{
int maxdist;
qboolean 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 - ::g->viletryx ) > maxdist
|| abs( thing->y - ::g->viletryy ) > maxdist )
{
return true; // not actually touching
}
::g->corpsehit = thing;
::g->corpsehit->momx = ::g->corpsehit->momy = 0;
::g->corpsehit->height <<= 2;
check = P_CheckPosition( ::g->corpsehit, ::g->corpsehit->x, ::g->corpsehit->y );
::g->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, void* )
{
int xl;
int xh;
int yl;
int yh;
int bx;
int by;
const mobjinfo_t* info;
mobj_t* temp;
if( actor->movedir != DI_NODIR )
{
// check for corpses to raise
::g->viletryx =
actor->x + actor->info->speed * xspeed[actor->movedir];
::g->viletryy =
actor->y + actor->info->speed * yspeed[actor->movedir];
xl = ( ::g->viletryx - ::g->bmaporgx - MAXRADIUS * 2 ) >> MAPBLOCKSHIFT;
xh = ( ::g->viletryx - ::g->bmaporgx + MAXRADIUS * 2 ) >> MAPBLOCKSHIFT;
yl = ( ::g->viletryy - ::g->bmaporgy - MAXRADIUS * 2 ) >> MAPBLOCKSHIFT;
yh = ( ::g->viletryy - ::g->bmaporgy + MAXRADIUS * 2 ) >> MAPBLOCKSHIFT;
::g->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 = ::g->corpsehit;
A_FaceTarget( actor, 0 );
actor->target = temp;
P_SetMobjState( actor, S_VILE_HEAL1 );
S_StartSound( ::g->corpsehit, sfx_slop );
info = ::g->corpsehit->info;
P_SetMobjState( ::g->corpsehit, ( statenum_t )info->raisestate );
::g->corpsehit->height <<= 2;
::g->corpsehit->flags = info->flags;
::g->corpsehit->health = info->spawnhealth;
::g->corpsehit->target = NULL;
return;
}
}
}
}
// Return to normal attack.
A_Chase( actor, 0 );
}
//
// A_VileStart
//
void A_VileStart( mobj_t* actor, void* )
{
S_StartSound( actor, sfx_vilatk );
}
//
// A_Fire
// Keep fire in front of player unless out of sight
//
void A_Fire( mobj_t* actor, void* );
void A_StartFire( mobj_t* actor, void* )
{
S_StartSound( actor, sfx_flamst );
A_Fire( actor, 0 );
}
void A_FireCrackle( mobj_t* actor, void* )
{
S_StartSound( actor, sfx_flame );
A_Fire( actor, 0 );
}
void A_Fire( mobj_t* actor, void* )
{
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, void* )
{
mobj_t* fog;
if( !actor->target )
{
return;
}
A_FaceTarget( actor, 0 );
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, 0 );
}
//
// A_VileAttack
//
void A_VileAttack( mobj_t* actor, void* )
{
mobj_t* fire;
int an;
if( !actor->target )
{
return;
}
A_FaceTarget( actor, 0 );
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.
//
void A_FatRaise( mobj_t* actor, void* )
{
A_FaceTarget( actor, 0 );
S_StartSound( actor, sfx_manatk );
}
void A_FatAttack1( mobj_t* actor, void* )
{
mobj_t* mo;
int an;
A_FaceTarget( actor, 0 );
// 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, void* )
{
mobj_t* mo;
int an;
A_FaceTarget( actor, 0 );
// 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, void* )
{
mobj_t* mo;
int an;
A_FaceTarget( actor, 0 );
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.
//
void A_SkullAttack( mobj_t* actor, void* )
{
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, 0 );
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 = ::g->thinkercap.next;
while( currentthinker != &::g->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, 0 );
}
//
// A_PainAttack
// Spawn a lost soul and launch it at the target
//
void A_PainAttack( mobj_t* actor, void* )
{
if( !actor->target )
{
return;
}
A_FaceTarget( actor, 0 );
A_PainShootSkull( actor, actor->angle );
}
void A_PainDie( mobj_t* actor, void* )
{
A_Fall( actor, 0 );
A_PainShootSkull( actor, actor->angle + ANG90 );
A_PainShootSkull( actor, actor->angle + ANG180 );
A_PainShootSkull( actor, actor->angle + ANG270 );
}
void A_Scream( mobj_t* actor, void* )
{
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, void* )
{
S_StartSound( actor, sfx_slop );
}
void A_Pain( mobj_t* actor, void* )
{
if( actor->info->painsound )
{
S_StartSound( actor, actor->info->painsound );
}
}
void A_Fall( mobj_t* actor, void* )
{
// 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, void* )
{
P_RadiusAttack( thingy, thingy->target, 128 );
}
//
// A_BossDeath
// Possibly trigger special effects
// if on first boss level
//
void A_BossDeath( mobj_t* mo, void* )
{
thinker_t* th;
mobj_t* mo2;
line_t junk;
int i;
if( ::g->gamemode == commercial )
{
if( ::g->gamemap != 7 )
{
return;
}
if( ( mo->type != MT_FATSO )
&& ( mo->type != MT_BABY ) )
{
return;
}
}
else
{
switch( ::g->gameepisode )
{
case 1:
if( ::g->gamemap != 8 )
{
return;
}
if( mo->type != MT_BRUISER )
{
return;
}
break;
case 2:
if( ::g->gamemap != 8 )
{
return;
}
if( mo->type != MT_CYBORG )
{
return;
}
break;
case 3:
if( ::g->gamemap != 8 )
{
return;
}
if( mo->type != MT_SPIDER )
{
return;
}
break;
case 4:
switch( ::g->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( ::g->gamemap != 8 )
{
return;
}
break;
}
}
// make sure there is a player alive for victory
for( i = 0 ; i < MAXPLAYERS ; i++ )
if( ::g->playeringame[i] && ::g->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 = ::g->thinkercap.next ; th != &::g->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( ::g->gamemode == commercial )
{
if( ::g->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( ::g->gameepisode )
{
case 1:
junk.tag = 666;
EV_DoFloor( &junk, lowerFloorToLowest );
return;
break;
case 4:
switch( ::g->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, void* )
{
S_StartSound( mo, sfx_hoof );
A_Chase( mo, 0 );
}
void A_Metal( mobj_t* mo, void* )
{
S_StartSound( mo, sfx_metal );
A_Chase( mo, 0 );
}
void A_BabyMetal( mobj_t* mo, void* )
{
S_StartSound( mo, sfx_bspwlk );
A_Chase( mo, 0 );
}
void
A_OpenShotgun2
( player_t* player,
pspdef_t* psp )
{
if( globalNetworking || ( player == &::g->players[::g->consoleplayer] ) )
{
S_StartSound( player->mo, sfx_dbopn );
}
}
void
A_LoadShotgun2
( player_t* player,
pspdef_t* psp )
{
if( globalNetworking || ( player == &::g->players[::g->consoleplayer] ) )
{
S_StartSound( player->mo, sfx_dbload );
}
}
void
A_ReFire
( player_t* player,
pspdef_t* psp );
void
A_CloseShotgun2
( player_t* player,
pspdef_t* psp )
{
if( globalNetworking || ( player == &::g->players[::g->consoleplayer] ) )
{
S_StartSound( player->mo, sfx_dbcls );
}
A_ReFire( player, psp );
}
void A_BrainAwake( mobj_t* mo, void* )
{
thinker_t* thinker;
mobj_t* m;
// find all the target spots
::g->easy = 0;
::g->numbraintargets = 0;
::g->braintargeton = 0;
thinker = ::g->thinkercap.next;
for( thinker = ::g->thinkercap.next ;
thinker != &::g->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 )
{
::g->braintargets[::g->numbraintargets] = m;
::g->numbraintargets++;
}
}
S_StartSound( NULL, sfx_bossit );
}
void A_BrainPain( mobj_t* mo, void* )
{
S_StartSound( NULL, sfx_bospn );
}
void A_BrainScream( mobj_t* mo, void* )
{
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, void* )
{
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, void* )
{
G_ExitLevel();
}
void A_BrainSpit( mobj_t* mo, void* )
{
mobj_t* targ;
mobj_t* newmobj;
::g->easy ^= 1;
if( ::g->gameskill <= sk_easy && ( !::g->easy ) )
{
return;
}
if( 1 )
{
// count number of thinkers
int numCorpse = 0;
int numEnemies = 0;
for( thinker_t* th = ::g->thinkercap.next; th != &::g->thinkercap; th = th->next )
{
if( th->function.acp1 == ( actionf_p1 )P_MobjThinker )
{
mobj_t* obj = ( mobj_t* )th;
if( obj->flags & MF_CORPSE )
{
numCorpse++;
}
else if( obj->type > MT_PLAYER && obj->type < MT_KEEN )
{
numEnemies++;
}
}
}
if( numCorpse > 48 )
{
for( int i = 0; i < 12; i++ )
{
for( thinker_t* th = ::g->thinkercap.next; th != &::g->thinkercap; th = th->next )
{
if( th->function.acp1 == ( actionf_p1 )P_MobjThinker )
{
mobj_t* obj = ( mobj_t* )th;
if( obj->flags & MF_CORPSE )
{
P_RemoveMobj( obj );
break;
}
}
}
}
}
if( numEnemies > 32 )
{
return;
}
}
// shoot a cube at current target
targ = ::g->braintargets[::g->braintargeton];
::g->braintargeton = ( ::g->braintargeton + 1 ) % ::g->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, void* );
// travelling cube sound
void A_SpawnSound( mobj_t* mo, void* )
{
S_StartSound( mo, sfx_boscub );
A_SpawnFly( mo, 0 );
}
void A_SpawnFly( mobj_t* mo, void* )
{
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, ( statenum_t )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, void* )
{
// Default death sound.
int sound = sfx_pldeth;
if( ( ::g->gamemode == commercial )
&& ( mo->health < -50 ) )
{
// IF THE PLAYER DIES
// LESS THAN -50% WITHOUT GIBBING
sound = sfx_pdiehi;
}
if( ::g->demoplayback || globalNetworking || ( mo == ::g->players[::g->consoleplayer].mo ) )
{
S_StartSound( mo, sound );
}
}
}; // extern "C"