ref: 0a94c0f99d6c34b30d2ac6fd7d5e516bd14dffb5
dir: /src/hexen/p_enemy.c/
// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// Copyright(C) 1993-1996 Id Software, Inc.
// Copyright(C) 1993-2008 Raven Software
// Copyright(C) 2008 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.
//
//-----------------------------------------------------------------------------
#include "h2def.h"
#include "m_random.h"
#include "i_system.h"
#include "i_swap.h"
#include "p_local.h"
#include "s_sound.h"
// Macros
// Types
// Private Data
// External Data
extern fixed_t FloatBobOffsets[64];
//----------------------------------------------------------------------------
//
// PROC P_RecursiveSound
//
//----------------------------------------------------------------------------
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)
{ // Already flooded
return;
}
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)
{ // Closed door
continue;
}
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);
}
}
}
//----------------------------------------------------------------------------
//
// PROC 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);
}
//----------------------------------------------------------------------------
//
// FUNC P_CheckMeleeRange
//
//----------------------------------------------------------------------------
boolean P_CheckMeleeRange(mobj_t * actor)
{
mobj_t *mo;
fixed_t dist;
if (!actor->target)
{
return (false);
}
mo = actor->target;
dist = P_AproxDistance(mo->x - actor->x, mo->y - actor->y);
if (dist >= MELEERANGE)
{
return (false);
}
if (!P_CheckSight(actor, mo))
{
return (false);
}
if (mo->z > actor->z + actor->height)
{ // Target is higher than the attacker
return (false);
}
else if (actor->z > mo->z + mo->height)
{ // Attacker is higher
return (false);
}
return (true);
}
//----------------------------------------------------------------------------
//
// FUNC P_CheckMeleeRange2
//
//----------------------------------------------------------------------------
boolean P_CheckMeleeRange2(mobj_t * actor)
{
mobj_t *mo;
fixed_t dist;
if (!actor->target)
{
return (false);
}
mo = actor->target;
dist = P_AproxDistance(mo->x - actor->x, mo->y - actor->y);
if (dist >= MELEERANGE * 2 || dist < MELEERANGE)
{
return (false);
}
if (!P_CheckSight(actor, mo))
{
return (false);
}
if (mo->z > actor->z + actor->height)
{ // Target is higher than the attacker
return (false);
}
else if (actor->z > mo->z + mo->height)
{ // Attacker is higher
return (false);
}
return (true);
}
//----------------------------------------------------------------------------
//
// FUNC P_CheckMissileRange
//
//----------------------------------------------------------------------------
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)
{ // Don't attack yet
return (false);
}
dist = (P_AproxDistance(actor->x - actor->target->x,
actor->y - actor->target->y) >> FRACBITS) - 64;
if (!actor->info->meleestate)
{ // No melee attack, so fire more frequently
dist -= 128;
}
if (dist > 200)
{
dist = 200;
}
if (P_Random() < dist)
{
return (false);
}
return (true);
}
/*
================
=
= P_Move
=
= Move in the current direction
= returns false if the move is blocked
================
*/
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, tryy;
line_t *ld;
boolean good;
if (actor->flags2 & MF2_BLASTED)
return (true);
if (actor->movedir == DI_NODIR)
{
return (false);
}
tryx = actor->x + actor->info->speed * xspeed[actor->movedir];
tryy = actor->y + actor->info->speed * yspeed[actor->movedir];
if (!P_TryMove(actor, tryx, tryy))
{ // 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 isn't a door that can be opened, return false
if (P_ActivateLine(ld, actor, 0, SPAC_USE))
{
good = true;
}
/* Old version before use/cross/impact specials were combined
if(P_UseSpecialLine(actor, ld))
{
good = true;
}
*/
}
return (good);
}
else
{
actor->flags &= ~MF_INFLOAT;
}
if (!(actor->flags & MF_FLOAT))
{
if (actor->z > actor->floorz)
{
P_HitFloor(actor);
}
actor->z = actor->floorz;
}
return (true);
}
//----------------------------------------------------------------------------
//
// FUNC P_TryWalk
//
// Attempts to move actor in its current (ob->moveangle) direction.
// If blocked by either a wall or an actor returns FALSE.
// If move is either clear of block only by a door, returns TRUE and sets.
// If a door is in the way, an OpenDoor call is made to start it opening.
//
//----------------------------------------------------------------------------
boolean P_TryWalk(mobj_t * actor)
{
if (!P_Move(actor))
{
return (false);
}
actor->movecount = P_Random() & 15;
return (true);
}
/*
================
=
= P_NewChaseDir
=
================
*/
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 P_NewChaseDir(mobj_t * actor)
{
fixed_t deltax, deltay;
dirtype_t d[3];
dirtype_t tdir, olddir, turnaround;
if (!actor->target)
I_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 || 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))
return; /*either moved forward or attacked */
}
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;
}
if (P_Random() & 1) /*randomly determine direction of search */
{
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't move
}
//---------------------------------------------------------------------------
//
// FUNC P_LookForMonsters
//
//---------------------------------------------------------------------------
#define MONS_LOOK_RANGE (16*64*FRACUNIT)
#define MONS_LOOK_LIMIT 64
boolean P_LookForMonsters(mobj_t * actor)
{
int count;
mobj_t *mo;
thinker_t *think;
if (!P_CheckSight(players[0].mo, actor))
{ // Player can't see monster
return (false);
}
count = 0;
for (think = thinkercap.next; think != &thinkercap; think = think->next)
{
if (think->function != P_MobjThinker)
{ // Not a mobj thinker
continue;
}
mo = (mobj_t *) think;
if (!(mo->flags & MF_COUNTKILL) || (mo == actor) || (mo->health <= 0))
{ // Not a valid monster
continue;
}
if (P_AproxDistance(actor->x - mo->x, actor->y - mo->y)
> MONS_LOOK_RANGE)
{ // Out of range
continue;
}
if (P_Random() < 16)
{ // Skip
continue;
}
if (count++ > MONS_LOOK_LIMIT)
{ // Stop searching
return (false);
}
if (!P_CheckSight(actor, mo))
{ // Out of sight
continue;
}
if (actor->type == MT_MINOTAUR)
{
if ((mo->type == MT_MINOTAUR) &&
(mo->target != actor->special1.p->mo))
{
continue;
}
}
// Found a target monster
actor->target = mo;
return (true);
}
return (false);
}
/*
================
=
= P_LookForPlayers
=
= If allaround is false, only look 180 degrees in front
= returns true if a player is targeted
================
*/
boolean P_LookForPlayers(mobj_t * actor, boolean allaround)
{
int c;
int stop;
player_t *player;
angle_t an;
fixed_t dist;
if (!netgame && players[0].health <= 0)
{ // Single player game and player is dead, look for monsters
return (P_LookForMonsters(actor));
}
c = 0;
// NOTE: This behavior has been changed from the Vanilla behavior, where
// an infinite loop can occur if players 0-3 all quit the game. Although
// technically this is not what Vanilla does, fixing this is highly
// desirable, and having the game simply lock up is not acceptable.
// stop = (actor->lastlook - 1) & 3;
// for (;; actor->lastlook = (actor->lastlook + 1) & 3)
stop = (actor->lastlook + MAXPLAYERS - 1) % MAXPLAYERS;
for (;; actor->lastlook = (actor->lastlook + 1) % MAXPLAYERS)
{
if (!playeringame[actor->lastlook])
continue;
if (c++ == 2 || actor->lastlook == stop)
return false; // done looking
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
}
}
if (player->mo->flags & MF_SHADOW)
{ // Player is invisible
if ((P_AproxDistance(player->mo->x - actor->x,
player->mo->y - actor->y) > 2 * MELEERANGE)
&& P_AproxDistance(player->mo->momx, player->mo->momy)
< 5 * FRACUNIT)
{ // Player is sneaking - can't detect
return (false);
}
if (P_Random() < 225)
{ // Player isn't sneaking, but still didn't detect
return (false);
}
}
if (actor->type == MT_MINOTAUR)
{
if (actor->special1.p == player)
{
continue; // Don't target master
}
}
actor->target = player->mo;
return (true);
}
return (false);
}
/*
===============================================================================
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;
sound = actor->info->seesound;
if (actor->flags2 & MF2_BOSS)
{ // Full volume
S_StartSound(NULL, 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)
{
actor->threshold--;
}
if (gameskill == sk_nightmare)
{ // Monsters move faster in nightmare mode
actor->tics -= actor->tics / 2;
if (actor->tics < 3)
{
actor->tics = 3;
}
}
//
// 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))
{ // got a new target
return;
}
P_SetMobjState(actor, actor->info->spawnstate);
return;
}
//
// don't attack twice in a row
//
if (actor->flags & MF_JUSTATTACKED)
{
actor->flags &= ~MF_JUSTATTACKED;
if (gameskill != sk_nightmare)
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 && 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)
{
if (actor->type == MT_BISHOP && P_Random() < 128)
{
S_StartSound(actor, actor->info->seesound);
}
else if (actor->type == MT_PIG)
{
S_StartSound(actor, SFX_PIG_ACTIVE1 + (P_Random() & 1));
}
else if (actor->flags2 & MF2_BOSS)
{
S_StartSound(NULL, actor->info->activesound);
}
else
{
S_StartSound(actor, actor->info->activesound);
}
}
}
//----------------------------------------------------------------------------
//
// PROC 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)
{ // Target is a ghost
actor->angle += (P_Random() - P_Random()) << 21;
}
}
//----------------------------------------------------------------------------
//
// PROC A_Pain
//
//----------------------------------------------------------------------------
void A_Pain(mobj_t * actor)
{
if (actor->info->painsound)
{
S_StartSound(actor, actor->info->painsound);
}
}
//============================================================================
//
// A_SetInvulnerable
//
//============================================================================
void A_SetInvulnerable(mobj_t * actor)
{
actor->flags2 |= MF2_INVULNERABLE;
}
//============================================================================
//
// A_UnSetInvulnerable
//
//============================================================================
void A_UnSetInvulnerable(mobj_t * actor)
{
actor->flags2 &= ~MF2_INVULNERABLE;
}
//============================================================================
//
// A_SetReflective
//
//============================================================================
void A_SetReflective(mobj_t * actor)
{
actor->flags2 |= MF2_REFLECTIVE;
if ((actor->type == MT_CENTAUR) || (actor->type == MT_CENTAURLEADER))
{
A_SetInvulnerable(actor);
}
}
//============================================================================
//
// A_UnSetReflective
//
//============================================================================
void A_UnSetReflective(mobj_t * actor)
{
actor->flags2 &= ~MF2_REFLECTIVE;
if ((actor->type == MT_CENTAUR) || (actor->type == MT_CENTAURLEADER))
{
A_UnSetInvulnerable(actor);
}
}
//----------------------------------------------------------------------------
//
// FUNC P_UpdateMorphedMonster
//
// Returns true if the pig morphs.
//
//----------------------------------------------------------------------------
boolean P_UpdateMorphedMonster(mobj_t * actor, int tics)
{
mobj_t *fog;
fixed_t x;
fixed_t y;
fixed_t z;
mobjtype_t moType;
mobj_t *mo;
mobj_t oldMonster;
actor->special1.i -= tics;
if (actor->special1.i > 0)
{
return (false);
}
moType = actor->special2.i;
switch (moType)
{
case MT_WRAITHB: // These must remain morphed
case MT_SERPENT:
case MT_SERPENTLEADER:
case MT_MINOTAUR:
return (false);
default:
break;
}
x = actor->x;
y = actor->y;
z = actor->z;
oldMonster = *actor; // Save pig vars
P_RemoveMobjFromTIDList(actor);
P_SetMobjState(actor, S_FREETARGMOBJ);
mo = P_SpawnMobj(x, y, z, moType);
if (P_TestMobjLocation(mo) == false)
{ // Didn't fit
P_RemoveMobj(mo);
mo = P_SpawnMobj(x, y, z, oldMonster.type);
mo->angle = oldMonster.angle;
mo->flags = oldMonster.flags;
mo->health = oldMonster.health;
mo->target = oldMonster.target;
mo->special = oldMonster.special;
mo->special1.i = 5 * 35; // Next try in 5 seconds
mo->special2.i = moType;
mo->tid = oldMonster.tid;
memcpy(mo->args, oldMonster.args, 5);
P_InsertMobjIntoTIDList(mo, oldMonster.tid);
return (false);
}
mo->angle = oldMonster.angle;
mo->target = oldMonster.target;
mo->tid = oldMonster.tid;
mo->special = oldMonster.special;
memcpy(mo->args, oldMonster.args, 5);
P_InsertMobjIntoTIDList(mo, oldMonster.tid);
fog = P_SpawnMobj(x, y, z + TELEFOGHEIGHT, MT_TFOG);
S_StartSound(fog, SFX_TELEPORT);
return (true);
}
//----------------------------------------------------------------------------
//
// PROC A_PigLook
//
//----------------------------------------------------------------------------
void A_PigLook(mobj_t * actor)
{
if (P_UpdateMorphedMonster(actor, 10))
{
return;
}
A_Look(actor);
}
//----------------------------------------------------------------------------
//
// PROC A_PigChase
//
//----------------------------------------------------------------------------
void A_PigChase(mobj_t * actor)
{
if (P_UpdateMorphedMonster(actor, 3))
{
return;
}
A_Chase(actor);
}
//============================================================================
//
// A_PigAttack
//
//============================================================================
void A_PigAttack(mobj_t * actor)
{
if (P_UpdateMorphedMonster(actor, 18))
{
return;
}
if (!actor->target)
{
return;
}
if (P_CheckMeleeRange(actor))
{
P_DamageMobj(actor->target, actor, actor, 2 + (P_Random() & 1));
S_StartSound(actor, SFX_PIG_ATTACK);
}
}
//============================================================================
//
// A_PigPain
//
//============================================================================
void A_PigPain(mobj_t * actor)
{
A_Pain(actor);
if (actor->z <= actor->floorz)
{
actor->momz = 3.5 * FRACUNIT;
}
}
void FaceMovementDirection(mobj_t * actor)
{
switch (actor->movedir)
{
case DI_EAST:
actor->angle = 0 << 24;
break;
case DI_NORTHEAST:
actor->angle = 32 << 24;
break;
case DI_NORTH:
actor->angle = 64 << 24;
break;
case DI_NORTHWEST:
actor->angle = 96 << 24;
break;
case DI_WEST:
actor->angle = 128 << 24;
break;
case DI_SOUTHWEST:
actor->angle = 160 << 24;
break;
case DI_SOUTH:
actor->angle = 192 << 24;
break;
case DI_SOUTHEAST:
actor->angle = 224 << 24;
break;
}
}
//----------------------------------------------------------------------------
//
// Minotaur variables
//
// special1 pointer to player that spawned it (mobj_t)
// special2 internal to minotaur AI
// args[0] args[0]-args[3] together make up minotaur start time
// args[1] |
// args[2] |
// args[3] V
// args[4] charge duration countdown
//----------------------------------------------------------------------------
void A_MinotaurFade0(mobj_t * actor)
{
actor->flags &= ~MF_ALTSHADOW;
actor->flags |= MF_SHADOW;
}
void A_MinotaurFade1(mobj_t * actor)
{
// Second level of transparency
actor->flags &= ~MF_SHADOW;
actor->flags |= MF_ALTSHADOW;
}
void A_MinotaurFade2(mobj_t * actor)
{
// Make fully visible
actor->flags &= ~MF_SHADOW;
actor->flags &= ~MF_ALTSHADOW;
}
//----------------------------------------------------------------------------
//
// A_MinotaurRoam -
//
//
//----------------------------------------------------------------------------
void A_MinotaurLook(mobj_t * actor);
void A_MinotaurRoam(mobj_t * actor)
{
unsigned int *starttime = (unsigned int *) actor->args;
actor->flags &= ~MF_SHADOW; // In case pain caused him to
actor->flags &= ~MF_ALTSHADOW; // skip his fade in.
if ((leveltime - *starttime) >= MAULATORTICS)
{
P_DamageMobj(actor, NULL, NULL, 10000);
return;
}
if (P_Random() < 30)
A_MinotaurLook(actor); // adjust to closest target
if (P_Random() < 6)
{
//Choose new direction
actor->movedir = P_Random() % 8;
FaceMovementDirection(actor);
}
if (!P_Move(actor))
{
// Turn
if (P_Random() & 1)
actor->movedir = (actor->movedir + 1) % 8;
else
actor->movedir = (actor->movedir + 7) % 8;
FaceMovementDirection(actor);
}
}
//----------------------------------------------------------------------------
//
// PROC A_MinotaurLook
//
// Look for enemy of player
//----------------------------------------------------------------------------
#define MINOTAUR_LOOK_DIST (16*54*FRACUNIT)
void A_MinotaurLook(mobj_t * actor)
{
mobj_t *mo = NULL;
player_t *player;
thinker_t *think;
fixed_t dist;
int i;
mobj_t *master = actor->special1.m;
actor->target = NULL;
if (deathmatch) // Quick search for players
{
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
continue;
player = &players[i];
mo = player->mo;
if (mo == master)
continue;
if (mo->health <= 0)
continue;
dist = P_AproxDistance(actor->x - mo->x, actor->y - mo->y);
if (dist > MINOTAUR_LOOK_DIST)
continue;
actor->target = mo;
break;
}
}
if (!actor->target) // Near player monster search
{
if (master && (master->health > 0) && (master->player))
mo = P_RoughMonsterSearch(master, 20);
else
mo = P_RoughMonsterSearch(actor, 20);
actor->target = mo;
}
if (!actor->target) // Normal monster search
{
for (think = thinkercap.next; think != &thinkercap;
think = think->next)
{
if (think->function != P_MobjThinker)
continue;
mo = (mobj_t *) think;
if (!(mo->flags & MF_COUNTKILL))
continue;
if (mo->health <= 0)
continue;
if (!(mo->flags & MF_SHOOTABLE))
continue;
dist = P_AproxDistance(actor->x - mo->x, actor->y - mo->y);
if (dist > MINOTAUR_LOOK_DIST)
continue;
if ((mo == master) || (mo == actor))
continue;
if ((mo->type == MT_MINOTAUR) &&
(mo->special1.m == actor->special1.m))
continue;
actor->target = mo;
break; // Found mobj to attack
}
}
if (actor->target)
{
P_SetMobjStateNF(actor, S_MNTR_WALK1);
}
else
{
P_SetMobjStateNF(actor, S_MNTR_ROAM1);
}
}
void A_MinotaurChase(mobj_t * actor)
{
unsigned int *starttime = (unsigned int *) actor->args;
actor->flags &= ~MF_SHADOW; // In case pain caused him to
actor->flags &= ~MF_ALTSHADOW; // skip his fade in.
if ((leveltime - *starttime) >= MAULATORTICS)
{
P_DamageMobj(actor, NULL, NULL, 10000);
return;
}
if (P_Random() < 30)
A_MinotaurLook(actor); // adjust to closest target
if (!actor->target || (actor->target->health <= 0) ||
!(actor->target->flags & MF_SHOOTABLE))
{ // look for a new target
P_SetMobjState(actor, S_MNTR_LOOK1);
return;
}
FaceMovementDirection(actor);
actor->reactiontime = 0;
// 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;
}
// Missile attack
if (actor->info->missilestate && P_CheckMissileRange(actor))
{
P_SetMobjState(actor, actor->info->missilestate);
return;
}
// chase towards target
if (!P_Move(actor))
{
P_NewChaseDir(actor);
}
// Active sound
if (actor->info->activesound && P_Random() < 6)
{
S_StartSound(actor, actor->info->activesound);
}
}
//----------------------------------------------------------------------------
//
// PROC A_MinotaurAtk1
//
// Melee attack.
//
//----------------------------------------------------------------------------
void A_MinotaurAtk1(mobj_t * actor)
{
if (!actor->target)
return;
S_StartSound(actor, SFX_MAULATOR_HAMMER_SWING);
if (P_CheckMeleeRange(actor))
{
P_DamageMobj(actor->target, actor, actor, HITDICE(4));
}
}
//----------------------------------------------------------------------------
//
// PROC A_MinotaurDecide
//
// Choose a missile attack.
//
//----------------------------------------------------------------------------
#define MNTR_CHARGE_SPEED (23*FRACUNIT)
void A_MinotaurDecide(mobj_t * actor)
{
angle_t angle;
mobj_t *target = actor->target;
int dist;
if (!target)
return;
dist = P_AproxDistance(actor->x - target->x, actor->y - target->y);
if (target->z + target->height > actor->z
&& target->z + target->height < actor->z + actor->height
&& dist < 16 * 64 * FRACUNIT
&& dist > 1 * 64 * FRACUNIT && P_Random() < 230)
{ // Charge attack
// Don't call the state function right away
P_SetMobjStateNF(actor, S_MNTR_ATK4_1);
actor->flags |= MF_SKULLFLY;
A_FaceTarget(actor);
angle = actor->angle >> ANGLETOFINESHIFT;
actor->momx = FixedMul(MNTR_CHARGE_SPEED, finecosine[angle]);
actor->momy = FixedMul(MNTR_CHARGE_SPEED, finesine[angle]);
actor->args[4] = 35 / 2; // Charge duration
}
else if (target->z == target->floorz
&& dist < 9 * 64 * FRACUNIT && P_Random() < 100)
{ // Floor fire attack
P_SetMobjState(actor, S_MNTR_ATK3_1);
actor->special2.i = 0;
}
else
{ // Swing attack
A_FaceTarget(actor);
// Don't need to call P_SetMobjState because the current state
// falls through to the swing attack
}
}
//----------------------------------------------------------------------------
//
// PROC A_MinotaurCharge
//
//----------------------------------------------------------------------------
void A_MinotaurCharge(mobj_t * actor)
{
mobj_t *puff;
if (!actor->target)
return;
if (actor->args[4] > 0)
{
puff = P_SpawnMobj(actor->x, actor->y, actor->z, MT_PUNCHPUFF);
puff->momz = 2 * FRACUNIT;
actor->args[4]--;
}
else
{
actor->flags &= ~MF_SKULLFLY;
P_SetMobjState(actor, actor->info->seestate);
}
}
//----------------------------------------------------------------------------
//
// PROC A_MinotaurAtk2
//
// Swing attack.
//
//----------------------------------------------------------------------------
void A_MinotaurAtk2(mobj_t * actor)
{
mobj_t *mo;
angle_t angle;
fixed_t momz;
if (!actor->target)
return;
S_StartSound(actor, SFX_MAULATOR_HAMMER_SWING);
if (P_CheckMeleeRange(actor))
{
P_DamageMobj(actor->target, actor, actor, HITDICE(3));
return;
}
mo = P_SpawnMissile(actor, actor->target, MT_MNTRFX1);
if (mo)
{
//S_StartSound(mo, sfx_minat2);
momz = mo->momz;
angle = mo->angle;
P_SpawnMissileAngle(actor, MT_MNTRFX1, angle - (ANG45 / 8), momz);
P_SpawnMissileAngle(actor, MT_MNTRFX1, angle + (ANG45 / 8), momz);
P_SpawnMissileAngle(actor, MT_MNTRFX1, angle - (ANG45 / 16), momz);
P_SpawnMissileAngle(actor, MT_MNTRFX1, angle + (ANG45 / 16), momz);
}
}
//----------------------------------------------------------------------------
//
// PROC A_MinotaurAtk3
//
// Floor fire attack.
//
//----------------------------------------------------------------------------
void A_MinotaurAtk3(mobj_t * actor)
{
mobj_t *mo;
player_t *player;
if (!actor->target)
{
return;
}
if (P_CheckMeleeRange(actor))
{
P_DamageMobj(actor->target, actor, actor, HITDICE(3));
if ((player = actor->target->player) != NULL)
{ // Squish the player
player->deltaviewheight = -16 * FRACUNIT;
}
}
else
{
mo = P_SpawnMissile(actor, actor->target, MT_MNTRFX2);
if (mo != NULL)
{
S_StartSound(mo, SFX_MAULATOR_HAMMER_HIT);
}
}
if (P_Random() < 192 && actor->special2.i == 0)
{
P_SetMobjState(actor, S_MNTR_ATK3_4);
actor->special2.i = 1;
}
}
//----------------------------------------------------------------------------
//
// PROC A_MntrFloorFire
//
//----------------------------------------------------------------------------
void A_MntrFloorFire(mobj_t * actor)
{
mobj_t *mo;
actor->z = actor->floorz;
mo = P_SpawnMobj(actor->x + ((P_Random() - P_Random()) << 10),
actor->y + ((P_Random() - P_Random()) << 10), ONFLOORZ,
MT_MNTRFX3);
mo->target = actor->target;
mo->momx = 1; // Force block checking
P_CheckMissileSpawn(mo);
}
//----------------------------------------------------------------------------
//
// PROC A_Scream
//
//----------------------------------------------------------------------------
void A_Scream(mobj_t * actor)
{
int sound;
S_StopSound(actor);
if (actor->player)
{
if (actor->player->morphTics)
{
S_StartSound(actor, actor->info->deathsound);
}
else
{
// Handle the different player death screams
if (actor->momz <= -39 * FRACUNIT)
{ // Falling splat
sound = SFX_PLAYER_FALLING_SPLAT;
}
else if (actor->health > -50)
{ // Normal death sound
switch (actor->player->class)
{
case PCLASS_FIGHTER:
sound = SFX_PLAYER_FIGHTER_NORMAL_DEATH;
break;
case PCLASS_CLERIC:
sound = SFX_PLAYER_CLERIC_NORMAL_DEATH;
break;
case PCLASS_MAGE:
sound = SFX_PLAYER_MAGE_NORMAL_DEATH;
break;
default:
sound = SFX_NONE;
break;
}
}
else if (actor->health > -100)
{ // Crazy death sound
switch (actor->player->class)
{
case PCLASS_FIGHTER:
sound = SFX_PLAYER_FIGHTER_CRAZY_DEATH;
break;
case PCLASS_CLERIC:
sound = SFX_PLAYER_CLERIC_CRAZY_DEATH;
break;
case PCLASS_MAGE:
sound = SFX_PLAYER_MAGE_CRAZY_DEATH;
break;
default:
sound = SFX_NONE;
break;
}
}
else
{ // Extreme death sound
switch (actor->player->class)
{
case PCLASS_FIGHTER:
sound = SFX_PLAYER_FIGHTER_EXTREME1_DEATH;
break;
case PCLASS_CLERIC:
sound = SFX_PLAYER_CLERIC_EXTREME1_DEATH;
break;
case PCLASS_MAGE:
sound = SFX_PLAYER_MAGE_EXTREME1_DEATH;
break;
default:
sound = SFX_NONE;
break;
}
sound += P_Random() % 3; // Three different extreme deaths
}
S_StartSound(actor, sound);
}
}
else
{
S_StartSound(actor, actor->info->deathsound);
}
}
//---------------------------------------------------------------------------
//
// PROC P_DropItem
//
//---------------------------------------------------------------------------
/*
void P_DropItem(mobj_t *source, mobjtype_t type, int special, int chance)
{
mobj_t *mo;
if(P_Random() > chance)
{
return;
}
mo = P_SpawnMobj(source->x, source->y,
source->z+(source->height>>1), type);
mo->momx = (P_Random()-P_Random())<<8;
mo->momy = (P_Random()-P_Random())<<8;
mo->momz = FRACUNIT*5+(P_Random()<<10);
mo->flags2 |= MF2_DROPPED;
mo->health = special;
}
*/
//----------------------------------------------------------------------------
//
// PROC A_NoBlocking
//
//----------------------------------------------------------------------------
void A_NoBlocking(mobj_t * actor)
{
actor->flags &= ~MF_SOLID;
// Check for monsters dropping things
/* switch(actor->type)
{
// Add the monster dropped items here
case MT_MUMMYLEADERGHOST:
P_DropItem(actor, MT_AMGWNDWIMPY, 3, 84);
break;
default:
break;
}
*/
}
//----------------------------------------------------------------------------
//
// PROC A_Explode
//
// Handles a bunch of exploding things.
//
//----------------------------------------------------------------------------
void A_Explode(mobj_t * actor)
{
int damage;
int distance;
boolean damageSelf;
damage = 128;
distance = 128;
damageSelf = true;
switch (actor->type)
{
case MT_FIREBOMB: // Time Bombs
actor->z += 32 * FRACUNIT;
actor->flags &= ~MF_SHADOW;
break;
case MT_MNTRFX2: // Minotaur floor fire
damage = 24;
break;
case MT_BISHOP: // Bishop radius death
damage = 25 + (P_Random() & 15);
break;
case MT_HAMMER_MISSILE: // Fighter Hammer
damage = 128;
damageSelf = false;
break;
case MT_FSWORD_MISSILE: // Fighter Runesword
damage = 64;
damageSelf = false;
break;
case MT_CIRCLEFLAME: // Cleric Flame secondary flames
damage = 20;
damageSelf = false;
break;
case MT_SORCBALL1: // Sorcerer balls
case MT_SORCBALL2:
case MT_SORCBALL3:
distance = 255;
damage = 255;
actor->args[0] = 1; // don't play bounce
break;
case MT_SORCFX1: // Sorcerer spell 1
damage = 30;
break;
case MT_SORCFX4: // Sorcerer spell 4
damage = 20;
break;
case MT_TREEDESTRUCTIBLE:
damage = 10;
break;
case MT_DRAGON_FX2:
damage = 80;
damageSelf = false;
break;
case MT_MSTAFF_FX:
damage = 64;
distance = 192;
damageSelf = false;
break;
case MT_MSTAFF_FX2:
damage = 80;
distance = 192;
damageSelf = false;
break;
case MT_POISONCLOUD:
damage = 4;
distance = 40;
break;
case MT_ZXMAS_TREE:
case MT_ZSHRUB2:
damage = 30;
distance = 64;
break;
default:
break;
}
P_RadiusAttack(actor, actor->target, damage, distance, damageSelf);
if (actor->z <= actor->floorz + (distance << FRACBITS)
&& actor->type != MT_POISONCLOUD)
{
P_HitFloor(actor);
}
}
//----------------------------------------------------------------------------
//
// PROC P_Massacre
//
// Kills all monsters.
//
//----------------------------------------------------------------------------
int P_Massacre(void)
{
int count;
mobj_t *mo;
thinker_t *think;
count = 0;
for (think = thinkercap.next; think != &thinkercap; think = think->next)
{
if (think->function != P_MobjThinker)
{ // Not a mobj thinker
continue;
}
mo = (mobj_t *) think;
if ((mo->flags & MF_COUNTKILL) && (mo->health > 0))
{
mo->flags2 &= ~(MF2_NONSHOOTABLE + MF2_INVULNERABLE);
mo->flags |= MF_SHOOTABLE;
P_DamageMobj(mo, NULL, NULL, 10000);
count++;
}
}
return count;
}
//----------------------------------------------------------------------------
//
// PROC A_SkullPop
//
//----------------------------------------------------------------------------
void A_SkullPop(mobj_t * actor)
{
mobj_t *mo;
player_t *player;
if (!actor->player)
{
return;
}
actor->flags &= ~MF_SOLID;
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 48 * FRACUNIT,
MT_BLOODYSKULL);
//mo->target = actor;
mo->momx = (P_Random() - P_Random()) << 9;
mo->momy = (P_Random() - P_Random()) << 9;
mo->momz = FRACUNIT * 2 + (P_Random() << 6);
// Attach player mobj to bloody skull
player = actor->player;
actor->player = NULL;
actor->special1.i = player->class;
mo->player = player;
mo->health = actor->health;
mo->angle = actor->angle;
player->mo = mo;
player->lookdir = 0;
player->damagecount = 32;
}
//----------------------------------------------------------------------------
//
// PROC A_CheckSkullFloor
//
//----------------------------------------------------------------------------
void A_CheckSkullFloor(mobj_t * actor)
{
if (actor->z <= actor->floorz)
{
P_SetMobjState(actor, S_BLOODYSKULLX1);
S_StartSound(actor, SFX_DRIP);
}
}
//----------------------------------------------------------------------------
//
// PROC A_CheckSkullDone
//
//----------------------------------------------------------------------------
void A_CheckSkullDone(mobj_t * actor)
{
if (actor->special2.i == 666)
{
P_SetMobjState(actor, S_BLOODYSKULLX2);
}
}
//----------------------------------------------------------------------------
//
// PROC A_CheckBurnGone
//
//----------------------------------------------------------------------------
void A_CheckBurnGone(mobj_t * actor)
{
if (actor->special2.i == 666)
{
P_SetMobjState(actor, S_PLAY_FDTH20);
}
}
//----------------------------------------------------------------------------
//
// PROC A_FreeTargMobj
//
//----------------------------------------------------------------------------
void A_FreeTargMobj(mobj_t * mo)
{
mo->momx = mo->momy = mo->momz = 0;
mo->z = mo->ceilingz + 4 * FRACUNIT;
mo->flags &=
~(MF_SHOOTABLE | MF_FLOAT | MF_SKULLFLY | MF_SOLID | MF_COUNTKILL);
mo->flags |= MF_CORPSE | MF_DROPOFF | MF_NOGRAVITY;
mo->flags2 &= ~(MF2_PASSMOBJ | MF2_LOGRAV);
mo->flags2 |= MF2_DONTDRAW;
mo->player = NULL;
mo->health = -1000; // Don't resurrect
}
//----------------------------------------------------------------------------
//
// CorpseQueue Routines
//
//----------------------------------------------------------------------------
// Corpse queue for monsters - this should be saved out
#define CORPSEQUEUESIZE 64
mobj_t *corpseQueue[CORPSEQUEUESIZE];
int corpseQueueSlot;
// throw another corpse on the queue
void A_QueueCorpse(mobj_t * actor)
{
mobj_t *corpse;
if (corpseQueueSlot >= CORPSEQUEUESIZE)
{ // Too many corpses - remove an old one
corpse = corpseQueue[corpseQueueSlot % CORPSEQUEUESIZE];
if (corpse)
P_RemoveMobj(corpse);
}
corpseQueue[corpseQueueSlot % CORPSEQUEUESIZE] = actor;
corpseQueueSlot++;
}
// Remove a mobj from the queue (for resurrection)
void A_DeQueueCorpse(mobj_t * actor)
{
int slot;
for (slot = 0; slot < CORPSEQUEUESIZE; slot++)
{
if (corpseQueue[slot] == actor)
{
corpseQueue[slot] = NULL;
break;
}
}
}
void P_InitCreatureCorpseQueue(boolean corpseScan)
{
thinker_t *think;
mobj_t *mo;
// Initialize queue
corpseQueueSlot = 0;
memset(corpseQueue, 0, sizeof(mobj_t *) * CORPSEQUEUESIZE);
if (!corpseScan)
return;
// Search mobj list for corpses and place them in this queue
for (think = thinkercap.next; think != &thinkercap; think = think->next)
{
if (think->function != P_MobjThinker)
continue;
mo = (mobj_t *) think;
if (!(mo->flags & MF_CORPSE))
continue; // Must be a corpse
if (mo->flags & MF_ICECORPSE)
continue; // Not ice corpses
// Only corpses that call A_QueueCorpse from death routine
switch (mo->type)
{
case MT_CENTAUR:
case MT_CENTAURLEADER:
case MT_DEMON:
case MT_DEMON2:
case MT_WRAITH:
case MT_WRAITHB:
case MT_BISHOP:
case MT_ETTIN:
case MT_PIG:
case MT_CENTAUR_SHIELD:
case MT_CENTAUR_SWORD:
case MT_DEMONCHUNK1:
case MT_DEMONCHUNK2:
case MT_DEMONCHUNK3:
case MT_DEMONCHUNK4:
case MT_DEMONCHUNK5:
case MT_DEMON2CHUNK1:
case MT_DEMON2CHUNK2:
case MT_DEMON2CHUNK3:
case MT_DEMON2CHUNK4:
case MT_DEMON2CHUNK5:
case MT_FIREDEMON_SPLOTCH1:
case MT_FIREDEMON_SPLOTCH2:
A_QueueCorpse(mo); // Add corpse to queue
break;
default:
break;
}
}
}
//----------------------------------------------------------------------------
//
// PROC A_AddPlayerCorpse
//
//----------------------------------------------------------------------------
#define BODYQUESIZE 32
mobj_t *bodyque[BODYQUESIZE];
int bodyqueslot;
void A_AddPlayerCorpse(mobj_t * actor)
{
if (bodyqueslot >= BODYQUESIZE)
{ // Too many player corpses - remove an old one
P_RemoveMobj(bodyque[bodyqueslot % BODYQUESIZE]);
}
bodyque[bodyqueslot % BODYQUESIZE] = actor;
bodyqueslot++;
}
//============================================================================
//
// A_SerpentUnHide
//
//============================================================================
void A_SerpentUnHide(mobj_t * actor)
{
actor->flags2 &= ~MF2_DONTDRAW;
actor->floorclip = 24 * FRACUNIT;
}
//============================================================================
//
// A_SerpentHide
//
//============================================================================
void A_SerpentHide(mobj_t * actor)
{
actor->flags2 |= MF2_DONTDRAW;
actor->floorclip = 0;
}
//============================================================================
//
// A_SerpentChase
//
//============================================================================
void A_SerpentChase(mobj_t * actor)
{
int delta;
int oldX, oldY, oldFloor;
if (actor->reactiontime)
{
actor->reactiontime--;
}
// Modify target threshold
if (actor->threshold)
{
actor->threshold--;
}
if (gameskill == sk_nightmare)
{ // Monsters move faster in nightmare mode
actor->tics -= actor->tics / 2;
if (actor->tics < 3)
{
actor->tics = 3;
}
}
//
// 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))
{ // got a new target
return;
}
P_SetMobjState(actor, actor->info->spawnstate);
return;
}
//
// don't attack twice in a row
//
if (actor->flags & MF_JUSTATTACKED)
{
actor->flags &= ~MF_JUSTATTACKED;
if (gameskill != sk_nightmare)
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;
}
//
// 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
//
oldX = actor->x;
oldY = actor->y;
oldFloor = actor->subsector->sector->floorpic;
if (--actor->movecount < 0 || !P_Move(actor))
{
P_NewChaseDir(actor);
}
if (actor->subsector->sector->floorpic != oldFloor)
{
P_TryMove(actor, oldX, oldY);
P_NewChaseDir(actor);
}
//
// make active sound
//
if (actor->info->activesound && P_Random() < 3)
{
S_StartSound(actor, actor->info->activesound);
}
}
//============================================================================
//
// A_SerpentRaiseHump
//
// Raises the hump above the surface by raising the floorclip level
//============================================================================
void A_SerpentRaiseHump(mobj_t * actor)
{
actor->floorclip -= 4 * FRACUNIT;
}
//============================================================================
//
// A_SerpentLowerHump
//
//============================================================================
void A_SerpentLowerHump(mobj_t * actor)
{
actor->floorclip += 4 * FRACUNIT;
}
//============================================================================
//
// A_SerpentHumpDecide
//
// Decided whether to hump up, or if the mobj is a serpent leader,
// to missile attack
//============================================================================
void A_SerpentHumpDecide(mobj_t * actor)
{
if (actor->type == MT_SERPENTLEADER)
{
if (P_Random() > 30)
{
return;
}
else if (P_Random() < 40)
{ // Missile attack
P_SetMobjState(actor, S_SERPENT_SURFACE1);
return;
}
}
else if (P_Random() > 3)
{
return;
}
if (!P_CheckMeleeRange(actor))
{ // The hump shouldn't occur when within melee range
if (actor->type == MT_SERPENTLEADER && P_Random() < 128)
{
P_SetMobjState(actor, S_SERPENT_SURFACE1);
}
else
{
P_SetMobjState(actor, S_SERPENT_HUMP1);
S_StartSound(actor, SFX_SERPENT_ACTIVE);
}
}
}
//============================================================================
//
// A_SerpentBirthScream
//
//============================================================================
void A_SerpentBirthScream(mobj_t * actor)
{
S_StartSound(actor, SFX_SERPENT_BIRTH);
}
//============================================================================
//
// A_SerpentDiveSound
//
//============================================================================
void A_SerpentDiveSound(mobj_t * actor)
{
S_StartSound(actor, SFX_SERPENT_ACTIVE);
}
//============================================================================
//
// A_SerpentWalk
//
// Similar to A_Chase, only has a hardcoded entering of meleestate
//============================================================================
void A_SerpentWalk(mobj_t * actor)
{
int delta;
if (actor->reactiontime)
{
actor->reactiontime--;
}
// Modify target threshold
if (actor->threshold)
{
actor->threshold--;
}
if (gameskill == sk_nightmare)
{ // Monsters move faster in nightmare mode
actor->tics -= actor->tics / 2;
if (actor->tics < 3)
{
actor->tics = 3;
}
}
//
// 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))
{ // got a new target
return;
}
P_SetMobjState(actor, actor->info->spawnstate);
return;
}
//
// don't attack twice in a row
//
if (actor->flags & MF_JUSTATTACKED)
{
actor->flags &= ~MF_JUSTATTACKED;
if (gameskill != sk_nightmare)
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, S_SERPENT_ATK1);
return;
}
//
// 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);
}
}
//============================================================================
//
// A_SerpentCheckForAttack
//
//============================================================================
void A_SerpentCheckForAttack(mobj_t * actor)
{
if (!actor->target)
{
return;
}
if (actor->type == MT_SERPENTLEADER)
{
if (!P_CheckMeleeRange(actor))
{
P_SetMobjState(actor, S_SERPENT_ATK1);
return;
}
}
if (P_CheckMeleeRange2(actor))
{
P_SetMobjState(actor, S_SERPENT_WALK1);
}
else if (P_CheckMeleeRange(actor))
{
if (P_Random() < 32)
{
P_SetMobjState(actor, S_SERPENT_WALK1);
}
else
{
P_SetMobjState(actor, S_SERPENT_ATK1);
}
}
}
//============================================================================
//
// A_SerpentChooseAttack
//
//============================================================================
void A_SerpentChooseAttack(mobj_t * actor)
{
if (!actor->target || P_CheckMeleeRange(actor))
{
return;
}
if (actor->type == MT_SERPENTLEADER)
{
P_SetMobjState(actor, S_SERPENT_MISSILE1);
}
}
//============================================================================
//
// A_SerpentMeleeAttack
//
//============================================================================
void A_SerpentMeleeAttack(mobj_t * actor)
{
if (!actor->target)
{
return;
}
if (P_CheckMeleeRange(actor))
{
P_DamageMobj(actor->target, actor, actor, HITDICE(5));
S_StartSound(actor, SFX_SERPENT_MELEEHIT);
}
if (P_Random() < 96)
{
A_SerpentCheckForAttack(actor);
}
}
//============================================================================
//
// A_SerpentMissileAttack
//
//============================================================================
void A_SerpentMissileAttack(mobj_t * actor)
{
if (!actor->target)
{
return;
}
P_SpawnMissile(actor, actor->target, MT_SERPENTFX);
}
//============================================================================
//
// A_SerpentHeadPop
//
//============================================================================
void A_SerpentHeadPop(mobj_t * actor)
{
P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT,
MT_SERPENT_HEAD);
}
//============================================================================
//
// A_SerpentSpawnGibs
//
//============================================================================
void A_SerpentSpawnGibs(mobj_t * actor)
{
mobj_t *mo;
mo = P_SpawnMobj(actor->x + ((P_Random() - 128) << 12),
actor->y + ((P_Random() - 128) << 12),
actor->floorz + FRACUNIT, MT_SERPENT_GIB1);
if (mo)
{
mo->momx = (P_Random() - 128) << 6;
mo->momy = (P_Random() - 128) << 6;
mo->floorclip = 6 * FRACUNIT;
}
mo = P_SpawnMobj(actor->x + ((P_Random() - 128) << 12),
actor->y + ((P_Random() - 128) << 12),
actor->floorz + FRACUNIT, MT_SERPENT_GIB2);
if (mo)
{
mo->momx = (P_Random() - 128) << 6;
mo->momy = (P_Random() - 128) << 6;
mo->floorclip = 6 * FRACUNIT;
}
mo = P_SpawnMobj(actor->x + ((P_Random() - 128) << 12),
actor->y + ((P_Random() - 128) << 12),
actor->floorz + FRACUNIT, MT_SERPENT_GIB3);
if (mo)
{
mo->momx = (P_Random() - 128) << 6;
mo->momy = (P_Random() - 128) << 6;
mo->floorclip = 6 * FRACUNIT;
}
}
//============================================================================
//
// A_FloatGib
//
//============================================================================
void A_FloatGib(mobj_t * actor)
{
actor->floorclip -= FRACUNIT;
}
//============================================================================
//
// A_SinkGib
//
//============================================================================
void A_SinkGib(mobj_t * actor)
{
actor->floorclip += FRACUNIT;
}
//============================================================================
//
// A_DelayGib
//
//============================================================================
void A_DelayGib(mobj_t * actor)
{
actor->tics -= P_Random() >> 2;
}
//============================================================================
//
// A_SerpentHeadCheck
//
//============================================================================
void A_SerpentHeadCheck(mobj_t * actor)
{
if (actor->z <= actor->floorz)
{
if (P_GetThingFloorType(actor) >= FLOOR_LIQUID)
{
P_HitFloor(actor);
P_SetMobjState(actor, S_NULL);
}
else
{
P_SetMobjState(actor, S_SERPENT_HEAD_X1);
}
}
}
//============================================================================
//
// A_CentaurAttack
//
//============================================================================
void A_CentaurAttack(mobj_t * actor)
{
if (!actor->target)
{
return;
}
if (P_CheckMeleeRange(actor))
{
P_DamageMobj(actor->target, actor, actor, P_Random() % 7 + 3);
}
}
//============================================================================
//
// A_CentaurAttack2
//
//============================================================================
void A_CentaurAttack2(mobj_t * actor)
{
if (!actor->target)
{
return;
}
P_SpawnMissile(actor, actor->target, MT_CENTAUR_FX);
S_StartSound(actor, SFX_CENTAURLEADER_ATTACK);
}
//============================================================================
//
// A_CentaurDropStuff
//
// Spawn shield/sword sprites when the centaur pulps //============================================================================
void A_CentaurDropStuff(mobj_t * actor)
{
mobj_t *mo;
angle_t angle;
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT,
MT_CENTAUR_SHIELD);
if (mo)
{
angle = actor->angle + ANG90;
mo->momz = FRACUNIT * 8 + (P_Random() << 10);
mo->momx = FixedMul(((P_Random() - 128) << 11) + FRACUNIT,
finecosine[angle >> ANGLETOFINESHIFT]);
mo->momy = FixedMul(((P_Random() - 128) << 11) + FRACUNIT,
finesine[angle >> ANGLETOFINESHIFT]);
mo->target = actor;
}
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT,
MT_CENTAUR_SWORD);
if (mo)
{
angle = actor->angle - ANG90;
mo->momz = FRACUNIT * 8 + (P_Random() << 10);
mo->momx = FixedMul(((P_Random() - 128) << 11) + FRACUNIT,
finecosine[angle >> ANGLETOFINESHIFT]);
mo->momy = FixedMul(((P_Random() - 128) << 11) + FRACUNIT,
finesine[angle >> ANGLETOFINESHIFT]);
mo->target = actor;
}
}
//============================================================================
//
// A_CentaurDefend
//
//============================================================================
void A_CentaurDefend(mobj_t * actor)
{
A_FaceTarget(actor);
if (P_CheckMeleeRange(actor) && P_Random() < 32)
{
A_UnSetInvulnerable(actor);
P_SetMobjState(actor, actor->info->meleestate);
}
}
//============================================================================
//
// A_BishopAttack
//
//============================================================================
void A_BishopAttack(mobj_t * actor)
{
if (!actor->target)
{
return;
}
S_StartSound(actor, actor->info->attacksound);
if (P_CheckMeleeRange(actor))
{
P_DamageMobj(actor->target, actor, actor, HITDICE(4));
return;
}
actor->special1.i = (P_Random() & 3) + 5;
}
//============================================================================
//
// A_BishopAttack2
//
// Spawns one of a string of bishop missiles
//============================================================================
void A_BishopAttack2(mobj_t * actor)
{
mobj_t *mo;
if (!actor->target || !actor->special1.i)
{
actor->special1.i = 0;
P_SetMobjState(actor, S_BISHOP_WALK1);
return;
}
mo = P_SpawnMissile(actor, actor->target, MT_BISH_FX);
if (mo)
{
mo->special1.m = actor->target;
mo->special2.i = 16; // High word == x/y, Low word == z
}
actor->special1.i--;
}
//============================================================================
//
// A_BishopMissileWeave
//
//============================================================================
void A_BishopMissileWeave(mobj_t * actor)
{
fixed_t newX, newY;
int weaveXY, weaveZ;
int angle;
weaveXY = actor->special2.i >> 16;
weaveZ = actor->special2.i & 0xFFFF;
angle = (actor->angle + ANG90) >> ANGLETOFINESHIFT;
newX = actor->x - FixedMul(finecosine[angle],
FloatBobOffsets[weaveXY] << 1);
newY = actor->y - FixedMul(finesine[angle],
FloatBobOffsets[weaveXY] << 1);
weaveXY = (weaveXY + 2) & 63;
newX += FixedMul(finecosine[angle], FloatBobOffsets[weaveXY] << 1);
newY += FixedMul(finesine[angle], FloatBobOffsets[weaveXY] << 1);
P_TryMove(actor, newX, newY);
actor->z -= FloatBobOffsets[weaveZ];
weaveZ = (weaveZ + 2) & 63;
actor->z += FloatBobOffsets[weaveZ];
actor->special2.i = weaveZ + (weaveXY << 16);
}
//============================================================================
//
// A_BishopMissileSeek
//
//============================================================================
void A_BishopMissileSeek(mobj_t * actor)
{
P_SeekerMissile(actor, ANG1 * 2, ANG1 * 3);
}
//============================================================================
//
// A_BishopDecide
//
//============================================================================
void A_BishopDecide(mobj_t * actor)
{
if (P_Random() < 220)
{
return;
}
else
{
P_SetMobjState(actor, S_BISHOP_BLUR1);
}
}
//============================================================================
//
// A_BishopDoBlur
//
//============================================================================
void A_BishopDoBlur(mobj_t * actor)
{
actor->special1.i = (P_Random() & 3) + 3; // Random number of blurs
if (P_Random() < 120)
{
P_ThrustMobj(actor, actor->angle + ANG90, 11 * FRACUNIT);
}
else if (P_Random() > 125)
{
P_ThrustMobj(actor, actor->angle - ANG90, 11 * FRACUNIT);
}
else
{ // Thrust forward
P_ThrustMobj(actor, actor->angle, 11 * FRACUNIT);
}
S_StartSound(actor, SFX_BISHOP_BLUR);
}
//============================================================================
//
// A_BishopSpawnBlur
//
//============================================================================
void A_BishopSpawnBlur(mobj_t * actor)
{
mobj_t *mo;
if (!--actor->special1.i)
{
actor->momx = 0;
actor->momy = 0;
if (P_Random() > 96)
{
P_SetMobjState(actor, S_BISHOP_WALK1);
}
else
{
P_SetMobjState(actor, S_BISHOP_ATK1);
}
}
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_BISHOPBLUR);
if (mo)
{
mo->angle = actor->angle;
}
}
//============================================================================
//
// A_BishopChase
//
//============================================================================
void A_BishopChase(mobj_t * actor)
{
actor->z -= FloatBobOffsets[actor->special2.i] >> 1;
actor->special2.i = (actor->special2.i + 4) & 63;
actor->z += FloatBobOffsets[actor->special2.i] >> 1;
}
//============================================================================
//
// A_BishopPuff
//
//============================================================================
void A_BishopPuff(mobj_t * actor)
{
mobj_t *mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 40 * FRACUNIT,
MT_BISHOP_PUFF);
if (mo)
{
mo->momz = FRACUNIT / 2;
}
}
//============================================================================
//
// A_BishopPainBlur
//
//============================================================================
void A_BishopPainBlur(mobj_t * actor)
{
mobj_t *mo;
if (P_Random() < 64)
{
P_SetMobjState(actor, S_BISHOP_BLUR1);
return;
}
mo = P_SpawnMobj(actor->x + ((P_Random() - P_Random()) << 12), actor->y
+ ((P_Random() - P_Random()) << 12),
actor->z + ((P_Random() - P_Random()) << 11),
MT_BISHOPPAINBLUR);
if (mo)
{
mo->angle = actor->angle;
}
}
//============================================================================
//
// DragonSeek
//
//============================================================================
static void DragonSeek(mobj_t * actor, angle_t thresh, angle_t turnMax)
{
int dir;
int dist;
angle_t delta;
angle_t angle;
mobj_t *target;
int search;
int i;
int bestArg;
angle_t bestAngle;
angle_t angleToSpot, angleToTarget;
mobj_t *mo;
target = actor->special1.m;
if (target == NULL)
{
return;
}
dir = P_FaceMobj(actor, target, &delta);
if (delta > thresh)
{
delta >>= 1;
if (delta > turnMax)
{
delta = turnMax;
}
}
if (dir)
{ // Turn clockwise
actor->angle += delta;
}
else
{ // Turn counter clockwise
actor->angle -= delta;
}
angle = actor->angle >> ANGLETOFINESHIFT;
actor->momx = FixedMul(actor->info->speed, finecosine[angle]);
actor->momy = FixedMul(actor->info->speed, finesine[angle]);
if (actor->z + actor->height < target->z
|| target->z + target->height < actor->z)
{
dist = P_AproxDistance(target->x - actor->x, target->y - actor->y);
dist = dist / actor->info->speed;
if (dist < 1)
{
dist = 1;
}
actor->momz = (target->z - actor->z) / dist;
}
else
{
dist = P_AproxDistance(target->x - actor->x, target->y - actor->y);
dist = dist / actor->info->speed;
}
if (target->flags & MF_SHOOTABLE && P_Random() < 64)
{ // attack the destination mobj if it's attackable
mobj_t *oldTarget;
if (abs(actor->angle - R_PointToAngle2(actor->x, actor->y,
target->x,
target->y)) < ANG45 / 2)
{
oldTarget = actor->target;
actor->target = target;
if (P_CheckMeleeRange(actor))
{
P_DamageMobj(actor->target, actor, actor, HITDICE(10));
S_StartSound(actor, SFX_DRAGON_ATTACK);
}
else if (P_Random() < 128 && P_CheckMissileRange(actor))
{
P_SpawnMissile(actor, target, MT_DRAGON_FX);
S_StartSound(actor, SFX_DRAGON_ATTACK);
}
actor->target = oldTarget;
}
}
if (dist < 4)
{ // Hit the target thing
if (actor->target && P_Random() < 200)
{
bestArg = -1;
bestAngle = ANG_MAX;
angleToTarget = R_PointToAngle2(actor->x, actor->y,
actor->target->x,
actor->target->y);
for (i = 0; i < 5; i++)
{
if (!target->args[i])
{
continue;
}
search = -1;
mo = P_FindMobjFromTID(target->args[i], &search);
angleToSpot = R_PointToAngle2(actor->x, actor->y,
mo->x, mo->y);
if (abs(angleToSpot - angleToTarget) < bestAngle)
{
bestAngle = abs(angleToSpot - angleToTarget);
bestArg = i;
}
}
if (bestArg != -1)
{
search = -1;
actor->special1.m =
P_FindMobjFromTID(target->args[bestArg], &search);
}
}
else
{
do
{
i = (P_Random() >> 2) % 5;
}
while (!target->args[i]);
search = -1;
actor->special1.m =
P_FindMobjFromTID(target->args[i], &search);
}
}
}
//============================================================================
//
// A_DragonInitFlight
//
//============================================================================
void A_DragonInitFlight(mobj_t * actor)
{
int search;
search = -1;
do
{ // find the first tid identical to the dragon's tid
actor->special1.m = P_FindMobjFromTID(actor->tid, &search);
if (search == -1)
{
P_SetMobjState(actor, actor->info->spawnstate);
return;
}
}
while (actor->special1.m == actor);
P_RemoveMobjFromTIDList(actor);
}
//============================================================================
//
// A_DragonFlight
//
//============================================================================
void A_DragonFlight(mobj_t * actor)
{
angle_t angle;
DragonSeek(actor, 4 * ANG1, 8 * ANG1);
if (actor->target)
{
if (!(actor->target->flags & MF_SHOOTABLE))
{ // target died
actor->target = NULL;
return;
}
angle = R_PointToAngle2(actor->x, actor->y, actor->target->x,
actor->target->y);
if (abs(actor->angle - angle) < ANG45 / 2
&& P_CheckMeleeRange(actor))
{
P_DamageMobj(actor->target, actor, actor, HITDICE(8));
S_StartSound(actor, SFX_DRAGON_ATTACK);
}
else if (abs(actor->angle - angle) <= ANG1 * 20)
{
P_SetMobjState(actor, actor->info->missilestate);
S_StartSound(actor, SFX_DRAGON_ATTACK);
}
}
else
{
P_LookForPlayers(actor, true);
}
}
//============================================================================
//
// A_DragonFlap
//
//============================================================================
void A_DragonFlap(mobj_t * actor)
{
A_DragonFlight(actor);
if (P_Random() < 240)
{
S_StartSound(actor, SFX_DRAGON_WINGFLAP);
}
else
{
S_StartSound(actor, actor->info->activesound);
}
}
//============================================================================
//
// A_DragonAttack
//
//============================================================================
void A_DragonAttack(mobj_t * actor)
{
P_SpawnMissile(actor, actor->target, MT_DRAGON_FX);
}
//============================================================================
//
// A_DragonFX2
//
//============================================================================
void A_DragonFX2(mobj_t * actor)
{
mobj_t *mo;
int i;
int delay;
delay = 16 + (P_Random() >> 3);
for (i = 1 + (P_Random() & 3); i; i--)
{
mo = P_SpawnMobj(actor->x + ((P_Random() - 128) << 14),
actor->y + ((P_Random() - 128) << 14),
actor->z + ((P_Random() - 128) << 12),
MT_DRAGON_FX2);
if (mo)
{
mo->tics = delay + (P_Random() & 3) * i * 2;
mo->target = actor->target;
}
}
}
//============================================================================
//
// A_DragonPain
//
//============================================================================
void A_DragonPain(mobj_t * actor)
{
A_Pain(actor);
if (!actor->special1.i)
{ // no destination spot yet
P_SetMobjState(actor, S_DRAGON_INIT);
}
}
//============================================================================
//
// A_DragonCheckCrash
//
//============================================================================
void A_DragonCheckCrash(mobj_t * actor)
{
if (actor->z <= actor->floorz)
{
P_SetMobjState(actor, S_DRAGON_CRASH1);
}
}
//============================================================================
// Demon AI
//============================================================================
//
// A_DemonAttack1 (melee)
//
void A_DemonAttack1(mobj_t * actor)
{
if (P_CheckMeleeRange(actor))
{
P_DamageMobj(actor->target, actor, actor, HITDICE(2));
}
}
//
// A_DemonAttack2 (missile)
//
void A_DemonAttack2(mobj_t * actor)
{
mobj_t *mo;
int fireBall;
if (actor->type == MT_DEMON)
{
fireBall = MT_DEMONFX1;
}
else
{
fireBall = MT_DEMON2FX1;
}
mo = P_SpawnMissile(actor, actor->target, fireBall);
if (mo)
{
mo->z += 30 * FRACUNIT;
S_StartSound(actor, SFX_DEMON_MISSILE_FIRE);
}
}
//
// A_DemonDeath
//
void A_DemonDeath(mobj_t * actor)
{
mobj_t *mo;
angle_t angle;
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT,
MT_DEMONCHUNK1);
if (mo)
{
angle = actor->angle + ANG90;
mo->momz = 8 * FRACUNIT;
mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
finecosine[angle >> ANGLETOFINESHIFT]);
mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
finesine[angle >> ANGLETOFINESHIFT]);
mo->target = actor;
}
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT,
MT_DEMONCHUNK2);
if (mo)
{
angle = actor->angle - ANG90;
mo->momz = 8 * FRACUNIT;
mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
finecosine[angle >> ANGLETOFINESHIFT]);
mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
finesine[angle >> ANGLETOFINESHIFT]);
mo->target = actor;
}
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT,
MT_DEMONCHUNK3);
if (mo)
{
angle = actor->angle - ANG90;
mo->momz = 8 * FRACUNIT;
mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
finecosine[angle >> ANGLETOFINESHIFT]);
mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
finesine[angle >> ANGLETOFINESHIFT]);
mo->target = actor;
}
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT,
MT_DEMONCHUNK4);
if (mo)
{
angle = actor->angle - ANG90;
mo->momz = 8 * FRACUNIT;
mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
finecosine[angle >> ANGLETOFINESHIFT]);
mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
finesine[angle >> ANGLETOFINESHIFT]);
mo->target = actor;
}
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT,
MT_DEMONCHUNK5);
if (mo)
{
angle = actor->angle - ANG90;
mo->momz = 8 * FRACUNIT;
mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
finecosine[angle >> ANGLETOFINESHIFT]);
mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
finesine[angle >> ANGLETOFINESHIFT]);
mo->target = actor;
}
}
//===========================================================================
//
// A_Demon2Death
//
//===========================================================================
void A_Demon2Death(mobj_t * actor)
{
mobj_t *mo;
angle_t angle;
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT,
MT_DEMON2CHUNK1);
if (mo)
{
angle = actor->angle + ANG90;
mo->momz = 8 * FRACUNIT;
mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
finecosine[angle >> ANGLETOFINESHIFT]);
mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
finesine[angle >> ANGLETOFINESHIFT]);
mo->target = actor;
}
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT,
MT_DEMON2CHUNK2);
if (mo)
{
angle = actor->angle - ANG90;
mo->momz = 8 * FRACUNIT;
mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
finecosine[angle >> ANGLETOFINESHIFT]);
mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
finesine[angle >> ANGLETOFINESHIFT]);
mo->target = actor;
}
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT,
MT_DEMON2CHUNK3);
if (mo)
{
angle = actor->angle - ANG90;
mo->momz = 8 * FRACUNIT;
mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
finecosine[angle >> ANGLETOFINESHIFT]);
mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
finesine[angle >> ANGLETOFINESHIFT]);
mo->target = actor;
}
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT,
MT_DEMON2CHUNK4);
if (mo)
{
angle = actor->angle - ANG90;
mo->momz = 8 * FRACUNIT;
mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
finecosine[angle >> ANGLETOFINESHIFT]);
mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
finesine[angle >> ANGLETOFINESHIFT]);
mo->target = actor;
}
mo = P_SpawnMobj(actor->x, actor->y, actor->z + 45 * FRACUNIT,
MT_DEMON2CHUNK5);
if (mo)
{
angle = actor->angle - ANG90;
mo->momz = 8 * FRACUNIT;
mo->momx = FixedMul((P_Random() << 10) + FRACUNIT,
finecosine[angle >> ANGLETOFINESHIFT]);
mo->momy = FixedMul((P_Random() << 10) + FRACUNIT,
finesine[angle >> ANGLETOFINESHIFT]);
mo->target = actor;
}
}
//
// A_SinkMobj
// Sink a mobj incrementally into the floor
//
boolean A_SinkMobj(mobj_t * actor)
{
if (actor->floorclip < actor->info->height)
{
switch (actor->type)
{
case MT_THRUSTFLOOR_DOWN:
case MT_THRUSTFLOOR_UP:
actor->floorclip += 6 * FRACUNIT;
break;
default:
actor->floorclip += FRACUNIT;
break;
}
return false;
}
return true;
}
//
// A_RaiseMobj
// Raise a mobj incrementally from the floor to
//
boolean A_RaiseMobj(mobj_t * actor)
{
int done = true;
// Raise a mobj from the ground
if (actor->floorclip > 0)
{
switch (actor->type)
{
case MT_WRAITHB:
actor->floorclip -= 2 * FRACUNIT;
break;
case MT_THRUSTFLOOR_DOWN:
case MT_THRUSTFLOOR_UP:
actor->floorclip -= actor->special2.i * FRACUNIT;
break;
default:
actor->floorclip -= 2 * FRACUNIT;
break;
}
if (actor->floorclip <= 0)
{
actor->floorclip = 0;
done = true;
}
else
{
done = false;
}
}
return done; // Reached target height
}
//============================================================================
// Wraith Variables
//
// special1 Internal index into floatbob
// special2
//============================================================================
//
// A_WraithInit
//
void A_WraithInit(mobj_t * actor)
{
actor->z += 48 << FRACBITS;
actor->special1.i = 0; // index into floatbob
}
void A_WraithRaiseInit(mobj_t * actor)
{
actor->flags2 &= ~MF2_DONTDRAW;
actor->flags2 &= ~MF2_NONSHOOTABLE;
actor->flags |= MF_SHOOTABLE | MF_SOLID;
actor->floorclip = actor->info->height;
}
void A_WraithRaise(mobj_t * actor)
{
if (A_RaiseMobj(actor))
{
// Reached it's target height
P_SetMobjState(actor, S_WRAITH_CHASE1);
}
P_SpawnDirt(actor, actor->radius);
}
void A_WraithMelee(mobj_t * actor)
{
int amount;
// Steal health from target and give to player
if (P_CheckMeleeRange(actor) && (P_Random() < 220))
{
amount = HITDICE(2);
P_DamageMobj(actor->target, actor, actor, amount);
actor->health += amount;
}
}
void A_WraithMissile(mobj_t * actor)
{
mobj_t *mo;
mo = P_SpawnMissile(actor, actor->target, MT_WRAITHFX1);
if (mo)
{
S_StartSound(actor, SFX_WRAITH_MISSILE_FIRE);
}
}
//
// A_WraithFX2 - spawns sparkle tail of missile
//
void A_WraithFX2(mobj_t * actor)
{
mobj_t *mo;
angle_t angle;
int i;
for (i = 0; i < 2; i++)
{
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_WRAITHFX2);
if (mo)
{
if (P_Random() < 128)
{
angle = actor->angle + (P_Random() << 22);
}
else
{
angle = actor->angle - (P_Random() << 22);
}
mo->momz = 0;
mo->momx = FixedMul((P_Random() << 7) + FRACUNIT,
finecosine[angle >> ANGLETOFINESHIFT]);
mo->momy = FixedMul((P_Random() << 7) + FRACUNIT,
finesine[angle >> ANGLETOFINESHIFT]);
mo->target = actor;
mo->floorclip = 10 * FRACUNIT;
}
}
}
// Spawn an FX3 around the actor during attacks
void A_WraithFX3(mobj_t * actor)
{
mobj_t *mo;
int numdropped = P_Random() % 15;
int i;
for (i = 0; i < numdropped; i++)
{
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_WRAITHFX3);
if (mo)
{
mo->x += (P_Random() - 128) << 11;
mo->y += (P_Random() - 128) << 11;
mo->z += (P_Random() << 10);
mo->target = actor;
}
}
}
// Spawn an FX4 during movement
void A_WraithFX4(mobj_t * actor)
{
mobj_t *mo;
int chance = P_Random();
int spawn4, spawn5;
if (chance < 10)
{
spawn4 = true;
spawn5 = false;
}
else if (chance < 20)
{
spawn4 = false;
spawn5 = true;
}
else if (chance < 25)
{
spawn4 = true;
spawn5 = true;
}
else
{
spawn4 = false;
spawn5 = false;
}
if (spawn4)
{
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_WRAITHFX4);
if (mo)
{
mo->x += (P_Random() - 128) << 12;
mo->y += (P_Random() - 128) << 12;
mo->z += (P_Random() << 10);
mo->target = actor;
}
}
if (spawn5)
{
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_WRAITHFX5);
if (mo)
{
mo->x += (P_Random() - 128) << 11;
mo->y += (P_Random() - 128) << 11;
mo->z += (P_Random() << 10);
mo->target = actor;
}
}
}
void A_WraithLook(mobj_t * actor)
{
// A_WraithFX4(actor); // too expensive
A_Look(actor);
}
void A_WraithChase(mobj_t * actor)
{
int weaveindex = actor->special1.i;
actor->z += FloatBobOffsets[weaveindex];
actor->special1.i = (weaveindex + 2) & 63;
// if (actor->floorclip > 0)
// {
// P_SetMobjState(actor, S_WRAITH_RAISE2);
// return;
// }
A_Chase(actor);
A_WraithFX4(actor);
}
//============================================================================
// Ettin AI
//============================================================================
void A_EttinAttack(mobj_t * actor)
{
if (P_CheckMeleeRange(actor))
{
P_DamageMobj(actor->target, actor, actor, HITDICE(2));
}
}
void A_DropMace(mobj_t * actor)
{
mobj_t *mo;
mo = P_SpawnMobj(actor->x, actor->y,
actor->z + (actor->height >> 1), MT_ETTIN_MACE);
if (mo)
{
mo->momx = (P_Random() - 128) << 11;
mo->momy = (P_Random() - 128) << 11;
mo->momz = FRACUNIT * 10 + (P_Random() << 10);
mo->target = actor;
}
}
//============================================================================
// Fire Demon AI
//
// special1 index into floatbob
// special2 whether strafing or not
//============================================================================
void A_FiredSpawnRock(mobj_t * actor)
{
mobj_t *mo;
int x, y, z;
int rtype = 0;
switch (P_Random() % 5)
{
case 0:
rtype = MT_FIREDEMON_FX1;
break;
case 1:
rtype = MT_FIREDEMON_FX2;
break;
case 2:
rtype = MT_FIREDEMON_FX3;
break;
case 3:
rtype = MT_FIREDEMON_FX4;
break;
case 4:
rtype = MT_FIREDEMON_FX5;
break;
}
x = actor->x + ((P_Random() - 128) << 12);
y = actor->y + ((P_Random() - 128) << 12);
z = actor->z + ((P_Random()) << 11);
mo = P_SpawnMobj(x, y, z, rtype);
if (mo)
{
mo->target = actor;
mo->momx = (P_Random() - 128) << 10;
mo->momy = (P_Random() - 128) << 10;
mo->momz = (P_Random() << 10);
mo->special1.i = 2; // Number bounces
}
// Initialize fire demon
actor->special2.i = 0;
actor->flags &= ~MF_JUSTATTACKED;
}
void A_FiredRocks(mobj_t * actor)
{
A_FiredSpawnRock(actor);
A_FiredSpawnRock(actor);
A_FiredSpawnRock(actor);
A_FiredSpawnRock(actor);
A_FiredSpawnRock(actor);
}
void A_FiredAttack(mobj_t * actor)
{
mobj_t *mo;
mo = P_SpawnMissile(actor, actor->target, MT_FIREDEMON_FX6);
if (mo)
S_StartSound(actor, SFX_FIRED_ATTACK);
}
void A_SmBounce(mobj_t * actor)
{
// give some more momentum (x,y,&z)
actor->z = actor->floorz + FRACUNIT;
actor->momz = (2 * FRACUNIT) + (P_Random() << 10);
actor->momx = P_Random() % 3 << FRACBITS;
actor->momy = P_Random() % 3 << FRACBITS;
}
#define FIREDEMON_ATTACK_RANGE 64*8*FRACUNIT
void A_FiredChase(mobj_t * actor)
{
int weaveindex = actor->special1.i;
mobj_t *target = actor->target;
angle_t ang;
fixed_t dist;
if (actor->reactiontime)
actor->reactiontime--;
if (actor->threshold)
actor->threshold--;
// Float up and down
actor->z += FloatBobOffsets[weaveindex];
actor->special1.i = (weaveindex + 2) & 63;
// Insure it stays above certain height
if (actor->z < actor->floorz + (64 * FRACUNIT))
{
actor->z += 2 * FRACUNIT;
}
if (!actor->target || !(actor->target->flags & MF_SHOOTABLE))
{ // Invalid target
P_LookForPlayers(actor, true);
return;
}
// Strafe
if (actor->special2.i > 0)
{
actor->special2.i--;
}
else
{
actor->special2.i = 0;
actor->momx = actor->momy = 0;
dist = P_AproxDistance(actor->x - target->x, actor->y - target->y);
if (dist < FIREDEMON_ATTACK_RANGE)
{
if (P_Random() < 30)
{
ang =
R_PointToAngle2(actor->x, actor->y, target->x, target->y);
if (P_Random() < 128)
ang += ANG90;
else
ang -= ANG90;
ang >>= ANGLETOFINESHIFT;
actor->momx = FixedMul(8 * FRACUNIT, finecosine[ang]);
actor->momy = FixedMul(8 * FRACUNIT, finesine[ang]);
actor->special2.i = 3; // strafe time
}
}
}
FaceMovementDirection(actor);
// Normal movement
if (!actor->special2.i)
{
if (--actor->movecount < 0 || !P_Move(actor))
{
P_NewChaseDir(actor);
}
}
// Do missile attack
if (!(actor->flags & MF_JUSTATTACKED))
{
if (P_CheckMissileRange(actor) && (P_Random() < 20))
{
P_SetMobjState(actor, actor->info->missilestate);
actor->flags |= MF_JUSTATTACKED;
return;
}
}
else
{
actor->flags &= ~MF_JUSTATTACKED;
}
// make active sound
if (actor->info->activesound && P_Random() < 3)
{
S_StartSound(actor, actor->info->activesound);
}
}
void A_FiredSplotch(mobj_t * actor)
{
mobj_t *mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_FIREDEMON_SPLOTCH1);
if (mo)
{
mo->momx = (P_Random() - 128) << 11;
mo->momy = (P_Random() - 128) << 11;
mo->momz = FRACUNIT * 3 + (P_Random() << 10);
}
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_FIREDEMON_SPLOTCH2);
if (mo)
{
mo->momx = (P_Random() - 128) << 11;
mo->momy = (P_Random() - 128) << 11;
mo->momz = FRACUNIT * 3 + (P_Random() << 10);
}
}
//============================================================================
//
// A_IceGuyLook
//
//============================================================================
void A_IceGuyLook(mobj_t * actor)
{
fixed_t dist;
fixed_t an;
A_Look(actor);
if (P_Random() < 64)
{
dist = ((P_Random() - 128) * actor->radius) >> 7;
an = (actor->angle + ANG90) >> ANGLETOFINESHIFT;
P_SpawnMobj(actor->x + FixedMul(dist, finecosine[an]),
actor->y + FixedMul(dist, finesine[an]),
actor->z + 60 * FRACUNIT,
MT_ICEGUY_WISP1 + (P_Random() & 1));
}
}
//============================================================================
//
// A_IceGuyChase
//
//============================================================================
void A_IceGuyChase(mobj_t * actor)
{
fixed_t dist;
fixed_t an;
mobj_t *mo;
A_Chase(actor);
if (P_Random() < 128)
{
dist = ((P_Random() - 128) * actor->radius) >> 7;
an = (actor->angle + ANG90) >> ANGLETOFINESHIFT;
mo = P_SpawnMobj(actor->x + FixedMul(dist, finecosine[an]),
actor->y + FixedMul(dist, finesine[an]),
actor->z + 60 * FRACUNIT,
MT_ICEGUY_WISP1 + (P_Random() & 1));
if (mo)
{
mo->momx = actor->momx;
mo->momy = actor->momy;
mo->momz = actor->momz;
mo->target = actor;
}
}
}
//============================================================================
//
// A_IceGuyAttack
//
//============================================================================
void A_IceGuyAttack(mobj_t * actor)
{
fixed_t an;
if (!actor->target)
{
return;
}
an = (actor->angle + ANG90) >> ANGLETOFINESHIFT;
P_SpawnMissileXYZ(actor->x + FixedMul(actor->radius >> 1,
finecosine[an]),
actor->y + FixedMul(actor->radius >> 1, finesine[an]),
actor->z + 40 * FRACUNIT, actor, actor->target,
MT_ICEGUY_FX);
an = (actor->angle - ANG90) >> ANGLETOFINESHIFT;
P_SpawnMissileXYZ(actor->x + FixedMul(actor->radius >> 1,
finecosine[an]),
actor->y + FixedMul(actor->radius >> 1, finesine[an]),
actor->z + 40 * FRACUNIT, actor, actor->target,
MT_ICEGUY_FX);
S_StartSound(actor, actor->info->attacksound);
}
//============================================================================
//
// A_IceGuyMissilePuff
//
//============================================================================
void A_IceGuyMissilePuff(mobj_t * actor)
{
P_SpawnMobj(actor->x, actor->y, actor->z + 2 * FRACUNIT, MT_ICEFX_PUFF);
}
//============================================================================
//
// A_IceGuyDie
//
//============================================================================
void A_IceGuyDie(mobj_t * actor)
{
void A_FreezeDeathChunks(mobj_t * actor);
actor->momx = 0;
actor->momy = 0;
actor->momz = 0;
actor->height <<= 2;
A_FreezeDeathChunks(actor);
}
//============================================================================
//
// A_IceGuyMissileExplode
//
//============================================================================
void A_IceGuyMissileExplode(mobj_t * actor)
{
mobj_t *mo;
int i;
for (i = 0; i < 8; i++)
{
mo = P_SpawnMissileAngle(actor, MT_ICEGUY_FX2, i * ANG45,
-0.3 * FRACUNIT);
if (mo)
{
mo->target = actor->target;
}
}
}
//============================================================================
//
// Sorcerer stuff
//
// Sorcerer Variables
// special1 Angle of ball 1 (all others relative to that)
// special2 which ball to stop at in stop mode (MT_???)
// args[0] Denfense time
// args[1] Number of full rotations since stopping mode
// args[2] Target orbit speed for acceleration/deceleration
// args[3] Movement mode (see SORC_ macros)
// args[4] Current ball orbit speed
// Sorcerer Ball Variables
// special1 Previous angle of ball (for woosh)
// special2 Countdown of rapid fire (FX4)
// args[0] If set, don't play the bounce sound when bouncing
//============================================================================
#define SORCBALL_INITIAL_SPEED 7
#define SORCBALL_TERMINAL_SPEED 25
#define SORCBALL_SPEED_ROTATIONS 5
#define SORC_DEFENSE_TIME 255
#define SORC_DEFENSE_HEIGHT 45
#define BOUNCE_TIME_UNIT (35/2)
#define SORCFX4_RAPIDFIRE_TIME (6*3) // 3 seconds
#define SORCFX4_SPREAD_ANGLE 20
#define SORC_DECELERATE 0
#define SORC_ACCELERATE 1
#define SORC_STOPPING 2
#define SORC_FIRESPELL 3
#define SORC_STOPPED 4
#define SORC_NORMAL 5
#define SORC_FIRING_SPELL 6
#define BALL1_ANGLEOFFSET 0
#define BALL2_ANGLEOFFSET (ANG_MAX/3)
#define BALL3_ANGLEOFFSET ((ANG_MAX/3)*2)
void A_SorcBallOrbit(mobj_t * actor);
void A_SorcSpinBalls(mobj_t * actor);
void A_SpeedBalls(mobj_t * actor);
void A_SlowBalls(mobj_t * actor);
void A_StopBalls(mobj_t * actor);
void A_AccelBalls(mobj_t * actor);
void A_DecelBalls(mobj_t * actor);
void A_SorcBossAttack(mobj_t * actor);
void A_SpawnFizzle(mobj_t * actor);
void A_CastSorcererSpell(mobj_t * actor);
void A_SorcUpdateBallAngle(mobj_t * actor);
void A_BounceCheck(mobj_t * actor);
void A_SorcFX1Seek(mobj_t * actor);
void A_SorcOffense1(mobj_t * actor);
void A_SorcOffense2(mobj_t * actor);
// Spawn spinning balls above head - actor is sorcerer
void A_SorcSpinBalls(mobj_t * actor)
{
mobj_t *mo;
fixed_t z;
A_SlowBalls(actor);
actor->args[0] = 0; // Currently no defense
actor->args[3] = SORC_NORMAL;
actor->args[4] = SORCBALL_INITIAL_SPEED; // Initial orbit speed
actor->special1.i = ANG1;
z = actor->z - actor->floorclip + actor->info->height;
mo = P_SpawnMobj(actor->x, actor->y, z, MT_SORCBALL1);
if (mo)
{
mo->target = actor;
mo->special2.i = SORCFX4_RAPIDFIRE_TIME;
}
mo = P_SpawnMobj(actor->x, actor->y, z, MT_SORCBALL2);
if (mo)
mo->target = actor;
mo = P_SpawnMobj(actor->x, actor->y, z, MT_SORCBALL3);
if (mo)
mo->target = actor;
}
//
// A_SorcBallOrbit() ==========================================
//
void A_SorcBallOrbit(mobj_t * actor)
{
int x, y;
angle_t angle, baseangle;
int mode = actor->target->args[3];
mobj_t *parent = (mobj_t *) actor->target;
int dist = parent->radius - (actor->radius << 1);
angle_t prevangle = actor->special1.i;
if (actor->target->health <= 0)
P_SetMobjState(actor, actor->info->painstate);
baseangle = (angle_t) parent->special1.i;
switch (actor->type)
{
case MT_SORCBALL1:
angle = baseangle + BALL1_ANGLEOFFSET;
break;
case MT_SORCBALL2:
angle = baseangle + BALL2_ANGLEOFFSET;
break;
case MT_SORCBALL3:
angle = baseangle + BALL3_ANGLEOFFSET;
break;
default:
I_Error("corrupted sorcerer");
return;
}
actor->angle = angle;
angle >>= ANGLETOFINESHIFT;
switch (mode)
{
case SORC_NORMAL: // Balls rotating normally
A_SorcUpdateBallAngle(actor);
break;
case SORC_DECELERATE: // Balls decelerating
A_DecelBalls(actor);
A_SorcUpdateBallAngle(actor);
break;
case SORC_ACCELERATE: // Balls accelerating
A_AccelBalls(actor);
A_SorcUpdateBallAngle(actor);
break;
case SORC_STOPPING: // Balls stopping
if ((parent->special2.i == actor->type) &&
(parent->args[1] > SORCBALL_SPEED_ROTATIONS) &&
(abs(angle - (parent->angle >> ANGLETOFINESHIFT)) <
(30 << 5)))
{
// Can stop now
actor->target->args[3] = SORC_FIRESPELL;
actor->target->args[4] = 0;
// Set angle so ball angle == sorcerer angle
switch (actor->type)
{
case MT_SORCBALL1:
parent->special1.i = (int) (parent->angle -
BALL1_ANGLEOFFSET);
break;
case MT_SORCBALL2:
parent->special1.i = (int) (parent->angle -
BALL2_ANGLEOFFSET);
break;
case MT_SORCBALL3:
parent->special1.i = (int) (parent->angle -
BALL3_ANGLEOFFSET);
break;
default:
break;
}
}
else
{
A_SorcUpdateBallAngle(actor);
}
break;
case SORC_FIRESPELL: // Casting spell
if (parent->special2.i == actor->type)
{
// Put sorcerer into special throw spell anim
if (parent->health > 0)
P_SetMobjStateNF(parent, S_SORC_ATTACK1);
if (actor->type == MT_SORCBALL1 && P_Random() < 200)
{
S_StartSound(NULL, SFX_SORCERER_SPELLCAST);
actor->special2.i = SORCFX4_RAPIDFIRE_TIME;
actor->args[4] = 128;
parent->args[3] = SORC_FIRING_SPELL;
}
else
{
A_CastSorcererSpell(actor);
parent->args[3] = SORC_STOPPED;
}
}
break;
case SORC_FIRING_SPELL:
if (parent->special2.i == actor->type)
{
if (actor->special2.i-- <= 0)
{
// Done rapid firing
parent->args[3] = SORC_STOPPED;
// Back to orbit balls
if (parent->health > 0)
P_SetMobjStateNF(parent, S_SORC_ATTACK4);
}
else
{
// Do rapid fire spell
A_SorcOffense2(actor);
}
}
break;
case SORC_STOPPED: // Balls stopped
default:
break;
}
if ((angle < prevangle) && (parent->args[4] == SORCBALL_TERMINAL_SPEED))
{
parent->args[1]++; // Bump rotation counter
// Completed full rotation - make woosh sound
S_StartSound(actor, SFX_SORCERER_BALLWOOSH);
}
actor->special1.i = angle; // Set previous angle
x = parent->x + FixedMul(dist, finecosine[angle]);
y = parent->y + FixedMul(dist, finesine[angle]);
actor->x = x;
actor->y = y;
actor->z = parent->z - parent->floorclip + parent->info->height;
}
//
// Set balls to speed mode - actor is sorcerer
//
void A_SpeedBalls(mobj_t * actor)
{
actor->args[3] = SORC_ACCELERATE; // speed mode
actor->args[2] = SORCBALL_TERMINAL_SPEED; // target speed
}
//
// Set balls to slow mode - actor is sorcerer
//
void A_SlowBalls(mobj_t * actor)
{
actor->args[3] = SORC_DECELERATE; // slow mode
actor->args[2] = SORCBALL_INITIAL_SPEED; // target speed
}
//
// Instant stop when rotation gets to ball in special2
// actor is sorcerer
//
void A_StopBalls(mobj_t * actor)
{
int chance = P_Random();
actor->args[3] = SORC_STOPPING; // stopping mode
actor->args[1] = 0; // Reset rotation counter
if ((actor->args[0] <= 0) && (chance < 200))
{
actor->special2.i = MT_SORCBALL2; // Blue
}
else if ((actor->health < (actor->info->spawnhealth >> 1)) &&
(chance < 200))
{
actor->special2.i = MT_SORCBALL3; // Green
}
else
{
actor->special2.i = MT_SORCBALL1; // Yellow
}
}
//
// Increase ball orbit speed - actor is ball
//
void A_AccelBalls(mobj_t * actor)
{
mobj_t *sorc = actor->target;
if (sorc->args[4] < sorc->args[2])
{
sorc->args[4]++;
}
else
{
sorc->args[3] = SORC_NORMAL;
if (sorc->args[4] >= SORCBALL_TERMINAL_SPEED)
{
// Reached terminal velocity - stop balls
A_StopBalls(sorc);
}
}
}
// Decrease ball orbit speed - actor is ball
void A_DecelBalls(mobj_t * actor)
{
mobj_t *sorc = actor->target;
if (sorc->args[4] > sorc->args[2])
{
sorc->args[4]--;
}
else
{
sorc->args[3] = SORC_NORMAL;
}
}
// Update angle if first ball - actor is ball
void A_SorcUpdateBallAngle(mobj_t * actor)
{
if (actor->type == MT_SORCBALL1)
{
actor->target->special1.i += ANG1 * actor->target->args[4];
}
}
// actor is ball
void A_CastSorcererSpell(mobj_t * actor)
{
mobj_t *mo;
int spell = actor->type;
angle_t ang1, ang2;
fixed_t z;
mobj_t *parent = actor->target;
S_StartSound(NULL, SFX_SORCERER_SPELLCAST);
// Put sorcerer into throw spell animation
if (parent->health > 0)
P_SetMobjStateNF(parent, S_SORC_ATTACK4);
switch (spell)
{
case MT_SORCBALL1: // Offensive
A_SorcOffense1(actor);
break;
case MT_SORCBALL2: // Defensive
z = parent->z - parent->floorclip +
SORC_DEFENSE_HEIGHT * FRACUNIT;
mo = P_SpawnMobj(actor->x, actor->y, z, MT_SORCFX2);
parent->flags2 |= MF2_REFLECTIVE | MF2_INVULNERABLE;
parent->args[0] = SORC_DEFENSE_TIME;
if (mo)
mo->target = parent;
break;
case MT_SORCBALL3: // Reinforcements
ang1 = actor->angle - ANG45;
ang2 = actor->angle + ANG45;
if (actor->health < (actor->info->spawnhealth / 3))
{ // Spawn 2 at a time
mo = P_SpawnMissileAngle(parent, MT_SORCFX3, ang1,
4 * FRACUNIT);
if (mo)
mo->target = parent;
mo = P_SpawnMissileAngle(parent, MT_SORCFX3, ang2,
4 * FRACUNIT);
if (mo)
mo->target = parent;
}
else
{
if (P_Random() < 128)
ang1 = ang2;
mo = P_SpawnMissileAngle(parent, MT_SORCFX3, ang1,
4 * FRACUNIT);
if (mo)
mo->target = parent;
}
break;
default:
break;
}
}
/*
void A_SpawnReinforcements(mobj_t *actor)
{
mobj_t *parent = actor->target;
mobj_t *mo;
angle_t ang;
ang = ANG1 * P_Random();
mo = P_SpawnMissileAngle(actor, MT_SORCFX3, ang, 5*FRACUNIT);
if (mo) mo->target = parent;
}
*/
// actor is ball
void A_SorcOffense1(mobj_t * actor)
{
mobj_t *mo;
angle_t ang1, ang2;
mobj_t *parent = (mobj_t *) actor->target;
ang1 = actor->angle + ANG1 * 70;
ang2 = actor->angle - ANG1 * 70;
mo = P_SpawnMissileAngle(parent, MT_SORCFX1, ang1, 0);
if (mo)
{
mo->target = parent;
mo->special1.m = parent->target;
mo->args[4] = BOUNCE_TIME_UNIT;
mo->args[3] = 15; // Bounce time in seconds
}
mo = P_SpawnMissileAngle(parent, MT_SORCFX1, ang2, 0);
if (mo)
{
mo->target = parent;
mo->special1.m = parent->target;
mo->args[4] = BOUNCE_TIME_UNIT;
mo->args[3] = 15; // Bounce time in seconds
}
}
// Actor is ball
void A_SorcOffense2(mobj_t * actor)
{
angle_t ang1;
mobj_t *mo;
int delta, index;
mobj_t *parent = actor->target;
mobj_t *dest = parent->target;
int dist;
index = actor->args[4] << 5;
actor->args[4] += 15;
delta = (finesine[index]) * SORCFX4_SPREAD_ANGLE;
delta = (delta >> FRACBITS) * ANG1;
ang1 = actor->angle + delta;
mo = P_SpawnMissileAngle(parent, MT_SORCFX4, ang1, 0);
if (mo)
{
mo->special2.i = 35 * 5 / 2; // 5 seconds
dist = P_AproxDistance(dest->x - mo->x, dest->y - mo->y);
dist = dist / mo->info->speed;
if (dist < 1)
dist = 1;
mo->momz = (dest->z - mo->z) / dist;
}
}
// Resume ball spinning
void A_SorcBossAttack(mobj_t * actor)
{
actor->args[3] = SORC_ACCELERATE;
actor->args[2] = SORCBALL_INITIAL_SPEED;
}
// spell cast magic fizzle
void A_SpawnFizzle(mobj_t * actor)
{
fixed_t x, y, z;
fixed_t dist = 5 * FRACUNIT;
angle_t angle = actor->angle >> ANGLETOFINESHIFT;
fixed_t speed = actor->info->speed;
angle_t rangle;
mobj_t *mo;
int ix;
x = actor->x + FixedMul(dist, finecosine[angle]);
y = actor->y + FixedMul(dist, finesine[angle]);
z = actor->z - actor->floorclip + (actor->height >> 1);
for (ix = 0; ix < 5; ix++)
{
mo = P_SpawnMobj(x, y, z, MT_SORCSPARK1);
if (mo)
{
rangle = angle + ((P_Random() % 5) << 1);
mo->momx = FixedMul(P_Random() % speed, finecosine[rangle]);
mo->momy = FixedMul(P_Random() % speed, finesine[rangle]);
mo->momz = FRACUNIT * 2;
}
}
}
//============================================================================
// Yellow spell - offense
//============================================================================
void A_SorcFX1Seek(mobj_t * actor)
{
A_BounceCheck(actor);
P_SeekerMissile(actor, ANG1 * 2, ANG1 * 6);
}
//============================================================================
// Blue spell - defense
//============================================================================
//
// FX2 Variables
// special1 current angle
// special2
// args[0] 0 = CW, 1 = CCW
// args[1]
//============================================================================
// Split ball in two
void A_SorcFX2Split(mobj_t * actor)
{
mobj_t *mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SORCFX2);
if (mo)
{
mo->target = actor->target;
mo->args[0] = 0; // CW
mo->special1.i = actor->angle; // Set angle
P_SetMobjStateNF(mo, S_SORCFX2_ORBIT1);
}
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_SORCFX2);
if (mo)
{
mo->target = actor->target;
mo->args[0] = 1; // CCW
mo->special1.i = actor->angle; // Set angle
P_SetMobjStateNF(mo, S_SORCFX2_ORBIT1);
}
P_SetMobjStateNF(actor, S_NULL);
}
// Orbit FX2 about sorcerer
void A_SorcFX2Orbit(mobj_t * actor)
{
angle_t angle;
fixed_t x, y, z;
mobj_t *parent = actor->target;
fixed_t dist = parent->info->radius;
if ((parent->health <= 0) || // Sorcerer is dead
(!parent->args[0])) // Time expired
{
P_SetMobjStateNF(actor, actor->info->deathstate);
parent->args[0] = 0;
parent->flags2 &= ~MF2_REFLECTIVE;
parent->flags2 &= ~MF2_INVULNERABLE;
}
if (actor->args[0] && (parent->args[0]-- <= 0)) // Time expired
{
P_SetMobjStateNF(actor, actor->info->deathstate);
parent->args[0] = 0;
parent->flags2 &= ~MF2_REFLECTIVE;
}
// Move to new position based on angle
if (actor->args[0]) // Counter clock-wise
{
actor->special1.i += ANG1 * 10;
angle = ((angle_t) actor->special1.i) >> ANGLETOFINESHIFT;
x = parent->x + FixedMul(dist, finecosine[angle]);
y = parent->y + FixedMul(dist, finesine[angle]);
z = parent->z - parent->floorclip + SORC_DEFENSE_HEIGHT * FRACUNIT;
z += FixedMul(15 * FRACUNIT, finecosine[angle]);
// Spawn trailer
P_SpawnMobj(x, y, z, MT_SORCFX2_T1);
}
else // Clock wise
{
actor->special1.i -= ANG1 * 10;
angle = ((angle_t) actor->special1.i) >> ANGLETOFINESHIFT;
x = parent->x + FixedMul(dist, finecosine[angle]);
y = parent->y + FixedMul(dist, finesine[angle]);
z = parent->z - parent->floorclip + SORC_DEFENSE_HEIGHT * FRACUNIT;
z += FixedMul(20 * FRACUNIT, finesine[angle]);
// Spawn trailer
P_SpawnMobj(x, y, z, MT_SORCFX2_T1);
}
actor->x = x;
actor->y = y;
actor->z = z;
}
//============================================================================
// Green spell - spawn bishops
//============================================================================
void A_SpawnBishop(mobj_t * actor)
{
mobj_t *mo;
mo = P_SpawnMobj(actor->x, actor->y, actor->z, MT_BISHOP);
if (mo)
{
if (!P_TestMobjLocation(mo))
{
P_SetMobjState(mo, S_NULL);
}
}
P_SetMobjState(actor, S_NULL);
}
/*
void A_SmokePuffEntry(mobj_t *actor)
{
P_SpawnMobj(actor->x, actor->y, actor->z, MT_MNTRSMOKE);
}
*/
void A_SmokePuffExit(mobj_t * actor)
{
P_SpawnMobj(actor->x, actor->y, actor->z, MT_MNTRSMOKEEXIT);
}
void A_SorcererBishopEntry(mobj_t * actor)
{
P_SpawnMobj(actor->x, actor->y, actor->z, MT_SORCFX3_EXPLOSION);
S_StartSound(actor, actor->info->seesound);
}
//============================================================================
// FX4 - rapid fire balls
//============================================================================
void A_SorcFX4Check(mobj_t * actor)
{
if (actor->special2.i-- <= 0)
{
P_SetMobjStateNF(actor, actor->info->deathstate);
}
}
//============================================================================
// Ball death - spawn stuff
//============================================================================
void A_SorcBallPop(mobj_t * actor)
{
S_StartSound(NULL, SFX_SORCERER_BALLPOP);
actor->flags &= ~MF_NOGRAVITY;
actor->flags2 |= MF2_LOGRAV;
actor->momx = ((P_Random() % 10) - 5) << FRACBITS;
actor->momy = ((P_Random() % 10) - 5) << FRACBITS;
actor->momz = (2 + (P_Random() % 3)) << FRACBITS;
actor->special2.i = 4 * FRACUNIT; // Initial bounce factor
actor->args[4] = BOUNCE_TIME_UNIT; // Bounce time unit
actor->args[3] = 5; // Bounce time in seconds
}
void A_BounceCheck(mobj_t * actor)
{
if (actor->args[4]-- <= 0)
{
if (actor->args[3]-- <= 0)
{
P_SetMobjState(actor, actor->info->deathstate);
switch (actor->type)
{
case MT_SORCBALL1:
case MT_SORCBALL2:
case MT_SORCBALL3:
S_StartSound(NULL, SFX_SORCERER_BIGBALLEXPLODE);
break;
case MT_SORCFX1:
S_StartSound(NULL, SFX_SORCERER_HEADSCREAM);
break;
default:
break;
}
}
else
{
actor->args[4] = BOUNCE_TIME_UNIT;
}
}
}
//============================================================================
// Class Bosses
//============================================================================
#define CLASS_BOSS_STRAFE_RANGE 64*10*FRACUNIT
void A_FastChase(mobj_t * actor)
{
int delta;
fixed_t dist;
angle_t ang;
mobj_t *target;
if (actor->reactiontime)
{
actor->reactiontime--;
}
// Modify target threshold
if (actor->threshold)
{
actor->threshold--;
}
if (gameskill == sk_nightmare)
{ // Monsters move faster in nightmare mode
actor->tics -= actor->tics / 2;
if (actor->tics < 3)
{
actor->tics = 3;
}
}
//
// 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))
{ // got a new target
return;
}
P_SetMobjState(actor, actor->info->spawnstate);
return;
}
//
// don't attack twice in a row
//
if (actor->flags & MF_JUSTATTACKED)
{
actor->flags &= ~MF_JUSTATTACKED;
if (gameskill != sk_nightmare)
P_NewChaseDir(actor);
return;
}
// Strafe
if (actor->special2.i > 0)
{
actor->special2.i--;
}
else
{
target = actor->target;
actor->special2.i = 0;
actor->momx = actor->momy = 0;
dist = P_AproxDistance(actor->x - target->x, actor->y - target->y);
if (dist < CLASS_BOSS_STRAFE_RANGE)
{
if (P_Random() < 100)
{
ang = R_PointToAngle2(actor->x, actor->y,
target->x, target->y);
if (P_Random() < 128)
ang += ANG90;
else
ang -= ANG90;
ang >>= ANGLETOFINESHIFT;
actor->momx = FixedMul(13 * FRACUNIT, finecosine[ang]);
actor->momy = FixedMul(13 * FRACUNIT, finesine[ang]);
actor->special2.i = 3; // strafe time
}
}
}
//
// check for missile attack
//
if (actor->info->missilestate)
{
if (gameskill < sk_nightmare && 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->special2.i)
{
if (--actor->movecount < 0 || !P_Move(actor))
{
P_NewChaseDir(actor);
}
}
}
void A_FighterAttack(mobj_t * actor)
{
extern void A_FSwordAttack2(mobj_t * actor);
if (!actor->target)
return;
A_FSwordAttack2(actor);
}
void A_ClericAttack(mobj_t * actor)
{
extern void A_CHolyAttack3(mobj_t * actor);
if (!actor->target)
return;
A_CHolyAttack3(actor);
}
void A_MageAttack(mobj_t * actor)
{
extern void A_MStaffAttack2(mobj_t * actor);
if (!actor->target)
return;
A_MStaffAttack2(actor);
}
void A_ClassBossHealth(mobj_t * actor)
{
if (netgame && !deathmatch) // co-op only
{
if (!actor->special1.i)
{
actor->health *= 5;
actor->special1.i = true; // has been initialized
}
}
}
//===========================================================================
//
// A_CheckFloor - Checks if an object hit the floor
//
//===========================================================================
void A_CheckFloor(mobj_t * actor)
{
if (actor->z <= actor->floorz)
{
actor->z = actor->floorz;
actor->flags2 &= ~MF2_LOGRAV;
P_SetMobjState(actor, actor->info->deathstate);
}
}
//============================================================================
//
// A_FreezeDeath
//
//============================================================================
void A_FreezeDeath(mobj_t * actor)
{
actor->tics = 75 + P_Random() + P_Random();
actor->flags |= MF_SOLID | MF_SHOOTABLE | MF_NOBLOOD;
actor->flags2 |= MF2_PUSHABLE | MF2_TELESTOMP | MF2_PASSMOBJ | MF2_SLIDE;
actor->height <<= 2;
S_StartSound(actor, SFX_FREEZE_DEATH);
if (actor->player)
{
actor->player->damagecount = 0;
actor->player->poisoncount = 0;
actor->player->bonuscount = 0;
if (actor->player == &players[consoleplayer])
{
SB_PaletteFlash(false);
}
}
else if (actor->flags & MF_COUNTKILL && actor->special)
{ // Initiate monster death actions
P_ExecuteLineSpecial(actor->special, actor->args, NULL, 0, actor);
}
}
//============================================================================
//
// A_IceSetTics
//
//============================================================================
void A_IceSetTics(mobj_t * actor)
{
int floor;
actor->tics = 70 + (P_Random() & 63);
floor = P_GetThingFloorType(actor);
if (floor == FLOOR_LAVA)
{
actor->tics >>= 2;
}
else if (floor == FLOOR_ICE)
{
actor->tics <<= 1;
}
}
//============================================================================
//
// A_IceCheckHeadDone
//
//============================================================================
void A_IceCheckHeadDone(mobj_t * actor)
{
if (actor->special2.i == 666)
{
P_SetMobjState(actor, S_ICECHUNK_HEAD2);
}
}
//============================================================================
//
// A_FreezeDeathChunks
//
//============================================================================
void A_FreezeDeathChunks(mobj_t * actor)
{
int i;
mobj_t *mo;
if (actor->momx || actor->momy || actor->momz)
{
actor->tics = 105;
return;
}
S_StartSound(actor, SFX_FREEZE_SHATTER);
for (i = 12 + (P_Random() & 15); i >= 0; i--)
{
mo = P_SpawnMobj(actor->x +
(((P_Random() - 128) * actor->radius) >> 7),
actor->y +
(((P_Random() - 128) * actor->radius) >> 7),
actor->z + (P_Random() * actor->height / 255),
MT_ICECHUNK);
P_SetMobjState(mo, mo->info->spawnstate + (P_Random() % 3));
if (mo)
{
mo->momz = FixedDiv(mo->z - actor->z, actor->height) << 2;
mo->momx = (P_Random() - P_Random()) << (FRACBITS - 7);
mo->momy = (P_Random() - P_Random()) << (FRACBITS - 7);
A_IceSetTics(mo); // set a random tic wait
}
}
for (i = 12 + (P_Random() & 15); i >= 0; i--)
{
mo = P_SpawnMobj(actor->x +
(((P_Random() - 128) * actor->radius) >> 7),
actor->y +
(((P_Random() - 128) * actor->radius) >> 7),
actor->z + (P_Random() * actor->height / 255),
MT_ICECHUNK);
P_SetMobjState(mo, mo->info->spawnstate + (P_Random() % 3));
if (mo)
{
mo->momz = FixedDiv(mo->z - actor->z, actor->height) << 2;
mo->momx = (P_Random() - P_Random()) << (FRACBITS - 7);
mo->momy = (P_Random() - P_Random()) << (FRACBITS - 7);
A_IceSetTics(mo); // set a random tic wait
}
}
if (actor->player)
{ // attach the player's view to a chunk of ice
mo = P_SpawnMobj(actor->x, actor->y, actor->z + VIEWHEIGHT,
MT_ICECHUNK);
P_SetMobjState(mo, S_ICECHUNK_HEAD);
mo->momz = FixedDiv(mo->z - actor->z, actor->height) << 2;
mo->momx = (P_Random() - P_Random()) << (FRACBITS - 7);
mo->momy = (P_Random() - P_Random()) << (FRACBITS - 7);
mo->flags2 |= MF2_ICEDAMAGE; // used to force blue palette
mo->flags2 &= ~MF2_FLOORCLIP;
mo->player = actor->player;
actor->player = NULL;
mo->health = actor->health;
mo->angle = actor->angle;
mo->player->mo = mo;
mo->player->lookdir = 0;
}
P_RemoveMobjFromTIDList(actor);
P_SetMobjState(actor, S_FREETARGMOBJ);
actor->flags2 |= MF2_DONTDRAW;
}
//===========================================================================
// Korax Variables
// special1 last teleport destination
// special2 set if "below half" script not yet run
//
// Korax Scripts (reserved)
// 249 Tell scripts that we are below half health
// 250-254 Control scripts
// 255 Death script
//
// Korax TIDs (reserved)
// 245 Reserved for Korax himself
// 248 Initial teleport destination
// 249 Teleport destination
// 250-254 For use in respective control scripts
// 255 For use in death script (spawn spots)
//===========================================================================
#define KORAX_SPIRIT_LIFETIME (5*(35/5)) // 5 seconds
#define KORAX_COMMAND_HEIGHT (120*FRACUNIT)
#define KORAX_COMMAND_OFFSET (27*FRACUNIT)
void KoraxFire1(mobj_t * actor, int type);
void KoraxFire2(mobj_t * actor, int type);
void KoraxFire3(mobj_t * actor, int type);
void KoraxFire4(mobj_t * actor, int type);
void KoraxFire5(mobj_t * actor, int type);
void KoraxFire6(mobj_t * actor, int type);
void KSpiritInit(mobj_t * spirit, mobj_t * korax);
#define KORAX_TID (245)
#define KORAX_FIRST_TELEPORT_TID (248)
#define KORAX_TELEPORT_TID (249)
void A_KoraxChase(mobj_t * actor)
{
mobj_t *spot;
int lastfound;
byte args[3] = { 0, 0, 0 };
if ((!actor->special2.i) &&
(actor->health <= (actor->info->spawnhealth / 2)))
{
lastfound = 0;
spot = P_FindMobjFromTID(KORAX_FIRST_TELEPORT_TID, &lastfound);
if (spot)
{
P_Teleport(actor, spot->x, spot->y, spot->angle, true);
}
P_StartACS(249, 0, args, actor, NULL, 0);
actor->special2.i = 1; // Don't run again
return;
}
if (!actor->target)
return;
if (P_Random() < 30)
{
P_SetMobjState(actor, actor->info->missilestate);
}
else if (P_Random() < 30)
{
S_StartSound(NULL, SFX_KORAX_ACTIVE);
}
// Teleport away
if (actor->health < (actor->info->spawnhealth >> 1))
{
if (P_Random() < 10)
{
lastfound = actor->special1.i;
spot = P_FindMobjFromTID(KORAX_TELEPORT_TID, &lastfound);
actor->special1.i = lastfound;
if (spot)
{
P_Teleport(actor, spot->x, spot->y, spot->angle, true);
}
}
}
}
void A_KoraxStep(mobj_t * actor)
{
A_Chase(actor);
}
void A_KoraxStep2(mobj_t * actor)
{
S_StartSound(NULL, SFX_KORAX_STEP);
A_Chase(actor);
}
void A_KoraxBonePop(mobj_t * actor)
{
mobj_t *mo;
byte args[5];
args[0] = args[1] = args[2] = args[3] = args[4] = 0;
// Spawn 6 spirits equalangularly
mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT1, ANG60 * 0,
5 * FRACUNIT);
if (mo)
KSpiritInit(mo, actor);
mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT2, ANG60 * 1,
5 * FRACUNIT);
if (mo)
KSpiritInit(mo, actor);
mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT3, ANG60 * 2,
5 * FRACUNIT);
if (mo)
KSpiritInit(mo, actor);
mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT4, ANG60 * 3,
5 * FRACUNIT);
if (mo)
KSpiritInit(mo, actor);
mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT5, ANG60 * 4,
5 * FRACUNIT);
if (mo)
KSpiritInit(mo, actor);
mo = P_SpawnMissileAngle(actor, MT_KORAX_SPIRIT6, ANG60 * 5,
5 * FRACUNIT);
if (mo)
KSpiritInit(mo, actor);
P_StartACS(255, 0, args, actor, NULL, 0); // Death script
}
void KSpiritInit(mobj_t * spirit, mobj_t * korax)
{
int i;
mobj_t *tail, *next;
spirit->health = KORAX_SPIRIT_LIFETIME;
spirit->special1.m = korax; // Swarm around korax
spirit->special2.i = 32 + (P_Random() & 7); // Float bob index
spirit->args[0] = 10; // initial turn value
spirit->args[1] = 0; // initial look angle
// Spawn a tail for spirit
tail = P_SpawnMobj(spirit->x, spirit->y, spirit->z, MT_HOLY_TAIL);
tail->special2.m = spirit; // parent
for (i = 1; i < 3; i++)
{
next = P_SpawnMobj(spirit->x, spirit->y, spirit->z, MT_HOLY_TAIL);
P_SetMobjState(next, next->info->spawnstate + 1);
tail->special1.m = next;
tail = next;
}
tail->special1.m = NULL; // last tail bit
}
void A_KoraxDecide(mobj_t * actor)
{
if (P_Random() < 220)
{
P_SetMobjState(actor, S_KORAX_MISSILE1);
}
else
{
P_SetMobjState(actor, S_KORAX_COMMAND1);
}
}
void A_KoraxMissile(mobj_t * actor)
{
int type = P_Random() % 6;
int sound = 0;
S_StartSound(actor, SFX_KORAX_ATTACK);
switch (type)
{
case 0:
type = MT_WRAITHFX1;
sound = SFX_WRAITH_MISSILE_FIRE;
break;
case 1:
type = MT_DEMONFX1;
sound = SFX_DEMON_MISSILE_FIRE;
break;
case 2:
type = MT_DEMON2FX1;
sound = SFX_DEMON_MISSILE_FIRE;
break;
case 3:
type = MT_FIREDEMON_FX6;
sound = SFX_FIRED_ATTACK;
break;
case 4:
type = MT_CENTAUR_FX;
sound = SFX_CENTAURLEADER_ATTACK;
break;
case 5:
type = MT_SERPENTFX;
sound = SFX_CENTAURLEADER_ATTACK;
break;
}
// Fire all 6 missiles at once
S_StartSound(NULL, sound);
KoraxFire1(actor, type);
KoraxFire2(actor, type);
KoraxFire3(actor, type);
KoraxFire4(actor, type);
KoraxFire5(actor, type);
KoraxFire6(actor, type);
}
// Call action code scripts (250-254)
void A_KoraxCommand(mobj_t * actor)
{
byte args[5];
fixed_t x, y, z;
angle_t ang;
int numcommands;
S_StartSound(actor, SFX_KORAX_COMMAND);
// Shoot stream of lightning to ceiling
ang = (actor->angle - ANG90) >> ANGLETOFINESHIFT;
x = actor->x + FixedMul(KORAX_COMMAND_OFFSET, finecosine[ang]);
y = actor->y + FixedMul(KORAX_COMMAND_OFFSET, finesine[ang]);
z = actor->z + KORAX_COMMAND_HEIGHT;
P_SpawnMobj(x, y, z, MT_KORAX_BOLT);
args[0] = args[1] = args[2] = args[3] = args[4] = 0;
if (actor->health <= (actor->info->spawnhealth >> 1))
{
numcommands = 5;
}
else
{
numcommands = 4;
}
switch (P_Random() % numcommands)
{
case 0:
P_StartACS(250, 0, args, actor, NULL, 0);
break;
case 1:
P_StartACS(251, 0, args, actor, NULL, 0);
break;
case 2:
P_StartACS(252, 0, args, actor, NULL, 0);
break;
case 3:
P_StartACS(253, 0, args, actor, NULL, 0);
break;
case 4:
P_StartACS(254, 0, args, actor, NULL, 0);
break;
}
}
#define KORAX_DELTAANGLE (85*ANG1)
#define KORAX_ARM_EXTENSION_SHORT (40*FRACUNIT)
#define KORAX_ARM_EXTENSION_LONG (55*FRACUNIT)
#define KORAX_ARM1_HEIGHT (108*FRACUNIT)
#define KORAX_ARM2_HEIGHT (82*FRACUNIT)
#define KORAX_ARM3_HEIGHT (54*FRACUNIT)
#define KORAX_ARM4_HEIGHT (104*FRACUNIT)
#define KORAX_ARM5_HEIGHT (86*FRACUNIT)
#define KORAX_ARM6_HEIGHT (53*FRACUNIT)
// Arm projectiles
// arm positions numbered:
// 1 top left
// 2 middle left
// 3 lower left
// 4 top right
// 5 middle right
// 6 lower right
// Arm 1 projectile
void KoraxFire1(mobj_t * actor, int type)
{
angle_t ang;
fixed_t x, y, z;
ang = (actor->angle - KORAX_DELTAANGLE) >> ANGLETOFINESHIFT;
x = actor->x + FixedMul(KORAX_ARM_EXTENSION_SHORT, finecosine[ang]);
y = actor->y + FixedMul(KORAX_ARM_EXTENSION_SHORT, finesine[ang]);
z = actor->z - actor->floorclip + KORAX_ARM1_HEIGHT;
P_SpawnKoraxMissile(x, y, z, actor, actor->target, type);
}
// Arm 2 projectile
void KoraxFire2(mobj_t * actor, int type)
{
angle_t ang;
fixed_t x, y, z;
ang = (actor->angle - KORAX_DELTAANGLE) >> ANGLETOFINESHIFT;
x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine[ang]);
y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine[ang]);
z = actor->z - actor->floorclip + KORAX_ARM2_HEIGHT;
P_SpawnKoraxMissile(x, y, z, actor, actor->target, type);
}
// Arm 3 projectile
void KoraxFire3(mobj_t * actor, int type)
{
angle_t ang;
fixed_t x, y, z;
ang = (actor->angle - KORAX_DELTAANGLE) >> ANGLETOFINESHIFT;
x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine[ang]);
y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine[ang]);
z = actor->z - actor->floorclip + KORAX_ARM3_HEIGHT;
P_SpawnKoraxMissile(x, y, z, actor, actor->target, type);
}
// Arm 4 projectile
void KoraxFire4(mobj_t * actor, int type)
{
angle_t ang;
fixed_t x, y, z;
ang = (actor->angle + KORAX_DELTAANGLE) >> ANGLETOFINESHIFT;
x = actor->x + FixedMul(KORAX_ARM_EXTENSION_SHORT, finecosine[ang]);
y = actor->y + FixedMul(KORAX_ARM_EXTENSION_SHORT, finesine[ang]);
z = actor->z - actor->floorclip + KORAX_ARM4_HEIGHT;
P_SpawnKoraxMissile(x, y, z, actor, actor->target, type);
}
// Arm 5 projectile
void KoraxFire5(mobj_t * actor, int type)
{
angle_t ang;
fixed_t x, y, z;
ang = (actor->angle + KORAX_DELTAANGLE) >> ANGLETOFINESHIFT;
x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine[ang]);
y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine[ang]);
z = actor->z - actor->floorclip + KORAX_ARM5_HEIGHT;
P_SpawnKoraxMissile(x, y, z, actor, actor->target, type);
}
// Arm 6 projectile
void KoraxFire6(mobj_t * actor, int type)
{
angle_t ang;
fixed_t x, y, z;
ang = (actor->angle + KORAX_DELTAANGLE) >> ANGLETOFINESHIFT;
x = actor->x + FixedMul(KORAX_ARM_EXTENSION_LONG, finecosine[ang]);
y = actor->y + FixedMul(KORAX_ARM_EXTENSION_LONG, finesine[ang]);
z = actor->z - actor->floorclip + KORAX_ARM6_HEIGHT;
P_SpawnKoraxMissile(x, y, z, actor, actor->target, type);
}
void A_KSpiritWeave(mobj_t * actor)
{
fixed_t newX, newY;
int weaveXY, weaveZ;
int angle;
weaveXY = actor->special2.i >> 16;
weaveZ = actor->special2.i & 0xFFFF;
angle = (actor->angle + ANG90) >> ANGLETOFINESHIFT;
newX = actor->x - FixedMul(finecosine[angle],
FloatBobOffsets[weaveXY] << 2);
newY = actor->y - FixedMul(finesine[angle],
FloatBobOffsets[weaveXY] << 2);
weaveXY = (weaveXY + (P_Random() % 5)) & 63;
newX += FixedMul(finecosine[angle], FloatBobOffsets[weaveXY] << 2);
newY += FixedMul(finesine[angle], FloatBobOffsets[weaveXY] << 2);
P_TryMove(actor, newX, newY);
actor->z -= FloatBobOffsets[weaveZ] << 1;
weaveZ = (weaveZ + (P_Random() % 5)) & 63;
actor->z += FloatBobOffsets[weaveZ] << 1;
actor->special2.i = weaveZ + (weaveXY << 16);
}
void A_KSpiritSeeker(mobj_t * actor, angle_t thresh, angle_t turnMax)
{
int dir;
int dist;
angle_t delta;
angle_t angle;
mobj_t *target;
fixed_t newZ;
fixed_t deltaZ;
target = actor->special1.m;
if (target == NULL)
{
return;
}
dir = P_FaceMobj(actor, target, &delta);
if (delta > thresh)
{
delta >>= 1;
if (delta > turnMax)
{
delta = turnMax;
}
}
if (dir)
{ // Turn clockwise
actor->angle += delta;
}
else
{ // Turn counter clockwise
actor->angle -= delta;
}
angle = actor->angle >> ANGLETOFINESHIFT;
actor->momx = FixedMul(actor->info->speed, finecosine[angle]);
actor->momy = FixedMul(actor->info->speed, finesine[angle]);
if (!(leveltime & 15)
|| actor->z > target->z + (target->info->height)
|| actor->z + actor->height < target->z)
{
newZ = target->z + ((P_Random() * target->info->height) >> 8);
deltaZ = newZ - actor->z;
if (abs(deltaZ) > 15 * FRACUNIT)
{
if (deltaZ > 0)
{
deltaZ = 15 * FRACUNIT;
}
else
{
deltaZ = -15 * FRACUNIT;
}
}
dist = P_AproxDistance(target->x - actor->x, target->y - actor->y);
dist = dist / actor->info->speed;
if (dist < 1)
{
dist = 1;
}
actor->momz = deltaZ / dist;
}
return;
}
void A_KSpiritRoam(mobj_t * actor)
{
if (actor->health-- <= 0)
{
S_StartSound(actor, SFX_SPIRIT_DIE);
P_SetMobjState(actor, S_KSPIRIT_DEATH1);
}
else
{
if (actor->special1.m)
{
A_KSpiritSeeker(actor, actor->args[0] * ANG1,
actor->args[0] * ANG1 * 2);
}
A_KSpiritWeave(actor);
if (P_Random() < 50)
{
S_StartSound(NULL, SFX_SPIRIT_ACTIVE);
}
}
}
void A_KBolt(mobj_t * actor)
{
// Countdown lifetime
if (actor->special1.i-- <= 0)
{
P_SetMobjState(actor, S_NULL);
}
}
#define KORAX_BOLT_HEIGHT 48*FRACUNIT
#define KORAX_BOLT_LIFETIME 3
void A_KBoltRaise(mobj_t * actor)
{
mobj_t *mo;
fixed_t z;
// Spawn a child upward
z = actor->z + KORAX_BOLT_HEIGHT;
if ((z + KORAX_BOLT_HEIGHT) < actor->ceilingz)
{
mo = P_SpawnMobj(actor->x, actor->y, z, MT_KORAX_BOLT);
if (mo)
{
mo->special1.i = KORAX_BOLT_LIFETIME;
}
}
else
{
// Maybe cap it off here
}
}