1 //----------------------------------------------------------------------------
2 // EDGE Creature Action Code
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 // -KM- 1998/09/27 Sounds.ddf stuff
27 //
28 // -AJA- 1999/07/21: Replaced some non-critical P_Randoms with M_Random,
29 // and removed some X_Random()-X_Random() things.
30 //
31
32 #include "i_defs.h"
33
34 #include "dm_data.h"
35 #include "dm_state.h"
36 #include "g_game.h"
37 #include "m_random.h"
38 #include "p_action.h"
39 #include "p_local.h"
40 #include "s_sound.h"
41 #include "w_wad.h"
42 #include "z_zone.h"
43
44 #include <float.h>
45
46 dirtype_e opposite[] =
47 {
48 DI_WEST,
49 DI_SOUTHWEST,
50 DI_SOUTH,
51 DI_SOUTHEAST,
52 DI_EAST,
53 DI_NORTHEAST,
54 DI_NORTH,
55 DI_NORTHWEST,
56 DI_NODIR
57 };
58
59 dirtype_e diags[] =
60 {
61 DI_NORTHWEST,
62 DI_NORTHEAST,
63 DI_SOUTHWEST,
64 DI_SOUTHEAST
65 };
66
67 // sqrt(2) / 2: The diagonal speed of creatures
68 #define SQ2 0.7071067812f
69
70 float xspeed[8] = {1.0f, SQ2, 0.0f, -SQ2, -1.0f, -SQ2, 0.0f, SQ2};
71 float yspeed[8] = {0.0f, SQ2, 1.0f, SQ2, 0.0f, -SQ2, -1.0f, -SQ2};
72
73 #undef SQ2
74
75 //
76 // ENEMY THINKING
77 //
78 // Enemies are allways spawned
79 // with targetplayer = -1, threshold = 0
80 // Most monsters are spawned unaware of all players,
81 // but some can be made preaware
82 //
83
84 //
85 // Called by P_NoiseAlert.
86 // Recursively traverse adjacent sectors,
87 // sound blocking lines cut off traversal.
88 //
RecursiveSound(sector_t * sec,int soundblocks,int player)89 static void RecursiveSound(sector_t * sec, int soundblocks, int player)
90 {
91 int i;
92 line_t *check;
93 sector_t *other;
94
95 // has the sound flooded this sector
96 if (sec->validcount == validcount && sec->soundtraversed <= soundblocks + 1)
97 return;
98
99 // wake up all monsters in this sector
100 sec->validcount = validcount;
101 sec->soundtraversed = soundblocks + 1;
102 sec->sound_player = player;
103
104 for (i = 0; i < sec->linecount; i++)
105 {
106 check = sec->lines[i];
107
108 if (!(check->flags & MLF_TwoSided))
109 continue;
110
111 // -AJA- 1999/07/19: Gaps are now stored in line_t.
112 if (check->gap_num == 0)
113 continue; // closed door
114
115 // -AJA- 2001/11/11: handle closed Sliding doors
116 if (check->slide_door && ! check->slide_door->s.see_through &&
117 ! check->slider_move)
118 {
119 continue;
120 }
121
122 if (check->frontsector == sec)
123 other = check->backsector;
124 else
125 other = check->frontsector;
126
127 if (check->flags & MLF_SoundBlock)
128 {
129 if (!soundblocks)
130 RecursiveSound(other, 1, player);
131 }
132 else
133 {
134 RecursiveSound(other, soundblocks, player);
135 }
136 }
137 }
138
P_NoiseAlert(player_t * p)139 void P_NoiseAlert(player_t *p)
140 {
141 validcount++;
142
143 RecursiveSound(p->mo->subsector->sector, 0, p->pnum);
144 }
145
146 //
147 // Move in the current direction,
148 // returns false if the move is blocked.
149 //
P_Move(mobj_t * actor,bool path)150 bool P_Move(mobj_t * actor, bool path)
151 {
152 vec3_t orig_pos;
153
154 orig_pos.Set(actor->x, actor->y, actor->z);
155
156 float tryx;
157 float tryy;
158
159 if (path)
160 {
161 tryx = actor->x + actor->speed * M_Cos(actor->angle);
162 tryy = actor->y + actor->speed * M_Sin(actor->angle);
163 }
164 else
165 {
166 if (actor->movedir == DI_NODIR)
167 return false;
168
169 if ((unsigned)actor->movedir >= 8)
170 I_Error("Weird actor->movedir!");
171
172 tryx = actor->x + actor->speed * xspeed[actor->movedir];
173 tryy = actor->y + actor->speed * yspeed[actor->movedir];
174 }
175
176 if (! P_TryMove(actor, tryx, tryy))
177 {
178 epi::array_iterator_c it;
179 line_t* ld;
180
181 // open any specials
182 if (actor->flags & MF_FLOAT && floatok)
183 {
184 // must adjust height
185 if (actor->z < float_destz)
186 actor->z += actor->info->float_speed;
187 else
188 actor->z -= actor->info->float_speed;
189
190 actor->flags |= MF_INFLOAT;
191 // FIXME: position interpolation
192 return true;
193 }
194
195 if (spechit.GetSize() == 0)
196 return false;
197
198 actor->movedir = DI_NODIR;
199
200 // -AJA- 1999/09/10: As Lee Killough points out, this is where
201 // monsters can get stuck in doortracks. We follow Lee's
202 // method: return true 90% of the time if the blocking line
203 // was the one activated, or false 90% of the time if there
204 // was some other line activated.
205
206 bool any_used = false;
207 bool block_used = false;
208
209 for (it=spechit.GetTailIterator(); it.IsValid(); it--)
210 {
211 ld = ITERATOR_TO_TYPE(it, line_t*);
212 if (P_UseSpecialLine(actor, ld, 0, -FLT_MAX, FLT_MAX))
213 {
214 any_used = true;
215
216 if (ld == blockline)
217 block_used = true;
218 }
219 }
220
221 return any_used && (P_Random() < 230 ? block_used : !block_used);
222 }
223
224 actor->flags &= ~MF_INFLOAT;
225
226 if (!(actor->flags & MF_FLOAT) &&
227 !(actor->extendedflags & EF_GRAVFALL))
228 actor->z = actor->floorz;
229
230 // -AJA- 2008/01/16: position interpolation
231 if ((actor->state->flags & SFF_Model) ||
232 (actor->flags & MF_FLOAT))
233 {
234 actor->lerp_num = CLAMP(2, actor->state->tics, 10);
235 actor->lerp_pos = 1;
236
237 actor->lerp_from = orig_pos;
238 }
239
240 return true;
241 }
242
243 //
244 // Attempts to move actor on
245 // in its current (ob->moveangle) direction.
246 // If blocked by either a wall or an actor
247 // returns FALSE
248 // If move is either clear or blocked only by a door,
249 // returns TRUE and sets...
250 // If a door is in the way,
251 // an OpenDoor call is made to start it opening.
252 //
TryWalk(mobj_t * actor)253 static bool TryWalk(mobj_t * actor)
254 {
255 if (!P_Move(actor, false))
256 return false;
257
258 actor->movecount = P_Random() & 15;
259 return true;
260 }
261
262 // -ACB- 1998/09/06 actor is now an object; different movement choices.
P_NewChaseDir(mobj_t * object)263 void P_NewChaseDir(mobj_t * object)
264 {
265 float deltax;
266 float deltay;
267 dirtype_e tdir;
268
269 dirtype_e d[3];
270 dirtype_e olddir;
271 dirtype_e turnaround;
272
273 olddir = object->movedir;
274 turnaround = opposite[olddir];
275
276 //
277 // Movement choice: Previously this was calculation to find
278 // the distance between object and target: if the object had
279 // no target, a fatal error was returned. However it is now
280 // possible to have movement without a target. if the object
281 // has a target, go for that; else if it has a supporting
282 // object aim to go within supporting distance of that; the
283 // remaining option is to walk aimlessly: the target destination
284 // is always 128 in the old movement direction, think
285 // of it like the donkey and the carrot sketch: the donkey will
286 // move towards the carrot, but since the carrot is always a
287 // set distance away from the donkey, the rather stupid mammal
288 // will spend eternity trying to get the carrot and will walk
289 // forever.
290 //
291 // -ACB- 1998/09/06
292
293 if (object->target)
294 {
295 deltax = object->target->x - object->x;
296 deltay = object->target->y - object->y;
297 }
298 else if (object->supportobj)
299 {
300 // not too close
301 deltax = (object->supportobj->x - object->x) - (object->supportobj->radius * 4);
302 deltay = (object->supportobj->y - object->y) - (object->supportobj->radius * 4);
303 }
304 else
305 {
306 deltax = 128 * xspeed[olddir];
307 deltay = 128 * yspeed[olddir];
308 }
309
310 if (deltax > 10)
311 d[1] = DI_EAST;
312 else if (deltax < -10)
313 d[1] = DI_WEST;
314 else
315 d[1] = DI_NODIR;
316
317 if (deltay < -10)
318 d[2] = DI_SOUTH;
319 else if (deltay > 10)
320 d[2] = DI_NORTH;
321 else
322 d[2] = DI_NODIR;
323
324 // try direct route
325 if (d[1] != DI_NODIR && d[2] != DI_NODIR)
326 {
327 object->movedir = diags[((deltay < 0) << 1) + (deltax > 0)];
328 if (object->movedir != turnaround && TryWalk(object))
329 return;
330 }
331
332 // try other directions
333 if (P_Random() > 200 || fabs(deltay) > fabs(deltax))
334 {
335 tdir = d[1];
336 d[1] = d[2];
337 d[2] = tdir;
338 }
339
340 if (d[1] == turnaround)
341 d[1] = DI_NODIR;
342
343 if (d[2] == turnaround)
344 d[2] = DI_NODIR;
345
346 if (d[1] != DI_NODIR)
347 {
348 object->movedir = d[1];
349 if (TryWalk(object))
350 {
351 // either moved forward or attacked
352 return;
353 }
354 }
355
356 if (d[2] != DI_NODIR)
357 {
358 object->movedir = d[2];
359
360 if (TryWalk(object))
361 return;
362 }
363
364 // there is no direct path to the player,
365 // so pick another direction.
366 if (olddir != DI_NODIR)
367 {
368 object->movedir = olddir;
369
370 if (TryWalk(object))
371 return;
372 }
373
374 // randomly determine direction of search
375 if (P_Random() & 1)
376 {
377 for (tdir = DI_EAST; tdir <= DI_SOUTHEAST; tdir = (dirtype_e)((int)tdir+1))
378 {
379 if (tdir != turnaround)
380 {
381 object->movedir = tdir;
382
383 if (TryWalk(object))
384 return;
385 }
386 }
387 }
388 else
389 {
390 for (tdir = DI_SOUTHEAST; tdir != (dirtype_e)(DI_EAST - 1); tdir = (dirtype_e)((int)tdir-1))
391 {
392 if (tdir != turnaround)
393 {
394 object->movedir = tdir;
395
396 if (TryWalk(object))
397 return;
398 }
399 }
400 }
401
402 if (turnaround != DI_NODIR)
403 {
404 object->movedir = turnaround;
405 if (TryWalk(object))
406 return;
407 }
408
409 // cannot move
410 object->movedir = DI_NODIR;
411 }
412
413 //
414 // Range is angle range on either side of eyes, 90 degrees for normal
415 // view, 180 degrees for total sight in all dirs.
416 //
417 // Returns true if a player is targeted.
418 //
P_LookForPlayers(mobj_t * actor,angle_t range)419 bool P_LookForPlayers(mobj_t * actor, angle_t range)
420 {
421 int c;
422 int stop;
423 player_t *player;
424 angle_t an;
425 float dist;
426
427 c = 0;
428 stop = (actor->lastlook - 1 + MAXPLAYERS) % MAXPLAYERS;
429
430 for (; actor->lastlook != stop; actor->lastlook = (actor->lastlook + 1) % MAXPLAYERS)
431 {
432 player = players[actor->lastlook];
433
434 if (!player)
435 continue;
436
437 SYS_ASSERT(player->mo);
438
439 // done looking ?
440 if (c++ >= 2)
441 break;
442
443 // dead ?
444 if (player->health <= 0)
445 continue;
446
447 // on the same team ?
448 if ((actor->side & player->mo->side) != 0)
449 continue;
450
451 if (range < ANG180)
452 {
453 an = R_PointToAngle(actor->x, actor->y, player->mo->x,
454 player->mo->y) - actor->angle;
455
456 if (range <= an && an <= (range * -1))
457 {
458 // behind back.
459 // if real close, react anyway
460 dist = P_ApproxDistance(player->mo->x - actor->x,
461 player->mo->y - actor->y);
462
463 if (dist > MELEERANGE)
464 continue;
465 }
466 }
467
468 // out of sight ?
469 if (!P_CheckSight(actor, player->mo))
470 continue;
471
472 actor->SetTarget(player->mo);
473 return true;
474 }
475
476 return false;
477 }
478
479 //
480 // BOSS-BRAIN HANDLING
481 //
482
483 //
484 // -AJA- Savegames: we assume that the spit-spot objects never
485 // disappear, or new ones appear. After all, they have to be
486 // there to be the target of the cubes. This means we don't
487 // need to save anything: the set of shoot-spots will be
488 // regenerated after the loadgame when the BrainShooter next
489 // tries to shoot a cube.
490
491 shoot_spot_info_t brain_spots = { -1, NULL };
492
P_LookForShootSpots(const mobjtype_c * spot_type)493 void P_LookForShootSpots(const mobjtype_c *spot_type)
494 {
495 int i;
496 mobj_t *cur;
497
498 brain_spots.number = 0;
499
500 // count them
501 for (cur=mobjlisthead; cur != NULL; cur=cur->next)
502 {
503 if (cur->info == spot_type)
504 brain_spots.number++;
505 }
506
507 if (brain_spots.number == 0)
508 {
509 I_Warning("No [%s] objects found for BossBrain shooter.\n",
510 spot_type->name.c_str());
511 return;
512 }
513
514 // create the spots
515 brain_spots.targets = new mobj_t* [brain_spots.number];
516
517 for (cur=mobjlisthead, i=0; cur != NULL; cur=cur->next)
518 {
519 if (cur->info == spot_type)
520 brain_spots.targets[i++] = cur;
521 }
522
523 SYS_ASSERT(i == brain_spots.number);
524 }
525
P_FreeShootSpots(void)526 void P_FreeShootSpots(void)
527 {
528 if (brain_spots.number < 0)
529 return;
530
531 if (brain_spots.targets)
532 {
533 SYS_ASSERT(brain_spots.targets);
534
535 delete[] brain_spots.targets;
536 }
537
538 brain_spots.number = -1;
539 brain_spots.targets = NULL;
540 }
541
SpawnDeathMissile(mobj_t * source,float x,float y,float z)542 static void SpawnDeathMissile(mobj_t *source, float x, float y, float z)
543 {
544 const mobjtype_c *info;
545 mobj_t *th;
546
547 info = mobjtypes.Lookup("BRAIN_DEATH_MISSILE");
548
549 th = P_MobjCreateObject(x, y, z, info);
550 if (th->info->seesound)
551 S_StartFX(th->info->seesound, P_MobjGetSfxCategory(th), th);
552
553 th->SetRealSource(source);
554
555 th->mom.x = (x - source->x) / 50.0f;
556 th->mom.y = -0.25f;
557 th->mom.z = (z - source->z) / 50.0f;
558
559 th->tics -= M_Random() & 7;
560
561 if (th->tics < 1)
562 th->tics = 1;
563 }
564
P_ActBrainScream(mobj_t * bossbrain)565 void P_ActBrainScream(mobj_t * bossbrain)
566 {
567 // The brain and his pain...
568
569 float x, y, z;
570 float min_x, max_x;
571
572 min_x = bossbrain->x - 280.0f;
573 max_x = bossbrain->x + 280.0f;
574
575 for (x = min_x; x < max_x; x += 4)
576 {
577 y = bossbrain->y - 320.0f;
578 z = bossbrain->z + (P_Random() - 180.0f) * 2.0f;
579
580 SpawnDeathMissile(bossbrain, x, y, z);
581 }
582
583 if (bossbrain->info->deathsound)
584 S_StartFX(bossbrain->info->deathsound, P_MobjGetSfxCategory(bossbrain), bossbrain);
585 }
586
P_ActBrainMissileExplode(mobj_t * mo)587 void P_ActBrainMissileExplode(mobj_t * mo)
588 {
589 float x, y, z;
590
591 if (! mo->source)
592 return;
593
594 x = mo->source->x + (P_Random() - 128.0f) * 4.0f;
595 y = mo->source->y - 320.0f;
596 z = mo->source->z + (P_Random() - 180.0f) * 2.0f;
597
598 SpawnDeathMissile(mo->source, x, y, z);
599 }
600
P_ActBrainDie(mobj_t * bossbrain)601 void P_ActBrainDie(mobj_t * bossbrain)
602 {
603 G_ExitLevel(TICRATE);
604 }
605
P_ActBrainSpit(mobj_t * shooter)606 void P_ActBrainSpit(mobj_t * shooter)
607 {
608 static int easy = 0;
609
610 // when skill is easy, only fire every second cube.
611
612 easy ^= 1;
613
614 if (gameskill <= sk_easy && (!easy))
615 return;
616
617 // shoot out a cube
618 P_ActRangeAttack(shooter);
619 }
620
621
P_ActCubeSpawn(mobj_t * cube)622 void P_ActCubeSpawn(mobj_t * cube)
623 {
624 mobj_t *targ;
625 mobj_t *newmobj;
626 const mobjtype_c *type;
627 int r;
628
629 targ = cube->target;
630
631 // -AJA- 2007/07/28: workaround for DeHackEd patches using S_SPAWNFIRE
632 if (!targ || !cube->currentattack ||
633 cube->currentattack->attackstyle != ATK_SHOOTTOSPOT)
634 return;
635
636 // Randomly select monster to spawn.
637 r = P_Random();
638
639 // Probability distribution (kind of :)),
640 // decreasing likelihood.
641 if (r < 50)
642 type = mobjtypes.Lookup("IMP");
643 else if (r < 90)
644 type = mobjtypes.Lookup("DEMON");
645 else if (r < 120)
646 type = mobjtypes.Lookup("SPECTRE");
647 else if (r < 130)
648 type = mobjtypes.Lookup("PAIN_ELEMENTAL");
649 else if (r < 160)
650 type = mobjtypes.Lookup("CACODEMON");
651 else if (r < 162)
652 type = mobjtypes.Lookup("ARCHVILE");
653 else if (r < 172)
654 type = mobjtypes.Lookup("REVENANT");
655 else if (r < 192)
656 type = mobjtypes.Lookup("ARACHNOTRON");
657 else if (r < 222)
658 type = mobjtypes.Lookup("MANCUBUS");
659 else if (r < 246)
660 type = mobjtypes.Lookup("HELL_KNIGHT");
661 else
662 type = mobjtypes.Lookup("BARON_OF_HELL");
663
664 newmobj = P_MobjCreateObject(targ->x, targ->y, targ->z, type);
665
666 if (P_LookForPlayers(newmobj, ANG180))
667 {
668 if (newmobj->info->chase_state)
669 P_SetMobjState(newmobj, newmobj->info->chase_state);
670 else
671 P_SetMobjState(newmobj, newmobj->info->spawn_state);
672 }
673
674 // telefrag anything in this spot
675 P_TeleportMove(newmobj, newmobj->x, newmobj->y, newmobj->z);
676 }
677
P_ActPlayerScream(mobj_t * mo)678 void P_ActPlayerScream(mobj_t * mo)
679 {
680 sfx_t *sound;
681
682 sound = mo->info->deathsound;
683
684 if ((mo->health < -50) && (W_CheckNumForName("DSPDIEHI") >= 0))
685 {
686 // if the player dies and unclipped health is < -50%...
687
688 sound = sfxdefs.GetEffect("PDIEHI");
689 }
690
691 S_StartFX(sound, P_MobjGetSfxCategory(mo), mo);
692 }
693
694
695 //--- editor settings ---
696 // vi:ts=4:sw=4:noexpandtab
697