1958 lines
40 KiB
C
1958 lines
40 KiB
C
// Emacs style mode select -*- C++ -*-
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Id:$
|
|
//
|
|
// Copyright (C) 1993-1996 by id Software, Inc.
|
|
//
|
|
// This source is available for distribution and/or modification
|
|
// only under the terms of the DOOM Source Code License as
|
|
// published by id Software. All rights reserved.
|
|
//
|
|
// The source is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
|
|
// for more details.
|
|
//
|
|
// $Log:$
|
|
//
|
|
// DESCRIPTION:
|
|
// Enemy thinking, AI.
|
|
// Action Pointer Functions
|
|
// that are associated with states/frames.
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
#include "doom_config.h"
|
|
|
|
|
|
|
|
#include "m_random.h"
|
|
#include "i_system.h"
|
|
#include "doomdef.h"
|
|
#include "p_local.h"
|
|
#include "s_sound.h"
|
|
#include "g_game.h"
|
|
#include "doomstat.h" // State.
|
|
#include "r_state.h" // State.
|
|
#include "sounds.h" // Data.
|
|
|
|
|
|
#define MAXSPECIALCROSS 8
|
|
#define FATSPREAD (ANG90/8)
|
|
#define SKULLSPEED (20*FRACUNIT)
|
|
|
|
|
|
typedef enum
|
|
{
|
|
DI_EAST,
|
|
DI_NORTHEAST,
|
|
DI_NORTH,
|
|
DI_NORTHWEST,
|
|
DI_WEST,
|
|
DI_SOUTHWEST,
|
|
DI_SOUTH,
|
|
DI_SOUTHEAST,
|
|
DI_NODIR,
|
|
NUMDIRS
|
|
} dirtype_t;
|
|
|
|
|
|
//
|
|
// P_NewChaseDir related LUT.
|
|
//
|
|
dirtype_t opposite[] =
|
|
{
|
|
DI_WEST, DI_SOUTHWEST, DI_SOUTH, DI_SOUTHEAST,
|
|
DI_EAST, DI_NORTHEAST, DI_NORTH, DI_NORTHWEST, DI_NODIR
|
|
};
|
|
|
|
dirtype_t diags[] =
|
|
{
|
|
DI_NORTHWEST, DI_NORTHEAST, DI_SOUTHWEST, DI_SOUTHEAST
|
|
};
|
|
|
|
mobj_t* soundtarget;
|
|
fixed_t xspeed[8] = { FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000 };
|
|
fixed_t yspeed[8] = { 0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000 };
|
|
int TRACEANGLE = 0xc000000;
|
|
mobj_t* corpsehit;
|
|
mobj_t* vileobj;
|
|
fixed_t viletryx;
|
|
fixed_t viletryy;
|
|
mobj_t* braintargets[32];
|
|
int numbraintargets;
|
|
int braintargeton;
|
|
|
|
|
|
extern line_t* spechit[MAXSPECIALCROSS];
|
|
extern int numspechit;
|
|
|
|
|
|
void A_Fall(mobj_t* actor);
|
|
void A_Fire(mobj_t* actor);
|
|
void A_ReFire(player_t* player, pspdef_t* psp);
|
|
void A_SpawnFly(mobj_t* mo);
|
|
|
|
|
|
//
|
|
// ENEMY THINKING
|
|
// Enemies are allways spawned
|
|
// with targetplayer = -1, threshold = 0
|
|
// Most monsters are spawned unaware of all players,
|
|
// but some can be made preaware
|
|
//
|
|
|
|
|
|
//
|
|
// Called by P_NoiseAlert.
|
|
// Recursively traverse adjacent sectors,
|
|
// sound blocking lines cut off traversal.
|
|
//
|
|
void P_RecursiveSound(sector_t* sec, int soundblocks)
|
|
{
|
|
int i;
|
|
line_t* check;
|
|
sector_t* other;
|
|
|
|
// wake up all monsters in this sector
|
|
if (sec->validcount == validcount
|
|
&& sec->soundtraversed <= soundblocks + 1)
|
|
{
|
|
return; // already flooded
|
|
}
|
|
|
|
sec->validcount = validcount;
|
|
sec->soundtraversed = soundblocks + 1;
|
|
sec->soundtarget = soundtarget;
|
|
|
|
for (i = 0; i < sec->linecount; i++)
|
|
{
|
|
check = sec->lines[i];
|
|
if (!(check->flags & ML_TWOSIDED))
|
|
continue;
|
|
|
|
P_LineOpening(check);
|
|
|
|
if (openrange <= 0)
|
|
continue; // closed door
|
|
|
|
if (sides[check->sidenum[0]].sector == sec)
|
|
other = sides[check->sidenum[1]].sector;
|
|
else
|
|
other = sides[check->sidenum[0]].sector;
|
|
|
|
if (check->flags & ML_SOUNDBLOCK)
|
|
{
|
|
if (!soundblocks)
|
|
P_RecursiveSound(other, 1);
|
|
}
|
|
else
|
|
P_RecursiveSound(other, soundblocks);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// P_NoiseAlert
|
|
// If a monster yells at a player,
|
|
// it will alert other monsters to the player.
|
|
//
|
|
void P_NoiseAlert(mobj_t* target, mobj_t* emmiter)
|
|
{
|
|
soundtarget = target;
|
|
validcount++;
|
|
P_RecursiveSound(emmiter->subsector->sector, 0);
|
|
}
|
|
|
|
|
|
//
|
|
// P_CheckMeleeRange
|
|
//
|
|
doom_boolean P_CheckMeleeRange(mobj_t* actor)
|
|
{
|
|
mobj_t* pl;
|
|
fixed_t dist;
|
|
|
|
if (!actor->target)
|
|
return false;
|
|
|
|
pl = actor->target;
|
|
dist = P_AproxDistance(pl->x - actor->x, pl->y - actor->y);
|
|
|
|
if (dist >= MELEERANGE - 20 * FRACUNIT + pl->info->radius)
|
|
return false;
|
|
|
|
if (!P_CheckSight(actor, actor->target))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// P_CheckMissileRange
|
|
//
|
|
doom_boolean P_CheckMissileRange(mobj_t* actor)
|
|
{
|
|
fixed_t dist;
|
|
|
|
if (!P_CheckSight(actor, actor->target))
|
|
return false;
|
|
|
|
if (actor->flags & MF_JUSTHIT)
|
|
{
|
|
// the target just hit the enemy,
|
|
// so fight back!
|
|
actor->flags &= ~MF_JUSTHIT;
|
|
return true;
|
|
}
|
|
|
|
if (actor->reactiontime)
|
|
return false; // do not attack yet
|
|
|
|
// OPTIMIZE: get this from a global checksight
|
|
dist = P_AproxDistance(actor->x - actor->target->x,
|
|
actor->y - actor->target->y) - 64 * FRACUNIT;
|
|
|
|
if (!actor->info->meleestate)
|
|
dist -= 128 * FRACUNIT; // no melee attack, so fire more
|
|
|
|
dist >>= 16;
|
|
|
|
if (actor->type == MT_VILE)
|
|
{
|
|
if (dist > 14 * 64)
|
|
return false; // too far away
|
|
}
|
|
|
|
|
|
if (actor->type == MT_UNDEAD)
|
|
{
|
|
if (dist < 196)
|
|
return false; // close for fist attack
|
|
dist >>= 1;
|
|
}
|
|
|
|
if (actor->type == MT_CYBORG
|
|
|| actor->type == MT_SPIDER
|
|
|| actor->type == MT_SKULL)
|
|
{
|
|
dist >>= 1;
|
|
}
|
|
|
|
if (dist > 200)
|
|
dist = 200;
|
|
|
|
if (actor->type == MT_CYBORG && dist > 160)
|
|
dist = 160;
|
|
|
|
if (P_Random() < dist)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//
|
|
// P_Move
|
|
// Move in the current direction,
|
|
// returns false if the move is blocked.
|
|
//
|
|
doom_boolean P_Move(mobj_t* actor)
|
|
{
|
|
fixed_t tryx;
|
|
fixed_t tryy;
|
|
|
|
line_t* ld;
|
|
|
|
// warning: 'catch', 'throw', and 'try'
|
|
// are all C++ reserved words
|
|
doom_boolean try_ok;
|
|
doom_boolean good;
|
|
|
|
if (actor->movedir == DI_NODIR)
|
|
return false;
|
|
|
|
if ((unsigned)actor->movedir >= 8)
|
|
I_Error("Error: Weird actor->movedir!");
|
|
|
|
tryx = actor->x + actor->info->speed * xspeed[actor->movedir];
|
|
tryy = actor->y + actor->info->speed * yspeed[actor->movedir];
|
|
|
|
try_ok = P_TryMove(actor, tryx, tryy);
|
|
|
|
if (!try_ok)
|
|
{
|
|
// open any specials
|
|
if (actor->flags & MF_FLOAT && floatok)
|
|
{
|
|
// must adjust height
|
|
if (actor->z < tmfloorz)
|
|
actor->z += FLOATSPEED;
|
|
else
|
|
actor->z -= FLOATSPEED;
|
|
|
|
actor->flags |= MF_INFLOAT;
|
|
return true;
|
|
}
|
|
|
|
if (!numspechit)
|
|
return false;
|
|
|
|
actor->movedir = DI_NODIR;
|
|
good = false;
|
|
while (numspechit--)
|
|
{
|
|
ld = spechit[numspechit];
|
|
// if the special is not a door
|
|
// that can be opened,
|
|
// return false
|
|
if (P_UseSpecialLine(actor, ld, 0))
|
|
good = true;
|
|
}
|
|
return good;
|
|
}
|
|
else
|
|
{
|
|
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.
|
|
//
|
|
doom_boolean 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("Error: P_NewChaseDir: called with no target");
|
|
|
|
olddir = actor->movedir;
|
|
turnaround = opposite[olddir];
|
|
|
|
deltax = actor->target->x - actor->x;
|
|
deltay = actor->target->y - actor->y;
|
|
|
|
if (deltax > 10 * FRACUNIT)
|
|
d[1] = DI_EAST;
|
|
else if (deltax < -10 * FRACUNIT)
|
|
d[1] = DI_WEST;
|
|
else
|
|
d[1] = DI_NODIR;
|
|
|
|
if (deltay < -10 * FRACUNIT)
|
|
d[2] = DI_SOUTH;
|
|
else if (deltay > 10 * FRACUNIT)
|
|
d[2] = DI_NORTH;
|
|
else
|
|
d[2] = DI_NODIR;
|
|
|
|
// try direct route
|
|
if (d[1] != DI_NODIR
|
|
&& d[2] != DI_NODIR)
|
|
{
|
|
actor->movedir = diags[((deltay < 0) << 1) + (deltax > 0)];
|
|
if (actor->movedir != turnaround && P_TryWalk(actor))
|
|
return;
|
|
}
|
|
|
|
// try other directions
|
|
if (P_Random() > 200
|
|
|| doom_abs(deltay) > doom_abs(deltax))
|
|
{
|
|
tdir = d[1];
|
|
d[1] = d[2];
|
|
d[2] = tdir;
|
|
}
|
|
|
|
if (d[1] == turnaround)
|
|
d[1] = DI_NODIR;
|
|
if (d[2] == turnaround)
|
|
d[2] = DI_NODIR;
|
|
|
|
if (d[1] != DI_NODIR)
|
|
{
|
|
actor->movedir = d[1];
|
|
if (P_TryWalk(actor))
|
|
{
|
|
// either moved forward or attacked
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (d[2] != DI_NODIR)
|
|
{
|
|
actor->movedir = d[2];
|
|
|
|
if (P_TryWalk(actor))
|
|
return;
|
|
}
|
|
|
|
// there is no direct path to the player,
|
|
// so pick another direction.
|
|
if (olddir != DI_NODIR)
|
|
{
|
|
actor->movedir = olddir;
|
|
|
|
if (P_TryWalk(actor))
|
|
return;
|
|
}
|
|
|
|
// randomly determine direction of search
|
|
if (P_Random() & 1)
|
|
{
|
|
for (tdir = DI_EAST;
|
|
tdir <= DI_SOUTHEAST;
|
|
tdir++)
|
|
{
|
|
if (tdir != turnaround)
|
|
{
|
|
actor->movedir = tdir;
|
|
|
|
if (P_TryWalk(actor))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (tdir = DI_SOUTHEAST;
|
|
tdir != (DI_EAST - 1);
|
|
tdir--)
|
|
{
|
|
if (tdir != turnaround)
|
|
{
|
|
actor->movedir = tdir;
|
|
|
|
if (P_TryWalk(actor))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (turnaround != DI_NODIR)
|
|
{
|
|
actor->movedir = turnaround;
|
|
if (P_TryWalk(actor))
|
|
return;
|
|
}
|
|
|
|
actor->movedir = DI_NODIR; // can not move
|
|
}
|
|
|
|
|
|
//
|
|
// P_LookForPlayers
|
|
// If allaround is false, only look 180 degrees in front.
|
|
// Returns true if a player is targeted.
|
|
//
|
|
doom_boolean P_LookForPlayers(mobj_t* actor, doom_boolean allaround)
|
|
{
|
|
int c;
|
|
int stop;
|
|
player_t* player;
|
|
sector_t* sector;
|
|
angle_t an;
|
|
fixed_t dist;
|
|
|
|
sector = actor->subsector->sector;
|
|
|
|
c = 0;
|
|
stop = (actor->lastlook - 1) & 3;
|
|
|
|
for (; ; actor->lastlook = (actor->lastlook + 1) & 3)
|
|
{
|
|
if (!playeringame[actor->lastlook])
|
|
continue;
|
|
|
|
if (c++ == 2
|
|
|| actor->lastlook == stop)
|
|
{
|
|
// done looking
|
|
return false;
|
|
}
|
|
|
|
player = &players[actor->lastlook];
|
|
|
|
if (player->health <= 0)
|
|
continue; // dead
|
|
|
|
if (!P_CheckSight(actor, player->mo))
|
|
continue; // out of sight
|
|
|
|
if (!allaround)
|
|
{
|
|
an = R_PointToAngle2(actor->x,
|
|
actor->y,
|
|
player->mo->x,
|
|
player->mo->y)
|
|
- actor->angle;
|
|
|
|
if (an > ANG90 && an < ANG270)
|
|
{
|
|
dist = P_AproxDistance(player->mo->x - actor->x,
|
|
player->mo->y - actor->y);
|
|
// if real close, react anyway
|
|
if (dist > MELEERANGE)
|
|
continue; // behind back
|
|
}
|
|
}
|
|
|
|
actor->target = player->mo;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//
|
|
// A_KeenDie
|
|
// DOOM II special, map 32.
|
|
// Uses special tag 666.
|
|
//
|
|
void A_KeenDie(mobj_t* mo)
|
|
{
|
|
thinker_t* th;
|
|
mobj_t* mo2;
|
|
line_t junk;
|
|
|
|
A_Fall(mo);
|
|
|
|
// scan the remaining thinkers
|
|
// to see if all Keens are dead
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
{
|
|
if (th->function.acp1 != (actionf_p1)P_MobjThinker)
|
|
continue;
|
|
|
|
mo2 = (mobj_t*)th;
|
|
if (mo2 != mo
|
|
&& mo2->type == mo->type
|
|
&& mo2->health > 0)
|
|
{
|
|
// other Keen not dead
|
|
return;
|
|
}
|
|
}
|
|
|
|
junk.tag = 666;
|
|
EV_DoDoor(&junk, door_open);
|
|
}
|
|
|
|
|
|
//
|
|
// ACTION ROUTINES
|
|
//
|
|
|
|
//
|
|
// A_Look
|
|
// Stay in state until a player is sighted.
|
|
//
|
|
void A_Look(mobj_t* actor)
|
|
{
|
|
mobj_t* targ;
|
|
|
|
actor->threshold = 0; // any shot will wake up
|
|
targ = actor->subsector->sector->soundtarget;
|
|
|
|
if (targ
|
|
&& (targ->flags & MF_SHOOTABLE))
|
|
{
|
|
actor->target = targ;
|
|
|
|
if (actor->flags & MF_AMBUSH)
|
|
{
|
|
if (P_CheckSight(actor, actor->target))
|
|
goto seeyou;
|
|
}
|
|
else
|
|
goto seeyou;
|
|
}
|
|
|
|
|
|
if (!P_LookForPlayers(actor, false))
|
|
return;
|
|
|
|
// go into chase state
|
|
seeyou:
|
|
if (actor->info->seesound)
|
|
{
|
|
int sound;
|
|
|
|
switch (actor->info->seesound)
|
|
{
|
|
case sfx_posit1:
|
|
case sfx_posit2:
|
|
case sfx_posit3:
|
|
sound = sfx_posit1 + P_Random() % 3;
|
|
break;
|
|
|
|
case sfx_bgsit1:
|
|
case sfx_bgsit2:
|
|
sound = sfx_bgsit1 + P_Random() % 2;
|
|
break;
|
|
|
|
default:
|
|
sound = actor->info->seesound;
|
|
break;
|
|
}
|
|
|
|
if (actor->type == MT_SPIDER
|
|
|| actor->type == MT_CYBORG)
|
|
{
|
|
// full volume
|
|
S_StartSound(0, sound);
|
|
}
|
|
else
|
|
S_StartSound(actor, sound);
|
|
}
|
|
|
|
P_SetMobjState(actor, actor->info->seestate);
|
|
}
|
|
|
|
|
|
//
|
|
// A_Chase
|
|
// Actor has a melee attack,
|
|
// so it tries to close as fast as possible
|
|
//
|
|
void A_Chase(mobj_t* actor)
|
|
{
|
|
int delta;
|
|
|
|
if (actor->reactiontime)
|
|
actor->reactiontime--;
|
|
|
|
// modify target threshold
|
|
if (actor->threshold)
|
|
{
|
|
if (!actor->target
|
|
|| actor->target->health <= 0)
|
|
{
|
|
actor->threshold = 0;
|
|
}
|
|
else
|
|
actor->threshold--;
|
|
}
|
|
|
|
// turn towards movement direction if not there yet
|
|
if (actor->movedir < 8)
|
|
{
|
|
actor->angle &= (7 << 29);
|
|
delta = actor->angle - (actor->movedir << 29);
|
|
|
|
if (delta > 0)
|
|
actor->angle -= ANG90 / 2;
|
|
else if (delta < 0)
|
|
actor->angle += ANG90 / 2;
|
|
}
|
|
|
|
if (!actor->target
|
|
|| !(actor->target->flags & MF_SHOOTABLE))
|
|
{
|
|
// look for a new target
|
|
if (P_LookForPlayers(actor, true))
|
|
return; // got a new target
|
|
|
|
P_SetMobjState(actor, actor->info->spawnstate);
|
|
return;
|
|
}
|
|
|
|
// do not attack twice in a row
|
|
if (actor->flags & MF_JUSTATTACKED)
|
|
{
|
|
actor->flags &= ~MF_JUSTATTACKED;
|
|
if (gameskill != sk_nightmare && !fastparm)
|
|
P_NewChaseDir(actor);
|
|
return;
|
|
}
|
|
|
|
// check for melee attack
|
|
if (actor->info->meleestate
|
|
&& P_CheckMeleeRange(actor))
|
|
{
|
|
if (actor->info->attacksound)
|
|
S_StartSound(actor, actor->info->attacksound);
|
|
|
|
P_SetMobjState(actor, actor->info->meleestate);
|
|
return;
|
|
}
|
|
|
|
// check for missile attack
|
|
if (actor->info->missilestate)
|
|
{
|
|
if (gameskill < sk_nightmare
|
|
&& !fastparm && actor->movecount)
|
|
{
|
|
goto nomissile;
|
|
}
|
|
|
|
if (!P_CheckMissileRange(actor))
|
|
goto nomissile;
|
|
|
|
P_SetMobjState(actor, actor->info->missilestate);
|
|
actor->flags |= MF_JUSTATTACKED;
|
|
return;
|
|
}
|
|
|
|
// ?
|
|
nomissile:
|
|
// possibly choose another target
|
|
if (netgame
|
|
&& !actor->threshold
|
|
&& !P_CheckSight(actor, actor->target))
|
|
{
|
|
if (P_LookForPlayers(actor, true))
|
|
return; // got a new target
|
|
}
|
|
|
|
// chase towards player
|
|
if (--actor->movecount < 0
|
|
|| !P_Move(actor))
|
|
{
|
|
P_NewChaseDir(actor);
|
|
}
|
|
|
|
// make active sound
|
|
if (actor->info->activesound
|
|
&& P_Random() < 3)
|
|
{
|
|
S_StartSound(actor, actor->info->activesound);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// A_FaceTarget
|
|
//
|
|
void A_FaceTarget(mobj_t* actor)
|
|
{
|
|
if (!actor->target)
|
|
return;
|
|
|
|
actor->flags &= ~MF_AMBUSH;
|
|
|
|
actor->angle = R_PointToAngle2(actor->x,
|
|
actor->y,
|
|
actor->target->x,
|
|
actor->target->y);
|
|
|
|
if (actor->target->flags & MF_SHADOW)
|
|
actor->angle += (P_Random() - P_Random()) << 21;
|
|
}
|
|
|
|
|
|
//
|
|
// A_PosAttack
|
|
//
|
|
void A_PosAttack(mobj_t* actor)
|
|
{
|
|
int angle;
|
|
int damage;
|
|
int slope;
|
|
|
|
if (!actor->target)
|
|
return;
|
|
|
|
A_FaceTarget(actor);
|
|
angle = actor->angle;
|
|
slope = P_AimLineAttack(actor, angle, MISSILERANGE);
|
|
|
|
S_StartSound(actor, sfx_pistol);
|
|
angle += (P_Random() - P_Random()) << 20;
|
|
damage = ((P_Random() % 5) + 1) * 3;
|
|
P_LineAttack(actor, angle, MISSILERANGE, slope, damage);
|
|
}
|
|
|
|
|
|
void A_SPosAttack(mobj_t* actor)
|
|
{
|
|
int i;
|
|
int angle;
|
|
int bangle;
|
|
int damage;
|
|
int slope;
|
|
|
|
if (!actor->target)
|
|
return;
|
|
|
|
S_StartSound(actor, sfx_shotgn);
|
|
A_FaceTarget(actor);
|
|
bangle = actor->angle;
|
|
slope = P_AimLineAttack(actor, bangle, MISSILERANGE);
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
angle = bangle + ((P_Random() - P_Random()) << 20);
|
|
damage = ((P_Random() % 5) + 1) * 3;
|
|
P_LineAttack(actor, angle, MISSILERANGE, slope, damage);
|
|
}
|
|
}
|
|
|
|
|
|
void A_CPosAttack(mobj_t* actor)
|
|
{
|
|
int angle;
|
|
int bangle;
|
|
int damage;
|
|
int slope;
|
|
|
|
if (!actor->target)
|
|
return;
|
|
|
|
S_StartSound(actor, sfx_shotgn);
|
|
A_FaceTarget(actor);
|
|
bangle = actor->angle;
|
|
slope = P_AimLineAttack(actor, bangle, MISSILERANGE);
|
|
|
|
angle = bangle + ((P_Random() - P_Random()) << 20);
|
|
damage = ((P_Random() % 5) + 1) * 3;
|
|
P_LineAttack(actor, angle, MISSILERANGE, slope, damage);
|
|
}
|
|
|
|
|
|
void A_CPosRefire(mobj_t* actor)
|
|
{
|
|
// keep firing unless target got out of sight
|
|
A_FaceTarget(actor);
|
|
|
|
if (P_Random() < 40)
|
|
return;
|
|
|
|
if (!actor->target
|
|
|| actor->target->health <= 0
|
|
|| !P_CheckSight(actor, actor->target))
|
|
{
|
|
P_SetMobjState(actor, actor->info->seestate);
|
|
}
|
|
}
|
|
|
|
|
|
void A_SpidRefire(mobj_t* actor)
|
|
{
|
|
// keep firing unless target got out of sight
|
|
A_FaceTarget(actor);
|
|
|
|
if (P_Random() < 10)
|
|
return;
|
|
|
|
if (!actor->target
|
|
|| actor->target->health <= 0
|
|
|| !P_CheckSight(actor, actor->target))
|
|
{
|
|
P_SetMobjState(actor, actor->info->seestate);
|
|
}
|
|
}
|
|
|
|
|
|
void A_BspiAttack(mobj_t* actor)
|
|
{
|
|
if (!actor->target)
|
|
return;
|
|
|
|
A_FaceTarget(actor);
|
|
|
|
// launch a missile
|
|
P_SpawnMissile(actor, actor->target, MT_ARACHPLAZ);
|
|
}
|
|
|
|
|
|
//
|
|
// A_TroopAttack
|
|
//
|
|
void A_TroopAttack(mobj_t* actor)
|
|
{
|
|
int damage;
|
|
|
|
if (!actor->target)
|
|
return;
|
|
|
|
A_FaceTarget(actor);
|
|
if (P_CheckMeleeRange(actor))
|
|
{
|
|
S_StartSound(actor, sfx_claw);
|
|
damage = (P_Random() % 8 + 1) * 3;
|
|
P_DamageMobj(actor->target, actor, actor, damage);
|
|
return;
|
|
}
|
|
|
|
// launch a missile
|
|
P_SpawnMissile(actor, actor->target, MT_TROOPSHOT);
|
|
}
|
|
|
|
|
|
void A_SargAttack(mobj_t* actor)
|
|
{
|
|
int damage;
|
|
|
|
if (!actor->target)
|
|
return;
|
|
|
|
A_FaceTarget(actor);
|
|
if (P_CheckMeleeRange(actor))
|
|
{
|
|
damage = ((P_Random() % 10) + 1) * 4;
|
|
P_DamageMobj(actor->target, actor, actor, damage);
|
|
}
|
|
}
|
|
|
|
|
|
void A_HeadAttack(mobj_t* actor)
|
|
{
|
|
int damage;
|
|
|
|
if (!actor->target)
|
|
return;
|
|
|
|
A_FaceTarget(actor);
|
|
if (P_CheckMeleeRange(actor))
|
|
{
|
|
damage = (P_Random() % 6 + 1) * 10;
|
|
P_DamageMobj(actor->target, actor, actor, damage);
|
|
return;
|
|
}
|
|
|
|
// launch a missile
|
|
P_SpawnMissile(actor, actor->target, MT_HEADSHOT);
|
|
}
|
|
|
|
|
|
void A_CyberAttack(mobj_t* actor)
|
|
{
|
|
if (!actor->target)
|
|
return;
|
|
|
|
A_FaceTarget(actor);
|
|
P_SpawnMissile(actor, actor->target, MT_ROCKET);
|
|
}
|
|
|
|
|
|
void A_BruisAttack(mobj_t* actor)
|
|
{
|
|
int damage;
|
|
|
|
if (!actor->target)
|
|
return;
|
|
|
|
if (P_CheckMeleeRange(actor))
|
|
{
|
|
S_StartSound(actor, sfx_claw);
|
|
damage = (P_Random() % 8 + 1) * 10;
|
|
P_DamageMobj(actor->target, actor, actor, damage);
|
|
return;
|
|
}
|
|
|
|
// launch a missile
|
|
P_SpawnMissile(actor, actor->target, MT_BRUISERSHOT);
|
|
}
|
|
|
|
|
|
//
|
|
// A_SkelMissile
|
|
//
|
|
void A_SkelMissile(mobj_t* actor)
|
|
{
|
|
mobj_t* mo;
|
|
|
|
if (!actor->target)
|
|
return;
|
|
|
|
A_FaceTarget(actor);
|
|
actor->z += 16 * FRACUNIT; // so missile spawns higher
|
|
mo = P_SpawnMissile(actor, actor->target, MT_TRACER);
|
|
actor->z -= 16 * FRACUNIT; // back to normal
|
|
|
|
mo->x += mo->momx;
|
|
mo->y += mo->momy;
|
|
mo->tracer = actor->target;
|
|
}
|
|
|
|
|
|
void A_Tracer(mobj_t* actor)
|
|
{
|
|
angle_t exact;
|
|
fixed_t dist;
|
|
fixed_t slope;
|
|
mobj_t* dest;
|
|
mobj_t* th;
|
|
|
|
if (gametic & 3)
|
|
return;
|
|
|
|
// spawn a puff of smoke behind the rocket
|
|
P_SpawnPuff(actor->x, actor->y, actor->z);
|
|
|
|
th = P_SpawnMobj(actor->x - actor->momx,
|
|
actor->y - actor->momy,
|
|
actor->z, MT_SMOKE);
|
|
|
|
th->momz = FRACUNIT;
|
|
th->tics -= P_Random() & 3;
|
|
if (th->tics < 1)
|
|
th->tics = 1;
|
|
|
|
// adjust direction
|
|
dest = actor->tracer;
|
|
|
|
if (!dest || dest->health <= 0)
|
|
return;
|
|
|
|
// change angle
|
|
exact = R_PointToAngle2(actor->x,
|
|
actor->y,
|
|
dest->x,
|
|
dest->y);
|
|
|
|
if (exact != actor->angle)
|
|
{
|
|
if (exact - actor->angle > 0x80000000)
|
|
{
|
|
actor->angle -= TRACEANGLE;
|
|
if (exact - actor->angle < 0x80000000)
|
|
actor->angle = exact;
|
|
}
|
|
else
|
|
{
|
|
actor->angle += TRACEANGLE;
|
|
if (exact - actor->angle > 0x80000000)
|
|
actor->angle = exact;
|
|
}
|
|
}
|
|
|
|
exact = actor->angle >> ANGLETOFINESHIFT;
|
|
actor->momx = FixedMul(actor->info->speed, finecosine[exact]);
|
|
actor->momy = FixedMul(actor->info->speed, finesine[exact]);
|
|
|
|
// change slope
|
|
dist = P_AproxDistance(dest->x - actor->x,
|
|
dest->y - actor->y);
|
|
|
|
dist = dist / actor->info->speed;
|
|
|
|
if (dist < 1)
|
|
dist = 1;
|
|
slope = (dest->z + 40 * FRACUNIT - actor->z) / dist;
|
|
|
|
if (slope < actor->momz)
|
|
actor->momz -= FRACUNIT / 8;
|
|
else
|
|
actor->momz += FRACUNIT / 8;
|
|
}
|
|
|
|
|
|
void A_SkelWhoosh(mobj_t* actor)
|
|
{
|
|
if (!actor->target)
|
|
return;
|
|
|
|
A_FaceTarget(actor);
|
|
S_StartSound(actor, sfx_skeswg);
|
|
}
|
|
|
|
|
|
void A_SkelFist(mobj_t* actor)
|
|
{
|
|
int damage;
|
|
|
|
if (!actor->target)
|
|
return;
|
|
|
|
A_FaceTarget(actor);
|
|
|
|
if (P_CheckMeleeRange(actor))
|
|
{
|
|
damage = ((P_Random() % 10) + 1) * 6;
|
|
S_StartSound(actor, sfx_skepch);
|
|
P_DamageMobj(actor->target, actor, actor, damage);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// PIT_VileCheck
|
|
// Detect a corpse that could be raised.
|
|
//
|
|
doom_boolean PIT_VileCheck(mobj_t* thing)
|
|
{
|
|
int maxdist;
|
|
doom_boolean check;
|
|
|
|
if (!(thing->flags & MF_CORPSE))
|
|
return true; // not a monster
|
|
|
|
if (thing->tics != -1)
|
|
return true; // not lying still yet
|
|
|
|
if (thing->info->raisestate == S_NULL)
|
|
return true; // monster doesn't have a raise state
|
|
|
|
maxdist = thing->info->radius + mobjinfo[MT_VILE].radius;
|
|
|
|
if (doom_abs(thing->x - viletryx) > maxdist
|
|
|| doom_abs(thing->y - viletryy) > maxdist)
|
|
return true; // not actually touching
|
|
|
|
corpsehit = thing;
|
|
corpsehit->momx = corpsehit->momy = 0;
|
|
corpsehit->height <<= 2;
|
|
check = P_CheckPosition(corpsehit, corpsehit->x, corpsehit->y);
|
|
corpsehit->height >>= 2;
|
|
|
|
if (!check)
|
|
return true; // doesn't fit here
|
|
|
|
return false; // got one, so stop checking
|
|
}
|
|
|
|
|
|
//
|
|
// A_VileChase
|
|
// Check for ressurecting a body
|
|
//
|
|
void A_VileChase(mobj_t* actor)
|
|
{
|
|
int xl;
|
|
int xh;
|
|
int yl;
|
|
int yh;
|
|
|
|
int bx;
|
|
int by;
|
|
|
|
mobjinfo_t* info;
|
|
mobj_t* temp;
|
|
|
|
if (actor->movedir != DI_NODIR)
|
|
{
|
|
// check for corpses to raise
|
|
viletryx =
|
|
actor->x + actor->info->speed * xspeed[actor->movedir];
|
|
viletryy =
|
|
actor->y + actor->info->speed * yspeed[actor->movedir];
|
|
|
|
xl = (viletryx - bmaporgx - MAXRADIUS * 2) >> MAPBLOCKSHIFT;
|
|
xh = (viletryx - bmaporgx + MAXRADIUS * 2) >> MAPBLOCKSHIFT;
|
|
yl = (viletryy - bmaporgy - MAXRADIUS * 2) >> MAPBLOCKSHIFT;
|
|
yh = (viletryy - bmaporgy + MAXRADIUS * 2) >> MAPBLOCKSHIFT;
|
|
|
|
vileobj = actor;
|
|
for (bx = xl; bx <= xh; bx++)
|
|
{
|
|
for (by = yl; by <= yh; by++)
|
|
{
|
|
// Call PIT_VileCheck to check
|
|
// whether object is a corpse
|
|
// that canbe raised.
|
|
if (!P_BlockThingsIterator(bx, by, PIT_VileCheck))
|
|
{
|
|
// got one!
|
|
temp = actor->target;
|
|
actor->target = corpsehit;
|
|
A_FaceTarget(actor);
|
|
actor->target = temp;
|
|
|
|
P_SetMobjState(actor, S_VILE_HEAL1);
|
|
S_StartSound(corpsehit, sfx_slop);
|
|
info = corpsehit->info;
|
|
|
|
P_SetMobjState(corpsehit, info->raisestate);
|
|
corpsehit->height <<= 2;
|
|
corpsehit->flags = info->flags;
|
|
corpsehit->health = info->spawnhealth;
|
|
corpsehit->target = 0;
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return to normal attack.
|
|
A_Chase(actor);
|
|
}
|
|
|
|
|
|
//
|
|
// A_VileStart
|
|
//
|
|
void A_VileStart(mobj_t* actor)
|
|
{
|
|
S_StartSound(actor, sfx_vilatk);
|
|
}
|
|
|
|
|
|
//
|
|
// A_Fire
|
|
// Keep fire in front of player unless out of sight
|
|
//
|
|
void A_StartFire(mobj_t* actor)
|
|
{
|
|
S_StartSound(actor, sfx_flamst);
|
|
A_Fire(actor);
|
|
}
|
|
|
|
|
|
void A_FireCrackle(mobj_t* actor)
|
|
{
|
|
S_StartSound(actor, sfx_flame);
|
|
A_Fire(actor);
|
|
}
|
|
|
|
|
|
void A_Fire(mobj_t* actor)
|
|
{
|
|
mobj_t* dest;
|
|
unsigned an;
|
|
|
|
dest = actor->tracer;
|
|
if (!dest)
|
|
return;
|
|
|
|
// don't move it if the vile lost sight
|
|
if (!P_CheckSight(actor->target, dest))
|
|
return;
|
|
|
|
an = dest->angle >> ANGLETOFINESHIFT;
|
|
|
|
P_UnsetThingPosition(actor);
|
|
actor->x = dest->x + FixedMul(24 * FRACUNIT, finecosine[an]);
|
|
actor->y = dest->y + FixedMul(24 * FRACUNIT, finesine[an]);
|
|
actor->z = dest->z;
|
|
P_SetThingPosition(actor);
|
|
}
|
|
|
|
|
|
//
|
|
// A_VileTarget
|
|
// Spawn the hellfire
|
|
//
|
|
void A_VileTarget(mobj_t* actor)
|
|
{
|
|
mobj_t* fog;
|
|
|
|
if (!actor->target)
|
|
return;
|
|
|
|
A_FaceTarget(actor);
|
|
|
|
fog = P_SpawnMobj(actor->target->x,
|
|
actor->target->x,
|
|
actor->target->z, MT_FIRE);
|
|
|
|
actor->tracer = fog;
|
|
fog->target = actor;
|
|
fog->tracer = actor->target;
|
|
A_Fire(fog);
|
|
}
|
|
|
|
|
|
//
|
|
// A_VileAttack
|
|
//
|
|
void A_VileAttack(mobj_t* actor)
|
|
{
|
|
mobj_t* fire;
|
|
int an;
|
|
|
|
if (!actor->target)
|
|
return;
|
|
|
|
A_FaceTarget(actor);
|
|
|
|
if (!P_CheckSight(actor, actor->target))
|
|
return;
|
|
|
|
S_StartSound(actor, sfx_barexp);
|
|
P_DamageMobj(actor->target, actor, actor, 20);
|
|
actor->target->momz = 1000 * FRACUNIT / actor->target->info->mass;
|
|
|
|
an = actor->angle >> ANGLETOFINESHIFT;
|
|
|
|
fire = actor->tracer;
|
|
|
|
if (!fire)
|
|
return;
|
|
|
|
// move the fire between the vile and the player
|
|
fire->x = actor->target->x - FixedMul(24 * FRACUNIT, finecosine[an]);
|
|
fire->y = actor->target->y - FixedMul(24 * FRACUNIT, finesine[an]);
|
|
P_RadiusAttack(fire, actor, 70);
|
|
}
|
|
|
|
|
|
//
|
|
// Mancubus attack,
|
|
// firing three missiles (bruisers)
|
|
// in three different directions?
|
|
// Doesn't look like it.
|
|
//
|
|
void A_FatRaise(mobj_t* actor)
|
|
{
|
|
A_FaceTarget(actor);
|
|
S_StartSound(actor, sfx_manatk);
|
|
}
|
|
|
|
|
|
void A_FatAttack1(mobj_t* actor)
|
|
{
|
|
mobj_t* mo;
|
|
int an;
|
|
|
|
A_FaceTarget(actor);
|
|
// Change direction to ...
|
|
actor->angle += FATSPREAD;
|
|
P_SpawnMissile(actor, actor->target, MT_FATSHOT);
|
|
|
|
mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT);
|
|
mo->angle += FATSPREAD;
|
|
an = mo->angle >> ANGLETOFINESHIFT;
|
|
mo->momx = FixedMul(mo->info->speed, finecosine[an]);
|
|
mo->momy = FixedMul(mo->info->speed, finesine[an]);
|
|
}
|
|
|
|
|
|
void A_FatAttack2(mobj_t* actor)
|
|
{
|
|
mobj_t* mo;
|
|
int an;
|
|
|
|
A_FaceTarget(actor);
|
|
// Now here choose opposite deviation.
|
|
actor->angle -= FATSPREAD;
|
|
P_SpawnMissile(actor, actor->target, MT_FATSHOT);
|
|
|
|
mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT);
|
|
mo->angle -= FATSPREAD * 2;
|
|
an = mo->angle >> ANGLETOFINESHIFT;
|
|
mo->momx = FixedMul(mo->info->speed, finecosine[an]);
|
|
mo->momy = FixedMul(mo->info->speed, finesine[an]);
|
|
}
|
|
|
|
|
|
void A_FatAttack3(mobj_t* actor)
|
|
{
|
|
mobj_t* mo;
|
|
int an;
|
|
|
|
A_FaceTarget(actor);
|
|
|
|
mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT);
|
|
mo->angle -= FATSPREAD / 2;
|
|
an = mo->angle >> ANGLETOFINESHIFT;
|
|
mo->momx = FixedMul(mo->info->speed, finecosine[an]);
|
|
mo->momy = FixedMul(mo->info->speed, finesine[an]);
|
|
|
|
mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT);
|
|
mo->angle += FATSPREAD / 2;
|
|
an = mo->angle >> ANGLETOFINESHIFT;
|
|
mo->momx = FixedMul(mo->info->speed, finecosine[an]);
|
|
mo->momy = FixedMul(mo->info->speed, finesine[an]);
|
|
}
|
|
|
|
|
|
//
|
|
// SkullAttack
|
|
// Fly at the player like a missile.
|
|
//
|
|
void A_SkullAttack(mobj_t* actor)
|
|
{
|
|
mobj_t* dest;
|
|
angle_t an;
|
|
int dist;
|
|
|
|
if (!actor->target)
|
|
return;
|
|
|
|
dest = actor->target;
|
|
actor->flags |= MF_SKULLFLY;
|
|
|
|
S_StartSound(actor, actor->info->attacksound);
|
|
A_FaceTarget(actor);
|
|
an = actor->angle >> ANGLETOFINESHIFT;
|
|
actor->momx = FixedMul(SKULLSPEED, finecosine[an]);
|
|
actor->momy = FixedMul(SKULLSPEED, finesine[an]);
|
|
dist = P_AproxDistance(dest->x - actor->x, dest->y - actor->y);
|
|
dist = dist / SKULLSPEED;
|
|
|
|
if (dist < 1)
|
|
dist = 1;
|
|
actor->momz = (dest->z + (dest->height >> 1) - actor->z) / dist;
|
|
}
|
|
|
|
|
|
//
|
|
// A_PainShootSkull
|
|
// Spawn a lost soul and launch it at the target
|
|
//
|
|
void A_PainShootSkull(mobj_t* actor, angle_t angle)
|
|
{
|
|
fixed_t x;
|
|
fixed_t y;
|
|
fixed_t z;
|
|
|
|
mobj_t* newmobj;
|
|
angle_t an;
|
|
int prestep;
|
|
int count;
|
|
thinker_t* currentthinker;
|
|
|
|
// count total number of skull currently on the level
|
|
count = 0;
|
|
|
|
currentthinker = thinkercap.next;
|
|
while (currentthinker != &thinkercap)
|
|
{
|
|
if ((currentthinker->function.acp1 == (actionf_p1)P_MobjThinker)
|
|
&& ((mobj_t*)currentthinker)->type == MT_SKULL)
|
|
count++;
|
|
currentthinker = currentthinker->next;
|
|
}
|
|
|
|
// if there are allready 20 skulls on the level,
|
|
// don't spit another one
|
|
if (count > 20)
|
|
return;
|
|
|
|
// okay, there's playe for another one
|
|
an = angle >> ANGLETOFINESHIFT;
|
|
|
|
prestep =
|
|
4 * FRACUNIT
|
|
+ 3 * (actor->info->radius + mobjinfo[MT_SKULL].radius) / 2;
|
|
|
|
x = actor->x + FixedMul(prestep, finecosine[an]);
|
|
y = actor->y + FixedMul(prestep, finesine[an]);
|
|
z = actor->z + 8 * FRACUNIT;
|
|
|
|
newmobj = P_SpawnMobj(x, y, z, MT_SKULL);
|
|
|
|
// Check for movements.
|
|
if (!P_TryMove(newmobj, newmobj->x, newmobj->y))
|
|
{
|
|
// kill it immediately
|
|
P_DamageMobj(newmobj, actor, actor, 10000);
|
|
return;
|
|
}
|
|
|
|
newmobj->target = actor->target;
|
|
A_SkullAttack(newmobj);
|
|
}
|
|
|
|
|
|
//
|
|
// A_PainAttack
|
|
// Spawn a lost soul and launch it at the target
|
|
//
|
|
void A_PainAttack(mobj_t* actor)
|
|
{
|
|
if (!actor->target)
|
|
return;
|
|
|
|
A_FaceTarget(actor);
|
|
A_PainShootSkull(actor, actor->angle);
|
|
}
|
|
|
|
|
|
void A_PainDie(mobj_t* actor)
|
|
{
|
|
A_Fall(actor);
|
|
A_PainShootSkull(actor, actor->angle + ANG90);
|
|
A_PainShootSkull(actor, actor->angle + ANG180);
|
|
A_PainShootSkull(actor, actor->angle + ANG270);
|
|
}
|
|
|
|
|
|
void A_Scream(mobj_t* actor)
|
|
{
|
|
int sound;
|
|
|
|
switch (actor->info->deathsound)
|
|
{
|
|
case 0:
|
|
return;
|
|
|
|
case sfx_podth1:
|
|
case sfx_podth2:
|
|
case sfx_podth3:
|
|
sound = sfx_podth1 + P_Random() % 3;
|
|
break;
|
|
|
|
case sfx_bgdth1:
|
|
case sfx_bgdth2:
|
|
sound = sfx_bgdth1 + P_Random() % 2;
|
|
break;
|
|
|
|
default:
|
|
sound = actor->info->deathsound;
|
|
break;
|
|
}
|
|
|
|
// Check for bosses.
|
|
if (actor->type == MT_SPIDER
|
|
|| actor->type == MT_CYBORG)
|
|
{
|
|
// full volume
|
|
S_StartSound(0, sound);
|
|
}
|
|
else
|
|
S_StartSound(actor, sound);
|
|
}
|
|
|
|
|
|
void A_XScream(mobj_t* actor)
|
|
{
|
|
S_StartSound(actor, sfx_slop);
|
|
}
|
|
|
|
|
|
void A_Pain(mobj_t* actor)
|
|
{
|
|
if (actor->info->painsound)
|
|
S_StartSound(actor, actor->info->painsound);
|
|
}
|
|
|
|
|
|
void A_Fall(mobj_t* actor)
|
|
{
|
|
// actor is on ground, it can be walked over
|
|
actor->flags &= ~MF_SOLID;
|
|
|
|
// So change this if corpse objects
|
|
// are meant to be obstacles.
|
|
}
|
|
|
|
|
|
//
|
|
// A_Explode
|
|
//
|
|
void A_Explode(mobj_t* thingy)
|
|
{
|
|
P_RadiusAttack(thingy, thingy->target, 128);
|
|
}
|
|
|
|
|
|
//
|
|
// A_BossDeath
|
|
// Possibly trigger special effects
|
|
// if on first boss level
|
|
//
|
|
void A_BossDeath(mobj_t* mo)
|
|
{
|
|
thinker_t* th;
|
|
mobj_t* mo2;
|
|
line_t junk;
|
|
int i;
|
|
|
|
if (gamemode == commercial)
|
|
{
|
|
if (gamemap != 7)
|
|
return;
|
|
|
|
if ((mo->type != MT_FATSO)
|
|
&& (mo->type != MT_BABY))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
switch (gameepisode)
|
|
{
|
|
case 1:
|
|
if (gamemap != 8)
|
|
return;
|
|
|
|
if (mo->type != MT_BRUISER)
|
|
return;
|
|
break;
|
|
|
|
case 2:
|
|
if (gamemap != 8)
|
|
return;
|
|
|
|
if (mo->type != MT_CYBORG)
|
|
return;
|
|
break;
|
|
|
|
case 3:
|
|
if (gamemap != 8)
|
|
return;
|
|
|
|
if (mo->type != MT_SPIDER)
|
|
return;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
switch (gamemap)
|
|
{
|
|
case 6:
|
|
if (mo->type != MT_CYBORG)
|
|
return;
|
|
break;
|
|
|
|
case 8:
|
|
if (mo->type != MT_SPIDER)
|
|
return;
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
if (gamemap != 8)
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// make sure there is a player alive for victory
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
if (playeringame[i] && players[i].health > 0)
|
|
break;
|
|
|
|
if (i == MAXPLAYERS)
|
|
return; // no one left alive, so do not end game
|
|
|
|
// scan the remaining thinkers to see
|
|
// if all bosses are dead
|
|
for (th = thinkercap.next; th != &thinkercap; th = th->next)
|
|
{
|
|
if (th->function.acp1 != (actionf_p1)P_MobjThinker)
|
|
continue;
|
|
|
|
mo2 = (mobj_t*)th;
|
|
if (mo2 != mo
|
|
&& mo2->type == mo->type
|
|
&& mo2->health > 0)
|
|
{
|
|
// other boss not dead
|
|
return;
|
|
}
|
|
}
|
|
|
|
// victory!
|
|
if (gamemode == commercial)
|
|
{
|
|
if (gamemap == 7)
|
|
{
|
|
if (mo->type == MT_FATSO)
|
|
{
|
|
junk.tag = 666;
|
|
EV_DoFloor(&junk, lowerFloorToLowest);
|
|
return;
|
|
}
|
|
|
|
if (mo->type == MT_BABY)
|
|
{
|
|
junk.tag = 667;
|
|
EV_DoFloor(&junk, raiseToTexture);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (gameepisode)
|
|
{
|
|
case 1:
|
|
junk.tag = 666;
|
|
EV_DoFloor(&junk, lowerFloorToLowest);
|
|
return;
|
|
break;
|
|
|
|
case 4:
|
|
switch (gamemap)
|
|
{
|
|
case 6:
|
|
junk.tag = 666;
|
|
EV_DoDoor(&junk, blazeOpen);
|
|
return;
|
|
break;
|
|
|
|
case 8:
|
|
junk.tag = 666;
|
|
EV_DoFloor(&junk, lowerFloorToLowest);
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
G_ExitLevel();
|
|
}
|
|
|
|
|
|
void A_Hoof(mobj_t* mo)
|
|
{
|
|
S_StartSound(mo, sfx_hoof);
|
|
A_Chase(mo);
|
|
}
|
|
|
|
|
|
void A_Metal(mobj_t* mo)
|
|
{
|
|
S_StartSound(mo, sfx_metal);
|
|
A_Chase(mo);
|
|
}
|
|
|
|
|
|
void A_BabyMetal(mobj_t* mo)
|
|
{
|
|
S_StartSound(mo, sfx_bspwlk);
|
|
A_Chase(mo);
|
|
}
|
|
|
|
|
|
void A_OpenShotgun2(player_t* player, pspdef_t* psp)
|
|
{
|
|
S_StartSound(player->mo, sfx_dbopn);
|
|
}
|
|
|
|
|
|
void A_LoadShotgun2(player_t* player, pspdef_t* psp)
|
|
{
|
|
S_StartSound(player->mo, sfx_dbload);
|
|
}
|
|
|
|
|
|
void A_CloseShotgun2(player_t* player, pspdef_t* psp)
|
|
{
|
|
S_StartSound(player->mo, sfx_dbcls);
|
|
A_ReFire(player, psp);
|
|
}
|
|
|
|
|
|
void A_BrainAwake(mobj_t* mo)
|
|
{
|
|
thinker_t* thinker;
|
|
mobj_t* m;
|
|
|
|
// find all the target spots
|
|
numbraintargets = 0;
|
|
braintargeton = 0;
|
|
|
|
thinker = thinkercap.next;
|
|
for (thinker = thinkercap.next;
|
|
thinker != &thinkercap;
|
|
thinker = thinker->next)
|
|
{
|
|
if (thinker->function.acp1 != (actionf_p1)P_MobjThinker)
|
|
continue; // not a mobj
|
|
|
|
m = (mobj_t*)thinker;
|
|
|
|
if (m->type == MT_BOSSTARGET)
|
|
{
|
|
braintargets[numbraintargets] = m;
|
|
numbraintargets++;
|
|
}
|
|
}
|
|
|
|
S_StartSound(0, sfx_bossit);
|
|
}
|
|
|
|
|
|
void A_BrainPain(mobj_t* mo)
|
|
{
|
|
S_StartSound(0, sfx_bospn);
|
|
}
|
|
|
|
|
|
void A_BrainScream(mobj_t* mo)
|
|
{
|
|
int x;
|
|
int y;
|
|
int z;
|
|
mobj_t* th;
|
|
|
|
for (x = mo->x - 196 * FRACUNIT; x < mo->x + 320 * FRACUNIT; x += FRACUNIT * 8)
|
|
{
|
|
y = mo->y - 320 * FRACUNIT;
|
|
z = 128 + P_Random() * 2 * FRACUNIT;
|
|
th = P_SpawnMobj(x, y, z, MT_ROCKET);
|
|
th->momz = P_Random() * 512;
|
|
|
|
P_SetMobjState(th, S_BRAINEXPLODE1);
|
|
|
|
th->tics -= P_Random() & 7;
|
|
if (th->tics < 1)
|
|
th->tics = 1;
|
|
}
|
|
|
|
S_StartSound(0, sfx_bosdth);
|
|
}
|
|
|
|
|
|
void A_BrainExplode(mobj_t* mo)
|
|
{
|
|
int x;
|
|
int y;
|
|
int z;
|
|
mobj_t* th;
|
|
|
|
x = mo->x + (P_Random() - P_Random()) * 2048;
|
|
y = mo->y;
|
|
z = 128 + P_Random() * 2 * FRACUNIT;
|
|
th = P_SpawnMobj(x, y, z, MT_ROCKET);
|
|
th->momz = P_Random() * 512;
|
|
|
|
P_SetMobjState(th, S_BRAINEXPLODE1);
|
|
|
|
th->tics -= P_Random() & 7;
|
|
if (th->tics < 1)
|
|
th->tics = 1;
|
|
}
|
|
|
|
|
|
void A_BrainDie(mobj_t* mo)
|
|
{
|
|
G_ExitLevel();
|
|
}
|
|
|
|
|
|
void A_BrainSpit(mobj_t* mo)
|
|
{
|
|
mobj_t* targ;
|
|
mobj_t* newmobj;
|
|
|
|
static int easy = 0;
|
|
|
|
easy ^= 1;
|
|
if (gameskill <= sk_easy && (!easy))
|
|
return;
|
|
|
|
// shoot a cube at current target
|
|
targ = braintargets[braintargeton];
|
|
braintargeton = (braintargeton + 1) % numbraintargets;
|
|
|
|
// spawn brain missile
|
|
newmobj = P_SpawnMissile(mo, targ, MT_SPAWNSHOT);
|
|
newmobj->target = targ;
|
|
newmobj->reactiontime =
|
|
((targ->y - mo->y) / newmobj->momy) / newmobj->state->tics;
|
|
|
|
S_StartSound(0, sfx_bospit);
|
|
}
|
|
|
|
|
|
// travelling cube sound
|
|
void A_SpawnSound(mobj_t* mo)
|
|
{
|
|
S_StartSound(mo, sfx_boscub);
|
|
A_SpawnFly(mo);
|
|
}
|
|
|
|
|
|
void A_SpawnFly(mobj_t* mo)
|
|
{
|
|
mobj_t* newmobj;
|
|
mobj_t* fog;
|
|
mobj_t* targ;
|
|
int r;
|
|
mobjtype_t type;
|
|
|
|
if (--mo->reactiontime)
|
|
return; // still flying
|
|
|
|
targ = mo->target;
|
|
|
|
// First spawn teleport fog.
|
|
fog = P_SpawnMobj(targ->x, targ->y, targ->z, MT_SPAWNFIRE);
|
|
S_StartSound(fog, sfx_telept);
|
|
|
|
// Randomly select monster to spawn.
|
|
r = P_Random();
|
|
|
|
// Probability distribution (kind of :),
|
|
// decreasing likelihood.
|
|
if (r < 50)
|
|
type = MT_TROOP;
|
|
else if (r < 90)
|
|
type = MT_SERGEANT;
|
|
else if (r < 120)
|
|
type = MT_SHADOWS;
|
|
else if (r < 130)
|
|
type = MT_PAIN;
|
|
else if (r < 160)
|
|
type = MT_HEAD;
|
|
else if (r < 162)
|
|
type = MT_VILE;
|
|
else if (r < 172)
|
|
type = MT_UNDEAD;
|
|
else if (r < 192)
|
|
type = MT_BABY;
|
|
else if (r < 222)
|
|
type = MT_FATSO;
|
|
else if (r < 246)
|
|
type = MT_KNIGHT;
|
|
else
|
|
type = MT_BRUISER;
|
|
|
|
newmobj = P_SpawnMobj(targ->x, targ->y, targ->z, type);
|
|
if (P_LookForPlayers(newmobj, true))
|
|
P_SetMobjState(newmobj, newmobj->info->seestate);
|
|
|
|
// telefrag anything in this spot
|
|
P_TeleportMove(newmobj, newmobj->x, newmobj->y);
|
|
|
|
// remove self (i.e., cube).
|
|
P_RemoveMobj(mo);
|
|
}
|
|
|
|
|
|
void A_PlayerScream(mobj_t* mo)
|
|
{
|
|
// Default death sound.
|
|
int sound = sfx_pldeth;
|
|
|
|
if ((gamemode == commercial)
|
|
&& (mo->health < -50))
|
|
{
|
|
// IF THE PLAYER DIES
|
|
// LESS THAN -50% WITHOUT GIBBING
|
|
sound = sfx_pdiehi;
|
|
}
|
|
|
|
S_StartSound(mo, sound);
|
|
}
|