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