/* Emacs style mode select -*- C++ -*- *----------------------------------------------------------------------------- * * * PrBoom: a Doom port merged with LxDoom and LSDLDoom * based on BOOM, a modified and improved DOOM engine * Copyright (C) 1999 by * id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman * Copyright (C) 1999-2000,2002 by * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze * Copyright 2005, 2006 by * Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * * DESCRIPTION: * Enemy thinking, AI. * Action Pointer Functions * that are associated with states/frames. * *-----------------------------------------------------------------------------*/ #include "doomstat.h" #include "m_random.h" #include "r_main.h" #include "p_maputl.h" #include "p_map.h" #include "p_setup.h" #include "p_spec.h" #include "s_sound.h" #include "sounds.h" #include "p_inter.h" #include "g_game.h" #include "p_enemy.h" #include "p_tick.h" #include "m_bbox.h" #include "lprintf.h" static mobj_t *current_actor; typedef enum { DI_EAST, DI_NORTHEAST, DI_NORTH, DI_NORTHWEST, DI_WEST, DI_SOUTHWEST, DI_SOUTH, DI_SOUTHEAST, DI_NODIR, NUMDIRS } dirtype_t; static void P_NewChaseDir(mobj_t *actor); void P_ZBumpCheck(mobj_t *); // phares // // ENEMY THINKING // Enemies are allways spawned // with targetplayer = -1, threshold = 0 // Most monsters are spawned unaware of all players, // but some can be made preaware // // // Called by P_NoiseAlert. // Recursively traverse adjacent sectors, // sound blocking lines cut off traversal. // // killough 5/5/98: reformatted, cleaned up static void P_RecursiveSound(sector_t *sec, int soundblocks, mobj_t *soundtarget) { int i; // 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; P_SetTarget(&sec->soundtarget, soundtarget); for (i=0; ilinecount; i++) { sector_t *other; line_t *check = sec->lines[i]; if (!(check->flags & ML_TWOSIDED)) continue; P_LineOpening(check); if (openrange <= 0) continue; // closed door other=sides[check->sidenum[sides[check->sidenum[0]].sector==sec]].sector; if (!(check->flags & ML_SOUNDBLOCK)) P_RecursiveSound(other, soundblocks, soundtarget); else if (!soundblocks) P_RecursiveSound(other, 1, soundtarget); } } // // 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 *emitter) { validcount++; P_RecursiveSound(emitter->subsector->sector, 0, target); } // // P_CheckMeleeRange // static boolean P_CheckMeleeRange(mobj_t *actor) { mobj_t *pl = actor->target; return // killough 7/18/98: friendly monsters don't attack other friends pl && !(actor->flags & pl->flags & MF_FRIEND) && (P_AproxDistance(pl->x-actor->x, pl->y-actor->y) < MELEERANGE - 20*FRACUNIT + pl->info->radius) && P_CheckSight(actor, actor->target); } // // P_HitFriend() // // killough 12/98 // This function tries to prevent shooting at friends static boolean P_HitFriend(mobj_t *actor) { return actor->flags & MF_FRIEND && actor->target && (P_AimLineAttack(actor, R_PointToAngle2(actor->x, actor->y, actor->target->x, actor->target->y), P_AproxDistance(actor->x-actor->target->x, actor->y-actor->target->y), 0), linetarget) && linetarget != actor->target && !((linetarget->flags ^ actor->flags) & MF_FRIEND); } // // P_CheckMissileRange // static 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; /* killough 7/18/98: no friendly fire at corpses * killough 11/98: prevent too much infighting among friends * cph - yikes, talk about fitting everything on one line... */ return !(actor->flags & MF_FRIEND) || (actor->target->health > 0 && (!(actor->target->flags & MF_FRIEND) || (actor->target->player ? monster_infighting || P_Random(pr_defect) >128 : !(actor->target->flags & MF_JUSTHIT) && P_Random(pr_defect) >128))); } /* killough 7/18/98: friendly monsters don't attack other friendly * monsters or players (except when attacked, and then only once) */ if (actor->flags & actor->target->flags & MF_FRIEND) return false; if (actor->reactiontime) return false; // do not attack yet // OPTIMIZE: get this from a global checksight dist = P_AproxDistance ( actor->x-actor->target->x, actor->y-actor->target->y) - 64*FRACUNIT; if (!actor->info->meleestate) dist -= 128*FRACUNIT; // no melee attack, so fire more dist >>= FRACBITS; if (actor->type == MT_VILE) if (dist > 14*64) return false; // too far away if (actor->type == MT_UNDEAD) { if (dist < 196) return false; // close for fist attack dist >>= 1; } if (actor->type == MT_CYBORG || actor->type == MT_SPIDER || actor->type == MT_SKULL) dist >>= 1; if (dist > 200) dist = 200; if (actor->type == MT_CYBORG && dist > 160) dist = 160; if (P_Random(pr_missrange) < dist) return false; if (P_HitFriend(actor)) return false; return true; } /* * P_IsOnLift * * killough 9/9/98: * * Returns true if the object is on a lift. Used for AI, * since it may indicate the need for crowded conditions, * or that a monster should stay on the lift for a while * while it goes up or down. */ static boolean P_IsOnLift(const mobj_t *actor) { const sector_t *sec = actor->subsector->sector; line_t line; int l; // Short-circuit: it's on a lift which is active. if (sec->floordata && ((thinker_t *) sec->floordata)->function==T_PlatRaise) return true; // Check to see if it's in a sector which can be activated as a lift. if ((line.tag = sec->tag)) for (l = -1; (l = P_FindLineFromLineTag(&line, l)) >= 0;) switch (lines[l].special) { case 10: case 14: case 15: case 20: case 21: case 22: case 47: case 53: case 62: case 66: case 67: case 68: case 87: case 88: case 95: case 120: case 121: case 122: case 123: case 143: case 162: case 163: case 181: case 182: case 144: case 148: case 149: case 211: case 227: case 228: case 231: case 232: case 235: case 236: return true; } return false; } /* * P_IsUnderDamage * * killough 9/9/98: * * Returns nonzero if the object is under damage based on * their current position. Returns 1 if the damage is moderate, * -1 if it is serious. Used for AI. */ static int P_IsUnderDamage(mobj_t *actor) { const struct msecnode_s *seclist; const ceiling_t *cl; // Crushing ceiling int dir = 0; for (seclist=actor->touching_sectorlist; seclist; seclist=seclist->m_tnext) if ((cl = seclist->m_sector->ceilingdata) && cl->thinker.function == T_MoveCeiling) dir |= cl->direction; return dir; } // // P_Move // Move in the current direction, // returns false if the move is blocked. // static fixed_t xspeed[8] = {FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000}; static fixed_t yspeed[8] = {0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000}; // 1/11/98 killough: Limit removed on special lines crossed extern line_t **spechit; // New code -- killough extern int numspechit; static boolean P_Move(mobj_t *actor, boolean dropoff) /* killough 9/12/98 */ { fixed_t tryx, tryy, deltax, deltay, origx, origy; boolean try_ok; int movefactor = ORIG_FRICTION_FACTOR; // killough 10/98 int friction = ORIG_FRICTION; int speed; if (actor->movedir == DI_NODIR) return false; #ifdef RANGECHECK if ((unsigned)actor->movedir >= 8) I_Error ("P_Move: Weird actor->movedir!"); #endif // killough 10/98: make monsters get affected by ice and sludge too: if (monster_friction) movefactor = P_GetMoveFactor(actor, &friction); speed = actor->info->speed; if (friction < ORIG_FRICTION && // sludge !(speed = ((ORIG_FRICTION_FACTOR - (ORIG_FRICTION_FACTOR-movefactor)/2) * speed) / ORIG_FRICTION_FACTOR)) speed = 1; // always give the monster a little bit of speed tryx = (origx = actor->x) + (deltax = speed * xspeed[actor->movedir]); tryy = (origy = actor->y) + (deltay = speed * yspeed[actor->movedir]); try_ok = P_TryMove(actor, tryx, tryy, dropoff); // killough 10/98: // Let normal momentum carry them, instead of steptoeing them across ice. 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) { // open any specials int good; if (actor->flags & MF_FLOAT && floatok) { if (actor->z < tmfloorz) // must adjust height actor->z += FLOATSPEED; else actor->z -= FLOATSPEED; actor->flags |= MF_INFLOAT; return true; } if (!numspechit) return false; 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). */ for (good = false; numspechit--; ) if (P_UseSpecialLine(actor, spechit[numspechit], 0)) good |= spechit[numspechit] == blockline ? 1 : 2; /* cph - compatibility maze here * Boom v2.01 and orig. Doom return "good" * Boom v2.02 and LxDoom return good && (P_Random(pr_trywalk)&3) * MBF plays even more games */ if (!good || comp[comp_doorstuck]) return good; if (!mbf_features) return (P_Random(pr_trywalk)&3); /* jff 8/13/98 */ else /* finally, MBF code */ return ((P_Random(pr_opendoor) >= 230) ^ (good & 1)); } else actor->flags &= ~MF_INFLOAT; /* killough 11/98: fall more slowly, under gravity, if felldown==true */ if (!(actor->flags & MF_FLOAT) && (!felldown || !mbf_features)) actor->z = actor->floorz; return true; } /* * P_SmartMove * * killough 9/12/98: Same as P_Move, except smarter */ static boolean P_SmartMove(mobj_t *actor) { mobj_t *target = actor->target; int on_lift, dropoff = false, under_damage; /* killough 9/12/98: Stay on a lift if target is on one */ on_lift = !comp[comp_staylift] && target && target->health > 0 && target->subsector->sector->tag==actor->subsector->sector->tag && P_IsOnLift(actor); under_damage = monster_avoid_hazards && P_IsUnderDamage(actor); // killough 10/98: allow dogs to drop off of taller ledges sometimes. // dropoff==1 means always allow it, dropoff==2 means only up to 128 high, // and only if the target is immediately on the other side of the line. #ifdef DOGS // haleyjd: allow all friends of HelperType to also jump down if ((actor->type == MT_DOGS || (actor->type == (HelperThing-1) && actor->flags&MF_FRIEND)) && target && dog_jumping && !((target->flags ^ actor->flags) & MF_FRIEND) && P_AproxDistance(actor->x - target->x, actor->y - target->y) < FRACUNIT*144 && P_Random(pr_dropoff) < 235) dropoff = 2; #endif if (!P_Move(actor, dropoff)) return false; // killough 9/9/98: avoid crushing ceilings or other damaging areas if ( (on_lift && P_Random(pr_stayonlift) < 230 && // Stay on lift !P_IsOnLift(actor)) || (monster_avoid_hazards && !under_damage && // Get away from damage (under_damage = P_IsUnderDamage(actor)) && (under_damage < 0 || P_Random(pr_avoidcrush) < 200)) ) actor->movedir = DI_NODIR; // avoid the area (most of the time anyway) 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. // static boolean P_TryWalk(mobj_t *actor) { if (!P_SmartMove(actor)) return false; actor->movecount = P_Random(pr_trywalk)&15; return true; } // // P_DoNewChaseDir // // killough 9/8/98: // // Most of P_NewChaseDir(), except for what // determines the new direction to take // static void P_DoNewChaseDir(mobj_t *actor, fixed_t deltax, fixed_t deltay) { dirtype_t xdir, ydir, tdir; dirtype_t olddir = actor->movedir; dirtype_t turnaround = olddir; if (turnaround != DI_NODIR) // find reverse direction turnaround ^= 4; xdir = deltax > 10*FRACUNIT ? DI_EAST : deltax < -10*FRACUNIT ? DI_WEST : DI_NODIR; ydir = deltay < -10*FRACUNIT ? DI_SOUTH : deltay > 10*FRACUNIT ? DI_NORTH : DI_NODIR; // try direct route if (xdir != DI_NODIR && ydir != DI_NODIR && turnaround != (actor->movedir = deltay < 0 ? deltax > 0 ? DI_SOUTHEAST : DI_SOUTHWEST : deltax > 0 ? DI_NORTHEAST : DI_NORTHWEST) && P_TryWalk(actor)) return; // try other directions if (P_Random(pr_newchase) > 200 || D_abs(deltay)>D_abs(deltax)) tdir = xdir, xdir = ydir, ydir = tdir; if ((xdir == turnaround ? xdir = DI_NODIR : xdir) != DI_NODIR && (actor->movedir = xdir, P_TryWalk(actor))) return; // either moved forward or attacked if ((ydir == turnaround ? ydir = DI_NODIR : ydir) != DI_NODIR && (actor->movedir = ydir, P_TryWalk(actor))) return; // there is no direct path to the player, so pick another direction. if (olddir != DI_NODIR && (actor->movedir = olddir, P_TryWalk(actor))) return; // randomly determine direction of search if (P_Random(pr_newchasedir) & 1) { for (tdir = DI_EAST; tdir <= DI_SOUTHEAST; tdir++) if (tdir != turnaround && (actor->movedir = tdir, P_TryWalk(actor))) return; } else for (tdir = DI_SOUTHEAST; tdir != DI_EAST-1; tdir--) if (tdir != turnaround && (actor->movedir = tdir, P_TryWalk(actor))) return; if ((actor->movedir = turnaround) != DI_NODIR && !P_TryWalk(actor)) actor->movedir = DI_NODIR; } // // killough 11/98: // // Monsters try to move away from tall dropoffs. // // In Doom, they were never allowed to hang over dropoffs, // and would remain stuck if involuntarily forced over one. // This logic, combined with p_map.c (P_TryMove), allows // monsters to free themselves without making them tend to // hang over dropoffs. static fixed_t dropoff_deltax, dropoff_deltay, floorz; static boolean PIT_AvoidDropoff(line_t *line) { if (line->backsector && // Ignore one-sided linedefs tmbbox[BOXRIGHT] > line->bbox[BOXLEFT] && tmbbox[BOXLEFT] < line->bbox[BOXRIGHT] && tmbbox[BOXTOP] > line->bbox[BOXBOTTOM] && // Linedef must be contacted tmbbox[BOXBOTTOM] < line->bbox[BOXTOP] && P_BoxOnLineSide(tmbbox, line) == -1) { fixed_t front = line->frontsector->floorheight; fixed_t back = line->backsector->floorheight; angle_t angle; // The monster must contact one of the two floors, // and the other must be a tall dropoff (more than 24). if (back == floorz && front < floorz - FRACUNIT*24) angle = R_PointToAngle2(0,0,line->dx,line->dy); // front side dropoff else if (front == floorz && back < floorz - FRACUNIT*24) angle = R_PointToAngle2(line->dx,line->dy,0,0); // back side dropoff else return true; // Move away from dropoff at a standard speed. // Multiple contacted linedefs are cumulative (e.g. hanging over corner) dropoff_deltax -= finesine[angle >> ANGLETOFINESHIFT]*32; dropoff_deltay += finecosine[angle >> ANGLETOFINESHIFT]*32; } return true; } // // Driver for above // static fixed_t P_AvoidDropoff(mobj_t *actor) { int yh=((tmbbox[BOXTOP] = actor->y+actor->radius)-bmaporgy)>>MAPBLOCKSHIFT; int yl=((tmbbox[BOXBOTTOM]= actor->y-actor->radius)-bmaporgy)>>MAPBLOCKSHIFT; int xh=((tmbbox[BOXRIGHT] = actor->x+actor->radius)-bmaporgx)>>MAPBLOCKSHIFT; int xl=((tmbbox[BOXLEFT] = actor->x-actor->radius)-bmaporgx)>>MAPBLOCKSHIFT; int bx, by; floorz = actor->z; // remember floor height dropoff_deltax = dropoff_deltay = 0; // check lines validcount++; for (bx=xl ; bx<=xh ; bx++) for (by=yl ; by<=yh ; by++) P_BlockLinesIterator(bx, by, PIT_AvoidDropoff); // all contacted lines return dropoff_deltax | dropoff_deltay; // Non-zero if movement prescribed } // // P_NewChaseDir // // killough 9/8/98: Split into two functions // static void P_NewChaseDir(mobj_t *actor) { mobj_t *target = actor->target; fixed_t deltax = target->x - actor->x; fixed_t deltay = target->y - actor->y; // killough 8/8/98: sometimes move away from target, keeping distance // // 1) Stay a certain distance away from a friend, to avoid being in their way // 2) Take advantage over an enemy without missiles, by keeping distance actor->strafecount = 0; if (mbf_features) { if (actor->floorz - actor->dropoffz > FRACUNIT*24 && actor->z <= actor->floorz && !(actor->flags & (MF_DROPOFF|MF_FLOAT)) && !comp[comp_dropoff] && P_AvoidDropoff(actor)) /* Move away from dropoff */ { P_DoNewChaseDir(actor, dropoff_deltax, dropoff_deltay); // If moving away from dropoff, set movecount to 1 so that // small steps are taken to get monster away from dropoff. actor->movecount = 1; return; } else { fixed_t dist = P_AproxDistance(deltax, deltay); // Move away from friends when too close, except // in certain situations (e.g. a crowded lift) if (actor->flags & target->flags & MF_FRIEND && distfriend << FRACBITS > dist && !P_IsOnLift(target) && !P_IsUnderDamage(actor)) { deltax = -deltax, deltay = -deltay; } else if (target->health > 0 && (actor->flags ^ target->flags) & MF_FRIEND) { // Live enemy target if (monster_backing && actor->info->missilestate && actor->type != MT_SKULL && ((!target->info->missilestate && dist < MELEERANGE*2) || (target->player && dist < MELEERANGE*3 && (target->player->readyweapon == wp_fist || target->player->readyweapon == wp_chainsaw)))) { // Back away from melee attacker actor->strafecount = P_Random(pr_enemystrafe) & 15; deltax = -deltax, deltay = -deltay; } } } } P_DoNewChaseDir(actor, deltax, deltay); // If strafing, set movecount to strafecount so that old Doom // logic still works the same, except in the strafing part if (actor->strafecount) actor->movecount = actor->strafecount; } // // P_IsVisible // // killough 9/9/98: whether a target is visible to a monster // static boolean P_IsVisible(mobj_t *actor, mobj_t *mo, boolean allaround) { if (!allaround) { angle_t an = R_PointToAngle2(actor->x, actor->y, mo->x, mo->y) - actor->angle; if (an > ANG90 && an < ANG270 && P_AproxDistance(mo->x-actor->x, mo->y-actor->y) > MELEERANGE) return false; } return P_CheckSight(actor, mo); } // // PIT_FindTarget // // killough 9/5/98 // // Finds monster targets for other monsters // static int current_allaround; static boolean PIT_FindTarget(mobj_t *mo) { mobj_t *actor = current_actor; if (!((mo->flags ^ actor->flags) & MF_FRIEND && // Invalid target mo->health > 0 && (mo->flags & MF_COUNTKILL || mo->type == MT_SKULL))) return true; // If the monster is already engaged in a one-on-one attack // with a healthy friend, don't attack around 60% the time { const mobj_t *targ = mo->target; if (targ && targ->target == mo && P_Random(pr_skiptarget) > 100 && (targ->flags ^ mo->flags) & MF_FRIEND && targ->health*2 >= targ->info->spawnhealth) return true; } if (!P_IsVisible(actor, mo, current_allaround)) return true; P_SetTarget(&actor->lastenemy, actor->target); // Remember previous target P_SetTarget(&actor->target, mo); // Found target // Move the selected monster to the end of its associated // list, so that it gets searched last next time. { thinker_t *cap = &thinkerclasscap[mo->flags & MF_FRIEND ? th_friends : th_enemies]; (mo->thinker.cprev->cnext = mo->thinker.cnext)->cprev = mo->thinker.cprev; (mo->thinker.cprev = cap->cprev)->cnext = &mo->thinker; (mo->thinker.cnext = cap)->cprev = &mo->thinker; } return false; } // // P_LookForPlayers // If allaround is false, only look 180 degrees in front. // Returns true if a player is targeted. // static boolean P_LookForPlayers(mobj_t *actor, boolean allaround) { player_t *player; int stop, stopc, c; if (actor->flags & MF_FRIEND) { // killough 9/9/98: friendly monsters go about players differently int anyone; #if 0 if (!allaround) // If you want friendly monsters not to awaken unprovoked return false; #endif // Go back to a player, no matter whether it's visible or not for (anyone=0; anyone<=1; anyone++) for (c=0; ctarget, players[c].mo); // killough 12/98: // get out of refiring loop, to avoid hitting player accidentally if (actor->info->missilestate) { P_SetMobjState(actor, actor->info->seestate); actor->flags &= ~MF_JUSTHIT; } return true; } return false; } // Change mask of 3 to (MAXPLAYERS-1) -- killough 2/15/98: stop = (actor->lastlook-1)&(MAXPLAYERS-1); c = 0; stopc = !mbf_features && !demo_compatibility && monsters_remember ? MAXPLAYERS : 2; // killough 9/9/98 for (;; actor->lastlook = (actor->lastlook+1)&(MAXPLAYERS-1)) { if (!playeringame[actor->lastlook]) continue; // killough 2/15/98, 9/9/98: if (c++ == stopc || actor->lastlook == stop) // done looking { // e6y // Fixed Boom incompatibilities. The following code was missed. // There are no more desyncs on Donce's demos on horror.wad // Use last known enemy if no players sighted -- killough 2/15/98: if (!mbf_features && !demo_compatibility && monsters_remember) { if (actor->lastenemy && actor->lastenemy->health > 0) { actor->target = actor->lastenemy; actor->lastenemy = NULL; return true; } } return false; } player = &players[actor->lastlook]; if (player->health <= 0) continue; // dead if (!P_IsVisible(actor, player->mo, allaround)) continue; P_SetTarget(&actor->target, player->mo); /* killough 9/9/98: give monsters a threshold towards getting players * (we don't want it to be too easy for a player with dogs :) */ if (!comp[comp_pursuit]) actor->threshold = 60; return true; } } // // Friendly monsters, by Lee Killough 7/18/98 // // Friendly monsters go after other monsters first, but // also return to owner if they cannot find any targets. // A marine's best friend :) killough 7/18/98, 9/98 // static boolean P_LookForMonsters(mobj_t *actor, boolean allaround) { thinker_t *cap, *th; if (demo_compatibility) return false; if (actor->lastenemy && actor->lastenemy->health > 0 && monsters_remember && !(actor->lastenemy->flags & actor->flags & MF_FRIEND)) // not friends { P_SetTarget(&actor->target, actor->lastenemy); P_SetTarget(&actor->lastenemy, NULL); return true; } /* Old demos do not support monster-seeking bots */ if (!mbf_features) return false; // Search the threaded list corresponding to this object's potential targets cap = &thinkerclasscap[actor->flags & MF_FRIEND ? th_enemies : th_friends]; // Search for new enemy if (cap->cnext != cap) // Empty list? bail out early { int x = (actor->x - bmaporgx)>>MAPBLOCKSHIFT; int y = (actor->y - bmaporgy)>>MAPBLOCKSHIFT; int d; current_actor = actor; current_allaround = allaround; // Search first in the immediate vicinity. if (!P_BlockThingsIterator(x, y, PIT_FindTarget)) return true; for (d=1; d<5; d++) { int i = 1 - d; do if (!P_BlockThingsIterator(x+i, y-d, PIT_FindTarget) || !P_BlockThingsIterator(x+i, y+d, PIT_FindTarget)) return true; while (++i < d); do if (!P_BlockThingsIterator(x-d, y+i, PIT_FindTarget) || !P_BlockThingsIterator(x+d, y+i, PIT_FindTarget)) return true; while (--i + d >= 0); } { // Random number of monsters, to prevent patterns from forming int n = (P_Random(pr_friends) & 31) + 15; for (th = cap->cnext; th != cap; th = th->cnext) if (--n < 0) { // Only a subset of the monsters were searched. Move all of // the ones which were searched so far, to the end of the list. (cap->cnext->cprev = cap->cprev)->cnext = cap->cnext; (cap->cprev = th->cprev)->cnext = cap; (th->cprev = cap)->cnext = th; break; } else if (!PIT_FindTarget((mobj_t *) th)) // If target sighted return true; } } return false; // No monster found } // // P_LookForTargets // // killough 9/5/98: look for targets to go after, depending on kind of monster // static boolean P_LookForTargets(mobj_t *actor, int allaround) { return actor->flags & MF_FRIEND ? P_LookForMonsters(actor, allaround) || P_LookForPlayers (actor, allaround): P_LookForPlayers (actor, allaround) || P_LookForMonsters(actor, allaround); } // // P_HelpFriend // // killough 9/8/98: Help friends in danger of dying // static boolean P_HelpFriend(mobj_t *actor) { thinker_t *cap, *th; // If less than 33% health, self-preservation rules if (actor->health*3 < actor->info->spawnhealth) return false; current_actor = actor; current_allaround = true; // Possibly help a friend under 50% health cap = &thinkerclasscap[actor->flags & MF_FRIEND ? th_friends : th_enemies]; for (th = cap->cnext; th != cap; th = th->cnext) if (((mobj_t *) th)->health*2 >= ((mobj_t *) th)->info->spawnhealth) { if (P_Random(pr_helpfriend) < 180) break; } else if (((mobj_t *) th)->flags & MF_JUSTHIT && ((mobj_t *) th)->target && ((mobj_t *) th)->target != actor->target && !PIT_FindTarget(((mobj_t *) th)->target)) { // Ignore any attacking monsters, while searching for friend actor->threshold = BASETHRESHOLD; return true; } return false; } // // A_KeenDie // DOOM II special, map 32. // Uses special tag 666. // void A_KeenDie(mobj_t* mo) { thinker_t *th; line_t junk; A_Fall(mo); // scan the remaining thinkers to see if all Keens are dead for (th = thinkercap.next ; th != &thinkercap ; th=th->next) if (th->function == P_MobjThinker) { mobj_t *mo2 = (mobj_t *) th; if (mo2 != mo && mo2->type == mo->type && mo2->health > 0) return; // other Keen not dead } junk.tag = 666; EV_DoDoor(&junk,open); } // // ACTION ROUTINES // // // A_Look // Stay in state until a player is sighted. // void A_Look(mobj_t *actor) { mobj_t *targ = actor->subsector->sector->soundtarget; actor->threshold = 0; // any shot will wake up /* killough 7/18/98: * Friendly monsters go after other monsters first, but * also return to player, without attacking them, if they * cannot find any targets. A marine's best friend :) */ actor->pursuecount = 0; if (!(actor->flags & MF_FRIEND && P_LookForTargets(actor, false)) && !((targ = actor->subsector->sector->soundtarget) && targ->flags & MF_SHOOTABLE && (P_SetTarget(&actor->target, targ), !(actor->flags & MF_AMBUSH) || P_CheckSight(actor, targ))) && (actor->flags & MF_FRIEND || !P_LookForTargets(actor, false))) return; // go into chase state if (actor->info->seesound) { int sound; switch (actor->info->seesound) { case sfx_posit1: case sfx_posit2: case sfx_posit3: sound = sfx_posit1+P_Random(pr_see)%3; break; case sfx_bgsit1: case sfx_bgsit2: sound = sfx_bgsit1+P_Random(pr_see)%2; break; default: sound = actor->info->seesound; break; } if (actor->type==MT_SPIDER || actor->type == MT_CYBORG) S_StartSound(NULL, sound); // full volume else S_StartSound(actor, sound); } P_SetMobjState(actor, actor->info->seestate); } // // A_KeepChasing // // killough 10/98: // Allows monsters to continue movement while attacking // static void A_KeepChasing(mobj_t *actor) { if (actor->movecount) { actor->movecount--; if (actor->strafecount) actor->strafecount--; P_SmartMove(actor); } } // // A_Chase // Actor has a melee attack, // so it tries to close as fast as possible // void A_Chase(mobj_t *actor) { if (actor->reactiontime) actor->reactiontime--; if (actor->threshold) { /* modify target threshold */ if (!actor->target || actor->target->health <= 0) actor->threshold = 0; else actor->threshold--; } /* turn towards movement direction if not there yet * killough 9/7/98: keep facing towards target if strafing or backing out */ if (actor->strafecount) A_FaceTarget(actor); else if (actor->movedir < 8) { int delta = (actor->angle &= (7<<29)) - (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)) { if (!P_LookForTargets(actor,true)) // look for a new target P_SetMobjState(actor, actor->info->spawnstate); // no new target return; } // do not attack twice in a row if (actor->flags & MF_JUSTATTACKED) { actor->flags &= ~MF_JUSTATTACKED; if (gameskill != sk_nightmare && !fastparm) P_NewChaseDir(actor); return; } // check for melee attack if (actor->info->meleestate && P_CheckMeleeRange(actor)) { if (actor->info->attacksound) S_StartSound(actor, actor->info->attacksound); P_SetMobjState(actor, actor->info->meleestate); /* killough 8/98: remember an attack * cph - DEMOSYNC? */ if (!actor->info->missilestate) actor->flags |= MF_JUSTHIT; return; } // check for missile attack if (actor->info->missilestate) if (!(gameskill < sk_nightmare && !fastparm && actor->movecount)) if (P_CheckMissileRange(actor)) { P_SetMobjState(actor, actor->info->missilestate); actor->flags |= MF_JUSTATTACKED; return; } if (!actor->threshold) { if (!mbf_features) { /* killough 9/9/98: for backward demo compatibility */ if (netgame && !P_CheckSight(actor, actor->target) && P_LookForPlayers(actor, true)) return; } /* killough 7/18/98, 9/9/98: new monster AI */ else if (help_friends && P_HelpFriend(actor)) return; /* killough 9/8/98: Help friends in need */ /* Look for new targets if current one is bad or is out of view */ else if (actor->pursuecount) actor->pursuecount--; else { /* Our pursuit time has expired. We're going to think about * changing targets */ actor->pursuecount = BASETHRESHOLD; /* Unless (we have a live target * and it's not friendly * and we can see it) * try to find a new one; return if sucessful */ if (!(actor->target && actor->target->health > 0 && ((comp[comp_pursuit] && !netgame) || (((actor->target->flags ^ actor->flags) & MF_FRIEND || (!(actor->flags & MF_FRIEND) && monster_infighting)) && P_CheckSight(actor, actor->target)))) && P_LookForTargets(actor, true)) return; /* (Current target was good, or no new target was found.) * * If monster is a missile-less friend, give up pursuit and * return to player, if no attacks have occurred recently. */ if (!actor->info->missilestate && actor->flags & MF_FRIEND) { if (actor->flags & MF_JUSTHIT) /* if recent action, */ actor->flags &= ~MF_JUSTHIT; /* keep fighting */ else if (P_LookForPlayers(actor, true)) /* else return to player */ return; } } } if (actor->strafecount) actor->strafecount--; // chase towards player if (--actor->movecount<0 || !P_SmartMove(actor)) P_NewChaseDir(actor); // make active sound if (actor->info->activesound && P_Random(pr_see)<3) S_StartSound(actor, actor->info->activesound); } // // 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) { // killough 5/5/98: remove dependence on order of evaluation: int t = P_Random(pr_facetarget); actor->angle += (t-P_Random(pr_facetarget))<<21; } } // // A_PosAttack // void A_PosAttack(mobj_t *actor) { int angle, damage, slope, t; if (!actor->target) return; A_FaceTarget(actor); angle = actor->angle; slope = P_AimLineAttack(actor, angle, MISSILERANGE, 0); /* killough 8/2/98 */ S_StartSound(actor, sfx_pistol); // killough 5/5/98: remove dependence on order of evaluation: t = P_Random(pr_posattack); angle += (t - P_Random(pr_posattack))<<20; damage = (P_Random(pr_posattack)%5 + 1)*3; P_LineAttack(actor, angle, MISSILERANGE, slope, damage); } void A_SPosAttack(mobj_t* actor) { int i, bangle, slope; if (!actor->target) return; S_StartSound(actor, sfx_shotgn); A_FaceTarget(actor); bangle = actor->angle; slope = P_AimLineAttack(actor, bangle, MISSILERANGE, 0); /* killough 8/2/98 */ for (i=0; i<3; i++) { // killough 5/5/98: remove dependence on order of evaluation: int t = P_Random(pr_sposattack); int angle = bangle + ((t - P_Random(pr_sposattack))<<20); int damage = ((P_Random(pr_sposattack)%5)+1)*3; P_LineAttack(actor, angle, MISSILERANGE, slope, damage); } } void A_CPosAttack(mobj_t *actor) { int angle, bangle, damage, slope, t; if (!actor->target) return; S_StartSound(actor, sfx_shotgn); A_FaceTarget(actor); bangle = actor->angle; slope = P_AimLineAttack(actor, bangle, MISSILERANGE, 0); /* killough 8/2/98 */ // killough 5/5/98: remove dependence on order of evaluation: t = P_Random(pr_cposattack); angle = bangle + ((t - P_Random(pr_cposattack))<<20); damage = ((P_Random(pr_cposattack)%5)+1)*3; P_LineAttack(actor, angle, MISSILERANGE, slope, damage); } void A_CPosRefire(mobj_t *actor) { // keep firing unless target got out of sight A_FaceTarget(actor); /* killough 12/98: Stop firing if a friend has gotten in the way */ if (P_HitFriend(actor)) goto stop; /* killough 11/98: prevent refiring on friends continuously */ if (P_Random(pr_cposrefire) < 40) { if (actor->target && actor->flags & actor->target->flags & MF_FRIEND) goto stop; else return; } if (!actor->target || actor->target->health <= 0 || !P_CheckSight(actor, actor->target)) stop: P_SetMobjState(actor, actor->info->seestate); } void A_SpidRefire(mobj_t* actor) { // keep firing unless target got out of sight A_FaceTarget(actor); /* killough 12/98: Stop firing if a friend has gotten in the way */ if (P_HitFriend(actor)) goto stop; if (P_Random(pr_spidrefire) < 10) return; // killough 11/98: prevent refiring on friends continuously if (!actor->target || actor->target->health <= 0 || actor->flags & actor->target->flags & MF_FRIEND || !P_CheckSight(actor, actor->target)) stop: P_SetMobjState(actor, actor->info->seestate); } void A_BspiAttack(mobj_t *actor) { if (!actor->target) return; A_FaceTarget(actor); P_SpawnMissile(actor, actor->target, MT_ARACHPLAZ); // launch a missile } // // A_TroopAttack // void A_TroopAttack(mobj_t *actor) { if (!actor->target) return; A_FaceTarget(actor); if (P_CheckMeleeRange(actor)) { int damage; S_StartSound(actor, sfx_claw); damage = (P_Random(pr_troopattack)%8+1)*3; P_DamageMobj(actor->target, actor, actor, damage); return; } P_SpawnMissile(actor, actor->target, MT_TROOPSHOT); // launch a missile } void A_SargAttack(mobj_t *actor) { if (!actor->target) return; A_FaceTarget(actor); if (P_CheckMeleeRange(actor)) { int damage = ((P_Random(pr_sargattack)%10)+1)*4; P_DamageMobj(actor->target, actor, actor, damage); } } void A_HeadAttack(mobj_t *actor) { if (!actor->target) return; A_FaceTarget (actor); if (P_CheckMeleeRange(actor)) { int damage = (P_Random(pr_headattack)%6+1)*10; P_DamageMobj(actor->target, actor, actor, damage); return; } P_SpawnMissile(actor, actor->target, MT_HEADSHOT); // launch a missile } void A_CyberAttack(mobj_t *actor) { if (!actor->target) return; A_FaceTarget(actor); P_SpawnMissile(actor, actor->target, MT_ROCKET); } void A_BruisAttack(mobj_t *actor) { if (!actor->target) return; if (P_CheckMeleeRange(actor)) { int damage; S_StartSound(actor, sfx_claw); damage = (P_Random(pr_bruisattack)%8+1)*10; P_DamageMobj(actor->target, actor, actor, damage); return; } P_SpawnMissile(actor, actor->target, MT_BRUISERSHOT); // launch a missile } // // A_SkelMissile // void A_SkelMissile(mobj_t *actor) { mobj_t *mo; if (!actor->target) return; A_FaceTarget (actor); actor->z += 16*FRACUNIT; // so missile spawns higher mo = P_SpawnMissile (actor, actor->target, MT_TRACER); actor->z -= 16*FRACUNIT; // back to normal mo->x += mo->momx; mo->y += mo->momy; P_SetTarget(&mo->tracer, actor->target); } int TRACEANGLE = 0xc000000; void A_Tracer(mobj_t *actor) { angle_t exact; fixed_t dist; fixed_t slope; mobj_t *dest; mobj_t *th; /* killough 1/18/98: this is why some missiles do not have smoke * and some do. Also, internal demos start at random gametics, thus * the bug in which revenants cause internal demos to go out of sync. * * killough 3/6/98: fix revenant internal demo bug by subtracting * levelstarttic from gametic. * * killough 9/29/98: use new "basetic" so that demos stay in sync * during pauses and menu activations, while retaining old demo sync. * * leveltime would have been better to use to start with in Doom, but * since old demos were recorded using gametic, we must stick with it, * and improvise around it (using leveltime causes desync across levels). */ if ((gametic-basetic) & 3) return; // spawn a puff of smoke behind the rocket P_SpawnPuff(actor->x, actor->y, actor->z); th = P_SpawnMobj (actor->x-actor->momx, actor->y-actor->momy, actor->z, MT_SMOKE); th->momz = FRACUNIT; th->tics -= P_Random(pr_tracer) & 3; if (th->tics < 1) th->tics = 1; // adjust direction dest = actor->tracer; if (!dest || dest->health <= 0) return; // change angle exact = R_PointToAngle2(actor->x, actor->y, dest->x, dest->y); if (exact != actor->angle) { if (exact - actor->angle > 0x80000000) { actor->angle -= TRACEANGLE; if (exact - actor->angle < 0x80000000) actor->angle = exact; } else { actor->angle += TRACEANGLE; if (exact - actor->angle > 0x80000000) actor->angle = exact; } } exact = actor->angle>>ANGLETOFINESHIFT; actor->momx = FixedMul(actor->info->speed, finecosine[exact]); actor->momy = FixedMul(actor->info->speed, finesine[exact]); // change slope dist = P_AproxDistance(dest->x - actor->x, dest->y - actor->y); dist = dist / actor->info->speed; if (dist < 1) dist = 1; slope = (dest->z+40*FRACUNIT - actor->z) / dist; if (slope < actor->momz) actor->momz -= FRACUNIT/8; else actor->momz += FRACUNIT/8; } void A_SkelWhoosh(mobj_t *actor) { if (!actor->target) return; A_FaceTarget(actor); S_StartSound(actor,sfx_skeswg); } void A_SkelFist(mobj_t *actor) { if (!actor->target) return; A_FaceTarget(actor); if (P_CheckMeleeRange(actor)) { int damage = ((P_Random(pr_skelfist)%10)+1)*6; S_StartSound(actor, sfx_skepch); P_DamageMobj(actor->target, actor, actor, damage); } } // // PIT_VileCheck // Detect a corpse that could be raised. // mobj_t* corpsehit; mobj_t* vileobj; fixed_t viletryx; fixed_t viletryy; static boolean PIT_VileCheck(mobj_t *thing) { int maxdist; boolean check; if (!(thing->flags & MF_CORPSE) ) return true; // not a monster if (thing->tics != -1) return true; // not lying still yet if (thing->info->raisestate == S_NULL) return true; // monster doesn't have a raise state maxdist = thing->info->radius + mobjinfo[MT_VILE].radius; if (D_abs(thing->x-viletryx) > maxdist || D_abs(thing->y-viletryy) > maxdist) return true; // not actually touching // Check to see if the radius and height are zero. If they are // phares // then this is a crushed monster that has been turned into a // | // gib. One of the options may be to ignore this guy. // V // Option 1: the original, buggy method, -> ghost (compatibility) // Option 2: ressurect the monster, but not as a ghost // Option 3: ignore the gib // if (Option3) // ^ // if ((thing->height == 0) && (thing->radius == 0)) // | // return true; // phares corpsehit = thing; corpsehit->momx = corpsehit->momy = 0; if (comp[comp_vile]) // phares { // | corpsehit->height <<= 2; // V check = P_CheckPosition(corpsehit,corpsehit->x,corpsehit->y); corpsehit->height >>= 2; } else { int height,radius; height = corpsehit->height; // save temporarily radius = corpsehit->radius; // save temporarily corpsehit->height = corpsehit->info->height; corpsehit->radius = corpsehit->info->radius; corpsehit->flags |= MF_SOLID; check = P_CheckPosition(corpsehit,corpsehit->x,corpsehit->y); corpsehit->height = height; // restore corpsehit->radius = radius; // restore // ^ corpsehit->flags &= ~MF_SOLID; } // | // phares if (!check) return true; // doesn't fit here return false; // got one, so stop checking } // // A_VileChase // Check for ressurecting a body // void A_VileChase(mobj_t* actor) { int xl, xh; int yl, yh; int bx, by; if (actor->movedir != DI_NODIR) { // check for corpses to raise viletryx = actor->x + actor->info->speed*xspeed[actor->movedir]; viletryy = actor->y + actor->info->speed*yspeed[actor->movedir]; xl = (viletryx - bmaporgx - MAXRADIUS*2)>>MAPBLOCKSHIFT; xh = (viletryx - bmaporgx + MAXRADIUS*2)>>MAPBLOCKSHIFT; yl = (viletryy - bmaporgy - MAXRADIUS*2)>>MAPBLOCKSHIFT; yh = (viletryy - bmaporgy + MAXRADIUS*2)>>MAPBLOCKSHIFT; vileobj = actor; for (bx=xl ; bx<=xh ; bx++) { for (by=yl ; by<=yh ; by++) { // Call PIT_VileCheck to check // whether object is a corpse // that canbe raised. if (!P_BlockThingsIterator(bx,by,PIT_VileCheck)) { mobjinfo_t *info; // got one! mobj_t* temp = actor->target; actor->target = corpsehit; A_FaceTarget(actor); actor->target = temp; P_SetMobjState(actor, S_VILE_HEAL1); S_StartSound(corpsehit, sfx_slop); info = corpsehit->info; P_SetMobjState(corpsehit,info->raisestate); if (comp[comp_vile]) // phares corpsehit->height <<= 2; // | else // V { corpsehit->height = info->height; // fix Ghost bug corpsehit->radius = info->radius; // fix Ghost bug } // phares /* killough 7/18/98: * friendliness is transferred from AV to raised corpse */ corpsehit->flags = (info->flags & ~MF_FRIEND) | (actor->flags & MF_FRIEND); if (!((corpsehit->flags ^ MF_COUNTKILL) & (MF_FRIEND | MF_COUNTKILL))) totallive++; corpsehit->health = info->spawnhealth; P_SetTarget(&corpsehit->target, NULL); // killough 11/98 if (mbf_features) { /* kilough 9/9/98 */ P_SetTarget(&corpsehit->lastenemy, NULL); corpsehit->flags &= ~MF_JUSTHIT; } /* killough 8/29/98: add to appropriate thread */ P_UpdateThinker(&corpsehit->thinker); return; } } } } A_Chase(actor); // Return to normal attack. } // // A_VileStart // void A_VileStart(mobj_t *actor) { S_StartSound(actor, sfx_vilatk); } // // A_Fire // Keep fire in front of player unless out of sight // void A_StartFire(mobj_t *actor) { S_StartSound(actor,sfx_flamst); A_Fire(actor); } void A_FireCrackle(mobj_t* actor) { S_StartSound(actor,sfx_flame); A_Fire(actor); } void A_Fire(mobj_t *actor) { unsigned an; mobj_t *dest = actor->tracer; if (!dest) return; // don't move it if the vile lost sight if (!P_CheckSight(actor->target, dest) ) return; an = dest->angle >> ANGLETOFINESHIFT; P_UnsetThingPosition(actor); actor->x = dest->x + FixedMul(24*FRACUNIT, finecosine[an]); actor->y = dest->y + FixedMul(24*FRACUNIT, finesine[an]); actor->z = dest->z; P_SetThingPosition(actor); } // // A_VileTarget // Spawn the hellfire // void A_VileTarget(mobj_t *actor) { mobj_t *fog; if (!actor->target) return; A_FaceTarget(actor); // killough 12/98: fix Vile fog coordinates // CPhipps - compatibility optioned fog = P_SpawnMobj(actor->target->x, (compatibility_level < lxdoom_1_compatibility) ? actor->target->x : actor->target->y, actor->target->z,MT_FIRE); P_SetTarget(&actor->tracer, fog); P_SetTarget(&fog->target, actor); P_SetTarget(&fog->tracer, actor->target); A_Fire(fog); } // // A_VileAttack // void A_VileAttack(mobj_t *actor) { mobj_t *fire; int an; if (!actor->target) return; A_FaceTarget(actor); if (!P_CheckSight(actor, actor->target)) return; S_StartSound(actor, sfx_barexp); P_DamageMobj(actor->target, actor, actor, 20); actor->target->momz = 1000*FRACUNIT/actor->target->info->mass; an = actor->angle >> ANGLETOFINESHIFT; fire = actor->tracer; if (!fire) return; // move the fire between the vile and the player fire->x = actor->target->x - FixedMul (24*FRACUNIT, finecosine[an]); fire->y = actor->target->y - FixedMul (24*FRACUNIT, finesine[an]); P_RadiusAttack(fire, actor, 70); } // // Mancubus attack, // firing three missiles (bruisers) // in three different directions? // Doesn't look like it. // #define FATSPREAD (ANG90/8) void A_FatRaise(mobj_t *actor) { A_FaceTarget(actor); S_StartSound(actor, sfx_manatk); } void A_FatAttack1(mobj_t *actor) { mobj_t *mo; int an; if (!actor->target) return; A_FaceTarget(actor); // Change direction to ... actor->angle += FATSPREAD; P_SpawnMissile(actor, actor->target, MT_FATSHOT); mo = P_SpawnMissile (actor, actor->target, MT_FATSHOT); mo->angle += FATSPREAD; an = mo->angle >> ANGLETOFINESHIFT; mo->momx = FixedMul(mo->info->speed, finecosine[an]); mo->momy = FixedMul(mo->info->speed, finesine[an]); } void A_FatAttack2(mobj_t *actor) { mobj_t *mo; int an; if (!actor->target) return; A_FaceTarget(actor); // Now here choose opposite deviation. actor->angle -= FATSPREAD; P_SpawnMissile(actor, actor->target, MT_FATSHOT); mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT); mo->angle -= FATSPREAD*2; an = mo->angle >> ANGLETOFINESHIFT; mo->momx = FixedMul(mo->info->speed, finecosine[an]); mo->momy = FixedMul(mo->info->speed, finesine[an]); } void A_FatAttack3(mobj_t *actor) { mobj_t *mo; int an; if (!actor->target) return; A_FaceTarget(actor); mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT); mo->angle -= FATSPREAD/2; an = mo->angle >> ANGLETOFINESHIFT; mo->momx = FixedMul(mo->info->speed, finecosine[an]); mo->momy = FixedMul(mo->info->speed, finesine[an]); mo = P_SpawnMissile(actor, actor->target, MT_FATSHOT); mo->angle += FATSPREAD/2; an = mo->angle >> ANGLETOFINESHIFT; mo->momx = FixedMul(mo->info->speed, finecosine[an]); mo->momy = FixedMul(mo->info->speed, finesine[an]); } // // SkullAttack // Fly at the player like a missile. // #define SKULLSPEED (20*FRACUNIT) void A_SkullAttack(mobj_t *actor) { mobj_t *dest; angle_t an; int dist; if (!actor->target) return; dest = actor->target; actor->flags |= MF_SKULLFLY; S_StartSound(actor, actor->info->attacksound); A_FaceTarget(actor); an = actor->angle >> ANGLETOFINESHIFT; actor->momx = FixedMul(SKULLSPEED, finecosine[an]); actor->momy = FixedMul(SKULLSPEED, finesine[an]); dist = P_AproxDistance(dest->x - actor->x, dest->y - actor->y); dist = dist / SKULLSPEED; if (dist < 1) dist = 1; actor->momz = (dest->z+(dest->height>>1) - actor->z) / dist; } // // A_PainShootSkull // Spawn a lost soul and launch it at the target // static void A_PainShootSkull(mobj_t *actor, angle_t angle) { fixed_t x,y,z; mobj_t *newmobj; angle_t an; int prestep; // The original code checked for 20 skulls on the level, // phares // and wouldn't spit another one if there were. If not in // phares // compatibility mode, we remove the limit. // phares // phares if (comp[comp_pain]) /* killough 10/98: compatibility-optioned */ { // count total number of skulls currently on the level int count = 0; thinker_t *currentthinker = NULL; while ((currentthinker = P_NextThinker(currentthinker,th_all)) != NULL) if ((currentthinker->function == P_MobjThinker) && ((mobj_t *)currentthinker)->type == MT_SKULL) count++; if (count > 20) // phares return; // phares } // okay, there's room for another one an = angle >> ANGLETOFINESHIFT; prestep = 4*FRACUNIT + 3*(actor->info->radius + mobjinfo[MT_SKULL].radius)/2; x = actor->x + FixedMul(prestep, finecosine[an]); y = actor->y + FixedMul(prestep, finesine[an]); z = actor->z + 8*FRACUNIT; if (comp[comp_skull]) /* killough 10/98: compatibility-optioned */ newmobj = P_SpawnMobj(x, y, z, MT_SKULL); // phares else // V { // Check whether the Lost Soul is being fired through a 1-sided // wall or an impassible line, or a "monsters can't cross" line. // If it is, then we don't allow the spawn. This is a bug fix, but // it should be considered an enhancement, since it may disturb // existing demos, so don't do it in compatibility mode. if (Check_Sides(actor,x,y)) return; newmobj = P_SpawnMobj(x, y, z, MT_SKULL); // Check to see if the new Lost Soul's z value is above the // ceiling of its new sector, or below the floor. If so, kill it. if ((newmobj->z > (newmobj->subsector->sector->ceilingheight - newmobj->height)) || (newmobj->z < newmobj->subsector->sector->floorheight)) { // kill it immediately P_DamageMobj(newmobj,actor,actor,10000); return; // ^ } // | } // phares /* killough 7/20/98: PEs shoot lost souls with the same friendliness */ newmobj->flags = (newmobj->flags & ~MF_FRIEND) | (actor->flags & MF_FRIEND); /* killough 8/29/98: add to appropriate thread */ P_UpdateThinker(&newmobj->thinker); // Check for movements. // killough 3/15/98: don't jump over dropoffs: if (!P_TryMove(newmobj, newmobj->x, newmobj->y, false)) { // kill it immediately P_DamageMobj(newmobj, actor, actor, 10000); return; } P_SetTarget(&newmobj->target, actor->target); A_SkullAttack(newmobj); } // // A_PainAttack // Spawn a lost soul and launch it at the target // void A_PainAttack(mobj_t *actor) { if (!actor->target) return; A_FaceTarget(actor); A_PainShootSkull(actor, actor->angle); } void A_PainDie(mobj_t *actor) { A_Fall(actor); A_PainShootSkull(actor, actor->angle+ANG90); A_PainShootSkull(actor, actor->angle+ANG180); A_PainShootSkull(actor, actor->angle+ANG270); } void A_Scream(mobj_t *actor) { int sound; switch (actor->info->deathsound) { case 0: return; case sfx_podth1: case sfx_podth2: case sfx_podth3: sound = sfx_podth1 + P_Random(pr_scream)%3; break; case sfx_bgdth1: case sfx_bgdth2: sound = sfx_bgdth1 + P_Random(pr_scream)%2; break; default: sound = actor->info->deathsound; break; } // Check for bosses. if (actor->type==MT_SPIDER || actor->type == MT_CYBORG) S_StartSound(NULL, sound); // full volume else S_StartSound(actor, sound); } void A_XScream(mobj_t *actor) { S_StartSound(actor, sfx_slop); } void A_Pain(mobj_t *actor) { if (actor->info->painsound) S_StartSound(actor, actor->info->painsound); } void A_Fall(mobj_t *actor) { // actor is on ground, it can be walked over actor->flags &= ~MF_SOLID; } // // A_Explode // void A_Explode(mobj_t *thingy) { P_RadiusAttack( thingy, thingy->target, 128 ); } // // A_BossDeath // Possibly trigger special effects // if on first boss level // void A_BossDeath(mobj_t *mo) { thinker_t *th; line_t junk; int i; if (gamemode == commercial) { if (gamemap != 7) return; if ((mo->type != MT_FATSO) && (mo->type != MT_BABY)) return; } else { // e6y // Additional check of gameepisode is necessary, because // there is no right or wrong solution for E4M6 in original EXEs, // there's nothing to emulate. if (comp[comp_666] && gameepisode < 4) { // e6y // Only following checks are present in doom2.exe ver. 1.666 and 1.9 // instead of separate checks for each episode in doomult.exe, plutonia.exe and tnt.exe // There is no more desync on doom.wad\episode3.lmp // http://www.doomworld.com/idgames/index.php?id=6909 if (gamemap != 8) return; if (mo->type == MT_BRUISER && gameepisode != 1) return; } else { switch(gameepisode) { case 1: if (gamemap != 8) return; if (mo->type != MT_BRUISER) return; break; case 2: if (gamemap != 8) return; if (mo->type != MT_CYBORG) return; break; case 3: if (gamemap != 8) return; if (mo->type != MT_SPIDER) return; break; case 4: switch(gamemap) { case 6: if (mo->type != MT_CYBORG) return; break; case 8: if (mo->type != MT_SPIDER) return; break; default: return; break; } break; default: if (gamemap != 8) return; break; } } } // make sure there is a player alive for victory for (i=0; i 0) break; if (i==MAXPLAYERS) return; // no one left alive, so do not end game // scan the remaining thinkers to see // if all bosses are dead for (th = thinkercap.next ; th != &thinkercap ; th=th->next) if (th->function == P_MobjThinker) { mobj_t *mo2 = (mobj_t *) th; if (mo2 != mo && mo2->type == mo->type && mo2->health > 0) return; // other boss not dead } // victory! if ( gamemode == commercial) { if (gamemap == 7) { if (mo->type == MT_FATSO) { junk.tag = 666; EV_DoFloor(&junk,lowerFloorToLowest); return; } if (mo->type == MT_BABY) { junk.tag = 667; EV_DoFloor(&junk,raiseToTexture); return; } } } else { switch(gameepisode) { case 1: junk.tag = 666; EV_DoFloor(&junk, lowerFloorToLowest); return; break; case 4: switch(gamemap) { case 6: junk.tag = 666; EV_DoDoor(&junk, blazeOpen); return; break; case 8: junk.tag = 666; EV_DoFloor(&junk, lowerFloorToLowest); return; break; } } } G_ExitLevel(); } void A_Hoof (mobj_t* mo) { S_StartSound(mo, sfx_hoof); A_Chase(mo); } void A_Metal(mobj_t *mo) { S_StartSound(mo, sfx_metal); A_Chase(mo); } void A_BabyMetal(mobj_t *mo) { S_StartSound(mo, sfx_bspwlk); A_Chase(mo); } void A_OpenShotgun2(player_t *player, pspdef_t *psp) { S_StartSound(player->mo, sfx_dbopn); } void A_LoadShotgun2(player_t *player, pspdef_t *psp) { S_StartSound(player->mo, sfx_dbload); } void A_CloseShotgun2(player_t *player, pspdef_t *psp) { S_StartSound(player->mo, sfx_dbcls); A_ReFire(player,psp); } // killough 2/7/98: Remove limit on icon landings: mobj_t **braintargets; int numbraintargets_alloc; int numbraintargets; struct brain_s brain; // killough 3/26/98: global state of boss brain // killough 3/26/98: initialize icon landings at level startup, // rather than at boss wakeup, to prevent savegame-related crashes void P_SpawnBrainTargets(void) // killough 3/26/98: renamed old function { thinker_t *thinker; // find all the target spots numbraintargets = 0; brain.targeton = 0; brain.easy = 0; // killough 3/26/98: always init easy to 0 for (thinker = thinkercap.next ; thinker != &thinkercap ; thinker = thinker->next) if (thinker->function == P_MobjThinker) { mobj_t *m = (mobj_t *) thinker; if (m->type == MT_BOSSTARGET ) { // killough 2/7/98: remove limit on icon landings: if (numbraintargets >= numbraintargets_alloc) braintargets = realloc(braintargets, (numbraintargets_alloc = numbraintargets_alloc ? numbraintargets_alloc*2 : 32) *sizeof *braintargets); braintargets[numbraintargets++] = m; } } } void A_BrainAwake(mobj_t *mo) { S_StartSound(NULL,sfx_bossit); // killough 3/26/98: only generates sound now } void A_BrainPain(mobj_t *mo) { S_StartSound(NULL,sfx_bospn); } void A_BrainScream(mobj_t *mo) { int x; for (x=mo->x - 196*FRACUNIT ; x< mo->x + 320*FRACUNIT ; x+= FRACUNIT*8) { int y = mo->y - 320*FRACUNIT; int z = 128 + P_Random(pr_brainscream)*2*FRACUNIT; mobj_t *th = P_SpawnMobj (x,y,z, MT_ROCKET); th->momz = P_Random(pr_brainscream)*512; P_SetMobjState(th, S_BRAINEXPLODE1); th->tics -= P_Random(pr_brainscream)&7; if (th->tics < 1) th->tics = 1; } S_StartSound(NULL,sfx_bosdth); } void A_BrainExplode(mobj_t *mo) { // killough 5/5/98: remove dependence on order of evaluation: int t = P_Random(pr_brainexp); int x = mo->x + (t - P_Random(pr_brainexp))*2048; int y = mo->y; int z = 128 + P_Random(pr_brainexp)*2*FRACUNIT; mobj_t *th = P_SpawnMobj(x,y,z, MT_ROCKET); th->momz = P_Random(pr_brainexp)*512; P_SetMobjState(th, S_BRAINEXPLODE1); th->tics -= P_Random(pr_brainexp)&7; if (th->tics < 1) th->tics = 1; } void A_BrainDie(mobj_t *mo) { G_ExitLevel(); } void A_BrainSpit(mobj_t *mo) { mobj_t *targ, *newmobj; if (!numbraintargets) // killough 4/1/98: ignore if no targets return; brain.easy ^= 1; // killough 3/26/98: use brain struct if (gameskill <= sk_easy && !brain.easy) return; // shoot a cube at current target targ = braintargets[brain.targeton++]; // killough 3/26/98: brain.targeton %= numbraintargets; // Use brain struct for targets // spawn brain missile newmobj = P_SpawnMissile(mo, targ, MT_SPAWNSHOT); P_SetTarget(&newmobj->target, targ); newmobj->reactiontime = (short)(((targ->y-mo->y)/newmobj->momy)/newmobj->state->tics); // killough 7/18/98: brain friendliness is transferred newmobj->flags = (newmobj->flags & ~MF_FRIEND) | (mo->flags & MF_FRIEND); // killough 8/29/98: add to appropriate thread P_UpdateThinker(&newmobj->thinker); S_StartSound(NULL, sfx_bospit); } // travelling cube sound void A_SpawnSound(mobj_t *mo) { S_StartSound(mo,sfx_boscub); A_SpawnFly(mo); } void A_SpawnFly(mobj_t *mo) { mobj_t *newmobj; mobj_t *fog; mobj_t *targ; int r; mobjtype_t type; if (--mo->reactiontime) return; // still flying targ = mo->target; // First spawn teleport fog. fog = P_SpawnMobj(targ->x, targ->y, targ->z, MT_SPAWNFIRE); S_StartSound(fog, sfx_telept); // Randomly select monster to spawn. r = P_Random(pr_spawnfly); // Probability distribution (kind of :), decreasing likelihood. if ( r<50 ) type = MT_TROOP; else if (r<90) type = MT_SERGEANT; else if (r<120) type = MT_SHADOWS; else if (r<130) type = MT_PAIN; else if (r<160) type = MT_HEAD; else if (r<162) type = MT_VILE; else if (r<172) type = MT_UNDEAD; else if (r<192) type = MT_BABY; else if (r<222) type = MT_FATSO; else if (r<246) type = MT_KNIGHT; else type = MT_BRUISER; newmobj = P_SpawnMobj(targ->x, targ->y, targ->z, type); /* killough 7/18/98: brain friendliness is transferred */ newmobj->flags = (newmobj->flags & ~MF_FRIEND) | (mo->flags & MF_FRIEND); /* killough 8/29/98: add to appropriate thread */ P_UpdateThinker(&newmobj->thinker); if (P_LookForTargets(newmobj,true)) /* killough 9/4/98 */ P_SetMobjState(newmobj, newmobj->info->seestate); // telefrag anything in this spot P_TeleportMove(newmobj, newmobj->x, newmobj->y, true); /* killough 8/9/98 */ // remove self (i.e., cube). P_RemoveMobj(mo); } void A_PlayerScream(mobj_t *mo) { int sound = sfx_pldeth; // Default death sound. if (gamemode != shareware && mo->health < -50) sound = sfx_pdiehi; // IF THE PLAYER DIES LESS THAN -50% WITHOUT GIBBING S_StartSound(mo, sound); } /* cph - MBF-added codepointer functions */ // killough 11/98: kill an object void A_Die(mobj_t *actor) { P_DamageMobj(actor, NULL, NULL, actor->health); } // // A_Detonate // killough 8/9/98: same as A_Explode, except that the damage is variable // void A_Detonate(mobj_t *mo) { P_RadiusAttack(mo, mo->target, mo->info->damage); } // // killough 9/98: a mushroom explosion effect, sorta :) // Original idea: Linguica // void A_Mushroom(mobj_t *actor) { int i, j, n = actor->info->damage; A_Explode(actor); // First make normal explosion // Now launch mushroom cloud for (i = -n; i <= n; i += 8) for (j = -n; j <= n; j += 8) { mobj_t target = *actor, *mo; target.x += i << FRACBITS; // Aim in many directions from source target.y += j << FRACBITS; target.z += P_AproxDistance(i,j) << (FRACBITS+2); // Aim up fairly high mo = P_SpawnMissile(actor, &target, MT_FATSHOT); // Launch fireball mo->momx >>= 1; mo->momy >>= 1; // Slow it down a bit mo->momz >>= 1; mo->flags &= ~MF_NOGRAVITY; // Make debris fall under gravity } } // // killough 11/98 // // The following were inspired by Len Pitre // // A small set of highly-sought-after code pointers // void A_Spawn(mobj_t *mo) { if (mo->state->misc1) { /* mobj_t *newmobj = */ P_SpawnMobj(mo->x, mo->y, (mo->state->misc2 << FRACBITS) + mo->z, mo->state->misc1 - 1); /* CPhipps - no friendlyness (yet) newmobj->flags = (newmobj->flags & ~MF_FRIEND) | (mo->flags & MF_FRIEND); */ } } void A_Turn(mobj_t *mo) { mo->angle += (unsigned int)(((uint_64_t) mo->state->misc1 << 32) / 360); } void A_Face(mobj_t *mo) { mo->angle = (unsigned int)(((uint_64_t) mo->state->misc1 << 32) / 360); } void A_Scratch(mobj_t *mo) { mo->target && (A_FaceTarget(mo), P_CheckMeleeRange(mo)) ? mo->state->misc2 ? S_StartSound(mo, mo->state->misc2) : (void) 0, P_DamageMobj(mo->target, mo, mo, mo->state->misc1) : (void) 0; } void A_PlaySound(mobj_t *mo) { S_StartSound(mo->state->misc2 ? NULL : mo, mo->state->misc1); } void A_RandomJump(mobj_t *mo) { if (P_Random(pr_randomjump) < mo->state->misc2) P_SetMobjState(mo, mo->state->misc1); } // // This allows linedef effects to be activated inside deh frames. // void A_LineEffect(mobj_t *mo) { static line_t junk; player_t player; player_t *oldplayer; junk = *lines; oldplayer = mo->player; mo->player = &player; player.health = 100; junk.special = (short)mo->state->misc1; if (!junk.special) return; junk.tag = (short)mo->state->misc2; if (!P_UseSpecialLine(mo, &junk, 0)) P_CrossSpecialLine(&junk, 0, mo); mo->state->misc1 = junk.special; mo->player = oldplayer; }