og-odamex/common/p_enemy.cpp

4038 lines
92 KiB
C++

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id$
//
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 2006-2026 by The Odamex Team.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// DESCRIPTION:
// Enemy thinking, AI.
// Action Pointer Functions
// that are associated with states/frames.
//
//-----------------------------------------------------------------------------
#include "odamex.h"
#include <math.h>
#include "m_random.h"
#include "m_alloc.h"
#include "m_bbox.h"
#include "i_system.h"
#include "p_local.h"
#include "p_lnspec.h"
#include "s_sound.h"
#include "r_state.h"
#include "gi.h"
#include "p_mobj.h"
#include "p_inter.h"
#include "d_player.h"
#include "p_setup.h"
#include "d_dehacked.h"
#include "g_gametype.h"
#include "g_skill.h"
#include "p_mapformat.h"
#include "p_boomfspec.h"
#include "c_effect.h"
#include "infomap.h"
EXTERN_CVAR(sv_allowexit)
EXTERN_CVAR(sv_fastmonsters)
EXTERN_CVAR(co_zdoomphys)
EXTERN_CVAR(co_novileghosts)
EXTERN_CVAR(co_zdoomsound)
EXTERN_CVAR(co_pursuit)
EXTERN_CVAR(co_helpfriends)
EXTERN_CVAR(co_friend_distance)
EXTERN_CVAR(co_monsterbacking)
EXTERN_CVAR(co_monsterfriction)
EXTERN_CVAR(co_avoidhazards)
EXTERN_CVAR(co_staylift)
EXTERN_CVAR(co_friend_ledgejumping)
EXTERN_CVAR(co_removesoullimit)
EXTERN_CVAR(co_friend_helpertype)
EXTERN_CVAR(co_friend_playerhelpers)
#ifdef CLIENT_APP
EXTERN_CVAR(cl_showfriends)
#endif
enum dirtype_t
{
DI_EAST,
DI_NORTHEAST,
DI_NORTH,
DI_NORTHWEST,
DI_WEST,
DI_SOUTHWEST,
DI_SOUTH,
DI_SOUTHEAST,
DI_NODIR,
NUMDIRS
};
//
// P_NewChaseDir related LUT.
//
dirtype_t opposite[9] =
{
DI_WEST, DI_SOUTHWEST, DI_SOUTH, DI_SOUTHEAST,
DI_EAST, DI_NORTHEAST, DI_NORTH, DI_NORTHWEST, DI_NODIR
};
dirtype_t diags[4] =
{
DI_NORTHWEST, DI_NORTHEAST, DI_SOUTHWEST, DI_SOUTHEAST
};
fixed_t xspeed[8] = {FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000};
fixed_t yspeed[8] = {0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000};
void A_Die (AActor *actor);
void A_Detonate (AActor *mo);
void A_Explode (AActor *thing);
void A_Mushroom (AActor *actor);
void A_Fall (AActor *actor);
void SV_UpdateMonsterRespawnCount();
void SV_SendRaiseMobj(const AActor* source, const AActor* corpse);
void SV_UpdateMobj(const AActor* mo);
void SV_Sound(const AActor* mo, byte channel, const char* name, byte attenuation);
void SV_SpawnMobj(AActor* mobj);
extern bool isFast;
//
// ENEMY THINKING
// Enemies are always 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 void P_RecursiveSound (sector_t& sector, int soundblocks, AActor *soundtarget)
{
// wake up all monsters in this sector
if (sector.validcount == validcount
&& sector.soundtraversed <= soundblocks+1)
{
return; // already flooded
}
sector.validcount = validcount;
sector.soundtraversed = soundblocks + 1;
sector.soundtarget = soundtarget->ptr();
for (int i = 0; i < sector.linecount; i++)
{
const line_t* check = sector.lines[i];
if (! (check->flags & ML_TWOSIDED) )
continue;
// It's tempting to rely on C++'s conversion of bool false/true to int 0 or 1.
// However, I'd much rather just explicitly state what I mean with exact index
// numbers and let the compiler micro-optimize it if it insists.
const size_t otherSidenumIndex = sides[ check->sidenum[0] ].sector == &sector ? 1 : 0;
sector_t& otherRef = *sides[ check->sidenum[otherSidenumIndex] ].sector;
// [SL] 2012-02-08 - FIXME: Currently only checks for a line opening at
// midpoint of a sloped linedef. P_RecursiveSound() in ZDoom 1.23 causes
// demo desyncs.
P_LineOpening(check, (check->v1->x >> 1) + (check->v2->x >> 1),
(check->v1->y >> 1) + (check->v2->y >> 1));
if (openrange <= 0)
continue; // closed door
if (check->flags & ML_SOUNDBLOCK)
{
if (!soundblocks)
P_RecursiveSound (otherRef, 1, soundtarget);
}
else
P_RecursiveSound (otherRef, soundblocks, soundtarget);
}
}
//
// P_NoiseAlert
// If a monster yells at a player,
// it will alert other monsters to the player.
//
void P_NoiseAlert (AActor *target, AActor *emmiter)
{
if (target->player && (!multiplayer && (target->player->cheats & CF_NOTARGET)))
return;
if (not emmiter->subsector)
return;
validcount++;
P_RecursiveSound (* emmiter->subsector->sector, 0, target);
}
//
// P_CheckRange
//
static bool P_CheckRange(AActor* actor, fixed_t range)
{
AActor* pl = actor->target;
return // killough 7/18/98: friendly monsters don't attack other friends
pl && !(actor->flags & pl->flags & MF_FRIEND && P_IsFriendlyThing(actor, pl)) &&
P_AproxDistance(pl->x - actor->x, pl->y - actor->y) < range &&
P_CheckSight(actor, actor->target) &&
pl->z <= actor->z + actor->height && actor->z <= pl->z + pl->height;
}
//
// P_CheckMeleeRange
//
bool P_CheckMeleeRange (AActor *actor)
{
AActor *pl;
fixed_t dist;
if (!actor->target)
return false;
fixed_t range = actor->info->meleerange;
pl = actor->target;
dist = P_AproxDistance (pl->x-actor->x, pl->y-actor->y);
if (actor->flags & pl->flags & MF_FRIEND && P_IsFriendlyThing(actor, pl))
return false;
if (dist >= range - 20 * FRACUNIT + pl->info->radius)
return false;
// [RH] If moving toward goal, then we've reached it.
if (actor->target == actor->goal)
return true;
// [RH] Don't melee things too far above or below actor.
if (P_AllowPassover())
{
if (pl->z > actor->z + actor->height)
return false;
if (pl->z + pl->height < actor->z)
return false;
}
if (!P_CheckSight(actor, pl))
return false;
return true;
}
//
// P_CheckMissileRange
//
bool P_CheckMissileRange (AActor *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;
/* killough 7/18/98: no friendly fire at corpses
* killough 11/98: prevent too much infighting among friends
* cph - yikes, talk about fitting everything on one line... */
return !(actor->flags & MF_FRIEND) ||
(actor->target->health > 0 &&
(!(actor->target->player
? true
: !(actor->target->flags & MF_JUSTHIT) && P_Random(actor) > 128)));
}
/* killough 7/18/98: friendly monsters don't attack other friendly
* monsters or players (except when attacked, and then only once)
*/
if (actor->flags & MF_FRIEND && P_IsFriendlyThing(actor, actor->target))
return false;
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->flags3 & MF3_SHORTMRANGE)
{
if (dist > 14*64)
return false; // too far away
}
if (actor->flags3 & MF3_LONGMELEE)
{
if (dist < 196)
return false; // close for fist attack
}
if (actor->flags3 & MF3_RANGEHALF)
dist >>= 1;
if (dist > 200)
dist = 200;
if (actor->flags3 & MF3_HIGHERMPROB && dist > 160)
dist = 160;
if (P_Random (actor) < dist)
return false;
return true;
}
//
// P_Move
// Move in the current direction,
// returns false if the move is blocked.
//
extern std::vector<line_t*> spechit;
bool P_Move (AActor *actor, int dropoff = 0)
{
fixed_t tryx, tryy, deltax, deltay, origx, origy;
bool try_ok;
int good;
int speed;
int movefactor = ORIG_FRICTION_FACTOR;
int friction = ORIG_FRICTION;
if (!actor->subsector)
return false;
if (actor->flags2 & MF2_BLASTED)
return true;
if (actor->movedir == DI_NODIR)
return false;
// [RH] Instead of yanking non-floating monsters to the ground,
// let gravity drop them down, unless they're moving down a step.
if (co_zdoomphys && !(actor->flags & MF_NOGRAVITY) && actor->z > actor->floorz
&& !(actor->flags2 & MF2_ONMOBJ))
{
if (actor->z > actor->floorz + 24*FRACUNIT)
{
return false;
}
else
{
actor->z = actor->floorz;
}
}
if ((unsigned)actor->movedir >= 8)
I_Error("Weird actor->movedir!");
speed = actor->info->speed;
// [AM] Quick monsters are faster.
if (actor->oflags & MFO_QUICK)
{
speed = speed + (speed / 2);
}
// killough 10/98: make monsters get affected by ice and sludge too:
if (co_monsterfriction)
movefactor = P_GetMoveFactor (actor, &friction);
if (friction < ORIG_FRICTION && // sludge
!(speed = ((ORIG_FRICTION_FACTOR - (ORIG_FRICTION_FACTOR-movefactor)/2)
* speed) / ORIG_FRICTION_FACTOR))
speed = 1; // always give the monster a little bit of speed
tryx = (origx = actor->x) + (deltax = speed * xspeed[actor->movedir]);
tryy = (origy = actor->y) + (deltay = speed * yspeed[actor->movedir]);
try_ok = P_TryMove(actor, tryx, tryy, dropoff);
if (try_ok && friction > ORIG_FRICTION)
{
actor->x = origx;
actor->y = origy;
movefactor *= FRACUNIT / ORIG_FRICTION_FACTOR / 4;
actor->momx += FixedMul (deltax, movefactor);
actor->momy += FixedMul (deltay, movefactor);
}
if (!try_ok)
{
// open any specials
if (actor->flags & MF_FLOAT && floatok)
{
fixed_t savedz = actor->z;
// must adjust height
if (actor->z < tmfloorz)
actor->z += FLOATSPEED;
else
actor->z -= FLOATSPEED;
// [RH] Check to make sure there's nothing in the way of the float
if (P_TestMobjZ(actor))
{
actor->flags |= MF_INFLOAT;
return true;
}
actor->z = savedz;
}
if (spechit.empty())
return false;
actor->movedir = DI_NODIR;
good = false;
while (!spechit.empty())
{
line_t *ld = spechit.back();
spechit.pop_back();
// if the special is not a door
// that can be opened,
// return false
if (P_UseSpecialLine (actor, ld, 0, false) ||
P_PushSpecialLine (actor, ld, 0))
good = true;
}
return good;
}
else
{
actor->flags &= ~MF_INFLOAT;
}
if (!(actor->flags & MF_FLOAT) && !felldown && !co_zdoomphys)
actor->z = actor->floorz;
return true;
}
/*
* P_SmartMove
*
* killough 9/12/98: Same as P_Move, except smarter
*/
bool P_SmartMove(AActor* actor)
{
AActor* target = actor->target;
bool on_lift;
int under_damage = 0;
int dropoff = 0;
/* killough 9/12/98: Stay on a lift if target is on one */
on_lift = co_staylift
&& target
&& target->health > 0
&& target->subsector
&& target->subsector->sector->tag == actor->subsector->sector->tag
&& P_IsOnLift(actor);
under_damage = co_avoidhazards && P_IsUnderDamage(actor); // e6y
// killough 10/98: allow dogs to drop off of taller ledges sometimes.
// dropoff==1 means always allow it, dropoff==2 means only up to 128 high,
// and only if the target is immediately on the other side of the line.
// allow all friends to jump down instead of just dogs
if (actor->flags & MF_FRIEND && target && co_friend_ledgejumping && P_AllowDropOff() &&
!((target->flags ^ actor->flags) & MF_FRIEND) &&
P_AproxDistance(actor->x - target->x, actor->y - target->y) < FRACUNIT * 144 &&
P_Random(actor) < 235)
dropoff = 2;
if (!P_Move(actor, dropoff))
return false;
// killough 9/9/98: avoid crushing ceilings or other damaging areas
if ((on_lift && P_Random(actor) < 230 && // Stay on lift
!P_IsOnLift(actor)) ||
(co_avoidhazards && !under_damage && // e6y // Get away from damage
(under_damage = P_IsUnderDamage(actor)) &&
(under_damage < 0 || P_Random(actor) < 200)))
actor->movedir = DI_NODIR; // avoid the area (most of the time anyway)
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.
//
bool P_TryWalk (AActor *actor)
{
if (!P_SmartMove (actor))
{
return false;
}
actor->movecount = P_Random (actor) & 15;
return true;
}
/*
* P_IsOnLift
*
* killough 9/9/98:
*
* Returns true if the object is on a lift. Used for AI,
* since it may indicate the need for crowded conditions,
* or that a monster should stay on the lift for a while
* while it goes up or down.
*/
bool P_IsOnLift(const AActor* actor)
{
if (not (actor && actor->subsector))
return false;
const sector_t* sec = actor->subsector->sector;
line_t line;
int l;
// Short-circuit: it's on a lift which is active.
if (sec->floordata && ((DFloor*)sec->floordata)->m_Status == DFloor::up)
return true;
// Check to see if it's in a sector which can be activated as a lift.
if ((line.id = sec->tag))
for (l = -1; (l = P_FindLineFromLineTag(&line, l)) >= 0;)
switch (lines[l].special)
{
case 10:
case 14:
case 15:
case 20:
case 21:
case 22:
case 47:
case 53:
case 62:
case 66:
case 67:
case 68:
case 87:
case 88:
case 95:
case 120:
case 121:
case 122:
case 123:
case 143:
case 162:
case 163:
case 181:
case 182:
case 144:
case 148:
case 149:
case 211:
case 227:
case 228:
case 231:
case 232:
case 235:
case 236:
return true;
}
return false;
}
//
// A_FaceTarget
//
void A_FaceTarget(AActor* actor)
{
if (!actor->target)
return;
actor->flags &= ~MF_AMBUSH;
actor->angle = P_PointToAngle(actor->x, actor->y, actor->target->x, actor->target->y);
if (actor->target->flags & MF_SHADOW)
actor->angle += P_RandomDiff(actor) << 21;
}
//=============================================================================
//
// P_HitFriend()
//
// killough 12/98
// This function tries to prevent shooting at friends that get in the line of fire
//
//=============================================================================
bool P_HitFriend(AActor* self)
{
if (self->flags & MF_FRIEND && self->target != NULL)
{
angle_t angle =
R_PointToAngle2(self->x, self->y, self->target->x, self->target->y);
fixed_t dist =
P_AproxDistance(self->x - self->target->x, self->y - self->target->y);
P_AimLineAttack(self, angle, dist);
if (linetarget != NULL && linetarget != self->target)
{
return P_IsFriendlyThing(self, linetarget);
}
}
return false;
}
extern fixed_t tmbbox[4];
namespace
{
bool PIT_AvoidDropoff(const line_t& line, const fixed_t floorz, fixed_t& dropoff_deltax, fixed_t& dropoff_deltay)
{
if (line.backsector && // Ignore one-sided linedefs
tmbbox[BOXRIGHT] > line.bbox[BOXLEFT] &&
tmbbox[BOXLEFT] < line.bbox[BOXRIGHT] &&
tmbbox[BOXTOP] > line.bbox[BOXBOTTOM] && // Linedef must be contacted
tmbbox[BOXBOTTOM] < line.bbox[BOXTOP] && P_BoxOnLineSide(tmbbox, &line) == -1)
{
const fixed_t front = line.frontsector->floorheight;
const fixed_t back = line.backsector->floorheight;
angle_t angle;
// The monster must contact one of the two floors,
// and the other must be a tall dropoff (more than 24).
if (back == floorz && front < floorz - FRACUNIT * 24)
angle = R_PointToAngle2(0, 0, line.dx, line.dy); // front side dropoff
else if (front == floorz && back < floorz - FRACUNIT * 24)
angle = R_PointToAngle2(line.dx, line.dy, 0, 0); // back side dropoff
else
return true;
// Move away from dropoff at a standard speed.
// Multiple contacted linedefs are cumulative (e.g. hanging over corner)
dropoff_deltax -= finesine[angle >> ANGLETOFINESHIFT] * 32;
dropoff_deltay += finecosine[angle >> ANGLETOFINESHIFT] * 32;
}
return true;
}
//
// Driver for above
//
fixed_t P_AvoidDropoff(AActor* actor, fixed_t& dropoff_deltax, fixed_t& dropoff_deltay)
{
tmbbox[BOXTOP] = actor->y + actor->radius;
tmbbox[BOXBOTTOM] = actor->y - actor->radius;
tmbbox[BOXRIGHT] = actor->x + actor->radius;
tmbbox[BOXLEFT] = actor->x - actor->radius;
const int yh = (tmbbox[BOXTOP] - bmaporgy) >> MAPBLOCKSHIFT;
const int yl = (tmbbox[BOXBOTTOM] - bmaporgy) >> MAPBLOCKSHIFT;
const int xh = (tmbbox[BOXRIGHT] - bmaporgx) >> MAPBLOCKSHIFT;
const int xl = (tmbbox[BOXLEFT] - bmaporgx) >> MAPBLOCKSHIFT;
const fixed_t floorz = actor->z; // remember floor height
dropoff_deltax = dropoff_deltay = 0;
// check lines
validcount++;
for (int bx = xl; bx <= xh; bx++)
for (int by = yl; by <= yh; by++)
P_BlockLinesIterator(bx, by, PIT_AvoidDropoff, floorz, dropoff_deltax, dropoff_deltay); // all contacted lines
return dropoff_deltax | dropoff_deltay; // Non-zero if movement prescribed
}
//
// P_DoNewChaseDir
//
// killough 9/8/98:
//
// Most of P_NewChaseDir(), except for what
// determines the new direction to take
//
// Uses the new ZDoom chase code
//
void P_DoNewChaseDir(AActor* actor, fixed_t deltax, fixed_t deltay)
{
dirtype_t d[3];
int tdir;
dirtype_t olddir = (dirtype_t)actor->movedir;
dirtype_t turnaround = opposite[olddir];
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(actor) > 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(actor) & 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
}
} // namespace
void P_NewChaseDir (AActor *actor)
{
if (!actor->target)
I_Error ("P_NewChaseDir: called with no target");
fixed_t deltax = actor->target->x - actor->x;
fixed_t deltay = actor->target->y - actor->y;
// killough 8/8/98: sometimes move away from target, keeping distance
//
// 1) Stay a certain distance away from a friend, to avoid being in their way
// 2) Take advantage over an enemy without missiles, by keeping distance
actor->strafecount = 0;
fixed_t dropoff_deltax = 0;
fixed_t dropoff_deltay = 0;
if (P_IsMBFCompatMode())
{
if (actor->floorz - actor->dropoffz > FRACUNIT * 24 &&
actor->z <= actor->floorz && !(actor->flags & (MF_DROPOFF | MF_FLOAT)) &&
!P_AllowDropOff() && P_AvoidDropoff(actor, dropoff_deltax, dropoff_deltay)) /* Move away from dropoff */
{
P_DoNewChaseDir(actor, dropoff_deltax, dropoff_deltay);
// If moving away from dropoff, set movecount to 1 so that
// small steps are taken to get monster away from dropoff.
actor->movecount = 1;
return;
}
else
{
fixed_t dist = P_AproxDistance(deltax, deltay);
if (actor->flags & actor->target->flags & MF_FRIEND &&
P_IsFriendlyThing(actor, actor->target) &&
co_friend_distance.asInt() << FRACBITS > dist && !P_IsOnLift(actor) &&
!P_IsUnderDamage(actor))
{
deltax = -deltax, deltay = -deltay;
}
else if (actor->target->health > 0 &&
(actor->flags ^ actor->target->flags) & MF_FRIEND)
{ // Live enemy target
if (co_monsterbacking &&
actor->info->missilestate &&
actor->type != MT_SKULL &&
((!actor->target->info->missilestate &&
dist < actor->target->info->meleerange * 2) ||
(actor->target->player &&
dist < actor->target->player->mo->info->meleerange * 3 &&
weaponinfo[actor->target->player->readyweapon].flags &
WPF_FLEEMELEE)))
{ // Back away from melee attacker
actor->strafecount = P_Random(actor) & 15;
deltax = -deltax, deltay = -deltay;
}
}
}
}
P_DoNewChaseDir(actor, deltax, deltay);
// If strafing, set movecount to strafecount so that old Doom
// logic still works the same, except in the strafing part
if (actor->strafecount)
actor->movecount = actor->strafecount;
}
static bool P_IsVisible(AActor* actor, AActor* mo, bool allaround)
{
if (!allaround)
{
angle_t an = R_PointToAngle2(actor->x, actor->y, mo->x, mo->y) - actor->angle;
if (an > ANG90 && an < ANG270 &&
P_AproxDistance(mo->x - actor->x, mo->y - actor->y) > WAKEUPRANGE)
return false;
}
return P_CheckSight(actor, mo);
}
//
// P_HelpFriend
//
// killough 9/8/98: Help friends in danger of dying
//
static bool P_HelpFriend(AActor* actor)
{
int killcount = 0;
AActor* it;
TThinkerIterator<AActor> iterator;
// If less than 33% health, self-preservation rules
if (actor->health * 3 < actor->info->spawnhealth)
return false;
while ((it = iterator.Next()))
{
if (P_IsFriendlyThing(actor, it))
{
// Possibly help a friend under 50% health
if (it->health * 2 >= it->info->spawnhealth)
{
if (P_Random(actor) < 180)
break;
}
else if (it->flags & MF_JUSTHIT && it->target &&
it->target != actor->target)
{
AActor* enemy = P_RoughTargetSearch(actor, FixedToAngle(INT2FIXED(90)),
896, RoughMonsterCheck);
if (!enemy)
{
// Ignore any attacking monsters, while searching for friend
actor->threshold = BASETHRESHOLD;
return true;
}
else
{
actor->target = enemy->ptr();
}
}
}
}
return false;
}
//============================================================================
//
// P_LookForMonsters
//
// Selects a live enemy monster
//
//============================================================================
bool P_LookForMonsters(AActor* actor, bool allaround)
{
if (!P_IsMBFCompatMode())
return false;
// Use last known enemy if no hatee sighted -- killough 2/15/98:
if (actor->lastenemy && actor->lastenemy->health > 0 &&
!(actor->lastenemy->flags & actor->flags & MF_FRIEND))
{
actor->target = actor->lastenemy;
actor->lastenemy = AActor::AActorPtr();
return true;
}
else
{
actor->lastenemy = AActor::AActorPtr();
}
// This is NOT MBF behavior
// But we want a smarter monster check for friendlies and hostiles attacking friendlies.
AActor* enemy = P_RoughTargetSearch(actor, FixedToAngle(INT2FIXED(180)), 7, RoughMonsterCheck);
if (enemy)
{
actor->target = enemy->ptr();
return true;
}
if (!actor->target)
{
// [RH] use goal as target
if (actor->goal)
{
actor->target = actor->goal;
return true;
}
}
return false;
}
/**
* @brief Spawn a helper. (borrowed from SpawnMonster from horde)
*
* @param point Spawn point of the monster.
* @param recipe Recipe of a monster to spawn.
* @param offset Offset from the spawn point to spawn at.
* @param target Player to target.
* @return Actor pointer we just spawned, or NULL if the spawn failed.
*/
AActor::AActorPtr SpawnHelper(const MapThing SpawnPoint, mobjtype_t SpawnType, const AActor* origin)
{
AActor* mo = new AActor(
SpawnPoint.x << FRACBITS, SpawnPoint.y << FRACBITS,
(level.flags & LEVEL_USEPLAYERSTARTZ ? SpawnPoint.z << FRACBITS : ONFLOORZ),
SpawnType);
if (mo)
{
if (P_TestMobjLocation(mo))
{
mo->flags |= MF_FRIEND;
mo->angle = ANG45 * (SpawnPoint.angle / 45);
P_GiveFriendlyOwnerInfo(mo, origin);
P_FriendlyEffects(mo);
SV_SpawnMobj(mo);
// Spawn a teleport fog if it's not an ambush.
if ((SpawnPoint.flags & MF_AMBUSH) == 0)
{
AActor* tele =
new AActor(SpawnPoint.x << FRACBITS, SpawnPoint.y << FRACBITS,
(level.flags & LEVEL_USEPLAYERSTARTZ ? SpawnPoint.z << FRACBITS : ONFLOORZ) + INT2FIXED(gameinfo.telefogHeight),
MT_TFOG);
SV_SpawnMobj(tele);
S_NetSound(tele, CHAN_VOICE, "misc/teleport", ATTN_NORM);
}
return mo->ptr();
}
else
{
// Spawn blocked.
mo->Destroy();
}
}
return AActor::AActorPtr();
}
void P_ClearHelpers()
{
helperspawns.clear();
}
void P_SetupHelpers()
{
if (!serverside)
return;
for (Players::iterator it = players.begin(); it != players.end(); ++it)
{
if (!(it->ingame()))
continue;
for (int i = 0; i < co_friend_playerhelpers.asInt(); i++)
{
mobjtype_t monstertype = static_cast<mobjtype_t>(deh.helper);
if (!co_friend_helpertype.str().empty())
monstertype = P_INameToMobj(co_friend_helpertype.str());
if (monstertype == MT_NULL)
monstertype = MT_DOGS;
HelperSpawns helper = {monstertype, it->id};
helperspawns.push_back(helper);
}
}
}
void P_RunHelperTics()
{
if (::helperspawns.empty())
{
// nothing to do
return;
}
std::vector<HelperSpawns>::const_iterator i = ::helperspawns.begin();
while (i != ::helperspawns.end())
{
// Remove any disconnected player helpers in the queue
// before spawning
auto it = std::find_if(::players.begin(), ::players.end(),
[&](const player_s& p) { return p.id == i->playerid; });
if (it == players.end() || !it->ingame())
{
i = ::helperspawns.erase(i);
continue;
}
bool spawned = false;
if (G_UsesCoopSpawns())
{
// Spawn at coop player spawns
for (const auto& spawn : ::playerstarts)
{
AActor::AActorPtr mo = SpawnHelper(spawn, i->helpertype, it->mo);
if (!mo)
{
continue;
}
else
{
spawned = true;
break;
}
}
}
else if (G_IsTeamGame())
{
// Spawn at player's team starts
TeamInfo* teamInfo = GetTeamInfo(it->userinfo.team);
for (const auto& spawn : teamInfo->Starts)
{
AActor::AActorPtr mo = SpawnHelper(spawn, i->helpertype, it->mo);
if (!mo)
{
continue;
}
else
{
spawned = true;
break;
}
}
}
else
{
// Spawn at deathmatch spawns
for (const auto& spawn : ::DeathMatchStarts)
{
AActor::AActorPtr mo = SpawnHelper(spawn, i->helpertype, it->mo);
if (!mo)
{
continue;
}
else
{
spawned = true;
break;
}
}
}
if (spawned)
i = ::helperspawns.erase(i);
else
i++;
}
}
//
// P_LookForPlayers
// If allaround is false, only look 180 degrees in front.
// Returns true if a player is targeted.
//
// This function can trip you up if you're not careful. The piece of
// functionality that is critical to vanilla compatibility is actor->lastlook.
// In vanilla, it was randomly set to a number between 0 and 3 on mobj
// creation, and this function is SUPPOSED to loop through all player numbers,
// set the mobj target and leave lastlook at the last-looked at player, in
// the hopes that not all monsters will immediately target the same player.
//
// However, the looping logic is _very_ tricky to get your head around, as
// the function was written with a fixed array of players in mind and makes
// some bad assumptions. The ending sentinal (stop) can refer to a player
// index that doesn't exist. The hard-limit counter (c) is post-incremented,
// so the loop will do at most two sight-checks, but lastlook is actually
// incremented one more time than that.
//
bool P_LookForPlayers(AActor *actor, bool allaround)
{
// [AM] Check subsectors first.
if (actor->subsector == NULL)
return false;
sector_t* sector = actor->subsector->sector;
if (!sector)
return false;
if (actor->flags & MF_FRIEND)
{ // killough 9/9/98: friendly monsters go about players differently
// Go back to a player, no matter whether it's visible or not
for (Players::iterator it = players.begin(); it != players.end(); ++it)
{
if (it->id == actor->friend_playerid && !it->spectator && it->health > 0)
{
if (it->ingame() && it->playerstate == PST_LIVE && !it->spectator &&
it->mo && it->mo->health > 0 && P_IsFriendlyThing(it->mo, actor) &&
P_IsVisible(actor, it->mo, allaround))
{
actor->target = it->mo;
// killough 12/98:
// get out of refiring loop, to avoid hitting player accidentally
if (actor->info->missilestate)
{
P_SetMobjState(actor, actor->info->seestate);
actor->flags &= ~MF_JUSTHIT;
}
return true;
}
}
}
return false;
}
// Construct our table of ingame players
// [AM] TODO: Have the Players container handle this instead of having to
// check every single tic.
static player_t* playeringame[MAXPLAYERS];
memset(playeringame, 0, sizeof(player_t*) * MAXPLAYERS);
short maxid = 0;
for (auto& player : players)
{
if (player.ingame() && !(player.spectator))
{
playeringame[(player.id) - 1] = &player;
maxid = player.id;
}
}
// If there are no ingame players, we need to bug out now because
// otherwise we're going to cause an infinite loop.
if (maxid == 0)
return false;
// denis - vanilla sync, original code always looped over size-4 array.
if (maxid < MAXPLAYERS_VANILLA)
maxid = MAXPLAYERS_VANILLA;
// denis - prevents calling P_CheckSight twice on the same player
static bool sightcheckfailed[MAXPLAYERS];
memset(sightcheckfailed, 0, sizeof(bool) * maxid);
int counter = 0;
// [AM] Vanilla braindamage, "fixing" will lead to vanilla desyncs
unsigned int stop;
if (actor->lastlook > 0)
stop = actor->lastlook - 1;
else
stop = maxid - 1;
for ( ; ; actor->lastlook = (actor->lastlook + 1) % maxid)
{
if (playeringame[actor->lastlook] == NULL)
continue;
if (++counter == 3 || actor->lastlook == stop)
{
// done looking
// [RH] Use goal as a last resort
if (!actor->target && actor->goal)
{
actor->target = actor->goal;
return true;
}
return false;
}
if (sightcheckfailed[actor->lastlook])
continue;
player_t* player = playeringame[actor->lastlook];
if (player->cheats & CF_NOTARGET)
continue; // no target
if (player->health <= 0)
continue; // dead
if (!player->mo)
continue; // out of game
if (!P_IsVisible(actor, player->mo, allaround))
{
sightcheckfailed[actor->lastlook] = true;
continue;
}
// [RH] Need to be sure the reactiontime is 0 if the monster is
// leaving its goal to go after a player.
if (actor->goal && actor->target == actor->goal)
actor->reactiontime = 0;
actor->target = player->mo->ptr();
return true;
}
return false;
}
//
// P_LookForTargets
//
// killough 9/5/98: look for targets to go after, depending on kind of monster
//
static bool P_LookForTargets(AActor* actor, bool allaround)
{
return actor->flags & MF_FRIEND
? P_LookForMonsters(actor, allaround) || P_LookForPlayers(actor, allaround)
: P_LookForPlayers(actor, allaround) || P_LookForMonsters(actor, allaround);
}
//
// A_KeenDie
// DOOM II special, map 32.
// Uses special tag 666.
//
void A_KeenDie (AActor *actor)
{
A_Fall (actor);
// scan the remaining thinkers
// to see if all Keens are dead
AActor *other;
TThinkerIterator<AActor> iterator;
while ( (other = iterator.Next ()) )
{
if (other != actor && other->type == actor->type && other->health > 0)
{
// other Keen not dead
return;
}
}
EV_DoDoor (DDoor::doorOpen, NULL, NULL, 666, 2*FRACUNIT, 0, NoKey);
}
//
// ACTION ROUTINES
//
//
// P_NewRandomDir
//
// villsa [STRIFE] new function
//
// haleyjd: Almost identical to the tail-end of P_NewChaseDir, this function
// finds a purely random direction for an object to walk. Called from
// A_RandomWalk.
//
// Shockingly similar to the RandomWalk pointer in Eternity :)
//
void P_NewRandomDir(AActor* actor)
{
int dir = 0;
int omovedir = opposite[actor->movedir]; // haleyjd 20110223: nerfed this...
// randomly determine direction of search
if (P_Random(actor) & 1)
{
// Try all non-reversal directions forward, first
for (dir = 0; dir < DI_NODIR; dir++)
{
if (dir != omovedir)
{
actor->movedir = dir;
if (P_Random(actor) & 1)
{
if (P_TryWalk(actor))
break;
}
}
}
// haleyjd 20110223: logic missing entirely:
// failed all non-reversal directions? try reversing
if (dir > DI_SOUTHEAST)
{
if (omovedir == DI_NODIR)
{
actor->movedir = DI_NODIR;
return;
}
actor->movedir = omovedir;
if (P_TryWalk(actor))
return;
else
{
actor->movedir = DI_NODIR;
return;
}
}
}
else
{
// Try directions one at a time in backward order
dir = DI_SOUTHEAST;
while (1)
{
// haleyjd 09/05/10: missing random code.
if (dir != omovedir)
{
actor->movedir = dir;
// villsa 09/06/10: un-inlined code
if (P_TryWalk(actor))
return;
}
// Ran out of non-reversal directions to try? Reverse.
if (--dir == -1)
{
if (omovedir == DI_NODIR)
{
actor->movedir = DI_NODIR;
return;
}
actor->movedir = omovedir;
// villsa 09/06/10: un-inlined code
if (P_TryWalk(actor))
return;
else
{
actor->movedir = DI_NODIR;
return;
}
} // end if(--dir == -1)
} // end while(1)
} // end else
}
//
// A_RandomWalk
//
// [STRIFE] New function.
// haleyjd 09/05/10: Action routine used to meander about.
//
void A_RandomWalk(AActor* actor)
{
// Standing actors do not wander.
//if (actor->flags & MF_STAND)
// return;
if (actor->reactiontime)
actor->reactiontime--; // count down reaction time
else
{
// turn to a new angle
if (actor->movedir < DI_NODIR)
{
int delta;
actor->angle &= (7 << 29);
delta = actor->angle - (actor->movedir << 29);
if (delta < 0)
actor->angle += ANG90 / 2;
else if (delta > 0)
actor->angle -= ANG90 / 2;
}
// try moving
if (--actor->movecount < 0 || !P_SmartMove(actor))
{
P_NewRandomDir(actor);
actor->movecount += 5;
}
}
}
//
// A_Look
// Stay in state until a player is sighted.
// [RH] Will also leave state to move to goal.
//
void A_Look (AActor *actor)
{
AActor *targ;
AActor *newgoal;
if(not (actor && actor->subsector))
return;
// [RH] Set goal now if appropriate
if (actor->special == Thing_SetGoal && actor->args[0] == 0)
{
actor->special = 0;
newgoal = AActor::FindGoal (NULL, actor->args[1], MT_PATHNODE);
actor->goal = newgoal->ptr();
actor->reactiontime = actor->args[2] * TICRATE + level.time;
}
actor->threshold = 0; // any shot will wake up
targ = actor->subsector->sector->soundtarget;
if (targ && targ->player && (targ->player->cheats & CF_NOTARGET))
return;
/* killough 7/18/98:
* Friendly monsters go after other monsters first, but
* also return to player, without attacking them, if they
* cannot find any targets. A marine's best friend :)
*/
actor->pursuecount = 0;
// GhostlyDeath -- can't hear spectators
if (targ && targ->player && targ->player->spectator)
{
actor->target = AActor::AActorPtr();
//return;
}
if (targ && (targ->flags & MF_SHOOTABLE))
{
actor->target = targ->ptr();
if (actor->flags & MF_AMBUSH)
{
if (P_CheckSight(actor, actor->target))
goto seeyou;
}
else
goto seeyou;
}
if (!P_LookForTargets (actor, false))
return;
// go into chase state
seeyou:
// GhostlyDeath -- Can't see spectators
if (actor->target->player && actor->target->player->spectator)
{
actor->target = AActor::AActorPtr();
//return;
}
// [RH] Don't start chasing after a goal if it isn't time yet.
if (actor->target == actor->goal)
{
if (actor->reactiontime > level.time)
actor->target = AActor::AActorPtr();
}
else if (actor->info->seesound)
{
char sound[MAX_SNDNAME];
M_StringCopy(sound, actor->info->seesound, MAX_SNDNAME);
if (sound[strlen(sound) - 1] == '1')
{
sound[strlen(sound) - 1] = P_Random(actor)%3 + '1';
if (S_FindSound (sound) == -1)
sound[strlen(sound) - 1] = '1';
}
if (!co_zdoomsound && (actor->flags2 & MF2_BOSS || actor->flags3 & MF3_FULLVOLSOUNDS))
S_Sound(CHAN_VOICE, sound, 1, ATTN_NORM);
else
S_Sound (actor, CHAN_VOICE, sound, 1, ATTN_NORM);
}
if (actor->target)
P_SetMobjState (actor, actor->info->seestate, true);
}
#include "m_vectors.h"
//
// A_Chase
// Actor has a melee attack,
// so it tries to close as fast as possible
//
void A_Chase (AActor *actor)
{
int delta;
AActor *ngoal;
// GhostlyDeath -- Don't chase spectators at all
if (actor->target && actor->target->player && actor->target->player->spectator)
{
actor->target = AActor::AActorPtr();
//return;
}
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
* killough 9/7/98: keep facing towards target if strafing or backing out
*/
if (actor->strafecount)
A_FaceTarget(actor);
else if (actor->movedir < 8)
{
actor->angle &= (angle_t)(7<<29);
delta = actor->angle - (actor->movedir << 29);
if (delta > 0)
actor->angle -= ANG90/2;
else if (delta < 0)
actor->angle += ANG90/2;
}
// [RH] If the target is dead or a friend (and not a goal), stop chasing it.
if (actor->target && actor->target != actor->goal && actor->target->health <= 0)
actor->target = AActor::AActorPtr();
if (!actor->target || !(actor->target->flags & MF_SHOOTABLE))
{
// look for a new target
if (P_LookForTargets (actor, true) && actor->target != actor->goal)
return; // got a new target
if (!actor->target)
{
if (actor->flags & MF_FRIEND)
{
A_Look(actor);
if (!actor->target)
{
A_RandomWalk(actor);
return;
}
}
else
{
P_SetMobjState(actor, actor->info->spawnstate,
true); // denis - todo - this sometimes leads to a stack
// overflow due to infinite recursion:
// A_Chase->SetMobjState->A_Look->SetMobjState
return;
}
}
}
// do not attack twice in a row
if (actor->flags & MF_JUSTATTACKED)
{
actor->flags &= ~MF_JUSTATTACKED;
if (G_GetCurrentSkill().respawn_counter == 0 && !sv_fastmonsters)
P_NewChaseDir (actor);
return;
}
// [RH] Don't attack if just moving toward goal
if (actor->target == actor->goal)
{
if (P_CheckMeleeRange (actor))
{
// reached the goal
actor->reactiontime = actor->goal->args[1] * TICRATE + level.time;
ngoal = AActor::FindGoal (NULL, actor->goal->args[0], MT_PATHNODE);
if (ngoal)
actor->goal = ngoal->ptr();
else
actor->goal = AActor::AActorPtr();
actor->target = AActor::AActorPtr();
P_SetMobjState (actor, actor->info->spawnstate, true);
return;
}
goto nomissile;
}
// check for melee attack
if (actor->info->meleestate && P_CheckMeleeRange (actor))
{
if (actor->info->attacksound)
S_Sound (actor, CHAN_WEAPON, actor->info->attacksound, 1, ATTN_NORM);
if (serverside)
P_SetMobjState (actor, actor->info->meleestate, true);
if (!actor->info->missilestate)
actor->flags |= MF_JUSTHIT;
return;
}
// check for missile attack
if (actor->info->missilestate)
{
if (!G_GetCurrentSkill().fast_monsters && actor->movecount && !sv_fastmonsters)
{
goto nomissile;
}
if (!P_CheckMissileRange (actor))
goto nomissile;
if (serverside)
P_SetMobjState (actor, actor->info->missilestate, true);
actor->flags |= MF_JUSTATTACKED;
return;
}
// ?
nomissile:
// possibly choose another target
if (!actor->threshold)
{
if (!(actor->flags & MF_FRIEND) && !co_pursuit)
{
if (multiplayer && !P_CheckSight(actor, actor->target) &&
P_LookForPlayers(actor, true))
{
return; // got a new target
}
}
else if (co_helpfriends && P_HelpFriend(actor))
{
return;
}
else if (actor->pursuecount)
{
actor->pursuecount--;
}
else
{
/* Our pursuit time has expired. We're going to think about
* changing targets */
actor->pursuecount = BASETHRESHOLD;
// look for new target, unless conditions are met
if (!(actor->target && // have a target
actor->target->health > 0 && // and the target is alive
(!P_IsFriendlyThing(actor, actor->target) ||
!(actor->flags & MF_FRIEND)) && // and the target is not friendly
P_CheckSight(actor, actor->target) // and we can see it
) &&
P_LookForTargets(actor, true))
return;
/* (Current target was good, or no new target was found.)
*
* If monster is a missile-less friend, give up pursuit and
* return to player, if no attacks have occurred recently.
*/
if (!actor->info->missilestate && actor->flags & MF_FRIEND)
{
if (actor->flags & MF_JUSTHIT) /* if recent action, */
actor->flags &= ~MF_JUSTHIT; /* keep fighting */
else if (P_LookForPlayers(actor, true)) /* else return to player */
return;
}
}
}
if (actor->strafecount)
actor->strafecount--;
// chase towards player
if (--actor->movecount < 0 || !P_SmartMove (actor))
{
P_NewChaseDir (actor);
}
// make active sound
if (actor->info->activesound && P_Random (actor) < 3)
{
S_Sound (actor, CHAN_VOICE, actor->info->activesound, 1, ATTN_IDLE);
}
}
//
// [RH] A_MonsterRail
//
// New function to let monsters shoot a railgun
//
void A_MonsterRail (AActor *actor)
{
if (!actor->target)
return;
actor->flags &= ~MF_AMBUSH;
actor->angle = R_PointToAngle2 (actor->x,
actor->y,
actor->target->x,
actor->target->y);
//actor->pitch = tantoangle[P_AimLineAttack (actor, actor->angle, MISSILERANGE) >> DBITS];
actor->pitch = -(int)(tan ((float)P_AimLineAttack (actor, actor->angle, MISSILERANGE)/65536.0f)*ANG180/PI);
// Let the aim trail behind the player
actor->angle = R_PointToAngle2 (actor->x,
actor->y,
actor->target->x - actor->target->momx * 3,
actor->target->y - actor->target->momy * 3);
if (actor->target->flags & MF_SHADOW)
{
int t = P_Random(actor);
actor->angle += (t-P_Random(actor))<<21;
}
P_RailAttack (actor, actor->info->damage, 0);
}
//
//
// A_PosAttack
//
void A_PosAttack (AActor *actor)
{
int angle;
int damage;
int slope;
if (!actor->target)
return;
A_FaceTarget (actor);
angle = actor->angle;
slope = P_AimLineAttack (actor, angle, MISSILERANGE);
S_Sound (actor, CHAN_WEAPON, "grunt/attack", 1, ATTN_NORM);
angle += P_RandomDiff (actor)<<20;
damage = ((P_Random (actor)%5)+1)*3;
P_LineAttack (actor, angle, MISSILERANGE, slope, damage);
}
void A_SPosAttack (AActor *actor)
{
int i;
int bangle;
int slope;
if (!actor->target)
return;
S_Sound (actor, CHAN_WEAPON, "shotguy/attack", 1, ATTN_NORM);
A_FaceTarget (actor);
bangle = actor->angle;
slope = P_AimLineAttack (actor, bangle, MISSILERANGE);
for (i=0 ; i<3 ; i++)
{
int angle = bangle + (P_RandomDiff (actor)<<20);
int damage = ((P_Random (actor)%5)+1)*3;
P_LineAttack(actor, angle, MISSILERANGE, slope, damage);
}
}
void A_CPosAttack (AActor *actor)
{
int angle;
int bangle;
int damage;
int slope;
if (!actor->target)
return;
S_Sound (actor, CHAN_WEAPON, "chainguy/attack", 1, ATTN_NORM);
A_FaceTarget (actor);
bangle = actor->angle;
slope = P_AimLineAttack (actor, bangle, MISSILERANGE);
angle = bangle + (P_RandomDiff (actor)<<20);
damage = ((P_Random (actor)%5)+1)*3;
P_LineAttack (actor, angle, MISSILERANGE, slope, damage);
}
void A_CPosRefire (AActor *actor)
{
// keep firing unless target got out of sight
A_FaceTarget (actor);
bool stop = false;
if (P_Random (actor) < 40)
return;
/* killough 12/98: Stop firing if a friend has gotten in the way */
if (actor->flags & MF_FRIEND && P_HitFriend(actor))
stop = true;
if (!actor->target
|| (actor->target->player && actor->target->player->spectator)
|| actor->target->health <= 0
|| !P_CheckSight(actor, actor->target)
|| stop
)
{
P_SetMobjState (actor, actor->info->seestate, true);
}
}
void A_SpidRefire (AActor *actor)
{
// keep firing unless target got out of sight
A_FaceTarget (actor);
bool stop = false;
if (P_Random (actor) < 10)
return;
/* killough 12/98: Stop firing if a friend has gotten in the way */
if (actor->flags & MF_FRIEND && P_HitFriend(actor))
stop = true;
if (!actor->target
|| (actor->target->player && actor->target->player->spectator)
|| actor->target->health <= 0
|| !P_CheckSight(actor, actor->target)
|| stop
)
{
P_SetMobjState (actor, actor->info->seestate, true);
}
}
void A_BspiAttack (AActor *actor)
{
if (!actor->target)
return;
A_FaceTarget (actor);
// launch a missile
if(serverside)
P_SpawnMissile (actor, actor->target, MT_ARACHPLAZ);
}
//
// A_TroopAttack
//
void A_TroopAttack (AActor *actor)
{
if (!actor->target)
return;
A_FaceTarget (actor);
if (P_CheckMeleeRange (actor))
{
S_Sound (actor, CHAN_WEAPON, "imp/melee", 1, ATTN_NORM);
int damage = (P_Random (actor)%8+1)*3;
P_DamageMobj (actor->target, actor, actor, damage, MOD_HIT);
return;
}
// launch a missile
if(serverside)
P_SpawnMissile (actor, actor->target, MT_TROOPSHOT);
}
void A_SargAttack (AActor *actor)
{
if (!actor->target)
return;
A_FaceTarget (actor);
if (P_CheckMeleeRange (actor))
{
int damage = ((P_Random (actor)%10)+1)*4;
P_DamageMobj (actor->target, actor, actor, damage, MOD_HIT);
}
}
void A_HeadAttack (AActor *actor)
{
if (!actor->target)
return;
A_FaceTarget (actor);
if (P_CheckMeleeRange (actor))
{
int damage = (P_Random (actor)%6+1)*10;
P_DamageMobj (actor->target, actor, actor, damage, MOD_HIT);
return;
}
// launch a missile
if(serverside)
P_SpawnMissile (actor, actor->target, MT_HEADSHOT);
}
void A_CyberAttack (AActor *actor)
{
if (!actor->target)
return;
A_FaceTarget (actor);
if(serverside)
{
P_SpawnMissile (actor, actor->target, MT_ROCKET);
}
}
void P_GiveFriendlyOwnerInfo(AActor* friendly, const AActor* origin)
{
if (origin->player && friendly->flags & MF_FRIEND)
{
friendly->friend_playerid = origin->player->id;
friendly->friend_teamid = origin->player->userinfo.team;
}
else if (origin->flags & MF_FRIEND)
{
friendly->friend_playerid = origin->friend_playerid;
friendly->friend_teamid = origin->friend_teamid;
}
}
void A_BruisAttack (AActor *actor)
{
if (!actor->target)
return;
if (P_CheckMeleeRange (actor))
{
int damage = (P_Random (actor)%8+1)*10;
S_Sound (actor, CHAN_WEAPON, "baron/melee", 1, ATTN_NORM);
P_DamageMobj (actor->target, actor, actor, damage, MOD_HIT);
return;
}
// launch a missile
if(serverside)
P_SpawnMissile (actor, actor->target, MT_BRUISERSHOT);
}
//
// A_SkelMissile
//
void A_SkelMissile (AActor *actor)
{
if (!actor->target)
return;
A_FaceTarget (actor);
if(serverside)
{
actor->z += 16*FRACUNIT; // so missile spawns higher
AActor *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;
}
}
#define TRACEANGLE (0xc000000)
void A_Tracer (AActor *actor)
{
// killough 1/18/98: this is why some missiles do not have smoke
// and some do. Also, internal demos start at random gametics, thus
// the bug in which revenants cause internal demos to go out of sync.
//
// killough 3/6/98: fix revenant internal demo bug by subtracting
// levelstarttic from gametic:
//
// [RH] level.time is always 0-based, so nothing special to do here.
// denis - demogametic must be 0-based, but from start of entire demo,
// not just this level!
extern int demostartgametic;
int demogametic = gametic - demostartgametic;
if (demogametic & 3)
return;
// spawn a puff of smoke behind the rocket
if(serverside)
{
P_SpawnTracerPuff(actor->x, actor->y, actor->z);
AActor* th = new AActor (actor->x - actor->momx,
actor->y - actor->momy,
actor->z, MT_SMOKE);
th->momz = FRACUNIT;
th->tics -= P_Random (th)&3;
if (th->tics < 1)
th->tics = 1;
}
// adjust direction
AActor *dest = actor->tracer;
if (!dest || dest->health <= 0)
return;
// change angle
angle_t exact = P_PointToAngle (actor->x,
actor->y,
dest->x,
dest->y);
if (exact != actor->angle)
{
if (exact - actor->angle > ANG180)
{
actor->angle -= TRACEANGLE;
if (exact - actor->angle < ANG180)
actor->angle = exact;
}
else
{
actor->angle += TRACEANGLE;
if (exact - actor->angle > ANG180)
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
fixed_t dist = P_AproxDistance (dest->x - actor->x,
dest->y - actor->y);
dist = dist / actor->info->speed;
if (dist < 1)
dist = 1;
fixed_t slope = (dest->z+40*FRACUNIT - actor->z) / dist;
if (slope < actor->momz)
actor->momz -= FRACUNIT/8;
else
actor->momz += FRACUNIT/8;
}
void A_SkelWhoosh (AActor *actor)
{
if (!actor->target)
return;
A_FaceTarget (actor);
S_Sound (actor, CHAN_WEAPON, "skeleton/swing", 1, ATTN_NORM);
}
void A_SkelFist (AActor *actor)
{
if (!actor->target)
return;
A_FaceTarget (actor);
if (P_CheckMeleeRange (actor))
{
int damage = ((P_Random (actor)%10)+1)*6;
S_Sound (actor, CHAN_WEAPON, "skeleton/melee", 1, ATTN_NORM);
P_DamageMobj (actor->target, actor, actor, damage, MOD_HIT);
}
}
//
// PIT_VileCheck
// Detect a corpse that could be raised.
//
AActor* corpsehit;
bool PIT_VileCheck (AActor& thing, AActor* /*vileobj*/, const fixed_t viletryx, const fixed_t viletryy, const int viletryradius)
{
bool check;
if (thing.oflags & MFO_NORAISE)
return true; // [AM] Can't raise
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
const int maxdist = thing.info->radius + viletryradius;
if ( abs(thing.x - viletryx) > maxdist
|| abs(thing.y - viletryy) > maxdist )
return true; // not actually touching
corpsehit = &thing;
corpsehit->momx = corpsehit->momy = 0;
if (P_AllowPassover())
corpsehit->flags |= MF_SOLID;
if (co_novileghosts)
{
corpsehit->flags |= MF_SOLID;
const int height = corpsehit->height; // save temporarily
const int radius = corpsehit->radius; // save temporarily
corpsehit->height = P_ThingInfoHeight(corpsehit->info);
corpsehit->radius = corpsehit->info->radius;
check = P_CheckPosition(corpsehit, corpsehit->x, corpsehit->y);
corpsehit->height = height; // restore
corpsehit->radius = radius; // restore
corpsehit->flags &= ~MF_SOLID;
}
else
{
corpsehit->height <<= 2;
check = P_CheckPosition(corpsehit, corpsehit->x, corpsehit->y);
corpsehit->height >>= 2;
}
if (P_AllowPassover())
corpsehit->flags &= ~MF_SOLID;
return !check;
}
//
// A_VileChase
// Check for ressurecting a body
//
void A_VileChase (AActor *actor)
{
if (!P_HealCorpse(actor, mobjinfo[MT_VILE].radius, S_VILE_HEAL1, 31)) //dsslop
A_Chase(actor); // Return to normal attack.
}
//
// A_VileStart
//
void A_VileStart (AActor *actor)
{
S_Sound (actor, CHAN_VOICE, "vile/start", 1, ATTN_NORM);
}
//
// A_Fire
// Keep fire in front of player unless out of sight
//
void A_Fire (AActor *actor);
void A_StartFire (AActor *actor)
{
S_Sound(actor, CHAN_BODY, "vile/firestrt", 1, ATTN_NORM);
A_Fire(actor);
}
void A_FireCrackle (AActor *actor)
{
S_Sound(actor, CHAN_BODY, "vile/firecrkl", 1, ATTN_NORM);
A_Fire(actor);
}
void A_Fire (AActor *actor)
{
AActor* 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;
actor->SetOrigin (dest->x + FixedMul (24*FRACUNIT, finecosine[an]),
dest->y + FixedMul (24*FRACUNIT, finesine[an]),
dest->z);
}
//
// A_VileTarget
// Spawn the hellfire
//
void A_VileTarget (AActor *actor)
{
AActor *fog;
if (!actor->target)
return;
A_FaceTarget (actor);
fog = new AActor (actor->target->x,
actor->target->x,
actor->target->z, MT_FIRE);
actor->tracer = fog->ptr();
fog->target = actor->ptr();
fog->tracer = actor->target;
A_Fire (fog);
}
//
// A_VileAttack
//
void A_VileAttack (AActor *actor)
{
AActor *fire;
int an;
if (!actor->target)
return;
A_FaceTarget (actor);
if (!P_CheckSight(actor, actor->target))
return;
S_Sound (actor, CHAN_WEAPON, "vile/stop", 1, ATTN_NORM);
P_DamageMobj(actor->target, actor, actor, 20, MOD_VILEFIRE);
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, 70, true, MOD_VILEFIRE);
}
//
// Mancubus attack,
// firing three missiles (bruisers) in three different directions?
// Doesn't look like it.
//
#define FATSPREAD (ANG90/8)
void A_FatRaise (AActor *actor)
{
A_FaceTarget (actor);
S_Sound (actor, CHAN_WEAPON, "fatso/raiseguns", 1, ATTN_NORM);
}
void A_FatAttack1 (AActor *actor)
{
if(!actor->target)
return;
A_FaceTarget (actor);
if(serverside)
{
// Change direction to ...
actor->angle += FATSPREAD;
P_SpawnMissile (actor, actor->target, MT_FATSHOT);
AActor *mo = P_SpawnMissile (actor, actor->target, MT_FATSHOT);
mo->angle += FATSPREAD;
int an = mo->angle >> ANGLETOFINESHIFT;
mo->momx = FixedMul(mo->info->speed, finecosine[an]);
mo->momy = FixedMul(mo->info->speed, finesine[an]);
}
}
void A_FatAttack2 (AActor *actor)
{
if(!actor->target)
return;
A_FaceTarget (actor);
if(serverside)
{
// Now here choose opposite deviation.
actor->angle -= FATSPREAD;
P_SpawnMissile (actor, actor->target, MT_FATSHOT);
AActor *mo = P_SpawnMissile (actor, actor->target, MT_FATSHOT);
mo->angle -= FATSPREAD*2;
int an = mo->angle >> ANGLETOFINESHIFT;
mo->momx = FixedMul(mo->info->speed, finecosine[an]);
mo->momy = FixedMul(mo->info->speed, finesine[an]);
}
}
void A_FatAttack3 (AActor *actor)
{
if(!actor->target)
return;
A_FaceTarget (actor);
if(serverside)
{
AActor *mo = P_SpawnMissile (actor, actor->target, MT_FATSHOT);
mo->angle -= FATSPREAD/2;
int 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]);
}
}
//
// killough 9/98: a mushroom explosion effect, sorta :)
// Original idea: Linguica
//
void A_Mushroom(AActor* actor)
{
if (!serverside)
return;
int n = actor->info->damage;
// Mushroom parameters are part of code pointer's state
fixed_t misc1 = actor->state->misc1 ? actor->state->misc1 : FRACUNIT * 4;
fixed_t misc2 = actor->state->misc2 ? actor->state->misc2 : FRACUNIT / 2;
A_Explode(actor); // make normal explosion
for (int i = -n; i <= n; i += 8) // launch mushroom cloud
{
for (int j = -n; j <= n; j += 8)
{
AActor* target =
new AActor(actor->x, actor->y, actor->z, MT_UNKNOWNTHING);
target->x += i << FRACBITS; // Aim in many directions from source
target->y += j << FRACBITS;
target->z += P_AproxDistance(i, j) * misc1; // Aim fairly high
AActor* mo = P_SpawnMissile(actor, target, MT_FATSHOT); // Launch fireball
if (mo != NULL)
{
mo->momx = FixedMul(mo->momx, misc2);
mo->momy = FixedMul(mo->momy, misc2); // Slow down a bit
mo->momz = FixedMul(mo->momz, misc2);
mo->flags &= ~MF_NOGRAVITY; // Make debris fall under gravity
}
target->Destroy();
}
}
}
//
// SkullAttack
// Fly at the player like a missile.
//
#define SKULLSPEED (20*FRACUNIT)
void A_SkullAttack (AActor *actor)
{
AActor* dest;
angle_t an;
int dist;
if (!actor->target)
return;
dest = actor->target;
actor->flags |= MF_SKULLFLY;
S_Sound (actor, CHAN_VOICE, actor->info->attacksound, 1, ATTN_NORM);
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_BetaSkullAttack()
// killough 10/98: this emulates the beta version's lost soul attacks
//
void A_BetaSkullAttack(AActor* actor)
{
if (!actor->target || actor->target->type == MT_SKULL)
return;
// [FG] fix crash when attack sound is missing
if (actor->info->attacksound)
{
S_Sound(actor, CHAN_VOICE, actor->info->attacksound, 1, ATTN_NORM);
}
A_FaceTarget(actor);
int damage = (P_Random(actor) % 8 + 1) * actor->info->damage;
P_DamageMobj(actor->target, actor, actor, damage);
}
//
// A_SpawnObject
// Basically just A_Spawn with better behavior and more args.
// args[0]: Type of actor to spawn
// args[1]: Angle (degrees, in fixed point), relative to calling actor's angle
// args[2]: X spawn offset (fixed point), relative to calling actor
// args[3]: Y spawn offset (fixed point), relative to calling actor
// args[4]: Z spawn offset (fixed point), relative to calling actor
// args[5]: X velocity (fixed point)
// args[6]: Y velocity (fixed point)
// args[7]: Z velocity (fixed point)
//
void A_SpawnObject(AActor* actor)
{
int type, angle, ofs_x, ofs_y, ofs_z, vel_x, vel_y, vel_z;
angle_t an;
fixed_t fan, dx, dy;
AActor* mo;
if (!actor->state->args[0] || !serverside)
return;
type = actor->state->args[0] - 1;
angle = actor->state->args[1];
ofs_x = actor->state->args[2];
ofs_y = actor->state->args[3];
ofs_z = actor->state->args[4];
vel_x = actor->state->args[5];
vel_y = actor->state->args[6];
vel_z = actor->state->args[7];
if (!CheckIfDehActorDefined((mobjtype_t)type))
{
I_Error("A_SpawnObject: Attempted to spawn undefined object type.");
}
// calculate position offsets
an = actor->angle + (unsigned int)(((int64_t)angle << 16) / 360);
fan = an >> ANGLETOFINESHIFT;
dx = FixedMul(ofs_x, finecosine[fan]) - FixedMul(ofs_y, finesine[fan]);
dy = FixedMul(ofs_x, finesine[fan]) + FixedMul(ofs_y, finecosine[fan]);
// spawn it, yo
mo = new AActor(actor->x + dx, actor->y + dy, actor->z + ofs_z, (mobjtype_t)type);
if (!mo)
return;
// angle dangle
mo->angle = an;
// set velocity
mo->momx = FixedMul(vel_x, finecosine[fan]) - FixedMul(vel_y, finesine[fan]);
mo->momy = FixedMul(vel_x, finesine[fan]) + FixedMul(vel_y, finecosine[fan]);
mo->momz = vel_z;
// if spawned object is a missile, set target+tracer
if (mo->info->flags & (MF_MISSILE | MF_BOUNCES))
{
// if spawner is also a missile, copy 'em
if (actor->info->flags & (MF_MISSILE | MF_BOUNCES))
{
mo->target = actor->target;
mo->tracer = actor->tracer;
}
// otherwise, set 'em as if a monster fired 'em
else
{
mo->target = actor->ptr();
mo->tracer = actor->target;
}
}
mo->flags = (mo->flags & ~MF_FRIEND) | (actor->flags & MF_FRIEND);
P_GiveFriendlyOwnerInfo(mo, actor);
P_FriendlyEffects(mo);
SV_UpdateMobj(mo);
}
//
// A_MonsterProjectile
// A parameterized monster projectile attack.
// args[0]: Type of actor to spawn
// args[1]: Angle (degrees, in fixed point), relative to calling actor's angle
// args[2]: Pitch (degrees, in fixed point), relative to calling actor's pitch; approximated
// args[3]: X/Y spawn offset, relative to calling actor's angle
// args[4]: Z spawn offset, relative to actor's default projectile fire height
//
void A_MonsterProjectile(AActor* actor)
{
int type, angle, pitch, spawnofs_xy, spawnofs_z;
AActor* mo;
int an;
if (!actor->target || !actor->state->args[0] || !serverside)
return;
type = actor->state->args[0] - 1;
angle = actor->state->args[1];
pitch = actor->state->args[2];
spawnofs_xy = actor->state->args[3];
spawnofs_z = actor->state->args[4];
if (!CheckIfDehActorDefined((mobjtype_t)type))
{
I_Error("A_MonsterProjectile: Attempted to spawn undefined projectile type.");
}
A_FaceTarget(actor);
mo = P_SpawnMissile(actor, actor->target, (mobjtype_t)type);
if (!mo)
return;
// adjust angle
mo->angle += (angle_t)(((int64_t)angle << 16) / 360);
an = mo->angle >> ANGLETOFINESHIFT;
mo->momx = FixedMul(mo->info->speed, finecosine[an]);
mo->momy = FixedMul(mo->info->speed, finesine[an]);
// adjust pitch (approximated, using Doom's ye olde
// finetangent table; same method as monster aim)
mo->momz += FixedMul(mo->info->speed, DegToSlope(pitch));
// adjust position
an = (actor->angle - ANG90) >> ANGLETOFINESHIFT;
mo->x += FixedMul(spawnofs_xy, finecosine[an]);
mo->y += FixedMul(spawnofs_xy, finesine[an]);
mo->z += spawnofs_z;
// always set the 'tracer' field, so this pointer
// can be used to fire seeker missiles at will.
mo->tracer = actor->target;
SV_UpdateMobj(mo);
}
//
// A_MonsterBulletAttack
// A parameterized monster bullet attack.
// args[0]: Horizontal spread (degrees, in fixed point)
// args[1]: Vertical spread (degrees, in fixed point)
// args[2]: Number of bullets to fire; if not set, defaults to 1
// args[3]: Base damage of attack (e.g. for 3d5, customize the 3); if not set, defaults to 3
// args[4]: Attack damage modulus (e.g. for 3d5, customize the 5); if not set, defaults to 5
//
void A_MonsterBulletAttack(AActor* actor)
{
int hspread, vspread, numbullets, damagebase, damagemod;
int aimslope, i, damage, angle, slope;
if (!actor->target)
return;
hspread = actor->state->args[0];
vspread = actor->state->args[1];
numbullets = actor->state->args[2];
damagebase = actor->state->args[3];
damagemod = actor->state->args[4];
A_FaceTarget(actor);
S_Sound(actor, CHAN_WEAPON, actor->info->attacksound, 1, ATTN_NORM);
aimslope = P_AimLineAttack(actor, actor->angle, MISSILERANGE);
for (i = 0; i < numbullets; i++)
{
damage = (P_Random(actor) % damagemod + 1) * damagebase;
angle = (int)actor->angle + P_RandomHitscanAngle(hspread);
slope = aimslope + P_RandomHitscanSlope(vspread);
P_LineAttack(actor, angle, MISSILERANGE, slope, damage);
}
}
//
// A_MonsterMeleeAttack
// A parameterized monster melee attack.
// args[0]: Base damage of attack (e.g. for 3d8, customize the 3); if not set, defaults to 3
// args[1]: Attack damage modulus (e.g. for 3d8, customize the 8); if not set, defaults to 8
// args[2]: Sound to play if attack hits args[3]: Range (fixed point); if not set, defaults to monster's melee range
//
void A_MonsterMeleeAttack(AActor* actor)
{
int damagebase, damagemod, hitsound, range;
int damage;
if (!actor->target)
return;
damagebase = actor->state->args[0];
damagemod = actor->state->args[1];
hitsound = actor->state->args[2];
range = actor->state->args[3];
if (range <= 0)
range = actor->info->meleerange;
range += actor->target->info->radius - 20 * FRACUNIT;
A_FaceTarget(actor);
if (!P_CheckRange(actor, range))
return;
S_Sound(actor, CHAN_WEAPON, SoundMap[hitsound].c_str(), 1, ATTN_NORM);
damage = (P_Random(actor) % damagemod + 1) * damagebase;
P_DamageMobj(actor->target, actor, actor, damage, MOD_HIT);
}
//
// A_RadiusDamage
// A parameterized version of A_Explode. Friggin' finally. :P
// args[0]: Damage (int)
// args[1]: Radius (also int; no real need for fractional precision here)
//
void A_RadiusDamage(AActor* actor)
{
if (!actor->state)
return;
P_RadiusAttack(actor, actor->target, actor->state->args[0], actor->state->args[1], true, MOD_R_SPLASH);
}
//
// A_NoiseAlert
// Alerts nearby monsters (via sound) to the calling actor's target's presence.
//
void A_NoiseAlert(AActor* actor)
{
if (!actor->target)
return;
P_NoiseAlert(actor->target, actor);
}
//
// A_HealChase
// A parameterized version of A_VileChase.
// args[0]: State to jump to on the calling actor when resurrecting a corpse
// args[1]: Sound to play when resurrecting a corpse
//
void A_HealChase(AActor* actor)
{
int state, sound;
if (!actor || !serverside)
return;
state = actor->state->args[0];
sound = actor->state->args[1];
if (!P_HealCorpse(actor, actor->info->radius, state, sound))
A_Chase(actor);
}
//
// P_HealCorpse
// A generic corpse resurrection codepointer.
//
bool P_HealCorpse(AActor* actor, int radius, int healstate, int healsound)
{
// don't attempt to resurrect clientside
if (!serverside)
{
return false;
}
if (actor->movedir != DI_NODIR)
{
// check for corpses to raise
const fixed_t viletryx = actor->x + (actor->info->speed * xspeed[actor->movedir]);
const fixed_t viletryy = actor->y + (actor->info->speed * yspeed[actor->movedir]);
const int xl = (viletryx - bmaporgx - MAXRADIUS * 2) >> MAPBLOCKSHIFT;
const int xh = (viletryx - bmaporgx + MAXRADIUS * 2) >> MAPBLOCKSHIFT;
const int yl = (viletryy - bmaporgy - MAXRADIUS * 2) >> MAPBLOCKSHIFT;
const int yh = (viletryy - bmaporgy + MAXRADIUS * 2) >> MAPBLOCKSHIFT;
auto* vileobj = actor;
const int viletryradius = radius;
for (int bx = xl; bx <= xh; bx++)
{
for (int by = yl; by <= yh; by++)
{
// Call PIT_VileCheck to check
// whether object is a corpse
// that can be raised.
if (!P_BlockThingsIterator(bx, by, PIT_VileCheck, nullptr, vileobj, viletryx, viletryy, viletryradius))
{
mobjinfo_t* info;
// got one!
AActor::AActorPtr temp = actor->target;
actor->target = corpsehit->ptr();
A_FaceTarget(actor);
actor->target = temp;
P_SetMobjState(actor, (statenum_t)healstate, true);
if (!clientside)
SV_Sound(corpsehit, CHAN_BODY, SoundMap[healsound].c_str(), ATTN_IDLE);
else
S_Sound(corpsehit, CHAN_BODY, SoundMap[healsound].c_str(), 1, ATTN_IDLE);
info = corpsehit->info;
corpsehit->flags =
(info->flags & ~MF_FRIEND) | (actor->flags & MF_FRIEND);
P_GiveFriendlyOwnerInfo(corpsehit, actor);
if (serverside)
{
if (!(actor->flags & MF_FRIEND))
{
level.respawned_monsters++;
SV_UpdateMonsterRespawnCount();
}
}
P_SetMobjState(corpsehit, info->raisestate);
SV_SendRaiseMobj(actor, corpsehit);
// [Nes] - Classic demo compatability: Ghost monster bug.
if ((co_novileghosts))
{
corpsehit->height =
P_ThingInfoHeight(info); // [RH] Use real mobj height
corpsehit->radius = info->radius; // [RH] Use real radius
}
else
{
corpsehit->height <<= 2;
}
corpsehit->health = info->spawnhealth;
corpsehit->lastenemy = AActor::AActorPtr();
corpsehit->target = AActor::AActorPtr();
return true;
}
}
}
}
return false;
}
//
// A_SeekTracer
// A parameterized seeker missile function.
// args[0]: direct-homing threshold angle (degrees, in fixed point)
// args[1]: maximum turn angle (degrees, in fixed point)
//
void A_SeekTracer(AActor* actor)
{
angle_t threshold, maxturnangle;
if (!actor || !serverside)
return;
threshold = FixedToAngle(actor->state->args[0]);
maxturnangle = FixedToAngle(actor->state->args[1]);
if (P_SeekerMissile(actor, actor->tracer, threshold, maxturnangle, true))
{
actor->flags2 |= MF2_SEEKERMISSILE;
SV_UpdateMobj(actor);
}
else
{
actor->flags2 &= ~MF2_SEEKERMISSILE;
}
}
//
// A_FindTracer
// Search for a valid tracer (seek target), if the calling actor doesn't already have one.
// args[0]: field-of-view to search in (degrees, in fixed point); if zero, will search
// in all directions
// args[1]: distance to search (map blocks, i.e. 128 units)
//
void A_FindTracer(AActor* actor)
{
angle_t fov;
int dist;
if (!actor || (actor->tracer != AActor::AActorPtr() && actor->tracer->health > 0) ||
!serverside)
return;
if (actor->tracer != AActor::AActorPtr() && actor->tracer->health <= 0)
actor->tracer = AActor::AActorPtr(); // [Blair] Clear tracer if it died, to keep
// with MBF21 spec
fov = FixedToAngle(actor->state->args[0]);
dist = (actor->state->args[1]);
AActor* tracer = P_RoughTargetSearch(actor, fov, dist, RoughTracerCheck);
if (!tracer || tracer->health <= 0)
return;
actor->tracer = tracer->ptr();
actor->flags2 |= MF2_SEEKERMISSILE;
SV_UpdateMobj(actor);
}
//
// A_ClearTracer
// Clear current tracer (seek target).
//
void A_ClearTracer(AActor* actor)
{
if (!actor || !serverside)
return;
actor->tracer = AActor::AActorPtr();
actor->flags2 &= ~MF2_SEEKERMISSILE;
SV_UpdateMobj(actor);
}
//
// A_JumpIfHealthBelow
// Jumps to a state if caller's health is below the specified threshold.
// args[0]: State to jump to
// args[1]: Health threshold
//
void A_JumpIfHealthBelow(AActor* actor)
{
int state, health;
if (!actor)
return;
state = actor->state->args[0];
health = actor->state->args[1];
if (actor->health < health)
P_SetMobjState(actor, (statenum_t)state, true);
}
//
// A_JumpIfTargetInSight
// Jumps to a state if caller's target is in line-of-sight.
// args[0]: State to jump to
// args[1]: Field-of-view to check (degrees, in fixed point); if zero, will check in all
// directions
//
void A_JumpIfTargetInSight(AActor* actor)
{
int state;
angle_t fov;
if (!actor || !actor->target)
return;
state = (actor->state->args[0]);
fov = FixedToAngle(actor->state->args[1]);
// Check FOV first since it's faster
if (fov > 0 && !P_CheckFov(actor, actor->target, fov))
return;
if (P_CheckSight(actor, actor->target))
P_SetMobjState(actor, (statenum_t)state, true);
}
//
// A_JumpIfTargetCloser
// Jumps to a state if caller's target is closer than the specified distance.
// args[0]: State to jump to
// args[1]: Distance threshold
//
void A_JumpIfTargetCloser(AActor* actor)
{
int state, distance;
if (!actor || !actor->target)
return;
state = actor->state->args[0];
distance = actor->state->args[1];
if (distance >
P_AproxDistance(actor->x - actor->target->x, actor->y - actor->target->y))
P_SetMobjState(actor, (statenum_t)state, true);
}
//
// A_JumpIfTracerInSight
// Jumps to a state if caller's tracer (seek target) is in line-of-sight.
// args[0]: State to jump to
// args[1]: Field-of-view to check (degrees, in fixed point); if zero, will check in all
// directions
//
void A_JumpIfTracerInSight(AActor* actor)
{
angle_t fov;
int state;
if (!actor || !actor->tracer || !serverside)
return;
state = (actor->state->args[0]);
fov = FixedToAngle(actor->state->args[1]);
// Check FOV first since it's faster
if (fov > 0 && !P_CheckFov(actor, actor->tracer, fov))
return;
if (actor->tracer->health <= 0)
return;
if (P_CheckSight(actor, actor->tracer))
P_SetMobjState(actor, (statenum_t)state, true);
}
//
// A_JumpIfTracerCloser
// Jumps to a state if caller's tracer (seek target) is closer than the specified
// distance.
// args[0]: State to jump to
// args[1]: Distance threshold (fixed point)
//
void A_JumpIfTracerCloser(AActor* actor)
{
int state, distance;
if (!actor || !actor->tracer || !serverside)
return;
state = actor->state->args[0];
distance = actor->state->args[1];
if (actor->tracer->health <= 0)
return;
if (distance >
P_AproxDistance(actor->x - actor->tracer->x, actor->y - actor->tracer->y))
P_SetMobjState(actor, (statenum_t)state, true);
}
//
// A_JumpIfFlagsSet
// Jumps to a state if caller has the specified thing flags set.
// args[0]: State to jump to
// args[1]: Standard Flag(s) to check
// args[2] and args[3]: MBF21 Flag(s) to check
// DeHackEd args3 is split into args[2] and args[3]
// because the MBF21 flags are split across AActor::flags2 and AActor::flags3
//
void A_JumpIfFlagsSet(AActor* actor)
{
if (!actor)
return;
const int state = actor->state->args[0];
const int flags = actor->state->args[1];
const int flags2 = actor->state->args[2];
const int flags3 = actor->state->args[3];
if ((actor->flags & flags) == flags &&
(actor->flags2 & flags2) == flags2 &&
(actor->flags3 & flags3) == flags3)
P_SetMobjState(actor, (statenum_t)state, true);
}
//
// A_AddFlags
// Adds the specified thing flags to the caller.
// args[0]: Standard Flag(s) to add
// args[1] and args[2]: MBF21 Flag(s) to add
// DeHackEd args2 is split into args[1] and args[2]
// because the MBF21 flags are split across AActor::flags2 and AActor::flags3
//
void A_AddFlags(AActor* actor)
{
if (!actor)
return;
const int flags = actor->state->args[0];
const int flags2 = actor->state->args[1];
const int flags3 = actor->state->args[2];
const bool update_blockmap =
((flags & MF_NOBLOCKMAP) && !(actor->flags & MF_NOBLOCKMAP)) ||
((flags & MF_NOSECTOR) && !(actor->flags & MF_NOSECTOR));
if (flags & MF_TRANSLUCENT)
actor->translucency = TRANSLUC66;
if (update_blockmap)
actor->UnlinkFromWorld();
actor->flags |= flags;
actor->flags2 |= flags2;
actor->flags3 |= flags3;
if (update_blockmap)
actor->LinkToWorld();
}
//
// A_RemoveFlags
// Removes the specified thing flags from the caller.
// args[0]: Flag(s) to remove
// args[1] and args[2]: MBF21 Flag(s) to remove
// DeHackEd args2 is split into args[1] and args[2]
// because the MBF21 flags are split across AActor::flags2 and AActor::flags3
//
void A_RemoveFlags(AActor* actor)
{
if (!actor)
return;
const int flags = actor->state->args[0];
const int flags2 = actor->state->args[1];
const int flags3 = actor->state->args[2];
const bool update_blockmap =
((flags & MF_NOBLOCKMAP) && (actor->flags & MF_NOBLOCKMAP)) ||
((flags & MF_NOSECTOR) && (actor->flags & MF_NOSECTOR));
if (flags & MF_TRANSLUCENT)
actor->translucency = FRACUNIT;
if (update_blockmap)
actor->UnlinkFromWorld();
actor->flags &= ~flags;
actor->flags2 &= ~flags2;
actor->flags3 &= ~flags3;
if (update_blockmap)
actor->LinkToWorld();
}
void A_Stop(AActor* actor)
{
actor->momx = actor->momy = actor->momz = 0;
}
// P_FriendlyEffects
void P_FriendlyEffects()
{
CLIENT_ONLY(
if (!cl_showfriends)
return;
TThinkerIterator<AActor> iterator;
AActor* other;
while ((other = iterator.Next()))
{
if (other->health <= 0)
{
other->effects &= ~FX_FRIENDHEARTS;
continue;
}
if (other->player || !(other->flags & MF_FRIEND) ||
(other->oflags & MFO_BOSSPOOL))
continue;
if (validplayer(displayplayer()) && displayplayer().mo &&
P_IsFriendlyThing(displayplayer().mo, other) && sentient(other))
{
other->effects |= FX_FRIENDHEARTS;
other->translation = translationref_t(&friendtable[0]);
}
else
{
other->effects &= ~FX_FRIENDHEARTS;
other->translation = nullptr;
}
}
)
}
void P_FriendlyEffects(AActor* mo)
{
CLIENT_ONLY(
if (!cl_showfriends)
return;
if (mo->health <= 0)
{
mo->effects &= ~FX_FRIENDHEARTS;
return;
}
if (mo->player || !(mo->flags & MF_FRIEND) ||
(mo->oflags & MFO_BOSSPOOL))
return;
if (validplayer(displayplayer()) && displayplayer().mo &&
P_IsFriendlyThing(displayplayer().mo, mo) && sentient(mo))
{
mo->effects |= FX_FRIENDHEARTS;
mo->translation = translationref_t(&friendtable[0]);
}
else
{
mo->effects &= ~FX_FRIENDHEARTS;
mo->translation = nullptr;
}
)
}
#ifdef CLIENT_APP
CVAR_FUNC_IMPL(cl_showfriends)
{
if (var)
{
P_FriendlyEffects();
}
else
{
// Clear all friendly effects
TThinkerIterator<AActor> iterator;
AActor* other;
while ((other = iterator.Next()))
{
if (other->flags & MF_FRIEND)
{
other->effects &= ~FX_FRIENDHEARTS;
other->translation = nullptr;
}
}
}
}
#endif
// P_RemoveSoulLimit
bool P_RemoveSoulLimit()
{
if (level.flags & LEVEL_COMPAT_LIMITPAIN)
return false;
return co_removesoullimit;
}
//
// A_PainShootSkull
// Spawn a lost soul and launch it at the target
//
void A_PainShootSkull (AActor *actor, angle_t angle)
{
if(!serverside)
return;
// count total number of skull currently on the level
int count = 0;
TThinkerIterator<AActor> iterator;
AActor* other;
while ( (other = iterator.Next ()) )
{
if (other->type == MT_SKULL)
count++;
}
// if there are already 20 skulls on the level,
// don't spit another one
// co_removesoullimit removes the standard limit
if (count > 20 && !P_RemoveSoulLimit())
return;
// multiplayer retains a hard limit of 128
if (multiplayer && count > 128)
return;
// okay, there's room for another one
const angle_t an = angle >> ANGLETOFINESHIFT;
const int prestep = 4*FRACUNIT + 3*(actor->info->radius + mobjinfo[MT_SKULL].radius)/2;
const fixed_t x = actor->x + FixedMul (prestep, finecosine[an]);
const fixed_t y = actor->y + FixedMul (prestep, finesine[an]);
const fixed_t z = actor->z + 8*FRACUNIT;
// Check whether the Lost Soul is being fired through a 1-sided // phares
// wall or an impassible line, or a "monsters can't cross" line.// |
// If it is, then we don't allow the spawn. // V
// denis - vanilla desync
/*
if (Check_Sides(actor,x,y))
return;*/
other = new AActor (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.
// denis - vanilla desync
/*
if ((other->z >
(other->subsector->sector->ceilingheight - other->height)) ||
(other->z < other->subsector->sector->floorheight))
{
// kill it immediately
P_DamageMobj (other, actor, actor, 10000, MOD_UNKNOWN); // ^
return; // |
} // phares
*/
/* killough 7/20/98: PEs shoot lost souls with the same friendliness */
other->flags = (other->flags & ~MF_FRIEND) | (actor->flags & MF_FRIEND);
P_GiveFriendlyOwnerInfo(other, actor);
// Check for movements.
if (!P_TryMove(other, x, y, false))
{
// kill it immediately
P_DamageMobj (other, actor, actor, 10000, MOD_UNKNOWN);
return;
}
other->target = actor->target;
A_SkullAttack (other);
}
//
// A_PainAttack
// Spawn a lost soul and launch it at the target
//
void A_PainAttack (AActor *actor)
{
if (!actor->target)
return;
A_FaceTarget (actor);
if(!serverside)
return;
A_PainShootSkull (actor, actor->angle);
}
void A_PainDie (AActor *actor)
{
A_Fall (actor);
if(!serverside)
return;
A_PainShootSkull (actor, actor->angle+ANG90);
A_PainShootSkull (actor, actor->angle+ANG180);
A_PainShootSkull (actor, actor->angle+ANG270);
}
void A_Scream (AActor *actor)
{
char sound[MAX_SNDNAME];
if (actor->info->deathsound == NULL)
return;
M_StringCopy(sound, actor->info->deathsound, MAX_SNDNAME);
if (stricmp(sound, "grunt/death1") == 0 ||
stricmp(sound, "shotguy/death1") == 0 ||
stricmp(sound, "chainguy/death1") == 0)
{
sound[strlen(sound) - 1] = P_Random(actor) % 3 + '1';
}
if (stricmp(sound, "imp/death1") == 0 ||
stricmp(sound, "imp/death2") == 0)
{
sound[strlen(sound) - 1] = P_Random(actor) % 2 + '1';
}
if (!co_zdoomsound && (actor->flags2 & MF2_BOSS || actor->flags3 & MF3_FULLVOLSOUNDS))
S_Sound(CHAN_VOICE, sound, 1, ATTN_NORM);
else
S_Sound (actor, CHAN_VOICE, sound, 1, ATTN_NORM);
}
void A_XScream (AActor *actor)
{
if (actor->player)
S_Sound (actor, CHAN_VOICE, "*gibbed", 1, ATTN_NORM);
else
S_Sound (actor, CHAN_VOICE, "misc/gibbed", 1, ATTN_NORM);
}
void A_Pain (AActor *actor)
{
if (actor->info->painsound)
S_Sound (actor, CHAN_VOICE, actor->info->painsound, 1, ATTN_NORM);
}
void A_Fall (AActor *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.
// Remove any sort of boss effect on kill
// OFlags hack because of client issues
// Only remove the sparkling fountain, keep the transition
if (actor->type != MT_PLAYER && actor->effects)
{
actor->effects = 0;
}
}
// killough 11/98: kill an object
void A_Die (AActor *actor)
{
P_DamageMobj (actor, NULL, NULL, actor->health, MOD_UNKNOWN);
}
//
// A_Detonate
// killough 8/9/98: same as A_Explode, except that the damage is variable
//
void A_Detonate (AActor *mo)
{
P_RadiusAttack (mo, mo->target, mo->damage, mo->damage, true, MOD_R_SPLASH);
if (mo->z <= mo->floorz + (mo->damage<<FRACBITS))
{
P_HitFloor (mo);
}
}
//
// A_Explode
//
void A_Explode (AActor *thing)
{
// [RH] figure out means of death;
int mod;
static constexpr int damage = 128;
static constexpr int distance = 128;
static constexpr bool hurtSource = true;
switch (thing->type) {
case MT_BARREL:
mod = MOD_BARREL;
break;
case MT_ROCKET:
mod = MOD_R_SPLASH;
break;
default:
mod = MOD_R_SPLASH;
break;
}
P_RadiusAttack (thing, thing->target, damage, distance, hurtSource, mod);
}
//
// P_Massacre
// Kill all monsters in the current level
// Currently only used by A_BossDeath for specialaction_killmonsters
//
int P_Massacre()
{
int killcount = 0;
AActor* actor;
TThinkerIterator<AActor> it;
while ( (actor = it.Next()) )
{
if ((actor->flags & MF_SHOOTABLE) && (actor->flags & MF_COUNTKILL ||
(actor->type == MT_SKULL)))
{
if (actor->health > 0)
{
killcount++;
actor->flags2 &= ~(MF2_DORMANT | MF2_INVULNERABLE);
P_DamageMobj(actor, nullptr, nullptr, 10000);
}
if (actor->type == MT_PAIN)
{
// todo: this is based on woof and zdoom behavior, but doesn't
// really account for dehacked adding other spawn-on-death monsters
A_Fall(actor);
P_SetMobjState(actor, S_PAIN_DIE6);
}
}
}
return killcount;
}
//
// A_BossDeath
// Possibly trigger special effects if on a boss level
//
void A_BossDeath(AActor *actor)
{
// make sure there is a player alive for victory
if (std::none_of(players.begin(), players.end(), [](const player_t& p){ return p.ingame() && p.health > 0; }))
return; // no one left alive, so do not end game
if (!level.bossactions.empty())
{
// see if a BossAction applies to this type
const auto ba = std::find_if(level.bossactions.begin(), level.bossactions.end(),
[&actor](bossaction_t ba){
return (ba.type == actor->type) || (ba.flags & actor->flags3);
}
);
if (ba == level.bossactions.end())
return;
// scan the remaining thinkers to see if all bosses are dead
TThinkerIterator<AActor> iterator;
AActor* other;
while ((other = iterator.Next()))
{
if (other != actor && other->type == actor->type && other->health > 0)
{
// other boss not dead
return;
}
}
for (const bossaction_t& ba : level.bossactions)
{
if ((ba.type == actor->type) || (ba.flags & actor->flags3))
{
// TODO: if a standardized line special for massacre is introduced, use that instead
if (ba.special == 280)
{
P_Massacre();
continue;
}
// De-facto UMAPINFO standard interpretation: activate the bossaction linespecial
// as though it's associated with line 0.
line_t ld = lines[0];
if (map_format.getZDoom())
{
maplinedef_t mld;
mld.special = (ba.special);
mld.tag = (ba.tag);
P_TranslateLineDef(&ld, &mld);
}
else
{
ld.special = ba.special;
ld.id = ba.tag;
}
if (!P_UseSpecialLine(actor, &ld, 0, true))
P_CrossSpecialLine(&ld, 0, actor, true);
}
}
}
}
void A_Hoof (AActor *mo)
{
S_Sound (mo, CHAN_BODY, "cyber/hoof", 1, ATTN_IDLE);
A_Chase (mo);
}
void A_Metal (AActor *mo)
{
S_Sound (mo, CHAN_BODY, "spider/walk", 1, ATTN_IDLE);
A_Chase (mo);
}
void A_BabyMetal (AActor *mo)
{
S_Sound (mo, CHAN_BODY, "baby/walk", 1, ATTN_IDLE);
A_Chase (mo);
}
// killough 2/7/98: Remove limit on icon landings:
AActor **braintargets;
int numbraintargets_alloc;
int numbraintargets;
struct brain_s brain; // killough 3/26/98: global state of boss brain
// killough 3/26/98: initialize icon landings at level startup,
// rather than at boss wakeup, to prevent savegame-related crashes
void P_SpawnBrainTargets (void) // killough 3/26/98: renamed old function
{
AActor *other;
TThinkerIterator<AActor> iterator;
// find all the target spots
numbraintargets = 0;
brain.targeton = 0;
brain.easy = 0; // killough 3/26/98: always init easy to 0
while ( (other = iterator.Next ()) )
{
if (other->type == MT_BOSSTARGET)
{ // killough 2/7/98: remove limit on icon landings:
if (numbraintargets >= numbraintargets_alloc)
{
braintargets = (AActor **)M_Realloc (braintargets,
(numbraintargets_alloc = numbraintargets_alloc ?
numbraintargets_alloc*2 : 32) *sizeof *braintargets);
}
braintargets[numbraintargets++] = other;
}
}
}
void A_BrainAwake (AActor *mo)
{
// killough 3/26/98: only generates sound now
S_Sound (mo, CHAN_VOICE, "brain/sight", 1, ATTN_NONE);
}
void A_BrainPain (AActor *mo)
{
S_Sound (mo, CHAN_VOICE, "brain/pain", 1, ATTN_NONE);
}
void A_BrainScream (AActor *mo)
{
if(!clientside)
return;
int x;
int y;
int z;
AActor* th;
for (x=mo->x - 196*FRACUNIT ; x< mo->x + 320*FRACUNIT ; x+= FRACUNIT*8)
{
y = mo->y - 320*FRACUNIT;
z = 128 + (P_Random (mo) << (FRACBITS + 1));
th = new AActor (x,y,z, MT_ROCKET);
th->momz = P_Random (mo) << 9;
P_SetMobjState (th, S_BRAINEXPLODE1, true);
th->tics -= P_Random (mo) & 7;
if (th->tics < 1)
th->tics = 1;
}
S_Sound (mo, CHAN_VOICE, "brain/death", 1, ATTN_NONE);
}
void A_BrainExplode (AActor *mo)
{
if(!clientside)
return;
int x = mo->x + P_RandomDiff (mo)*2048;
int y = mo->y;
int z = 128 + P_Random (mo)*2*FRACUNIT;
AActor *th = new AActor (x, y, z, MT_ROCKET);
th->momz = P_Random (mo) << 9;
P_SetMobjState (th, S_BRAINEXPLODE1, true);
th->tics -= P_Random (mo) & 7;
if (th->tics < 1)
th->tics = 1;
}
void A_BrainDie (AActor *mo)
{
// [RH] If noexit, then don't end the level.
if (sv_gametype != GM_COOP && !sv_allowexit)
return;
G_ExitLevel (0, 1);
}
void A_BrainSpit (AActor *mo)
{
if(!serverside)
return;
// [RH] Do nothing if there are no brain targets.
if (numbraintargets == 0)
return;
brain.easy ^= 1; // killough 3/26/98: use brain struct
if (G_GetCurrentSkill().easy_boss_brain && (!brain.easy))
return;
// shoot a cube at current target
AActor* targ = braintargets[brain.targeton++]; // killough 3/26/98:
brain.targeton %= numbraintargets; // Use brain struct for targets
// spawn brain missile
AActor* newmobj = P_SpawnMissile(mo, targ, MT_SPAWNSHOT);
if(newmobj)
{
newmobj->target = targ->ptr();
newmobj->reactiontime =
((targ->y - mo->y)/newmobj->momy) / newmobj->state->tics;
// killough 7/18/98: brain friendliness is transferred
newmobj->flags = (newmobj->flags & ~MF_FRIEND) | (mo->flags & MF_FRIEND);
P_GiveFriendlyOwnerInfo(newmobj, mo);
}
S_Sound (mo, CHAN_WEAPON, "brain/spit", 1, ATTN_NONE);
}
void A_SpawnFly (AActor *mo);
// travelling cube sound
void A_SpawnSound (AActor *mo)
{
S_Sound (mo, CHAN_BODY, "brain/cube", 1, ATTN_IDLE);
A_SpawnFly(mo);
}
void A_SpawnFly (AActor *mo)
{
AActor* newmobj;
AActor* fog;
AActor* targ;
int r;
mobjtype_t type;
if (--mo->reactiontime)
return; // still flying
if(!serverside)
return;
targ = mo->target;
// When loading a save game, any in-flight cube will have lost its pointer to its target.
if (!targ) {
mo->Destroy ();
return;
}
// First spawn teleport fog.
fog = new AActor (targ->x, targ->y, targ->z, MT_SPAWNFIRE);
S_Sound (fog, CHAN_BODY, "misc/teleport", 1, ATTN_NORM);
// Randomly select monster to spawn.
r = P_Random (mo);
// 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 = new AActor (targ->x, targ->y, targ->z, type);
/* killough 7/18/98: brain friendliness is transferred */
newmobj->flags = (newmobj->flags & ~MF_FRIEND) | (mo->flags & MF_FRIEND);
P_GiveFriendlyOwnerInfo(newmobj, mo);
if (P_LookForTargets (newmobj, true))
P_SetMobjState (newmobj, newmobj->info->seestate, true);
// telefrag anything in this spot
P_TeleportMove (newmobj, newmobj->x, newmobj->y, newmobj->z, true);
// remove self (i.e., cube).
mo->Destroy ();
// [SL] 2011-06-19 - Emulate vanilla doom bug where monsters spawned after
// the start of the level (eg, spawned from a cube) are respawned at map
// location (0, 0).
memset(&newmobj->spawnpoint, 0, sizeof(newmobj->spawnpoint));
}
void A_PlayerScream (AActor *mo)
{
if (!mo)
return;
char nametemp[128];
const char *sound;
// [SL] 2011-06-15 - Spectators shouldn't make any noises
if (mo->player && mo->player->spectator)
return;
// [Fly] added for csDoom
if (mo->health < -mo->info->spawnhealth)
{
A_XScream (mo);
return;
}
if (!(gameinfo.flags & GI_NOCRAZYDEATH) && (mo->health < -50))
{
// IF THE PLAYER DIES LESS THAN -50% WITHOUT GIBBING
sound = "*xdeath1";
} else {
// [RH] More variety in death sounds
//sprintf (nametemp, "*death%d", (M_Random ()&3) + 1); // denis - do not use randomness source!
snprintf (nametemp, 128, "*death1");
sound = nametemp;
}
S_Sound (mo, CHAN_VOICE, sound, 1, ATTN_NORM);
}
void A_Gibify(AActor *mo) // denis - squash thing
{
mo->flags &= ~MF_SOLID;
mo->height = 0;
mo->radius = 0;
}
//
// killough 11/98
//
// The following were inspired by Len Pitre
//
// A small set of highly-sought-after code pointers
//
void A_Spawn(AActor* mo)
{
if (!serverside)
return;
// Partial integration of A_Spawn.
// ToDo: Currently missing MBF's MF_FRIEND flag support!
if (mo->state->misc1)
{
AActor* newmobj;
newmobj = new AActor(mo->x, mo->y, (mo->state->misc2 << FRACBITS) + mo->z,
(mobjtype_t)(mo->state->misc1 - 1));
newmobj->flags = (newmobj->flags & ~MF_FRIEND) | (mo->flags & MF_FRIEND);
P_GiveFriendlyOwnerInfo(newmobj, mo);
P_FriendlyEffects(newmobj);
}
}
void A_Turn(AActor* mo)
{
mo->angle += (angle_t)(((uint64_t)mo->state->misc1 << 32) / 360);
}
void A_Face(AActor* mo)
{
mo->angle = (angle_t)(((uint64_t)mo->state->misc1 << 32) / 360);
}
void A_Scratch(AActor* mo)
{
if (mo->target)
{
A_FaceTarget(mo);
if (P_CheckMeleeRange(mo))
{
if (mo->state->misc2)
S_Sound(mo, CHAN_BODY, SoundMap[mo->state->misc2].c_str(), 1, ATTN_NORM);
P_DamageMobj(mo->target, mo, mo, mo->state->misc1);
}
}
}
void A_PlaySound(AActor* mo)
{
// Play the sound from the SoundMap
int sndmap = mo->state->misc1;
const char* snd;
auto soundIt = SoundMap.find(sndmap);
if (soundIt == SoundMap.end())
{
DPrintFmt("Warning: Sound ID is beyond the array of the Sound Map!\n");
snd = nullptr;
}
else
{
snd = soundIt->second.c_str();
}
S_Sound(mo, CHAN_BODY, snd, 1,
(mo->state->misc2 ? ATTN_NONE : ATTN_NORM));
}
void A_RandomJump(AActor* mo)
{
if (P_Random(mo) < mo->state->misc2)
P_SetMobjState(mo, (statenum_t)mo->state->misc1);
}
//
// This allows linedef effects to be activated inside deh frames.
//
void A_LineEffect(AActor* mo)
{
/* [AM] Not implemented...yet. */
if (!(mo->oflags & MFO_LINEDONE)) // Unless already used up
{
line_t junk = *lines; // Fake linedef set to 1st
if ((junk.special = (short)mo->state->misc1)) // Linedef type
{
// [FG] made static
static player_t player; // Remember player status
player_t* oldplayer = mo->player; // Remember player status
mo->player = &player; // Fake player
player.health = 100; // Alive player
junk.id = (short)mo->state->misc2; // Sector tag for linedef
if (!P_UseSpecialLine(mo, &junk, 0, mo->flags & MF2_BOSS)) // Try using it
P_CrossSpecialLine(&junk, 0, mo, mo->flags & MF2_BOSS); // Try crossing it
if (!junk.special) // If type cleared,
mo->oflags |= MFO_LINEDONE; // no more for this thing
mo->player = oldplayer; // Restore player status
}
}
}
VERSION_CONTROL (p_enemy_cpp, "$Id$")