mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2025-03-16 15:41:16 +00:00
2306 lines
40 KiB
C++
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"
|
|
|