1 /** @file p_mobj.c World map object interactions.
2 *
3 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2005-2013 Daniel Swanson <danij@dengine.net>
5 * @authors Copyright © 2003-2005 Samuel Villarreal <svkaiser@gmail.com>
6 * @authors Copyright © 1999 by Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman (PrBoom 2.2.6)
7 * @authors Copyright © 1999-2000 by Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze (PrBoom 2.2.6)
8 * @authors Copyright © 1993-1996 by id Software, Inc.
9 *
10 * @par License
11 * GPL: http://www.gnu.org/licenses/gpl.html
12 *
13 * <small>This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by the
15 * Free Software Foundation; either version 2 of the License, or (at your
16 * option) any later version. This program is distributed in the hope that it
17 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
18 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
19 * Public License for more details. You should have received a copy of the GNU
20 * General Public License along with this program; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22 * 02110-1301 USA</small>
23 */
24
25 #ifdef MSVC
26 # pragma optimize("g", off)
27 #endif
28
29 #include <math.h>
30 #include <stdio.h>
31 #include <string.h>
32
33 #include "jdoom64.h"
34
35 #include "dmu_lib.h"
36 #include "hu_stuff.h"
37 #include "g_common.h"
38 #include "p_map.h"
39 #include "p_terraintype.h"
40 #include "player.h"
41 #include "p_tick.h"
42 #include "p_actor.h"
43 #include "p_start.h"
44
45 #define VANISHTICS (2*TICSPERSEC)
46 #define SPAWNFADETICS (1*TICSPERSEC)
47
48 #define MAX_BOB_OFFSET (8)
49
P_ExplodeMissile(mobj_t * mo)50 void P_ExplodeMissile(mobj_t *mo)
51 {
52 mo->mom[MX] = mo->mom[MY] = mo->mom[MZ] = 0;
53
54 P_MobjChangeState(mo, P_GetState(mo->type, SN_DEATH));
55
56 mo->tics -= P_Random() & 3;
57
58 if(mo->tics < 1)
59 mo->tics = 1;
60
61 if(mo->flags & MF_MISSILE)
62 {
63 mo->flags &= ~MF_MISSILE;
64 mo->flags |= MF_VIEWALIGN;
65 // Remove the brightshadow flag.
66 if(mo->flags & MF_BRIGHTSHADOW)
67 mo->flags &= ~MF_BRIGHTSHADOW;
68 if(mo->flags & MF_BRIGHTEXPLODE)
69 mo->flags |= MF_BRIGHTSHADOW;
70 }
71
72 if(mo->info->deathSound)
73 S_StartSound(mo->info->deathSound, mo);
74 }
75
P_FloorBounceMissile(mobj_t * mo)76 void P_FloorBounceMissile(mobj_t *mo)
77 {
78 mo->mom[MZ] = -mo->mom[MZ];
79 P_MobjChangeState(mo, P_GetState(mo->type, SN_DEATH));
80 }
81
P_MobjMoveXY(mobj_t * mo)82 void P_MobjMoveXY(mobj_t *mo)
83 {
84 coord_t pos[3], mom[3];
85 //player_t *player;
86 dd_bool largeNegative;
87
88 // $democam: cameramen have their own movement code
89 if(P_CameraXYMovement(mo))
90 return;
91
92 if(INRANGE_OF(mo->mom[MX], 0, NOMOM_THRESHOLD) &&
93 INRANGE_OF(mo->mom[MY], 0, NOMOM_THRESHOLD))
94 {
95 if(mo->flags & MF_SKULLFLY)
96 {
97 // The skull slammed into something.
98 mo->flags &= ~MF_SKULLFLY;
99 mo->mom[MX] = mo->mom[MY] = mo->mom[MZ] = 0;
100
101 P_MobjChangeState(mo, P_GetState(mo->type, SN_SPAWN));
102 }
103
104 return;
105 }
106
107 mom[MX] = MINMAX_OF(-MAXMOM, mo->mom[MX], MAXMOM);
108 mom[MY] = MINMAX_OF(-MAXMOM, mo->mom[MY], MAXMOM);
109 mo->mom[MX] = mom[MX];
110 mo->mom[MY] = mom[MY];
111
112 //player = mo->player;
113
114 do
115 {
116 /**
117 * DOOM.exe bug fix:
118 * Large negative displacements were never considered. This explains the
119 * tendency for Mancubus fireballs to pass through walls.
120 */
121
122 largeNegative = false;
123 if(!cfg.moveBlock && (mom[MX] < -MAXMOMSTEP || mom[MY] < -MAXMOMSTEP))
124 {
125 // Make an exception for "north-only wallrunning".
126 if(!(cfg.wallRunNorthOnly && mo->wallRun))
127 largeNegative = true;
128 }
129
130 if(largeNegative || mom[MX] > MAXMOMSTEP || mom[MY] > MAXMOMSTEP)
131 {
132 pos[VX] = mo->origin[VX] + mom[VX] / 2;
133 pos[VY] = mo->origin[VY] + mom[VY] / 2;
134 mom[VX] /= 2;
135 mom[VY] /= 2;
136 }
137 else
138 {
139 pos[VX] = mo->origin[VX] + mom[MX];
140 pos[VY] = mo->origin[VY] + mom[MY];
141 mom[MX] = mom[MY] = 0;
142 }
143
144 // If mobj was wallrunning - stop.
145 if(mo->wallRun)
146 mo->wallRun = false;
147
148 // $dropoff_fix.
149 if(!P_TryMoveXY(mo, pos[VX], pos[VY], true, false))
150 {
151 // Blocked move.
152 if(mo->flags2 & MF2_SLIDE)
153 {
154 // Try to slide along it.
155 P_SlideMove(mo);
156 }
157 else if(mo->flags & MF_MISSILE)
158 {
159 Sector* backSec;
160
161 /// @kludge: Prevent missiles exploding against the sky.
162 if(tmCeilingLine &&
163 (backSec = P_GetPtrp(tmCeilingLine, DMU_BACK_SECTOR)))
164 {
165 world_Material* mat = P_GetPtrp(backSec, DMU_CEILING_MATERIAL);
166
167 if((P_GetIntp(mat, DMU_FLAGS) & MATF_SKYMASK) &&
168 mo->origin[VZ] > P_GetDoublep(backSec, DMU_CEILING_HEIGHT))
169 {
170 P_MobjRemove(mo, false);
171 return;
172 }
173 }
174
175 if(tmFloorLine &&
176 (backSec = P_GetPtrp(tmFloorLine, DMU_BACK_SECTOR)))
177 {
178 world_Material* mat = P_GetPtrp(backSec, DMU_FLOOR_MATERIAL);
179
180 if((P_GetIntp(mat, DMU_FLAGS) & MATF_SKYMASK) &&
181 mo->origin[VZ] < P_GetDoublep(backSec, DMU_FLOOR_HEIGHT))
182 {
183 P_MobjRemove(mo, false);
184 return;
185 }
186 }
187 // kludge end.
188
189 P_ExplodeMissile(mo);
190 }
191 else
192 {
193 mo->mom[MX] = mo->mom[MY] = 0;
194 }
195 }
196 } while(!INRANGE_OF(mom[MX], 0, NOMOM_THRESHOLD) ||
197 !INRANGE_OF(mom[MY], 0, NOMOM_THRESHOLD));
198
199 // Slow down.
200 Mobj_XYMoveStopping(mo);
201 }
202
203 /*
204 static int PIT_Splash(Sector* sector, void* parameters)
205 {
206 mobj_t* mo = (mobj_t*)parameters;
207 coord_t floorheight = P_GetDoublep(sector, DMU_FLOOR_HEIGHT);
208
209 // Is the mobj touching the floor of this sector?
210 if(mo->origin[VZ] < floorheight &&
211 mo->origin[VZ] + mo->height / 2 > floorheight)
212 {
213 /// @todo Play a sound, spawn a generator, etc.
214 }
215
216 // Continue checking.
217 return false;
218 }
219 */
220
P_HitFloor(mobj_t * mo)221 void P_HitFloor(mobj_t* mo)
222 {
223 //Mobj_TouchedSectorsIterator(mo, PIT_Splash, mo);
224 }
225
P_MobjMoveZ(mobj_t * mo)226 void P_MobjMoveZ(mobj_t *mo)
227 {
228 coord_t gravity = XS_Gravity(Mobj_Sector(mo));
229 coord_t dist, delta;
230
231 // $democam: cameramen get special z movement.
232 if(P_CameraZMovement(mo))
233 return;
234
235 // $voodoodolls: Check for smooth step up unless a voodoo doll.
236 if(mo->player && mo->player->plr->mo == mo &&
237 mo->origin[VZ] < mo->floorZ)
238 {
239 mo->player->viewHeight -= mo->floorZ - mo->origin[VZ];
240 mo->player->viewHeightDelta =
241 (cfg.common.plrViewHeight - mo->player->viewHeight) / 8;
242 }
243
244 // Adjust height.
245 mo->origin[VZ] += mo->mom[MZ];
246
247 if((mo->flags2 & MF2_FLY) &&
248 mo->onMobj && mo->origin[VZ] > mo->onMobj->origin[VZ] + mo->onMobj->height)
249 mo->onMobj = NULL; // We were on a mobj, we are NOT now.
250
251 if(!((mo->flags ^ MF_FLOAT) & (MF_FLOAT | MF_SKULLFLY | MF_INFLOAT)) &&
252 mo->target && !P_MobjIsCamera(mo->target))
253 {
254 // Float down towards target if too close.
255 dist = M_ApproxDistance(mo->origin[VX] - mo->target->origin[VX],
256 mo->origin[VY] - mo->target->origin[VY]);
257
258 //delta = (mo->target->origin[VZ] + (mo->height / 2)) - mo->origin[VZ];
259 delta = (mo->target->origin[VZ] + mo->target->height / 2) -
260 (mo->origin[VZ] + mo->height / 2);
261
262 // Don't go INTO the target.
263 if(!(dist < mo->radius + mo->target->radius &&
264 fabs(delta) < mo->height + mo->target->height))
265 {
266 if(delta < 0 && dist < -(delta * 3))
267 {
268 mo->origin[VZ] -= FLOATSPEED;
269 P_MobjSetSRVOZ(mo, -FLOATSPEED);
270 }
271 else if(delta > 0 && dist < (delta * 3))
272 {
273 mo->origin[VZ] += FLOATSPEED;
274 P_MobjSetSRVOZ(mo, FLOATSPEED);
275 }
276 }
277 }
278
279 // Do some fly-bobbing.
280 if(mo->player && (mo->flags2 & MF2_FLY) && mo->origin[VZ] > mo->floorZ &&
281 !mo->onMobj && (mapTime & 2))
282 {
283 mo->origin[VZ] += FIX2FLT(finesine[(FINEANGLES / 20 * mapTime >> 2) & FINEMASK]);
284 }
285
286 // Clip movement. Another thing?
287 // jd64 >
288 if(mo->origin[VZ] <= mo->floorZ && (mo->flags & MF_MISSILE))
289 {
290 // Hit the floor
291 mo->origin[VZ] = mo->floorZ;
292 P_ExplodeMissile(mo);
293 return;
294 }
295 // < jd64
296
297 // Clip movement. Another thing?
298 if(mo->onMobj && mo->origin[VZ] <= mo->onMobj->origin[VZ] + mo->onMobj->height)
299 {
300 if(mo->mom[MZ] < 0)
301 {
302 if(mo->player && mo->mom[MZ] < -gravity * 8 &&
303 !(mo->flags2 & MF2_FLY))
304 {
305 // Squat down.
306 // Decrease viewheight for a moment after hitting the ground
307 // (hard), and utter appropriate sound.
308 mo->player->viewHeightDelta = mo->mom[MZ] / 8;
309
310 if(mo->player->health > 0)
311 S_StartSound(SFX_OOF, mo);
312 }
313
314 mo->mom[MZ] = 0;
315 }
316
317 if(mo->mom[MZ] == 0)
318 mo->origin[VZ] = mo->onMobj->origin[VZ] + mo->onMobj->height;
319
320 if((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP))
321 {
322 P_ExplodeMissile(mo);
323 return;
324 }
325 }
326
327 // jd64 >
328 // MotherDemon's Fire attacks can climb up/down stairs
329 // DJS - FIXME!
330 #if 0
331 if((mo->flags & MF_MISSILE) && (mo->type == MT_FIREEND))
332 {
333 mo->origin[VZ] = mo->floorZ;
334
335 if(mo->type == MT_FIREEND)
336 {
337 return;
338 }
339 else
340 {
341 P_ExplodeMissile(mo);
342 return;
343 }
344 }
345 #endif
346 // < d64tc
347
348 // The floor.
349 if(mo->origin[VZ] <= mo->floorZ)
350 { // Hit the floor.
351 dd_bool movingDown;
352
353 // Note (id):
354 // somebody left this after the setting momz to 0,
355 // kinda useless there.
356 //
357 // cph - This was the a bug in the linuxdoom-1.10 source which
358 // caused it not to sync Doom 2 v1.9 demos. Someone
359 // added the above comment and moved up the following code. So
360 // demos would desync in close lost soul fights.
361 // Note that this only applies to original Doom 1 or Doom2 demos - not
362 // Final Doom and Ultimate Doom. So we test demo_compatibility *and*
363 // gameMission. (Note we assume that Doom1 is always Ult Doom, which
364 // seems to hold for most published demos.)
365 //
366 // fraggle - cph got the logic here slightly wrong. There are three
367 // versions of Doom 1.9:
368 //
369 // * The version used in registered doom 1.9 + doom2 - no bounce
370 // * The version used in ultimate doom - has bounce
371 // * The version used in final doom - has bounce
372 //
373 // So we need to check that this is either retail or commercial
374 // (but not doom2)
375 int correctLostSoulBounce = true;
376
377 if(correctLostSoulBounce && (mo->flags & MF_SKULLFLY))
378 {
379 // The skull slammed into something.
380 mo->mom[MZ] = -mo->mom[MZ];
381 }
382
383 if((movingDown = (mo->mom[MZ] < 0)))
384 {
385 if(mo->player && mo->player->plr->mo == mo &&
386 mo->mom[MZ] < -gravity * 8 && !(mo->flags2 & MF2_FLY))
387 {
388 // Squat down.
389 // Decrease viewheight for a moment after hitting the ground
390 // (hard), and utter appropriate sound.
391 mo->player->viewHeightDelta = mo->mom[MZ] / 8;
392 mo->player->jumpTics = 10;
393
394 /**
395 * DOOM bug:
396 * Dead players would grunt when hitting the ground (e.g.,
397 * after an archvile attack).
398 */
399 if(mo->player->health > 0)
400 S_StartSound(SFX_OOF, mo);
401 }
402 }
403
404 mo->origin[VZ] = mo->floorZ;
405
406 if(movingDown)
407 P_HitFloor(mo);
408
409 /**
410 * See lost soul bouncing comment above. We need this here for bug
411 * compatibility with original Doom2 v1.9 - if a soul is charging
412 * and hit by a raising floor this would incorrectly reverse it's
413 * Y momentum.
414 */
415 if(!correctLostSoulBounce && (mo->flags & MF_SKULLFLY))
416 mo->mom[MZ] = -mo->mom[MZ];
417
418 if(!((mo->flags ^ MF_MISSILE) & (MF_MISSILE | MF_NOCLIP)))
419 {
420 if(mo->flags2 & MF2_FLOORBOUNCE)
421 {
422 P_FloorBounceMissile(mo);
423 return;
424 }
425 else
426 {
427 P_ExplodeMissile(mo);
428 return;
429 }
430 }
431
432 if(movingDown && mo->mom[MZ] < 0)
433 mo->mom[MZ] = 0;
434 }
435 else if(mo->flags2 & MF2_LOGRAV)
436 {
437 if(mo->mom[MZ] == 0)
438 mo->mom[MZ] = -(gravity / 8) * 2;
439 else
440 mo->mom[MZ] -= (gravity / 8);
441 }
442 else if(!(mo->flags & MF_NOGRAVITY))
443 {
444 if(mo->mom[MZ] == 0)
445 mo->mom[MZ] = -gravity * 2;
446 else
447 mo->mom[MZ] -= gravity;
448 }
449
450 if(mo->origin[VZ] + mo->height > mo->ceilingZ)
451 { // Hit the ceiling.
452 if(mo->mom[MZ] > 0)
453 mo->mom[MZ] = 0;
454
455 mo->origin[VZ] = mo->ceilingZ - mo->height;
456
457 if(mo->flags & MF_SKULLFLY)
458 { // The skull slammed into something.
459 mo->mom[MZ] = -mo->mom[MZ];
460 }
461
462 if(!((mo->flags ^ MF_MISSILE) & (MF_MISSILE | MF_NOCLIP)))
463 {
464 // Don't explode against sky.
465 if(P_GetIntp(P_GetPtrp(Mobj_Sector(mo), DMU_CEILING_MATERIAL),
466 DMU_FLAGS) & MATF_SKYMASK)
467 {
468 P_MobjRemove(mo, false);
469 }
470 else
471 {
472 P_ExplodeMissile(mo);
473 }
474 }
475 }
476 }
477
P_NightmareRespawn(mobj_t * mobj)478 void P_NightmareRespawn(mobj_t* mobj)
479 {
480 mobj_t* mo;
481
482 // Something is occupying it's position?
483 if(!P_CheckPositionXY(mobj, mobj->spawnSpot.origin[VX],
484 mobj->spawnSpot.origin[VY]))
485 return; // No respwan.
486
487 if((mo = P_SpawnMobj(mobj->type, mobj->spawnSpot.origin,
488 mobj->spawnSpot.angle, mobj->spawnSpot.flags)))
489 {
490 mo->reactionTime = 18;
491
492 // Spawn a teleport fog at old spot.
493 if((mo = P_SpawnMobjXYZ(MT_TFOG, mobj->origin[VX], mobj->origin[VY], 0,
494 mobj->angle, MSF_Z_FLOOR)))
495 S_StartSound(SFX_TELEPT, mo);
496
497 // Spawn a teleport fog at the new spot.
498 if((mo = P_SpawnMobj(MT_TFOG, mobj->spawnSpot.origin,
499 mobj->spawnSpot.angle,
500 mobj->spawnSpot.flags)))
501 S_StartSound(SFX_TELEPT, mo);
502 }
503
504 // Remove the old monster.
505 P_MobjRemove(mobj, true);
506 }
507
P_MobjThinker(void * mobjThinkerPtr)508 void P_MobjThinker(void *mobjThinkerPtr)
509 {
510 mobj_t *mobj = mobjThinkerPtr;
511
512 if(mobj->ddFlags & DDMF_REMOTE)
513 return; // Remote mobjs are handled separately.
514
515 // The first three bits of the selector special byte contain a
516 // relative health level.
517 P_UpdateHealthBits(mobj);
518
519 #if __JHERETIC__
520 // Lightsources must stay where they're hooked.
521 if(mobj->type == MT_LIGHTSOURCE)
522 {
523 if(mobj->moveDir > 0)
524 mobj->origin[VZ] = P_GetDoublep(Mobj_Sector(mobj), DMU_FLOOR_HEIGHT);
525 else
526 mobj->origin[VZ] = P_GetDoublep(Mobj_Sector(mobj), DMU_CEILING_HEIGHT);
527
528 mobj->origin[VZ] += FIX2FLT(mobj->moveDir);
529 return;
530 }
531 #endif
532
533 // Handle X and Y momentums.
534 if(NON_ZERO(mobj->mom[MX]) || NON_ZERO(mobj->mom[MY]) || (mobj->flags & MF_SKULLFLY))
535 {
536 P_MobjMoveXY(mobj);
537
538 //// @todo decent NOP/NULL/Nil function pointer please.
539 if(mobj->thinker.function == (thinkfunc_t) NOPFUNC)
540 return; // Mobj was removed.
541 }
542
543 if(mobj->flags2 & MF2_FLOATBOB)
544 { // Floating item bobbing motion.
545 // Keep it on the floor.
546 mobj->origin[VZ] = mobj->floorZ;
547 mobj->floorClip = 0;
548
549 if(mobj->floorClip < -MAX_BOB_OFFSET)
550 {
551 // We don't want it going through the floor.
552 mobj->floorClip = -MAX_BOB_OFFSET;
553 }
554 }
555 else if(!FEQUAL(mobj->origin[VZ], mobj->floorZ) || NON_ZERO(mobj->mom[MZ]))
556 {
557 P_MobjMoveZ(mobj);
558 if(mobj->thinker.function != (thinkfunc_t) P_MobjThinker) // Must've been removed.
559 return; // Mobj was removed.
560 }
561 // Non-sentient objects at rest.
562 else if(!(mobj->mom[MX] == 0 && mobj->mom[MY] == 0) && !sentient(mobj) &&
563 !(mobj->player) && !((mobj->flags & MF_CORPSE) &&
564 cfg.slidingCorpses))
565 {
566 // Objects fall off ledges if they are hanging off slightly push
567 // off of ledge if hanging more than halfway off
568
569 if(mobj->origin[VZ] > mobj->dropOffZ && // Only objects contacting dropoff.
570 !(mobj->flags & MF_NOGRAVITY) && cfg.fallOff)
571 {
572 P_ApplyTorque(mobj);
573 }
574 else
575 {
576 mobj->intFlags &= ~MIF_FALLING;
577 mobj->gear = 0; // Reset torque.
578 }
579 }
580
581 if(cfg.slidingCorpses)
582 {
583 if(((mobj->flags & MF_CORPSE)? mobj->origin[VZ] > mobj->dropOffZ :
584 mobj->origin[VZ] - mobj->dropOffZ > 24) && // Only objects contacting drop off
585 !(mobj->flags & MF_NOGRAVITY)) // Only objects which fall.
586 {
587 P_ApplyTorque(mobj); // Apply torque.
588 }
589 else
590 {
591 mobj->intFlags &= ~MIF_FALLING;
592 mobj->gear = 0; // Reset torque.
593 }
594 }
595
596 // $vanish: dead monsters disappear after some time.
597 if(cfg.corpseTime && (mobj->flags & MF_CORPSE) && mobj->corpseTics != -1)
598 {
599 if(++mobj->corpseTics < cfg.corpseTime * TICSPERSEC)
600 {
601 mobj->translucency = 0; // Opaque.
602 }
603 else if(mobj->corpseTics < cfg.corpseTime * TICSPERSEC + VANISHTICS)
604 {
605 // Translucent during vanishing.
606 mobj->translucency =
607 ((mobj->corpseTics -
608 cfg.corpseTime * TICSPERSEC) * 255) / VANISHTICS;
609 }
610 else
611 {
612 // Too long; get rid of the corpse.
613 mobj->corpseTics = -1;
614 return;
615 }
616 }
617
618 // jd64 >
619 // Fade monsters upon spawning.
620 if(mobj->intFlags & MIF_FADE)
621 {
622 if(++mobj->spawnFadeTics < SPAWNFADETICS)
623 {
624 mobj->translucency = MINMAX_OF(0, 255 -
625 255 * mobj->spawnFadeTics / SPAWNFADETICS, 255);
626 }
627 else
628 {
629 mobj->intFlags &= ~MIF_FADE;
630 mobj->translucency = 0;
631 }
632 }
633 // < d64tc
634
635 // Cycle through states, calling action functions at transitions.
636 if(mobj->tics != -1)
637 {
638 mobj->tics--;
639
640 P_MobjAngleSRVOTicker(mobj); // "angle-servo"; smooth actor turning.
641
642 // You can cycle through multiple STATES in a tic.
643 if(!mobj->tics)
644 {
645 P_MobjClearSRVO(mobj);
646 if(!P_MobjChangeState(mobj, mobj->state->nextState))
647 return; // Freed itself.
648 }
649 }
650 else if(!IS_CLIENT)
651 {
652 // Check for nightmare respawn.
653 if(!(mobj->flags & MF_COUNTKILL))
654 return;
655
656 if(!gfw_Rule(respawnMonsters))
657 return;
658
659 mobj->moveCount++;
660
661 if(mobj->moveCount < 12 * 35)
662 return;
663
664 if(mapTime & 31)
665 return;
666
667 if(P_Random() > 4)
668 return;
669
670 P_NightmareRespawn(mobj);
671 }
672 }
673
P_SpawnMobjXYZ(mobjtype_t type,coord_t x,coord_t y,coord_t z,angle_t angle,int spawnFlags)674 mobj_t *P_SpawnMobjXYZ(mobjtype_t type, coord_t x, coord_t y, coord_t z, angle_t angle,
675 int spawnFlags)
676 {
677 mobjinfo_t *info;
678 int ddflags = 0;
679 coord_t space;
680 mobj_t *mo;
681
682 if(type < MT_FIRST || type >= Get(DD_NUMMOBJTYPES))
683 {
684 App_Log(DE2_MAP_ERROR, "Attempt to spawn unknown mobj type %i", type);
685 return NULL;
686 }
687
688 info = &MOBJINFO[type];
689
690 // Clients only spawn local objects.
691 if(!(info->flags & MF_LOCAL) && IS_CLIENT)
692 return NULL;
693
694 // Not for deathmatch?
695 if(gfw_Rule(deathmatch) && (info->flags & MF_NOTDMATCH))
696 return NULL;
697
698 // Check for specific disabled objects.
699 if(IS_NETGAME)
700 {
701 // Cooperative weapons?
702 if(cfg.noCoopWeapons && !gfw_Rule(deathmatch) && type >= MT_CLIP &&
703 type <= MT_SUPERSHOTGUN)
704 return NULL;
705
706 /**
707 * @todo cfg.noCoopAnything: Exactly which objects is this supposed to
708 * prevent spawning? (at least not MT_PLAYER*...). -jk
709 */
710 #if 0
711 // Don't spawn any special objects in coop?
712 if(cfg.noCoopAnything && !deathmatch)
713 return NULL;
714 #endif
715
716 // BFG disabled in netgames?
717 if(cfg.noNetBFG && type == MT_MISC25)
718 return NULL;
719 }
720
721 // Don't spawn any monsters?
722 if(gfw_Rule(noMonsters) && ((info->flags & MF_COUNTKILL) || type == MT_SKULL))
723 return NULL;
724
725 if(info->flags & MF_SOLID)
726 ddflags |= DDMF_SOLID;
727 if(info->flags2 & MF2_DONTDRAW)
728 ddflags |= DDMF_DONTDRAW;
729
730 mo = Mobj_CreateXYZ(P_MobjThinker, x, y, z, angle, info->radius,
731 info->height, ddflags);
732 mo->type = type;
733 mo->info = info;
734 mo->flags = info->flags;
735 mo->flags2 = info->flags2;
736 mo->flags3 = info->flags3;
737 mo->damage = info->damage;
738 mo->health = info->spawnHealth * (IS_NETGAME ? cfg.common.netMobHealthModifier : 1);
739 mo->moveDir = DI_NODIR;
740
741 // Spectres get selector = 1.
742 mo->selector = (type == MT_SHADOWS)? 1 : 0;
743 P_UpdateHealthBits(mo); // Set the health bits of the selector.
744
745 mo->reactionTime = info->reactionTime;
746
747 mo->lastLook = P_Random() % MAXPLAYERS;
748
749 // Must link before setting state (ID assigned for the mo).
750 Mobj_SetState(mo, P_GetState(mo->type, SN_SPAWN));
751
752 // Set BSP leaf and/or block links.
753 P_MobjLink(mo);
754
755 mo->floorZ = P_GetDoublep(Mobj_Sector(mo), DMU_FLOOR_HEIGHT);
756 mo->dropOffZ = mo->floorZ;
757 mo->ceilingZ = P_GetDoublep(Mobj_Sector(mo), DMU_CEILING_HEIGHT);
758
759 if((spawnFlags & MSF_Z_CEIL) || (info->flags & MF_SPAWNCEILING))
760 {
761 mo->origin[VZ] = mo->ceilingZ - mo->info->height - z;
762 }
763 else if((spawnFlags & MSF_Z_RANDOM) || (info->flags2 & MF2_SPAWNFLOAT))
764 {
765 space = mo->ceilingZ - mo->info->height - mo->floorZ;
766 if(space > 48)
767 {
768 space -= 40;
769 mo->origin[VZ] = ((space * P_Random()) / 256) + mo->floorZ + 40;
770 }
771 else
772 {
773 mo->origin[VZ] = mo->floorZ;
774 }
775 }
776 else if(spawnFlags & MSF_Z_FLOOR)
777 {
778 mo->origin[VZ] = mo->floorZ + z;
779 }
780
781 /*if(spawnFlags & MTF_FLOAT)
782 {
783 mo->origin[VZ] += 96;
784 mo->flags |= (MF_FLOAT | MF_NOGRAVITY);
785 }*/
786
787 if(spawnFlags & MSF_DEAF)
788 mo->flags |= MF_AMBUSH;
789
790 mo->floorClip = 0;
791
792 if((mo->flags2 & MF2_FLOORCLIP) &&
793 FEQUAL(mo->origin[VZ], P_GetDoublep(Mobj_Sector(mo), DMU_FLOOR_HEIGHT)))
794 {
795 terraintype_t const *tt = P_MobjFloorTerrain(mo);
796 if(tt->flags & TTF_FLOORCLIP)
797 {
798 mo->floorClip = 10;
799 }
800 }
801
802 /*if(spawnFlags & MTF_WALKOFF)
803 mo->flags |= (MF_FLOAT | MF_DROPOFF);
804
805 if(spawnFlags & MTF_TRANSLUCENT)
806 mo->flags |= MF_SHADOW;*/
807
808 // Copy spawn attributes to the new mobj.
809 mo->spawnSpot.origin[VX] = x;
810 mo->spawnSpot.origin[VY] = y;
811 mo->spawnSpot.origin[VZ] = z;
812 mo->spawnSpot.angle = angle;
813 mo->spawnSpot.flags = spawnFlags;
814
815 return mo;
816 }
817
P_SpawnMobj(mobjtype_t type,coord_t const pos[3],angle_t angle,int spawnFlags)818 mobj_t *P_SpawnMobj(mobjtype_t type, coord_t const pos[3], angle_t angle, int spawnFlags)
819 {
820 return P_SpawnMobjXYZ(type, pos[VX], pos[VY], pos[VZ], angle, spawnFlags);
821 }
822
P_SpawnBlood(coord_t x,coord_t y,coord_t z,int damage,angle_t angle)823 void P_SpawnBlood(coord_t x, coord_t y, coord_t z, int damage, angle_t angle)
824 {
825 mobj_t* mo;
826
827 z += FIX2FLT((P_Random() - P_Random()) << 10);
828 if((mo = P_SpawnMobjXYZ(MT_BLOOD, x, y, z, angle, 0)))
829 {
830 mo->mom[MZ] = 2;
831 mo->tics -= P_Random() & 3;
832
833 if(mo->tics < 1)
834 mo->tics = 1;
835
836 if(damage <= 12 && damage >= 9)
837 P_MobjChangeState(mo, S_BLOOD2);
838 else if(damage < 9)
839 P_MobjChangeState(mo, S_BLOOD3);
840 }
841 }
842
843 /**
844 * Moves the missile forward a bit and possibly explodes it right there.
845 *
846 * @param th The missile to be checked.
847 *
848 * @return @c true, if the missile is at a valid location else
849 * @c false
850 */
P_CheckMissileSpawn(mobj_t * mo)851 dd_bool P_CheckMissileSpawn(mobj_t* mo)
852 {
853 // Move forward slightly so an angle can be computed if it explodes
854 // immediately.
855 mo->origin[VX] += mo->mom[MX] / 2;
856 mo->origin[VY] += mo->mom[MY] / 2;
857 mo->origin[VZ] += mo->mom[MZ] / 2;
858
859 if(!P_TryMoveXY(mo, mo->origin[VX], mo->origin[VY], false, false))
860 {
861 P_ExplodeMissile(mo);
862 return false;
863 }
864
865 return true;
866 }
867
P_SpawnMissile(mobjtype_t type,mobj_t * source,mobj_t * dest)868 mobj_t* P_SpawnMissile(mobjtype_t type, mobj_t *source, mobj_t *dest)
869 {
870 coord_t pos[3], spawnZOff = 0, dist = 0;
871 angle_t angle = 0;
872 //float slope = 0;
873 mobj_t *th = 0;
874 uint an;
875
876 memcpy(pos, source->origin, sizeof(pos));
877
878 if(source->player)
879 {
880 // See which target is to be aimed at.
881 angle = source->angle;
882 /*slope =*/ P_AimLineAttack(source, angle, 16 * 64);
883 if(!cfg.common.noAutoAim)
884 if(!lineTarget)
885 {
886 angle += 1 << 26;
887 /*slope =*/ P_AimLineAttack(source, angle, 16 * 64);
888
889 if(!lineTarget)
890 {
891 angle -= 2 << 26;
892 /*slope =*/ P_AimLineAttack(source, angle, 16 * 64);
893 }
894
895 if(!lineTarget)
896 {
897 angle = source->angle;
898 //slope = tan(LOOKDIR2RAD(source->dPlayer->lookDir)) / 1.2f;
899 }
900 }
901
902 if(!P_MobjIsCamera(source->player->plr->mo))
903 spawnZOff = cfg.common.plrViewHeight - 9 +
904 source->player->plr->lookDir / 173;
905 }
906 else
907 {
908 spawnZOff = 32;
909 }
910
911 pos[VZ] += spawnZOff;
912 pos[VZ] -= source->floorClip;
913
914 angle = M_PointToAngle2(pos, dest->origin);
915
916 if(!source->player)
917 {
918 // Fuzzy player.
919 if(dest->flags & MF_SHADOW)
920 angle += (P_Random() - P_Random()) << 20;
921 }
922
923 if(!(th = P_SpawnMobj(type, pos, angle, 0)))
924 return NULL;
925
926 if(th->info->seeSound)
927 S_StartSound(th->info->seeSound, th);
928
929 th->target = source; // Where it came from.
930 an = angle >> ANGLETOFINESHIFT;
931 th->mom[MX] = th->info->speed * FIX2FLT(finecosine[an]);
932 th->mom[MY] = th->info->speed * FIX2FLT(finesine[an]);
933
934 /*if(source->player)
935 { // Allow free-aim with the BFG in deathmatch?
936 if(deathmatch && cfg.netBFGFreeLook == 0 && type == MT_BFG)
937 th->mom[MZ] = 0;
938 else
939 th->mom[MZ] = th->info->speed * slope;
940 }
941 else*/
942 {
943 dist = M_ApproxDistance(dest->origin[VX] - pos[VX],
944 dest->origin[VY] - pos[VY]);
945 dist /= th->info->speed;
946 if(dist < 1)
947 dist = 1;
948 th->mom[MZ] = (dest->origin[VZ] - source->origin[VZ]) / dist;
949 }
950
951 // Make sure the speed is right (in 3D).
952 dist = M_ApproxDistance(M_ApproxDistance(th->mom[MX], th->mom[MY]), th->mom[MZ]);
953 if(dist < 1) dist = 1;
954 dist = th->info->speed / dist;
955
956 th->mom[MX] *= dist;
957 th->mom[MY] *= dist;
958 th->mom[MZ] *= dist;
959
960 th->tics -= P_Random() & 3;
961 if(th->tics < 1)
962 th->tics = 1;
963
964 if(P_CheckMissileSpawn(th))
965 return th;
966
967 return NULL;
968 }
969
970 /**
971 * d64tc
972 * dj - It would appear this routine has been stolen from HEXEN and then
973 * butchered...
974 * @todo: Make sure this still works correctly.
975 */
P_SPMAngle(mobjtype_t type,mobj_t * source,angle_t sourceAngle)976 mobj_t* P_SPMAngle(mobjtype_t type, mobj_t* source, angle_t sourceAngle)
977 {
978 coord_t pos[3], spawnZOff;
979 float fangle = LOOKDIR2RAD(source->player->plr->lookDir), movfactor = 1;
980 angle_t angle;
981 float slope;
982 mobj_t* th;
983 uint an;
984
985 pos[VX] = source->origin[VX];
986 pos[VY] = source->origin[VY];
987 pos[VZ] = source->origin[VZ];
988
989 // See which target is to be aimed at.
990 angle = sourceAngle;
991 slope = P_AimLineAttack(source, angle, 16 * 64);
992 if(!lineTarget)
993 {
994 angle += 1 << 26;
995 slope = P_AimLineAttack(source, angle, 16 * 64);
996 if(!lineTarget)
997 {
998 angle -= 2 << 26;
999 slope = P_AimLineAttack(source, angle, 16 * 64);
1000 }
1001
1002 if(!lineTarget)
1003 {
1004 angle = sourceAngle;
1005
1006 slope = sin(fangle) / 1.2;
1007 movfactor = cos(fangle);
1008 }
1009 }
1010
1011 if(!P_MobjIsCamera(source->player->plr->mo))
1012 spawnZOff = cfg.common.plrViewHeight - 9 +
1013 source->player->plr->lookDir / 173;
1014 else
1015 spawnZOff = 0;
1016
1017 pos[VZ] += spawnZOff;
1018 pos[VZ] -= source->floorClip;
1019
1020 if((th = P_SpawnMobj(type, pos, angle, 0)))
1021 {
1022 th->target = source;
1023 an = angle >> ANGLETOFINESHIFT;
1024 th->mom[MX] = movfactor * th->info->speed * FIX2FLT(finecosine[an]);
1025 th->mom[MY] = movfactor * th->info->speed * FIX2FLT(finesine[an]);
1026 th->mom[MZ] = th->info->speed * slope;
1027
1028 if(th->info->seeSound)
1029 S_StartSound(th->info->seeSound, th);
1030
1031 th->tics -= P_Random() & 3;
1032 if(th->tics < 1)
1033 th->tics = 1;
1034
1035 P_CheckMissileSpawn(th);
1036 }
1037
1038 return th;
1039 }
1040
1041 /**
1042 * d64tc
1043 */
P_SpawnMotherMissile(mobjtype_t type,coord_t x,coord_t y,coord_t z,mobj_t * source,mobj_t * dest)1044 mobj_t* P_SpawnMotherMissile(mobjtype_t type, coord_t x, coord_t y, coord_t z,
1045 mobj_t* source, mobj_t* dest)
1046 {
1047 angle_t angle;
1048 coord_t dist;
1049 mobj_t* th;
1050 uint an;
1051
1052 z -= source->floorClip;
1053
1054 angle = M_PointXYToAngle2(x, y, dest->origin[VX], dest->origin[VY]);
1055 if(dest->flags & MF_SHADOW) // Invisible target
1056 {
1057 angle += (P_Random() - P_Random()) << 21;
1058 }
1059
1060 if(!(th = P_SpawnMobjXYZ(type, x, y, z, angle, 0)))
1061 return NULL;
1062
1063 if(th->info->seeSound)
1064 S_StartSound(th->info->seeSound, th);
1065
1066 th->target = source; // Originator
1067 an = angle >> ANGLETOFINESHIFT;
1068 th->mom[MX] = th->info->speed * FIX2FLT(finecosine[an]);
1069 th->mom[MY] = th->info->speed * FIX2FLT(finesine[an]);
1070
1071 dist = M_ApproxDistance(dest->origin[VX] - x, dest->origin[VY] - y);
1072 dist /= th->info->speed;
1073
1074 if(dist < 1)
1075 dist = 1;
1076 th->mom[MZ] = (dest->origin[VZ] - z + 30) / dist;
1077
1078 th->tics -= P_Random() & 3;
1079 if(th->tics < 1)
1080 th->tics = 1;
1081
1082 P_CheckMissileSpawn(th);
1083 return th;
1084 }
1085