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, ¶ms);
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