diff options
| author | Dave Chapman <dave@dchapman.com> | 2006-03-28 15:44:01 +0000 |
|---|---|---|
| committer | Dave Chapman <dave@dchapman.com> | 2006-03-28 15:44:01 +0000 |
| commit | 47f4a458d636a889e955e68f896708f1276febc0 (patch) | |
| tree | 99f770c02ef606f0abbdcd332ac39e69830d8007 /apps/plugins/doom/p_enemy.c | |
| parent | fff7d6157d56f233cad5c2003475e47a5ff809a7 (diff) | |
| download | rockbox-47f4a458d636a889e955e68f896708f1276febc0.zip rockbox-47f4a458d636a889e955e68f896708f1276febc0.tar.gz rockbox-47f4a458d636a889e955e68f896708f1276febc0.tar.bz2 rockbox-47f4a458d636a889e955e68f896708f1276febc0.tar.xz | |
Patch #2969 - Doom! Currently only working on the H300.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@9312 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/plugins/doom/p_enemy.c')
| -rw-r--r-- | apps/plugins/doom/p_enemy.c | 2623 |
1 files changed, 2623 insertions, 0 deletions
diff --git a/apps/plugins/doom/p_enemy.c b/apps/plugins/doom/p_enemy.c new file mode 100644 index 0000000..b1f40bc --- /dev/null +++ b/apps/plugins/doom/p_enemy.c @@ -0,0 +1,2623 @@ +/* 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 by + * Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze + * + * 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 "rockmacros.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; + +void A_Fall(mobj_t *actor); +void A_FaceTarget(mobj_t *actor); +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; i<sec->linecount; 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 +// +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 + if (actor->type == MT_DOGS && 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. +// + +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 || abs(deltay)>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 != (unsigned)(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; c<MAXPLAYERS; c++) + if (playeringame[c] && players[c].playerstate==PST_LIVE && + (anyone || P_IsVisible(actor, players[c].mo, allaround))) + { + P_SetTarget(&actor->target, 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 + 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,p_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 +// + +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; + +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_Fire(mobj_t *actor); + +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; + + 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; + + 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; + + 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 +// + +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; + for (currentthinker = thinkercap.next; + currentthinker != &thinkercap; + currentthinker = currentthinker->next) + 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 + { + 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<MAXPLAYERS; i++) + if (playeringame[i] && players[i].health > 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) +{ + (void)psp; + S_StartSound(player->mo, sfx_dbopn); +} + +void A_LoadShotgun2(player_t *player, pspdef_t *psp) +{ + (void)psp; + S_StartSound(player->mo, sfx_dbload); +} + +void A_ReFire(player_t *player, pspdef_t *psp); + +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) +{ + (void)mo; + S_StartSound(NULL,sfx_bossit); // killough 3/26/98: only generates sound now +} + +void A_BrainPain(mobj_t *mo) +{ + (void)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: + (void)mo; + 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) +{ + (void)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); +} + +void A_SpawnFly(mobj_t *mo); + +// 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; +} + +/***** Start of new functions for Andy Baker's stealth monsters ******/ + +void P_BecomeVisible(mobj_t* actor) +{ + actor->invisible = false; + actor->flags &= ~MF_TRANSLUCBITS; +}; + +void P_IncreaseVisibility(mobj_t* actor) +{ + if (actor->invisible) { + actor->invisible = false; + actor->flags |= MF_TRANSLUC25; + } else switch (actor->flags & MF_TRANSLUCBITS) { + case MF_TRANSLUC25: + actor->flags &= ~MF_TRANSLUCBITS; + actor->flags |= MF_TRANSLUC50; + break; + case MF_TRANSLUC50: + actor->flags &= ~MF_TRANSLUCBITS; + actor->flags |= MF_TRANSLUC25; + actor->flags |= MF_TRANSLUC50; + break; + case MF_TRANSLUC75: + actor->flags &= ~MF_TRANSLUCBITS; + break; + } +} + +void P_DecreaseVisibility(mobj_t* actor) +{ + if (actor->invisible) + return; // already invisible + + switch (actor->flags & MF_TRANSLUCBITS) { + case 0: + actor->flags &= ~MF_TRANSLUCBITS; + actor->flags |= MF_TRANSLUC75; + break; + case MF_TRANSLUC75: + actor->flags &= ~MF_TRANSLUCBITS; + actor->flags |= MF_TRANSLUC50; + break; + case MF_TRANSLUC50: + actor->flags &= ~MF_TRANSLUCBITS; + actor->flags |= MF_TRANSLUC25; + break; + case MF_TRANSLUC25: + actor->flags &= ~MF_TRANSLUCBITS; + actor->invisible = true; + } +} +/***** End of new functions for Andy Baker's stealth monsters ******/ + |