1 /** @file p_enemy.c Enemy thinking, AI.
2  *
3  * Action Pointer Functions that are associated with states/frames.
4  *
5  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
6  * @authors Copyright © 2005-2013 Daniel Swanson <danij@dengine.net>
7  * @authors Copyright © 1999 by Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman (PrBoom 2.2.6)
8  * @authors Copyright © 1999-2000 by Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze (PrBoom 2.2.6)
9  * @authors Copyright © 1999 Activision
10  * @authors Copyright © 1993-1996 by id Software, Inc.
11  *
12  * @par License
13  * GPL: http://www.gnu.org/licenses/gpl.html
14  *
15  * <small>This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by the
17  * Free Software Foundation; either version 2 of the License, or (at your
18  * option) any later version. This program is distributed in the hope that it
19  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
20  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
21  * Public License for more details. You should have received a copy of the GNU
22  * General Public License along with this program; if not, write to the Free
23  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
24  * 02110-1301 USA</small>
25  */
26 
27 #include <math.h>
28 
29 #ifdef MSVC
30 #  pragma optimize("g", off)
31 #endif
32 
33 #include "jheretic.h"
34 
35 #include "dmu_lib.h"
36 #include "p_map.h"
37 #include "p_mapspec.h"
38 #include "p_floor.h"
39 
40 #define MONS_LOOK_RANGE     (20*64)
41 #define MONS_LOOK_LIMIT     64
42 
43 #define MNTR_CHARGE_SPEED   (13)
44 
45 #define MAX_GEN_PODS        16
46 
47 #define BODYQUESIZE         32
48 
49 // Eight directional movement speeds.
50 #define MOVESPEED_DIAGONAL      (0.71716309f)
51 
52 static const coord_t dirSpeed[8][2] =
53 {
54     {1, 0},
55     {MOVESPEED_DIAGONAL, MOVESPEED_DIAGONAL},
56     {0, 1},
57     {-MOVESPEED_DIAGONAL, MOVESPEED_DIAGONAL},
58     {-1, 0},
59     {-MOVESPEED_DIAGONAL, -MOVESPEED_DIAGONAL},
60     {0, -1},
61     {MOVESPEED_DIAGONAL, -MOVESPEED_DIAGONAL}
62 };
63 #undef MOVESPEED_DIAGONAL
64 
65 mobj_t *bodyque[BODYQUESIZE];
66 int bodyqueslot;
67 
P_ClearBodyQueue(void)68 void P_ClearBodyQueue(void)
69 {
70     memset(bodyque, 0, sizeof(bodyque));
71     bodyqueslot = 0;
72 }
73 
74 /**
75  * If a monster yells at a player, it will alert other monsters to the
76  * player's whereabouts.
77  */
P_NoiseAlert(mobj_t * target,mobj_t * emitter)78 void P_NoiseAlert(mobj_t *target, mobj_t *emitter)
79 {
80     VALIDCOUNT++;
81     P_RecursiveSound(target, Mobj_Sector(emitter), 0);
82 }
83 
P_CheckMeleeRange(mobj_t * actor)84 dd_bool P_CheckMeleeRange(mobj_t* actor)
85 {
86     mobj_t* pl;
87     coord_t dist, range;
88 
89     if(!actor->target)
90         return false;
91 
92     pl = actor->target;
93     dist = M_ApproxDistance(pl->origin[VX] - actor->origin[VX],
94                             pl->origin[VY] - actor->origin[VY]);
95 
96     if(!cfg.common.netNoMaxZMonsterMeleeAttack)
97     {
98         // Account for Z height difference.
99         if(pl->origin[VZ] > actor->origin[VZ] + actor->height ||
100            pl->origin[VZ] + pl->height < actor->origin[VZ])
101             return false;
102     }
103 
104     range = MELEERANGE - 20 + pl->info->radius;
105     if(dist >= range)
106         return false;
107 
108     if(!P_CheckSight(actor, actor->target))
109         return false;
110 
111     return true;
112 }
113 
P_CheckMissileRange(mobj_t * actor)114 dd_bool P_CheckMissileRange(mobj_t* actor)
115 {
116     coord_t dist;
117 
118     if(!P_CheckSight(actor, actor->target))
119         return false;
120 
121     if(actor->flags & MF_JUSTHIT)
122     {   // The target just hit the enemy, so fight back!
123         actor->flags &= ~MF_JUSTHIT;
124         return true;
125     }
126 
127     if(actor->reactionTime)
128         return false; // Don't attack yet
129 
130     dist = M_ApproxDistance(actor->origin[VX] - actor->target->origin[VX],
131                             actor->origin[VY] - actor->target->origin[VY]) - 64;
132 
133     if(P_GetState(actor->type, SN_MELEE) == S_NULL)
134         dist -= 128; // No melee attack, so fire more frequently.
135 
136     // Imp's fly attack from far away
137     if(actor->type == MT_IMP)
138         dist /= 2;
139 
140     if(dist > 200)
141         dist = 200;
142 
143     if(P_Random() < dist)
144         return false;
145 
146     return true;
147 }
148 
149 /**
150  * Move in the current direction.
151  *
152  * @return                  @c false, if the move is blocked.
153  */
P_Move(mobj_t * actor,dd_bool dropoff)154 dd_bool P_Move(mobj_t *actor, dd_bool dropoff)
155 {
156     coord_t pos[2], step[2];
157     Line *ld;
158     dd_bool good;
159 
160     if(actor->moveDir == DI_NODIR)
161         return false;
162 
163     assert(VALID_MOVEDIR(actor->moveDir));
164 
165     step[VX] = actor->info->speed * dirSpeed[actor->moveDir][VX];
166     step[VY] = actor->info->speed * dirSpeed[actor->moveDir][VY];
167     pos[VX] = actor->origin[VX] + step[VX];
168     pos[VY] = actor->origin[VY] + step[VY];
169 
170     // killough $dropoff_fix.
171     if(!P_TryMoveXY(actor, pos[VX], pos[VY], dropoff, false))
172     {
173         // Float up and down to the contacted floor height.
174         if((actor->flags & MF_FLOAT) && tmFloatOk)
175         {
176             coord_t oldZ = actor->origin[VZ];
177 
178             if(actor->origin[VZ] < tmFloorZ)
179                 actor->origin[VZ] += FLOATSPEED;
180             else
181                 actor->origin[VZ] -= FLOATSPEED;
182 
183             // What if we just floated into another mobj?
184             if(P_CheckPosition(actor, actor->origin))
185             {
186                 // Looks ok: floated to an unoccupied spot.
187                 actor->flags |= MF_INFLOAT;
188             }
189             else
190             {
191                 // Let's not do this; undo the float.
192                 actor->origin[VZ] = oldZ;
193             }
194             return true;
195         }
196 
197         // Open any specials.
198         if(IterList_Empty(spechit)) return false;
199 
200         actor->moveDir = DI_NODIR;
201         good = false;
202         while((ld = IterList_Pop(spechit)) != NULL)
203         {
204             /**
205              * If the special is not a door that can be opened, return false.
206              *
207              * $unstuck: This is what caused monsters to get stuck in
208              * doortracks, because it thought that the monster freed itself
209              * by opening a door, even if it was moving towards the
210              * doortrack, and not the door itself.
211              *
212              * If a line blocking the monster is activated, return true 90%
213              * of the time. If a line blocking the monster is not activated,
214              * but some other line is, return false 90% of the time.
215              * A bit of randomness is needed to ensure it's free from
216              * lockups, but for most cases, it returns the correct result.
217              *
218              * Do NOT simply return false 1/4th of the time (causes monsters
219              * to back out when they shouldn't, and creates secondary
220              * stickiness).
221              */
222 
223             if(P_ActivateLine(ld, actor, 0, SPAC_USE))
224                 good |= ld == tmBlockingLine ? 1 : 2;
225         }
226 
227         if(!good || cfg.monstersStuckInDoors)
228             return good;
229         else
230             return (P_Random() >= 230) || (good & 1);
231     }
232     else
233     {
234         P_MobjSetSRVO(actor, step[VX], step[VY]);
235         actor->flags &= ~MF_INFLOAT;
236     }
237 
238     // $dropoff_fix: fall more slowly, under gravity, if tmFellDown==true
239     if(!(actor->flags & MF_FLOAT) && !tmFellDown)
240     {
241         if(actor->origin[VZ] > actor->floorZ)
242             P_HitFloor(actor);
243 
244         actor->origin[VZ] = actor->floorZ;
245     }
246 
247     return true;
248 }
249 
250 /**
251  * Attempts to move actor on in its current (ob->moveangle) direction.
252  * If blocked by either a wall or an actor returns FALSE
253  * If move is either clear or blocked only by a door, returns TRUE and sets...
254  * If a door is in the way, an OpenDoor call is made to start it opening.
255  */
tryMoveMobj(mobj_t * actor)256 static dd_bool tryMoveMobj(mobj_t *actor)
257 {
258     // $dropoff_fix
259     if(!P_Move(actor, false))
260     {
261         return false;
262     }
263 
264     actor->moveCount = P_Random() & 15;
265     return true;
266 }
267 
doNewChaseDir(mobj_t * actor,coord_t deltaX,coord_t deltaY)268 static void doNewChaseDir(mobj_t *actor, coord_t deltaX, coord_t deltaY)
269 {
270     dirtype_t xdir, ydir;
271     dirtype_t olddir = actor->moveDir;
272     dirtype_t turnaround = olddir;
273 
274     if(turnaround != DI_NODIR) // Find reverse direction.
275         turnaround ^= 4;
276 
277     xdir = (deltaX > 10  ? DI_EAST  : deltaX < -10 ? DI_WEST  : DI_NODIR);
278     ydir = (deltaY < -10 ? DI_SOUTH : deltaY > 10  ? DI_NORTH : DI_NODIR);
279 
280     // Try direct route.
281     if(xdir != DI_NODIR && ydir != DI_NODIR &&
282        turnaround != (actor->moveDir =
283                       deltaY < 0 ? deltaX >
284                       0 ? DI_SOUTHEAST : DI_SOUTHWEST : deltaX >
285                       0 ? DI_NORTHEAST : DI_NORTHWEST) && tryMoveMobj(actor))
286         return;
287 
288     // Try other directions.
289     if(P_Random() > 200 || fabs(deltaY) > fabs(deltaX))
290     {
291         dirtype_t temp = xdir;
292 
293         xdir = ydir;
294         ydir = temp;
295     }
296 
297     if((xdir == turnaround ? xdir = DI_NODIR : xdir) != DI_NODIR &&
298        (actor->moveDir = xdir, tryMoveMobj(actor)))
299         return; // Either moved forward or attacked.
300 
301     if((ydir == turnaround ? ydir = DI_NODIR : ydir) != DI_NODIR &&
302        (actor->moveDir = ydir, tryMoveMobj(actor)))
303         return;
304 
305     // There is no direct path to the player, so pick another direction.
306     if(olddir != DI_NODIR && (actor->moveDir = olddir, tryMoveMobj(actor)))
307         return;
308 
309     // Randomly determine direction of search.
310     if(P_Random() & 1)
311     {
312         int tdir;
313         for(tdir = DI_EAST; tdir <= DI_SOUTHEAST; tdir++)
314         {
315             if((dirtype_t)tdir != turnaround &&
316                (actor->moveDir = tdir, tryMoveMobj(actor)))
317                 return;
318         }
319     }
320     else
321     {
322         int tdir;
323         for(tdir = DI_SOUTHEAST; tdir != DI_EAST - 1; tdir--)
324         {
325             if((dirtype_t)tdir != turnaround &&
326                (actor->moveDir = tdir, tryMoveMobj(actor)))
327                 return;
328         }
329     }
330 
331     if((actor->moveDir = turnaround) != DI_NODIR && !tryMoveMobj(actor))
332         actor->moveDir = DI_NODIR;
333 }
334 
335 typedef struct {
336     mobj_t *averterMobj;  ///< Mobj attempting to avert the drop off.
337     AABoxd averterAABox;  ///< Current axis-aligned bounding box of the averter.
338     vec2d_t direction;    ///< Direction in which to move to avoid the drop off.
339 } pit_avoiddropoff_params_t;
340 
PIT_AvoidDropoff(Line * line,void * context)341 static int PIT_AvoidDropoff(Line *line, void *context)
342 {
343     pit_avoiddropoff_params_t *parm = (pit_avoiddropoff_params_t *)context;
344     Sector *backsector = P_GetPtrp(line, DMU_BACK_SECTOR);
345     AABoxd *aaBox      = P_GetPtrp(line, DMU_BOUNDING_BOX);
346 
347     if(backsector &&
348        // Line must be contacted
349        parm->averterAABox.minX < aaBox->maxX &&
350        parm->averterAABox.maxX > aaBox->minX &&
351        parm->averterAABox.minY < aaBox->maxY &&
352        parm->averterAABox.maxY > aaBox->minY &&
353        !Line_BoxOnSide(line, &parm->averterAABox))
354     {
355         Sector *frontsector = P_GetPtrp(line, DMU_FRONT_SECTOR);
356         coord_t front = P_GetDoublep(frontsector, DMU_FLOOR_HEIGHT);
357         coord_t back  = P_GetDoublep(backsector, DMU_FLOOR_HEIGHT);
358         vec2d_t lineDir;
359         angle_t angle;
360         uint an;
361 
362         P_GetDoublepv(line, DMU_DXY, lineDir);
363 
364         // The monster must contact one of the two floors, and the other must be
365         // a tall drop off (more than 24).
366         if(FEQUAL(back, parm->averterMobj->floorZ) &&
367            front < parm->averterMobj->floorZ - 24)
368         {
369             angle = M_PointToAngle(lineDir); // Front drop off.
370         }
371         else
372         {
373             if(FEQUAL(front, parm->averterMobj->floorZ) &&
374                back < parm->averterMobj->floorZ - 24)
375             {
376                 angle = M_PointXYToAngle(-lineDir[0], -lineDir[1]); // Back drop off.
377             }
378             return false;
379         }
380 
381         // Move away from drop off at a standard speed.
382         // Multiple contacted lines are cumulative (e.g., hanging over a corner).
383         an = angle >> ANGLETOFINESHIFT;
384         parm->direction[VX] -= FIX2FLT(finesine  [an]) * 32;
385         parm->direction[VY] += FIX2FLT(finecosine[an]) * 32;
386     }
387 
388     return false;
389 }
390 
391 /**
392  * Monsters try to move away from tall drop offs. (From PrBoom.)
393  *
394  * In Doom, they were never allowed to hang over drop offs, and would remain
395  * stuck if involuntarily forced over one. This logic, combined with
396  * p_map.c::P_TryMoveXY(), allows monsters to free themselves without making
397  * them tend to hang over drop offs.
398  *
399  * @param chaseDir  Direction in which the mobj is currently "chasing". If a
400  *                  drop off is found, this direction will be updated with a
401  *                  direction that will take the mobj back onto terra firma.
402  *
403  * @return  @c true iff the direction was changed to avoid a drop off.
404  */
shouldAvoidDropoff(mobj_t * mobj,pvec2d_t chaseDir)405 static dd_bool shouldAvoidDropoff(mobj_t *mobj, pvec2d_t chaseDir)
406 {
407     pit_avoiddropoff_params_t parm;
408 
409     DENG_ASSERT(mobj != 0);
410 
411     // Disabled? (inverted var name!)
412     if(cfg.avoidDropoffs) return false;
413 
414     if(mobj->floorZ - mobj->dropOffZ <= 24) return false;
415     if(mobj->origin[VZ] > mobj->floorZ) return false;
416     if(mobj->flags & (MF_DROPOFF | MF_FLOAT)) return false;
417 
418     parm.averterMobj       = mobj;
419     parm.averterAABox.minX = mobj->origin[VX] - mobj->radius;
420     parm.averterAABox.minY = mobj->origin[VY] - mobj->radius;
421     parm.averterAABox.maxX = mobj->origin[VX] + mobj->radius;
422     parm.averterAABox.maxY = mobj->origin[VY] + mobj->radius;
423     V2d_Set(parm.direction, 0, 0);
424 
425     VALIDCOUNT++;
426     Mobj_TouchedLinesIterator(mobj, PIT_AvoidDropoff, &parm);
427 
428     if(IS_ZERO(parm.direction[VX]) && IS_ZERO(parm.direction[VY]))
429         return false;
430 
431     // The mobj should attempt to move away from the drop off.
432     V2d_Copy(chaseDir, parm.direction);
433     return true;
434 }
435 
newChaseDir(mobj_t * mobj)436 static void newChaseDir(mobj_t *mobj)
437 {
438     vec2d_t chaseDir;
439     dd_bool avoiding;
440 
441     DENG_ASSERT(mobj != 0);
442 
443     // Nothing to chase?
444     if(!mobj->target) return;
445 
446     // Chase toward the target, unless there is a drop off to avoid.
447     V2d_Subtract(chaseDir, mobj->target->origin, mobj->origin);
448     avoiding = shouldAvoidDropoff(mobj, chaseDir);
449 
450     // Apply the direction change (if any).
451     doNewChaseDir(mobj, chaseDir[VX], chaseDir[VY]);
452 
453     if(avoiding)
454     {
455         // Take small steps away from the drop off.
456         mobj->moveCount = 1;
457     }
458 }
459 
460 typedef struct {
461     size_t              count;
462     size_t              maxTries;
463     mobj_t*             notThis;
464     mobj_t*             foundMobj;
465     coord_t             origin[2];
466     coord_t             maxDistance;
467     int                 minHealth;
468     int                 compFlags;
469     dd_bool             checkLOS;
470     byte                randomSkip;
471 } findmobjparams_t;
472 
findMobj(thinker_t * th,void * context)473 static int findMobj(thinker_t* th, void* context)
474 {
475     findmobjparams_t*   params = (findmobjparams_t*) context;
476     mobj_t*             mo = (mobj_t *) th;
477 
478     // Flags requirement?
479     if(params->compFlags > 0 && !(mo->flags & params->compFlags))
480         return false; // Continue iteration.
481 
482     // Minimum health requirement?
483     if(params->minHealth > 0 && mo->health < params->minHealth)
484         return false; // Continue iteration.
485 
486     // Exclude this mobj?
487     if(params->notThis && mo == params->notThis)
488         return false; // Continue iteration.
489 
490     // Out of range?
491     if(params->maxDistance > 0 &&
492        M_ApproxDistance(params->origin[VX] - mo->origin[VX],
493                         params->origin[VY] - mo->origin[VY]) >
494        params->maxDistance)
495         return false; // Continue iteration.
496 
497     // Randomly skip this?
498     if(params->randomSkip && P_Random() < params->randomSkip)
499         return false; // Continue iteration.
500 
501     if(params->maxTries > 0 && params->count++ > params->maxTries)
502         return true; // Stop iteration.
503 
504     // Out of sight?
505     if(params->checkLOS && params->notThis &&
506        !P_CheckSight(params->notThis, mo))
507         return false; // Continue iteration.
508 
509     // Found one!
510     params->foundMobj = mo;
511     return true; // Stop iteration.
512 }
513 
P_LookForMonsters(mobj_t * mo)514 dd_bool P_LookForMonsters(mobj_t* mo)
515 {
516     findmobjparams_t    params;
517 
518     if(!P_CheckSight(players[0].plr->mo, mo))
519         return false; // Player can't see the monster.
520 
521     params.count = 0;
522     params.notThis = mo;
523     params.origin[VX] = mo->origin[VX];
524     params.origin[VY] = mo->origin[VY];
525     params.foundMobj = NULL;
526     params.maxDistance = MONS_LOOK_RANGE;
527     params.maxTries = MONS_LOOK_LIMIT;
528     params.minHealth = 1;
529     params.compFlags = MF_COUNTKILL;
530     params.checkLOS = true;
531     params.randomSkip = 16;
532     Thinker_Iterate(P_MobjThinker, findMobj, &params);
533 
534     if(params.foundMobj)
535     {
536         mo->target = params.foundMobj;
537         return true;
538     }
539 
540     return false;
541 }
542 
543 /**
544  * If allaround is false, only look 180 degrees in front
545  * returns true if a player is targeted
546  */
P_LookForPlayers(mobj_t * actor,dd_bool allAround)547 dd_bool P_LookForPlayers(mobj_t* actor, dd_bool allAround)
548 {
549     // If in single player and player is dead, look for monsters.
550     if(!IS_NETGAME && players[0].health <= 0)
551         return P_LookForMonsters(actor);
552 
553     return Mobj_LookForPlayers(actor, allAround);
554 }
555 
556 /**
557  * Stay in state until a player is sighted.
558  */
A_Look(mobj_t * actor)559 void C_DECL A_Look(mobj_t *actor)
560 {
561     mobj_t *targ;
562     Sector *sec;
563 
564     // Any shot will wake up
565     actor->threshold = 0;
566     sec = Mobj_Sector(actor);
567     targ = P_ToXSector(sec)->soundTarget;
568     if(targ && (targ->flags & MF_SHOOTABLE))
569     {
570         actor->target = targ;
571         if(actor->flags & MF_AMBUSH)
572         {
573             if(P_CheckSight(actor, actor->target))
574                 goto seeyou;
575         }
576         else
577             goto seeyou;
578     }
579 
580     if(!P_LookForPlayers(actor, false))
581         return;
582 
583     // go into chase state
584   seeyou:
585     if(actor->info->seeSound)
586     {
587         int sound = actor->info->seeSound;
588 
589         if(actor->flags2 & MF2_BOSS)
590         {
591             // Full volume
592             S_StartSound(sound, NULL);
593         }
594         else
595         {
596             S_StartSound(sound, actor);
597         }
598     }
599 
600     P_MobjChangeState(actor, P_GetState(actor->type, SN_SEE));
601 }
602 
603 /**
604  * Actor has a melee attack, so it tries to close as fast as possible.
605  */
A_Chase(mobj_t * actor)606 void C_DECL A_Chase(mobj_t *actor)
607 {
608     int delta;
609     statenum_t state;
610 
611     if(actor->reactionTime)
612     {
613         actor->reactionTime--;
614     }
615 
616     // Modify target threshold.
617     if(actor->threshold)
618     {
619         actor->threshold--;
620     }
621 
622     if(gfw_Rule(skill) == SM_NIGHTMARE ||
623        gfw_Rule(fast))
624     {
625         // Monsters move faster in nightmare mode.
626         actor->tics -= actor->tics / 2;
627         if(actor->tics < 3)
628             actor->tics = 3;
629     }
630 
631     // Turn towards movement direction if not there yet.
632     if(actor->moveDir < DI_NODIR)
633     {
634         actor->angle &= (7 << 29);
635         delta = actor->angle - (actor->moveDir << 29);
636 
637         if(delta > 0)
638         {
639             actor->angle -= ANG90 / 2;
640         }
641         else if(delta < 0)
642         {
643             actor->angle += ANG90 / 2;
644         }
645     }
646 
647     if(!actor->target || !(actor->target->flags & MF_SHOOTABLE) ||
648        P_MobjIsCamera(actor->target))
649     {
650         // Look for a new target.
651         if(!P_LookForPlayers(actor, true))
652         {
653             P_MobjChangeState(actor, P_GetState(actor->type, SN_SPAWN));
654         }
655         return;
656     }
657 
658     // Don't attack twice in a row.
659     if(actor->flags & MF_JUSTATTACKED)
660     {
661         actor->flags &= ~MF_JUSTATTACKED;
662         if(gfw_Rule(skill) != SM_NIGHTMARE)
663         {
664             newChaseDir(actor);
665         }
666         return;
667     }
668 
669     // Check for melee attack.
670     if((state = P_GetState(actor->type, SN_MELEE)) != S_NULL &&
671        P_CheckMeleeRange(actor))
672     {
673         if(actor->info->attackSound)
674             S_StartSound(actor->info->attackSound, actor);
675 
676         P_MobjChangeState(actor, state);
677         return;
678     }
679 
680     // Check for missile attack.
681     if((state = P_GetState(actor->type, SN_MISSILE)) != S_NULL)
682     {
683         if(!(gfw_Rule(skill) != SM_NIGHTMARE && actor->moveCount))
684         {
685             if(P_CheckMissileRange(actor))
686             {
687                 P_MobjChangeState(actor, state);
688                 actor->flags |= MF_JUSTATTACKED;
689                 return;
690             }
691         }
692     }
693 
694     // Possibly choose another target.
695     if(IS_NETGAME && !actor->threshold &&
696        !P_CheckSight(actor, actor->target))
697     {
698         if(P_LookForPlayers(actor, true))
699             return; // Got a new target.
700     }
701 
702     // Chase towards player.
703     if(--actor->moveCount < 0 || !P_Move(actor, false))
704     {
705         newChaseDir(actor);
706     }
707 
708     // Make active sound.
709     if(actor->info->activeSound && P_Random() < 3)
710     {
711         if(actor->type == MT_WIZARD && P_Random() < 128)
712         {
713             S_StartSound(actor->info->seeSound, actor);
714         }
715         else if(actor->type == MT_SORCERER2)
716         {
717             S_StartSound(actor->info->activeSound, NULL);
718         }
719         else
720         {
721             S_StartSound(actor->info->activeSound, actor);
722         }
723     }
724 }
725 
A_FaceTarget(mobj_t * actor)726 void C_DECL A_FaceTarget(mobj_t* actor)
727 {
728     if(!actor->target) return;
729 
730     actor->turnTime = true; // $visangle-facetarget
731     actor->flags &= ~MF_AMBUSH;
732 
733     actor->angle = M_PointToAngle2(actor->origin, actor->target->origin);
734 
735     // Is target a ghost?
736     if(actor->target->flags & MF_SHADOW)
737     {
738         actor->angle += (P_Random() - P_Random()) << 21;
739     }
740 }
741 
A_Pain(mobj_t * actor)742 void C_DECL A_Pain(mobj_t* actor)
743 {
744     if(actor->info->painSound)
745         S_StartSound(actor->info->painSound, actor);
746 }
747 
A_DripBlood(mobj_t * actor)748 void C_DECL A_DripBlood(mobj_t* actor)
749 {
750     mobj_t* mo;
751 
752     if((mo = P_SpawnMobjXYZ(MT_BLOOD, actor->origin[VX] + FIX2FLT((P_Random() - P_Random()) << 11),
753                                       actor->origin[VY] + FIX2FLT((P_Random() - P_Random()) << 11),
754                                       actor->origin[VZ], P_Random() << 24, 0)))
755     {
756         mo->mom[MX] = FIX2FLT((P_Random() - P_Random()) << 10);
757         mo->mom[MY] = FIX2FLT((P_Random() - P_Random()) << 10);
758 
759         mo->flags2 |= MF2_LOGRAV;
760     }
761 }
762 
A_KnightAttack(mobj_t * actor)763 void C_DECL A_KnightAttack(mobj_t* actor)
764 {
765     if(!actor->target)
766         return;
767 
768     if(P_CheckMeleeRange(actor))
769     {
770         P_DamageMobj(actor->target, actor, actor, HITDICE(3), false);
771         S_StartSound(SFX_KGTAT2, actor);
772         return;
773     }
774 
775     // Throw axe.
776     S_StartSound(actor->info->attackSound, actor);
777     if(actor->type == MT_KNIGHTGHOST || P_Random() < 40)
778     {
779         // Red axe.
780         P_SpawnMissile(MT_REDAXE, actor, actor->target, true);
781         return;
782     }
783 
784     // Green axe.
785     P_SpawnMissile(MT_KNIGHTAXE, actor, actor->target, true);
786 }
787 
A_ImpExplode(mobj_t * actor)788 void C_DECL A_ImpExplode(mobj_t* actor)
789 {
790     mobj_t* mo;
791 
792     if((mo = P_SpawnMobj(MT_IMPCHUNK1, actor->origin, P_Random() << 24, 0)))
793     {
794         mo->mom[MX] = FIX2FLT((P_Random() - P_Random()) << 10);
795         mo->mom[MY] = FIX2FLT((P_Random() - P_Random()) << 10);
796         mo->mom[MZ] = 9;
797     }
798 
799     if((mo = P_SpawnMobj(MT_IMPCHUNK2, actor->origin, P_Random() << 24, 0)))
800     {
801         mo->mom[MX] = FIX2FLT((P_Random() - P_Random()) << 10);
802         mo->mom[MY] = FIX2FLT((P_Random() - P_Random()) << 10);
803         mo->mom[MZ] = 9;
804     }
805 
806     if(actor->special1 == 666)
807         P_MobjChangeState(actor, S_IMP_XCRASH1); // Extreme death crash.
808 }
809 
A_BeastPuff(mobj_t * actor)810 void C_DECL A_BeastPuff(mobj_t* actor)
811 {
812     if(P_Random() > 64)
813     {
814         P_SpawnMobjXYZ(MT_PUFFY,
815                       actor->origin[VX] + FIX2FLT((P_Random() - P_Random()) << 10),
816                       actor->origin[VY] + FIX2FLT((P_Random() - P_Random()) << 10),
817                       actor->origin[VZ] + FIX2FLT((P_Random() - P_Random()) << 10),
818                       P_Random() << 24, 0);
819     }
820 }
821 
A_ImpMeAttack(mobj_t * actor)822 void C_DECL A_ImpMeAttack(mobj_t* actor)
823 {
824     if(!actor->target)
825         return;
826 
827     S_StartSound(actor->info->attackSound, actor);
828 
829     if(P_CheckMeleeRange(actor))
830     {
831         P_DamageMobj(actor->target, actor, actor, 5 + (P_Random() & 7), false);
832     }
833 }
834 
A_ImpMsAttack(mobj_t * actor)835 void C_DECL A_ImpMsAttack(mobj_t* actor)
836 {
837     mobj_t* dest;
838     uint an;
839     int dist;
840 
841     if(!actor->target || P_Random() > 64)
842     {
843         P_MobjChangeState(actor, P_GetState(actor->type, SN_SEE));
844         return;
845     }
846 
847     dest = actor->target;
848 
849     actor->flags |= MF_SKULLFLY;
850 
851     S_StartSound(actor->info->attackSound, actor);
852 
853     A_FaceTarget(actor);
854     an = actor->angle >> ANGLETOFINESHIFT;
855     actor->mom[MX] = 12 * FIX2FLT(finecosine[an]);
856     actor->mom[MY] = 12 * FIX2FLT(finesine[an]);
857 
858     dist = M_ApproxDistance(dest->origin[VX] - actor->origin[VX],
859                             dest->origin[VY] - actor->origin[VY]);
860     dist /= 12;
861     if(dist < 1)
862         dist = 1;
863 
864     actor->mom[MZ] = (dest->origin[VZ] + (dest->height /2) - actor->origin[VZ]) / dist;
865 }
866 
867 /**
868  * Fireball attack of the imp leader.
869  */
A_ImpMsAttack2(mobj_t * actor)870 void C_DECL A_ImpMsAttack2(mobj_t* actor)
871 {
872     if(!actor->target)
873         return;
874 
875     S_StartSound(actor->info->attackSound, actor);
876 
877     if(P_CheckMeleeRange(actor))
878     {
879         P_DamageMobj(actor->target, actor, actor, 5 + (P_Random() & 7), false);
880         return;
881     }
882 
883     P_SpawnMissile(MT_IMPBALL, actor, actor->target, true);
884 }
885 
A_ImpDeath(mobj_t * actor)886 void C_DECL A_ImpDeath(mobj_t* actor)
887 {
888     actor->flags &= ~MF_SOLID;
889     actor->flags2 |= MF2_FLOORCLIP;
890 
891     if(actor->origin[VZ] <= actor->floorZ)
892         P_MobjChangeState(actor, S_IMP_CRASH1);
893 }
894 
A_ImpXDeath1(mobj_t * actor)895 void C_DECL A_ImpXDeath1(mobj_t* actor)
896 {
897     actor->flags &= ~MF_SOLID;
898     actor->flags |= MF_NOGRAVITY;
899     actor->flags2 |= MF2_FLOORCLIP;
900 
901     actor->special1 = 666; // Flag the crash routine.
902 }
903 
A_ImpXDeath2(mobj_t * actor)904 void C_DECL A_ImpXDeath2(mobj_t* actor)
905 {
906     actor->flags &= ~MF_NOGRAVITY;
907 
908     if(actor->origin[VZ] <= actor->floorZ)
909         P_MobjChangeState(actor, S_IMP_CRASH1);
910 }
911 
912 /**
913  * @return          @c true, if the chicken morphs.
914  */
P_UpdateChicken(mobj_t * actor,int tics)915 dd_bool P_UpdateChicken(mobj_t* actor, int tics)
916 {
917     mobj_t* fog;
918     coord_t pos[3];
919     mobjtype_t moType;
920     mobj_t* mo;
921     mobj_t oldChicken;
922 
923     actor->special1 -= tics;
924 
925     if(actor->special1 > 0)
926         return false;
927 
928     moType = actor->special2;
929 
930     memcpy(pos, actor->origin, sizeof(pos));
931 
932     //// @todo Do this properly!
933     memcpy(&oldChicken, actor, sizeof(oldChicken));
934 
935     if(!(mo = P_SpawnMobj(moType, pos, oldChicken.angle, 0)))
936         return false;
937 
938     P_MobjChangeState(actor, S_FREETARGMOBJ);
939 
940     if(P_TestMobjLocation(mo) == false)
941     {
942         // Didn't fit.
943         P_MobjRemove(mo, true);
944 
945         if((mo = P_SpawnMobj(MT_CHICKEN, pos, oldChicken.angle, 0)))
946         {
947             mo->flags = oldChicken.flags;
948             mo->health = oldChicken.health;
949             mo->target = oldChicken.target;
950 
951             mo->special1 = 5 * TICSPERSEC;  // Next try in 5 seconds.
952             mo->special2 = moType;
953         }
954 
955         return false;
956     }
957 
958     mo->target = oldChicken.target;
959 
960     if((fog = P_SpawnMobjXYZ(MT_TFOG, pos[VX], pos[VY],
961                             pos[VZ] + TELEFOGHEIGHT, mo->angle + ANG180, 0)))
962         S_StartSound(SFX_TELEPT, fog);
963 
964     return true;
965 }
966 
A_ChicAttack(mobj_t * actor)967 void C_DECL A_ChicAttack(mobj_t* actor)
968 {
969     if(P_UpdateChicken(actor, 18))
970         return;
971 
972     if(!actor->target)
973         return;
974 
975     if(P_CheckMeleeRange(actor))
976         P_DamageMobj(actor->target, actor, actor, 1 + (P_Random() & 1), false);
977 }
978 
A_ChicLook(mobj_t * actor)979 void C_DECL A_ChicLook(mobj_t* actor)
980 {
981     if(P_UpdateChicken(actor, 10))
982         return;
983 
984     A_Look(actor);
985 }
986 
A_ChicChase(mobj_t * actor)987 void C_DECL A_ChicChase(mobj_t* actor)
988 {
989     if(P_UpdateChicken(actor, 3))
990         return;
991 
992     A_Chase(actor);
993 }
994 
A_ChicPain(mobj_t * actor)995 void C_DECL A_ChicPain(mobj_t* actor)
996 {
997     if(P_UpdateChicken(actor, 10))
998         return;
999 
1000     S_StartSound(actor->info->painSound, actor);
1001 }
1002 
A_Feathers(mobj_t * actor)1003 void C_DECL A_Feathers(mobj_t* actor)
1004 {
1005     int         i, count;
1006     mobj_t     *mo;
1007 
1008     // In Pain?
1009     if(actor->health > 0)
1010         count = P_Random() < 32 ? 2 : 1;
1011     else // Death.
1012         count = 5 + (P_Random() & 3);
1013 
1014     for(i = 0; i < count; ++i)
1015     {
1016         if((mo = P_SpawnMobjXYZ(MT_FEATHER,
1017                                actor->origin[VX], actor->origin[VY],
1018                                actor->origin[VZ] + 20, P_Random() << 24, 0)))
1019         {
1020             mo->target = actor;
1021 
1022             mo->mom[MX] = FIX2FLT((P_Random() - P_Random()) << 8);
1023             mo->mom[MY] = FIX2FLT((P_Random() - P_Random()) << 8);
1024             mo->mom[MZ] = 1 + FIX2FLT(P_Random() << 9);
1025 
1026             P_MobjChangeState(mo, S_FEATHER1 + (P_Random() & 7));
1027         }
1028     }
1029 }
1030 
A_MummyAttack(mobj_t * actor)1031 void C_DECL A_MummyAttack(mobj_t* actor)
1032 {
1033     if(!actor->target)
1034         return;
1035 
1036     S_StartSound(actor->info->attackSound, actor);
1037 
1038     if(P_CheckMeleeRange(actor))
1039     {
1040         P_DamageMobj(actor->target, actor, actor, HITDICE(2), false);
1041         S_StartSound(SFX_MUMAT2, actor);
1042         return;
1043     }
1044 
1045     S_StartSound(SFX_MUMAT1, actor);
1046 }
1047 
1048 /**
1049  * Mummy leader missile attack.
1050  */
A_MummyAttack2(mobj_t * actor)1051 void C_DECL A_MummyAttack2(mobj_t* actor)
1052 {
1053     mobj_t *mo;
1054 
1055     if(!actor->target)
1056         return;
1057 
1058     if(P_CheckMeleeRange(actor))
1059     {
1060         P_DamageMobj(actor->target, actor, actor, HITDICE(2), false);
1061         return;
1062     }
1063 
1064     mo = P_SpawnMissile(MT_MUMMYFX1, actor, actor->target, true);
1065 
1066     if(mo != NULL)
1067         mo->tracer = actor->target;
1068 }
1069 
A_MummyFX1Seek(mobj_t * actor)1070 void C_DECL A_MummyFX1Seek(mobj_t* actor)
1071 {
1072     P_SeekerMissile(actor, ANGLE_1 * 10, ANGLE_1 * 20);
1073 }
1074 
A_MummySoul(mobj_t * mummy)1075 void C_DECL A_MummySoul(mobj_t* mummy)
1076 {
1077     mobj_t* mo;
1078 
1079     if((mo = P_SpawnMobjXYZ(MT_MUMMYSOUL, mummy->origin[VX],
1080                                           mummy->origin[VY],
1081                                           mummy->origin[VZ] + 10,
1082                                           mummy->angle, 0)))
1083     {
1084         mo->mom[MZ] = 1;
1085     }
1086 }
1087 
A_Sor1Pain(mobj_t * actor)1088 void C_DECL A_Sor1Pain(mobj_t* actor)
1089 {
1090     actor->special1 = 20; // Number of steps to walk fast.
1091     A_Pain(actor);
1092 }
1093 
A_Sor1Chase(mobj_t * actor)1094 void C_DECL A_Sor1Chase(mobj_t* actor)
1095 {
1096     if(actor->special1)
1097     {
1098         actor->special1--;
1099         actor->tics -= 3;
1100     }
1101 
1102     A_Chase(actor);
1103 }
1104 
1105 /**
1106  * Sorcerer demon attack.
1107  */
A_Srcr1Attack(mobj_t * actor)1108 void C_DECL A_Srcr1Attack(mobj_t* actor)
1109 {
1110     mobj_t     *mo;
1111     angle_t     angle;
1112 
1113     if(!actor->target)
1114         return;
1115 
1116     S_StartSound(actor->info->attackSound, actor);
1117 
1118     if(P_CheckMeleeRange(actor))
1119     {
1120         P_DamageMobj(actor->target, actor, actor, HITDICE(8), false);
1121         return;
1122     }
1123 
1124     if(actor->health > (actor->info->spawnHealth / 3) * 2)
1125     {
1126         // Spit one fireball.
1127         P_SpawnMissile(MT_SRCRFX1, actor, actor->target, true);
1128     }
1129     else
1130     {
1131         // Spit three fireballs.
1132         mo = P_SpawnMissile(MT_SRCRFX1, actor, actor->target, true);
1133         if(mo)
1134         {
1135             angle = mo->angle;
1136             P_SpawnMissileAngle(MT_SRCRFX1, actor, angle - ANGLE_1 * 3, mo->mom[MZ]);
1137             P_SpawnMissileAngle(MT_SRCRFX1, actor, angle + ANGLE_1 * 3, mo->mom[MZ]);
1138         }
1139 
1140         if(actor->health < actor->info->spawnHealth / 3)
1141         {
1142             // Maybe attack again?
1143             if(actor->special1)
1144             {
1145                 // Just attacked, so don't attack again/
1146                 actor->special1 = 0;
1147             }
1148             else
1149             {
1150                 // Set state to attack again/
1151                 actor->special1 = 1;
1152                 P_MobjChangeState(actor, S_SRCR1_ATK4);
1153             }
1154         }
1155     }
1156 }
1157 
A_SorcererRise(mobj_t * actor)1158 void C_DECL A_SorcererRise(mobj_t* actor)
1159 {
1160     mobj_t*             mo;
1161 
1162     actor->flags &= ~MF_SOLID;
1163     if((mo = P_SpawnMobj(MT_SORCERER2, actor->origin, actor->angle, 0)))
1164     {
1165         P_MobjChangeState(mo, S_SOR2_RISE1);
1166         mo->target = actor->target;
1167     }
1168 }
1169 
P_DSparilTeleport(mobj_t * actor)1170 void P_DSparilTeleport(mobj_t* actor)
1171 {
1172     // No spots?
1173     if(bossSpotCount > 0)
1174     {
1175         int i, tries;
1176         const mapspot_t* dest;
1177 
1178         i = P_Random();
1179         tries = bossSpotCount;
1180 
1181         do
1182         {
1183             dest = &mapSpots[bossSpots[++i % bossSpotCount]];
1184             if(M_ApproxDistance(actor->origin[VX] - dest->origin[VX],
1185                                 actor->origin[VY] - dest->origin[VY]) >= 128)
1186             {
1187                 // A suitable teleport destination is available.
1188                 coord_t prevpos[3];
1189                 angle_t oldAngle;
1190 
1191                 memcpy(prevpos, actor->origin, sizeof(prevpos));
1192                 oldAngle = actor->angle;
1193 
1194                 if(P_TeleportMove(actor, dest->origin[VX], dest->origin[VY], false))
1195                 {
1196                     mobj_t* mo;
1197 
1198                     if((mo = P_SpawnMobj(MT_SOR2TELEFADE, prevpos, oldAngle + ANG180, 0)))
1199                         S_StartSound(SFX_TELEPT, mo);
1200 
1201                     P_MobjChangeState(actor, S_SOR2_TELE1);
1202                     actor->origin[VZ] = actor->floorZ;
1203                     actor->angle = dest->angle;
1204                     actor->mom[MX] = actor->mom[MY] = actor->mom[MZ] = 0;
1205                     S_StartSound(SFX_TELEPT, actor);
1206                 }
1207 
1208                 return;
1209             }
1210         } while(tries-- > 0); // Don't stay here forever.
1211     }
1212 }
1213 
A_Srcr2Decide(mobj_t * actor)1214 void C_DECL A_Srcr2Decide(mobj_t* actor)
1215 {
1216     static int chance[] = {
1217         192, 120, 120, 120, 64, 64, 32, 16, 0
1218     };
1219 
1220     // No spots?
1221     if(!bossSpotCount) return;
1222 
1223     if(P_Random() < chance[actor->health / (actor->info->spawnHealth / 8)])
1224     {
1225         P_DSparilTeleport(actor);
1226     }
1227 }
1228 
A_Srcr2Attack(mobj_t * actor)1229 void C_DECL A_Srcr2Attack(mobj_t* actor)
1230 {
1231     int chance;
1232 
1233     if(!actor->target) return;
1234 
1235     S_StartSound(actor->info->attackSound, NULL);
1236 
1237     if(P_CheckMeleeRange(actor))
1238     {
1239         P_DamageMobj(actor->target, actor, actor, HITDICE(20), false);
1240         return;
1241     }
1242 
1243     chance = actor->health < actor->info->spawnHealth / 2 ? 96 : 48;
1244     if(P_Random() < chance)
1245     {
1246         // Wizard spawners.
1247         P_SpawnMissileAngle(MT_SOR2FX2, actor, actor->angle - ANG45, 1.0f/2);
1248         P_SpawnMissileAngle(MT_SOR2FX2, actor, actor->angle + ANG45, 1.0f/2);
1249     }
1250     else
1251     {
1252         // Blue bolt.
1253         P_SpawnMissile(MT_SOR2FX1, actor, actor->target, true);
1254     }
1255 }
1256 
A_BlueSpark(mobj_t * actor)1257 void C_DECL A_BlueSpark(mobj_t* actor)
1258 {
1259     int i;
1260 
1261     for(i = 0; i < 2; ++i)
1262     {
1263         mobj_t* mo;
1264 
1265         if((mo = P_SpawnMobj(MT_SOR2FXSPARK, actor->origin, P_Random() << 24, 0)))
1266         {
1267             mo->mom[MX] = FIX2FLT((P_Random() - P_Random()) << 9);
1268             mo->mom[MY] = FIX2FLT((P_Random() - P_Random()) << 9);
1269             mo->mom[MZ] = 1 + FIX2FLT(P_Random() << 8);
1270         }
1271     }
1272 }
1273 
A_GenWizard(mobj_t * actor)1274 void C_DECL A_GenWizard(mobj_t* actor)
1275 {
1276     mobj_t* mo, *fog;
1277 
1278     if(!(mo = P_SpawnMobjXYZ(MT_WIZARD, actor->origin[VX], actor->origin[VY],
1279                                         actor->origin[VZ] - (MOBJINFO[MT_WIZARD].height / 2),
1280                                         actor->angle, 0)))
1281         return;
1282 
1283     if(P_TestMobjLocation(mo) == false)
1284     {   // Didn't fit.
1285         P_MobjRemove(mo, true);
1286         return;
1287     }
1288 
1289     actor->mom[MX] = actor->mom[MY] = actor->mom[MZ] = 0;
1290 
1291     P_MobjChangeState(actor, P_GetState(actor->type, SN_DEATH));
1292 
1293     actor->flags &= ~MF_MISSILE;
1294 
1295     if((fog = P_SpawnMobj(MT_TFOG, actor->origin, actor->angle + ANG180, 0)))
1296         S_StartSound(SFX_TELEPT, fog);
1297 }
1298 
A_Sor2DthInit(mobj_t * actor)1299 void C_DECL A_Sor2DthInit(mobj_t* actor)
1300 {
1301     // Set the animation loop counter.
1302     actor->special1 = 7;
1303 
1304     // Kill monsters early.
1305     P_Massacre();
1306 }
1307 
A_Sor2DthLoop(mobj_t * actor)1308 void C_DECL A_Sor2DthLoop(mobj_t* actor)
1309 {
1310     if(--actor->special1)
1311     {   // Need to loop.
1312         P_MobjChangeState(actor, S_SOR2_DIE4);
1313     }
1314 }
1315 
1316 /**
1317  * D'Sparil Sound Routines.
1318  */
A_SorZap(mobj_t * actor)1319 void C_DECL A_SorZap(mobj_t* actor)
1320 {
1321     S_StartSound(SFX_SORZAP, NULL);
1322 }
1323 
A_SorRise(mobj_t * actor)1324 void C_DECL A_SorRise(mobj_t* actor)
1325 {
1326     S_StartSound(SFX_SORRISE, NULL);
1327 }
1328 
A_SorDSph(mobj_t * actor)1329 void C_DECL A_SorDSph(mobj_t* actor)
1330 {
1331     S_StartSound(SFX_SORDSPH, NULL);
1332 }
1333 
A_SorDExp(mobj_t * actor)1334 void C_DECL A_SorDExp(mobj_t* actor)
1335 {
1336     S_StartSound(SFX_SORDEXP, NULL);
1337 }
1338 
A_SorDBon(mobj_t * actor)1339 void C_DECL A_SorDBon(mobj_t* actor)
1340 {
1341     S_StartSound(SFX_SORDBON, NULL);
1342 }
1343 
A_SorSightSnd(mobj_t * actor)1344 void C_DECL A_SorSightSnd(mobj_t* actor)
1345 {
1346     S_StartSound(SFX_SORSIT, NULL);
1347 }
1348 
1349 /**
1350  * Minotaur Melee attack.
1351  */
A_MinotaurAtk1(mobj_t * actor)1352 void C_DECL A_MinotaurAtk1(mobj_t* actor)
1353 {
1354     player_t*           player;
1355 
1356     if(!actor->target)
1357         return;
1358 
1359     S_StartSound(SFX_STFPOW, actor);
1360 
1361     if(P_CheckMeleeRange(actor))
1362     {
1363         P_DamageMobj(actor->target, actor, actor, HITDICE(4), false);
1364 
1365         if((player = actor->target->player) != NULL)
1366         {
1367             // Squish the player.
1368             player->viewHeightDelta = -16;
1369         }
1370     }
1371 }
1372 
1373 /**
1374  * Minotaur : Choose a missile attack.
1375  */
A_MinotaurDecide(mobj_t * actor)1376 void C_DECL A_MinotaurDecide(mobj_t* actor)
1377 {
1378     uint an;
1379     mobj_t* target;
1380     coord_t dist;
1381 
1382     target = actor->target;
1383     if(!target) return;
1384 
1385     S_StartSound(SFX_MINSIT, actor);
1386 
1387     dist = M_ApproxDistance(actor->origin[VX] - target->origin[VX],
1388                             actor->origin[VY] - target->origin[VY]);
1389 
1390     if(target->origin[VZ] + target->height > actor->origin[VZ] &&
1391        target->origin[VZ] + target->height < actor->origin[VZ] + actor->height &&
1392        dist < 8 * 64 && dist > 1 * 64 && P_Random() < 150)
1393     {
1394         // Charge attack.
1395         // Don't call the state function right away.
1396         P_MobjChangeStateNoAction(actor, S_MNTR_ATK4_1);
1397         actor->flags |= MF_SKULLFLY;
1398 
1399         A_FaceTarget(actor);
1400 
1401         an = actor->angle >> ANGLETOFINESHIFT;
1402         actor->mom[MX] = MNTR_CHARGE_SPEED * FIX2FLT(finecosine[an]);
1403         actor->mom[MY] = MNTR_CHARGE_SPEED * FIX2FLT(finesine[an]);
1404 
1405         // Charge duration.
1406         actor->special1 = 35 / 2;
1407     }
1408     else if(target->origin[VZ] == target->floorZ && dist < 9 * 64 &&
1409             P_Random() < 220)
1410     {
1411         // Floor fire attack.
1412         P_MobjChangeState(actor, S_MNTR_ATK3_1);
1413         actor->special2 = 0;
1414     }
1415     else
1416     {
1417         // Swing attack.
1418         A_FaceTarget(actor);
1419         // NOTE: Don't need to call P_MobjChangeState because the current
1420         //       state falls through to the swing attack
1421     }
1422 }
1423 
A_MinotaurCharge(mobj_t * actor)1424 void C_DECL A_MinotaurCharge(mobj_t* actor)
1425 {
1426     mobj_t*             puff;
1427 
1428     if(actor->special1)
1429     {
1430         if((puff = P_SpawnMobj(MT_PHOENIXPUFF, actor->origin,
1431                                   P_Random() << 24, 0)))
1432             puff->mom[MZ] = 2;
1433         actor->special1--;
1434     }
1435     else
1436     {
1437         actor->flags &= ~MF_SKULLFLY;
1438         P_MobjChangeState(actor, P_GetState(actor->type, SN_SEE));
1439     }
1440 }
1441 
1442 /**
1443  * Minotaur : Swing attack.
1444  */
A_MinotaurAtk2(mobj_t * actor)1445 void C_DECL A_MinotaurAtk2(mobj_t* actor)
1446 {
1447     mobj_t* mo;
1448 
1449     if(!actor->target) return;
1450 
1451     S_StartSound(SFX_MINAT2, actor);
1452 
1453     if(P_CheckMeleeRange(actor))
1454     {
1455         P_DamageMobj(actor->target, actor, actor, HITDICE(5), false);
1456         return;
1457     }
1458 
1459     mo = P_SpawnMissile(MT_MNTRFX1, actor, actor->target, true);
1460     if(mo)
1461     {
1462         angle_t angle = mo->angle;
1463         coord_t momZ = mo->mom[MZ];
1464 
1465         S_StartSound(SFX_MINAT2, mo);
1466 
1467         P_SpawnMissileAngle(MT_MNTRFX1, actor, angle - (ANG45 / 8), momZ);
1468         P_SpawnMissileAngle(MT_MNTRFX1, actor, angle + (ANG45 / 8), momZ);
1469         P_SpawnMissileAngle(MT_MNTRFX1, actor, angle - (ANG45 / 16), momZ);
1470         P_SpawnMissileAngle(MT_MNTRFX1, actor, angle + (ANG45 / 16), momZ);
1471     }
1472 }
1473 
1474 /**
1475  * Minotaur : Floor fire attack.
1476  */
A_MinotaurAtk3(mobj_t * actor)1477 void C_DECL A_MinotaurAtk3(mobj_t* actor)
1478 {
1479 
1480     player_t*           player;
1481 
1482     if(!actor->target)
1483         return;
1484 
1485     if(P_CheckMeleeRange(actor))
1486     {
1487         P_DamageMobj(actor->target, actor, actor, HITDICE(5), false);
1488 
1489         if((player = actor->target->player) != NULL)
1490         {
1491             // Squish the player.
1492             player->viewHeightDelta = -16;
1493         }
1494     }
1495     else
1496     {
1497         mobj_t* mo;
1498         dd_bool fixFloorFire = (!cfg.fixFloorFire && actor->floorClip > 0);
1499 
1500         /**
1501          * Original Heretic bug:
1502          * When an attempt is made to spawn MT_MNTRFX2 (the Maulotaur's
1503          * ground flame) the z coordinate is set to ONFLOORZ but if the
1504          * Maulotaur's feet are currently clipped (i.e., it is in a sector
1505          * whose terrain info is set to clip) then FOOTCLIPSIZE is
1506          * subtracted from the z coordinate. So when P_SpawnMobj is called,
1507          * z != ONFLOORZ, so rather than being set to the height of the
1508          * floor it is left at 2146838915 (float: 32758.162).
1509          *
1510          * This in turn means that when P_TryMoveXY is called (via
1511          * P_CheckMissileSpawn), the test which is there to check whether a
1512          * missile hits an upper side section will return true
1513          * (ceilingheight - thingz > thingheight).
1514          *
1515          * This results in P_ExplodeMissile being called instantly.
1516          *
1517          * jHeretic fixes this bug, however we maintain original behaviour
1518          * using the following method:
1519          *
1520          * 1) Do not call P_CheckMissileSpawn from P_SpawnMissile.
1521          * 2) Use special-case logic here which behaves similarly.
1522          */
1523 
1524         if((mo = P_SpawnMissile(MT_MNTRFX2, actor, actor->target,
1525                                 (fixFloorFire? false : true))))
1526         {
1527             if(fixFloorFire)
1528             {
1529                 P_MobjUnlink(mo);
1530                 mo->origin[VX] += mo->mom[MX] / 2;
1531                 mo->origin[VY] += mo->mom[MY] / 2;
1532                 mo->origin[VZ] += mo->mom[MZ] / 2;
1533                 P_MobjLink(mo);
1534 
1535                 P_ExplodeMissile(mo);
1536             }
1537             else
1538             {
1539                 S_StartSound(SFX_MINAT1, mo);
1540             }
1541         }
1542     }
1543 
1544     if(P_Random() < 192 && actor->special2 == 0)
1545     {
1546         P_MobjChangeState(actor, S_MNTR_ATK3_4);
1547         actor->special2 = 1;
1548     }
1549 }
1550 
A_MntrFloorFire(mobj_t * actor)1551 void C_DECL A_MntrFloorFire(mobj_t* actor)
1552 {
1553     mobj_t* mo;
1554     coord_t pos[3];
1555     angle_t angle;
1556 
1557     // Make sure we are on the floor.
1558     actor->origin[VZ] = actor->floorZ;
1559 
1560     pos[VX] = actor->origin[VX];
1561     pos[VY] = actor->origin[VY];
1562     pos[VZ] = 0;
1563 
1564     pos[VX] += FIX2FLT((P_Random() - P_Random()) << 10);
1565     pos[VY] += FIX2FLT((P_Random() - P_Random()) << 10);
1566 
1567     angle = M_PointToAngle2(actor->origin, pos);
1568 
1569     if((mo = P_SpawnMobj(MT_MNTRFX3, pos, angle, MSF_Z_FLOOR)))
1570     {
1571         mo->target = actor->target;
1572         mo->mom[MX] = FIX2FLT(1); // Force block checking.
1573 
1574         P_CheckMissileSpawn(mo);
1575     }
1576 }
1577 
P_Attack(mobj_t * actor,int meleeDamage,mobjtype_t missileType)1578 int P_Attack(mobj_t *actor, int meleeDamage, mobjtype_t missileType)
1579 {
1580     if (actor->target)
1581     {
1582         if (P_CheckMeleeRange(actor))
1583         {
1584             P_DamageMobj(actor->target, actor, actor, meleeDamage, false);
1585             return 1;
1586         }
1587         else
1588         {
1589             mobj_t *mis;
1590             if ((mis = P_SpawnMissile(missileType, actor, actor->target, true)) != NULL)
1591             {
1592                 if (missileType == MT_MUMMYFX1)
1593                 {
1594                     // Tracer is used to keep track of where the missile is homing.
1595                     mis->tracer = actor->target;
1596                 }
1597                 else if (missileType == MT_WHIRLWIND)
1598                 {
1599                     P_InitWhirlwind(mis, actor->target);
1600                 }
1601             }
1602             return 2;
1603         }
1604     }
1605     return 0;
1606 }
1607 
A_BeastAttack(mobj_t * actor)1608 void C_DECL A_BeastAttack(mobj_t* actor)
1609 {
1610     if(!actor->target)
1611         return;
1612 
1613     S_StartSound(actor->info->attackSound, actor);
1614 
1615     if(P_CheckMeleeRange(actor))
1616     {
1617         P_DamageMobj(actor->target, actor, actor, HITDICE(3), false);
1618         return;
1619     }
1620 
1621     P_SpawnMissile(MT_BEASTBALL, actor, actor->target, true);
1622 }
1623 
P_InitWhirlwind(mobj_t * whirlwind,mobj_t * target)1624 void P_InitWhirlwind(mobj_t *whirlwind, mobj_t *target)
1625 {
1626     whirlwind->origin[VZ] -= 32;
1627     whirlwind->special1 = 60;
1628     whirlwind->special2 = 50; // Timer for active sound.
1629     whirlwind->special3 = 20 * TICSPERSEC; // Duration.
1630     whirlwind->tracer = target;
1631 }
1632 
A_HeadAttack(mobj_t * actor)1633 void C_DECL A_HeadAttack(mobj_t* actor)
1634 {
1635     static int atkResolve1[] = { 50, 150 };
1636     static int atkResolve2[] = { 150, 200 };
1637     mobj_t* fire, *baseFire, *mo, *target;
1638     int randAttack;
1639     coord_t dist;
1640     int i;
1641 
1642     // Ice ball     (close 20% : far 60%)
1643     // Fire column  (close 40% : far 20%)
1644     // Whirlwind    (close 40% : far 20%)
1645     // Distance threshold = 8 cells
1646 
1647     target = actor->target;
1648     if(target == NULL)
1649         return;
1650 
1651     A_FaceTarget(actor);
1652 
1653     if(P_CheckMeleeRange(actor))
1654     {
1655         P_DamageMobj(target, actor, actor, HITDICE(6), false);
1656         return;
1657     }
1658 
1659     dist = M_ApproxDistance(actor->origin[VX] - target->origin[VX],
1660                             actor->origin[VY] - target->origin[VY]) > 8 * 64;
1661 
1662     randAttack = P_Random();
1663     if(randAttack < atkResolve1[(FLT2FIX(dist) != 0)? 1 : 0])
1664     {
1665         // Ice ball
1666         P_SpawnMissile(MT_HEADFX1, actor, target, true);
1667         S_StartSound(SFX_HEDAT2, actor);
1668     }
1669     else if(randAttack < atkResolve2[(FLT2FIX(dist) != 0)? 1 : 0])
1670     {
1671         // Fire column
1672         baseFire = P_SpawnMissile(MT_HEADFX3, actor, target, true);
1673         if(baseFire != NULL)
1674         {
1675             P_MobjChangeState(baseFire, S_HEADFX3_4);  // Don't grow
1676             for(i = 0; i < 5; ++i)
1677             {
1678                 if((fire = P_SpawnMobj(MT_HEADFX3, baseFire->origin,
1679                                                    baseFire->angle, 0)))
1680                 {
1681                     if(i == 0)
1682                         S_StartSound(SFX_HEDAT1, actor);
1683 
1684                     fire->target = baseFire->target;
1685                     fire->mom[MX] = baseFire->mom[MX];
1686                     fire->mom[MY] = baseFire->mom[MY];
1687                     fire->mom[MZ] = baseFire->mom[MZ];
1688                     fire->damage = 0;
1689                     fire->special3 = (i + 1) * 2;
1690 
1691                     P_CheckMissileSpawn(fire);
1692                 }
1693             }
1694         }
1695     }
1696     else
1697     {
1698         // Whirlwind.
1699         if((mo = P_SpawnMissile(MT_WHIRLWIND, actor, target, true)))
1700         {
1701             P_InitWhirlwind(mo, target);
1702             S_StartSound(SFX_HEDAT3, actor);
1703         }
1704     }
1705 }
1706 
A_WhirlwindSeek(mobj_t * actor)1707 void C_DECL A_WhirlwindSeek(mobj_t* actor)
1708 {
1709     actor->special3 -= 3;
1710     if(actor->special3 < 0)
1711     {
1712         actor->mom[MX] = actor->mom[MY] = actor->mom[MZ] = 0;
1713         P_MobjChangeState(actor, P_GetState(actor->type, SN_DEATH));
1714         actor->flags &= ~MF_MISSILE;
1715         return;
1716     }
1717 
1718     if((actor->special2 -= 3) < 0)
1719     {
1720         actor->special2 = 58 + (P_Random() & 31);
1721         S_StartSound(SFX_HEDAT3, actor);
1722     }
1723 
1724     if(actor->tracer && actor->tracer->flags & MF_SHADOW)
1725         return;
1726 
1727     P_SeekerMissile(actor, ANGLE_1 * 10, ANGLE_1 * 30);
1728 }
1729 
A_HeadIceImpact(mobj_t * ice)1730 void C_DECL A_HeadIceImpact(mobj_t* ice)
1731 {
1732     uint i;
1733 
1734     for(i = 0; i < 8; ++i)
1735     {
1736         mobj_t* shard;
1737         angle_t angle = i * ANG45;
1738 
1739         if((shard = P_SpawnMobj(MT_HEADFX2, ice->origin, angle, 0)))
1740         {
1741             unsigned int an = angle >> ANGLETOFINESHIFT;
1742 
1743             shard->target = ice->target;
1744             shard->mom[MX] = shard->info->speed * FIX2FLT(finecosine[an]);
1745             shard->mom[MY] = shard->info->speed * FIX2FLT(finesine[an]);
1746             shard->mom[MZ] = -.6f;
1747 
1748             P_CheckMissileSpawn(shard);
1749         }
1750     }
1751 }
1752 
A_HeadFireGrow(mobj_t * fire)1753 void C_DECL A_HeadFireGrow(mobj_t* fire)
1754 {
1755     fire->special3--;
1756     fire->origin[VZ] += 9;
1757 
1758     if(fire->special3 == 0)
1759     {
1760         fire->damage = fire->info->damage;
1761         P_MobjChangeState(fire, S_HEADFX3_4);
1762     }
1763 }
1764 
A_SnakeAttack(mobj_t * actor)1765 void C_DECL A_SnakeAttack(mobj_t* actor)
1766 {
1767     if(!actor->target)
1768     {
1769         P_MobjChangeState(actor, S_SNAKE_WALK1);
1770         return;
1771     }
1772 
1773     S_StartSound(actor->info->attackSound, actor);
1774     A_FaceTarget(actor);
1775     P_SpawnMissile(MT_SNAKEPRO_A, actor, actor->target, true);
1776 }
1777 
A_SnakeAttack2(mobj_t * actor)1778 void C_DECL A_SnakeAttack2(mobj_t* actor)
1779 {
1780     if(!actor->target)
1781     {
1782         P_MobjChangeState(actor, S_SNAKE_WALK1);
1783         return;
1784     }
1785 
1786     S_StartSound(actor->info->attackSound, actor);
1787     A_FaceTarget(actor);
1788     P_SpawnMissile(MT_SNAKEPRO_B, actor, actor->target, true);
1789 }
1790 
A_ClinkAttack(mobj_t * actor)1791 void C_DECL A_ClinkAttack(mobj_t* actor)
1792 {
1793     int     damage;
1794 
1795     if(!actor->target)
1796         return;
1797 
1798     S_StartSound(actor->info->attackSound, actor);
1799     if(P_CheckMeleeRange(actor))
1800     {
1801         damage = ((P_Random() % 7) + 3);
1802         P_DamageMobj(actor->target, actor, actor, damage, false);
1803     }
1804 }
1805 
A_GhostOff(mobj_t * actor)1806 void C_DECL A_GhostOff(mobj_t* actor)
1807 {
1808     actor->flags &= ~MF_SHADOW;
1809 }
1810 
A_WizAtk1(mobj_t * actor)1811 void C_DECL A_WizAtk1(mobj_t* actor)
1812 {
1813     A_FaceTarget(actor);
1814     actor->flags &= ~MF_SHADOW;
1815 }
1816 
A_WizAtk2(mobj_t * actor)1817 void C_DECL A_WizAtk2(mobj_t* actor)
1818 {
1819     A_FaceTarget(actor);
1820     actor->flags |= MF_SHADOW;
1821 }
1822 
A_WizAtk3(mobj_t * actor)1823 void C_DECL A_WizAtk3(mobj_t* actor)
1824 {
1825     mobj_t* mo;
1826     angle_t angle;
1827     coord_t momZ;
1828 
1829     actor->flags &= ~MF_SHADOW;
1830 
1831     if(!actor->target)
1832         return;
1833 
1834     S_StartSound(actor->info->attackSound, actor);
1835 
1836     if(P_CheckMeleeRange(actor))
1837     {
1838         P_DamageMobj(actor->target, actor, actor, HITDICE(4), false);
1839         return;
1840     }
1841 
1842     mo = P_SpawnMissile(MT_WIZFX1, actor, actor->target, true);
1843     if(mo)
1844     {
1845         momZ = mo->mom[MZ];
1846         angle = mo->angle;
1847 
1848         P_SpawnMissileAngle(MT_WIZFX1, actor, angle - (ANG45 / 8), momZ);
1849         P_SpawnMissileAngle(MT_WIZFX1, actor, angle + (ANG45 / 8), momZ);
1850     }
1851 }
1852 
A_Scream(mobj_t * actor)1853 void C_DECL A_Scream(mobj_t* actor)
1854 {
1855     switch(actor->type)
1856     {
1857     case MT_CHICPLAYER:
1858     case MT_SORCERER1:
1859     case MT_MINOTAUR:
1860         // Make boss death sounds full volume
1861         S_StartSound(actor->info->deathSound, NULL);
1862         break;
1863 
1864     case MT_PLAYER:
1865         // Handle the different player death screams
1866         if(actor->special1 < 10)
1867         {
1868             // Wimpy death sound.
1869             S_StartSound(SFX_PLRWDTH, actor);
1870         }
1871         else if(actor->health > -50)
1872         {
1873             // Normal death sound.
1874             S_StartSound(actor->info->deathSound, actor);
1875         }
1876         else if(actor->health > -100)
1877         {
1878             // Crazy death sound.
1879             S_StartSound(SFX_PLRCDTH, actor);
1880         }
1881         else
1882         {
1883             // Extreme death sound.
1884             S_StartSound(SFX_GIBDTH, actor);
1885         }
1886         break;
1887 
1888     default:
1889         S_StartSound(actor->info->deathSound, actor);
1890         break;
1891     }
1892 }
1893 
P_DropItem(mobjtype_t type,mobj_t * source,int special,int chance)1894 mobj_t* P_DropItem(mobjtype_t type, mobj_t* source, int special, int chance)
1895 {
1896     mobj_t* mo;
1897 
1898     if(P_Random() > chance)
1899         return NULL;
1900 
1901     if((mo = P_SpawnMobjXYZ(type, source->origin[VX], source->origin[VY],
1902                                   source->origin[VZ] + source->height / 2, source->angle, 0)))
1903     {
1904         mo->mom[MX] = FIX2FLT((P_Random() - P_Random()) << 8);
1905         mo->mom[MY] = FIX2FLT((P_Random() - P_Random()) << 8);
1906         if(!(mo->info->flags2 & MF2_FLOATBOB))
1907             mo->mom[MZ] = 5 + FIX2FLT(P_Random() << 10);
1908 
1909         mo->flags |= MF_DROPPED;
1910         mo->health = special;
1911     }
1912 
1913     return mo;
1914 }
1915 
A_NoBlocking(mobj_t * actor)1916 void C_DECL A_NoBlocking(mobj_t* actor)
1917 {
1918     actor->flags &= ~MF_SOLID;
1919 
1920     // Check for monsters dropping things.
1921     switch(actor->type)
1922     {
1923     case MT_MUMMY:
1924     case MT_MUMMYLEADER:
1925     case MT_MUMMYGHOST:
1926     case MT_MUMMYLEADERGHOST:
1927         P_DropItem(MT_AMGWNDWIMPY, actor, 3, 84);
1928         break;
1929 
1930     case MT_KNIGHT:
1931     case MT_KNIGHTGHOST:
1932         P_DropItem(MT_AMCBOWWIMPY, actor, 5, 84);
1933         break;
1934 
1935     case MT_WIZARD:
1936         P_DropItem(MT_AMBLSRWIMPY, actor, 10, 84);
1937         P_DropItem(MT_ARTITOMEOFPOWER, actor, 0, 4);
1938         break;
1939 
1940     case MT_HEAD:
1941         P_DropItem(MT_AMBLSRWIMPY, actor, 10, 84);
1942         P_DropItem(MT_ARTIEGG, actor, 0, 51);
1943         break;
1944 
1945     case MT_BEAST:
1946         P_DropItem(MT_AMCBOWWIMPY, actor, 10, 84);
1947         break;
1948 
1949     case MT_CLINK:
1950         P_DropItem(MT_AMSKRDWIMPY, actor, 20, 84);
1951         break;
1952 
1953     case MT_SNAKE:
1954         P_DropItem(MT_AMPHRDWIMPY, actor, 5, 84);
1955         break;
1956 
1957     case MT_MINOTAUR:
1958         P_DropItem(MT_ARTISUPERHEAL, actor, 0, 51);
1959         P_DropItem(MT_AMPHRDWIMPY, actor, 10, 84);
1960         break;
1961 
1962     default:
1963         break;
1964     }
1965 }
1966 
A_Explode(mobj_t * actor)1967 void C_DECL A_Explode(mobj_t *actor)
1968 {
1969     int damage = 128;
1970 
1971     switch(actor->type)
1972     {
1973     case MT_FIREBOMB: // Time Bomb
1974         actor->origin[VZ] += 32;
1975         actor->flags &= ~MF_SHADOW;
1976         actor->flags |= /*MF_BRIGHTSHADOW |*/ MF_VIEWALIGN;
1977         break;
1978 
1979     case MT_MNTRFX2: // Minotaur floor fire
1980         damage = 24;
1981         break;
1982 
1983     case MT_SOR2FX1: // D'Sparil missile
1984         damage = 80 + (P_Random() & 31);
1985         break;
1986 
1987     default: break;
1988     }
1989 
1990     P_RadiusAttack(actor, actor->target, damage, damage - 1);
1991     P_HitFloor(actor);
1992 }
1993 
A_PodPain(mobj_t * actor)1994 void C_DECL A_PodPain(mobj_t* actor)
1995 {
1996     int                 i, count, chance;
1997 
1998     chance = P_Random();
1999     if(chance < 128)
2000         return;
2001 
2002     if(chance > 240)
2003         count = 2;
2004     else
2005         count = 1;
2006 
2007     for(i = 0; i < count; ++i)
2008     {
2009         mobj_t*             goo;
2010 
2011         if((goo = P_SpawnMobjXYZ(MT_PODGOO, actor->origin[VX], actor->origin[VY],
2012                                 actor->origin[VZ] + 48, actor->angle, 0)))
2013         {
2014             goo->target = actor;
2015             goo->mom[MX] = FIX2FLT((P_Random() - P_Random()) << 9);
2016             goo->mom[MY] = FIX2FLT((P_Random() - P_Random()) << 9);
2017             goo->mom[MZ] = 1.0f / 2 + FIX2FLT((P_Random() << 9));
2018         }
2019     }
2020 }
2021 
A_RemovePod(mobj_t * actor)2022 void C_DECL A_RemovePod(mobj_t* actor)
2023 {
2024     mobj_t*             mo;
2025 
2026     if((mo = actor->generator))
2027     {
2028         if(mo->special1 > 0)
2029             mo->special1--;
2030     }
2031 }
2032 
A_MakePod(mobj_t * actor)2033 void C_DECL A_MakePod(mobj_t* actor)
2034 {
2035     mobj_t* mo;
2036 
2037     // Too many generated pods?
2038     if(actor->special1 == MAX_GEN_PODS)
2039         return;
2040 
2041     if(!(mo = P_SpawnMobjXYZ(MT_POD, actor->origin[VX], actor->origin[VY], 0,
2042                             actor->angle, MSF_Z_FLOOR)))
2043         return;
2044 
2045     if(P_CheckPositionXY(mo, mo->origin[VX], mo->origin[VY]) == false)
2046     {
2047         // Didn't fit.
2048         P_MobjRemove(mo, true);
2049         return;
2050     }
2051 
2052     P_MobjChangeState(mo, S_POD_GROW1);
2053     P_ThrustMobj(mo, P_Random() << 24, 4.5f);
2054 
2055     S_StartSound(SFX_NEWPOD, mo);
2056 
2057     // Increment generated pod count.
2058     actor->special1++;
2059 
2060     // Link the generator to the pod.
2061     mo->generator = actor;
2062     return;
2063 }
2064 
A_RestoreArtifact(mobj_t * mo)2065 void C_DECL A_RestoreArtifact(mobj_t* mo)
2066 {
2067     mo->flags |= MF_SPECIAL;
2068     P_MobjChangeState(mo, P_GetState(mo->type, SN_SPAWN));
2069     S_StartSound(SFX_RESPAWN, mo);
2070 }
2071 
A_RestoreSpecialThing1(mobj_t * mo)2072 void C_DECL A_RestoreSpecialThing1(mobj_t *mo)
2073 {
2074     if(mo->type == MT_WMACE)
2075     {
2076         // Do random mace placement.
2077         P_RepositionMace(mo);
2078     }
2079 
2080     mo->flags2 &= ~MF2_DONTDRAW;
2081     S_StartSound(SFX_RESPAWN, mo);
2082 }
2083 
A_RestoreSpecialThing2(mobj_t * thing)2084 void C_DECL A_RestoreSpecialThing2(mobj_t* thing)
2085 {
2086     thing->flags |= MF_SPECIAL;
2087     P_MobjChangeState(thing, P_GetState(thing->type, SN_SPAWN));
2088 }
2089 
massacreMobj(thinker_t * th,void * context)2090 static int massacreMobj(thinker_t* th, void* context)
2091 {
2092     int*                count = (int*) context;
2093     mobj_t*             mo = (mobj_t *) th;
2094 
2095     if(!mo->player && sentient(mo) && (mo->flags & MF_SHOOTABLE))
2096     {
2097         P_DamageMobj(mo, NULL, NULL, 10000, false);
2098         (*count)++;
2099     }
2100 
2101     return false; // Continue iteration.
2102 }
2103 
2104 /**
2105  * Kills all monsters.
2106  */
P_Massacre(void)2107 int P_Massacre(void)
2108 {
2109     int                 count = 0;
2110 
2111     // Only massacre when actually in a level.
2112     if(G_GameState() == GS_MAP)
2113     {
2114         Thinker_Iterate(P_MobjThinker, massacreMobj, &count);
2115     }
2116 
2117     return count;
2118 }
2119 
2120 typedef struct {
2121     mobjtype_t type;
2122     size_t count;
2123 } countmobjoftypeparams_t;
2124 
countMobjOfType(thinker_t * th,void * context)2125 static int countMobjOfType(thinker_t *th, void *context)
2126 {
2127     countmobjoftypeparams_t *params = (countmobjoftypeparams_t *) context;
2128     mobj_t *mo = (mobj_t *) th;
2129 
2130     if(params->type == mo->type && mo->health > 0)
2131     {
2132         params->count++;
2133     }
2134 
2135     return false; // Continue iteration.
2136 }
2137 
2138 typedef enum {
2139     ST_SPAWN_FLOOR,
2140     //ST_SPAWN_DOOR,
2141     ST_LEAVEMAP
2142 } SpecialType;
2143 
2144 /// @todo Should be defined in MapInfo.
2145 typedef struct {
2146     //int gameModeBits;
2147     char const *mapPath;
2148     //dd_bool compatAnyBoss; ///< @c true= type requirement optional by compat option.
2149     mobjtype_t bossType;
2150     dd_bool massacreOnDeath;
2151     SpecialType special;
2152     int tag;
2153     int type;
2154 } BossTrigger;
2155 
2156 /**
2157  * Trigger special effects on certain maps if all "bosses" are dead.
2158  */
A_BossDeath(mobj_t * actor)2159 void C_DECL A_BossDeath(mobj_t *actor)
2160 {
2161     static BossTrigger const bossTriggers[] =
2162     {
2163         { "E1M8", MT_HEAD,      false, ST_SPAWN_FLOOR, 666, FT_LOWER },
2164         { "E2M8", MT_MINOTAUR,  true,  ST_SPAWN_FLOOR, 666, FT_LOWER },
2165         { "E3M8", MT_SORCERER2, true,  ST_SPAWN_FLOOR, 666, FT_LOWER },
2166         { "E4M8", MT_HEAD,      true,  ST_SPAWN_FLOOR, 666, FT_LOWER },
2167         { "E5M8", MT_MINOTAUR,  true,  ST_SPAWN_FLOOR, 666, FT_LOWER },
2168     };
2169     static int const numBossTriggers = sizeof(bossTriggers) / sizeof(bossTriggers[0]);
2170 
2171     int i;
2172     AutoStr *currentMapPath = G_CurrentMapUriPath();
2173     for(i = 0; i < numBossTriggers; ++i)
2174     {
2175         BossTrigger const *trigger = &bossTriggers[i];
2176 
2177         // Not a boss on this map?
2178         if(actor->type != trigger->bossType) continue;
2179 
2180         if(Str_CompareIgnoreCase(currentMapPath, trigger->mapPath)) continue;
2181 
2182         // Scan the remaining thinkers to determine if this is indeed the last boss.
2183         {
2184             countmobjoftypeparams_t parm;
2185             parm.type  = actor->type;
2186             parm.count = 0;
2187             Thinker_Iterate(P_MobjThinker, countMobjOfType, &parm);
2188 
2189             // Anything left alive?
2190             if(parm.count) continue;
2191         }
2192 
2193         // Kill all remaining enemies?
2194         if(trigger->massacreOnDeath)
2195         {
2196             P_Massacre();
2197         }
2198 
2199         // Trigger the special.
2200         switch(trigger->special)
2201         {
2202         case ST_SPAWN_FLOOR: {
2203             Line *dummyLine = P_AllocDummyLine();
2204             P_ToXLine(dummyLine)->tag = trigger->tag;
2205             EV_DoFloor(dummyLine, (floortype_e)trigger->type);
2206             P_FreeDummyLine(dummyLine);
2207             break; }
2208 
2209         /*case ST_SPAWN_DOOR: {
2210             Line *dummyLine = P_AllocDummyLine();
2211             P_ToXLine(dummyLine)->tag = trigger->tag;
2212             EV_DoDoor(dummyLine, (doortype_e)trigger->type);
2213             P_FreeDummyLine(dummyLine);
2214             break; }*/
2215 
2216         case ST_LEAVEMAP:
2217             G_SetGameActionMapCompletedAndSetNextMap();
2218             break;
2219 
2220         default: DENG_ASSERT(!"A_BossDeath: Unknown trigger special type");
2221         }
2222     }
2223 }
2224 
A_ESound(mobj_t * mo)2225 void C_DECL A_ESound(mobj_t *mo)
2226 {
2227     int sound;
2228 
2229     switch(mo->type)
2230     {
2231     case MT_SOUNDWATERFALL:
2232         sound = SFX_WATERFL;
2233         break;
2234 
2235     case MT_SOUNDWIND:
2236         sound = SFX_WIND;
2237         break;
2238 
2239     default:
2240         return;
2241     }
2242 
2243     S_StartSound(sound, mo);
2244 }
2245 
A_SpawnTeleGlitter(mobj_t * actor)2246 void C_DECL A_SpawnTeleGlitter(mobj_t* actor)
2247 {
2248     mobj_t* mo;
2249     if(!actor) return;
2250 
2251     if((mo = P_SpawnMobjXYZ(MT_TELEGLITTER,
2252                            actor->origin[VX] + ((P_Random() & 31) - 16),
2253                            actor->origin[VY] + ((P_Random() & 31) - 16),
2254                            P_GetDoublep(Mobj_Sector(actor), DMU_FLOOR_HEIGHT),
2255                            P_Random() << 24, 0)))
2256     {
2257         mo->mom[MZ] = 1.0f / 4;
2258         mo->special3 = 1000;
2259     }
2260 }
2261 
A_SpawnTeleGlitter2(mobj_t * actor)2262 void C_DECL A_SpawnTeleGlitter2(mobj_t* actor)
2263 {
2264     mobj_t* mo;
2265 
2266     if(!actor) return;
2267 
2268     if((mo = P_SpawnMobjXYZ(MT_TELEGLITTER2,
2269                            actor->origin[VX] + ((P_Random() & 31) - 16),
2270                            actor->origin[VY] + ((P_Random() & 31) - 16),
2271                            P_GetDoublep(Mobj_Sector(actor), DMU_FLOOR_HEIGHT),
2272                            P_Random() << 24, 0)))
2273     {
2274         mo->mom[MZ] = 1.0f / 4;
2275         mo->special3 = 1000;
2276     }
2277 }
2278 
A_AccTeleGlitter(mobj_t * actor)2279 void C_DECL A_AccTeleGlitter(mobj_t* actor)
2280 {
2281     if(++actor->special3 > 35)
2282         actor->mom[MZ] += actor->mom[MZ] / 2;
2283 }
2284 
A_InitKeyGizmo(mobj_t * gizmo)2285 void C_DECL A_InitKeyGizmo(mobj_t* gizmo)
2286 {
2287     mobj_t* mo;
2288     statenum_t state;
2289 
2290     switch(gizmo->type)
2291     {
2292     case MT_KEYGIZMOBLUE:
2293         state = S_KGZ_BLUEFLOAT1;
2294         break;
2295 
2296     case MT_KEYGIZMOGREEN:
2297         state = S_KGZ_GREENFLOAT1;
2298         break;
2299 
2300     case MT_KEYGIZMOYELLOW:
2301         state = S_KGZ_YELLOWFLOAT1;
2302         break;
2303 
2304     default:
2305         return;
2306     }
2307 
2308     if((mo = P_SpawnMobjXYZ(MT_KEYGIZMOFLOAT,
2309                             gizmo->origin[VX], gizmo->origin[VY], gizmo->origin[VZ] + 60,
2310                             gizmo->angle, 0)))
2311     {
2312         P_MobjChangeState(mo, state);
2313     }
2314 }
2315 
A_VolcanoSet(mobj_t * volcano)2316 void C_DECL A_VolcanoSet(mobj_t* volcano)
2317 {
2318     volcano->tics = 105 + (P_Random() & 127);
2319 }
2320 
A_VolcanoBlast(mobj_t * volcano)2321 void C_DECL A_VolcanoBlast(mobj_t* volcano)
2322 {
2323     int i, count;
2324 
2325     count = 1 + (P_Random() % 3);
2326     for(i = 0; i < count; i++)
2327     {
2328         mobj_t* blast;
2329         unsigned int an;
2330 
2331         if((blast = P_SpawnMobjXYZ(MT_VOLCANOBLAST,
2332                                    volcano->origin[VX], volcano->origin[VY],
2333                                    volcano->origin[VZ] + 44, P_Random() << 24, 0)))
2334         {
2335             blast->target = volcano;
2336 
2337             an = blast->angle >> ANGLETOFINESHIFT;
2338             blast->mom[MX] = 1 * FIX2FLT(finecosine[an]);
2339             blast->mom[MY] = 1 * FIX2FLT(finesine[an]);
2340             blast->mom[MZ] = 2.5f + FIX2FLT(P_Random() << 10);
2341 
2342             S_StartSound(SFX_VOLSHT, blast);
2343             P_CheckMissileSpawn(blast);
2344         }
2345     }
2346 }
2347 
A_VolcBallImpact(mobj_t * ball)2348 void C_DECL A_VolcBallImpact(mobj_t* ball)
2349 {
2350     uint i;
2351 
2352     if(ball->origin[VZ] <= ball->floorZ)
2353     {
2354         ball->flags |= MF_NOGRAVITY;
2355         ball->flags2 &= ~MF2_LOGRAV;
2356         ball->origin[VZ] += 28;
2357     }
2358 
2359     P_RadiusAttack(ball, ball->target, 25, 24);
2360     for(i = 0; i < 4; ++i)
2361     {
2362         mobj_t* tiny;
2363 
2364         if((tiny = P_SpawnMobj(MT_VOLCANOTBLAST, ball->origin, i * ANG90, 0)))
2365         {
2366             unsigned int  an = tiny->angle >> ANGLETOFINESHIFT;
2367 
2368             tiny->target = ball;
2369             tiny->mom[MX] = .7f * FIX2FLT(finecosine[an]);
2370             tiny->mom[MY] = .7f * FIX2FLT(finesine[an]);
2371             tiny->mom[MZ] = 1 + FIX2FLT(P_Random() << 9);
2372 
2373             P_CheckMissileSpawn(tiny);
2374         }
2375     }
2376 }
2377 
A_SkullPop(mobj_t * actor)2378 void C_DECL A_SkullPop(mobj_t* actor)
2379 {
2380     mobj_t* mo;
2381 
2382     if((mo = P_SpawnMobjXYZ(MT_BLOODYSKULL, actor->origin[VX], actor->origin[VY],
2383                            actor->origin[VZ] + 48, actor->angle, 0)))
2384     {
2385         player_t* player;
2386 
2387         mo->mom[MX] = FIX2FLT((P_Random() - P_Random()) << 9);
2388         mo->mom[MY] = FIX2FLT((P_Random() - P_Random()) << 9);
2389         mo->mom[MZ] = 2 + FIX2FLT(P_Random() << 6);
2390 
2391         // Attach player mobj to bloody skull.
2392         player = actor->player;
2393         actor->player = NULL;
2394         actor->dPlayer = NULL;
2395         actor->flags &= ~MF_SOLID;
2396 
2397         mo->player = player;
2398         mo->dPlayer = (player? player->plr : 0);
2399         mo->health = actor->health;
2400 
2401         if(player)
2402         {
2403             player->plr->mo = mo;
2404             player->plr->lookDir = 0;
2405             player->damageCount = 32;
2406         }
2407     }
2408 }
2409 
A_CheckSkullFloor(mobj_t * actor)2410 void C_DECL A_CheckSkullFloor(mobj_t* actor)
2411 {
2412     if(actor->origin[VZ] <= actor->floorZ)
2413         P_MobjChangeState(actor, S_BLOODYSKULLX1);
2414 }
2415 
A_CheckSkullDone(mobj_t * actor)2416 void C_DECL A_CheckSkullDone(mobj_t* actor)
2417 {
2418     if(actor->special2 == 666)
2419         P_MobjChangeState(actor, S_BLOODYSKULLX2);
2420 }
2421 
A_CheckBurnGone(mobj_t * actor)2422 void C_DECL A_CheckBurnGone(mobj_t* actor)
2423 {
2424     if(actor->special2 == 666)
2425         P_MobjChangeState(actor, S_PLAY_FDTH20);
2426 }
2427 
A_FreeTargMobj(mobj_t * mo)2428 void C_DECL A_FreeTargMobj(mobj_t *mo)
2429 {
2430     mo->mom[MX] = mo->mom[MY] = mo->mom[MZ] = 0;
2431     mo->origin[VZ] = mo->ceilingZ + 4;
2432 
2433     mo->flags &= ~(MF_SHOOTABLE | MF_FLOAT | MF_SKULLFLY | MF_SOLID);
2434     mo->flags |= MF_CORPSE | MF_DROPOFF | MF_NOGRAVITY;
2435     mo->flags2 &= ~(MF2_PASSMOBJ | MF2_LOGRAV);
2436 
2437     mo->player = NULL;
2438     mo->dPlayer = NULL;
2439 }
2440 
A_AddPlayerCorpse(mobj_t * actor)2441 void C_DECL A_AddPlayerCorpse(mobj_t* actor)
2442 {
2443     // Too many player corpses?
2444     if(bodyqueslot >= BODYQUESIZE)
2445     {
2446         // Remove an old one.
2447         P_MobjRemove(bodyque[bodyqueslot % BODYQUESIZE], true);
2448     }
2449 
2450     bodyque[bodyqueslot % BODYQUESIZE] = actor;
2451     bodyqueslot++;
2452 }
2453 
A_FlameSnd(mobj_t * actor)2454 void C_DECL A_FlameSnd(mobj_t* actor)
2455 {
2456     S_StartSound(SFX_HEDAT1, actor); // Burn sound.
2457 }
2458 
A_HideThing(mobj_t * actor)2459 void C_DECL A_HideThing(mobj_t* actor)
2460 {
2461     //P_MobjUnlink(actor);
2462     actor->flags2 |= MF2_DONTDRAW;
2463 }
2464 
A_UnHideThing(mobj_t * actor)2465 void C_DECL A_UnHideThing(mobj_t* actor)
2466 {
2467     //P_MobjLink(actor);
2468     actor->flags2 &= ~MF2_DONTDRAW;
2469 }
2470