// Emacs style mode select -*- C++ -*- //----------------------------------------------------------------------------- // // $Id:$ // // Copyright (C) 1993-1996 by id Software, Inc. // // This source is available for distribution and/or modification // only under the terms of the DOOM Source Code License as // published by id Software. All rights reserved. // // The source is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License // for more details. // // $Log:$ // // DESCRIPTION: // Enemy thinking, AI. // Action Pointer Functions // that are associated with states/frames. // //----------------------------------------------------------------------------- #include #include "templates.h" #include "m_random.h" #include "m_alloc.h" #include "i_system.h" #include "doomdef.h" #include "p_local.h" #include "p_lnspec.h" #include "p_effect.h" #include "s_sound.h" #include "g_game.h" #include "doomstat.h" #include "r_state.h" #include "c_cvars.h" #include "p_enemy.h" #include "a_sharedglobal.h" #include "a_doomglobal.h" #include "a_action.h" #include "gi.h" static FRandom pr_checkmissilerange ("CheckMissileRange"); static FRandom pr_opendoor ("OpenDoor"); static FRandom pr_trywalk ("TryWalk"); static FRandom pr_newchasedir ("NewChaseDir"); static FRandom pr_lookformonsters ("LookForMonsters"); static FRandom pr_lookforplayers ("LookForPlayers"); static FRandom pr_scaredycat ("Anubis"); FRandom pr_chase ("Chase"); static FRandom pr_facetarget ("FaceTarget"); static FRandom pr_railface ("RailFace"); static FRandom pr_dropitem ("DropItem"); static FRandom pr_look2 ("LookyLooky"); static FRandom pr_look3 ("IGotHooky"); static FRandom pr_slook ("SlooK"); static FRandom pr_skiptarget("SkipTarget"); // movement interpolation is fine for objects that are moved by their own // momentum. But for monsters it is problematic. // 1. They don't move every tic // 2. Their animation is not designed for movement interpolation // The result is that they tend to 'glide' across the floor // so this CVAR allows to switch it off. CVAR(Bool, nomonsterinterpolation, false, CVAR_GLOBALCONFIG|CVAR_ARCHIVE) // // P_NewChaseDir related LUT. // dirtype_t opposite[9] = { DI_WEST, DI_SOUTHWEST, DI_SOUTH, DI_SOUTHEAST, DI_EAST, DI_NORTHEAST, DI_NORTH, DI_NORTHWEST, DI_NODIR }; dirtype_t diags[4] = { DI_NORTHWEST, DI_NORTHEAST, DI_SOUTHWEST, DI_SOUTHEAST }; fixed_t xspeed[8] = {FRACUNIT,46341,0,-46341,-FRACUNIT,-46341,0,46341}; fixed_t yspeed[8] = {0,46341,FRACUNIT,46341,0,-46341,-FRACUNIT,-46341}; // // ENEMY THINKING // Enemies are always spawned // with targetplayer = -1, threshold = 0 // Most monsters are spawned unaware of all players, // but some can be made preaware // //---------------------------------------------------------------------------- // // PROC P_RecursiveSound // // Called by P_NoiseAlert. // Recursively traverse adjacent sectors, // sound blocking lines cut off traversal. //---------------------------------------------------------------------------- void P_RecursiveSound (sector_t *sec, AActor *soundtarget, bool splash, int soundblocks) { int i; line_t* check; sector_t* other; AActor* actor; // wake up all monsters in this sector if (sec->validcount == validcount && sec->soundtraversed <= soundblocks+1) { return; // already flooded } sec->validcount = validcount; sec->soundtraversed = soundblocks+1; sec->SoundTarget = soundtarget; // [RH] Set this in the actors in the sector instead of the sector itself. for (actor = sec->thinglist; actor != NULL; actor = actor->snext) { if (actor != soundtarget && (!splash || !(actor->flags4 & MF4_NOSPLASHALERT))) { actor->LastHeard = soundtarget; } } for (i = 0; i < sec->linecount; i++) { check = sec->lines[i]; if (check->sidenum[1] == NO_SIDE || !(check->flags & ML_TWOSIDED)) { continue; } if ( sides[ check->sidenum[0] ].sector == sec) other = sides[ check->sidenum[1] ].sector; else other = sides[ check->sidenum[0] ].sector; // check for closed door if ((sec->floorplane.ZatPoint (check->v1->x, check->v1->y) >= other->ceilingplane.ZatPoint (check->v1->x, check->v1->y) && sec->floorplane.ZatPoint (check->v2->x, check->v2->y) >= other->ceilingplane.ZatPoint (check->v2->x, check->v2->y)) || (other->floorplane.ZatPoint (check->v1->x, check->v1->y) >= sec->ceilingplane.ZatPoint (check->v1->x, check->v1->y) && other->floorplane.ZatPoint (check->v2->x, check->v2->y) >= sec->ceilingplane.ZatPoint (check->v2->x, check->v2->y)) || (other->floorplane.ZatPoint (check->v1->x, check->v1->y) >= other->ceilingplane.ZatPoint (check->v1->x, check->v1->y) && other->floorplane.ZatPoint (check->v2->x, check->v2->y) >= other->ceilingplane.ZatPoint (check->v2->x, check->v2->y))) { continue; } if (check->flags & ML_SOUNDBLOCK) { if (!soundblocks) P_RecursiveSound (other, soundtarget, splash, 1); } else { P_RecursiveSound (other, soundtarget, splash, soundblocks); } } } //---------------------------------------------------------------------------- // // PROC P_NoiseAlert // // If a monster yells at a player, it will alert other monsters to the // player. // //---------------------------------------------------------------------------- void P_NoiseAlert (AActor *target, AActor *emmiter, bool splash) { if (emmiter == NULL) return; if (target != NULL && target->player && (target->player->cheats & CF_NOTARGET)) return; validcount++; P_RecursiveSound (emmiter->Sector, target, splash, 0); } //---------------------------------------------------------------------------- // // AActor :: CheckMeleeRange // //---------------------------------------------------------------------------- bool AActor::CheckMeleeRange () { AActor *pl; fixed_t dist; if (!target) return false; pl = target; dist = P_AproxDistance (pl->x - x, pl->y - y); if (dist >= meleerange + pl->radius) return false; // [RH] If moving toward goal, then we've reached it. if (target == goal) return true; // [RH] Don't melee things too far above or below actor. if (pl->z > z + height) return false; if (pl->z + pl->height < z) return false; if (!P_CheckSight (this, pl, 0)) return false; return true; } //---------------------------------------------------------------------------- // // FUNC P_CheckMeleeRange2 // //---------------------------------------------------------------------------- bool P_CheckMeleeRange2 (AActor *actor) { AActor *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-20*FRACUNIT + mo->radius) { 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; } if (!P_CheckSight(actor, mo)) { return false; } return true; } // // P_CheckMissileRange // BOOL P_CheckMissileRange (AActor *actor) { fixed_t dist; if (!P_CheckSight (actor, actor->target, 4)) return false; if (actor->flags & MF_JUSTHIT) { // the target just hit the enemy, so fight back! actor->flags &= ~MF_JUSTHIT; return true; } if (actor->reactiontime) return false; // do not attack yet // OPTIMIZE: get this from a global checksight // [RH] What? dist = P_AproxDistance (actor->x-actor->target->x, actor->y-actor->target->y) - 64*FRACUNIT; if (actor->MeleeState == NULL) dist -= 128*FRACUNIT; // no melee attack, so fire more return actor->SuggestMissileAttack (dist); } bool AActor::SuggestMissileAttack (fixed_t dist) { // new version encapsulates the different behavior in flags instead of virtual functions // The advantage is that this allows inheriting the missile attack attributes from the // various Doom monsters by custom monsters // Making these values customizable is not necessary and in most case more confusing than // helpful because no value here translates into anything really meaningful. if (flags4 & MF4_SHORTMISSILERANGE && dist>14*64*FRACUNIT) return false; // The Arch Vile's special behavior turned into a flag if (flags4 & MF4_LONGMELEERANGE && dist < 196*FRACUNIT) return false; // From the Revenant: close enough for fist attack if (flags4 & MF4_MISSILEMORE) dist >>= 1; if (flags4 & MF4_MISSILEEVENMORE) dist >>= 3; return pr_checkmissilerange() >= MIN (dist >> FRACBITS, MinMissileChance); } //============================================================================= // // P_HitFriend() // // killough 12/98 // This function tries to prevent shooting at friends that get in the line of fire // // [GrafZahl] Taken from MBF but this has been cleaned up to make it readable. // //============================================================================= bool P_HitFriend(AActor * self) { if (self->flags&MF_FRIENDLY && self->target != NULL) { angle_t angle = R_PointToAngle2 (self->x, self->y, self->target->x, self->target->y); fixed_t dist = P_AproxDistance (self->x-self->target->x, self->y-self->target->y); P_AimLineAttack (self, angle, dist, 0); if (linetarget != NULL && linetarget != self->target) { return self->IsFriend (linetarget); } } return false; } // // P_Move // Move in the current direction, // returns false if the move is blocked. // BOOL P_Move (AActor *actor) { fixed_t tryx, tryy, deltax, deltay, origx, origy; BOOL try_ok; int speed; int movefactor = ORIG_FRICTION_FACTOR; int friction = ORIG_FRICTION; if (actor->flags2 & MF2_BLASTED) return true; if (actor->movedir == DI_NODIR) return false; // [RH] Instead of yanking non-floating monsters to the ground, // let gravity drop them down, unless they're moving down a step. if (!(actor->flags & MF_NOGRAVITY) && actor->z > actor->floorz && !(actor->flags2 & MF2_ONMOBJ)) { if (actor->z > actor->floorz + actor->MaxStepHeight) { return false; } else { actor->z = actor->floorz; } } if ((unsigned)actor->movedir >= 8) I_Error ("Weird actor->movedir!"); speed = actor->Speed; #if 0 // [RH] I'm not so sure this is such a good idea // killough 10/98: make monsters get affected by ice and sludge too: movefactor = P_GetMoveFactor (actor, &friction); if (friction < ORIG_FRICTION) { // sludge speed = ((ORIG_FRICTION_FACTOR - (ORIG_FRICTION_FACTOR-movefactor)/2) * speed) / ORIG_FRICTION_FACTOR; if (speed == 0) { // always give the monster a little bit of speed speed = ksgn(actor->Speed); } } #endif tryx = (origx = actor->x) + (deltax = FixedMul (speed, xspeed[actor->movedir])); tryy = (origy = actor->y) + (deltay = FixedMul (speed, yspeed[actor->movedir])); // Like P_XYMovement this should do multiple moves if the step size is too large fixed_t maxmove = actor->radius - FRACUNIT; int steps = 1; if (maxmove > 0) { const fixed_t xspeed = abs (deltax); const fixed_t yspeed = abs (deltay); if (xspeed > yspeed) { if (xspeed > maxmove) { steps = 1 + xspeed / maxmove; } } else { if (yspeed > maxmove) { steps = 1 + yspeed / maxmove; } } } try_ok = true; for(int i=1; i < steps; i++) { try_ok = P_TryMove(actor, origx + Scale(deltax, i, steps), origy + Scale(deltay, i, steps), false); if (!try_ok) break; } // killough 3/15/98: don't jump over dropoffs: if (try_ok) try_ok = P_TryMove (actor, tryx, tryy, false); // [GrafZahl] Interpolating monster movement as it is done here just looks bad // so make it switchable! if (nomonsterinterpolation) { actor->PrevX = actor->x; actor->PrevY = actor->y; actor->PrevZ = actor->z; } if (try_ok && friction > ORIG_FRICTION) { actor->x = origx; actor->y = origy; movefactor *= FRACUNIT / ORIG_FRICTION_FACTOR / 4; actor->momx += FixedMul (deltax, movefactor); actor->momy += FixedMul (deltay, movefactor); } if (!try_ok) { if ((actor->flags & MF_FLOAT) && floatok) { // must adjust height fixed_t savedz = actor->z; if (actor->z < tmfloorz) actor->z += FLOATSPEED; else actor->z -= FLOATSPEED; // [RH] Check to make sure there's nothing in the way of the float if (P_TestMobjZ (actor)) { actor->flags |= MF_INFLOAT; return true; } actor->z = savedz; } if (!spechit.Size ()) return false; // open any specials actor->movedir = DI_NODIR; // if the special is not a door that can be opened, return false // // killough 8/9/98: this is what caused monsters to get stuck in // doortracks, because it thought that the monster freed itself // by opening a door, even if it was moving towards the doortrack, // and not the door itself. // // killough 9/9/98: If a line blocking the monster is activated, // return true 90% of the time. If a line blocking the monster is // not activated, but some other line is, return false 90% of the // time. A bit of randomness is needed to ensure it's free from // lockups, but for most cases, it returns the correct result. // // Do NOT simply return false 1/4th of the time (causes monsters to // back out when they shouldn't, and creates secondary stickiness). line_t *ld; int good = 0; while (spechit.Pop (ld)) { // [RH] let monsters push lines, as well as use them if (P_ActivateLine (ld, actor, 0, SPAC_USE) || ((actor->flags & MF2_PUSHWALL) && P_ActivateLine (ld, actor, 0, SPAC_PUSH))) { good |= ld == BlockingLine ? 1 : 2; } } return good && ((pr_opendoor() >= 203) ^ (good & 1)); } else { actor->flags &= ~MF_INFLOAT; } return true; } // // TryWalk // Attempts to move actor on // in its current (ob->moveangle) direction. // If blocked by either a wall or an actor // returns FALSE // If move is either clear or blocked only by a door, // returns TRUE and sets... // If a door is in the way, // an OpenDoor call is made to start it opening. // BOOL P_TryWalk (AActor *actor) { if (!P_Move (actor)) { return false; } actor->movecount = pr_trywalk() & 15; return true; } /* ================ = = P_NewChaseDir = ================ */ void P_NewChaseDir (AActor *actor) { fixed_t deltax, deltay; dirtype_t d[3]; int tdir; dirtype_t olddir, turnaround; if (actor->target == NULL) I_Error ("P_NewChaseDir: called with no target"); olddir = (dirtype_t)actor->movedir; turnaround = opposite[olddir]; deltax = actor->target->x - actor->x; deltay = actor->target->y - actor->y; // [RH] Make monsters run away from frightening players if ((actor->target->player != NULL && (actor->target->player->cheats & CF_FRIGHTENING)) || actor->flags4 & MF4_FRIGHTENED) { deltax = -deltax; deltay = -deltay; } 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 (pr_newchasedir() > 200 || abs(deltay) > abs(deltax)) { swap (d[1], d[2]); } if (d[1] == turnaround) d[1] = DI_NODIR; if (d[2] == turnaround) d[2] = DI_NODIR; if (d[1] != DI_NODIR) { actor->movedir = d[1]; if (P_TryWalk (actor)) { // either moved forward or attacked return; } } if (d[2] != DI_NODIR) { actor->movedir = d[2]; if (P_TryWalk (actor)) return; } // there is no direct path to the player, so pick another direction. if (olddir != DI_NODIR) { actor->movedir = olddir; if (P_TryWalk (actor)) return; } // randomly determine direction of search if (pr_newchasedir() & 1) { for (tdir = DI_EAST; tdir <= DI_SOUTHEAST; tdir++) { if (tdir != turnaround) { actor->movedir = tdir; if ( P_TryWalk(actor) ) return; } } } else { for (tdir = DI_SOUTHEAST; tdir != (DI_EAST-1); tdir--) { if (tdir != turnaround) { actor->movedir = tdir; if ( P_TryWalk(actor) ) return; } } } if (turnaround != DI_NODIR) { actor->movedir =turnaround; if ( P_TryWalk(actor) ) return; } actor->movedir = DI_NODIR; // can not move } void P_RandomChaseDir (AActor *actor) { dirtype_t olddir, turnaround; int tdir, i; olddir = (dirtype_t)actor->movedir; turnaround = opposite[olddir]; int turndir; // Friendly monsters like to head toward a player if (actor->flags & MF_FRIENDLY) { AActor *player; fixed_t deltax, deltay; dirtype_t d[3]; if (actor->FriendPlayer != 0) { player = players[actor->FriendPlayer - 1].mo; } else { if (!multiplayer) { i = 0; } else for (i = pr_newchasedir() & (MAXPLAYERS-1); !playeringame[i]; i = (i+1) & (MAXPLAYERS-1)) { } player = players[i].mo; } if (pr_newchasedir() & 1 || !P_CheckSight (actor, player)) { deltax = player->x - actor->x; deltay = player->y - actor->y; if (deltax>128*FRACUNIT) d[1]= DI_EAST; else if (deltax<-128*FRACUNIT) d[1]= DI_WEST; else d[1]=DI_NODIR; if (deltay<-128*FRACUNIT) d[2]= DI_SOUTH; else if (deltay>128*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 (pr_newchasedir() > 200 || abs(deltay) > abs(deltax)) { swap (d[1], d[2]); } if (d[1] == turnaround) d[1] = DI_NODIR; if (d[2] == turnaround) d[2] = DI_NODIR; if (d[1] != DI_NODIR) { actor->movedir = d[1]; if (P_TryWalk (actor)) { // either moved forward or attacked return; } } if (d[2] != DI_NODIR) { actor->movedir = d[2]; if (P_TryWalk (actor)) return; } } } // If the actor elects to continue in its current direction, let it do // so unless the way is blocked. Then it must turn. if (pr_newchasedir() < 150) { if (P_TryWalk (actor)) return; } turndir = (pr_newchasedir() & 1) ? -1 : 1; if (olddir == DI_NODIR) { olddir = (dirtype_t)(pr_newchasedir() & 7); } for (tdir = (olddir + turndir) & 7; tdir != olddir; tdir = (tdir + turndir) & 7) { if (tdir != turnaround) { actor->movedir = tdir; if (P_TryWalk (actor)) return; } } /* if (pr_newchasedir() & 1) { for (tdir = olddir; tdir <= DI_SOUTHEAST; ++tdir) { if (tdir != turnaround) { actor->movedir = tdir; if (P_TryWalk (actor)) return; } } } else { for (tdir = DI_SOUTHEAST; tdir >= DI_EAST; --tdir) { if (tdir != turnaround) { actor->movedir = tdir; if (P_TryWalk (actor)) return; } } } */ if (turnaround != DI_NODIR) { actor->movedir = turnaround; if (P_TryWalk (actor)) { actor->movecount = pr_newchasedir() & 15; return; } } actor->movedir = DI_NODIR; // cannot move } //--------------------------------------------------------------------------- // // FUNC P_LookForMonsters // //--------------------------------------------------------------------------- #define MONS_LOOK_RANGE (20*64*FRACUNIT) #define MONS_LOOK_LIMIT 64 BOOL P_LookForMonsters (AActor *actor) { int count; AActor *mo; TThinkerIterator iterator; if (!P_CheckSight (players[0].mo, actor, 2)) { // Player can't see monster return false; } count = 0; while ( (mo = iterator.Next ()) ) { if (!(mo->flags3 & MF3_ISMONSTER) || (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 (pr_lookformonsters() < 16) { // Skip continue; } if (++count >= MONS_LOOK_LIMIT) { // Stop searching return false; } if (mo->IsKindOf (RUNTIME_TYPE(actor)) || actor->IsKindOf (RUNTIME_TYPE(mo))) { // [RH] Don't go after same species continue; } if (!P_CheckSight (actor, mo, 2)) { // Out of sight continue; } // Found a target monster actor->target = mo; return true; } return false; } //============================================================================ // // LookForTIDinBlock // // Finds a target with the specified TID in a mapblock. Alternatively, it // can find a target with a specified TID if something in this mapblock is // already targetting it. // //============================================================================ AActor *LookForTIDinBlock (AActor *lookee, int index) { FBlockNode *block; AActor *link; AActor *other; for (block = blocklinks[index]; block != NULL; block = block->NextActor) { link = block->Me; if (!(link->flags & MF_SHOOTABLE)) continue; // not shootable (observer or dead) if (link == lookee) continue; if (link->health <= 0) continue; // dead if (link->flags2 & MF2_DORMANT) continue; // don't target dormant things if (link->tid == lookee->TIDtoHate) { other = link; } else if (link->target != NULL && link->target->tid == lookee->TIDtoHate) { other = link->target; if (!(other->flags & MF_SHOOTABLE) || other->health <= 0 || (other->flags2 & MF2_DORMANT)) { continue; } } else { continue; } if (!(lookee->flags3 & MF3_NOSIGHTCHECK)) { if (!P_CheckSight (lookee, other, 2)) continue; // out of sight /* if (!allaround) { angle_t an = R_PointToAngle2 (actor->x, actor->y, other->x, other->y) - actor->angle; if (an > ANG90 && an < ANG270) { fixed_t dist = P_AproxDistance (other->x - actor->x, other->y - actor->y); // if real close, react anyway if (dist > MELEERANGE) continue; // behind back } } */ } return other; } return NULL; } //============================================================================ // // P_LookForTID // // Selects a live monster with the given TID // //============================================================================ BOOL P_LookForTID (AActor *actor, BOOL allaround) { AActor *other; other = P_BlockmapSearch (actor, 0, LookForTIDinBlock); if (other != NULL) { if (actor->goal && actor->target == actor->goal) actor->reactiontime = 0; actor->target = other; actor->LastLook.Actor = other; return true; } // The actor's TID could change because of death or because of // Thing_ChangeTID. If it's not what we expect, then don't use // it as a base for the iterator. if (actor->LastLook.Actor != NULL && actor->LastLook.Actor->tid != actor->TIDtoHate) { actor->LastLook.Actor = NULL; } FActorIterator iterator (actor->TIDtoHate, actor->LastLook.Actor); int c = (pr_look3() & 31) + 7; // Look for between 7 and 38 hatees at a time while ((other = iterator.Next()) != actor->LastLook.Actor) { if (other == NULL) continue; if (!(other->flags & MF_SHOOTABLE)) continue; // not shootable (observer or dead) if (other == actor) continue; // don't hate self if (other->health <= 0) continue; // dead if (other->flags2 & MF2_DORMANT) continue; // don't target dormant things if (--c == 0) break; if (!(actor->flags3 & MF3_NOSIGHTCHECK)) { if (!P_CheckSight (actor, other, 2)) continue; // out of sight if (!allaround) { angle_t an = R_PointToAngle2 (actor->x, actor->y, other->x, other->y) - actor->angle; if (an > ANG90 && an < ANG270) { fixed_t dist = P_AproxDistance (other->x - actor->x, other->y - actor->y); // if real close, react anyway if (dist > MELEERANGE) continue; // behind back } } } // [RH] Need to be sure the reactiontime is 0 if the monster is // leaving its goal to go after something else. if (actor->goal && actor->target == actor->goal) actor->reactiontime = 0; actor->target = other; actor->LastLook.Actor = other; return true; } actor->LastLook.Actor = other; if (actor->target == NULL) { // [RH] use goal as target if (actor->goal != NULL) { actor->target = actor->goal; return true; } // Use last known enemy if no hatee sighted -- killough 2/15/98: if (actor->lastenemy != NULL && actor->lastenemy->health > 0) { actor->target = actor->lastenemy; actor->lastenemy = NULL; return true; } } return false; } //============================================================================ // // LookForTIDinBlock // // Finds a non-friendly monster in a mapblock. It can also use targets of // friendlies in this mapblock to find non-friendlies in other mapblocks. // //============================================================================ AActor *LookForEnemiesInBlock (AActor *lookee, int index) { FBlockNode *block; AActor *link; AActor *other; for (block = blocklinks[index]; block != NULL; block = block->NextActor) { link = block->Me; if (!(link->flags & MF_SHOOTABLE)) continue; // not shootable (observer or dead) if (link == lookee) continue; if (link->health <= 0) continue; // dead if (link->flags2 & MF2_DORMANT) continue; // don't target dormant things if (!(link->flags3 & MF3_ISMONSTER)) continue; // don't target it if it isn't a monster (could be a barrel) other = NULL; if (link->flags & MF_FRIENDLY) { if (deathmatch && lookee->FriendPlayer != 0 && link->FriendPlayer != 0 && lookee->FriendPlayer != link->FriendPlayer) { // This is somebody else's friend, so go after it other = link; } else if (link->target != NULL && !(link->target->flags & MF_FRIENDLY)) { other = link->target; if (!(other->flags & MF_SHOOTABLE) || other->health <= 0 || (other->flags2 & MF2_DORMANT)) { other = NULL;; } } } else { other = link; } // [MBF] If the monster is already engaged in a one-on-one attack // with a healthy friend, don't attack around 60% the time. // [GrafZahl] This prevents friendlies from attacking all the same // target. if (other) { AActor *targ = other->target; if (targ && targ->target == other && pr_skiptarget() > 100 && lookee->IsFriend (targ) && targ->health*2 >= targ->GetDefault()->health) { continue; } } if (other == NULL || !P_CheckSight (lookee, other, 2)) continue; // out of sight /* if (!allaround) { angle_t an = R_PointToAngle2 (actor->x, actor->y, other->x, other->y) - actor->angle; if (an > ANG90 && an < ANG270) { fixed_t dist = P_AproxDistance (other->x - actor->x, other->y - actor->y); // if real close, react anyway if (dist > MELEERANGE) continue; // behind back } } */ return other; } return NULL; } //============================================================================ // // P_LookForEnemies // // Selects a live enemy monster // //============================================================================ BOOL P_LookForEnemies (AActor *actor, BOOL allaround) { AActor *other; other = P_BlockmapSearch (actor, 10, LookForEnemiesInBlock); if (other != NULL) { if (actor->goal && actor->target == actor->goal) actor->reactiontime = 0; actor->target = other; // actor->LastLook.Actor = other; return true; } if (actor->target == NULL) { // [RH] use goal as target if (actor->goal != NULL) { actor->target = actor->goal; return true; } // Use last known enemy if no enemies sighted -- killough 2/15/98: if (actor->lastenemy != NULL && actor->lastenemy->health > 0) { actor->target = actor->lastenemy; actor->lastenemy = NULL; return true; } } return false; } /* ================ = = P_LookForPlayers = = If allaround is false, only look 180 degrees in front = returns true if a player is targeted ================ */ BOOL P_LookForPlayers (AActor *actor, BOOL allaround) { int c; int stop; int pnum; player_t* player; angle_t an; fixed_t dist; if (actor->TIDtoHate != 0) { if (P_LookForTID (actor, allaround)) { return true; } if (!(actor->flags3 & MF3_HUNTPLAYERS)) { return false; } } else if (actor->flags & MF_FRIENDLY) { return P_LookForEnemies (actor, allaround); } if (!(gameinfo.gametype & (GAME_Doom|GAME_Strife)) && !multiplayer && players[0].health <= 0) { // Single player game and player is dead; look for monsters return P_LookForMonsters (actor); } c = 0; if (actor->TIDtoHate != 0) { pnum = pr_look2() & (MAXPLAYERS-1); } else { pnum = actor->LastLook.PlayerNumber; } stop = (pnum - 1) & (MAXPLAYERS-1); for (;;) { pnum = (pnum + 1) & (MAXPLAYERS-1); if (!playeringame[pnum]) continue; if (actor->TIDtoHate == 0) { actor->LastLook.PlayerNumber = pnum; } if (++c == MAXPLAYERS-1 || pnum == stop) { // done looking if (actor->target == NULL) { // [RH] use goal as target if (actor->goal != NULL) { actor->target = actor->goal; return true; } // Use last known enemy if no players sighted -- killough 2/15/98: if (actor->lastenemy != NULL && actor->lastenemy->health > 0) { actor->target = actor->lastenemy; actor->lastenemy = NULL; return true; } } return actor->target == actor->goal && actor->goal != NULL; } player = &players[pnum]; if (!(player->mo->flags & MF_SHOOTABLE)) continue; // not shootable (observer or dead) if (player->cheats & CF_NOTARGET) continue; // no target if (player->health <= 0) continue; // dead if (!P_CheckSight (actor, player->mo, 2)) 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->RenderStyle == STYLE_Translucent || player->mo->RenderStyle == STYLE_OptFuzzy) { 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 (pr_lookforplayers() < 225) { // Player isn't sneaking, but still didn't detect return false; } } // [RH] Need to be sure the reactiontime is 0 if the monster is // leaving its goal to go after a player. if (actor->goal && actor->target == actor->goal) actor->reactiontime = 0; actor->target = player->mo; return true; } } // // ACTION ROUTINES // // // A_Look // Stay in state until a player is sighted. // [RH] Will also leave state to move to goal. // void A_Look (AActor *actor) { AActor *targ; // [RH] Set goal now if appropriate if (actor->special == Thing_SetGoal && actor->args[0] == 0) { TActorIterator iterator (actor->args[1]); actor->special = 0; actor->goal = iterator.Next (); actor->reactiontime = actor->args[2] * TICRATE + level.maptime; } actor->threshold = 0; // any shot will wake up if (actor->TIDtoHate != 0) { targ = actor->target; } else { targ = (compatflags & COMPATF_SOUNDTARGET)? actor->Sector->SoundTarget : actor->LastHeard; // [RH] If the soundtarget is dead, don't chase it if (targ != NULL && targ->health <= 0) { targ = NULL; } if (targ && targ->player && (targ->player->cheats & CF_NOTARGET)) { return; } } // [RH] Andy Baker's stealth monsters if (actor->flags & MF_STEALTH) { actor->visdir = -1; } if (targ && (targ->flags & MF_SHOOTABLE)) { if (actor->IsFriend (targ)) // be a little more precise! { // If we find a valid target here, the wandering logic should *not* // be activated! If would cause the seestate to be set twice. if (P_LookForPlayers (actor, actor->flags4 & MF4_LOOKALLAROUND)) goto seeyou; // Let the actor wander around aimlessly looking for a fight if (actor->SeeState != NULL) { if (!(actor->flags & MF_INCHASE)) { actor->SetState (actor->SeeState); } } else { A_Wander (actor); } } else { actor->target = targ; if (actor->flags & MF_AMBUSH) { if (P_CheckSight (actor, actor->target, 2)) goto seeyou; } else goto seeyou; } } if (!P_LookForPlayers (actor, actor->flags4 & MF4_LOOKALLAROUND)) return; // go into chase state seeyou: // [RH] Don't start chasing after a goal if it isn't time yet. if (actor->target == actor->goal) { if (actor->reactiontime > level.maptime) actor->target = NULL; } else if (actor->SeeSound) { if (actor->flags2 & MF2_BOSS) { // full volume S_SoundID (actor, CHAN_VOICE, actor->SeeSound, 1, ATTN_SURROUND); } else { S_SoundID (actor, CHAN_VOICE, actor->SeeSound, 1, ATTN_NORM); } } if (actor->target && !(actor->flags & MF_INCHASE)) { actor->SetState (actor->SeeState); } } void A_Wander (AActor *self) { int delta; // [RH] Strife probably clears this flag somewhere, but I couldn't find where. // This seems as good a place as any. self->flags4 &= ~MF4_INCOMBAT; if (self->flags4 & MF4_STANDSTILL) return; if (self->threshold != 0) { self->threshold--; return; } // turn towards movement direction if not there yet if (self->movedir < DI_NODIR) { self->angle &= (angle_t)(7<<29); delta = self->angle - (self->movedir << 29); if (delta > 0) { self->angle -= ANG90/2; } else if (delta < 0) { self->angle += ANG90/2; } } if (--self->movecount < 0 || !P_Move (self)) { P_RandomChaseDir (self); self->movecount += 5; } } void A_Look2 (AActor *self) { AActor *targ; self->threshold = 0; targ = self->LastHeard; if (targ != NULL && targ->health <= 0) { targ = NULL; } if (targ && (targ->flags & MF_SHOOTABLE)) { if ((level.flags & LEVEL_NOALLIES) || (self->flags & MF_FRIENDLY) != (targ->flags & MF_FRIENDLY)) { if (self->flags & MF_AMBUSH) { if (!P_CheckSight (self, targ, 2)) goto nosee; } self->target = targ; self->threshold = 10; self->SetState (self->SeeState); return; } else { if (!P_LookForPlayers (self, self->flags4 & MF4_LOOKALLAROUND)) goto nosee; self->SetState (self->SeeState); self->flags4 |= MF4_INCOMBAT; return; } } nosee: if (pr_look2() < 30) { self->SetState (self->SpawnState + (pr_look2() & 1) + 1); } if (!(self->flags4 & MF4_STANDSTILL) && pr_look2() < 40) { self->SetState (self->SpawnState + 3); } } /* ============== = = A_Chase = = Actor has a melee attack, so it tries to close as fast as possible = */ // [GrafZahl] integrated A_FastChase, A_SerpentChase and A_SerpentWalk into this // to allow the monsters using those functions to take advantage of the // enhancements. // //============================================================================= #define CLASS_BOSS_STRAFE_RANGE 64*10*FRACUNIT void A_DoChase (AActor *actor, bool fastchase, FState *meleestate, FState *missilestate, bool playactive, bool nightmarefast) { int delta; actor->flags |= MF_INCHASE; // [RH] Andy Baker's stealth monsters if (actor->flags & MF_STEALTH) { actor->visdir = -1; } if (actor->reactiontime) { actor->reactiontime--; } // [RH] Don't chase invisible targets if (actor->target != NULL && actor->target->renderflags & RF_INVISIBLE && actor->target != actor->goal) { actor->target = NULL; } // modify target threshold if (actor->threshold) { if (actor->target == NULL || actor->target->health <= 0) { actor->threshold = 0; } else { actor->threshold--; } } if (nightmarefast && (gameskill == sk_nightmare || (dmflags & DF_FAST_MONSTERS))) { // 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 &= (angle_t)(7<<29); delta = actor->angle - (actor->movedir << 29); if (delta > 0) { actor->angle -= ANG90/2; } else if (delta < 0) { actor->angle += ANG90/2; } } // [RH] If the target is dead (and not a goal), stop chasing it. if (actor->target && actor->target != actor->goal && actor->target->health <= 0) actor->target = NULL; // [RH] Friendly monsters will consider chasing whoever hurts a player if they // don't already have a target. if (actor->flags & MF_FRIENDLY && actor->target == NULL) { player_t *player; if (actor->FriendPlayer != 0) { player = &players[actor->FriendPlayer - 1]; } else { int i; if (!multiplayer) { i = 0; } else for (i = pr_newchasedir() & (MAXPLAYERS-1); !playeringame[i]; i = (i+1) & (MAXPLAYERS-1)) { } player = &players[i]; } if (player->attacker && player->attacker->health > 0 && player->attacker->flags & MF_SHOOTABLE && pr_newchasedir() < 80) { if (!(player->attacker->flags & MF_FRIENDLY) || (deathmatch && actor->FriendPlayer != 0 && player->attacker->FriendPlayer != 0 && actor->FriendPlayer != player->attacker->FriendPlayer)) { actor->target = player->attacker; } } } if (!actor->target || !(actor->target->flags & MF_SHOOTABLE)) { // look for a new target if (P_LookForPlayers (actor, true) && actor->target != actor->goal) { // got a new target actor->flags &= ~MF_INCHASE; return; } if (actor->target == NULL) { if (actor->flags & MF_FRIENDLY) { A_Look (actor); if (actor->target == NULL) { A_Wander (actor); actor->flags &= ~MF_INCHASE; return; } } else { actor->SetState (actor->SpawnState); actor->flags &= ~MF_INCHASE; return; } } } // do not attack twice in a row if (actor->flags & MF_JUSTATTACKED) { actor->flags &= ~MF_JUSTATTACKED; if ((gameskill != sk_nightmare) && !(dmflags & DF_FAST_MONSTERS)) { P_NewChaseDir (actor); } actor->flags &= ~MF_INCHASE; return; } // [RH] Don't attack if just moving toward goal if (actor->target == actor->goal) { if (actor->CheckMeleeRange ()) { // reached the goal TActorIterator iterator (actor->goal->args[0]); TActorIterator specit (actor->goal->tid); AActor *spec; // Execute the specials of any PatrolSpecials with the same TID // as the goal. while ( (spec = specit.Next()) ) { LineSpecials[spec->special] (NULL, actor, false, spec->args[0], spec->args[1], spec->args[2], spec->args[3], spec->args[4]); } angle_t lastgoalang = actor->goal->angle; actor->goal = iterator.Next (); if (actor->goal != NULL) { actor->reactiontime = actor->goal->args[1] * TICRATE + level.maptime; } else { actor->reactiontime = actor->GetDefault()->reactiontime; actor->angle = lastgoalang; // Look in direction of last goal } actor->target = NULL; actor->flags |= MF_JUSTATTACKED; if (actor->goal != NULL && actor->goal->args[1] != 0) { actor->flags4 |= MF4_INCOMBAT; actor->SetState (actor->SpawnState); } actor->flags &= ~MF_INCHASE; return; } goto nomissile; } // Strafe (Hexen's class bosses) // This was the sole reason for the separate A_FastChase function but // it can be just as easily handled by a simple flag so the monsters // can take advantage of all the other enhancements of A_Chase. if (fastchase) { if (actor->special2 > 0) { actor->special2--; } else { actor->special2 = 0; actor->momx = 0; actor->momy = 0; fixed_t dist = P_AproxDistance (actor->x - actor->target->x, actor->y - actor->target->y); if (dist < CLASS_BOSS_STRAFE_RANGE) { if (pr_chase() < 100) { angle_t ang = R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y); if (pr_chase() < 128) ang += ANGLE_90; else ang -= ANGLE_90; actor->momx = 13 * finecosine[ang>>ANGLETOFINESHIFT]; actor->momy = 13 * finesine[ang>>ANGLETOFINESHIFT]; actor->special2 = 3; // strafe time } } } } // [RH] Scared monsters attack less frequently if (((actor->target->player == NULL || !(actor->target->player->cheats & CF_FRIGHTENING)) && !(actor->flags4 & MF4_FRIGHTENED)) || pr_scaredycat() < 43) { // check for melee attack if (meleestate && actor->CheckMeleeRange ()) { if (actor->AttackSound) S_SoundID (actor, CHAN_WEAPON, actor->AttackSound, 1, ATTN_NORM); actor->SetState (meleestate); actor->flags &= ~MF_INCHASE; return; } // check for missile attack if (missilestate) { if (gameskill < sk_nightmare && actor->movecount && !(dmflags & DF_FAST_MONSTERS)) { goto nomissile; } if (!P_CheckMissileRange (actor)) goto nomissile; actor->SetState (missilestate); actor->flags |= MF_JUSTATTACKED; actor->flags4 |= MF4_INCOMBAT; actor->flags &= ~MF_INCHASE; return; } } nomissile: // possibly choose another target if ((multiplayer || actor->TIDtoHate) && !actor->threshold && !P_CheckSight (actor, actor->target, 0) ) { bool lookForBetter = false; BOOL gotNew; if (actor->flags3 & MF3_NOSIGHTCHECK) { actor->flags3 &= ~MF3_NOSIGHTCHECK; lookForBetter = true; } gotNew = P_LookForPlayers (actor, true); if (lookForBetter) { actor->flags3 |= MF3_NOSIGHTCHECK; } if (gotNew) { actor->flags &= ~MF_INCHASE; return; // got a new target } } // // chase towards player // // class bosses don't do this when strafing if (!fastchase || !actor->special2) { // CANTLEAVEFLOORPIC handling was completely missing in the non-serpent functions! fixed_t oldX = actor->x; fixed_t oldY = actor->y; int oldFloor = actor->floorpic; // chase towards player if (--actor->movecount < 0 || !P_Move (actor)) { P_NewChaseDir (actor); } // if the move was illegal, reset it // (copied from A_SerpentChase - it applies to everything with CANTLEAVEFLOORPIC!) if (actor->flags2&MF2_CANTLEAVEFLOORPIC && actor->floorpic != oldFloor ) { if (P_TryMove (actor, oldX, oldY, false)) { if (nomonsterinterpolation) { actor->PrevX = oldX; actor->PrevY = oldY; } } P_NewChaseDir (actor); } } // make active sound if (playactive && pr_chase() < 3) { actor->PlayActiveSound (); } actor->flags &= ~MF_INCHASE; } void A_Chase (AActor *actor) { A_DoChase (actor, false, actor->MeleeState, actor->MissileState, true, !!(gameinfo.gametype & GAME_Raven)); } void A_FastChase (AActor *actor) { A_DoChase (actor, true, actor->MeleeState, actor->MissileState, true, true); } // // A_FaceTarget // void A_FaceTarget (AActor *actor) { if (!actor->target) return; // [RH] Andy Baker's stealth monsters if (actor->flags & MF_STEALTH) { actor->visdir = 1; } actor->flags &= ~MF_AMBUSH; actor->angle = R_PointToAngle2 (actor->x, actor->y, actor->target->x, actor->target->y); if (actor->target->flags & MF_SHADOW) { actor->angle += pr_facetarget.Random2() << 21; } } // // [RH] A_MonsterRail // // New function to let monsters shoot a railgun // void A_MonsterRail (AActor *actor) { if (!actor->target) return; // [RH] Andy Baker's stealth monsters if (actor->flags & MF_STEALTH) { actor->visdir = 1; } actor->flags &= ~MF_AMBUSH; actor->angle = R_PointToAngle2 (actor->x, actor->y, actor->target->x, actor->target->y); actor->pitch = P_AimLineAttack (actor, actor->angle, MISSILERANGE); // Let the aim trail behind the player actor->angle = R_PointToAngle2 (actor->x, actor->y, actor->target->x - actor->target->momx * 3, actor->target->y - actor->target->momy * 3); if (actor->target->flags & MF_SHADOW) { actor->angle += pr_railface.Random2() << 21; } P_RailAttack (actor, actor->damage, 0); } void A_Scream (AActor *actor) { if (actor->DeathSound) { // Check for bosses. if (actor->flags2 & MF2_BOSS) { // full volume S_SoundID (actor, CHAN_VOICE, actor->DeathSound, 1, ATTN_SURROUND); } else { S_SoundID (actor, CHAN_VOICE, actor->DeathSound, 1, ATTN_NORM); } } } void A_XScream (AActor *actor) { if (actor->player) S_Sound (actor, CHAN_VOICE, "*gibbed", 1, ATTN_NORM); else S_Sound (actor, CHAN_VOICE, "misc/gibbed", 1, ATTN_NORM); } // Strife's version of A_XScrem void A_XXScream (AActor *actor) { if (!(actor->flags & MF_NOBLOOD) || actor->DeathSound == 0) { A_XScream (actor); } else { S_SoundID (actor, CHAN_VOICE, actor->DeathSound, 1, ATTN_NORM); } } //--------------------------------------------------------------------------- // // PROC P_DropItem // //--------------------------------------------------------------------------- CVAR(Int, sv_dropstyle, 0, CVAR_SERVERINFO | CVAR_ARCHIVE); AInventory *P_DropItem (AActor *source, const TypeInfo *type, int special, int chance) { if (type != NULL && pr_dropitem() <= chance) { AActor *mo; fixed_t spawnz; spawnz = source->z; if (!(compatflags & COMPATF_NOTOSSDROPS)) { int style = sv_dropstyle; if (style==0) style= (gameinfo.gametype == GAME_Strife)? 2:1; if (style==2) { spawnz += 24*FRACUNIT; } else { spawnz += source->height / 2; } } mo = Spawn (type, source->x, source->y, spawnz); mo->flags |= MF_DROPPED; mo->flags &= ~MF_NOGRAVITY; // [RH] Make sure it is affected by gravity if (mo->IsKindOf (RUNTIME_CLASS(AInventory))) { if (special > 0) { static_cast(mo)->Amount = special; } else if (mo->IsKindOf (RUNTIME_CLASS(AAmmo))) { // Half ammo when dropped by bad guys. static_cast(mo)->Amount /= 2; } else if (mo->IsKindOf (RUNTIME_CLASS(AWeapon))) { // The same goes for ammo from a weapon. static_cast(mo)->AmmoGive1 /= 2; static_cast(mo)->AmmoGive2 /= 2; } if (static_cast(mo)->SpecialDropAction (source)) { return NULL; } } if (!(compatflags & COMPATF_NOTOSSDROPS)) { P_TossItem (mo); } return static_cast(mo); } return NULL; } //============================================================================ // // P_TossItem // //============================================================================ void P_TossItem (AActor *item) { int style = sv_dropstyle; if (style==0) style= (gameinfo.gametype == GAME_Strife)? 2:1; if (style==2) { item->momx += pr_dropitem.Random2(7) << FRACBITS; item->momy += pr_dropitem.Random2(7) << FRACBITS; } else { item->momx = pr_dropitem.Random2() << 8; item->momy = pr_dropitem.Random2() << 8; item->momz = FRACUNIT*5 + (pr_dropitem() << 10); } } void A_Pain (AActor *actor) { // [RH] Vary player pain sounds depending on health (ala Quake2) if (actor->player && actor->player->morphTics == 0) { const char *painchoice; if (actor->health < 25) painchoice = "*pain25"; else if (actor->health < 50) painchoice = "*pain50"; else if (actor->health < 75) painchoice = "*pain75"; else painchoice = "*pain100"; S_Sound (actor, CHAN_VOICE, painchoice, 1, ATTN_NORM); } else if (actor->PainSound) { S_SoundID (actor, CHAN_VOICE, actor->PainSound, 1, ATTN_NORM); } } // killough 11/98: kill an object void A_Die (AActor *actor) { P_DamageMobj (actor, NULL, NULL, actor->health, MOD_UNKNOWN); } // // A_Detonate // killough 8/9/98: same as A_Explode, except that the damage is variable // void A_Detonate (AActor *mo) { P_RadiusAttack (mo, mo->target, mo->damage, mo->damage, mo->DamageType, true); if (mo->z <= mo->floorz + (mo->damage<PreExplode (); thing->GetExplodeParms (damage, distance, hurtSource); P_RadiusAttack (thing, thing->target, damage, distance, thing->DamageType, hurtSource); if (thing->z <= thing->floorz + (distance<target != NULL && thing->target->player != NULL) { validcount++; P_RecursiveSound (thing->Sector, thing->target, false, 0); } } bool CheckBossDeath (AActor *actor) { int i; // make sure there is a player alive for victory for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i] && players[i].health > 0) break; if (i == MAXPLAYERS) return false; // no one left alive, so do not end game // Make sure all bosses are dead TThinkerIterator iterator; AActor *other; while ( (other = iterator.Next ()) ) { if (other != actor && (other->health > 0 || (other->flags & MF_ICECORPSE)) && other->GetClass() == actor->GetClass()) { // Found a living boss // [RH] Frozen bosses don't count as dead until they shatter return false; } } // The boss death is good return true; } // // A_BossDeath // Possibly trigger special effects if on a boss level // void A_BossDeath (AActor *actor) { enum { MT_FATSO, MT_BABY, MT_BRUISER, MT_CYBORG, MT_SPIDER, MT_HEAD, MT_MINOTAUR, MT_SORCERER2 } type; // Do generic special death actions first bool checked = false; FSpecialAction *sa = level.info->specialactions; while (sa) { if (name(actor->GetClass()->Name+1) == sa->Type) { if (!checked && !CheckBossDeath(actor)) { return; } checked = true; LineSpecials[sa->Action](NULL, actor, false, sa->Args[0], sa->Args[1], sa->Args[2], sa->Args[3], sa->Args[4]); } sa = sa->Next; } // [RH] These all depend on the presence of level flags now // rather than being hard-coded to specific levels/episodes. if ((level.flags & (LEVEL_MAP07SPECIAL| LEVEL_BRUISERSPECIAL| LEVEL_CYBORGSPECIAL| LEVEL_SPIDERSPECIAL| LEVEL_HEADSPECIAL| LEVEL_MINOTAURSPECIAL| LEVEL_SORCERER2SPECIAL)) == 0) return; if (strcmp (RUNTIME_TYPE(actor)->Name+1, "Fatso") == 0) type = MT_FATSO; else if (strcmp (RUNTIME_TYPE(actor)->Name+1, "Arachnotron") == 0) type = MT_BABY; else if (strcmp (RUNTIME_TYPE(actor)->Name+1, "BaronOfHell") == 0) type = MT_BRUISER; else if (strcmp (RUNTIME_TYPE(actor)->Name+1, "Cyberdemon") == 0) type = MT_CYBORG; else if (strcmp (RUNTIME_TYPE(actor)->Name+1, "SpiderMastermind") == 0) type = MT_SPIDER; else if (strcmp (RUNTIME_TYPE(actor)->Name+1, "Ironlich") == 0) type = MT_HEAD; else if (strcmp (RUNTIME_TYPE(actor)->Name+1, "Minotaur") == 0) type = MT_MINOTAUR; else if (strcmp (RUNTIME_TYPE(actor)->Name+1, "Sorcerer2") == 0) type = MT_SORCERER2; else return; if ( ((level.flags & LEVEL_MAP07SPECIAL) && (type == MT_FATSO || type == MT_BABY)) || ((level.flags & LEVEL_BRUISERSPECIAL) && (type == MT_BRUISER)) || ((level.flags & LEVEL_CYBORGSPECIAL) && (type == MT_CYBORG)) || ((level.flags & LEVEL_SPIDERSPECIAL) && (type == MT_SPIDER)) || ((level.flags & LEVEL_HEADSPECIAL) && (type == MT_HEAD)) || ((level.flags & LEVEL_MINOTAURSPECIAL) && (type == MT_MINOTAUR)) || ((level.flags & LEVEL_SORCERER2SPECIAL) && (type == MT_SORCERER2)) ) ; else return; if (!CheckBossDeath (actor)) { return; } // victory! if (level.flags & LEVEL_SPECKILLMONSTERS) { // Kill any remaining monsters P_Massacre (); } if (level.flags & LEVEL_MAP07SPECIAL) { if (type == MT_FATSO) { EV_DoFloor (DFloor::floorLowerToLowest, NULL, 666, FRACUNIT, 0, 0, 0); return; } if (type == MT_BABY) { EV_DoFloor (DFloor::floorRaiseByTexture, NULL, 667, FRACUNIT, 0, 0, 0); return; } } else { switch (level.flags & LEVEL_SPECACTIONSMASK) { case LEVEL_SPECLOWERFLOOR: EV_DoFloor (DFloor::floorLowerToLowest, NULL, 666, FRACUNIT, 0, 0, 0); return; case LEVEL_SPECOPENDOOR: EV_DoDoor (DDoor::doorOpen, NULL, NULL, 666, 8*FRACUNIT, 0, 0, 0); return; } } // [RH] If noexit, then don't end the level. if ((deathmatch || alwaysapplydmflags) && (dmflags & DF_NO_EXIT)) return; G_ExitLevel (0, false); } //---------------------------------------------------------------------------- // // PROC P_Massacre // // Kills all monsters. // //---------------------------------------------------------------------------- int P_Massacre () { // jff 02/01/98 'em' cheat - kill all monsters // partially taken from Chi's .46 port // // killough 2/7/98: cleaned up code and changed to use dprintf; // fixed lost soul bug (LSs left behind when PEs are killed) int killcount = 0; AActor *actor; TThinkerIterator iterator; while ( (actor = iterator.Next ()) ) { if (!(actor->flags2 & MF2_DORMANT) && (actor->flags3 & MF3_ISMONSTER)) { killcount += actor->Massacre(); } } return killcount; } // // A_SinkMobj // Sink a mobj incrementally into the floor // bool A_SinkMobj (AActor *actor) { if (actor->floorclip < actor->height) { actor->floorclip += actor->GetSinkSpeed (); return false; } return true; } // // A_RaiseMobj // Raise a mobj incrementally from the floor to // bool A_RaiseMobj (AActor *actor) { bool done = true; // Raise a mobj from the ground if (actor->floorclip > 0) { actor->floorclip -= actor->GetRaiseSpeed (); if (actor->floorclip <= 0) { actor->floorclip = 0; done = true; } else { done = false; } } return done; // Reached target height } void A_ClassBossHealth (AActor *actor) { if (multiplayer && !deathmatch) // co-op only { if (!actor->special1) { actor->health *= 5; actor->special1 = true; // has been initialized } } }