ref: 6528c14df3fa13bd3d5aed1e715205975b724f14
dir: /src/strife/p_enemy.c/
// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 2005 Simon Howard
//
// 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.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
// 02111-1307, USA.
//
// DESCRIPTION:
// Enemy thinking, AI.
// Action Pointer Functions
// that are associated with states/frames.
//
//-----------------------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include "m_random.h"
#include "i_system.h"
#include "doomdef.h"
#include "p_local.h"
#include "s_sound.h"
#include "g_game.h"
#include "z_zone.h" // villsa [STRIFE]
// State.
#include "doomstat.h"
#include "r_state.h"
// Data.
#include "sounds.h"
// [STRIFE] Dialog / Inventory
#include "p_dialog.h"
#include "deh_str.h"
#include "w_wad.h"
#include "f_finale.h"
#include "p_inter.h"
// Forward Declarations:
void A_RandomWalk(mobj_t *);
void A_ProgrammerAttack(mobj_t* actor);
void A_FireSigilEOffshoot(mobj_t *actor);
void A_SpectreCAttack(mobj_t *actor);
void A_SpectreDAttack(mobj_t *actor);
void A_SpectreEAttack(mobj_t *actor);
void P_ThrustMobj(mobj_t *actor, angle_t angle, fixed_t force);
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
};
void A_Fall (mobj_t *actor);
//
// ENEMY THINKING
// Enemies are allways spawned
// with targetplayer = -1, threshold = 0
// Most monsters are spawned unaware of all players,
// but some can be made preaware
//
//
// Called by P_NoiseAlert.
// Recursively traverse adjacent sectors,
// sound blocking lines cut off traversal.
//
// haleyjd 09/05/10: [STRIFE] Verified unmodified
//
mobj_t* soundtarget;
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.
//
// haleyjd 09/05/10: [STRIFE] Verified unmodified
//
void
P_NoiseAlert
( mobj_t* target,
mobj_t* emmiter )
{
soundtarget = target;
validcount++;
P_RecursiveSound (emmiter->subsector->sector, 0);
}
//
// P_WakeUpThing
//
// villsa [STRIFE] New function
// Wakes up an mobj.nearby when somebody has been punched.
//
static void P_WakeUpThing(mobj_t* puncher, mobj_t* bystander)
{
if(!(bystander->flags & MF_INCOMBAT))
{
bystander->target = puncher;
if(bystander->info->seesound)
S_StartSound(bystander, bystander->info->seesound);
P_SetMobjState(bystander, bystander->info->seestate);
}
}
//
// P_DoPunchAlert
//
// villsa [STRIFE] New function (by Quasar ;)
// Wake up buddies nearby when the player thinks he's gotten too clever
// with the punch dagger. Walks sector links.
//
void P_DoPunchAlert(mobj_t *puncher, mobj_t *punchee)
{
mobj_t *rover;
// don't bother with this crap if we're already on alert
if(punchee->subsector->sector->soundtarget)
return;
// gotta still be alive to call for help
if(punchee->health <= 0)
return;
// has to be something you can wake up and kill too
if(!(punchee->flags & MF_COUNTKILL) || punchee->flags & MF_INCOMBAT)
return;
// make the punchee hurt - haleyjd 09/05/10: Fixed to use painstate.
punchee->target = puncher;
P_SetMobjState(punchee, punchee->info->painstate);
// wake up everybody nearby
// scan forward on sector list
for(rover = punchee->snext; rover; rover = rover->snext)
{
// we only wake up certain thing types (Acolytes and Templars?)
if(rover->health > 0 && rover->type >= MT_GUARD1 && rover->type <= MT_PGUARD &&
(P_CheckSight(rover, puncher) || P_CheckSight(rover, punchee)))
{
P_WakeUpThing(puncher, rover);
rover->flags |= MF_INCOMBAT;
}
}
// scan backward on sector list
for(rover = punchee->sprev; rover; rover = rover->sprev)
{
// we only wake up certain thing types (Acolytes and Templars?)
if(rover->health > 0 && rover->type >= MT_GUARD1 && rover->type <= MT_PGUARD &&
(P_CheckSight(rover, puncher) || P_CheckSight(rover, punchee)))
{
P_WakeUpThing(puncher, rover);
rover->flags |= MF_INCOMBAT;
}
}
}
//
// P_CheckMeleeRange
//
// [STRIFE] Minor change to meleerange.
//
boolean P_CheckMeleeRange(mobj_t* actor)
{
mobj_t* pl;
fixed_t dist;
if(!actor->target)
return false;
pl = actor->target;
if(actor->z + 3 * actor->height / 2 < pl->z) // villsa [STRIFE]
return false;
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
//
// [STRIFE]
// Changes to eliminate DOOM-specific code and to allow for
// varying attack ranges for Strife monsters, as well as a general tweak
// to considered distance for all monsters.
//
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;
// villsa [STRIFE] checks for acolytes
// haleyjd 09/05/10: Repaired to match disassembly: Was including
// SHADOWGUARD in the wrong case, was missing MT_SENTINEL entirely.
// Structure of ASM also indicates this was probably a switch
// statement turned into a cascading if/else by the compiler.
switch(actor->type)
{
case MT_GUARD1:
case MT_GUARD2:
case MT_GUARD3:
case MT_GUARD4:
case MT_GUARD5:
case MT_GUARD6:
// oddly, not all Acolytes are included here...
dist >>= 4;
break;
case MT_SHADOWGUARD:
case MT_CRUSADER:
case MT_SENTINEL:
dist >>= 1;
break;
default:
break;
}
// villsa [STRIFE] changed to 150
if (dist > 150)
dist = 150;
// haleyjd 09/10/10: Hex-Rays was leaving this out completely:
if (actor->type == MT_CRUSADER && dist > 120)
dist = 120;
if (P_Random () < dist)
return false;
return true;
}
//
// P_CheckRobotRange
//
// villsa [STRIFE] New function
//
boolean P_CheckRobotRange(mobj_t *actor)
{
fixed_t dist;
if(!P_CheckSight(actor, actor->target))
return false;
if(actor->reactiontime)
return false; // do not attack yet
dist = (P_AproxDistance(actor->x-actor->target->x,
actor->y-actor->target->y) - 64*FRACUNIT) >> FRACBITS;
return (dist < 200);
}
//
// P_Move
// Move in the current direction,
// returns false if the move is blocked.
//
// [STRIFE]
// villsa/haleyjd 09/05/10: Modified for terrain types and 3D object
// clipping. Below constants are verified to be unmodified:
//
fixed_t xspeed[8] = {FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000};
fixed_t yspeed[8] = {0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000};
#define MAXSPECIALCROSS 8
extern line_t* spechit[MAXSPECIALCROSS];
extern int numspechit;
boolean P_Move (mobj_t* actor)
{
fixed_t tryx;
fixed_t tryy;
line_t* ld;
// warning: 'catch', 'throw', and 'try'
// are all C++ reserved words
boolean try_ok;
boolean good;
if (actor->movedir == DI_NODIR)
return false;
if ((unsigned)actor->movedir >= 8)
I_Error ("Weird actor->movedir!");
tryx = actor->x + actor->info->speed*xspeed[actor->movedir];
tryy = actor->y + actor->info->speed*yspeed[actor->movedir];
try_ok = P_TryMove (actor, tryx, tryy);
if (!try_ok)
{
// open any specials
if (actor->flags & MF_FLOAT && floatok)
{
// must adjust height
if (actor->z < tmfloorz)
actor->z += FLOATSPEED; // [STRIFE] Note FLOATSPEED == 5*FRACUNIT
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|MF_FEETCLIPPED); // villsa [STRIFE]
// villsa [STRIFE]
if(P_GetTerrainType(actor) != FLOOR_SOLID)
actor->flags |= MF_FEETCLIPPED;
}
// villsa [STRIFE] Removed pulling non-floating actors down to the ground.
// (haleyjd 09/05/10: Verified)
/*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.
//
// haleyjd 09/05/10: [STRIFE] Verified unmodified.
//
boolean P_TryWalk (mobj_t* actor)
{
if (!P_Move (actor))
{
return false;
}
actor->movecount = P_Random()&15;
return true;
}
//
// P_NewChaseDir
//
void P_NewChaseDir(mobj_t* actor)
{
fixed_t deltax;
fixed_t deltay;
dirtype_t d[3];
int tdir;
dirtype_t olddir;
dirtype_t turnaround;
// villsa [STRIFE] don't bomb out and instead set spawnstate
if(!actor->target)
{
//I_Error("P_NewChaseDir: called with no target");
P_SetMobjState(actor, actor->info->spawnstate);
return;
}
olddir = actor->movedir;
turnaround=opposite[olddir];
deltax = actor->target->x - actor->x;
deltay = actor->target->y - actor->y;
if (deltax>10*FRACUNIT)
d[1]= DI_EAST;
else if (deltax<-10*FRACUNIT)
d[1]= DI_WEST;
else
d[1]=DI_NODIR;
if (deltay<-10*FRACUNIT)
d[2]= DI_SOUTH;
else if (deltay>10*FRACUNIT)
d[2]= DI_NORTH;
else
d[2]=DI_NODIR;
// try direct route
if (d[1] != DI_NODIR
&& d[2] != DI_NODIR)
{
actor->movedir = diags[((deltay<0)<<1)+(deltax>0)];
if (actor->movedir != (int) turnaround && P_TryWalk(actor))
return;
}
// try other directions
if (P_Random() > 200
|| abs(deltay)>abs(deltax))
{
tdir=d[1];
d[1]=d[2];
d[2]=tdir;
}
if (d[1]==turnaround)
d[1]=DI_NODIR;
if (d[2]==turnaround)
d[2]=DI_NODIR;
if (d[1]!=DI_NODIR)
{
actor->movedir = d[1];
if (P_TryWalk(actor))
{
// either moved forward or attacked
return;
}
}
if (d[2]!=DI_NODIR)
{
actor->movedir =d[2];
if (P_TryWalk(actor))
return;
}
// there is no direct path to the player,
// so pick another direction.
if (olddir!=DI_NODIR)
{
actor->movedir =olddir;
if (P_TryWalk(actor))
return;
}
// randomly determine direction of search
if (P_Random()&1)
{
for ( tdir=DI_EAST;
tdir<=DI_SOUTHEAST;
tdir++ )
{
if (tdir != (int) turnaround)
{
actor->movedir =tdir;
if ( P_TryWalk(actor) )
return;
}
}
}
else
{
for ( tdir=DI_SOUTHEAST;
tdir != (DI_EAST-1);
tdir-- )
{
if (tdir != (int) 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_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(mobj_t* actor)
{
int dir = 0;
// randomly determine direction of search
if(P_Random() & 1)
{
for(dir = 0; dir < DI_NODIR; dir++)
{
if(dir != opposite[actor->movedir])
{
actor->movedir = dir;
if(P_Random() & 1)
{
if(P_TryWalk(actor))
break;
}
}
}
}
else
{
dir = DI_SOUTHEAST;
while(1)
{
// haleyjd 09/05/10: P_TryWalk -> P_Move, missing random code.
if(dir != opposite[actor->movedir])
{
actor->movedir = dir;
// villsa 09/06/10: un-inlined code
if(P_TryWalk(actor))
return;
}
if(--dir == -1)
{
if(opposite[actor->movedir] == DI_NODIR)
{
actor->movedir = DI_NODIR;
return;
}
actor->movedir = opposite[actor->movedir];
// 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
}
// haleyjd 09/05/10: Needed below.
extern void P_BulletSlope (mobj_t *mo);
//
// P_LookForPlayers
//
// If allaround is false, only look 180 degrees in front.
// Returns true if a player is targeted.
//
// [STRIFE]
// haleyjd 09/05/10: Modifications to support friendly units.
//
boolean
P_LookForPlayers
( mobj_t* actor,
boolean allaround )
{
int c;
int stop;
player_t* player;
sector_t* sector;
angle_t an;
fixed_t dist;
mobj_t * master = players[actor->miscdata].mo;
// haleyjd 09/05/10: handle Allies
if(actor->flags & MF_ALLY)
{
// Deathmatch: support team behavior for Rebels.
if(netgame)
{
// Rebels adopt the allied player's target if it is not of the same
// allegiance. Other allies do it unconditionally.
if(master && master->target &&
(master->target->type != MT_REBEL1 ||
master->target->miscdata != actor->miscdata))
{
actor->target = master->target;
}
else
{
// haleyjd 09/06/10: Note that this sets actor->target in Strife!
P_BulletSlope(actor);
// Clear target if nothing is visible, or if the target is a
// friendly Rebel or the allied player.
if(!linetarget ||
actor->target->type == MT_REBEL1 &&
actor->target->miscdata == actor->miscdata ||
actor->target == master)
{
actor->target = NULL;
return false;
}
}
}
else
{
// Single-player: Adopt any non-allied player target.
if(master && master->target && !(master->target->flags & MF_ALLY))
{
actor->target = master->target;
return true;
}
// haleyjd 09/06/10: Note that this sets actor->target in Strife!
P_BulletSlope(actor);
// Clear target if nothing is visible, or if the target is an ally.
if(!linetarget || actor->target->flags & MF_ALLY)
{
actor->target = NULL;
return false;
}
}
return true;
}
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;
}
// haleyjd 09/05/10: [STRIFE] Removed A_KeenDie
//
// ACTION ROUTINES
//
//
// A_Look
// Stay in state until a player is sighted.
//
// [STRIFE]
// haleyjd 09/05/10: Adjusted for allies, Inquisitors, etc.
//
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) )
{
// [STRIFE] Allies wander when they call this.
if(actor->flags & MF_ALLY)
A_RandomWalk(actor);
else
{
actor->target = targ;
if ( actor->flags & MF_AMBUSH )
{
if (P_CheckSight (actor, actor->target))
goto seeyou;
}
else
goto seeyou;
}
}
// haleyjd 09/05/10: This is bizarre, as Rogue keeps using the GIVEQUEST flag
// as a parameter to control allaround look behavior. Did they just run out of
// flags, or what?
// STRIFE-TODO: Needs serious verification.
if (!P_LookForPlayers (actor, actor->flags & MF_GIVEQUEST) )
return;
// go into chase state
seeyou:
if (actor->info->seesound)
{
int sound = actor->info->seesound;
mobj_t * emitter = actor;
// [STRIFE] Removed DOOM random sounds.
// [STRIFE] Only Inquisitors roar loudly here.
if (actor->type == MT_INQUISITOR)
emitter = NULL;
S_StartSound (emitter, sound);
}
// [STRIFE] Set threshold (kinda odd as it's still set to 0 above...)
actor->threshold = 20;
P_SetMobjState (actor, actor->info->seestate);
}
//
// A_RandomWalk
//
// [STRIFE] New function.
// haleyjd 09/05/10: Action routine used to meander about.
//
void A_RandomWalk(mobj_t* 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_Move(actor))
{
P_NewRandomDir(actor);
actor->movecount += 5;
}
}
}
//
// A_FriendLook
//
// [STRIFE] New function
// haleyjd 09/05/10: Action function used mostly by mundane characters such as
// peasants.
//
void A_FriendLook(mobj_t* actor)
{
mobj_t *soundtarget = actor->subsector->sector->soundtarget;
actor->threshold = 0;
if(soundtarget && soundtarget->flags & MF_SHOOTABLE)
{
// Handle allies, except on maps 3 and 34 (Front Base/Movement Base)
if((actor->flags & MF_ALLY) == (soundtarget->flags & MF_ALLY) &&
gamemap != 3 && gamemap != 34)
{
// STRIFE-TODO: Needs serious verification.
if(P_LookForPlayers(actor, actor->flags & MF_GIVEQUEST))
{
P_SetMobjState(actor, actor->info->seestate);
actor->flags |= MF_INCOMBAT;
return;
}
}
else
{
actor->target = soundtarget;
if(!(actor->flags & MF_AMBUSH) || P_CheckSight(actor, actor->target))
{
actor->threshold = 10;
P_SetMobjState(actor, actor->info->seestate);
return;
}
}
}
// do some idle animation
if(P_Random() < 30)
P_SetMobjState(actor, actor->info->spawnstate + 1 + (P_Random() & 1));
// wander around a bit
if(!(actor->flags & MF_STAND) && P_Random() < 40)
P_SetMobjState(actor, actor->info->spawnstate + 3);
}
//
// A_Listen
//
// [STRIFE] New function
// haleyjd 09/05/10: Action routine used to strictly listen for a target.
//
void A_Listen(mobj_t* actor)
{
mobj_t *soundtarget;
actor->threshold = 0;
soundtarget = actor->subsector->sector->soundtarget;
if(soundtarget && (soundtarget->flags & MF_SHOOTABLE))
{
if((actor->flags & MF_ALLY) != (soundtarget->flags & MF_ALLY))
{
actor->target = soundtarget;
if(!(actor->flags & MF_AMBUSH) || P_CheckSight(actor, actor->target))
{
if(actor->info->seesound)
S_StartSound(actor, actor->info->seesound);
actor->threshold = 10;
P_SetMobjState(actor, actor->info->seestate);
}
}
}
}
//
// A_Chase
// Actor has a melee attack,
// so it tries to close as fast as possible
//
// haleyjd 09/05/10: [STRIFE] Various minor changes
//
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;
// [STRIFE] Checks only against fastparm, not gameskill == 5
if (!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)
{
// [STRIFE] Checks only fastparm.
if (!fastparm && actor->movecount)
{
goto nomissile;
}
if (!P_CheckMissileRange (actor))
goto nomissile;
P_SetMobjState (actor, actor->info->missilestate);
// [STRIFE] Add INCOMBAT flag to disable dialog
actor->flags |= (MF_INCOMBAT|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);
}
// [STRIFE] Changes to active sound behavior:
// * Significantly more frequent
// * Acolytes have randomized wandering sounds
// make active sound
if (actor->info->activesound && P_Random () < 38)
{
if(actor->info->activesound >= sfx_agrac1 &&
actor->info->activesound <= sfx_agrac4)
{
S_StartSound(actor, sfx_agrac1 + P_Random() % 4);
}
else
S_StartSound(actor, actor->info->activesound);
}
}
//
// A_FaceTarget
//
// [STRIFE]
// haleyjd 09/05/10: Handling for visibility-modifying flags.
//
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)
{
// [STRIFE] increased SHADOW inaccuracy by a power of 2
int t = P_Random();
actor->angle += (t - P_Random()) << 22;
}
else if(actor->target->flags & MF_MVIS)
{
// [STRIFE] MVIS gives even worse aiming!
int t = P_Random();
actor->angle += (t - P_Random()) << 23;
}
}
//
// A_PeasantPunch
//
// [STRIFE] New function
// haleyjd 09/05/10: Attack used by Peasants as a one-time retaliation
// when the player or a monster injures them. Weak doesn't begin to
// describe it :P
//
void A_PeasantPunch(mobj_t* actor)
{
if(!actor->target)
return;
A_FaceTarget(actor);
if(P_CheckMeleeRange(actor))
P_DamageMobj(actor->target, actor, actor, 2 * (P_Random() % 5) + 2);
}
//
// A_ReaverAttack
//
// [STRIFE] New function
// haleyjd 09/06/10: Action routine used by Reavers to fire bullets.
// Also apparently used by Inquistors, though they don't seem to use
// it too often, as they're content to blow your face off with their
// HE grenades instead.
//
void A_ReaverAttack(mobj_t* actor)
{
int i = 0;
fixed_t slope;
if(!actor->target)
return;
S_StartSound(actor, sfx_reavat);
A_FaceTarget(actor);
slope = P_AimLineAttack(actor, actor->angle, 2048*FRACUNIT);
do
{
int t = P_Random();
angle_t shootangle = actor->angle + ((t - P_Random()) << 20);
int damage = (P_Random() & 7) + 1;
P_LineAttack(actor, shootangle, 2048*FRACUNIT, slope, damage);
++i;
}
while(i < 3);
}
//
// A_BulletAttack
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for generic bullet attacks. Used by
// a lot of different characters, including Acolytes, Rebels, and Macil.
//
void A_BulletAttack(mobj_t* actor)
{
int t, damage;
fixed_t slope;
angle_t shootangle;
if(!actor->target)
return;
S_StartSound(actor, sfx_rifle);
A_FaceTarget(actor);
slope = P_AimLineAttack(actor, actor->angle, 2048*FRACUNIT);
t = P_Random();
shootangle = ((t - P_Random()) << 19) + actor->angle;
damage = 3 * (P_Random() % 5 + 1);
P_LineAttack(actor, shootangle, 2048*FRACUNIT, slope, damage);
}
//
// A_CheckTargetVisible
//
// [STRIFE] New function
// haleyjd 09/06/10: Action routine which sets a thing back to its
// seestate at random, or if it cannot see its target, or its target
// is dead. Used by diverse actors.
//
void A_CheckTargetVisible(mobj_t* actor)
{
A_FaceTarget(actor);
if(P_Random() >= 30)
{
mobj_t *target = actor->target;
if(!target || target->health <= 0 || !P_CheckSight(actor, target) ||
P_Random() < 40)
{
P_SetMobjState(actor, actor->info->seestate);
}
}
}
//
// A_SentinelAttack
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function implementing the Sentinel's laser attack
// villsa 09/06/10 implemented
//
void A_SentinelAttack(mobj_t* actor)
{
mobj_t* mo;
mobj_t* mo2;
fixed_t x;
fixed_t y;
fixed_t z;
angle_t an;
int i;
mo = P_SpawnFacingMissile(actor, actor->target, MT_L_LASER);
an = actor->angle >> ANGLETOFINESHIFT;
if(mo->momy | mo->momx) // villsa - fixed typo (yes, they actually used '|' instead of'||')
{
for(i = 8; i > 1; i--)
{
x = mo->x + FixedMul(mobjinfo[MT_L_LASER].radius * i, finecosine[an]);
y = mo->y + FixedMul(mobjinfo[MT_L_LASER].radius * i, finesine[an]);
z = mo->z + i * (mo->momz >> 2);
mo2 = P_SpawnMobj(x, y, z, MT_R_LASER);
mo2->target = actor;
mo2->momx = mo->momx;
mo2->momy = mo->momy;
mo2->momz = mo->momz;
P_CheckMissileSpawn(mo2);
}
}
mo->z += mo->momz >> 2;
}
//
// A_StalkerThink
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function to drive Stalker logic.
//
void A_StalkerThink(mobj_t* actor)
{
statenum_t statenum;
if(actor->flags & MF_NOGRAVITY)
{
if(actor->ceilingz - actor->info->height <= actor->z)
return;
statenum = S_SPID_11; // 1020
}
else
statenum = S_SPID_18; // 1027
P_SetMobjState(actor, statenum);
}
//
// A_StalkerSetLook
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function to marshall transitions to the
// Stalker's spawnstate.
//
void A_StalkerSetLook(mobj_t* actor)
{
statenum_t statenum;
if(!actor) // weird; totally unnecessary.
return;
if(actor->flags & MF_NOGRAVITY)
{
if(actor->state->nextstate == S_SPID_01) // 1010
return;
statenum = S_SPID_01; // 1010
}
else
{
if(actor->state->nextstate == S_SPID_02) // 1011
return;
statenum = S_SPID_02; // 1011
}
P_SetMobjState(actor, statenum);
}
//
// A_StalkerDrop
//
// [STRIFE] New function
// haleyjd 09/06/10: Dead simple: removes NOGRAVITY status.
//
void A_StalkerDrop(mobj_t* actor)
{
actor->flags &= ~MF_NOGRAVITY;
}
//
// A_StalkerScratch
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for Stalker's attack.
//
void A_StalkerScratch(mobj_t* actor)
{
if(actor->flags & MF_NOGRAVITY)
{
// Drop him down before he can attack
P_SetMobjState(actor, S_SPID_11); // 1020
return;
}
if(!actor->target)
return;
A_FaceTarget(actor);
if(P_CheckMeleeRange(actor))
P_DamageMobj(actor->target, actor, actor, 2 * (P_Random() % 8) + 2);
}
//
// A_FloatWeave
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function which is responsible for floating
// actors' constant upward and downward movement. Probably a really bad
// idea in retrospect given how dodgy the 3D clipping implementation is.
//
void A_FloatWeave(mobj_t* actor)
{
fixed_t height;
fixed_t z;
if(actor->threshold)
return;
if(actor->flags & MF_INFLOAT)
return;
height = actor->info->height; // v2
z = actor->floorz + 96*FRACUNIT; // v1
if ( z > actor->ceilingz - height - 16*FRACUNIT )
z = actor->ceilingz - height - 16*FRACUNIT;
if ( z >= actor->z )
actor->momz += FRACUNIT;
else
actor->momz -= FRACUNIT;
if ( z == actor->z )
actor->threshold = 4;
else
actor->threshold = 8;
}
//
// A_RobotMelee
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for Reaver and Templar melee attacks.
//
void A_RobotMelee(mobj_t* actor)
{
if(!actor->target)
return;
A_FaceTarget(actor);
if(P_CheckMeleeRange(actor))
{
S_StartSound(actor, sfx_revbld);
P_DamageMobj(actor->target, actor, actor, 3 * (P_Random() % 8 + 1));
}
}
//
// A_TemplarMauler
//
// [STRIFE] New function
// haleyjd 09/06/10: Exactly what it sounds like. Kicks your ass.
//
void A_TemplarMauler(mobj_t* actor)
{
int i, t;
int angle;
int bangle;
int damage;
int slope;
if(!actor->target)
return;
S_StartSound(actor, sfx_pgrdat);
A_FaceTarget(actor);
bangle = actor->angle;
slope = P_AimLineAttack(actor, bangle, 2048*FRACUNIT);
for(i = 0; i < 10; i++)
{
// haleyjd 09/06/10: Very carefully preserved order of P_Random calls
damage = (P_Random() & 4) * 2;
t = P_Random();
angle = bangle + ((t - P_Random()) << 19);
t = P_Random();
slope = ((t - P_Random()) << 5) + slope;
P_LineAttack(actor, angle, 2112*FRACUNIT, slope, damage);
}
}
//
// A_CrusaderAttack
//
// villsa [STRIFE] new codepointer
// 09/06/10: Action function for the Crusader's Flamethrower.
// Very similar to the player's flamethrower, seeing how it was ripped
// off a Crusader by the Rat People ;)
//
void A_CrusaderAttack(mobj_t* actor)
{
if(!actor->target)
return;
actor->z += (8*FRACUNIT);
if(P_CheckRobotRange(actor))
{
A_FaceTarget(actor);
actor->angle -= (ANG90 / 8);
P_SpawnFacingMissile(actor, actor->target, MT_C_FLAME);
}
else if(P_CheckMissileRange(actor))
{
A_FaceTarget(actor);
actor->z += (16*FRACUNIT);
P_SpawnFacingMissile(actor, actor->target, MT_C_MISSILE);
actor->angle -= (ANG45 / 32);
actor->z -= (16*FRACUNIT);
P_SpawnFacingMissile(actor, actor->target, MT_C_MISSILE);
actor->angle += (ANG45 / 16);
P_SpawnFacingMissile(actor, actor->target, MT_C_MISSILE);
P_SetMobjState(actor, actor->info->seestate);
actor->reactiontime += 15;
}
else
P_SetMobjState(actor, actor->info->seestate);
actor->z -= (8*FRACUNIT);
}
//
// A_CrusaderLeft
//
// villsa [STRIFE] new codepointer
//
void A_CrusaderLeft(mobj_t* actor)
{
mobj_t* mo;
actor->angle += (ANG90 / 16);
mo = P_SpawnFacingMissile(actor, actor->target, MT_C_FLAME);
mo->momz = FRACUNIT;
mo->z += (16*FRACUNIT);
}
//
// A_CrusaderRight
//
// villsa [STRIFE] new codepointer
//
void A_CrusaderRight(mobj_t* actor)
{
mobj_t* mo;
actor->angle -= (ANG90 / 16);
mo = P_SpawnFacingMissile(actor, actor->target, MT_C_FLAME);
mo->momz = FRACUNIT;
mo->z += (16*FRACUNIT);
}
//
// A_CheckTargetVisible2
//
// [STRIFE] New function
// haleyjd 09/06/10: Mostly the same as CheckTargetVisible, except without
// the randomness.
//
void A_CheckTargetVisible2(mobj_t* actor)
{
if(!actor->target || actor->target->health <= 0 ||
!P_CheckSight(actor, actor->target))
{
P_SetMobjState(actor, actor->info->seestate);
}
}
//
// A_InqFlyCheck
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function to check if an Inquisitor wishes
// to take to flight.
//
void A_InqFlyCheck(mobj_t* actor)
{
if(!actor->target)
return;
A_FaceTarget(actor);
// if not in "robot" range, shoot grenades.
if(!P_CheckRobotRange(actor))
P_SetMobjState(actor, S_ROB3_14); // 1061
if(actor->z != actor->target->z)
{
// Take off all zig!
if(actor->z + actor->height + 54*FRACUNIT < actor->ceilingz)
P_SetMobjState(actor, S_ROB3_17); // 1064
}
}
//
// A_InqGrenade
//
// villsa [STRIFE] new codepointer
// 09/06/10: Inquisitor grenade attack action routine.
//
void A_InqGrenade(mobj_t* actor)
{
mobj_t* mo;
if(!actor->target)
return;
A_FaceTarget(actor);
actor->z += MAXRADIUS;
// grenade 1
actor->angle -= (ANG45 / 32);
mo = P_SpawnFacingMissile(actor, actor->target, MT_INQGRENADE);
mo->momz += (9*FRACUNIT);
// grenade 2
actor->angle += (ANG45 / 16);
mo = P_SpawnFacingMissile(actor, actor->target, MT_INQGRENADE);
mo->momz += (16*FRACUNIT);
actor->z -= MAXRADIUS;
}
//
// A_InqTakeOff
//
// [STRIFE] New function
// haleyjd 09/06/10: Makes an Inquisitor start flying.
//
void A_InqTakeOff(mobj_t* actor)
{
angle_t an;
fixed_t speed = actor->info->speed * (2 * FRACUNIT / 3);
fixed_t dist;
if(!actor->target)
return;
S_StartSound(actor, sfx_inqjmp);
actor->z += 64 * FRACUNIT;
A_FaceTarget(actor);
an = actor->angle >> ANGLETOFINESHIFT;
actor->momx = FixedMul(finecosine[an], speed);
actor->momy = FixedMul(finesine[an], speed);
dist = P_AproxDistance(actor->target->x - actor->x,
actor->target->y - actor->y);
dist /= speed;
if(dist < 1)
dist = 1;
actor->momz = (actor->target->z - actor->z) / dist;
actor->reactiontime = 60;
actor->flags |= MF_NOGRAVITY;
}
//
// A_InqFly
//
// [STRIFE] New function
// haleyjd 09/06/10: Handles an Inquisitor in flight.
//
void A_InqFly(mobj_t* actor)
{
if(!(leveltime & 7))
S_StartSound(actor, sfx_inqjmp);
if(--actor->reactiontime < 0 || !actor->momx || !actor->momy ||
actor->z <= actor->floorz)
{
// Come in for a landing.
P_SetMobjState(actor, actor->info->seestate);
actor->reactiontime = 0;
actor->flags &= ~MF_NOGRAVITY;
}
}
//
// A_FireSigilWeapon
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for the Entity's attack.
//
void A_FireSigilWeapon(mobj_t* actor)
{
int choice = P_Random() % 5;
// STRIFE-TODO: Needs verification. This switch is just weird.
switch(choice)
{
case 0:
A_ProgrammerAttack(actor);
break;
// ain't not seen no case 1, bub...
case 2:
A_FireSigilEOffshoot(actor);
break;
case 3:
A_SpectreCAttack(actor);
break;
case 4:
A_SpectreDAttack(actor);
break;
case 5: // BUG: never used? wtf were they thinking?
A_SpectreEAttack(actor);
break;
default:
break;
}
}
//
// A_ProgrammerAttack
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for the Programmer's main
// attack; equivalent to the player's first Sigil.
//
void A_ProgrammerAttack(mobj_t* actor)
{
mobj_t *mo;
if(!actor->target)
return;
mo = P_SpawnMobj(actor->x, actor->y, ONFLOORZ, MT_SIGIL_A_GROUND);
mo->threshold = 25;
mo->target = actor;
mo->health = -2;
mo->tracer = actor->target;
}
//
// A_Sigil_A_Action
//
// [STRIFE] New function
// haleyjd 09/06/10: Called by MT_SIGIL_A_GROUND to zot anyone nearby with
// corny looking lightning bolts.
//
void A_Sigil_A_Action(mobj_t* actor)
{
int t, x, y, type;
mobj_t *mo;
if(actor->threshold)
actor->threshold--;
t = P_Random();
actor->momx += ((t & 3) - (P_Random() & 3)) << FRACBITS;
t = P_Random();
actor->momy += ((t & 3) - (P_Random() & 3)) << FRACBITS;
t = P_Random();
x = 50*FRACUNIT * ((t & 3) - (P_Random() & 3)) + actor->x;
t = P_Random();
y = 50*FRACUNIT * ((t & 3) - (P_Random() & 3)) + actor->y;
if(actor->threshold <= 25)
type = MT_SIGIL_A_ZAP_LEFT;
else
type = MT_SIGIL_A_ZAP_RIGHT;
mo = P_SpawnMobj(x, y, ONCEILINGZ, type);
mo->momz = -18 * FRACUNIT;
mo->target = actor->target;
mo->health = actor->health;
mo = P_SpawnMobj(actor->x, actor->y, ONCEILINGZ, MT_SIGIL_A_ZAP_RIGHT);
mo->momz = -18 * FRACUNIT;
mo->target = actor->target;
mo->health = actor->health;
}
//
// A_SpectreEAttack
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for the Loremaster's Spectre.
// Equivalent to the player's final Sigil attack.
//
void A_SpectreEAttack(mobj_t* actor)
{
mobj_t *mo;
if(!actor->target)
return;
mo = P_SpawnMissile(actor, actor->target, MT_SIGIL_SE_SHOT);
mo->health = -2;
}
//
// A_SpectreCAttack
//
// villsa [STRIFE] new codepointer
// 09/06/10: Action routine for the Oracle's Spectre. Equivalent to the player's
// third Sigil attack.
//
void A_SpectreCAttack(mobj_t* actor)
{
mobj_t* mo;
int i;
if(!actor->target)
return;
mo = P_SpawnMobj(actor->x, actor->y, actor->z + (32*FRACUNIT), MT_SIGIL_A_ZAP_RIGHT);
mo->momz = -(18*FRACUNIT);
mo->target = actor;
mo->health = -2;
mo->tracer = actor->target;
actor->angle -= ANG90;
for(i = 0; i < 20; i++)
{
actor->angle += (ANG90 / 10);
mo = P_SpawnMortar(actor, MT_SIGIL_C_SHOT);
mo->health = -2;
mo->z = actor->z + (32*FRACUNIT);
}
actor->angle -= ANG90;
}
//
// A_AlertSpectreC
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function called by the Oracle when it is
// killed. Finds an MT_SPECTRE_C anywhere on the map and awakens it.
//
void A_AlertSpectreC(mobj_t* actor)
{
thinker_t *th;
for(th = thinkercap.next; th != &thinkercap; th = th->next)
{
if(th->function.acp1 == (actionf_p1)P_MobjThinker)
{
mobj_t *mo = (mobj_t *)th;
if(mo->type == MT_SPECTRE_C)
{
P_SetMobjState(mo, mo->info->seestate);
mo->target = actor->target;
return;
}
}
}
}
//
// A_Sigil_E_Action
//
// villsa [STRIFE] new codepointer
// 09/06/10: Action routine for Sigil "E" shots. Spawns the room-filling
// lightning bolts that seem to often do almost nothing.
//
void A_Sigil_E_Action(mobj_t* actor)
{
actor->angle += ANG90;
P_SpawnMortar(actor, MT_SIGIL_E_OFFSHOOT);
actor->angle -= ANG180;
P_SpawnMortar(actor, MT_SIGIL_E_OFFSHOOT);
actor->angle += ANG90;
P_SpawnMortar(actor, MT_SIGIL_E_OFFSHOOT);
}
//
// A_SigilTrail
//
// villsa [STRIFE] new codepointer
//
void A_SigilTrail(mobj_t* actor)
{
mobj_t* mo;
mo = P_SpawnMobj(actor->x - actor->momx,
actor->y - actor->momy,
actor->z, MT_SIGIL_TRAIL);
mo->angle = actor->angle;
mo->health = actor->health;
}
//
// A_SpectreDAttack
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function for Macil's Spectre.
// Equivalent of the player's fourth Sigil attack.
//
void A_SpectreDAttack(mobj_t* actor)
{
mobj_t *mo;
if(!actor->target)
return;
mo = P_SpawnMissile(actor, actor->target, MT_SIGIL_SD_SHOT);
mo->health = -2;
mo->tracer = actor->target;
}
//
// A_FireSigilEOffshoot
//
// [STRIFE] New function
// haleyjd 09/06/10: Action function to fire part of a Sigil E
// attack. Used at least by the Entity.
//
void A_FireSigilEOffshoot(mobj_t* actor)
{
mobj_t *mo;
if(!actor->target)
return;
mo = P_SpawnMissile(actor, actor->target, MT_SIGIL_E_OFFSHOOT);
mo->health = -2;
}
//
// A_ShadowOff
//
// villsa [STRIFE] new codepointer
// 09/06/10: Disables SHADOW and MVIS flags.
//
void A_ShadowOff(mobj_t* actor)
{
actor->flags &= ~(MF_SHADOW|MF_MVIS);
}
//
// A_ModifyVisibility
//
// villsa [STRIFE] new codepointer
// 09/06/10: Turns on SHADOW, and turns off MVIS.
//
void A_ModifyVisibility(mobj_t* actor)
{
actor->flags |= MF_SHADOW;
actor->flags &= ~MF_MVIS;
}
//
// A_ShadowOn
//
// villsa [STRIFE] new codepointer
// 09/06/10: Turns on SHADOW and MVIS.
//
void A_ShadowOn(mobj_t* actor)
{
actor->flags |= (MF_SHADOW|MF_MVIS);
}
//
// A_SetTLOptions
//
// villsa [STRIFE] new codepointer
// 09/06/10: Sets SHADOW and/or MVIS based on the thing's spawnpoint options.
//
void A_SetTLOptions(mobj_t* actor)
{
if(actor->spawnpoint.options & MTF_TRANSLUCENT)
actor->flags |= MF_SHADOW;
if(actor->spawnpoint.options & MTF_MVIS)
actor->flags |= MF_MVIS;
}
//
// A_BossMeleeAtk
//
// villsa [STRIFE] new codepointer
// 09/06/10: Gratuitous melee attack used by multiple boss characters,
// just for the sake of having one. It's not like anybody in their right
// mind would get close to any of the maniacs that use this ;)
//
void A_BossMeleeAtk(mobj_t* actor)
{
if(!actor->target)
return;
P_DamageMobj(actor->target, actor, actor, 10 * (P_Random() & 9));
}
//
// A_BishopAttack
//
// villsa [STRIFE] new codepointer
// 09/06/10: Bishop's homing missile attack.
//
void A_BishopAttack(mobj_t* actor)
{
mobj_t* mo;
if(!actor->target)
return;
actor->z += MAXRADIUS;
mo = P_SpawnMissile(actor, actor->target, MT_SEEKMISSILE);
mo->tracer = actor->target;
actor->z -= MAXRADIUS;
}
//
// A_FireHookShot
//
// villsa [STRIFE] new codepointer
// 09/06/10: Action function for the Loremaster's hookshot attack.
//
void A_FireHookShot(mobj_t* actor)
{
if(!actor->target)
return;
P_SpawnMissile(actor, actor->target, MT_HOOKSHOT);
}
//
// A_FireChainShot
//
// villsa [STRIFE] new codepointer
// 09/06/10: Action function for the hookshot projectile. Spawns echoes
// to create a chain-like appearance.
//
void A_FireChainShot(mobj_t* actor)
{
S_StartSound(actor, sfx_tend);
P_SpawnMobj(actor->x, actor->y, actor->z, MT_CHAINSHOT); // haleyjd: fixed type
P_SpawnMobj(actor->x - (actor->momx >> 1),
actor->y - (actor->momy >> 1),
actor->z, MT_CHAINSHOT);
P_SpawnMobj(actor->x - actor->momx,
actor->y - actor->momy,
actor->z, MT_CHAINSHOT);
}
//
// A_MissileSmoke
//
// villsa [STRIFE] new codepointer
//
void A_MissileSmoke(mobj_t* actor)
{
mobj_t* mo;
S_StartSound(actor, sfx_rflite);
P_SpawnPuff(actor->x, actor->y, actor->z);
mo = P_SpawnMobj(actor->x - actor->momx,
actor->y - actor->momy,
actor->z, MT_MISSILESMOKE);
mo->momz = FRACUNIT;
}
//
// A_SpawnSparkPuff
//
// villsa [STRIFE] new codepointer
//
void A_SpawnSparkPuff(mobj_t* actor)
{
int r;
mobj_t* mo;
fixed_t x;
fixed_t y;
r = P_Random();
x = (10*FRACUNIT) * ((r & 3) - (P_Random() & 3)) + actor->x;
r = P_Random();
y = (10*FRACUNIT) * ((r & 3) - (P_Random() & 3)) + actor->y;
mo = P_SpawnMobj(x, y, actor->z, MT_SPARKPUFF);
P_SetMobjState(mo, S_BNG4_01); // 199
mo->momz = FRACUNIT;
}
// haleyjd 09/05/10: [STRIFE] Removed:
// A_PosAttack, A_SPosAttack, A_CPosAttack, A_CPosRefire, A_SpidRefire,
// A_BspiAttack, A_TroopAttack, A_SargAttack, A_HeadAttack, A_CyberAttack,
// A_BruisAttack, A_SkelMissile
int TRACEANGLE = 0xE000000; // villsa [STRIFE] changed from 0xC000000 to 0xE000000
//
// A_Tracer
//
void A_Tracer (mobj_t* actor)
{
angle_t exact;
fixed_t dist;
fixed_t slope;
mobj_t* dest;
//mobj_t* th;
// villsa [STRIFE] not used
/*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)
{
// villsa [STRIFE] slightly different algorithm
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;
}
//
// A_ProgrammerMelee
//
// villsa [STRIFE] new codepointer
// 09/08/10: Melee attack for the Programmer.
// haleyjd - fixed damage formula
//
void A_ProgrammerMelee(mobj_t* actor)
{
if(!actor->target)
return;
A_FaceTarget(actor);
if(P_CheckMeleeRange(actor))
{
S_StartSound(actor, sfx_mtalht);
P_DamageMobj(actor->target, actor, actor, 8 * (P_Random() % 10 + 1));
}
}
// haleyjd 09/05/10: [STRIFE] Removed:
// A_SkelWhoosh, A_SkelFist, PIT_VileCheck, A_VileChase, A_VileStart,
// A_StartFire, A_FireCrackle, A_Fire, A_VileTarget, A_VileAttack
// A_FatRaise, A_FatAttack1, A_FatAttack2, A_FatAttack3, A_SkullAttack,
// A_PainShootSkull, A_PainAttack, A_PainDie
//
// A_Scream
//
// villsa [STRIFE]
// * Has no random death sounds, so play deathsound directly
// * Full-volume roars for the Entity and Inquisitor.
//
void A_Scream(mobj_t* actor)
{
if(!actor->info->deathsound)
return;
// Check for bosses.
if(actor->type == MT_ENTITY || actor->type == MT_INQUISITOR)
S_StartSound(NULL, actor->info->deathsound); // full volume
else
S_StartSound(actor, actor->info->deathsound);
}
//
// A_XScream
//
// villsa [STRIFE]
// * Robots will play deathsound while non-robots play the slop sfx
//
void A_XScream(mobj_t* actor)
{
int sound;
if(actor->flags & MF_NOBLOOD && actor->info->deathsound)
sound = actor->info->deathsound;
else
sound = sfx_slop;
S_StartSound(actor, sound);
}
//
// A_Pain
//
// villsa [STRIFE]
// * Play random peasant sounds; otherwise play painsound directly
//
void A_Pain(mobj_t* actor)
{
int sound = actor->info->painsound;
if(sound)
{
if(sound >= sfx_pespna && sound <= sfx_pespnd)
sound = sfx_pespna + (P_Random() % 4);
S_StartSound(actor, sound);
}
}
//
// A_PeasantCrash
//
// villsa [STRIFE] new codepointer
// 09/08/10: Called from Peasant's "crash" state (not to be confused with
// Heretic crash states), which is invoked when the Peasant has taken
// critical but sub-fatal damage. It will "bleed out" the rest of its
// health by calling this function repeatedly.
//
void A_PeasantCrash(mobj_t* actor)
{
// Set INCOMBAT, because you probably wouldn't feel like talking either
// if somebody just stabbed you in the gut with a punch dagger...
actor->flags |= MF_INCOMBAT;
if(!(P_Random() % 5))
{
A_Pain(actor); // inlined in asm
actor->health--;
}
if(actor->health <= 0)
P_KillMobj(actor->target, actor);
}
//
// A_Fall
//
// [STRIFE]
// * Set INCOMBAT, and clear NOGRAVITY and SHADOW
//
void A_Fall (mobj_t *actor)
{
// villsa [STRIFE] set incombat flag to stop dialog
actor->flags |= MF_INCOMBAT;
// actor is on ground, it can be walked over
// villsa [STRIFE] remove nogravity/shadow flags as well
actor->flags &= ~(MF_SOLID|MF_NOGRAVITY|MF_SHADOW);
}
//
// A_HideZombie
//
// villsa [STRIFE] new codepointer
//
void A_HideZombie(mobj_t* actor)
{
line_t junk;
junk.tag = 999;
EV_DoDoor(&junk, blazeClose);
if(actor->target && actor->target->player)
P_NoiseAlert(actor->target, actor); // inlined in asm
}
//
// A_MerchantPain
//
// villsa [STRIFE] new codepointer
// 09/08/10: Pain pointer for merchant characters. They close up shop for
// a while and set off the alarm.
//
void A_MerchantPain(mobj_t* actor)
{
line_t junk;
junk.tag = 999;
EV_DoDoor(&junk, shopClose);
if(actor->target && actor->target->player)
P_NoiseAlert(actor->target, actor); // inlined in asm
}
// haleyjd 09/05/10: Removed unused CheckBossEnd Choco routine.
// haleyjd 09/05/10: [STRIFE] Removed:
// A_Hoof, A_Metal, A_BabyMetal, A_OpenShotgun2, A_LoadShotgun2,
// A_CloseShotgun2, A_BrainAwake, A_BrainPain, A_BrainScream, A_BrainExplode,
// A_BrainDie, A_BrainSpit, A_SpawnSound, A_SpawnFly
//
// A_ProgrammerDie
//
// villsa [STRIFE] new codepointer
// 09/08/10: Action routine for the Programmer's grisly death. Spawns the
// separate mechanical base object and sends it flying off in some random
// direction.
//
void A_ProgrammerDie(mobj_t* actor)
{
int r;
angle_t an;
mobj_t* mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z + (24*FRACUNIT), MT_PROGRAMMERBASE);
r = P_Random();
an = ((r - P_Random()) << 22) + actor->angle - ANG180;
mo->angle = an;
P_ThrustMobj(mo, an, mo->info->speed); // inlined in asm
mo->momz = P_Random() << 9;
}
//
// A_InqTossArm
//
// villsa [STRIFE] new codepointer
// 09/08/10: Inquisitor death action. Spawns an arm and tosses it.
//
void A_InqTossArm(mobj_t* actor)
{
int r;
angle_t an;
mobj_t* mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z + (24*FRACUNIT), MT_INQARM);
r = P_Random();
an = ((r - P_Random()) << 22) + actor->angle - ANG90;
mo->angle = an;
P_ThrustMobj(mo, an, mo->info->speed); // inlined in asm
mo->momz = P_Random() << 10;
}
//
// A_SpawnSpectreA
//
// villsa [STRIFE] new codepointer (unused)
// 09/08/10: Spawns Spectre A. Or would, if anything actually used this.
// This is evidence that the Programmer's spectre, which appears in the
// catacombs in the final version, was originally meant to be spawned
// after his death.
//
void A_SpawnSpectreA(mobj_t* actor)
{
mobj_t* mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SPECTRE_A);
mo->momz = P_Random() << 9;
}
//
// A_SpawnSpectreB
//
// villsa [STRIFE] new codepointer
// 09/08/10: Action function to spawn the Bishop's spectre.
//
void A_SpawnSpectreB(mobj_t* actor)
{
mobj_t* mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SPECTRE_B);
mo->momz = P_Random() << 9;
}
//
// A_SpawnSpectreC
//
// villsa [STRIFE] new codepointer (unused)
// 09/08/10: Action function to spawn the Oracle's spectre. Also
// unused, because the Oracle's spectre is already present on the
// map and is awakened on his death. Also left over from the
// unreleased beta (and demo) versions.
//
void A_SpawnSpectreC(mobj_t* actor)
{
mobj_t* mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SPECTRE_C);
mo->momz = P_Random() << 9;
}
//
// A_SpawnSpectreD
//
// villsa [STRIFE] new codepointer
// 09/08/10: Action function to spawn Macil's Spectre.
//
void A_SpawnSpectreD(mobj_t* actor)
{
mobj_t* mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SPECTRE_D);
mo->momz = P_Random() << 9;
}
//
// A_SpawnSpectreE
//
// villsa [STRIFE] new codepointer
// 09/08/10: Action function to spawn the Loremaster's Spectre.
//
void A_SpawnSpectreE(mobj_t* actor)
{
mobj_t* mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SPECTRE_E);
mo->momz = P_Random() << 9;
}
// [STRIFE] New statics - Remember the Entity's spawning position.
static fixed_t entity_pos_x = 0;
static fixed_t entity_pos_y = 0;
static fixed_t entity_pos_z = 0;
//
// A_SpawnEntity
//
// villsa [STRIFE] new codepointer
// 09/08/10: You will fall on your knees before the True God, the
// One Light.
//
void A_SpawnEntity(mobj_t* actor)
{
mobj_t* mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z + (70*FRACUNIT), MT_ENTITY);
mo->momz = (5*FRACUNIT);
entity_pos_x = mo->x;
entity_pos_y = mo->y;
entity_pos_z = mo->z;
}
//
// P_ThrustMobj
//
// villsa [STRIFE] new function
// Thrusts an thing in a specified force/direction
// Beware! This is inlined everywhere in the asm
//
void P_ThrustMobj(mobj_t *actor, angle_t angle, fixed_t force)
{
angle_t an = angle >> ANGLETOFINESHIFT;
actor->momx += FixedMul(finecosine[an], force);
actor->momy += FixedMul(finesine[an], force);
}
//
// A_EntityDeath
//
// [STRIFE]
// haleyjd 09/08/10: The death of the Entity's spectre brings forth
// three subentities, which are significantly less dangerous on their
// own but threatening together.
//
void A_EntityDeath(mobj_t* actor)
{
mobj_t *subentity;
angle_t an;
fixed_t dist;
dist = 2 * mobjinfo[MT_SUBENTITY].radius;
// Subentity One
an = actor->angle >> ANGLETOFINESHIFT;
subentity = P_SpawnMobj(FixedMul(finecosine[an], dist) + entity_pos_x,
FixedMul(finesine[an], dist) + entity_pos_y,
entity_pos_z, MT_SUBENTITY);
subentity->target = actor->target;
A_FaceTarget(subentity);
P_ThrustMobj(subentity, subentity->angle, 625 << 13);
// Subentity Two
an = (actor->angle + ANG90) >> ANGLETOFINESHIFT;
subentity = P_SpawnMobj(FixedMul(finecosine[an], dist) + entity_pos_x,
FixedMul(finesine[an], dist) + entity_pos_y,
entity_pos_z, MT_SUBENTITY);
subentity->target = actor->target;
P_ThrustMobj(subentity, actor->angle + ANG90, 4);
A_FaceTarget(subentity);
// Subentity Three
an = (actor->angle - ANG90) >> ANGLETOFINESHIFT;
subentity = P_SpawnMobj(FixedMul(finecosine[an], dist) + entity_pos_x,
FixedMul(finesine[an], dist) + entity_pos_y,
entity_pos_z, MT_SUBENTITY);
subentity->target = actor->target;
P_ThrustMobj(subentity, actor->angle - ANG90, 4);
A_FaceTarget(subentity);
}
//
// A_SpawnZombie
//
// villsa [STRIFE] new codepointer
//
void A_SpawnZombie(mobj_t* actor)
{
P_SpawnMobj(actor->x, actor->y, actor->z, MT_ZOMBIE);
}
//
// A_ZombieInSpecialSector
//
// villsa [STRIFE] new codepointer
//
void A_ZombieInSpecialSector(mobj_t* actor)
{
sector_t* sector;
fixed_t force;
angle_t angle;
int tagval;
sector = actor->subsector->sector;
if(actor->z != sector->floorheight) // [STRIFE] TODO - verify
return;
if(sector->special <= 15)
P_DamageMobj(actor, NULL, NULL, 999);
else if(sector->special == 18)
{
tagval = sector->tag - 100;
force = (tagval % 10) << 12;
angle = (tagval / 10) << 29;
P_ThrustMobj(actor, angle, force); // inlined in asm
}
}
//
// A_CrystalExplode
//
// villsa [STRIFE] new codepointer
//
void A_CrystalExplode(mobj_t* actor)
{
sector_t* sector;
mobj_t* rubble;
int i;
int r;
sector = actor->subsector->sector;
sector->lightlevel = 0;
sector->floorheight = P_FindLowestFloorSurrounding(sector);
// spawn rubble
for(i = 0; i < 8; i++)
{
rubble = P_SpawnMobj(actor->x, actor->y, actor->z, MT_RUBBLE1 + i);
r = P_Random();
rubble->momx = ((r & 0x0f) - (P_Random() & 7)) << FRACBITS;
r = P_Random();
rubble->momy = ((r & 7) - (P_Random() & 7)) << FRACBITS;
rubble->momz = ((P_Random() & 3) << FRACBITS) + (7*FRACUNIT);
}
}
// [STRIFE] New static global - buffer used for various player messages.
static char pmsgbuffer[80];
//
// P_FreePrisoners
//
// haleyjd 09/08/10: [STRIFE] New function
// * Called when the prisoners get freed, obviously. Gives a
// message and awards quest token 13.
//
void P_FreePrisoners(void)
{
int i;
DEH_snprintf(pmsgbuffer, sizeof(pmsgbuffer), "You've freed the prisoners!");
for(i = 0; i < MAXPLAYERS; i++)
{
P_GiveItemToPlayer(&players[i], SPR_TOKN, MT_TOKEN_QUEST13);
players[i].message = pmsgbuffer;
}
}
//
// P_DestroyConverter
//
// haleyjd 09/08/10: [STRIFE] New function
// * Called when the converter is shut down in the factory.
// Gives several items and a message.
//
void P_DestroyConverter(void)
{
int i;
DEH_snprintf(pmsgbuffer, sizeof(pmsgbuffer), "You've destroyed the Converter!");
for(i = 0; i < MAXPLAYERS; i++)
{
P_GiveItemToPlayer(&players[i], SPR_TOKN, MT_TOKEN_QUEST25);
P_GiveItemToPlayer(&players[i], SPR_TOKN, MT_TOKEN_STAMINA);
P_GiveItemToPlayer(&players[i], SPR_TOKN, MT_TOKEN_NEW_ACCURACY);
players[i].message = pmsgbuffer;
}
}
//
// A_QuestMsg
//
// villsa [STRIFE] new codepointer
// Displays text based on quest item's name
// Quest item is based on actor's speed
//
void A_QuestMsg(mobj_t* actor)
{
char* name;
int quest;
int i;
// get name
name = DEH_String(mobjinfo[(MT_TOKEN_QUEST1 - 1) + actor->info->speed].name);
strcpy(pmsgbuffer, name); // inlined in asm
// give quest and display message to players
for(i = 0; i < MAXPLAYERS; i++)
{
quest = 1 << ((actor->info->speed) - 1);
players[i].message = pmsgbuffer;
players[i].questflags |= quest;
}
}
//
// A_ExtraLightOff
//
// villsa [STRIFE] new codepointer
// 09/08/10: Called by the Power Crystal to turn off the extended
// flash of light caused by its explosion.
//
void A_ExtraLightOff(mobj_t* actor)
{
if(!actor->target)
return;
if(!actor->target->player)
return;
actor->target->player->extralight = 0;
}
//
// A_CrystalRadiusAtk
//
// villsa [STRIFE] new codepointer
// 09/08/10: Called by the power crystal when it dies.
//
void A_CrystalRadiusAtk(mobj_t* actor)
{
P_RadiusAttack(actor, actor->target, 512);
if(!(actor->target && actor->target->player))
return;
// set extralight to 5 for near full-bright
actor->target->player->extralight = 5;
}
//
// A_DeathExplode5
//
// villsa [STRIFE] new codepointer
//
void A_DeathExplode5(mobj_t* actor)
{
P_RadiusAttack(actor, actor->target, 192);
if(actor->target && actor->target->player)
P_NoiseAlert(actor->target, actor); // inlined in asm
}
//
// A_DeathExplode1
//
// villsa [STRIFE] new codepointer
//
void A_DeathExplode1(mobj_t* actor)
{
P_RadiusAttack(actor, actor->target, 128);
if(actor->target && actor->target->player)
P_NoiseAlert(actor->target, actor); // inlined in asm
}
//
// A_DeathExplode2
//
// villsa [STRIFE] new codepointer
//
void A_DeathExplode2(mobj_t* actor)
{
P_RadiusAttack(actor, actor->target, 64);
if(actor->target && actor->target->player)
P_NoiseAlert(actor->target, actor); // inlined in asm
}
//
// A_DeathExplode3
//
// villsa [STRIFE] new codepointer
//
void A_DeathExplode3(mobj_t* actor)
{
P_RadiusAttack(actor, actor->target, 32);
if(actor->target && actor->target->player)
P_NoiseAlert(actor->target, actor); // inlined in asm
}
//
// A_RaiseAlarm
//
// villsa [STRIFE] new codepointer
// 09/08/10: Set off the infamous alarm. This is just a noise alert.
//
void A_RaiseAlarm(mobj_t* actor)
{
if(actor->target && actor->target->player)
P_NoiseAlert(actor->target, actor); // inlined in asm
}
//
// A_MissileTick
// villsa [STRIFE] - new codepointer
//
void A_MissileTick(mobj_t* actor)
{
int r = actor->reactiontime--;
if(r - 1 <= 0)
{
P_ExplodeMissile(actor);
actor->flags &= ~MF_MISSILE;
}
}
//
// A_SpawnGrenadeFire
// villsa [STRIFE] - new codepointer
//
void A_SpawnGrenadeFire(mobj_t* actor)
{
P_SpawnMobj(actor->x, actor->y, actor->z, MT_PFLAME);
}
//
// A_NodeChunk
// villsa [STRIFE] - new codepointer
//
void A_NodeChunk(mobj_t* actor)
{
int r;
mobj_t* mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z + (10*FRACUNIT), MT_NODE);
r = P_Random();
mo->momx = ((r & 0x0f) - (P_Random() & 7)) << FRACBITS;
r = P_Random();
mo->momy = ((r & 7) - (P_Random() & 0x0f)) << FRACBITS;
mo->momz = (P_Random() & 0x0f) << FRACBITS;
}
//
// A_HeadChunk
// villsa [STRIFE] - new codepointer
//
void A_HeadChunk(mobj_t* actor)
{
int r;
mobj_t* mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z + (10*FRACUNIT), MT_SPECTREHEAD);
r = P_Random();
mo->momx = ((r & 7) - (P_Random() & 0x0f)) << FRACBITS;
r = P_Random();
mo->momy = ((r & 0x0f) - (P_Random() & 7)) << FRACBITS;
mo->momz = (P_Random() & 7) << FRACBITS;
}
//
// A_BurnSpread
// villsa [STRIFE] - new codepointer
//
void A_BurnSpread(mobj_t* actor)
{
int t;
mobj_t* mo;
fixed_t x;
fixed_t y;
actor->momz -= (8*FRACUNIT);
t = P_Random();
actor->momx += ((t & 3) - (P_Random() & 3)) << FRACBITS;
t = P_Random();
actor->momy += ((t & 3) - (P_Random() & 3)) << FRACBITS;
S_StartSound(actor, sfx_lgfire);
if(actor->flags & MF_DROPPED)
return; // not the parent
x = actor->x + (((P_Random() + 12) & 31) << FRACBITS);
y = actor->y + (((P_Random() + 12) & 31) << FRACBITS);
// spawn child
mo = P_SpawnMobj(x, y, actor->z + (4*FRACUNIT), MT_PFLAME);
t = P_Random();
mo->momx += ((t & 7) - (P_Random() & 7)) << FRACBITS;
t = P_Random();
mo->momy += ((t & 7) - (P_Random() & 7)) << FRACBITS;
mo->momz -= FRACUNIT;
mo->flags |= MF_DROPPED;
mo->reactiontime = (P_Random() & 3) + 2;
}
//
// A_BossDeath
//
// Possibly trigger special effects
// if on first boss level
//
// haleyjd 09/17/10: [STRIFE]
// * Modified to handle all Strife bosses.
//
void A_BossDeath (mobj_t* actor)
{
int i;
thinker_t *th;
line_t junk;
// only the following types can be a boss:
switch(actor->type)
{
case MT_CRUSADER:
case MT_SPECTRE_A:
case MT_SPECTRE_B:
case MT_SPECTRE_C:
case MT_SPECTRE_D:
case MT_SPECTRE_E:
case MT_SUBENTITY:
case MT_PROGRAMMER:
break;
default:
return;
}
// check for a living player
for(i = 0; i < MAXPLAYERS; i++)
{
if(playeringame[i] && players[i].health > 0)
break;
}
if(i == MAXPLAYERS)
return; // everybody's dead.
// check for a still living boss
for(th = thinkercap.next; th != &thinkercap; th = th->next)
{
if(th->function.acp1 == P_MobjThinker)
{
mobj_t *mo = (mobj_t *)th;
if(mo != actor && mo->type == actor->type && mo->health > 0)
return; // one is still alive.
}
}
// Victory!
switch(actor->type)
{
case MT_CRUSADER:
junk.tag = 667;
EV_DoFloor(&junk, lowerFloorToLowest);
break;
case MT_SPECTRE_A:
GiveVoiceObjective("VOC95", "LOG95", 0);
junk.tag = 999;
EV_DoFloor(&junk, lowerFloorToLowest);
break;
case MT_SPECTRE_B:
P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_BISHOP);
GiveVoiceObjective("VOC74", "LOG74", 0);
break;
case MT_SPECTRE_C:
// Look for an MT_ORACLE - this is for in case the player awakened the
// Oracle's spectre without killing the Oracle, which is possible by
// looking up to max and firing the Sigil at it. If this were not done,
// a serious sequence break possibility would arise where one could
// kill both the Oracle AND Macil, possibly throwing the game out of
// sorts entirely. Too bad they thought of it ;) However this also
// causes a bug sometimes! The Oracle, in its death state, sets the
// Spectre C back to its seestate. If the Spectre C is already dead,
// it becomes an undead ghost monster. Then it's a REAL spectre ;)
for(th = thinkercap.next; th != &thinkercap; th = th->next)
{
if(th->function.acp1 == P_MobjThinker)
{
mobj_t *mo = (mobj_t *)th;
// KILL ALL ORACLES! RAWWR!
if(mo != actor && mo->type == MT_ORACLE && mo->health > 0)
P_KillMobj(actor, mo);
}
}
P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_ORACLE);
// Bishop is dead? - verify.
if(players[0].questflags & QF_QUEST21)
P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_QUEST22);
// Macil is dead?
if(players[0].questflags & QF_QUEST24)
{
// Loremaster is dead?
if(players[0].questflags & QF_QUEST26)
{
// We wield the complete sigil, blahblah
GiveVoiceObjective("VOC85", "LOG85", 0);
}
}
else
{
// So much for prognostication.
GiveVoiceObjective("VOC87", "LOG87", 0);
}
junk.tag = 222; // Open the exit door again;
EV_DoDoor(&junk, open); // Note this is NOT the Loremaster door...
break;
case MT_SPECTRE_D:
P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_MACIL);
if(players[0].questflags & QF_QUEST25) // Destroyed converter?
GiveVoiceObjective("VOC106", "LOG106", 0);
else
GiveVoiceObjective("VOC79", "LOG79", 0);
break;
case MT_SPECTRE_E:
P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_LOREMASTER);
if(!netgame)
{
P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_STAMINA);
P_GiveItemToPlayer(&players[0], SPR_TOKN, MT_TOKEN_NEW_ACCURACY);
}
if(players[0].sigiltype == 4)
GiveVoiceObjective("VOC85", "LOG85", 0);
else
GiveVoiceObjective("VOC83", "LOG83", 0);
junk.tag = 666;
EV_DoFloor(&junk, lowerFloorToLowest);
break;
case MT_SUBENTITY:
F_StartFinale();
break;
case MT_PROGRAMMER:
F_StartFinale();
G_ExitLevel(0);
break;
default:
// Real classy, Rogue.
if(actor->type)
I_Error("Error: Unconnected BossDeath id %d", actor->type);
break;
}
}
//
// A_AcolyteSpecial
// villsa [STRIFE] - new codepointer
//
void A_AcolyteSpecial(mobj_t* actor)
{
int i;
thinker_t* th;
if(actor->type != MT_GUARD8)
return; // must be MT_GUARD8
for(i = 0; i < MAXPLAYERS; i++)
{
if(playeringame[i] && &players[i].health > 0)
break;
}
if(i == 8)
return;
for(th = thinkercap.next; th != &thinkercap; th = th->next)
{
if(th->function.acp1 == P_MobjThinker)
{
mobj_t *mo = (mobj_t *)th;
if(mo != actor && mo->type == actor->type && mo->health > 0)
return;
if(mo->type != MT_GUARD8)
continue; // not MT_GUARD8
// give quest token #7 to all players
for(i = 0; i < MAXPLAYERS; i++)
P_GiveItemToPlayer(&players[i], SPR_TOKN, MT_TOKEN_QUEST7);
// play voice, give objective
GiveVoiceObjective("VOC14", "LOG14", 0);
}
}
}
//
// A_InqChase
// villsa [STRIFE] - new codepointer
//
void A_InqChase(mobj_t* actor)
{
S_StartSound(actor, sfx_inqact);
A_Chase(actor);
}
//
// A_StalkerChase
// villsa [STRIFE] - new codepointer
//
void A_StalkerChase(mobj_t* actor)
{
S_StartSound(actor, sfx_spdwlk);
A_Chase(actor);
}
//
// A_PlayerScream
//
// [STRIFE]
// * Modified to eliminate gamemode check and to use Strife sound.
//
void A_PlayerScream (mobj_t* mo)
{
// Default death sound.
int sound = sfx_pldeth;
// villsa [STRIFE] don't check for gamemode
if(mo->health < -50)
{
// IF THE PLAYER DIES
// LESS THAN -50% WITHOUT GIBBING
sound = sfx_plxdth; // villsa [STRIFE] different sound
}
S_StartSound (mo, sound);
}
//
// A_TeleportBeacon
// villsa [STRIFE] - new codepointer
//
void A_TeleportBeacon(mobj_t* actor)
{
mobj_t* mobj;
mobj_t* fog;
fixed_t fog_x;
fixed_t fog_y;
if(actor->target != players[actor->miscdata].mo)
actor->target = players[actor->miscdata].mo;
mobj = P_SpawnMobj(actor->x, actor->y, ONFLOORZ, MT_REBEL1);
if(!P_TryMove(mobj, mobj->x, mobj->y))
{
// Rebel is probably stuck in something.. too bad
P_RemoveMobj(mobj);
return;
}
// beacon no longer solid
actor->flags &= ~MF_SOLID;
// set color and flags
mobj->flags |= ((actor->miscdata << MF_TRANSSHIFT) | MF_INCOMBAT);
mobj->target = NULL;
// double Rebel's health in deathmatch mode
if(deathmatch)
mobj->health <<= 1;
if(actor->target)
{
mobj_t* targ = actor->target->target;
if(targ)
{
if(targ->type != MT_REBEL1 || targ->miscdata != mobj->miscdata)
mobj->target = targ;
}
}
P_SetMobjState(mobj, mobj->info->seestate);
mobj->angle = actor->angle;
fog_x = mobj->x + FixedMul(20*FRACUNIT, finecosine[actor->angle>>ANGLETOFINESHIFT]);
fog_y = mobj->y + FixedMul(20*FRACUNIT, finesine[actor->angle>>ANGLETOFINESHIFT]);
fog = P_SpawnMobj(fog_x, fog_y, mobj->z, MT_TFOG);
S_StartSound(fog, sfx_telept);
if(--actor->health < 0)
P_RemoveMobj(actor);
}
//
// A_BodyParts
//
// villsa [STRIFE] new codepointer
// 09/06/10: Spawns gibs when organic actors get splattered, or junk
// when robots explode.
//
void A_BodyParts(mobj_t* actor)
{
mobjtype_t type;
mobj_t* mo;
angle_t an;
if(actor->flags & MF_NOBLOOD) // Robots are flagged NOBLOOD
type = MT_JUNK;
else
type = MT_MEAT;
mo = P_SpawnMobj(actor->x, actor->y, actor->z + (24*FRACUNIT), type);
P_SetMobjState(mo, mo->info->spawnstate + (P_Random() % 19));
an = (P_Random() << 13) / 255;
mo->angle = an << ANGLETOFINESHIFT;
mo->momx += FixedMul(finecosine[an], (P_Random() & 0x0f) << FRACBITS);
mo->momy += FixedMul(finesine[an], (P_Random() & 0x0f) << FRACBITS);
mo->momz += (P_Random() & 0x0f) << FRACBITS;
}
//
// A_ClaxonBlare
//
// [STRIFE] New function
// haleyjd 09/08/10: The ever-dreadful Strife alarm!
//
void A_ClaxonBlare(mobj_t* actor)
{
// Timer ran down?
if(--actor->reactiontime < 0)
{
// reset to initial state
actor->target = NULL;
actor->reactiontime = actor->info->reactiontime;
// listen for more noise
A_Listen(actor);
// If we heard something, stay on for a while,
// otherwise return to spawnstate.
if(actor->target)
actor->reactiontime = 50;
else
P_SetMobjState(actor, actor->info->spawnstate);
}
// When almost ran down, clear the soundtarget so it doesn't
// retrigger the alarm.
// Also, play the harsh, grating claxon.
if(actor->reactiontime == 2)
actor->subsector->sector->soundtarget = NULL;
else if(actor->reactiontime > 50)
S_StartSound(actor, sfx_alarm);
}
//
// A_ActiveSound
//
// villsa [STRIFE] new codepointer
// 09/06/10: Plays an object's active sound periodically.
//
void A_ActiveSound(mobj_t* actor)
{
if(actor->info->activesound)
{
if(!(leveltime & 7)) // haleyjd: added parens
S_StartSound(actor, actor->info->activesound);
}
}
//
// A_ClearSoundTarget
//
// villsa [STRIFE] new codepointer
// 09/06/10: Clears the actor's sector soundtarget, so that the actor
// will not be continually alerted/awakened ad infinitum. Used by
// shopkeepers.
//
void A_ClearSoundTarget(mobj_t* actor)
{
actor->subsector->sector->soundtarget = NULL;
}
//
// A_DropBurnFlesh
//
// villsa [STRIFE] new codepointer
//
void A_DropBurnFlesh(mobj_t* actor)
{
mobj_t* mo;
mobjtype_t type;
type = actor->type;
mo = P_SpawnMobj(actor->x, actor->y, actor->z + (24*FRACUNIT), MT_BURNDROP);
mo->momz = -FRACUNIT;
actor->type = MT_SFIREBALL;
P_RadiusAttack(actor, actor, 64);
actor->type = type;
}
//
// A_FlameDeath
//
// villsa [STRIFE] new codepointer
// 09/06/10: Death animation for flamethrower fireballs.
//
void A_FlameDeath(mobj_t* actor)
{
actor->flags |= MF_NOGRAVITY;
actor->momz = (P_Random() & 3) << FRACBITS;
}
//
// A_ClearForceField
//
// villsa [STRIFE] new codepointer
// check for all matching lines in the sector
// and disable blocking/midtextures
//
void A_ClearForceField(mobj_t* actor)
{
int i;
sector_t *sec;
line_t *secline;
actor->flags &= ~(MF_SOLID|MF_SPECIAL);
sec = actor->subsector->sector;
if(!sec->linecount)
return;
for(i = 0; i < sec->linecount; i++)
{
secline = sec->lines[i];
if(!(secline->flags & ML_TWOSIDED))
continue;
if(secline->special != 148)
continue;
secline->flags &= ~ML_BLOCKING;
secline->special = 0;
sides[secline->sidenum[0]].midtexture = 0;
sides[secline->sidenum[1]].midtexture = 0;
}
}