From 0df2cb80cf03d7259746834220d209b306a8c503 Mon Sep 17 00:00:00 2001 From: Simon Howard Date: Thu, 4 Sep 2008 23:15:36 +0000 Subject: Add GPLed Heretic/Hexen source. Subversion-branch: /branches/raven-branch Subversion-revision: 1195 --- src/hexen/p_enemy.c | 5308 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 5308 insertions(+) create mode 100644 src/hexen/p_enemy.c (limited to 'src/hexen/p_enemy.c') diff --git a/src/hexen/p_enemy.c b/src/hexen/p_enemy.c new file mode 100644 index 00000000..f9ad16eb --- /dev/null +++ b/src/hexen/p_enemy.c @@ -0,0 +1,5308 @@ + +//************************************************************************** +//** +//** p_enemy.c : Heretic 2 : Raven Software, Corp. +//** +//** $RCSfile: p_enemy.c,v $ +//** $Revision: 1.170 $ +//** $Date: 96/01/06 03:23:28 $ +//** $Author: bgokey $ +//** +//************************************************************************** + +#include "h2def.h" +#include "p_local.h" +#include "soundst.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;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 != ((player_t *)actor->special1)->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; + sector_t *sector; + 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)); + } + sector = actor->subsector->sector; + c = 0; + stop = (actor->lastlook-1)&3; + for( ; ; actor->lastlook = (actor->lastlook+1)&3 ) + { + if (!playeringame[actor->lastlook]) + continue; + + if (c++ == 2 || actor->lastlook == stop) + 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(((player_t *)(actor->special1)) == 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 -= tics; + if(actor->special1 > 0) + { + return(false); + } + moType = actor->special2; + 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 = 5*35; // Next try in 5 seconds + mo->special2 = 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)%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 = (mobj_t *)(actor->special1); + + actor->target = NULL; + if (deathmatch) // Quick search for players + { + for (i=0; imo; + 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 == actor->special1)) 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 = 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 == 0) + { + P_SetMobjState(actor, S_MNTR_ATK3_4); + actor->special2 = 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<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 = 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 == 666) + { + P_SetMobjState(actor, S_BLOODYSKULLX2); + } +} + +//---------------------------------------------------------------------------- +// +// PROC A_CheckBurnGone +// +//---------------------------------------------------------------------------- + +void A_CheckBurnGone(mobj_t *actor) +{ + if(actor->special2 == 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; slotnext) + { + 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) +{ + mobj_t *mo; + + if(!actor->target) + { + return; + } + mo = 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 = (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) + { + actor->special1 = 0; + P_SetMobjState(actor, S_BISHOP_WALK1); + return; + } + mo = P_SpawnMissile(actor, actor->target, MT_BISH_FX); + if(mo) + { + mo->special1 = (int)actor->target; + mo->special2 = 16; // High word == x/y, Low word == z + } + actor->special1--; +} + +//============================================================================ +// +// A_BishopMissileWeave +// +//============================================================================ + +void A_BishopMissileWeave(mobj_t *actor) +{ + fixed_t newX, newY; + int weaveXY, weaveZ; + int angle; + + weaveXY = actor->special2>>16; + weaveZ = actor->special2&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 = weaveZ+(weaveXY<<16); +} + +//============================================================================ +// +// A_BishopMissileSeek +// +//============================================================================ + +void A_BishopMissileSeek(mobj_t *actor) +{ + P_SeekerMissile(actor, ANGLE_1*2, ANGLE_1*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 = (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) + { + 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]>>1; + actor->special2 = (actor->special2+4)&63; + actor->z += FloatBobOffsets[actor->special2]>>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 = (mobj_t *)actor->special1; + 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)) < ANGLE_45/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 = ANGLE_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 = (int)P_FindMobjFromTID(target->args[bestArg], + &search); + } + } + else + { + do + { + i = (P_Random()>>2)%5; + } while(!target->args[i]); + search = -1; + actor->special1 = (int)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 = (int)P_FindMobjFromTID(actor->tid, &search); + if(search == -1) + { + P_SetMobjState(actor, actor->info->spawnstate); + return; + } + } while(actor->special1 == (int)actor); + P_RemoveMobjFromTIDList(actor); +} + +//============================================================================ +// +// A_DragonFlight +// +//============================================================================ + +void A_DragonFlight(mobj_t *actor) +{ + angle_t angle; + + DragonSeek(actor, 4*ANGLE_1, 8*ANGLE_1); + 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) < ANGLE_45/2 && P_CheckMeleeRange(actor)) + { + P_DamageMobj(actor->target, actor, actor, HITDICE(8)); + S_StartSound(actor, SFX_DRAGON_ATTACK); + } + else if(abs(actor->angle-angle) <= ANGLE_1*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) +{ + mobj_t *mo; + + mo = 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) + { // 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*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<special1 = 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; ix, 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; + actor->z += FloatBobOffsets[weaveindex]; + actor->special1 = (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; + + 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 = 2; // Number bounces + } + + // Initialize fire demon + actor->special2 = 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<momy = P_Random()%3<special1; + 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 = (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 > 0) + { + actor->special2--; + } + else + { + actor->special2 = 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 += ANGLE_90; + else + ang -= ANGLE_90; + ang>>=ANGLETOFINESHIFT; + actor->momx = FixedMul(8*FRACUNIT, finecosine[ang]); + actor->momy = FixedMul(8*FRACUNIT, finesine[ang]); + actor->special2 = 3; // strafe time + } + } + } + + FaceMovementDirection(actor); + + // Normal movement + if (!actor->special2) + { + 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) +{ + mobj_t *mo; + mo = 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 (ANGLE_MAX/3) +#define BALL3_ANGLEOFFSET ((ANGLE_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 = ANGLE_1; + 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 = 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; + + if (actor->target->health <= 0) + P_SetMobjState(actor, actor->info->painstate); + + baseangle = (angle_t)parent->special1; + 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"); + break; + } + 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 == 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 = (int)(parent->angle - + BALL1_ANGLEOFFSET); + break; + case MT_SORCBALL2: + parent->special1 = (int)(parent->angle - + BALL2_ANGLEOFFSET); + break; + case MT_SORCBALL3: + parent->special1 = (int)(parent->angle - + BALL3_ANGLEOFFSET); + break; + default: + break; + } + } + else + { + A_SorcUpdateBallAngle(actor); + } + break; + case SORC_FIRESPELL: // Casting spell + if (parent->special2 == 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 = 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 == actor->type) + { + if (actor->special2-- <= 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 = 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 = MT_SORCBALL2; // Blue + } + else if((actor->health < (actor->info->spawnhealth >> 1)) && + (chance < 200)) + { + actor->special2 = MT_SORCBALL3; // Green + } + else + { + actor->special2 = 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 += ANGLE_1*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 - ANGLE_45; + ang2 = actor->angle + ANGLE_45; + 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 = ANGLE_1 * 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 + ANGLE_1*70; + ang2 = actor->angle - ANGLE_1*70; + mo = P_SpawnMissileAngle(parent, MT_SORCFX1, ang1, 0); + if (mo) + { + mo->target = parent; + mo->special1 = (int)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 = (int)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)*ANGLE_1; + ang1 = actor->angle + delta; + mo = P_SpawnMissileAngle(parent, MT_SORCFX4, ang1, 0); + if (mo) + { + mo->special2 = 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,ANGLE_1*2,ANGLE_1*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 = 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 = 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 += ANGLE_1*10; + angle = ((angle_t)actor->special1) >> 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 -= ANGLE_1*10; + angle = ((angle_t)actor->special1) >> 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-- <= 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 = 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 > 0) + { + actor->special2--; + } + else + { + target = actor->target; + actor->special2 = 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 += ANGLE_90; + else + ang -= ANGLE_90; + ang>>=ANGLETOFINESHIFT; + actor->momx = FixedMul(13*FRACUNIT, finecosine[ang]); + actor->momy = FixedMul(13*FRACUNIT, finesine[ang]); + actor->special2 = 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) + { + 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) + { + actor->health *= 5; + actor->special1 = 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 == 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) && + (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 = 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; + spot = P_FindMobjFromTID(KORAX_TELEPORT_TID, &lastfound); + actor->special1 = 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) +{ + fixed_t x,y,z; + mobj_t *mo; + byte args[5]; + + args[0]=args[1]=args[2]=args[3]=args[4]=0; + x=actor->x, y=actor->y, z=actor->z; + + // Spawn 6 spirits equalangularly + mo = P_SpawnMissileAngle(actor,MT_KORAX_SPIRIT1, ANGLE_60*0, 5*FRACUNIT); + if (mo) KSpiritInit(mo,actor); + mo = P_SpawnMissileAngle(actor,MT_KORAX_SPIRIT2, ANGLE_60*1, 5*FRACUNIT); + if (mo) KSpiritInit(mo,actor); + mo = P_SpawnMissileAngle(actor,MT_KORAX_SPIRIT3, ANGLE_60*2, 5*FRACUNIT); + if (mo) KSpiritInit(mo,actor); + mo = P_SpawnMissileAngle(actor,MT_KORAX_SPIRIT4, ANGLE_60*3, 5*FRACUNIT); + if (mo) KSpiritInit(mo,actor); + mo = P_SpawnMissileAngle(actor,MT_KORAX_SPIRIT5, ANGLE_60*4, 5*FRACUNIT); + if (mo) KSpiritInit(mo,actor); + mo = P_SpawnMissileAngle(actor,MT_KORAX_SPIRIT6, ANGLE_60*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 = (int)korax; // Swarm around korax + spirit->special2 = 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 = (int)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 = (int)next; + tail = next; + } + tail->special1 = 0; // 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; + + 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 - ANGLE_90) >> 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*ANGLE_1) +#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) +{ + mobj_t *mo; + 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; + mo = P_SpawnKoraxMissile(x,y,z,actor, actor->target, type); +} + + +// Arm 2 projectile +void KoraxFire2(mobj_t *actor, int type) +{ + mobj_t *mo; + 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; + mo = P_SpawnKoraxMissile(x,y,z,actor, actor->target, type); +} + +// Arm 3 projectile +void KoraxFire3(mobj_t *actor, int type) +{ + mobj_t *mo; + 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; + mo = P_SpawnKoraxMissile(x,y,z,actor, actor->target, type); +} + +// Arm 4 projectile +void KoraxFire4(mobj_t *actor, int type) +{ + mobj_t *mo; + 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; + mo = P_SpawnKoraxMissile(x,y,z,actor, actor->target, type); +} + +// Arm 5 projectile +void KoraxFire5(mobj_t *actor, int type) +{ + mobj_t *mo; + 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; + mo = P_SpawnKoraxMissile(x,y,z,actor, actor->target, type); +} + +// Arm 6 projectile +void KoraxFire6(mobj_t *actor, int type) +{ + mobj_t *mo; + 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; + mo = 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>>16; + weaveZ = actor->special2&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 = 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 = (mobj_t *)actor->special1; + 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) + { + A_KSpiritSeeker(actor, actor->args[0]*ANGLE_1, + actor->args[0]*ANGLE_1*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-- <= 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 = KORAX_BOLT_LIFETIME; + } + } + else + { + // Maybe cap it off here + } +} + -- cgit v1.2.3