// P_enemy.c #include "doomdef.h" #include "p_local.h" void A_Fall (mobj_t *actor); /* =============================================================================== 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 =============================================================================== */ /* ================= = = P_NoiseAlert = = If a monster yells at a player, it will alert other monsters to the player = ================= */ mobj_t *soundtarget; void P_RecursiveSound (sector_t *sec, int soundblocks) { int i; line_t *check; sector_t *other; // wake up all monsters in this sector if (sec->validcount == validcount && sec->soundtraversed <= soundblocks+1) return; // already flooded sec->validcount = validcount; sec->soundtraversed = soundblocks+1; sec->soundtarget = soundtarget; for (i=0 ;ilinecount ; i++) { check = sec->lines[i]; if (! (check->flags & ML_TWOSIDED) ) continue; P_LineOpening (check); if (openrange <= 0) continue; // closed door if ( sides[ check->sidenum[0] ].sector == sec) other = sides[ check->sidenum[1] ] .sector; else other = sides[ check->sidenum[0] ].sector; if (check->flags & ML_SOUNDBLOCK) { if (!soundblocks) P_RecursiveSound (other, 1); } else P_RecursiveSound (other, soundblocks); } } /* ================= = = P_NoiseAlert = = If a monster yells at a player, it will alert other monsters to the player = ================= */ void P_NoiseAlert (mobj_t *target, mobj_t *emmiter) { soundtarget = target; validcount++; P_RecursiveSound (emmiter->subsector->sector, 0); } /* ================ = = P_CheckMeleeRange = ================ */ boolean P_CheckMeleeRange (mobj_t *actor) { mobj_t *pl; fixed_t dist; if (!actor->target) return false; pl = actor->target; dist = P_AproxDistance (pl->x-actor->x, pl->y-actor->y); if (dist >= MELEERANGE-20*FRACUNIT+pl->info->radius) return false; if (! P_CheckSight (actor, actor->target) ) return false; return true; } /* ================ = = P_CheckMissileRange = ================ */ boolean P_CheckMissileRange (mobj_t *actor) { fixed_t dist; if (! P_CheckSight (actor, actor->target) ) return false; if ( actor->flags & MF_JUSTHIT ) { // the target just hit the enemy, so fight back! actor->flags &= ~MF_JUSTHIT; return true; } if (actor->reactiontime) return false; // don't 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 >>= 16; 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 () < dist) return false; return true; } /* ================ = = P_Move = = Move in the current direction = returns false if the move is blocked ================ */ fixed_t xspeed[8] = {FRACUNIT,47000,0,-47000,-FRACUNIT,-47000,0,47000}; fixed_t yspeed[8] = {0,47000,FRACUNIT,47000,0,-47000,-FRACUNIT,-47000}; #define MAXSPECIALCROSS 8 extern line_t *spechit[MAXSPECIALCROSS]; extern int numspechit; boolean P_Move (mobj_t *actor) { fixed_t tryx, tryy; line_t *ld; boolean try, good; if (actor->movedir == DI_NODIR) return false; if ((unsigned)actor->movedir >= 8) I_Error ("Weird actor->movedir!"); tryx = actor->x + actor->info->speed*xspeed[actor->movedir]; tryy = actor->y + actor->info->speed*yspeed[actor->movedir]; try = P_TryMove (actor, tryx, tryy); if (!try) { // open any specials if (actor->flags & MF_FLOAT && floatok) { // must adjust height if (actor->z < tmfloorz) actor->z += FLOATSPEED; else actor->z -= FLOATSPEED; actor->flags |= MF_INFLOAT; return true; } if (!numspechit) return false; actor->movedir = DI_NODIR; good = false; while (numspechit--) { ld = spechit[numspechit]; // if the special isn't a door that can be opened, return false if (P_UseSpecialLine (actor, ld,0)) good = true; } return good; } else { actor->flags &= ~MF_INFLOAT; } if (! (actor->flags & MF_FLOAT) ) actor->z = actor->floorz; return true; } /* ================================== = = TryWalk = = Attempts to move actoron 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_Move (actor)) { return false; } actor->movecount = P_Random()&15; return true; } /* ================ = = P_NewChaseDir = ================ */ dirtype_t opposite[] = {DI_WEST, DI_SOUTHWEST, DI_SOUTH, DI_SOUTHEAST, DI_EAST, DI_NORTHEAST, DI_NORTH, DI_NORTHWEST, DI_NODIR}; dirtype_t diags[] = {DI_NORTHWEST,DI_NORTHEAST,DI_SOUTHWEST,DI_SOUTHEAST}; void P_NewChaseDir (mobj_t *actor) { fixed_t deltax,deltay; dirtype_t d[3]; dirtype_t tdir, olddir, turnaround; if (!actor->target) I_Error ("P_NewChaseDir: called with no target"); olddir = actor->movedir; turnaround=opposite[olddir]; deltax = actor->target->x - actor->x; deltay = actor->target->y - actor->y; if (deltax>10*FRACUNIT) d[1]= DI_EAST; else if (deltax<-10*FRACUNIT) d[1]= DI_WEST; else d[1]=DI_NODIR; if (deltay<-10*FRACUNIT) d[2]= DI_SOUTH; else if (deltay>10*FRACUNIT) d[2]= DI_NORTH; else d[2]=DI_NODIR; // try direct route if (d[1] != DI_NODIR && d[2] != DI_NODIR) { actor->movedir = diags[((deltay<0)<<1)+(deltax>0)]; if (actor->movedir != turnaround && P_TryWalk(actor)) return; } // try other directions if (P_Random() > 200 || abs(deltay)>abs(deltax)) { tdir=d[1]; d[1]=d[2]; d[2]=tdir; } if (d[1]==turnaround) d[1]=DI_NODIR; if (d[2]==turnaround) d[2]=DI_NODIR; if (d[1]!=DI_NODIR) { actor->movedir = d[1]; if (P_TryWalk(actor)) return; /*either moved forward or attacked*/ } if (d[2]!=DI_NODIR) { actor->movedir =d[2]; if (P_TryWalk(actor)) return; } /* there is no direct path to the player, so pick another direction */ if (olddir!=DI_NODIR) { actor->movedir =olddir; if (P_TryWalk(actor)) return; } if (P_Random()&1) /*randomly determine direction of search*/ { for (tdir=DI_EAST ; tdir<=DI_SOUTHEAST ; tdir++) { if (tdir!=turnaround) { actor->movedir =tdir; if ( P_TryWalk(actor) ) return; } } } else { for (tdir=DI_SOUTHEAST ; tdir != (DI_EAST-1) ;tdir--) { if (tdir!=turnaround) { actor->movedir =tdir; if ( P_TryWalk(actor) ) return; } } } if (turnaround != DI_NODIR) { actor->movedir =turnaround; if ( P_TryWalk(actor) ) return; } actor->movedir = DI_NODIR; // can't move } /* ================ = = P_LookForPlayers = = If allaround is false, only look 180 degrees in front = returns true if a player is targeted ================ */ boolean P_LookForPlayers (mobj_t *actor, boolean allaround) { int c, stop; player_t *player; sector_t *sector; angle_t an; fixed_t dist; sector = actor->subsector->sector; c = 0; stop = (actor->lastlook-1)&3; for ( ; ; actor->lastlook = (actor->lastlook+1)&3 ) { if (!playeringame[actor->lastlook]) continue; if (c++ == 2 || actor->lastlook == stop) return false; // done looking player = &players[actor->lastlook]; if (player->health <= 0) continue; // dead if (!P_CheckSight (actor, player->mo)) continue; // out of sight if (!allaround) { an = R_PointToAngle2 (actor->x, actor->y, player->mo->x, player->mo->y) - actor->angle; if (an > ANG90 && an < ANG270) { dist = P_AproxDistance (player->mo->x - actor->x, player->mo->y - actor->y); // if real close, react anyway if (dist > MELEERANGE) continue; // behind back } } actor->target = player->mo; return true; } return false; } //============================================================================= /* ============== = = A_KeenDie = ============== */ void A_KeenDie (mobj_t *mo) { thinker_t *th; mobj_t *mo2; 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) continue; 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->threshold = 0; // any shot will wake up targ = actor->subsector->sector->soundtarget; if (targ && (targ->flags & MF_SHOOTABLE) ) { actor->target = targ; if ( actor->flags & MF_AMBUSH ) { if (P_CheckSight (actor, actor->target)) goto seeyou; } else goto seeyou; } if (!P_LookForPlayers (actor, false) ) return; // go into chase state seeyou: if (actor->info->seesound) { int sound; switch (actor->info->seesound) { case sfx_posit1: case sfx_posit2: case sfx_posit3: sound = sfx_posit1+P_Random()%3; break; case sfx_bgsit1: case sfx_bgsit2: sound = sfx_bgsit1+P_Random()%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_Chase = = Actor has a melee attack, so it tries to close as fast as possible = ============== */ void A_Chase (mobj_t *actor) { int delta; if (actor->reactiontime) actor->reactiontime--; // // modify target threshold // if (actor->threshold) { if (!actor->target || actor->target->health <= 0) actor->threshold = 0; else actor->threshold--; } // // turn towards movement direction if not there yet // if (actor->movedir < 8) { actor->angle &= (7<<29); delta = actor->angle - (actor->movedir << 29); if (delta > 0) actor->angle -= ANG90/2; else if (delta < 0) actor->angle += ANG90/2; } if (!actor->target || !(actor->target->flags&MF_SHOOTABLE)) { // look for a new target if (P_LookForPlayers(actor,true)) return; // got a new target P_SetMobjState (actor, actor->info->spawnstate); return; } // // don't attack twice in a row // if (actor->flags & MF_JUSTATTACKED) { actor->flags &= ~MF_JUSTATTACKED; if (gameskill != sk_nightmare && !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); return; } // // check for missile attack // if (actor->info->missilestate) { if (gameskill < sk_nightmare && !fastparm && actor->movecount) goto nomissile; if (!P_CheckMissileRange (actor)) goto nomissile; P_SetMobjState (actor, actor->info->missilestate); actor->flags |= MF_JUSTATTACKED; return; } nomissile: // // possibly choose another target // if (netgame && !actor->threshold && !P_CheckSight (actor, actor->target) ) { if (P_LookForPlayers(actor,true)) return; // got a new target } // // chase towards player // if (--actor->movecount<0 || !P_Move (actor)) { P_NewChaseDir (actor); } // // make active sound // if (actor->info->activesound && P_Random () < 3) 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) actor->angle += (P_Random()-P_Random())<<21; } /* ============== = = A_PosAttack = ============== */ void A_PosAttack (mobj_t *actor) { int angle, damage, slope; if (!actor->target) return; A_FaceTarget (actor); angle = actor->angle; slope = P_AimLineAttack (actor, angle, MISSILERANGE); S_StartSound (actor, sfx_pistol); angle += (P_Random()-P_Random())<<20; damage = ((P_Random()%5)+1)*3; P_LineAttack (actor, angle, MISSILERANGE, slope, damage); } void A_SPosAttack (mobj_t *actor) { int i; int angle, bangle, damage, slope; if (!actor->target) return; S_StartSound (actor, sfx_shotgn); A_FaceTarget (actor); bangle = actor->angle; slope = P_AimLineAttack (actor, bangle, MISSILERANGE); for (i=0 ; i<3 ; i++) { angle = bangle + ((P_Random()-P_Random())<<20); damage = ((P_Random()%5)+1)*3; P_LineAttack (actor, angle, MISSILERANGE, slope, damage); } } void A_CPosAttack (mobj_t *actor) { int angle, bangle, damage, slope; if (!actor->target) return; S_StartSound (actor, sfx_shotgn); A_FaceTarget (actor); bangle = actor->angle; slope = P_AimLineAttack (actor, bangle, MISSILERANGE); angle = bangle + ((P_Random()-P_Random())<<20); damage = ((P_Random()%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); if (P_Random () < 40) return; if (!actor->target || actor->target->health <= 0 || !P_CheckSight (actor, actor->target) ) P_SetMobjState (actor, actor->info->seestate); } void A_SpidRefire (mobj_t *actor) { // keep firing unless target got out of sight A_FaceTarget (actor); if (P_Random () < 10) return; if (!actor->target || actor->target->health <= 0 || !P_CheckSight (actor, actor->target) ) P_SetMobjState (actor, actor->info->seestate); } void A_BspiAttack (mobj_t *actor) { if (!actor->target) return; A_FaceTarget (actor); // // launch a missile // P_SpawnMissile (actor, actor->target, MT_ARACHPLAZ); } /* ============== = = A_TroopAttack = ============== */ void A_TroopAttack (mobj_t *actor) { int damage; if (!actor->target) return; A_FaceTarget (actor); if (P_CheckMeleeRange (actor)) { S_StartSound (actor, sfx_claw); damage = (P_Random()%8+1)*3; P_DamageMobj (actor->target, actor, actor, damage); return; } // // launch a missile // P_SpawnMissile (actor, actor->target, MT_TROOPSHOT); } void A_SargAttack (mobj_t *actor) { int damage; if (!actor->target) return; A_FaceTarget (actor); if (P_CheckMeleeRange (actor)) { damage = ((P_Random()%10)+1)*4; P_DamageMobj (actor->target, actor, actor, damage); } } void A_HeadAttack (mobj_t *actor) { int damage; if (!actor->target) return; A_FaceTarget (actor); if (P_CheckMeleeRange (actor)) { damage = (P_Random()%6+1)*10; P_DamageMobj (actor->target, actor, actor, damage); return; } // // launch a missile // P_SpawnMissile (actor, actor->target, MT_HEADSHOT); } 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) { int damage; if (!actor->target) return; if (P_CheckMeleeRange (actor)) { S_StartSound (actor, sfx_claw); damage = (P_Random()%8+1)*10; P_DamageMobj (actor->target, actor, actor, damage); return; } // // launch a missile // P_SpawnMissile (actor, actor->target, MT_BRUISERSHOT); } //=========================================================================== /* ================ = = 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; mo->tracer = actor->target; } int TRACEANGLE = 0xc000000; void A_Tracer (mobj_t *actor) { angle_t exact; fixed_t dist, slope; mobj_t *dest; mobj_t *th; if (gametic & 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()&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) { int damage; if (!actor->target) return; A_FaceTarget (actor); if (P_CheckMeleeRange (actor)) { damage = ((P_Random()%10)+1)*6; S_StartSound (actor, sfx_skepch); P_DamageMobj (actor->target, actor, actor, damage); } } //=========================================================================== /* ================== = = PIT_VileCheck = ================== */ mobj_t *corpsehit; mobj_t *vileobj; fixed_t viletryx, 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 ( abs(thing->x - viletryx) > maxdist || abs(thing->y - viletryy) > maxdist ) return true; // not actually touching corpsehit = thing; corpsehit->momx = corpsehit->momy = 0; corpsehit->height <<= 2; check = P_CheckPosition (corpsehit, corpsehit->x, corpsehit->y); corpsehit->height >>= 2; 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, yl, yh; int bx,by; mobjinfo_t *info; mobj_t *temp; 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++) if (!P_BlockThingsIterator(bx,by,PIT_VileCheck)) { // got one! 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); corpsehit->height <<= 2; corpsehit->flags = info->flags; corpsehit->health = info->spawnhealth; corpsehit->target = NULL; return; } } A_Chase (actor); } /* ================ = = 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) { mobj_t *dest; unsigned an; 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); fog = P_SpawnMobj (actor->target->x, actor->target->x, actor->target->z, MT_FIRE); actor->tracer = fog; fog->target = actor; 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 ); } //=========================================================================== #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); 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); 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; int count; thinker_t *currentthinker; // // if there are allready 20 skulls on the level, don't spit another one // count = 0; currentthinker = thinkercap.next; while (currentthinker != &thinkercap) { if (currentthinker->function == P_MobjThinker && ((mobj_t *)currentthinker)->type == MT_SKULL) count++; currentthinker = currentthinker->next; } if (count > 20) return; 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; newmobj = P_SpawnMobj (x , y, z, MT_SKULL); if (!P_TryMove (newmobj, newmobj->x, newmobj->y)) { P_DamageMobj (newmobj,actor,actor,10000); // kill it immediately return; } 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 ()%3; break; case sfx_bgdth1: case sfx_bgdth2: sound = sfx_bgdth1 + P_Random ()%2; break; default: sound = actor->info->deathsound; break; } 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; mobj_t *mo2; line_t junk; int i; if (commercial) { if (gamemap != 7) return; if ((mo->type != MT_FATSO) && (mo->type != MT_BABY)) return; } else { if (gamemap != 8) return; // bruisers apear on other levels if (mo->type == MT_BRUISER && gameepisode != 1) return; // bruisers aren't bosses on other episodes } // // make sure there is a player alive for victory // for (i=0 ; i 0) break; if (i==MAXPLAYERS) return; // no one left alive, so don't 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) continue; mo2 = (mobj_t *)th; if (mo2 != mo && mo2->type == mo->type && mo2->health > 0) return; // other boss not dead } // // victory! // if (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 { if (gameepisode == 1) { junk.tag = 666; EV_DoFloor (&junk, lowerFloorToLowest); return; } } 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_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); } //============================================================================= mobj_t *braintargets[32]; int numbraintargets; int braintargeton; void A_BrainAwake (mobj_t *mo) { thinker_t *thinker; mobj_t *m; // find all the target spots numbraintargets = 0; braintargeton = 0; thinker = thinkercap.next; for (thinker = thinkercap.next ; thinker != &thinkercap ; thinker = thinker->next) { if (thinker->function != P_MobjThinker) continue; // not a mobj m = (mobj_t *)thinker; if (m->type == MT_BOSSTARGET ) { braintargets[numbraintargets] = m; numbraintargets++; } } S_StartSound (NULL,sfx_bossit); } void A_BrainPain (mobj_t *mo) { S_StartSound (NULL,sfx_bospn); } void A_BrainScream (mobj_t *mo) { int x,y,z; mobj_t *th; for (x=mo->x - 196*FRACUNIT ; x< mo->x + 320*FRACUNIT ; x+= FRACUNIT*8) { y = mo->y - 320*FRACUNIT; z = 128 + P_Random()*2*FRACUNIT; th = P_SpawnMobj (x,y,z, MT_ROCKET); th->momz = P_Random()*512; P_SetMobjState (th, S_BRAINEXPLODE1); th->tics -= P_Random()&7; if (th->tics < 1) th->tics = 1; } S_StartSound (NULL,sfx_bosdth); } void A_BrainExplode (mobj_t *mo) { int x,y,z; mobj_t *th; x = mo->x + (P_Random () - P_Random ())*2048; y = mo->y; z = 128 + P_Random()*2*FRACUNIT; th = P_SpawnMobj (x,y,z, MT_ROCKET); th->momz = P_Random()*512; P_SetMobjState (th, S_BRAINEXPLODE1); th->tics -= P_Random()&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; mobj_t *newmobj; static int easy = 0; easy ^= 1; if (gameskill <= sk_easy && (!easy)) return; // shoot a cube at current target targ = braintargets[braintargeton]; braintargeton = (braintargeton+1)%numbraintargets; // spawn brain missile newmobj = P_SpawnMissile (mo, targ, MT_SPAWNSHOT); newmobj->target = targ; newmobj->reactiontime = ((targ->y - mo->y)/newmobj->momy) / newmobj->state->tics; S_StartSound(NULL, sfx_bospit); } void A_SpawnFly (mobj_t *mo); void A_SpawnSound (mobj_t *mo) // travelling cube sound { 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; // teleport fog fog = P_SpawnMobj (targ->x, targ->y, targ->z, MT_SPAWNFIRE); S_StartSound (fog, sfx_telept); // monster r = P_Random (); 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); if (P_LookForPlayers (newmobj, true) ) P_SetMobjState (newmobj, newmobj->info->seestate); // telefrag anything in this spot P_TeleportMove (newmobj, newmobj->x, newmobj->y); // remove self P_RemoveMobj (mo); } void A_PlayerScream (mobj_t *mo) { int sound = sfx_pldeth; if (commercial && // IF THE PLAYER DIES LESS THAN -50% WITHOUT GIBBING mo->health < -50) sound = sfx_pdiehi; S_StartSound (mo, sound); }