1 //----------------------------------------------------------------------------
2 // EDGE Play Simulation Action routines
3 //----------------------------------------------------------------------------
4 //
5 // Copyright (c) 1999-2009 The EDGE Team.
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
16 //
17 //----------------------------------------------------------------------------
18 //
19 // Based on the DOOM source code, released by Id Software under the
20 // following copyright:
21 //
22 // Copyright (C) 1993-1996 by id Software, Inc.
23 //
24 //----------------------------------------------------------------------------
25 //
26 // Notes:
27 // All Procedures here are never called directly, except possibly
28 // by another P_Act* Routine. Otherwise the procedure is called
29 // by referencing an code pointer from the states[] table. The only
30 // exception to these rules are P_ActMissileContact and
31 // P_SlammedIntoObject that requiring "acting" on the part
32 // of an obj.
33 //
34 // This file was created for all action code by DDF.
35 //
36 // -KM- 1998/09/27 Added sounds.ddf capability
37 // -KM- 1998/12/21 New smooth visibility.
38 // -AJA- 1999/07/21: Replaced some non-critical P_Randoms with M_Random.
39 // -AJA- 1999/08/08: Replaced some P_Random()-P_Random() stuff.
40 //
41
42 #include "i_defs.h"
43 #include "p_action.h"
44
45 #include "con_main.h"
46 #include "dm_defs.h"
47 #include "dm_state.h"
48 #include "g_game.h"
49 #include "m_misc.h"
50 #include "m_random.h"
51 #include "p_local.h"
52 #include "p_weapon.h"
53 #include "r_state.h"
54 #include "rad_trig.h"
55 #include "s_sound.h"
56 #include "w_wad.h"
57 #include "z_zone.h"
58
59
60 cvar_c g_aggression;
61
62
AttackSfxCat(const mobj_t * mo)63 static int AttackSfxCat(const mobj_t *mo)
64 {
65 int category = P_MobjGetSfxCategory(mo);
66
67 if (category == SNCAT_Player)
68 return SNCAT_Weapon;
69
70 return category;
71 }
72
SfxFlags(const mobjtype_c * info)73 static int SfxFlags(const mobjtype_c *info)
74 {
75 int flags = 0;
76
77 if (info->extendedflags & EF_ALWAYSLOUD)
78 flags |= FX_Boss;
79
80 return flags;
81 }
82
83 //-----------------------------------------
84 //--------------MISCELLANOUS---------------
85 //-----------------------------------------
86
87 //
88 // P_ActActivateLineType
89 //
90 // Allows things to also activate linetypes, bringing them into the
91 // fold with radius triggers, which can also do it. There's only two
92 // parameters needed: linetype number & tag number, which are stored
93 // in the state's `action_par' field as a pointer to two integers.
94 //
P_ActActivateLineType(mobj_t * mo)95 void P_ActActivateLineType(mobj_t * mo)
96 {
97 int *values;
98
99 if (!mo->state || !mo->state->action_par)
100 return;
101
102 values = (int *) mo->state->action_par;
103
104 // Note the `NULL' here: this prevents the activation from failing
105 // because the object isn't a PLAYER, for example.
106 P_RemoteActivation(NULL, values[0], values[1], 0, line_Any);
107 }
108
109 //
110 // P_ActEnableRadTrig
111 // P_ActDisableRadTrig
112 //
113 // Allows things to enable or disable radius triggers (by tag number),
114 // like linetypes can do already.
115 //
P_ActEnableRadTrig(mobj_t * mo)116 void P_ActEnableRadTrig(mobj_t * mo)
117 {
118 if (!mo->state || !mo->state->action_par)
119 return;
120
121 int *value = (int *) mo->state->action_par;
122
123 RAD_EnableByTag(mo, value[0], false);
124 }
125
P_ActDisableRadTrig(mobj_t * mo)126 void P_ActDisableRadTrig(mobj_t * mo)
127 {
128 if (!mo->state || !mo->state->action_par)
129 return;
130
131 int *value = (int *) mo->state->action_par;
132
133 RAD_EnableByTag(mo, value[0], true);
134 }
135
136 //
137 // P_ActLookForTargets
138 //
139 // Looks for targets: used in the same way as enemy things look
140 // for players
141 //
142 // TODO: Write a decent procedure.
143 // -KM- 1999/01/31 Added sides. Still has to search every mobj on the
144 // map to find a target. There must be a better way...
145 // -AJA- 2004/04/28: Rewritten. Mobjs on same side are never targeted.
146 //
147 // NOTE: a better way might be: do a mini "BSP render", use a small 1D
148 // occlusion buffer (e.g. 64 bits).
149 //
P_ActLookForTargets(mobj_t * we)150 bool P_ActLookForTargets(mobj_t *we)
151 {
152 mobj_t *them;
153
154 // Optimisation: nobody to support when side is zero
155 if (we->side == 0)
156 return P_LookForPlayers(we, we->info->sight_angle);
157
158 for (them = mobjlisthead; them != NULL; them = them->next)
159 {
160 if (them == we)
161 continue;
162
163 bool same_side = ((them->side & we->side) != 0);
164
165 // only target monsters or players (not barrels)
166 if (! (them->extendedflags & EF_MONSTER) && ! them->player)
167 continue;
168
169 if (! (them->flags & MF_SHOOTABLE))
170 continue;
171
172 if (same_side && !we->supportobj && them->supportobj != we)
173 {
174 if (them->supportobj && P_CheckSight(we, them->supportobj))
175 them = them->supportobj;
176 else if (! P_CheckSight(we, them))
177 continue; // OK since same side
178
179 if (them)
180 {
181 we->SetSupportObj(them);
182 if (we->info->meander_state)
183 P_SetMobjStateDeferred(we, we->info->meander_state, 0);
184 return true;
185 }
186 }
187
188 if (same_side)
189 continue;
190
191 /// if (them == we->supportobj || we == them->supportobj ||
192 /// (them->supportobj && them->supportobj == we->supportobj))
193 /// continue;
194
195 if ((we->info == them->info) && ! (we->extendedflags & EF_DISLOYALTYPE))
196 continue;
197
198 /// -AJA- IDEALLY: use this to prioritize possible targets.
199 /// if (! (them->target &&
200 /// (them->target == we || them->target == we->supportobj)))
201 /// continue;
202
203 if (P_CheckSight(we, them))
204 {
205 we->SetTarget(them);
206 if (we->info->chase_state)
207 P_SetMobjStateDeferred(we, we->info->chase_state, 0);
208 return true;
209 }
210 }
211
212 return false;
213 }
214
215 //
216 // DecideMeleeAttack
217 //
218 // This is based on P_CheckMeleeRange, except that it relys upon
219 // info from the objects close combat attack, the original code
220 // used a set value for all objects which was MELEERANGE + 20,
221 // this code allows different melee ranges for different objects.
222 //
223 // -ACB- 1998/08/15
224 // -KM- 1998/11/25 Added attack parameter.
225 //
DecideMeleeAttack(mobj_t * object,const atkdef_c * attack)226 static bool DecideMeleeAttack(mobj_t * object, const atkdef_c * attack)
227 {
228 mobj_t *target;
229 float distance;
230 float meleedist;
231
232 target = object->target;
233
234 if (!target)
235 return false;
236
237 distance = P_ApproxDistance(target->x - object->x, target->y - object->y);
238
239 if (level_flags.true3dgameplay)
240 distance = P_ApproxDistance(target->z - object->z, distance);
241
242 meleedist = attack ? attack->range : MELEERANGE;
243 meleedist += target->radius - 20.0f; // Check the thing's actual radius
244
245 if (distance >= meleedist)
246 return false;
247
248 return P_CheckSight(object, target);
249 }
250
251 //
252 // DecideRangeAttack
253 //
254 // This is based on P_CheckMissileRange, contrary the name it does more
255 // than check the missile range, it makes a decision of whether or not an
256 // attack should be made or not depending on the object with the option
257 // to attack. A return of false is mandatory if the object cannot see its
258 // target (makes sense, doesn't it?), after this the distance is calculated,
259 // it will eventually be check to see if it is greater than a number from
260 // the Random Number Generator; if so the procedure returns true. Essentially
261 // the closer the object is to its target, the more chance an attack will
262 // be made (another logical decision).
263 //
264 // -ACB- 1998/08/15
265 //
DecideRangeAttack(mobj_t * object)266 static bool DecideRangeAttack(mobj_t * object)
267 {
268 percent_t chance;
269 float distance;
270 const atkdef_c *attack;
271
272 if (! object->target)
273 return false;
274
275 if (object->info->rangeattack)
276 attack = object->info->rangeattack;
277 else
278 return false; // cannot evaluate range with no attack range
279
280 // Just been hit (and have felt pain), so in true tit-for-tat
281 // style, the object - without regard to anything else - hits back.
282 if (object->flags & MF_JUSTHIT)
283 {
284 if (! P_CheckSight(object, object->target))
285 return false;
286
287 object->flags &= ~MF_JUSTHIT;
288 return true;
289 }
290
291 // Bit slow on the up-take: the object hasn't had time to
292 // react his target.
293 if (object->reactiontime)
294 return false;
295
296 // Get the distance, a basis for our decision making from now on
297 distance = P_ApproxDistance(object->x - object->target->x,
298 object->y - object->target->y);
299
300 // If no close-combat attack, increase the chance of a missile attack
301 if (!object->info->melee_state)
302 distance -= 192;
303 else
304 distance -= 64;
305
306 // Object is too far away to attack?
307 if (attack->range && distance >= attack->range)
308 return false;
309
310 // Object is too close to target
311 if (attack->tooclose && attack->tooclose >= distance)
312 return false;
313
314 // Object likes to fire? if so, double the chance of it happening
315 if (object->extendedflags & EF_TRIGGERHAPPY)
316 distance /= 2;
317
318 // The chance in the object is one given that the attack will happen, so
319 // we inverse the result (since its one in 255) to get the chance that
320 // the attack will not happen.
321 chance = 1.0f - object->info->minatkchance;
322 chance = MIN(distance / 255.0f, chance);
323
324 // now after modifing distance where applicable, we get the random number and
325 // check if it is less than distance, if so no attack is made.
326 if (P_RandomTest(chance))
327 return false;
328
329 return P_CheckSight(object, object->target);
330 }
331
332 //
333 // P_ActFaceTarget
334 //
335 // Look at the prey......
336 //
P_ActFaceTarget(mobj_t * object)337 void P_ActFaceTarget(mobj_t * object)
338 {
339 mobj_t *target = object->target;
340
341 if (!target)
342 return;
343
344 if (object->flags & MF_STEALTH)
345 object->vis_target = VISIBLE;
346
347 object->flags &= ~MF_AMBUSH;
348
349 object->angle = R_PointToAngle(object->x, object->y, target->x, target->y);
350
351 float dist = R_PointToDist(object->x, object->y, target->x, target->y);
352
353 if (dist >= 0.1f)
354 {
355 float dz = MO_MIDZ(target) - MO_MIDZ(object);
356
357 object->vertangle = M_ATan(dz / dist);
358 }
359
360 if (target->flags & MF_FUZZY)
361 {
362 object->angle += P_RandomNegPos() << (ANGLEBITS - 11);
363 object->vertangle += M_ATan(P_RandomNegPos() / 1024.0f);
364 }
365
366 if (target->visibility < VISIBLE)
367 {
368 float amount = (VISIBLE - target->visibility);
369
370 object->angle += (angle_t)(P_RandomNegPos() * (ANGLEBITS - 12) * amount);
371 object->vertangle += M_ATan(P_RandomNegPos() * amount / 2048.0f);
372 }
373
374 // don't look up/down too far...
375 if (object->vertangle < ANG180 && object->vertangle > ANG45)
376 object->vertangle = ANG45;
377
378 if (object->vertangle >= ANG180 && object->vertangle < ANG315)
379 object->vertangle = ANG315;
380 }
381
382
P_ActMakeIntoCorpse(mobj_t * mo)383 void P_ActMakeIntoCorpse(mobj_t * mo)
384 {
385 // Gives the effect of the object being a corpse....
386
387 if (mo->flags & MF_STEALTH)
388 mo->vis_target = VISIBLE; // dead and very visible
389
390 // object is on ground, it can be walked over
391 mo->flags &= ~MF_SOLID;
392
393 mo->tag = 0;
394 }
395
396
P_BringCorpseToLife(mobj_t * corpse)397 void P_BringCorpseToLife(mobj_t * corpse)
398 {
399 // Bring a corpse back to life (the opposite of the above routine).
400 // Handles players too !
401
402 const mobjtype_c *info = corpse->info;
403
404 corpse->flags = info->flags;
405 corpse->health = info->spawnhealth;
406 corpse->radius = info->radius;
407 corpse->height = info->height;
408 corpse->extendedflags = info->extendedflags;
409 corpse->hyperflags = info->hyperflags;
410 corpse->vis_target = PERCENT_2_FLOAT(info->translucency);
411 corpse->tag = corpse->spawnpoint.tag;
412
413 if (corpse->player)
414 {
415 corpse->player->playerstate = PST_LIVE;
416 corpse->player->health = corpse->health;
417 corpse->player->std_viewheight = corpse->height *
418 PERCENT_2_FLOAT(info->viewheight);
419 }
420
421 if (info->overkill_sound)
422 S_StartFX(info->overkill_sound, P_MobjGetSfxCategory(corpse), corpse);
423
424 if (info->raise_state)
425 P_SetMobjState(corpse, info->raise_state);
426 else if (info->meander_state)
427 P_SetMobjState(corpse, info->meander_state);
428 else if (info->idle_state)
429 P_SetMobjState(corpse, info->idle_state);
430 else
431 I_Error("Object %s has no RESURRECT states.\n", info->name.c_str());
432 }
433
434
P_ActResetSpreadCount(mobj_t * mo)435 void P_ActResetSpreadCount(mobj_t * mo)
436 {
437 // Resets the spreader count for fixed-order spreaders, normally used
438 // at the beginning of a set of missile states to ensure that an object
439 // fires in the same object each time.
440
441 mo->spreadcount = 0;
442 }
443
444 //-------------------------------------------------------------------
445 //-------------------VISIBILITY HANDLING ROUTINES--------------------
446 //-------------------------------------------------------------------
447
P_ActTransSet(mobj_t * mo)448 void P_ActTransSet(mobj_t * mo)
449 {
450 float value = VISIBLE;
451
452 const state_t *st = mo->state;
453
454 if (st && st->action_par)
455 {
456 value = ((percent_t *)st->action_par)[0];
457 value = MAX(0.0f, MIN(1.0f, value));
458 }
459
460 mo->visibility = mo->vis_target = value;
461 }
462
P_ActTransFade(mobj_t * mo)463 void P_ActTransFade(mobj_t * mo)
464 {
465 float value = INVISIBLE;
466
467 const state_t *st = mo->state;
468
469 if (st && st->action_par)
470 {
471 value = ((percent_t *)st->action_par)[0];
472 value = MAX(0.0f, MIN(1.0f, value));
473 }
474
475 mo->vis_target = value;
476 }
477
P_ActTransLess(mobj_t * mo)478 void P_ActTransLess(mobj_t * mo)
479 {
480 float value = 0.05f;
481
482 const state_t *st = mo->state;
483
484 if (st && st->action_par)
485 {
486 value = ((percent_t *)st->action_par)[0];
487 value = MAX(0.0f, MIN(1.0f, value));
488 }
489
490 mo->vis_target -= value;
491
492 if (mo->vis_target < INVISIBLE)
493 mo->vis_target = INVISIBLE;
494 }
495
P_ActTransMore(mobj_t * mo)496 void P_ActTransMore(mobj_t * mo)
497 {
498 float value = 0.05f;
499
500 const state_t *st = mo->state;
501
502 if (st && st->action_par)
503 {
504 value = ((percent_t *)st->action_par)[0];
505 value = MAX(0.0f, MIN(1.0f, value));
506 }
507
508 mo->vis_target += value;
509
510 if (mo->vis_target > VISIBLE)
511 mo->vis_target = VISIBLE;
512 }
513
514 //
515 // P_ActTransAlternate
516 //
517 // Alters the translucency of an item, EF_LESSVIS is used
518 // internally to tell the object if it should be getting
519 // more visible or less visible; EF_LESSVIS is set when an
520 // object is to get less visible (because it has become
521 // to a level of lowest translucency) and the flag is unset
522 // if the object has become as highly translucent as possible.
523 //
P_ActTransAlternate(mobj_t * object)524 void P_ActTransAlternate(mobj_t * object)
525 {
526 const state_t *st;
527 float value = 0.05f;
528
529 st = object->state;
530
531 if (st && st->action_par)
532 {
533 value = ((percent_t *)st->action_par)[0];
534 value = MAX(0.0f, MIN(1.0f, value));
535 }
536
537 if (object->extendedflags & EF_LESSVIS)
538 {
539 object->vis_target -= value;
540 if (object->vis_target <= INVISIBLE)
541 {
542 object->vis_target = INVISIBLE;
543 object->extendedflags &= ~EF_LESSVIS;
544 }
545 }
546 else
547 {
548 object->vis_target += value;
549 if (object->vis_target >= VISIBLE)
550 {
551 object->vis_target = VISIBLE;
552 object->extendedflags |= EF_LESSVIS;
553 }
554 }
555 }
556
557
P_ActDLightSet(mobj_t * mo)558 void P_ActDLightSet(mobj_t * mo)
559 {
560 const state_t *st = mo->state;
561
562 if (st && st->action_par)
563 {
564 mo->dlight.r = MAX(0.0f, ((int *)st->action_par)[0]);
565
566 if (mo->info->hyperflags & HF_QUADRATIC_COMPAT)
567 mo->dlight.r = DLIT_COMPAT_RAD(mo->dlight.r);
568
569 mo->dlight.target = mo->dlight.r;
570 }
571 }
572
573
P_ActDLightFade(mobj_t * mo)574 void P_ActDLightFade(mobj_t * mo)
575 {
576 const state_t *st = mo->state;
577
578 if (st && st->action_par)
579 {
580 mo->dlight.target = MAX(0.0f, ((int *)st->action_par)[0]);
581
582 if (mo->info->hyperflags & HF_QUADRATIC_COMPAT)
583 mo->dlight.target = DLIT_COMPAT_RAD(mo->dlight.target);
584 }
585 }
586
587
P_ActDLightRandom(mobj_t * mo)588 void P_ActDLightRandom(mobj_t * mo)
589 {
590 const state_t *st = mo->state;
591
592 if (st && st->action_par)
593 {
594 int low = ((int *)st->action_par)[0];
595 int high = ((int *)st->action_par)[1];
596
597 // Note: using M_Random so that gameplay is unaffected
598 float qty = low + (high - low) * M_Random() / 255.0f;
599
600 if (mo->info->hyperflags & HF_QUADRATIC_COMPAT)
601 qty = DLIT_COMPAT_RAD(qty);
602
603 mo->dlight.r = MAX(0.0f, qty);
604 mo->dlight.target = mo->dlight.r;
605 }
606 }
607
P_ActDLightColour(struct mobj_s * mo)608 void P_ActDLightColour(struct mobj_s *mo)
609 {
610 const state_t *st = mo->state;
611
612 if (st && st->action_par)
613 {
614 mo->dlight.color = ((rgbcol_t*) st->action_par)[0];
615 }
616 }
617
P_ActSetSkin(mobj_t * mo)618 void P_ActSetSkin(mobj_t * mo)
619 {
620 const state_t *st = mo->state;
621
622 if (st && st->action_par)
623 {
624 int skin = ((int *)st->action_par)[0];
625
626 if (skin < 0 || skin > 9)
627 I_Error("Thing [%s]: Bad skin number %d in SET_SKIN action.\n",
628 mo->info->name.c_str(), skin);
629
630 mo->model_skin = skin;
631 }
632 }
633
634
635
636 //-------------------------------------------------------------------
637 //------------------- MOVEMENT ROUTINES -----------------------------
638 //-------------------------------------------------------------------
639
P_ActFaceDir(mobj_t * mo)640 void P_ActFaceDir(mobj_t * mo)
641 {
642 const state_t *st = mo->state;
643
644 if (st && st->action_par)
645 mo->angle = *(angle_t *)st->action_par;
646 else
647 mo->angle = 0;
648 }
649
P_ActTurnDir(mobj_t * mo)650 void P_ActTurnDir(mobj_t * mo)
651 {
652 const state_t *st = mo->state;
653
654 angle_t turn = ANG180;
655
656 if (st && st->action_par)
657 turn = *(angle_t *)st->action_par;
658
659 mo->angle += turn;
660 }
661
P_ActTurnRandom(mobj_t * mo)662 void P_ActTurnRandom(mobj_t * mo)
663 {
664 const state_t *st = mo->state;
665 int turn = 359;
666
667 if (st && st->action_par)
668 {
669 turn = (int)ANG_2_FLOAT(*(angle_t *)st->action_par);
670 }
671
672 turn = turn * P_Random() / 90; // 10 bits of angle
673
674 if (turn < 0)
675 mo->angle -= (angle_t)((-turn) << (ANGLEBITS - 10));
676 else
677 mo->angle += (angle_t)(turn << (ANGLEBITS - 10));
678 }
679
P_ActMlookFace(mobj_t * mo)680 void P_ActMlookFace(mobj_t * mo)
681 {
682 const state_t *st = mo->state;
683
684 if (st && st->action_par)
685 mo->vertangle = M_ATan(*(float *)st->action_par);
686 else
687 mo->vertangle = 0;
688 }
689
P_ActMlookTurn(mobj_t * mo)690 void P_ActMlookTurn(mobj_t * mo)
691 {
692 const state_t *st = mo->state;
693
694 if (st && st->action_par)
695 mo->vertangle = M_ATan(*(float *)st->action_par);
696 }
697
P_ActMoveFwd(mobj_t * mo)698 void P_ActMoveFwd(mobj_t * mo)
699 {
700 const state_t *st = mo->state;
701
702 if (st && st->action_par)
703 {
704 float amount = *(float *)st->action_par;
705
706 float dx = M_Cos(mo->angle);
707 float dy = M_Sin(mo->angle);
708
709 mo->mom.x += dx * amount;
710 mo->mom.y += dy * amount;
711 }
712 }
713
P_ActMoveRight(mobj_t * mo)714 void P_ActMoveRight(mobj_t * mo)
715 {
716 const state_t *st = mo->state;
717
718 if (st && st->action_par)
719 {
720 float amount = *(float *)st->action_par;
721
722 float dx = M_Cos(mo->angle - ANG90);
723 float dy = M_Sin(mo->angle - ANG90);
724
725 mo->mom.x += dx * amount;
726 mo->mom.y += dy * amount;
727 }
728 }
729
P_ActMoveUp(mobj_t * mo)730 void P_ActMoveUp(mobj_t * mo)
731 {
732 const state_t *st = mo->state;
733
734 if (st && st->action_par)
735 mo->mom.z += *(float *)st->action_par;
736 }
737
P_ActStopMoving(mobj_t * mo)738 void P_ActStopMoving(mobj_t * mo)
739 {
740 mo->mom.x = mo->mom.y = mo->mom.z = 0;
741 }
742
743
744 //-------------------------------------------------------------------
745 //-------------------SOUND CAUSING ROUTINES--------------------------
746 //-------------------------------------------------------------------
747
P_ActPlaySound(mobj_t * mo)748 void P_ActPlaySound(mobj_t * mo)
749 {
750 // Generate an arbitrary sound.
751
752 sfx_t *sound = NULL;
753
754 if (mo->state && mo->state->action_par)
755 sound = (sfx_t *) mo->state->action_par;
756
757 if (! sound)
758 {
759 M_WarnError("P_ActPlaySound: missing sound name in %s.\n",
760 mo->info->name.c_str());
761 return;
762 }
763
764 S_StartFX(sound, P_MobjGetSfxCategory(mo), mo);
765 }
766
767
P_ActKillSound(mobj_t * mo)768 void P_ActKillSound(mobj_t * mo)
769 {
770 // Kill any current sounds from this thing.
771
772 S_StopFX(mo);
773 }
774
775
P_ActMakeAmbientSound(mobj_t * mo)776 void P_ActMakeAmbientSound(mobj_t * mo)
777 {
778 // Just a sound generating procedure that cause the sound ref
779 // in seesound to be generated.
780
781 if (mo->info->seesound)
782 S_StartFX(mo->info->seesound, P_MobjGetSfxCategory(mo), mo);
783 else
784 I_Debugf("%s has no ambient sound\n", mo->info->name.c_str());
785 }
786
787
P_ActMakeAmbientSoundRandom(mobj_t * mo)788 void P_ActMakeAmbientSoundRandom(mobj_t * mo)
789 {
790 // Give a small "random" chance that this object will make its
791 // ambient sound. Currently this is a set value of 50, however
792 // the code that drives this, should allow for the user to set
793 // the value, note for further DDF Development.
794
795 if (mo->info->seesound)
796 {
797 if (M_Random() < 50)
798 S_StartFX(mo->info->seesound, P_MobjGetSfxCategory(mo), mo);
799 }
800 else
801 I_Debugf("%s has no ambient sound\n", mo->info->name.c_str());
802 }
803
804
P_ActMakeActiveSound(mobj_t * mo)805 void P_ActMakeActiveSound(mobj_t * mo)
806 {
807 // Just a sound generating procedure that cause the sound ref
808 // in activesound to be generated.
809 //
810 // -KM- 1999/01/31
811
812 if (mo->info->activesound)
813 S_StartFX(mo->info->activesound, P_MobjGetSfxCategory(mo), mo);
814 else
815 I_Debugf("%s has no ambient sound\n", mo->info->name.c_str());
816 }
817
818
P_ActMakeDyingSound(mobj_t * mo)819 void P_ActMakeDyingSound(mobj_t * mo)
820 {
821 // This procedure is like everyother sound generating
822 // procedure with the exception that if the object is
823 // a boss (EF_ALWAYSLOUD extended flag) then the sound is
824 // generated at full volume (source = NULL).
825
826 sfx_t *sound = mo->info->deathsound;
827
828 if (sound)
829 S_StartFX(sound, P_MobjGetSfxCategory(mo), mo, SfxFlags(mo->info));
830 else
831 I_Debugf("%s has no death sound\n", mo->info->name.c_str());
832 }
833
834
P_ActMakePainSound(mobj_t * mo)835 void P_ActMakePainSound(mobj_t * mo)
836 {
837 // Ow!! it hurts!
838
839 if (mo->info->painsound)
840 S_StartFX(mo->info->painsound, P_MobjGetSfxCategory(mo),
841 mo, SfxFlags(mo->info));
842 else
843 I_Debugf("%s has no pain sound\n", mo->info->name.c_str());
844 }
845
846
P_ActMakeOverKillSound(mobj_t * mo)847 void P_ActMakeOverKillSound(mobj_t * mo)
848 {
849 if (mo->info->overkill_sound)
850 S_StartFX(mo->info->overkill_sound, P_MobjGetSfxCategory(mo),
851 mo, SfxFlags(mo->info));
852 else
853 I_Debugf("%s has no overkill sound\n", mo->info->name.c_str());
854 }
855
856
P_ActMakeCloseAttemptSound(mobj_t * mo)857 void P_ActMakeCloseAttemptSound(mobj_t * mo)
858 {
859 // Attempting close combat sound
860
861 if (! mo->info->closecombat)
862 I_Error("Object [%s] used CLOSEATTEMPTSND action, "
863 "but has no CLOSE_ATTACK\n", mo->info->name.c_str());
864
865 sfx_t *sound = mo->info->closecombat->initsound;
866
867 if (sound)
868 S_StartFX(sound, P_MobjGetSfxCategory(mo), mo);
869 else
870 I_Debugf("%s has no close combat attempt sound\n", mo->info->name.c_str());
871 }
872
873
P_ActMakeRangeAttemptSound(mobj_t * mo)874 void P_ActMakeRangeAttemptSound(mobj_t * mo)
875 {
876 // Attempting range attack sound
877
878 if (! mo->info->rangeattack)
879 I_Error("Object [%s] used RANGEATTEMPTSND action, "
880 "but has no RANGE_ATTACK\n", mo->info->name.c_str());
881
882 sfx_t *sound = mo->info->rangeattack->initsound;
883
884 if (sound)
885 S_StartFX(sound, P_MobjGetSfxCategory(mo), mo);
886 else
887 I_Debugf("%s has no range attack attempt sound\n", mo->info->name.c_str());
888 }
889
890
891 //-------------------------------------------------------------------
892 //-------------------EXPLOSION DAMAGE ROUTINES-----------------------
893 //-------------------------------------------------------------------
894
895 //
896 // P_ActDamageExplosion
897 //
898 // Radius Attack damage set by info->damage. Used for the original Barrels
899 //
P_ActDamageExplosion(mobj_t * object)900 void P_ActDamageExplosion(mobj_t * object)
901 {
902 float damage;
903
904 DAMAGE_COMPUTE(damage, &object->info->explode_damage);
905
906 #ifdef DEVELOPERS
907 if (!damage)
908 {
909 L_WriteDebug("%s caused no explosion damage\n", object->info->name.c_str());
910 return;
911 }
912 #endif
913
914 // -AJA- 2004/09/27: new EXPLODE_RADIUS command (overrides normal calc)
915 float radius = object->info->explode_radius;
916 if (radius == 0) radius = damage;
917
918 P_RadiusAttack(object, object->source, radius, damage,
919 &object->info->explode_damage, false);
920 }
921
922 //
923 // P_ActThrust
924 //
925 // Thrust set by info->damage.
926 //
P_ActThrust(mobj_t * object)927 void P_ActThrust(mobj_t * object)
928 {
929 float damage;
930
931 DAMAGE_COMPUTE(damage, &object->info->explode_damage);
932
933 #ifdef DEVELOPERS
934 if (!damage)
935 {
936 L_WriteDebug("%s caused no thrust\n", object->info->name.c_str());
937 return;
938 }
939 #endif
940
941 float radius = object->info->explode_radius;
942 if (radius == 0) radius = damage;
943
944 P_RadiusAttack(object, object->source, radius, damage,
945 &object->info->explode_damage, true);
946 }
947
948 //-------------------------------------------------------------------
949 //-------------------MISSILE HANDLING ROUTINES-----------------------
950 //-------------------------------------------------------------------
951
952 //
953 // P_ActExplode
954 //
955 // The object blows up, like a missile.
956 //
957 // -AJA- 1999/08/21: Replaced P_ActExplodeMissile (which was identical
958 // to p_mobj's P_ExplodeMissile) with this.
959 //
P_ActExplode(mobj_t * object)960 void P_ActExplode(mobj_t * object)
961 {
962 P_MobjExplodeMissile(object);
963 }
964
965 //
966 // P_ActCheckMissileSpawn
967 //
968 // This procedure handles a newly spawned missile, it moved
969 // by half the amount of momentum and then checked to see
970 // if the move is possible, if not the projectile is
971 // exploded. Also the number of initial tics on its
972 // current state is taken away from by a random number
973 // between 0 and 3, although the number of tics will never
974 // go below 1.
975 //
976 // -ACB- 1998/08/04
977 //
978 // -AJA- 1999/08/22: Fixed a bug that occasionally caused the game to
979 // go into an infinite loop. NOTE WELL: don't fiddle with the
980 // object's x & y directly, use P_TryMove instead, or
981 // P_ChangeThingPosition.
982 //
CheckMissileSpawn(mobj_t * projectile)983 static void CheckMissileSpawn(mobj_t * projectile)
984 {
985 projectile->tics -= P_Random() & 3;
986
987 if (projectile->tics < 1)
988 projectile->tics = 1;
989
990 projectile->z += projectile->mom.z / 2;
991
992 if (!P_TryMove(projectile,
993 projectile->x + projectile->mom.x / 2,
994 projectile->y + projectile->mom.y / 2))
995 {
996 P_MobjExplodeMissile(projectile);
997 }
998 }
999
1000 //
1001 // LaunchProjectile
1002 //
1003 // This procedure launches a project the direction of the target mobj.
1004 // * source - the source of the projectile
1005 // * target - the target of the projectile
1006 // * type - the mobj type of the projectile
1007 //
1008 // For all sense and purposes it is possible for the target to be a dummy
1009 // mobj, just to act as a carrier for a set of target co-ordinates.
1010 //
1011 // Missiles can be spawned at different locations on and around
1012 // the mobj. Traditionally an mobj would fire a projectile
1013 // at a height of 32 from the centerpoint of that
1014 // mobj, this was true for all creatures from the Cyberdemon to
1015 // the Imp. The currentattack holds the height and x & y
1016 // offsets that dictates the spawning location of a projectile.
1017 //
1018 // Traditionally: Height = 4*8
1019 // x-offset = 0
1020 // y-offset = 0
1021 //
1022 // The exception to this rule is the revenant, which did increase
1023 // its z value by 16 before firing: This was a hack
1024 // to launch a missile at a height of 48. The revenants
1025 // height was reduced to normal after firing, this new code
1026 // makes that an unnecesary procedure.
1027 //
1028 // projx, projy & projz are the projectiles spawn location
1029 //
1030 // NOTE: may return NULL.
1031 //
DoLaunchProjectile(mobj_t * source,float tx,float ty,float tz,mobj_t * target,const mobjtype_c * type)1032 static mobj_t *DoLaunchProjectile(mobj_t * source, float tx, float ty, float tz,
1033 mobj_t * target, const mobjtype_c * type)
1034 {
1035 const atkdef_c *attack = source->currentattack;
1036
1037 if (! attack)
1038 return NULL;
1039
1040 // -AJA- projz now handles crouching
1041 float projx = source->x;
1042 float projy = source->y;
1043 float projz = source->z + attack->height * source->height / source->info->height;
1044
1045 angle_t angle = source->angle;
1046
1047 projx += attack->xoffset * M_Cos(angle + ANG90);
1048 projy += attack->xoffset * M_Sin(angle + ANG90);
1049
1050 float yoffset;
1051
1052 if (attack->yoffset)
1053 yoffset = attack->yoffset;
1054 else
1055 yoffset = source->radius - 0.5f;
1056
1057 projx += yoffset * M_Cos(angle) * M_Cos(source->vertangle);
1058 projy += yoffset * M_Sin(angle) * M_Cos(source->vertangle);
1059 projz += yoffset * M_Sin(source->vertangle);
1060
1061 mobj_t *projectile = P_MobjCreateObject(projx, projy, projz, type);
1062
1063 // currentattack is held so that when a collision takes place
1064 // with another object, we know whether or not the object hit
1065 // can shake off the attack or is damaged by it.
1066 //
1067 projectile->currentattack = attack;
1068 projectile->SetRealSource(source);
1069
1070 // check for blocking lines between source and projectile
1071 if (P_MapCheckBlockingLine(source, projectile))
1072 {
1073 P_MobjExplodeMissile(projectile);
1074 return NULL;
1075 }
1076
1077 // launch sound
1078 if (projectile->info && projectile->info->seesound)
1079 {
1080 int category = AttackSfxCat(source);
1081 int flags = SfxFlags(projectile->info);
1082
1083 mobj_t *sfx_source = projectile;
1084 if (category == SNCAT_Player || category == SNCAT_Weapon)
1085 sfx_source = source;
1086
1087 S_StartFX(projectile->info->seesound, category, sfx_source, flags);
1088 }
1089
1090 angle = R_PointToAngle(projx, projy, tx, ty);
1091
1092 // Now add the fact that the target may be difficult to spot and
1093 // make the projectile's target the same as the sources. Only
1094 // do these if the object is not a dummy object, otherwise just
1095 // flag the missile not to trace: you cannot track a target that
1096 // does not exist...
1097
1098 projectile->SetTarget(target);
1099
1100 if (! target)
1101 {
1102 tz += attack->height;
1103 }
1104 else
1105 {
1106 projectile->extendedflags |= EF_FIRSTCHECK;
1107
1108 if (! (attack->flags & AF_Player))
1109 {
1110 if (target->flags & MF_FUZZY)
1111 angle += P_RandomNegPos() << (ANGLEBITS - 12);
1112
1113 if (target->visibility < VISIBLE)
1114 angle += (angle_t)(P_RandomNegPos() * 64 * (VISIBLE - target->visibility));
1115 }
1116 }
1117
1118 // Calculate slope
1119 float slope = P_ApproxSlope(tx - projx, ty - projy, tz - projz);
1120
1121 // -AJA- 1999/09/11: add in attack's angle & slope offsets.
1122 angle -= attack->angle_offset;
1123 slope += attack->slope_offset;
1124
1125 // is the attack not accurate?
1126 if (!source->player || source->player->refire > 0)
1127 {
1128 if (attack->accuracy_angle > 0.0f)
1129 angle += (attack->accuracy_angle >> 8) * P_RandomNegPos();
1130
1131 if (attack->accuracy_slope > 0.0f)
1132 slope += attack->accuracy_slope * (P_RandomNegPos() / 255.0f);
1133 }
1134
1135 P_SetMobjDirAndSpeed(projectile, angle, slope, projectile->speed);
1136 CheckMissileSpawn(projectile);
1137
1138 return projectile;
1139 }
1140
LaunchProjectile(mobj_t * source,mobj_t * target,const mobjtype_c * type)1141 static mobj_t *LaunchProjectile(mobj_t * source, mobj_t * target,
1142 const mobjtype_c * type)
1143 {
1144 float tx, ty, tz;
1145
1146 if (source->currentattack && (source->currentattack->flags & AF_NoTarget))
1147 target = NULL;
1148
1149 P_TargetTheory(source, target, &tx, &ty, &tz);
1150
1151 return DoLaunchProjectile(source, tx, ty, tz, target, type);
1152 }
1153
1154 //
1155 // LaunchSmartProjectile
1156 //
1157 // This procedure has the same effect as
1158 // LaunchProjectile, but it calculates a point where the target
1159 // and missile will intersect. This comes from the fact that to shoot
1160 // something, you have to aim slightly ahead of it. It will also put
1161 // an end to circle-strafing. :-)
1162 //
1163 // -KM- 1998/10/29
1164 // -KM- 1998/12/16 Fixed it up. Works quite well :-)
1165 //
LaunchSmartProjectile(mobj_t * source,mobj_t * target,const mobjtype_c * type)1166 static void LaunchSmartProjectile(mobj_t * source, mobj_t * target,
1167 const mobjtype_c * type)
1168 {
1169 float t = -1;
1170 float mx = 0, my = 0;
1171
1172 if (target)
1173 {
1174 mx = target->mom.x;
1175 my = target->mom.y;
1176
1177 float dx = source->x - target->x;
1178 float dy = source->y - target->y;
1179
1180 float s = type->speed;
1181 if (level_flags.fastparm)
1182 s *= type->fast;
1183
1184 float a = mx * mx + my * my - s * s;
1185 float b = 2 * (dx * mx + dy * my);
1186 float c = dx * dx + dy * dy;
1187
1188 float t1 = -1, t2 = -1;
1189
1190 // find solution to the quadratic equation
1191 if (a && ((b * b - 4 * a * c) >= 0))
1192 {
1193 t1 = -b + (float)sqrt(b * b - 4.0f * a * c);
1194 t1 /= 2.0f * a;
1195
1196 t2 = -b - (float)sqrt(b * b - 4.0f * a * c);
1197 t2 /= 2.0f * a;
1198 }
1199
1200 if (t1 < 0)
1201 t = t2;
1202 else if (t2 < 0)
1203 t = t1;
1204 else
1205 t = (t1 < t2) ? t1 : t2;
1206 }
1207
1208 if (t <= 0)
1209 {
1210 // -AJA- when no target, fall back to "dumb mode"
1211 LaunchProjectile(source, target, type);
1212 }
1213 else
1214 {
1215 // -AJA- 2005/02/07: assumes target doesn't move up or down
1216
1217 float tx = target->x + mx * t;
1218 float ty = target->y + my * t;
1219 float tz = MO_MIDZ(target);
1220
1221 DoLaunchProjectile(source, tx, ty, tz, target, type);
1222
1223 #if 0 // -AJA- this doesn't seem correct / consistent
1224 if (projectile)
1225 source->angle = projectile->angle;
1226 #endif
1227 }
1228 }
1229
Weakness_CheckHit(mobj_t * target,const atkdef_c * attack,float x,float y,float z)1230 static inline bool Weakness_CheckHit(mobj_t *target,
1231 const atkdef_c *attack, float x, float y, float z)
1232 {
1233 const weakness_info_c *weak = &target->info->weak;
1234
1235 if (weak->classes == BITSET_EMPTY)
1236 return false;
1237
1238 I_Debugf("Weakness_CheckHit: target=[%s] classes=0x%08x\n", target->info->name.c_str(), weak->classes);
1239
1240 if (BITSET_EMPTY != (attack->attack_class & ~weak->classes))
1241 return false;
1242
1243 if (target->height < 1)
1244 return false;
1245
1246 // compute vertical position. Clamping it means that a missile
1247 // which hits the target on the head (coming sharply down) will
1248 // still register as a head-shot.
1249 z = (z - target->z) / target->height;
1250 z = CLAMP(0.01f, z, 0.99f);
1251
1252 I_Debugf("HEIGHT CHECK: %1.2f < %1.2f < %1.2f\n",
1253 weak->height[0], z, weak->height[1]);
1254
1255 if (z < weak->height[0] || z > weak->height[1])
1256 return false;
1257
1258 angle_t ang = R_PointToAngle(target->x, target->y, x, y);
1259
1260 ang -= target->angle;
1261
1262 I_Debugf("ANGLE CHECK: %1.2f < %1.2f < %1.2f\n",
1263 ANG_2_FLOAT(weak->angle[0]), ANG_2_FLOAT(ang),
1264 ANG_2_FLOAT(weak->angle[1]));
1265
1266 if (weak->angle[0] <= weak->angle[1])
1267 {
1268 if (ang < weak->angle[0] || ang > weak->angle[1])
1269 return false;
1270 }
1271 else
1272 {
1273 if (ang < weak->angle[0] && ang > weak->angle[1])
1274 return false;
1275 }
1276
1277 return true;
1278 }
1279
1280
1281 //
1282 // P_MissileContact
1283 //
1284 // Called by PIT_CheckRelThing when a missile comes into
1285 // contact with another object. Placed here with
1286 // the other missile code for cleaner code.
1287 //
1288 // Returns: -1 if missile should pass through.
1289 // 0 if hit but no damage was done.
1290 // +1 if hit and damage was done.
1291 //
P_MissileContact(mobj_t * object,mobj_t * target)1292 int P_MissileContact(mobj_t * object, mobj_t * target)
1293 {
1294 mobj_t *source = object->source;
1295
1296 if (source)
1297 {
1298 // check for ghosts (attack passes through)
1299 if (object->currentattack && BITSET_EMPTY ==
1300 (object->currentattack->attack_class & ~target->info->ghost))
1301 return -1;
1302
1303 if ((target->side & source->side) != 0)
1304 {
1305 if (target->hyperflags & HF_SIDEGHOST)
1306 return -1;
1307
1308 if (target->hyperflags & HF_SIDEIMMUNE)
1309 return 0;
1310 }
1311
1312 if (source->info == target->info)
1313 {
1314 if (!(target->extendedflags & EF_DISLOYALTYPE))
1315 return 0;
1316 }
1317
1318 if (object->currentattack != NULL &&
1319 ! (target->extendedflags & EF_OWNATTACKHURTS))
1320 {
1321 if (object->currentattack == target->info->rangeattack)
1322 return 0;
1323 if (object->currentattack == target->info->closecombat)
1324 return 0;
1325 }
1326 }
1327
1328 const damage_c *damtype;
1329
1330 // transitional hack
1331 if (object->currentattack)
1332 damtype = &object->currentattack->damage;
1333 else
1334 damtype = &object->info->explode_damage;
1335
1336 float damage;
1337 DAMAGE_COMPUTE(damage, damtype);
1338
1339 bool weak_spot = false;
1340
1341 // check for Weakness against the attack
1342 if (Weakness_CheckHit(target, object->currentattack,
1343 object->x, object->y, MO_MIDZ(object)))
1344 {
1345 damage *= target->info->weak.multiply;
1346 weak_spot = true;
1347 }
1348
1349 // check for immunity against the attack
1350 if (object->hyperflags & HF_INVULNERABLE)
1351 return 0;
1352
1353 if (!weak_spot && object->currentattack && BITSET_EMPTY ==
1354 (object->currentattack->attack_class & ~target->info->immunity))
1355 {
1356 return 0;
1357 }
1358
1359 // support for "tunnelling" missiles, which should only do damage at
1360 // the first impact.
1361 if (object->extendedflags & EF_TUNNEL)
1362 {
1363 // this hash is very basic, but should work OK
1364 u32_t hash = (u32_t)(long)target;
1365
1366 if (object->tunnel_hash[0] == hash || object->tunnel_hash[1] == hash)
1367 return -1;
1368
1369 object->tunnel_hash[0] = object->tunnel_hash[1];
1370 object->tunnel_hash[1] = hash;
1371 }
1372
1373 // Berserk handling
1374 if (object->player && object->currentattack &&
1375 object->player->powers[PW_Berserk] != 0.0f)
1376 {
1377 damage *= object->currentattack->berserk_mul;
1378 }
1379
1380 if (!damage)
1381 {
1382 #ifdef DEVELOPERS
1383 L_WriteDebug("%s missile did zero damage.\n",
1384 object->info->name.c_str());
1385 #endif
1386 return 0;
1387 }
1388
1389 P_DamageMobj(target, object, object->source, damage, damtype, weak_spot);
1390 return 1;
1391 }
1392
1393 //
1394 // P_BulletContact
1395 //
1396 // Called by PTR_ShootTraverse when a bullet comes into contact with
1397 // another object. Needed so that the "DISLOYAL" special will behave
1398 // in the same manner for bullets as for missiles.
1399 //
1400 // Note: also used for Close-Combat attacks.
1401 //
1402 // Returns: -1 if bullet should pass through.
1403 // 0 if hit but no damage was done.
1404 // +1 if hit and damage was done.
1405 //
P_BulletContact(mobj_t * source,mobj_t * target,float damage,const damage_c * damtype,float x,float y,float z)1406 int P_BulletContact(mobj_t * source, mobj_t * target,
1407 float damage, const damage_c *damtype,
1408 float x, float y, float z)
1409 {
1410 // check for ghosts (attack passes through)
1411 if (source->currentattack && BITSET_EMPTY ==
1412 (source->currentattack->attack_class & ~target->info->ghost))
1413 return -1;
1414
1415 if ((target->side & source->side) != 0)
1416 {
1417 if (target->hyperflags & HF_SIDEGHOST)
1418 return -1;
1419
1420 if (target->hyperflags & HF_SIDEIMMUNE)
1421 return 0;
1422 }
1423
1424 if (source->info == target->info)
1425 {
1426 if (! (target->extendedflags & EF_DISLOYALTYPE))
1427 return 0;
1428 }
1429
1430 if (source->currentattack != NULL &&
1431 !(target->extendedflags & EF_OWNATTACKHURTS))
1432 {
1433 if (source->currentattack == target->info->rangeattack)
1434 return 0;
1435 if (source->currentattack == target->info->closecombat)
1436 return 0;
1437 }
1438
1439 // ignore damage in GOD mode, or with INVUL powerup
1440 if (target->player)
1441 {
1442 if ((target->player->cheats & CF_GODMODE) ||
1443 target->player->powers[PW_Invulnerable] > 0)
1444 {
1445 // emulate the thrust that P_DamageMobj() would have done
1446 if (source && damage > 0 && !(target->flags & MF_NOCLIP))
1447 P_ThrustMobj(target, source, damage);
1448
1449 return 0;
1450 }
1451 }
1452
1453 bool weak_spot = false;
1454
1455 // check for Weakness against the attack
1456 if (Weakness_CheckHit(target, source->currentattack, x, y, z))
1457 {
1458 damage *= target->info->weak.multiply;
1459 weak_spot = true;
1460 }
1461
1462 // check for immunity against the attack
1463 if (target->hyperflags & HF_INVULNERABLE)
1464 return 0;
1465
1466 if (!weak_spot && source->currentattack && BITSET_EMPTY ==
1467 (source->currentattack->attack_class & ~target->info->immunity))
1468 {
1469 return 0;
1470 }
1471
1472 if (!damage)
1473 {
1474 #ifdef DEVELOPERS
1475 L_WriteDebug("%s's shoot/combat attack did zero damage.\n",
1476 source->info->name.c_str());
1477 #endif
1478 return 0;
1479 }
1480
1481 P_DamageMobj(target, source, source, damage, damtype, weak_spot);
1482 return 1;
1483 }
1484
1485 //
1486 // P_ActCreateSmokeTrail
1487 //
1488 // Just spawns smoke behind an mobj: the smoke is
1489 // risen by giving it z momentum, in order to
1490 // prevent the smoke appearing uniform (which obviously
1491 // does not happen), the number of tics that the smoke
1492 // mobj has is "randomly" reduced, although the number
1493 // of tics never gets to zero or below.
1494 //
1495 // -ACB- 1998/08/10 Written
1496 // -ACB- 1999/10/01 Check thing's current attack has a smoke projectile
1497 //
P_ActCreateSmokeTrail(mobj_t * projectile)1498 void P_ActCreateSmokeTrail(mobj_t * projectile)
1499 {
1500 const atkdef_c *attack = projectile->currentattack;
1501
1502 if (attack == NULL)
1503 return;
1504
1505 if (attack->puff == NULL)
1506 {
1507 M_WarnError("P_ActCreateSmokeTrail: attack %s has no PUFF object\n",
1508 attack->name.c_str());
1509 return;
1510 }
1511
1512 // spawn a puff of smoke behind the rocket
1513 mobj_t *smoke = P_MobjCreateObject(
1514 projectile->x - projectile->mom.x / 2.0f,
1515 projectile->y - projectile->mom.y / 2.0f,
1516 projectile->z, attack->puff);
1517
1518 smoke->mom.z = smoke->info->float_speed;
1519 smoke->tics -= M_Random() & 3;
1520
1521 if (smoke->tics < 1)
1522 smoke->tics = 1;
1523 }
1524
1525 //
1526 // P_ActHomingProjectile
1527 //
1528 // This projectile will alter its course to intercept its
1529 // target, if is possible for this procedure to be called
1530 // and nothing results because of a chance that the
1531 // projectile will not chase its target.
1532 //
1533 // As this code is based on the revenant tracer, it did use
1534 // a bit check on the current gametic - which was why every so
1535 // often a revenant fires a missile straight and not one that
1536 // homes in on its target: If the gametic has bits 1+2 on
1537 // (which boils down to 1 in every 4 tics), the trick in this
1538 // is that - in conjuntion with the tic count for the
1539 // tracing object's states - the tracing will always fail or
1540 // pass the check: if it passes first time, it will always
1541 // pass and vice versa. The problem with this was two fold:
1542 // demos will go out of sync if the starting gametic if different
1543 // from when the demo was recorded (which admittly is easily
1544 // fixable), the second is that for someone designing a new
1545 // tracing projectile it would be more than a bit confusing to
1546 // joe "dooming" public.
1547 //
1548 // The new system that affects the original gameplay slightly is
1549 // to get a random chance of the projectile not homing in on its
1550 // target and working this out first time round, the test result
1551 // is recorded (by clearing the 'target' field).
1552 //
1553 // -ACB- 1998/08/10
1554 //
P_ActHomingProjectile(mobj_t * projectile)1555 void P_ActHomingProjectile(mobj_t * projectile)
1556 {
1557 const atkdef_c *attack = projectile->currentattack;
1558
1559 if (attack == NULL)
1560 return;
1561
1562 if (attack->flags & AF_TraceSmoke)
1563 P_ActCreateSmokeTrail(projectile);
1564
1565 if (projectile->extendedflags & EF_FIRSTCHECK)
1566 {
1567 projectile->extendedflags &= ~EF_FIRSTCHECK;
1568
1569 if (P_RandomTest(attack->notracechance))
1570 {
1571 projectile->SetTarget(NULL);
1572 return;
1573 }
1574 }
1575
1576 mobj_t *destination = projectile->target;
1577
1578 if (!destination || destination->health <= 0)
1579 return;
1580
1581 // change angle
1582 angle_t exact = R_PointToAngle(projectile->x, projectile->y,
1583 destination->x, destination->y);
1584
1585 if (exact != projectile->angle)
1586 {
1587 if (exact - projectile->angle > ANG180)
1588 {
1589 projectile->angle -= attack->trace_angle;
1590
1591 if (exact - projectile->angle < ANG180)
1592 projectile->angle = exact;
1593 }
1594 else
1595 {
1596 projectile->angle += attack->trace_angle;
1597
1598 if (exact - projectile->angle > ANG180)
1599 projectile->angle = exact;
1600 }
1601 }
1602
1603 projectile->mom.x = projectile->speed * M_Cos(projectile->angle);
1604 projectile->mom.y = projectile->speed * M_Sin(projectile->angle);
1605
1606 // change slope
1607 float slope = P_ApproxSlope(destination->x - projectile->x,
1608 destination->y - projectile->y,
1609 MO_MIDZ(destination) - projectile->z);
1610
1611 slope *= projectile->speed;
1612
1613 if (slope < projectile->mom.z)
1614 projectile->mom.z -= 0.125f;
1615 else
1616 projectile->mom.z += 0.125f;
1617 }
1618
1619 //
1620 // P_ActHomeToSpot
1621 //
1622 // This projectile will alter its course to intercept its target,
1623 // or explode if it has reached it. Used by the bossbrain cube.
1624 //
P_ActHomeToSpot(mobj_t * projectile)1625 void P_ActHomeToSpot(mobj_t * projectile)
1626 {
1627 mobj_t *target = projectile->target;
1628
1629 if (!target)
1630 {
1631 P_MobjExplodeMissile(projectile);
1632 return;
1633 }
1634
1635 float dx = target->x - projectile->x;
1636 float dy = target->y - projectile->y;
1637 float dz = target->z - projectile->z;
1638
1639 float ck_radius = target->radius + projectile->radius + 2;
1640 float ck_height = target->height + projectile->height + 2;
1641
1642 // reached target ?
1643 if (fabs(dx) <= ck_radius && fabs(dy) <= ck_radius && fabs(dz) <= ck_height)
1644 {
1645 P_MobjExplodeMissile(projectile);
1646 return;
1647 }
1648
1649 // calculate new angles
1650 angle_t angle = R_PointToAngle(0, 0, dx, dy);
1651 float slope = P_ApproxSlope(dx, dy, dz);
1652
1653 P_SetMobjDirAndSpeed(projectile, angle, slope, projectile->speed);
1654 }
1655
1656 //
1657 // LaunchOrderedSpread
1658 //
1659 // Due to the unique way of handling that the mancubus fires, it is necessary
1660 // to write a single procedure to handle the firing. In real terms it amounts
1661 // to a glorified hack; The table holds the angle modifier and the choice of
1662 // whether the firing object or the projectile is affected. This procedure
1663 // should NOT be used for players as it will alter the player's mobj, bypassing
1664 // the normal player controls; The only reason for its existance is to maintain
1665 // the original mancubus behaviour. Although it is possible to make this generic,
1666 // the benefits of doing so are minimal. Purist function....
1667 //
1668 // -ACB- 1998/08/15
1669 //
LaunchOrderedSpread(mobj_t * mo)1670 static void LaunchOrderedSpread(mobj_t * mo)
1671 {
1672 // left side = angle modifier
1673 // right side = object or projectile (true for object).
1674 static int spreadorder[] =
1675 {
1676 (ANG90 / 8), true,
1677 (ANG90 / 8), false,
1678 -(ANG90 / 8), true,
1679 -(ANG90 / 4), false,
1680 -(ANG90 / 16), false,
1681 (ANG90 / 16), false
1682 };
1683
1684 const atkdef_c *attack = mo->currentattack;
1685
1686 if (attack == NULL)
1687 return;
1688
1689 int count = mo->spreadcount;
1690
1691 if (count < 0 || count > 12)
1692 count = mo->spreadcount = 0;
1693
1694 // object or projectile?
1695 // true --> the object, false --> the projectile.
1696 if (spreadorder[count + 1])
1697 {
1698 mo->angle += spreadorder[count];
1699
1700 LaunchProjectile(mo, mo->target, attack->atk_mobj);
1701 }
1702 else
1703 {
1704 mobj_t *projectile = LaunchProjectile(mo, mo->target,
1705 attack->atk_mobj);
1706 if (projectile == NULL)
1707 return;
1708
1709 projectile->angle += spreadorder[count];
1710
1711 projectile->mom.x = projectile->speed * M_Cos(projectile->angle);
1712 projectile->mom.y = projectile->speed * M_Sin(projectile->angle);
1713 }
1714
1715 mo->spreadcount += 2;
1716 }
1717
1718 //
1719 // LaunchRandomSpread
1720 //
1721 // This is a the generic function that should be used for a spreader like
1722 // mancubus, although its random nature would certainly be a change to the
1723 // ordered method used now. The random number is bit shifted to the right
1724 // and then the ANG90 is divided by it, the first bit of the RN is checked
1725 // to detemine if the angle is change is negative or not (approx 50% chance).
1726 // The result is the modifier for the projectile's angle.
1727 //
1728 // -ACB- 1998/08/15
1729 //
LaunchRandomSpread(mobj_t * mo)1730 static void LaunchRandomSpread(mobj_t * mo)
1731 {
1732 if (mo->currentattack == NULL)
1733 return;
1734
1735 mobj_t *projectile = LaunchProjectile(mo, mo->target,
1736 mo->currentattack->atk_mobj);
1737 if (projectile == NULL)
1738 return;
1739
1740 int i = P_Random() & 127;
1741
1742 if (i >> 1)
1743 {
1744 angle_t spreadangle = (ANG90 / (i >> 1));
1745
1746 if (i & 1)
1747 spreadangle -= spreadangle << 1;
1748
1749 projectile->angle += spreadangle;
1750 }
1751
1752 projectile->mom.x = projectile->speed * M_Cos(projectile->angle);
1753 projectile->mom.y = projectile->speed * M_Sin(projectile->angle);
1754 }
1755
1756 //-------------------------------------------------------------------
1757 //-------------------LINEATTACK ATTACK ROUTINES-----------------------
1758 //-------------------------------------------------------------------
1759
1760 // -KM- 1998/11/25 Added uncertainty to the z component of the line.
ShotAttack(mobj_t * mo)1761 static void ShotAttack(mobj_t * mo)
1762 {
1763 const atkdef_c *attack = mo->currentattack;
1764
1765 if (! attack)
1766 return;
1767
1768 float range = (attack->range > 0) ? attack->range : MISSILERANGE;
1769
1770 // -ACB- 1998/09/05 Remember to use the object angle, fool!
1771 angle_t objangle = mo->angle;
1772 float objslope;
1773
1774 if ((mo->player && !mo->target) || (attack->flags & AF_NoTarget))
1775 objslope = M_Tan(mo->vertangle);
1776 else
1777 P_AimLineAttack(mo, objangle, range, &objslope);
1778
1779 if (attack->sound)
1780 S_StartFX(attack->sound, AttackSfxCat(mo), mo);
1781
1782 // -AJA- 1999/09/10: apply the attack's angle offsets.
1783 objangle -= attack->angle_offset;
1784 objslope += attack->slope_offset;
1785
1786 for (int i = 0; i < attack->count; i++)
1787 {
1788 angle_t angle = objangle;
1789 float slope = objslope;
1790
1791 // is the attack not accurate?
1792 if (!mo->player || mo->player->refire > 0)
1793 {
1794 if (attack->accuracy_angle > 0)
1795 angle += (attack->accuracy_angle >> 8) * P_RandomNegPos();
1796
1797 if (attack->accuracy_slope > 0)
1798 slope += attack->accuracy_slope * (P_RandomNegPos() / 255.0f);
1799 }
1800
1801 float damage;
1802 DAMAGE_COMPUTE(damage, &attack->damage);
1803
1804 if (mo->player && mo->player->powers[PW_Berserk] != 0.0f)
1805 damage *= attack->berserk_mul;
1806
1807 P_LineAttack(mo, angle, range, slope, damage,
1808 &attack->damage, attack->puff);
1809 }
1810 }
1811
1812 // -KM- 1998/11/25 BFG Spray attack. Must be used from missiles.
1813 // Will do a BFG spray on every monster in sight.
SprayAttack(mobj_t * mo)1814 static void SprayAttack(mobj_t * mo)
1815 {
1816 const atkdef_c *attack = mo->currentattack;
1817
1818 if (! attack)
1819 return;
1820
1821 float range = (attack->range > 0) ? attack->range : MISSILERANGE;
1822
1823 // offset angles from its attack angle
1824 for (int i = 0; i < 40; i++)
1825 {
1826 angle_t an = mo->angle - ANG90 / 2 + (ANG90 / 40) * i;
1827
1828 // mo->source is the originator (player) of the missile
1829 mobj_t *target = P_AimLineAttack(mo->source ? mo->source : mo, an, range, NULL);
1830
1831 if (! target)
1832 continue;
1833
1834 mobj_t *ball = P_MobjCreateObject(target->x, target->y,
1835 target->z + target->height / 4,
1836 attack->atk_mobj);
1837
1838 ball->SetTarget(mo->target);
1839
1840 // check for immunity against the attack
1841 if (target->hyperflags & HF_INVULNERABLE)
1842 continue;
1843
1844 if (BITSET_EMPTY == (attack->attack_class & ~target->info->immunity))
1845 continue;
1846
1847 float damage;
1848 DAMAGE_COMPUTE(damage, &attack->damage);
1849
1850 if (mo->player && mo->player->powers[PW_Berserk] != 0.0f)
1851 damage *= attack->berserk_mul;
1852
1853 if (damage)
1854 P_DamageMobj(target, NULL, mo->source, damage, &attack->damage);
1855 }
1856 }
1857
DoMeleeAttack(mobj_t * mo)1858 static void DoMeleeAttack(mobj_t * mo)
1859 {
1860 const atkdef_c *attack = mo->currentattack;
1861
1862 float range = (attack->range > 0) ? attack->range : MISSILERANGE;
1863
1864 float damage;
1865 DAMAGE_COMPUTE(damage, &attack->damage);
1866
1867 // -KM- 1998/11/25 Berserk ability
1868 // -ACB- 2004/02/04 Only zero is off
1869 if (mo->player && mo->player->powers[PW_Berserk] != 0.0f)
1870 damage *= attack->berserk_mul;
1871
1872 // -KM- 1998/12/21 Use Line attack so bullet puffs are spawned.
1873
1874 if (! DecideMeleeAttack(mo, attack))
1875 {
1876 P_LineAttack(mo, mo->angle, range, M_Tan(mo->vertangle),
1877 damage, &attack->damage, attack->puff);
1878 return;
1879 }
1880
1881 if (attack->sound)
1882 S_StartFX(attack->sound, AttackSfxCat(mo), mo);
1883
1884 float slope;
1885
1886 P_AimLineAttack(mo, mo->angle, range, &slope);
1887
1888 P_LineAttack(mo, mo->angle, range, slope, damage,
1889 &attack->damage, attack->puff);
1890 }
1891
1892 //-------------------------------------------------------------------
1893 //--------------------TRACKER HANDLING ROUTINES----------------------
1894 //-------------------------------------------------------------------
1895
1896 //
1897 // A Tracker is an object that follows its target, by being on top of
1898 // it. This is the attack style used by an Arch-Vile. The first routines
1899 // handle the tracker itself, the last two are called by the source of
1900 // the tracker.
1901 //
1902
1903 //
1904 // P_ActTrackerFollow
1905 //
1906 // Called by the tracker to follow its target.
1907 //
1908 // -ACB- 1998/08/22
1909 //
P_ActTrackerFollow(mobj_t * tracker)1910 void P_ActTrackerFollow(mobj_t * tracker)
1911 {
1912 mobj_t *destination = tracker->target;
1913
1914 if (!destination || !tracker->source)
1915 return;
1916
1917 // Can the parent of the tracker see the target?
1918 if (!P_CheckSight(tracker->source, destination))
1919 return;
1920
1921 angle_t angle = destination->angle;
1922
1923 P_ChangeThingPosition(tracker,
1924 destination->x + 24 * M_Cos(angle),
1925 destination->y + 24 * M_Sin(angle),
1926 destination->z);
1927 }
1928
1929 //
1930 // P_ActTrackerActive
1931 //
1932 // Called by the tracker to make its active sound: also tracks
1933 //
1934 // -ACB- 1998/08/22
1935 //
P_ActTrackerActive(mobj_t * tracker)1936 void P_ActTrackerActive(mobj_t * tracker)
1937 {
1938 if (tracker->info->activesound)
1939 S_StartFX(tracker->info->activesound, P_MobjGetSfxCategory(tracker), tracker);
1940
1941 P_ActTrackerFollow(tracker);
1942 }
1943
1944 //
1945 // P_ActTrackerStart
1946 //
1947 // Called by the tracker to make its launch (see) sound: also tracks
1948 //
1949 // -ACB- 1998/08/22
1950 //
P_ActTrackerStart(mobj_t * tracker)1951 void P_ActTrackerStart(mobj_t * tracker)
1952 {
1953 if (tracker->info->seesound)
1954 S_StartFX(tracker->info->seesound, P_MobjGetSfxCategory(tracker), tracker);
1955
1956 P_ActTrackerFollow(tracker);
1957 }
1958
1959 //
1960 // LaunchTracker
1961 //
1962 // This procedure starts a tracking object off and links
1963 // the tracker and the monster together.
1964 //
1965 // -ACB- 1998/08/22
1966 //
LaunchTracker(mobj_t * object)1967 static void LaunchTracker(mobj_t * object)
1968 {
1969 const atkdef_c *attack = object->currentattack;
1970 mobj_t *target = object->target;
1971
1972 if (!attack || !target)
1973 return;
1974
1975 mobj_t *tracker = P_MobjCreateObject(target->x, target->y, target->z,
1976 attack->atk_mobj);
1977
1978 // link the tracker to the object
1979 object->SetTracer(tracker);
1980
1981 // tracker source is the object
1982 tracker->SetRealSource(object);
1983
1984 // tracker's target is the object's target
1985 tracker->SetTarget(target);
1986
1987 P_ActTrackerFollow(tracker);
1988 }
1989
1990 //
1991 // P_ActEffectTracker
1992 //
1993 // Called by the object that launched the tracker to
1994 // cause damage to its target and a radius attack
1995 // (explosion) at the location of the tracker.
1996 //
1997 // -ACB- 1998/08/22
1998 //
P_ActEffectTracker(mobj_t * object)1999 void P_ActEffectTracker(mobj_t * object)
2000 {
2001 mobj_t *tracker;
2002 mobj_t *target;
2003 const atkdef_c *attack;
2004 angle_t angle;
2005 float damage;
2006
2007 if (!object->target || !object->currentattack)
2008 return;
2009
2010 attack = object->currentattack;
2011 target = object->target;
2012
2013 if (attack->flags & AF_FaceTarget)
2014 P_ActFaceTarget(object);
2015
2016 if (attack->flags & AF_NeedSight)
2017 {
2018 if (!P_CheckSight(object, target))
2019 return;
2020 }
2021
2022 if (attack->sound)
2023 S_StartFX(attack->sound, P_MobjGetSfxCategory(object), object);
2024
2025 angle = object->angle;
2026 tracker = object->tracer;
2027
2028 DAMAGE_COMPUTE(damage, &attack->damage);
2029
2030 if (damage)
2031 P_DamageMobj(target, object, object, damage, &attack->damage);
2032 #ifdef DEVELOPERS
2033 else
2034 L_WriteDebug("%s + %s attack has zero damage\n",
2035 object->info->name.c_str(),
2036 tracker->info->name.c_str());
2037 #endif
2038
2039 // -ACB- 2000/03/11 Check for zero mass
2040 if (target->info->mass)
2041 target->mom.z = 1000 / target->info->mass;
2042 else
2043 target->mom.z = 2000;
2044
2045 if (!tracker)
2046 return;
2047
2048 // move the tracker between the object and the object's target
2049
2050 P_ChangeThingPosition(tracker,
2051 target->x - 24 * M_Cos(angle),
2052 target->y - 24 * M_Sin(angle),
2053 target->z);
2054
2055 #ifdef DEVELOPERS
2056 if (!tracker->info->explode_damage.nominal)
2057 L_WriteDebug("%s + %s explosion has zero damage\n",
2058 object->info->name.c_str(),
2059 tracker->info->name.c_str());
2060 #endif
2061
2062 DAMAGE_COMPUTE(damage, &tracker->info->explode_damage);
2063
2064 float radius = object->info->explode_radius;
2065 if (radius == 0) radius = damage;
2066
2067 P_RadiusAttack(tracker, object, radius, damage,
2068 &tracker->info->explode_damage, false);
2069 }
2070
2071 //-----------------------------------------------------------------
2072 //--------------------BOSS HANDLING PROCEDURES---------------------
2073 //-----------------------------------------------------------------
2074
ShootToSpot(mobj_t * object)2075 static void ShootToSpot(mobj_t * object)
2076 {
2077 // Note: using a static int here for better randomness.
2078 // -AJA- FIXME: should be global to allow synchronisation in Netgames
2079 static int current_spot = 0;
2080
2081 if (! object->currentattack)
2082 return;
2083
2084 if (brain_spots.number < 0)
2085 {
2086 if (! object->info->spitspot)
2087 {
2088 M_WarnError("Thing [%s] used SHOOT_TO_SPOT attack, but has no "
2089 "SPIT_SPOT\n", object->info->name.c_str());
2090 return;
2091 }
2092
2093 P_LookForShootSpots(object->info->spitspot);
2094 }
2095
2096 if (brain_spots.number == 0)
2097 return;
2098
2099 SYS_ASSERT(brain_spots.targets);
2100 SYS_ASSERT(brain_spots.number > 0);
2101
2102 current_spot += P_Random();
2103 current_spot %= brain_spots.number;
2104
2105 LaunchProjectile(object, brain_spots.targets[current_spot],
2106 object->currentattack->atk_mobj);
2107 }
2108
2109 //-------------------------------------------------------------------
2110 //-------------------OBJECT-SPAWN-OBJECT HANDLING--------------------
2111 //-------------------------------------------------------------------
2112
2113 //
2114 // P_ActObjectSpawning
2115 //
2116 // An Object spawns another object and is spawned in the state specificed
2117 // by attack->objinitstate. The procedure is based on the A_PainShootSkull
2118 // which is the routine for shooting skulls from a pain elemental. In
2119 // this the object being created is decided in the attack. This
2120 // procedure also used the new blocking line check to see if
2121 // the object is spawned across a blocking line, if so the procedure
2122 // terminates.
2123 //
2124 // -ACB- 1998/08/23
2125 //
ObjectSpawning(mobj_t * parent,angle_t angle)2126 static void ObjectSpawning(mobj_t * parent, angle_t angle)
2127 {
2128 float slope;
2129 const atkdef_c *attack;
2130
2131 attack = parent->currentattack;
2132 if (! attack)
2133 return;
2134
2135 const mobjtype_c *shoottype = attack->spawnedobj;
2136
2137 if (! shoottype)
2138 {
2139 I_Error("Object [%s] uses spawning attack [%s], but no object "
2140 "specified.\n",
2141 parent->info->name.c_str(),
2142 attack->name.c_str());
2143 }
2144
2145 if (attack->spawn_limit > 0)
2146 {
2147 int count = 0;
2148 for (mobj_t *mo = mobjlisthead; mo; mo = mo->next)
2149 if (mo->info == shoottype)
2150 if (++count >= attack->spawn_limit)
2151 return;
2152 }
2153
2154 // -AJA- 1999/09/10: apply the angle offset of the attack.
2155 angle -= attack->angle_offset;
2156 slope = M_Tan(parent->vertangle) + attack->slope_offset;
2157
2158 float spawnx = parent->x;
2159 float spawny = parent->y;
2160 float spawnz = parent->z + attack->height;
2161
2162 if (attack->flags & AF_PrestepSpawn)
2163 {
2164 float prestep = 4.0f + 1.5f * parent->radius + shoottype->radius;
2165
2166 spawnx += prestep * M_Cos(angle);
2167 spawny += prestep * M_Sin(angle);
2168 }
2169
2170 mobj_t *child = P_MobjCreateObject(spawnx, spawny, spawnz, shoottype);
2171
2172 // Blocking line detected between object and spawnpoint?
2173 if (P_MapCheckBlockingLine(parent, child))
2174 {
2175 // -KM- 1999/01/31 Explode objects over remove them.
2176 // -AJA- 2000/02/01: Remove now the default.
2177 if (attack->flags & AF_KillFailedSpawn)
2178 P_KillMobj(parent, child, NULL);
2179 else
2180 P_RemoveMobj(child);
2181
2182 return;
2183 }
2184
2185 if (attack->sound)
2186 S_StartFX(attack->sound, AttackSfxCat(parent), parent);
2187
2188 // If the object cannot move from its position, remove it or kill it.
2189 if (!P_TryMove(child, child->x, child->y))
2190 {
2191 if (attack->flags & AF_KillFailedSpawn)
2192 P_KillMobj(parent, child, NULL);
2193 else
2194 P_RemoveMobj(child);
2195
2196 return;
2197 }
2198
2199 if (! (attack->flags & AF_NoTarget))
2200 child->SetTarget(parent->target);
2201
2202 child->SetSupportObj(parent);
2203
2204 child->side = parent->side;
2205
2206 // -AJA- 2004/09/27: keep ambush status of parent
2207 child->flags |= (parent->flags & MF_AMBUSH);
2208
2209 // -AJA- 1999/09/25: Set the initial direction & momentum when
2210 // the ANGLED_SPAWN attack special is used.
2211 if (attack->flags & AF_AngledSpawn)
2212 P_SetMobjDirAndSpeed(child, angle, slope, attack->assault_speed);
2213
2214 P_SetMobjStateDeferred(child, attack->objinitstate, 0);
2215 }
2216
2217 //
2218 // P_ActObjectTripleSpawn
2219 //
2220 // Spawns three objects at 90, 180 and 270 degrees. This is essentially
2221 // another purist function to support the death sequence of the Pain
2222 // elemental. However it could be used as in conjunction with radius
2223 // triggers to generate a nice teleport spawn invasion.
2224 //
2225 // -ACB- 1998/08/23 (I think....)
2226 //
2227
ObjectTripleSpawn(mobj_t * object)2228 static void ObjectTripleSpawn(mobj_t * object)
2229 {
2230 ObjectSpawning(object, object->angle + ANG90);
2231 ObjectSpawning(object, object->angle + ANG180);
2232 ObjectSpawning(object, object->angle + ANG270);
2233 }
2234
2235 //-------------------------------------------------------------------
2236 //-------------------SKULLFLY HANDLING ROUTINES----------------------
2237 //-------------------------------------------------------------------
2238
2239 //
2240 // SkullFlyAssault
2241 //
2242 // This is the attack procedure for objects that launch themselves
2243 // at their target like a missile.
2244 //
2245 // -ACB- 1998/08/16
2246 //
SkullFlyAssault(mobj_t * object)2247 static void SkullFlyAssault(mobj_t * object)
2248 {
2249 if (!object->currentattack)
2250 return;
2251
2252 if (!object->target && !object->player)
2253 {
2254 // -AJA- 2000/09/29: fix for the zombie lost soul bug
2255 // -AJA- 2000/10/22: monsters only ! Don't stuff up gibs/missiles.
2256 if (object->extendedflags & EF_MONSTER)
2257 object->flags |= MF_SKULLFLY;
2258 return;
2259 }
2260
2261 float speed = object->currentattack->assault_speed;
2262
2263 // -KM- 1999/01/31 Fix skulls in nightmare mode
2264 if (level_flags.fastparm)
2265 speed *= object->info->fast;
2266
2267 sfx_t *sound = object->currentattack->initsound;
2268
2269 if (sound)
2270 S_StartFX(sound, P_MobjGetSfxCategory(object), object);
2271
2272 object->flags |= MF_SKULLFLY;
2273
2274 // determine destination
2275 float tx, ty, tz;
2276
2277 P_TargetTheory(object, object->target, &tx, &ty, &tz);
2278
2279 float slope = P_ApproxSlope(tx - object->x, ty - object->y, tz - object->z);
2280
2281 P_SetMobjDirAndSpeed(object, object->angle, slope, speed);
2282 }
2283
2284 //
2285 // P_SlammedIntoObject
2286 //
2287 // Used when a flying object hammers into another object when on the
2288 // attack. Replaces the code in PIT_Checkthing.
2289 //
2290 // -ACB- 1998/07/29: Written
2291 //
2292 // -AJA- 1999/09/12: Now uses P_SetMobjStateDeferred, since this
2293 // routine can be called by TryMove/PIT_CheckRelThing.
2294 //
P_SlammedIntoObject(mobj_t * object,mobj_t * target)2295 void P_SlammedIntoObject(mobj_t * object, mobj_t * target)
2296 {
2297 if (object->currentattack)
2298 {
2299 if (target != NULL)
2300 {
2301 // -KM- 1999/01/31 Only hurt shootable objects...
2302 if (target->flags & MF_SHOOTABLE)
2303 {
2304 float damage;
2305
2306 DAMAGE_COMPUTE(damage, &object->currentattack->damage);
2307
2308 P_DamageMobj(target, object, object, damage,
2309 &object->currentattack->damage);
2310 }
2311 }
2312
2313 sfx_t *sound = object->currentattack->sound;
2314 if (sound)
2315 S_StartFX(sound, P_MobjGetSfxCategory(object), object);
2316 }
2317
2318 object->flags &= ~MF_SKULLFLY;
2319 object->mom.x = object->mom.y = object->mom.z = 0;
2320
2321 P_SetMobjStateDeferred(object, object->info->idle_state, 0);
2322 }
2323
2324
P_UseThing(mobj_t * user,mobj_t * thing,float open_bottom,float open_top)2325 bool P_UseThing(mobj_t * user, mobj_t * thing, float open_bottom,
2326 float open_top)
2327 {
2328 // Called when this thing is attempted to be used (i.e. by pressing
2329 // the spacebar near it) by the player. Returns true if successfully
2330 // used, or false if other things should be checked.
2331
2332 // item is disarmed ?
2333 if (!(thing->flags & MF_TOUCHY))
2334 return false;
2335
2336 // can be reached ?
2337 open_top = MIN(open_top, thing->z + thing->height);
2338 open_bottom = MAX(open_bottom, thing->z);
2339
2340 if (user->z >= open_top ||
2341 (user->z + user->height + USE_Z_RANGE < open_bottom))
2342 return false;
2343
2344 // OK, disarm and put into touch states
2345 SYS_ASSERT(thing->info->touch_state > 0);
2346
2347 thing->flags &= ~MF_TOUCHY;
2348 P_SetMobjStateDeferred(thing, thing->info->touch_state, 0);
2349
2350 return true;
2351 }
2352
2353
P_TouchyContact(mobj_t * touchy,mobj_t * victim)2354 void P_TouchyContact(mobj_t *touchy, mobj_t *victim)
2355 {
2356 // Used whenever a thing comes into contact with a TOUCHY object.
2357 //
2358 // -AJA- 1999/09/12: Now uses P_SetMobjStateDeferred, since this
2359 // routine can be called by TryMove/PIT_CheckRelThing.
2360
2361 // dead thing touching. Can happen with a sliding player corpse.
2362 if (victim->health <= 0)
2363 return;
2364
2365 // don't harm the grenadier...
2366 if (touchy->source == victim)
2367 return;
2368
2369 touchy->SetTarget(victim);
2370 touchy->flags &= ~MF_TOUCHY; // disarm
2371
2372 if (touchy->info->touch_state)
2373 P_SetMobjStateDeferred(touchy, touchy->info->touch_state, 0);
2374 else
2375 P_MobjExplodeMissile(touchy);
2376 }
2377
2378
P_ActTouchyRearm(mobj_t * touchy)2379 void P_ActTouchyRearm(mobj_t * touchy)
2380 {
2381 touchy->flags |= MF_TOUCHY;
2382 }
2383
P_ActTouchyDisarm(mobj_t * touchy)2384 void P_ActTouchyDisarm(mobj_t * touchy)
2385 {
2386 touchy->flags &= ~MF_TOUCHY;
2387 }
2388
2389
P_ActBounceRearm(mobj_t * mo)2390 void P_ActBounceRearm(mobj_t * mo)
2391 {
2392 mo->extendedflags &= ~EF_JUSTBOUNCED;
2393 }
2394
P_ActBounceDisarm(mobj_t * mo)2395 void P_ActBounceDisarm(mobj_t * mo)
2396 {
2397 mo->extendedflags |= EF_JUSTBOUNCED;
2398 }
2399
2400
P_ActDropItem(mobj_t * mo)2401 void P_ActDropItem(mobj_t * mo)
2402 {
2403 const mobjtype_c *info = mo->info->dropitem;
2404
2405 if (mo->state && mo->state->action_par)
2406 {
2407 mobj_strref_c *ref = (mobj_strref_c *) mo->state->action_par;
2408
2409 info = ref->GetRef();
2410 }
2411
2412 if (! info)
2413 {
2414 M_WarnError("P_ActDropItem: %s specifies no item to drop.\n",
2415 mo->info->name.c_str());
2416 return;
2417 }
2418
2419 // unlike normal drops, these ones are displaced randomly
2420
2421 float dx = P_RandomNegPos() * mo->info->radius / 255.0f;
2422 float dy = P_RandomNegPos() * mo->info->radius / 255.0f;
2423
2424 mobj_t *item = P_MobjCreateObject(mo->x + dx, mo->y + dy, mo->floorz, info);
2425 SYS_ASSERT(item);
2426
2427 item->flags |= MF_DROPPED;
2428 item->flags &= ~MF_SOLID;
2429
2430 item->angle = mo->angle;
2431
2432 // allow respawning
2433 item->spawnpoint.x = item->x;
2434 item->spawnpoint.y = item->y;
2435 item->spawnpoint.z = item->z;
2436 item->spawnpoint.angle = item->angle;
2437 item->spawnpoint.vertangle = item->vertangle;
2438 item->spawnpoint.info = info;
2439 item->spawnpoint.flags = 0;
2440 }
2441
P_ActSpawn(mobj_t * mo)2442 void P_ActSpawn(mobj_t * mo)
2443 {
2444 if (!mo->state || !mo->state->action_par)
2445 I_Error("SPAWN() action used without a object name!\n");
2446
2447 mobj_strref_c *ref = (mobj_strref_c *) mo->state->action_par;
2448
2449 const mobjtype_c *info = ref->GetRef();
2450 SYS_ASSERT(info);
2451
2452 mobj_t *item = P_MobjCreateObject(mo->x, mo->y, mo->z, info);
2453 SYS_ASSERT(item);
2454
2455 item->angle = mo->angle;
2456 item->side = mo->side;
2457
2458 item->SetSource(mo);
2459 }
2460
2461
P_ActPathCheck(mobj_t * mo)2462 void P_ActPathCheck(mobj_t * mo)
2463 {
2464 // Checks if the creature is a path follower, and if so enters the
2465 // meander states.
2466
2467 if (! mo->path_trigger || ! mo->info->meander_state)
2468 return;
2469
2470 P_SetMobjStateDeferred(mo, mo->info->meander_state, 0);
2471
2472 mo->movedir = DI_SLOWTURN;
2473 mo->movecount = 0;
2474 }
2475
2476
P_ActPathFollow(mobj_t * mo)2477 void P_ActPathFollow(mobj_t * mo)
2478 {
2479 // For path-following creatures (spawned via RTS), makes the creature
2480 // follow the path by trying to get to the next node.
2481
2482 if (!mo->path_trigger)
2483 return;
2484
2485 if (RAD_CheckReachedTrigger(mo))
2486 {
2487 // reached the very last one ?
2488 if (!mo->path_trigger)
2489 {
2490 mo->movedir = DI_NODIR;
2491 return;
2492 }
2493
2494 mo->movedir = DI_SLOWTURN;
2495 return;
2496 }
2497
2498 float dx = mo->path_trigger->x - mo->x;
2499 float dy = mo->path_trigger->y - mo->y;
2500
2501 angle_t diff = R_PointToAngle(0, 0, dx, dy) - mo->angle;
2502
2503 // movedir value:
2504 // 0 for slow turning.
2505 // 1 for fast turning.
2506 // 2 for walking.
2507 // 3 for evasive maneouvres.
2508
2509 // if (mo->movedir < 2)
2510
2511 if (mo->movedir == DI_SLOWTURN || mo->movedir == DI_FASTTURN)
2512 {
2513 if (diff > ANG1*15 && diff < (ANG_MAX-ANG1*15))
2514 {
2515 angle_t step = ANG1*30;
2516
2517 if (diff < ANG180)
2518 mo->angle += P_Random() * (step >> 8);
2519 else
2520 mo->angle -= P_Random() * (step >> 8);
2521
2522 return;
2523 }
2524
2525 // we are now facing the next node
2526 mo->angle += diff;
2527 mo->movedir = DI_WALKING;
2528
2529 diff = 0;
2530 }
2531
2532 if (mo->movedir == DI_WALKING)
2533 {
2534 if (diff < ANG1*30)
2535 mo->angle += ANG1 * 2;
2536 else if (diff > (ANG_MAX-ANG1*30))
2537 mo->angle -= ANG1 * 2;
2538 else
2539 mo->movedir = DI_SLOWTURN;
2540
2541 if (! P_Move(mo, true))
2542 {
2543 mo->movedir = DI_EVASIVE;
2544 mo->angle = P_Random() << (ANGLEBITS - 8);
2545 mo->movecount = 1 + (P_Random() & 7);
2546 }
2547 return;
2548 }
2549
2550 // make evasive maneouvres
2551 mo->movecount--;
2552
2553 if (mo->movecount <= 0)
2554 {
2555 mo->movedir = DI_FASTTURN;
2556 return;
2557 }
2558
2559 P_Move(mo, true);
2560 }
2561
2562 //-------------------------------------------------------------------
2563 //--------------------ATTACK HANDLING PROCEDURES---------------------
2564 //-------------------------------------------------------------------
2565
2566 //
2567 // P_DoAttack
2568 //
2569 // When an object goes on the attack, it current attack is handled here;
2570 // the attack type is discerned and the assault is launched.
2571 //
2572 // -ACB- 1998/08/07
2573 //
P_DoAttack(mobj_t * object)2574 static void P_DoAttack(mobj_t * object)
2575 {
2576 const atkdef_c *attack = object->currentattack;
2577
2578 SYS_ASSERT(attack);
2579
2580 switch (attack->attackstyle)
2581 {
2582 case ATK_CLOSECOMBAT:
2583 {
2584 DoMeleeAttack(object);
2585 break;
2586 }
2587
2588 case ATK_PROJECTILE:
2589 {
2590 LaunchProjectile(object, object->target, attack->atk_mobj);
2591 break;
2592 }
2593
2594 case ATK_SMARTPROJECTILE:
2595 {
2596 LaunchSmartProjectile(object, object->target, attack->atk_mobj);
2597 break;
2598 }
2599
2600 case ATK_RANDOMSPREAD:
2601 {
2602 LaunchRandomSpread(object);
2603 break;
2604 }
2605
2606 case ATK_SHOOTTOSPOT:
2607 {
2608 ShootToSpot(object);
2609 break;
2610 }
2611
2612 case ATK_SHOT:
2613 {
2614 ShotAttack(object);
2615 break;
2616 }
2617
2618 case ATK_SKULLFLY:
2619 {
2620 SkullFlyAssault(object);
2621 break;
2622 }
2623
2624 case ATK_SPAWNER:
2625 {
2626 ObjectSpawning(object, object->angle);
2627 break;
2628 }
2629
2630 case ATK_SPREADER:
2631 {
2632 LaunchOrderedSpread(object);
2633 break;
2634 }
2635
2636 case ATK_TRACKER:
2637 {
2638 LaunchTracker(object);
2639 break;
2640 }
2641
2642 case ATK_TRIPLESPAWNER:
2643 {
2644 ObjectTripleSpawn(object);
2645 break;
2646 }
2647
2648 // -KM- 1998/11/25 Added spray attack
2649 case ATK_SPRAY:
2650 {
2651 SprayAttack(object);
2652 break;
2653 }
2654
2655 default: // THIS SHOULD NOT HAPPEN
2656 {
2657 if (strict_errors)
2658 I_Error("P_DoAttack: %s has an unknown attack type.\n",
2659 object->info->name.c_str());
2660 break;
2661 }
2662 }
2663 }
2664
2665 //
2666 // P_ActComboAttack
2667 //
2668 // This is called at end of a set of states that can result in
2669 // either a closecombat or ranged attack. The procedure checks
2670 // to see if the target is within melee range and picks the
2671 // approiate attack.
2672 //
2673 // -ACB- 1998/08/07
2674 //
P_ActComboAttack(mobj_t * object)2675 void P_ActComboAttack(mobj_t * object)
2676 {
2677 const atkdef_c *attack;
2678
2679 if (!object->target)
2680 return;
2681
2682 if (DecideMeleeAttack(object, object->info->closecombat))
2683 attack = object->info->closecombat;
2684 else
2685 attack = object->info->rangeattack;
2686
2687 if (attack)
2688 {
2689 if (attack->flags & AF_FaceTarget)
2690 P_ActFaceTarget(object);
2691
2692 if (attack->flags & AF_NeedSight)
2693 {
2694 if (!P_CheckSight(object, object->target))
2695 return;
2696 }
2697
2698 object->currentattack = attack;
2699 P_DoAttack(object);
2700 }
2701 #ifdef DEVELOPERS
2702 else
2703 {
2704 if (!object->info->closecombat)
2705 M_WarnError("%s hasn't got a close combat attack\n", object->info->name.c_str());
2706 else
2707 M_WarnError("%s hasn't got a range attack\n", object->info->name.c_str());
2708 }
2709 #endif
2710
2711 }
2712
2713 //
2714 // P_ActMeleeAttack
2715 //
2716 // Setup a close combat assault
2717 //
2718 // -ACB- 1998/08/07
2719 //
P_ActMeleeAttack(mobj_t * object)2720 void P_ActMeleeAttack(mobj_t * object)
2721 {
2722 const atkdef_c *attack;
2723
2724 attack = object->info->closecombat;
2725
2726 // -AJA- 1999/08/10: Multiple attack support.
2727 if (object->state && object->state->action_par)
2728 attack = (const atkdef_c *) object->state->action_par;
2729
2730 if (!attack)
2731 {
2732 M_WarnError("P_ActMeleeAttack: %s has no close combat attack.\n",
2733 object->info->name.c_str());
2734 return;
2735 }
2736
2737 if (attack->flags & AF_FaceTarget)
2738 P_ActFaceTarget(object);
2739
2740 if (attack->flags & AF_NeedSight)
2741 {
2742 if (!object->target || !P_CheckSight(object, object->target))
2743 return;
2744 }
2745
2746 object->currentattack = attack;
2747 P_DoAttack(object);
2748 }
2749
2750 //
2751 // P_ActRangeAttack
2752 //
2753 // Setup an attack at range
2754 //
2755 // -ACB- 1998/08/07
2756 //
P_ActRangeAttack(mobj_t * object)2757 void P_ActRangeAttack(mobj_t * object)
2758 {
2759 const atkdef_c *attack;
2760
2761 attack = object->info->rangeattack;
2762
2763 // -AJA- 1999/08/10: Multiple attack support.
2764 if (object->state && object->state->action_par)
2765 attack = (const atkdef_c *) object->state->action_par;
2766
2767 if (!attack)
2768 {
2769 M_WarnError("P_ActRangeAttack: %s hasn't got a range attack.\n",
2770 object->info->name.c_str());
2771 return;
2772 }
2773
2774 if (attack->flags & AF_FaceTarget)
2775 P_ActFaceTarget(object);
2776
2777 if (attack->flags & AF_NeedSight)
2778 {
2779 if (!object->target || !P_CheckSight(object, object->target))
2780 return;
2781 }
2782
2783 object->currentattack = attack;
2784 P_DoAttack(object);
2785 }
2786
2787 //
2788 // P_ActSpareAttack
2789 //
2790 // Setup an attack that is not defined as close or range. can be
2791 // used to act as a follow attack for close or range, if you want one to
2792 // add to the others.
2793 //
2794 // -ACB- 1998/08/24
2795 //
P_ActSpareAttack(mobj_t * object)2796 void P_ActSpareAttack(mobj_t *object)
2797 {
2798 const atkdef_c *attack;
2799
2800 attack = object->info->spareattack;
2801
2802 // -AJA- 1999/08/10: Multiple attack support.
2803 if (object->state && object->state->action_par)
2804 attack = (const atkdef_c *) object->state->action_par;
2805
2806 if (attack)
2807 {
2808 if ((attack->flags & AF_FaceTarget) && object->target)
2809 P_ActFaceTarget(object);
2810
2811 if ((attack->flags & AF_NeedSight) && object->target)
2812 {
2813 if (!P_CheckSight(object, object->target))
2814 return;
2815 }
2816
2817 object->currentattack = attack;
2818 P_DoAttack(object);
2819 }
2820 #ifdef DEVELOPERS
2821 else
2822 {
2823 M_WarnError("P_ActSpareAttack: %s hasn't got a spare attack\n", object->info->name.c_str());
2824 return;
2825 }
2826 #endif
2827
2828 }
2829
2830 //
2831 // P_ActRefireCheck
2832 //
2833 // This procedure will be called inbetween firing on an object
2834 // that will fire repeatly (Chaingunner/Arachontron etc...), the
2835 // purpose of this is to see if the object should refire and
2836 // performs checks to that effect, first there is a check to see
2837 // if the object will keep firing regardless and the others
2838 // check if the the target exists, is alive and within view. The
2839 // only other code here is a stealth check: a object with stealth
2840 // capabilitys will lose the ability while firing.
2841 //
2842 // -ACB- 1998/08/10
2843 //
P_ActRefireCheck(mobj_t * object)2844 void P_ActRefireCheck(mobj_t * object)
2845 {
2846 mobj_t *target;
2847 const atkdef_c *attack;
2848
2849 attack = object->currentattack;
2850
2851 if (! attack)
2852 return;
2853
2854 if (attack->flags & AF_FaceTarget)
2855 P_ActFaceTarget(object);
2856
2857 // Random chance that object will keep firing regardless
2858 if (P_RandomTest(attack->keepfirechance))
2859 return;
2860
2861 target = object->target;
2862
2863 if (!target || (target->health <= 0) || !P_CheckSight(object, target))
2864 {
2865 if (object->info->chase_state)
2866 P_SetMobjStateDeferred(object, object->info->chase_state, 0);
2867 }
2868 else if (object->flags & MF_STEALTH)
2869 {
2870 object->vis_target = VISIBLE;
2871 }
2872 }
2873
2874 //
2875 // P_ActReloadCheck
2876 //
2877 // Enter reload states if the monster has shot a certain number of
2878 // shots (given by RELOAD_SHOTS command).
2879 //
2880 // -AJA- 2004/11/15: added this.
2881 //
P_ActReloadCheck(mobj_t * object)2882 void P_ActReloadCheck(mobj_t * object)
2883 {
2884 object->shot_count++;
2885
2886 if (object->shot_count >= object->info->reload_shots)
2887 {
2888 object->shot_count = 0;
2889
2890 if (object->info->reload_state)
2891 P_SetMobjStateDeferred(object, object->info->reload_state, 0);
2892 }
2893 }
2894
P_ActReloadReset(mobj_t * object)2895 void P_ActReloadReset(mobj_t * object)
2896 {
2897 object->shot_count = 0;
2898 }
2899
2900 //---------------------------------------------
2901 //-----------LOOKING AND CHASING---------------
2902 //---------------------------------------------
2903
2904 extern mobj_t ** bmap_things;
2905
2906 //
2907 // CreateAggression
2908 //
2909 // Sets an object up to target a previously stored object.
2910 //
2911 // -ACB- 2000/06/20 Re-written and Simplified
2912 //
2913 // -AJA- 2009/07/05 Rewritten again, using the blockmap
2914 //
CreateAggression(mobj_t * mo)2915 static bool CreateAggression(mobj_t * mo)
2916 {
2917 if (mo->target && mo->target->health > 0)
2918 return false;
2919
2920 // pick a block in blockmap to check
2921 int bdx = P_RandomNegPos() / 17;
2922 int bdy = P_RandomNegPos() / 17;
2923
2924 int block_x = BLOCKMAP_GET_X(mo->x) + bdx;
2925 int block_y = BLOCKMAP_GET_X(mo->y) + bdy;
2926
2927 block_x = abs(block_x + bmap_width) % bmap_width;
2928 block_y = abs(block_y + bmap_height) % bmap_height;
2929
2930 // I_Debugf("BLOCKMAP POS: %3d %3d (size: %d %d)\n", block_x, block_y, bmap_width, bmap_height);
2931
2932 int bnum = block_y * bmap_width + block_x;
2933
2934 for (mobj_t *other = bmap_things[bnum]; other; other = other->bnext)
2935 {
2936 if (! (other->info->extendedflags & EF_MONSTER) || other->health <= 0)
2937 continue;
2938
2939 if (other == mo)
2940 continue;
2941
2942 if (other->info == mo->info)
2943 {
2944 if (! (other->info->extendedflags & EF_DISLOYALTYPE))
2945 continue;
2946
2947 // Type the same and it can't hurt own kind - not good.
2948 if (! (other->info->extendedflags & EF_OWNATTACKHURTS))
2949 continue;
2950 }
2951
2952 // don't attack a friend if we cannot hurt them.
2953 // -AJA- I'm assuming that even friends will 'infight'.
2954 if ((mo->info->side & other->info->side) != 0 &&
2955 (other->info->hyperflags & (HF_SIDEIMMUNE | HF_ULTRALOYAL)))
2956 {
2957 continue;
2958 }
2959
2960 // POTENTIAL TARGET
2961
2962 // fairly low chance of trying it, in case this block
2963 // contains many monsters (spread the love)
2964 if (P_Random() > 99)
2965 continue;
2966
2967 // sight check is expensive, do it last
2968 if (! P_CheckSight(mo, other))
2969 continue;
2970
2971 // OK, you got me
2972 mo->SetTarget(other);
2973
2974 I_Debugf("Created aggression : %s --> %s\n",
2975 mo->info->name.c_str(),
2976 other->info->name.c_str());
2977
2978 if (mo->info->seesound)
2979 S_StartFX(mo->info->seesound, P_MobjGetSfxCategory(mo),
2980 mo, SfxFlags(mo->info));
2981
2982 if (mo->info->chase_state)
2983 P_SetMobjStateDeferred(mo, mo->info->chase_state, 0);
2984
2985 return true;
2986 }
2987
2988 return false;
2989 }
2990
2991
2992 //
2993 // P_ActStandardLook
2994 //
2995 // Standard Lookout procedure
2996 //
2997 // -ACB- 1998/08/22
2998 //
P_ActStandardLook(mobj_t * object)2999 void P_ActStandardLook(mobj_t * object)
3000 {
3001 int targ_pnum;
3002 mobj_t *targ = NULL;
3003
3004 object->threshold = 0; // any shot will wake up
3005
3006 targ_pnum = object->subsector->sector->sound_player;
3007
3008 if (targ_pnum >= 0 && targ_pnum < MAXPLAYERS &&
3009 players[targ_pnum])
3010 {
3011 targ = players[targ_pnum]->mo;
3012 }
3013
3014 // -AJA- 2004/09/02: ignore the sound of a friend
3015 // FIXME: maybe wake up and support that player ??
3016 if (targ && (targ->side & object->side) != 0)
3017 targ = NULL;
3018
3019 if (object->flags & MF_STEALTH)
3020 object->vis_target = VISIBLE;
3021
3022 if (g_aggression.d)
3023 if (CreateAggression(object) || CreateAggression(object))
3024 return;
3025
3026 if (targ && (targ->flags & MF_SHOOTABLE))
3027 {
3028 object->SetTarget(targ);
3029
3030 if (object->flags & MF_AMBUSH)
3031 {
3032 if (!P_CheckSight(object, object->target) &&
3033 !P_LookForPlayers(object, object->info->sight_angle))
3034 return;
3035 }
3036 }
3037 else
3038 {
3039 if (!P_LookForPlayers(object, object->info->sight_angle))
3040 return;
3041 }
3042
3043 if (object->info->seesound)
3044 {
3045 S_StartFX(object->info->seesound, P_MobjGetSfxCategory(object),
3046 object, SfxFlags(object->info));
3047 }
3048
3049 // -AJA- this will remove objects which have no chase states.
3050 // For compatibility with original DOOM.
3051 P_SetMobjStateDeferred(object, object->info->chase_state, 0);
3052 }
3053
3054 //
3055 // P_ActPlayerSupportLook
3056 //
3057 // Player Support Lookout procedure
3058 //
3059 // -ACB- 1998/09/05
3060 //
P_ActPlayerSupportLook(mobj_t * object)3061 void P_ActPlayerSupportLook(mobj_t * object)
3062 {
3063 object->threshold = 0; // any shot will wake up
3064
3065 if (object->flags & MF_STEALTH)
3066 object->vis_target = VISIBLE;
3067
3068 if (!object->supportobj)
3069 {
3070 if (! P_ActLookForTargets(object))
3071 return;
3072
3073 // -AJA- 2004/09/02: join the player's side
3074 if (object->side == 0)
3075 object->side = object->target->side;
3076
3077 if (object->info->seesound)
3078 {
3079 S_StartFX(object->info->seesound, P_MobjGetSfxCategory(object),
3080 object, SfxFlags(object->info));
3081 }
3082 }
3083
3084 if (object->info->meander_state)
3085 P_SetMobjStateDeferred(object, object->info->meander_state, 0);
3086 }
3087
3088 //
3089 // P_ActStandardMeander
3090 //
P_ActStandardMeander(mobj_t * object)3091 void P_ActStandardMeander(mobj_t * object)
3092 {
3093 int delta;
3094
3095 object->threshold = 0; // any shot will wake up
3096
3097 // move within supporting distance of player
3098 if (--object->movecount < 0 || !P_Move(object, false))
3099 P_NewChaseDir(object);
3100
3101 // turn towards movement direction if not there yet
3102 if (object->movedir < 8)
3103 {
3104 object->angle &= (7 << 29);
3105 delta = object->angle - (object->movedir << 29);
3106
3107 if (delta > 0)
3108 object->angle -= ANG45;
3109 else if (delta < 0)
3110 object->angle += ANG45;
3111 }
3112 }
3113
3114 //
3115 // P_ActPlayerSupportMeander
3116 //
P_ActPlayerSupportMeander(mobj_t * object)3117 void P_ActPlayerSupportMeander(mobj_t * object)
3118 {
3119 int delta;
3120
3121 object->threshold = 0; // any shot will wake up
3122
3123 // move within supporting distance of player
3124 if (--object->movecount < 0 || !P_Move(object, false))
3125 P_NewChaseDir(object);
3126
3127 // turn towards movement direction if not there yet
3128 if (object->movedir < 8)
3129 {
3130 object->angle &= (7 << 29);
3131 delta = object->angle - (object->movedir << 29);
3132
3133 if (delta > 0)
3134 object->angle -= ANG45;
3135 else if (delta < 0)
3136 object->angle += ANG45;
3137 }
3138
3139 //
3140 // we have now meandered, now check for a support object, if we don't
3141 // look for one and return; else look for targets to take out, if we
3142 // find one, go for the chase.
3143 //
3144 /* if (!object->supportobj)
3145 {
3146 P_ActPlayerSupportLook(object);
3147 return;
3148 } */
3149
3150 P_ActLookForTargets(object);
3151 }
3152
3153 //
3154 // P_ActStandardChase
3155 //
3156 // Standard AI Chase Procedure
3157 //
3158 // -ACB- 1998/08/22 Procedure Written
3159 // -ACB- 1998/09/05 Added Support Object Check
3160 //
P_ActStandardChase(mobj_t * object)3161 void P_ActStandardChase(mobj_t * object)
3162 {
3163 int delta;
3164 sfx_t *sound;
3165
3166 if (object->reactiontime)
3167 object->reactiontime--;
3168
3169 // object has a pain threshold, while this is true, reduce it. while
3170 // the threshold is true, the object will remain intent on its target.
3171 if (object->threshold)
3172 {
3173 if (!object->target || object->target->health <= 0)
3174 object->threshold = 0;
3175 else
3176 object->threshold--;
3177 }
3178
3179 // A Chasing Stealth Creature becomes less visible
3180 if (object->flags & MF_STEALTH)
3181 object->vis_target = INVISIBLE;
3182
3183 // turn towards movement direction if not there yet
3184 if (object->movedir < 8)
3185 {
3186 object->angle &= (7 << 29);
3187 delta = object->angle - (object->movedir << 29);
3188
3189 if (delta > 0)
3190 object->angle -= ANG45;
3191 else if (delta < 0)
3192 object->angle += ANG45;
3193 }
3194
3195 if (!object->target || !(object->target->flags & MF_SHOOTABLE))
3196 {
3197 if (P_ActLookForTargets(object))
3198 return;
3199
3200 // -ACB- 1998/09/06 Target is not relevant: NULLify.
3201 object->SetTarget(NULL);
3202
3203 P_SetMobjStateDeferred(object, object->info->idle_state, 0);
3204 return;
3205 }
3206
3207 // do not attack twice in a row
3208 if (object->flags & MF_JUSTATTACKED)
3209 {
3210 object->flags &= ~MF_JUSTATTACKED;
3211
3212 // -KM- 1998/12/16 Nightmare mode set the fast parm.
3213 if (!level_flags.fastparm)
3214 P_NewChaseDir(object);
3215
3216 return;
3217 }
3218
3219 sound = object->info->attacksound;
3220
3221 // check for melee attack
3222 if (object->info->melee_state && DecideMeleeAttack(object, object->info->closecombat))
3223 {
3224 if (sound)
3225 S_StartFX(sound, P_MobjGetSfxCategory(object), object);
3226
3227 if (object->info->melee_state)
3228 P_SetMobjStateDeferred(object, object->info->melee_state, 0);
3229 return;
3230 }
3231
3232 // check for missile attack
3233 if (object->info->missile_state)
3234 {
3235 // -KM- 1998/12/16 Nightmare set the fastparm.
3236 if (!(!level_flags.fastparm && object->movecount))
3237 {
3238 if (DecideRangeAttack(object))
3239 {
3240 if (object->info->missile_state)
3241 P_SetMobjStateDeferred(object, object->info->missile_state, 0);
3242 object->flags |= MF_JUSTATTACKED;
3243 return;
3244 }
3245 }
3246 }
3247
3248 // possibly choose another target
3249 // -ACB- 1998/09/05 Object->support->object check, go for new targets
3250 if (!P_CheckSight(object, object->target) && !object->threshold)
3251 {
3252 if (P_ActLookForTargets(object))
3253 return;
3254 }
3255
3256 // chase towards player
3257 if (--object->movecount < 0 || !P_Move(object, false))
3258 P_NewChaseDir(object);
3259
3260 // make active sound
3261 if (object->info->activesound && M_Random() < 3)
3262 S_StartFX(object->info->activesound, P_MobjGetSfxCategory(object), object);
3263 }
3264
3265 //
3266 // P_ActResurrectChase
3267 //
3268 // Before undertaking the standard chase procedure, the object
3269 // will check for a nearby corpse and raises one if it exists.
3270 //
3271 // -ACB- 1998/09/05 Support Check: Raised object supports raiser's supportobj
3272 //
P_ActResurrectChase(mobj_t * object)3273 void P_ActResurrectChase(mobj_t * object)
3274 {
3275 mobj_t *corpse;
3276
3277 corpse = P_MapFindCorpse(object);
3278
3279 if (corpse)
3280 {
3281 object->angle = R_PointToAngle(object->x, object->y, corpse->x, corpse->y);
3282 if (object->info->res_state)
3283 P_SetMobjStateDeferred(object, object->info->res_state, 0);
3284
3285 // corpses without raise states should be skipped
3286 SYS_ASSERT(corpse->info->raise_state);
3287
3288 P_BringCorpseToLife(corpse);
3289
3290 // -ACB- 1998/09/05 Support Check: Res creatures to support that object
3291 if (object->supportobj)
3292 {
3293 corpse->SetSupportObj(object->supportobj);
3294 corpse->SetTarget(object->target);
3295 }
3296 else
3297 {
3298 corpse->SetSupportObj(NULL);
3299 corpse->SetTarget(NULL);
3300 }
3301
3302 // -AJA- Resurrected creatures are on Archvile's side (like MBF)
3303 corpse->side = object->side;
3304 return;
3305 }
3306
3307 P_ActStandardChase(object);
3308 }
3309
3310 //
3311 // P_ActWalkSoundChase
3312 //
3313 // Make a sound and then chase...
3314 //
P_ActWalkSoundChase(mobj_t * object)3315 void P_ActWalkSoundChase(mobj_t * object)
3316 {
3317 if (!object->info->walksound)
3318 {
3319 M_WarnError("WALKSOUND_CHASE: %s hasn't got a walksound.\n",
3320 object->info->name.c_str());
3321 return;
3322 }
3323
3324 S_StartFX(object->info->walksound, P_MobjGetSfxCategory(object), object);
3325 P_ActStandardChase(object);
3326 }
3327
3328
P_ActDie(mobj_t * mo)3329 void P_ActDie(mobj_t * mo)
3330 {
3331 // Boom/MBF compatibility.
3332
3333 P_DamageMobj(mo, NULL, NULL, mo->health + 1, NULL);
3334 }
3335
P_ActKeenDie(mobj_t * mo)3336 void P_ActKeenDie(mobj_t * mo)
3337 {
3338 P_ActMakeIntoCorpse(mo);
3339
3340 // see if all other Keens are dead
3341 for (mobj_t *cur = mobjlisthead; cur != NULL; cur = cur->next)
3342 {
3343 if (cur == mo)
3344 continue;
3345
3346 if (cur->info != mo->info)
3347 continue;
3348
3349 if (cur->health > 0)
3350 return; // other Keen not dead
3351 }
3352
3353 L_WriteDebug("P_ActKeenDie: ALL DEAD, activating...\n");
3354
3355 P_RemoteActivation(NULL, 2 /* door type */, 666 /* tag */, 0, line_Any);
3356 }
3357
3358
P_ActCheckMoving(mobj_t * mo)3359 void P_ActCheckMoving(mobj_t * mo)
3360 {
3361 // -KM- 1999/01/31 Returns a player to spawnstate when not moving.
3362
3363 player_t *pl = mo->player;
3364
3365 if (pl)
3366 {
3367 if (pl->actual_speed < PLAYER_STOPSPEED)
3368 {
3369 P_SetMobjStateDeferred(mo, mo->info->idle_state, 0);
3370
3371 // we delay a little bit, in order to prevent a loop where
3372 // CHECK_ACTIVITY jumps to SWIM states (for example) and
3373 // then CHECK_MOVING jumps right back to IDLE states.
3374 mo->tics = 2;
3375 }
3376 return;
3377 }
3378
3379 if (fabs(mo->mom.x) < STOPSPEED && fabs(mo->mom.y) < STOPSPEED)
3380 {
3381 mo->mom.x = mo->mom.y = 0;
3382 P_SetMobjStateDeferred(mo, mo->info->idle_state, 0);
3383 }
3384 }
3385
P_ActCheckActivity(mobj_t * mo)3386 void P_ActCheckActivity(mobj_t *mo)
3387 {
3388 player_t *pl = mo->player;
3389
3390 if (! pl)
3391 return;
3392
3393 if (pl->swimming)
3394 {
3395 // enter the SWIM states (if present)
3396 statenum_t swim_st = P_MobjFindLabel(pl->mo, "SWIM");
3397
3398 if (swim_st == S_NULL)
3399 swim_st = pl->mo->info->chase_state;
3400
3401 if (swim_st != S_NULL)
3402 P_SetMobjStateDeferred(pl->mo, swim_st, 0);
3403
3404 return;
3405 }
3406
3407 if (pl->powers[PW_Jetpack] > 0)
3408 {
3409 // enter the FLY states (if present)
3410 statenum_t fly_st = P_MobjFindLabel(pl->mo, "FLY");
3411
3412 if (fly_st != S_NULL)
3413 P_SetMobjStateDeferred(pl->mo, fly_st, 0);
3414
3415 return;
3416 }
3417
3418 if (mo->on_ladder >= 0)
3419 {
3420 // enter the CLIMB states (if present)
3421 statenum_t climb_st = P_MobjFindLabel(pl->mo, "CLIMB");
3422
3423 if (climb_st != S_NULL)
3424 P_SetMobjStateDeferred(pl->mo, climb_st, 0);
3425
3426 return;
3427 }
3428
3429 /* Otherwise: do nothing */
3430 }
3431
3432
P_ActCheckBlood(mobj_t * mo)3433 void P_ActCheckBlood(mobj_t * mo)
3434 {
3435 // -KM- 1999/01/31 Part of the extra blood option, makes blood stick around...
3436 // -AJA- 1999/10/02: ...but not indefinitely.
3437
3438 if (level_flags.more_blood && mo->tics >= 0)
3439 {
3440 int val = P_Random();
3441
3442 // exponential formula
3443 mo->tics = ((val * val * val) >> 18) * TICRATE + TICRATE;
3444 }
3445 }
3446
3447
P_ActJump(mobj_t * mo)3448 void P_ActJump(mobj_t * mo)
3449 {
3450 // Jumps to the given label, possibly randomly.
3451 //
3452 // Note: nothing to do with monsters physically jumping.
3453
3454 if (!mo->state || !mo->state->action_par)
3455 {
3456 M_WarnError("JUMP action used in [%s] without a label !\n",
3457 mo->info->name.c_str());
3458 return;
3459 }
3460
3461 act_jump_info_t *jump = (act_jump_info_t *) mo->state->action_par;
3462
3463 SYS_ASSERT(jump->chance >= 0);
3464 SYS_ASSERT(jump->chance <= 1);
3465
3466 if (P_RandomTest(jump->chance))
3467 {
3468 mo->next_state = (mo->state->jumpstate == S_NULL) ?
3469 NULL : (states + mo->state->jumpstate);
3470 }
3471 }
3472
3473
P_ActSetInvuln(struct mobj_s * mo)3474 void P_ActSetInvuln(struct mobj_s *mo)
3475 {
3476 mo->hyperflags |= HF_INVULNERABLE;
3477 }
3478
P_ActClearInvuln(struct mobj_s * mo)3479 void P_ActClearInvuln(struct mobj_s *mo)
3480 {
3481 mo->hyperflags &= ~HF_INVULNERABLE;
3482 }
3483
3484
P_ActBecome(struct mobj_s * mo)3485 void P_ActBecome(struct mobj_s *mo)
3486 {
3487 if (!mo->state || !mo->state->action_par)
3488 {
3489 I_Error("BECOME action used in [%s] without arguments!\n",
3490 mo->info->name.c_str());
3491 return; /* NOT REACHED */
3492 }
3493
3494 act_become_info_t *become = (act_become_info_t *) mo->state->action_par;
3495
3496 if (! become->info)
3497 {
3498 become->info = mobjtypes.Lookup(become->info_ref.c_str());
3499 SYS_ASSERT(become->info); // lookup should be OK (fatal error if not found)
3500 }
3501
3502 // DO THE DEED !!
3503
3504 P_UnsetThingPosition(mo);
3505 {
3506 mo->info = become->info;
3507
3508 mo->radius = mo->info->radius;
3509 mo->height = mo->info->height;
3510 mo->speed = mo->info->speed * (level_flags.fastparm ? mo->info->fast : 1);
3511
3512 // Note: health is not changed
3513
3514 mo->flags = mo->info->flags;
3515 mo->extendedflags = mo->info->extendedflags;
3516 mo->hyperflags = mo->info->hyperflags;
3517
3518 mo->vis_target = PERCENT_2_FLOAT(mo->info->translucency);
3519 mo->currentattack = NULL;
3520 mo->model_skin = mo->info->model_skin;
3521 mo->model_last_frame = -1;
3522
3523 // handle dynamic lights
3524 {
3525 const dlight_info_c *dinfo = &mo->info->dlight[0];
3526
3527 if (dinfo->type != DLITE_None)
3528 {
3529 mo->dlight.target = dinfo->radius;
3530 mo->dlight.color = dinfo->colour;
3531
3532 // make renderer re-create shader info
3533 if (mo->dlight.shader)
3534 {
3535 // FIXME: delete mo->dlight.shader;
3536 mo->dlight.shader = NULL;
3537 }
3538 }
3539 }
3540 }
3541 P_SetThingPosition(mo);
3542
3543 statenum_t state = P_MobjFindLabel(mo, become->start.label.c_str());
3544 if (state == S_NULL)
3545 I_Error("BECOME action: frame '%s' in [%s] not found!\n",
3546 become->start.label.c_str(), mo->info->name.c_str());
3547
3548 state += become->start.offset;
3549
3550 P_SetMobjStateDeferred(mo, state, 0);
3551 }
3552
3553
3554 // -AJA- 1999/08/08: New attack flag FORCEAIM, which fixes chainsaw.
3555 //
P_PlayerAttack(mobj_t * p_obj,const atkdef_c * attack)3556 void P_PlayerAttack(mobj_t * p_obj, const atkdef_c * attack)
3557 {
3558 SYS_ASSERT(attack);
3559
3560 p_obj->currentattack = attack;
3561
3562 float range = (attack->range > 0) ? attack->range : MISSILERANGE;
3563
3564 // see which target is to be aimed at
3565 mobj_t *target = P_MapTargetAutoAim(p_obj, p_obj->angle, range,
3566 (attack->flags & AF_ForceAim) ? true : false);
3567
3568 p_obj->SetTarget(target);
3569
3570 if (attack->flags & AF_FaceTarget)
3571 P_ActFaceTarget(p_obj);
3572
3573 P_DoAttack(p_obj);
3574 }
3575
3576
3577 //--- editor settings ---
3578 // vi:ts=4:sw=4:noexpandtab
3579