1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1993-1996 by id Software, Inc.
4 // Copyright (C) 1998-2000 by DooM Legacy Team.
5 // Copyright (C) 1999-2020 by Sonic Team Junior.
6 //
7 // This program is free software distributed under the
8 // terms of the GNU General Public License, version 2.
9 // See the 'LICENSE' file for more details.
10 //-----------------------------------------------------------------------------
11 /// \file p_mobj.c
12 /// \brief Moving object handling. Spawn functions
13
14 #include "doomdef.h"
15 #include "g_game.h"
16 #include "g_input.h"
17 #include "st_stuff.h"
18 #include "hu_stuff.h"
19 #include "p_local.h"
20 #include "p_setup.h"
21 #include "r_main.h"
22 #include "r_skins.h"
23 #include "r_sky.h"
24 #include "r_splats.h"
25 #include "s_sound.h"
26 #include "z_zone.h"
27 #include "m_random.h"
28 #include "m_cheat.h"
29 #include "m_misc.h"
30 #include "info.h"
31 #include "i_video.h"
32 #include "lua_hook.h"
33 #include "b_bot.h"
34 #include "p_slopes.h"
35 #include "f_finale.h"
36 #include "m_cond.h"
37
38 static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}};
39 consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL);
40
41 actioncache_t actioncachehead;
42
43 static mobj_t *overlaycap = NULL;
44
P_InitCachedActions(void)45 void P_InitCachedActions(void)
46 {
47 actioncachehead.prev = actioncachehead.next = &actioncachehead;
48 }
49
P_RunCachedActions(void)50 void P_RunCachedActions(void)
51 {
52 actioncache_t *ac;
53 actioncache_t *next;
54
55 for (ac = actioncachehead.next; ac != &actioncachehead; ac = next)
56 {
57 var1 = states[ac->statenum].var1;
58 var2 = states[ac->statenum].var2;
59 astate = &states[ac->statenum];
60 if (ac->mobj && !P_MobjWasRemoved(ac->mobj)) // just in case...
61 states[ac->statenum].action.acp1(ac->mobj);
62 next = ac->next;
63 Z_Free(ac);
64 }
65 }
66
P_AddCachedAction(mobj_t * mobj,INT32 statenum)67 void P_AddCachedAction(mobj_t *mobj, INT32 statenum)
68 {
69 actioncache_t *newaction = Z_Calloc(sizeof(actioncache_t), PU_LEVEL, NULL);
70 newaction->mobj = mobj;
71 newaction->statenum = statenum;
72 actioncachehead.prev->next = newaction;
73 newaction->next = &actioncachehead;
74 newaction->prev = actioncachehead.prev;
75 actioncachehead.prev = newaction;
76 }
77
78 //
79 // P_SetupStateAnimation
80 //
P_SetupStateAnimation(mobj_t * mobj,state_t * st)81 FUNCINLINE static ATTRINLINE void P_SetupStateAnimation(mobj_t *mobj, state_t *st)
82 {
83 INT32 animlength = (mobj->sprite == SPR_PLAY && mobj->skin)
84 ? (INT32)(((skin_t *)mobj->skin)->sprites[mobj->sprite2].numframes) - 1
85 : st->var1;
86
87 if (!(st->frame & FF_ANIMATE))
88 return;
89
90 if (animlength <= 0 || st->var2 == 0)
91 {
92 mobj->frame &= ~FF_ANIMATE;
93 return; // Crash/stupidity prevention
94 }
95
96 mobj->anim_duration = (UINT16)st->var2;
97
98 if (st->frame & FF_GLOBALANIM)
99 {
100 // Attempt to account for the pre-ticker for objects spawned on load
101 if (!leveltime) return;
102
103 mobj->anim_duration -= (leveltime + 2) % st->var2; // Duration synced to timer
104 mobj->frame += ((leveltime + 2) / st->var2) % (animlength + 1); // Frame synced to timer (duration taken into account)
105 }
106 else if (st->frame & FF_RANDOMANIM)
107 {
108 mobj->frame += P_RandomKey(animlength + 1); // Random starting frame
109 mobj->anim_duration -= P_RandomKey(st->var2); // Random duration for first frame
110 }
111 }
112
113 //
114 // P_CycleStateAnimation
115 //
P_CycleStateAnimation(mobj_t * mobj)116 FUNCINLINE static ATTRINLINE void P_CycleStateAnimation(mobj_t *mobj)
117 {
118 // var2 determines delay between animation frames
119 if (!(mobj->frame & FF_ANIMATE) || --mobj->anim_duration != 0)
120 return;
121
122 mobj->anim_duration = (UINT16)mobj->state->var2;
123
124 if (mobj->sprite != SPR_PLAY)
125 {
126 // compare the current sprite frame to the one we started from
127 // if more than var1 away from it, swap back to the original
128 // else just advance by one
129 if (((++mobj->frame) & FF_FRAMEMASK) - (mobj->state->frame & FF_FRAMEMASK) > (UINT32)mobj->state->var1)
130 mobj->frame = (mobj->state->frame & FF_FRAMEMASK) | (mobj->frame & ~FF_FRAMEMASK);
131
132 return;
133 }
134
135 // sprite2 version of above
136 if (mobj->skin && (((++mobj->frame) & FF_FRAMEMASK) >= (UINT32)(((skin_t *)mobj->skin)->sprites[mobj->sprite2].numframes)))
137 mobj->frame &= ~FF_FRAMEMASK;
138 }
139
140 //
141 // P_CycleMobjState
142 //
P_CycleMobjState(mobj_t * mobj)143 static void P_CycleMobjState(mobj_t *mobj)
144 {
145 // state animations
146 P_CycleStateAnimation(mobj);
147
148 // cycle through states,
149 // calling action functions at transitions
150 if (mobj->tics != -1)
151 {
152 mobj->tics--;
153
154 // you can cycle through multiple states in a tic
155 if (!mobj->tics && mobj->state)
156 if (!P_SetMobjState(mobj, mobj->state->nextstate))
157 return; // freed itself
158 }
159 }
160
161 //
162 // P_CycleMobjState for players.
163 //
P_CyclePlayerMobjState(mobj_t * mobj)164 static void P_CyclePlayerMobjState(mobj_t *mobj)
165 {
166 // state animations
167 P_CycleStateAnimation(mobj);
168
169 // cycle through states,
170 // calling action functions at transitions
171 if (mobj->tics != -1)
172 {
173 mobj->tics--;
174
175 // you can cycle through multiple states in a tic
176 if (!mobj->tics && mobj->state)
177 if (!P_SetPlayerMobjState(mobj, mobj->state->nextstate))
178 return; // freed itself
179 }
180 }
181
182 //
183 // P_SetPlayerMobjState
184 // Returns true if the mobj is still present.
185 //
186 // Separate from P_SetMobjState because of the pw_flashing check and Super states
187 //
P_SetPlayerMobjState(mobj_t * mobj,statenum_t state)188 boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
189 {
190 state_t *st;
191 player_t *player = mobj->player;
192
193 // remember states seen, to detect cycles:
194 static statenum_t seenstate_tab[NUMSTATES]; // fast transition table
195 statenum_t *seenstate = seenstate_tab; // pointer to table
196 static INT32 recursion; // detects recursion
197 statenum_t i; // initial state
198 statenum_t tempstate[NUMSTATES]; // for use with recursion
199
200 #ifdef PARANOIA
201 if (player == NULL)
202 I_Error("P_SetPlayerMobjState used for non-player mobj. Use P_SetMobjState instead!\n(Mobj type: %d, State: %d)", mobj->type, state);
203 #endif
204
205 // Catch falling for nojumpspin
206 if ((state == S_PLAY_JUMP) && (player->charflags & SF_NOJUMPSPIN) && (P_MobjFlip(mobj)*mobj->momz < 0))
207 return P_SetPlayerMobjState(mobj, S_PLAY_FALL);
208
209 // Catch swimming versus flying
210 if ((state == S_PLAY_FLY || (state == S_PLAY_GLIDE && skins[player->skin].sprites[SPR2_SWIM].numframes))
211 && player->mo->eflags & MFE_UNDERWATER && !player->skidtime)
212 return P_SetPlayerMobjState(player->mo, S_PLAY_SWIM);
213 else if (state == S_PLAY_SWIM && !(player->mo->eflags & MFE_UNDERWATER))
214 {
215 if (player->charability == CA_GLIDEANDCLIMB)
216 return P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE);
217 else
218 return P_SetPlayerMobjState(player->mo, S_PLAY_FLY);
219 }
220
221 // Catch SF_NOSUPERSPIN jumps for Supers
222 if (player->powers[pw_super] && (player->charflags & SF_NOSUPERSPIN))
223 {
224 if (state == S_PLAY_JUMP)
225 {
226 if (player->mo->state-states == S_PLAY_WALK)
227 return P_SetPlayerMobjState(mobj, S_PLAY_FLOAT);
228 return true;
229 }
230 else if (player->mo->state-states == S_PLAY_FLOAT && state == S_PLAY_STND)
231 return true;
232 }
233 // You were in pain state after taking a hit, and you're moving out of pain state now?
234 else if (mobj->state == &states[mobj->info->painstate] && player->powers[pw_flashing] == flashingtics && state != mobj->info->painstate)
235 {
236 // Start flashing, since you've landed.
237 player->powers[pw_flashing] = flashingtics-1;
238 P_DoPityCheck(player);
239 }
240
241 // Set animation state
242 // The pflags version of this was just as convoluted.
243 switch(state)
244 {
245 case S_PLAY_STND:
246 case S_PLAY_WAIT:
247 case S_PLAY_NIGHTS_STAND:
248 player->panim = PA_IDLE;
249 break;
250 case S_PLAY_EDGE:
251 player->panim = PA_EDGE;
252 break;
253 case S_PLAY_WALK:
254 case S_PLAY_SKID:
255 case S_PLAY_FLOAT:
256 player->panim = PA_WALK;
257 break;
258 case S_PLAY_RUN:
259 case S_PLAY_FLOAT_RUN:
260 player->panim = PA_RUN;
261 break;
262 case S_PLAY_DASH:
263 player->panim = PA_DASH;
264 break;
265 case S_PLAY_PAIN:
266 case S_PLAY_STUN:
267 player->panim = PA_PAIN;
268 break;
269 case S_PLAY_ROLL:
270 //case S_PLAY_SPINDASH: -- everyone can ROLL thanks to zoom tubes...
271 case S_PLAY_NIGHTS_ATTACK:
272 player->panim = PA_ROLL;
273 break;
274 case S_PLAY_JUMP:
275 player->panim = PA_JUMP;
276 break;
277 case S_PLAY_SPRING:
278 player->panim = PA_SPRING;
279 break;
280 case S_PLAY_FALL:
281 case S_PLAY_NIGHTS_FLOAT:
282 player->panim = PA_FALL;
283 break;
284 case S_PLAY_FLY:
285 case S_PLAY_FLY_TIRED:
286 case S_PLAY_SWIM:
287 case S_PLAY_GLIDE:
288 case S_PLAY_BOUNCE:
289 case S_PLAY_BOUNCE_LANDING:
290 case S_PLAY_TWINSPIN:
291 player->panim = PA_ABILITY;
292 break;
293 case S_PLAY_SPINDASH: // ...but the act of SPINDASHING is charability2 specific.
294 case S_PLAY_FIRE:
295 case S_PLAY_FIRE_FINISH:
296 case S_PLAY_MELEE:
297 case S_PLAY_MELEE_FINISH:
298 case S_PLAY_MELEE_LANDING:
299 player->panim = PA_ABILITY2;
300 break;
301 case S_PLAY_RIDE:
302 player->panim = PA_RIDE;
303 break;
304 default:
305 player->panim = PA_ETC;
306 break;
307 }
308
309 if (recursion++) // if recursion detected,
310 memset(seenstate = tempstate, 0, sizeof tempstate); // clear state table
311
312 i = state;
313
314 do
315 {
316 if (state == S_NULL)
317 { // Bad SOC!
318 CONS_Alert(CONS_ERROR, "Cannot remove player mobj by setting its state to S_NULL.\n");
319 //P_RemoveMobj(mobj);
320 return false;
321 }
322
323 st = &states[state];
324 mobj->state = st;
325 mobj->tics = st->tics;
326
327 // Adjust the player's animation speed to match their velocity.
328 if (state == S_PLAY_STND && player->powers[pw_super] && skins[player->skin].sprites[SPR2_WAIT|FF_SPR2SUPER].numframes == 0) // if no super wait, don't wait at all
329 mobj->tics = -1;
330 else if (player->panim == PA_EDGE && (player->charflags & SF_FASTEDGE))
331 mobj->tics = 2;
332 else if (!(disableSpeedAdjust || player->charflags & SF_NOSPEEDADJUST))
333 {
334 fixed_t speed;// = FixedDiv(player->speed, FixedMul(mobj->scale, player->mo->movefactor));
335 if (player->panim == PA_FALL)
336 {
337 speed = FixedDiv(abs(mobj->momz), mobj->scale);
338 if (speed < 10<<FRACBITS)
339 mobj->tics = 4;
340 else if (speed < 20<<FRACBITS)
341 mobj->tics = 3;
342 else if (speed < 30<<FRACBITS)
343 mobj->tics = 2;
344 else
345 mobj->tics = 1;
346 }
347 else if (player->panim == PA_ABILITY2 && player->charability2 == CA2_SPINDASH)
348 {
349 fixed_t step = (player->maxdash - player->mindash)/4;
350 speed = (player->dashspeed - player->mindash);
351 if (speed > 3*step)
352 mobj->tics = 1;
353 else if (speed > step)
354 mobj->tics = 2;
355 else
356 mobj->tics = 3;
357 }
358 else
359 {
360 speed = FixedDiv(player->speed, FixedMul(mobj->scale, player->mo->movefactor));
361 if (player->panim == PA_ROLL || player->panim == PA_JUMP)
362 {
363 if (speed > 16<<FRACBITS)
364 mobj->tics = 1;
365 else
366 mobj->tics = 2;
367 }
368 else if (P_IsObjectOnGround(mobj) || ((player->charability == CA_FLOAT || player->charability == CA_SLOWFALL) && player->secondjump == 1) || player->powers[pw_super]) // Only if on the ground or superflying.
369 {
370 if (player->panim == PA_WALK)
371 {
372 if (speed > 12<<FRACBITS)
373 mobj->tics = 2;
374 else if (speed > 6<<FRACBITS)
375 mobj->tics = 3;
376 else
377 mobj->tics = 4;
378 }
379 else if ((player->panim == PA_RUN) || (player->panim == PA_DASH))
380 {
381 if (speed > 52<<FRACBITS)
382 mobj->tics = 1;
383 else
384 mobj->tics = 2;
385 }
386 }
387 }
388 }
389
390 // Player animations
391 if (st->sprite == SPR_PLAY)
392 {
393 skin_t *skin = ((skin_t *)mobj->skin);
394 UINT16 frame = (mobj->frame & FF_FRAMEMASK)+1;
395 UINT8 numframes, spr2;
396
397 if (skin)
398 {
399 spr2 = P_GetSkinSprite2(skin, (((player->powers[pw_super] && !(player->charflags & SF_NOSUPERSPRITES)) ? FF_SPR2SUPER : 0)|st->frame) & FF_FRAMEMASK, mobj->player);
400 numframes = skin->sprites[spr2].numframes;
401 }
402 else
403 {
404 spr2 = 0;
405 frame = 0;
406 numframes = 0;
407 }
408
409 if (mobj->sprite != SPR_PLAY)
410 {
411 mobj->sprite = SPR_PLAY;
412 frame = 0;
413 }
414 else if (mobj->sprite2 != spr2)
415 {
416 if ((st->frame & FF_SPR2MIDSTART) && numframes && P_RandomChance(FRACUNIT/2))
417 frame = numframes/2;
418 else
419 frame = 0;
420 }
421
422 if (frame >= numframes)
423 {
424 if (st->frame & FF_SPR2ENDSTATE) // no frame advancement
425 {
426 if (st->var1 == mobj->state-states)
427 frame--;
428 else
429 {
430 if (mobj->frame & FF_FRAMEMASK)
431 mobj->frame--;
432 return P_SetPlayerMobjState(mobj, st->var1);
433 }
434 }
435 else
436 frame = 0;
437 }
438
439 mobj->sprite2 = spr2;
440 mobj->frame = frame|(st->frame&~FF_FRAMEMASK);
441 if (P_PlayerFullbright(player))
442 mobj->frame |= FF_FULLBRIGHT;
443 }
444 // Regular sprites
445 else
446 {
447 mobj->sprite = st->sprite;
448 mobj->frame = st->frame;
449 }
450
451 P_SetupStateAnimation(mobj, st);
452
453 // Modified handling.
454 // Call action functions when the state is set
455
456 if (st->action.acp1)
457 {
458 var1 = st->var1;
459 var2 = st->var2;
460 astate = st;
461 st->action.acp1(mobj);
462
463 // woah. a player was removed by an action.
464 // this sounds like a VERY BAD THING, but there's nothing we can do now...
465 if (P_MobjWasRemoved(mobj))
466 return false;
467 }
468
469 seenstate[state] = 1 + st->nextstate;
470
471 state = st->nextstate;
472 } while (!mobj->tics && !seenstate[state]);
473
474 if (!mobj->tics)
475 CONS_Alert(CONS_WARNING, M_GetText("State cycle detected, exiting.\n"));
476
477 if (!--recursion)
478 for (;(state = seenstate[i]) > S_NULL; i = state - 1)
479 seenstate[i] = S_NULL; // erase memory of states
480
481 return true;
482 }
483
484
P_SetMobjState(mobj_t * mobj,statenum_t state)485 boolean P_SetMobjState(mobj_t *mobj, statenum_t state)
486 {
487 state_t *st;
488
489 // remember states seen, to detect cycles:
490 static statenum_t seenstate_tab[NUMSTATES]; // fast transition table
491 statenum_t *seenstate = seenstate_tab; // pointer to table
492 static INT32 recursion; // detects recursion
493 statenum_t i = state; // initial state
494 statenum_t tempstate[NUMSTATES]; // for use with recursion
495
496 #ifdef PARANOIA
497 if (mobj->player != NULL)
498 I_Error("P_SetMobjState used for player mobj. Use P_SetPlayerMobjState instead!\n(State called: %d)", state);
499 #endif
500
501 if (recursion++) // if recursion detected,
502 memset(seenstate = tempstate, 0, sizeof tempstate); // clear state table
503
504 do
505 {
506 if (state == S_NULL)
507 {
508 P_RemoveMobj(mobj);
509 return false;
510 }
511
512 st = &states[state];
513 mobj->state = st;
514 mobj->tics = st->tics;
515
516 // Player animations
517 if (st->sprite == SPR_PLAY)
518 {
519 skin_t *skin = ((skin_t *)mobj->skin);
520 UINT16 frame = (mobj->frame & FF_FRAMEMASK)+1;
521 UINT8 numframes, spr2;
522
523 if (skin)
524 {
525 spr2 = P_GetSkinSprite2(skin, st->frame & FF_FRAMEMASK, mobj->player);
526 numframes = skin->sprites[spr2].numframes;
527 }
528 else
529 {
530 spr2 = 0;
531 frame = 0;
532 numframes = 0;
533 }
534
535 if (mobj->sprite != SPR_PLAY)
536 {
537 mobj->sprite = SPR_PLAY;
538 frame = 0;
539 }
540 else if (mobj->sprite2 != spr2)
541 {
542 if ((st->frame & FF_SPR2MIDSTART) && numframes && P_RandomChance(FRACUNIT/2))
543 frame = numframes/2;
544 else
545 frame = 0;
546 }
547
548 if (frame >= numframes)
549 {
550 if (st->frame & FF_SPR2ENDSTATE) // no frame advancement
551 {
552 if (st->var1 == mobj->state-states)
553 frame--;
554 else
555 {
556 if (mobj->frame & FF_FRAMEMASK)
557 mobj->frame--;
558 return P_SetMobjState(mobj, st->var1);
559 }
560 }
561 else
562 frame = 0;
563 }
564
565 mobj->sprite2 = spr2;
566 mobj->frame = frame|(st->frame&~FF_FRAMEMASK);
567 }
568 // Regular sprites
569 else
570 {
571 mobj->sprite = st->sprite;
572 mobj->frame = st->frame;
573 }
574
575 P_SetupStateAnimation(mobj, st);
576
577 // Modified handling.
578 // Call action functions when the state is set
579
580 if (st->action.acp1)
581 {
582 var1 = st->var1;
583 var2 = st->var2;
584 astate = st;
585 st->action.acp1(mobj);
586 if (P_MobjWasRemoved(mobj))
587 return false;
588 }
589
590 seenstate[state] = 1 + st->nextstate;
591
592 state = st->nextstate;
593 } while (!mobj->tics && !seenstate[state]);
594
595 if (!mobj->tics)
596 CONS_Alert(CONS_WARNING, M_GetText("State cycle detected, exiting.\n"));
597
598 if (!--recursion)
599 for (;(state = seenstate[i]) > S_NULL; i = state - 1)
600 seenstate[i] = S_NULL; // erase memory of states
601
602 return true;
603 }
604
605 //----------------------------------------------------------------------------
606 //
607 // FUNC P_SetMobjStateNF
608 //
609 // Same as P_SetMobjState, but does not call the state function.
610 //
611 //----------------------------------------------------------------------------
612
P_SetMobjStateNF(mobj_t * mobj,statenum_t state)613 boolean P_SetMobjStateNF(mobj_t *mobj, statenum_t state)
614 {
615 state_t *st;
616
617 if (state == S_NULL)
618 { // Remove mobj
619 P_RemoveMobj(mobj);
620 return false;
621 }
622 st = &states[state];
623 mobj->state = st;
624 mobj->tics = st->tics;
625 mobj->sprite = st->sprite;
626 mobj->frame = st->frame;
627 P_SetupStateAnimation(mobj, st);
628
629 return true;
630 }
631
P_SetPrecipMobjState(precipmobj_t * mobj,statenum_t state)632 static boolean P_SetPrecipMobjState(precipmobj_t *mobj, statenum_t state)
633 {
634 state_t *st;
635
636 if (state == S_NULL)
637 { // Remove mobj
638 P_RemovePrecipMobj(mobj);
639 return false;
640 }
641 st = &states[state];
642 mobj->state = st;
643 mobj->tics = st->tics;
644 mobj->sprite = st->sprite;
645 mobj->frame = st->frame;
646 P_SetupStateAnimation((mobj_t*)mobj, st);
647
648 return true;
649 }
650
651 //
652 // P_MobjFlip
653 //
654 // Special utility to return +1 or -1 depending on mobj's gravity
655 //
P_MobjFlip(mobj_t * mobj)656 SINT8 P_MobjFlip(mobj_t *mobj)
657 {
658 if (mobj && mobj->eflags & MFE_VERTICALFLIP)
659 return -1;
660 return 1;
661 }
662
663 //
664 // P_WeaponOrPanel
665 //
666 // Returns true if weapon ring/panel; otherwise returns false
667 //
P_WeaponOrPanel(mobjtype_t type)668 boolean P_WeaponOrPanel(mobjtype_t type)
669 {
670 if (type == MT_BOUNCERING
671 || type == MT_AUTOMATICRING
672 || type == MT_INFINITYRING
673 || type == MT_RAILRING
674 || type == MT_EXPLOSIONRING
675 || type == MT_SCATTERRING
676 || type == MT_GRENADERING
677 || type == MT_BOUNCEPICKUP
678 || type == MT_RAILPICKUP
679 || type == MT_AUTOPICKUP
680 || type == MT_EXPLODEPICKUP
681 || type == MT_SCATTERPICKUP
682 || type == MT_GRENADEPICKUP)
683 return true;
684
685 return false;
686 }
687
688 //
689 // P_EmeraldManager
690 //
691 // Power Stone emerald management
692 //
P_EmeraldManager(void)693 void P_EmeraldManager(void)
694 {
695 thinker_t *think;
696 mobj_t *mo;
697 INT32 i,j;
698 INT32 numtospawn;
699 INT32 emeraldsspawned = 0;
700
701 boolean hasemerald[MAXHUNTEMERALDS];
702 INT32 numwithemerald = 0;
703
704 // record empty spawn points
705 mobj_t *spawnpoints[MAXHUNTEMERALDS];
706 INT32 numspawnpoints = 0;
707
708 for (i = 0; i < MAXHUNTEMERALDS; i++)
709 {
710 hasemerald[i] = false;
711 spawnpoints[i] = NULL;
712 }
713
714 for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
715 {
716 if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
717 continue;
718
719 mo = (mobj_t *)think;
720
721 if (mo->type == MT_EMERALDSPAWN)
722 {
723 if (mo->threshold || mo->target) // Either has the emerald spawned or is spawning
724 {
725 numwithemerald++;
726 emeraldsspawned |= mobjinfo[mo->reactiontime].speed;
727 }
728 else if (numspawnpoints < MAXHUNTEMERALDS)
729 spawnpoints[numspawnpoints++] = mo; // empty spawn points
730 }
731 else if (mo->type == MT_FLINGEMERALD)
732 {
733 numwithemerald++;
734 emeraldsspawned |= mo->threshold;
735 }
736 }
737
738 if (numspawnpoints == 0)
739 return;
740
741 // But wait! We need to check all the players too, to see if anyone has some of the emeralds.
742 for (i = 0; i < MAXPLAYERS; i++)
743 {
744 if (!playeringame[i] || players[i].spectator)
745 continue;
746
747 if (!players[i].mo)
748 continue;
749
750 if (players[i].powers[pw_emeralds] & EMERALD1)
751 {
752 numwithemerald++;
753 emeraldsspawned |= EMERALD1;
754 }
755 if (players[i].powers[pw_emeralds] & EMERALD2)
756 {
757 numwithemerald++;
758 emeraldsspawned |= EMERALD2;
759 }
760 if (players[i].powers[pw_emeralds] & EMERALD3)
761 {
762 numwithemerald++;
763 emeraldsspawned |= EMERALD3;
764 }
765 if (players[i].powers[pw_emeralds] & EMERALD4)
766 {
767 numwithemerald++;
768 emeraldsspawned |= EMERALD4;
769 }
770 if (players[i].powers[pw_emeralds] & EMERALD5)
771 {
772 numwithemerald++;
773 emeraldsspawned |= EMERALD5;
774 }
775 if (players[i].powers[pw_emeralds] & EMERALD6)
776 {
777 numwithemerald++;
778 emeraldsspawned |= EMERALD6;
779 }
780 if (players[i].powers[pw_emeralds] & EMERALD7)
781 {
782 numwithemerald++;
783 emeraldsspawned |= EMERALD7;
784 }
785 }
786
787 // All emeralds spawned, no worries
788 if (numwithemerald >= 7)
789 return;
790
791 // Set up spawning for the emeralds
792 numtospawn = 7 - numwithemerald;
793
794 #ifdef PARANOIA
795 if (numtospawn <= 0) // ???
796 I_Error("P_EmeraldManager: numtospawn is %d!\n", numtospawn);
797 #endif
798
799 for (i = 0, j = 0; i < numtospawn; i++)
800 {
801 INT32 tries = 0;
802 while (true)
803 {
804 tries++;
805
806 if (tries > 50)
807 break;
808
809 j = P_RandomKey(numspawnpoints);
810
811 if (hasemerald[j])
812 continue;
813
814 hasemerald[j] = true;
815
816 if (!(emeraldsspawned & EMERALD1))
817 {
818 spawnpoints[j]->reactiontime = MT_EMERALD1;
819 emeraldsspawned |= EMERALD1;
820 }
821 else if (!(emeraldsspawned & EMERALD2))
822 {
823 spawnpoints[j]->reactiontime = MT_EMERALD2;
824 emeraldsspawned |= EMERALD2;
825 }
826 else if (!(emeraldsspawned & EMERALD3))
827 {
828 spawnpoints[j]->reactiontime = MT_EMERALD3;
829 emeraldsspawned |= EMERALD3;
830 }
831 else if (!(emeraldsspawned & EMERALD4))
832 {
833 spawnpoints[j]->reactiontime = MT_EMERALD4;
834 emeraldsspawned |= EMERALD4;
835 }
836 else if (!(emeraldsspawned & EMERALD5))
837 {
838 spawnpoints[j]->reactiontime = MT_EMERALD5;
839 emeraldsspawned |= EMERALD5;
840 }
841 else if (!(emeraldsspawned & EMERALD6))
842 {
843 spawnpoints[j]->reactiontime = MT_EMERALD6;
844 emeraldsspawned |= EMERALD6;
845 }
846 else if (!(emeraldsspawned & EMERALD7))
847 {
848 spawnpoints[j]->reactiontime = MT_EMERALD7;
849 emeraldsspawned |= EMERALD7;
850 }
851 else
852 break;
853
854 spawnpoints[j]->threshold = emeraldspawndelay + P_RandomByte() * (TICRATE/5);
855 break;
856 }
857 }
858
859 emeraldspawndelay = 0;
860 }
861
862 //
863 // P_ExplodeMissile
864 //
P_ExplodeMissile(mobj_t * mo)865 void P_ExplodeMissile(mobj_t *mo)
866 {
867 mobj_t *explodemo;
868
869 I_Assert(mo != NULL);
870 I_Assert(!P_MobjWasRemoved(mo));
871
872 mo->momx = mo->momy = mo->momz = 0;
873
874 if (mo->flags & MF_NOCLIPTHING)
875 return;
876
877 if (mo->type == MT_DETON)
878 {
879 P_RadiusAttack(mo, mo, 96*FRACUNIT, 0, true);
880
881 explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE);
882 P_SetScale(explodemo, mo->scale);
883 explodemo->destscale = mo->destscale;
884 explodemo->momx += (P_RandomByte() % 32) * FixedMul(FRACUNIT/8, explodemo->scale);
885 explodemo->momy += (P_RandomByte() % 32) * FixedMul(FRACUNIT/8, explodemo->scale);
886 S_StartSound(explodemo, sfx_pop);
887 explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE);
888 P_SetScale(explodemo, mo->scale);
889 explodemo->destscale = mo->destscale;
890 explodemo->momx += (P_RandomByte() % 64) * FixedMul(FRACUNIT/8, explodemo->scale);
891 explodemo->momy -= (P_RandomByte() % 64) * FixedMul(FRACUNIT/8, explodemo->scale);
892 S_StartSound(explodemo, sfx_dmpain);
893 explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE);
894 P_SetScale(explodemo, mo->scale);
895 explodemo->destscale = mo->destscale;
896 explodemo->momx -= (P_RandomByte() % 128) * FixedMul(FRACUNIT/8, explodemo->scale);
897 explodemo->momy += (P_RandomByte() % 128) * FixedMul(FRACUNIT/8, explodemo->scale);
898 S_StartSound(explodemo, sfx_pop);
899 explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_EXPLODE);
900 P_SetScale(explodemo, mo->scale);
901 explodemo->destscale = mo->destscale;
902 explodemo->momx -= (P_RandomByte() % 96) * FixedMul(FRACUNIT/8, explodemo->scale);
903 explodemo->momy -= (P_RandomByte() % 96) * FixedMul(FRACUNIT/8, explodemo->scale);
904 S_StartSound(explodemo, sfx_cybdth);
905 }
906
907 mo->flags &= ~MF_MISSILE;
908
909 mo->flags |= MF_NOGRAVITY; // Dead missiles don't need to sink anymore.
910 mo->flags |= MF_NOCLIPTHING; // Dummy flag to indicate that this was already called.
911
912 if (mo->info->deathsound && !(mo->flags2 & MF2_DEBRIS))
913 S_StartSound(mo, mo->info->deathsound);
914
915 P_SetMobjState(mo, mo->info->deathstate);
916 }
917
918 // P_InsideANonSolidFFloor
919 //
920 // Returns TRUE if mobj is inside a non-solid 3d floor.
P_InsideANonSolidFFloor(mobj_t * mobj,ffloor_t * rover)921 boolean P_InsideANonSolidFFloor(mobj_t *mobj, ffloor_t *rover)
922 {
923 fixed_t topheight, bottomheight;
924 if (!(rover->flags & FF_EXISTS))
925 return false;
926
927 if ((((rover->flags & FF_BLOCKPLAYER) && mobj->player)
928 || ((rover->flags & FF_BLOCKOTHERS) && !mobj->player)))
929 return false;
930
931 topheight = P_GetFFloorTopZAt (rover, mobj->x, mobj->y);
932 bottomheight = P_GetFFloorBottomZAt(rover, mobj->x, mobj->y);
933
934 if (mobj->z > topheight)
935 return false;
936
937 if (mobj->z + mobj->height < bottomheight)
938 return false;
939
940 return true;
941 }
942
943 // P_GetFloorZ (and its ceiling counterpart)
944 // Gets the floor height (or ceiling height) of the mobj's contact point in sector, assuming object's center if moved to [x, y]
945 // If line is supplied, it's a divider line on the sector. Set it to NULL if you're not checking for collision with a line
946 // Supply boundsec ONLY when checking for specials! It should be the "in-level" sector, and sector the control sector (if separate).
947 // If set, then this function will iterate through boundsec's linedefs to find the highest contact point on the slope. Non-special-checking
948 // usage will handle that later.
HighestOnLine(fixed_t radius,fixed_t x,fixed_t y,line_t * line,pslope_t * slope,boolean actuallylowest)949 static fixed_t HighestOnLine(fixed_t radius, fixed_t x, fixed_t y, line_t *line, pslope_t *slope, boolean actuallylowest)
950 {
951 // Alright, so we're sitting on a line that contains our slope sector, and need to figure out the highest point we're touching...
952 // The solution is simple! Get the line's vertices, and pull each one in along its line until it touches the object's bounding box
953 // (assuming it isn't already inside), then test each point's slope Z and return the higher of the two.
954 vertex_t v1, v2;
955 v1.x = line->v1->x;
956 v1.y = line->v1->y;
957 v2.x = line->v2->x;
958 v2.y = line->v2->y;
959
960 /*CONS_Printf("BEFORE: v1 = %f %f %f\n",
961 FIXED_TO_FLOAT(v1.x),
962 FIXED_TO_FLOAT(v1.y),
963 FIXED_TO_FLOAT(P_GetSlopeZAt(slope, v1.x, v1.y))
964 );
965 CONS_Printf(" v2 = %f %f %f\n",
966 FIXED_TO_FLOAT(v2.x),
967 FIXED_TO_FLOAT(v2.y),
968 FIXED_TO_FLOAT(P_GetSlopeZAt(slope, v2.x, v2.y))
969 );*/
970
971 if (abs(v1.x-x) > radius) {
972 // v1's x is out of range, so rein it in
973 fixed_t diff = abs(v1.x-x) - radius;
974
975 if (v1.x < x) { // Moving right
976 v1.x += diff;
977 v1.y += FixedMul(diff, FixedDiv(line->dy, line->dx));
978 } else { // Moving left
979 v1.x -= diff;
980 v1.y -= FixedMul(diff, FixedDiv(line->dy, line->dx));
981 }
982 }
983
984 if (abs(v1.y-y) > radius) {
985 // v1's y is out of range, so rein it in
986 fixed_t diff = abs(v1.y-y) - radius;
987
988 if (v1.y < y) { // Moving up
989 v1.y += diff;
990 v1.x += FixedMul(diff, FixedDiv(line->dx, line->dy));
991 } else { // Moving down
992 v1.y -= diff;
993 v1.x -= FixedMul(diff, FixedDiv(line->dx, line->dy));
994 }
995 }
996
997 if (abs(v2.x-x) > radius) {
998 // v1's x is out of range, so rein it in
999 fixed_t diff = abs(v2.x-x) - radius;
1000
1001 if (v2.x < x) { // Moving right
1002 v2.x += diff;
1003 v2.y += FixedMul(diff, FixedDiv(line->dy, line->dx));
1004 } else { // Moving left
1005 v2.x -= diff;
1006 v2.y -= FixedMul(diff, FixedDiv(line->dy, line->dx));
1007 }
1008 }
1009
1010 if (abs(v2.y-y) > radius) {
1011 // v2's y is out of range, so rein it in
1012 fixed_t diff = abs(v2.y-y) - radius;
1013
1014 if (v2.y < y) { // Moving up
1015 v2.y += diff;
1016 v2.x += FixedMul(diff, FixedDiv(line->dx, line->dy));
1017 } else { // Moving down
1018 v2.y -= diff;
1019 v2.x -= FixedMul(diff, FixedDiv(line->dx, line->dy));
1020 }
1021 }
1022
1023 /*CONS_Printf("AFTER: v1 = %f %f %f\n",
1024 FIXED_TO_FLOAT(v1.x),
1025 FIXED_TO_FLOAT(v1.y),
1026 FIXED_TO_FLOAT(P_GetSlopeZAt(slope, v1.x, v1.y))
1027 );
1028 CONS_Printf(" v2 = %f %f %f\n",
1029 FIXED_TO_FLOAT(v2.x),
1030 FIXED_TO_FLOAT(v2.y),
1031 FIXED_TO_FLOAT(P_GetSlopeZAt(slope, v2.x, v2.y))
1032 );*/
1033
1034 // Return the higher of the two points
1035 if (actuallylowest)
1036 return min(
1037 P_GetSlopeZAt(slope, v1.x, v1.y),
1038 P_GetSlopeZAt(slope, v2.x, v2.y)
1039 );
1040 else
1041 return max(
1042 P_GetSlopeZAt(slope, v1.x, v1.y),
1043 P_GetSlopeZAt(slope, v2.x, v2.y)
1044 );
1045 }
1046
P_MobjFloorZ(mobj_t * mobj,sector_t * sector,sector_t * boundsec,fixed_t x,fixed_t y,line_t * line,boolean lowest,boolean perfect)1047 fixed_t P_MobjFloorZ(mobj_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, line_t *line, boolean lowest, boolean perfect)
1048 {
1049 I_Assert(mobj != NULL);
1050 I_Assert(sector != NULL);
1051
1052 if (sector->f_slope) {
1053 fixed_t testx, testy;
1054 pslope_t *slope = sector->f_slope;
1055
1056 // Get the corner of the object that should be the highest on the slope
1057 if (slope->d.x < 0)
1058 testx = mobj->radius;
1059 else
1060 testx = -mobj->radius;
1061
1062 if (slope->d.y < 0)
1063 testy = mobj->radius;
1064 else
1065 testy = -mobj->radius;
1066
1067 if ((slope->zdelta > 0) ^ !!(lowest)) {
1068 testx = -testx;
1069 testy = -testy;
1070 }
1071
1072 testx += x;
1073 testy += y;
1074
1075 // If the highest point is in the sector, then we have it easy! Just get the Z at that point
1076 if (R_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector))
1077 return P_GetSlopeZAt(slope, testx, testy);
1078
1079 // If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point
1080 if (perfect) {
1081 size_t i;
1082 line_t *ld;
1083 fixed_t bbox[4];
1084 fixed_t finalheight;
1085
1086 if (lowest)
1087 finalheight = INT32_MAX;
1088 else
1089 finalheight = INT32_MIN;
1090
1091 bbox[BOXLEFT] = x-mobj->radius;
1092 bbox[BOXRIGHT] = x+mobj->radius;
1093 bbox[BOXTOP] = y+mobj->radius;
1094 bbox[BOXBOTTOM] = y-mobj->radius;
1095 for (i = 0; i < boundsec->linecount; i++) {
1096 ld = boundsec->lines[i];
1097
1098 if (bbox[BOXRIGHT] <= ld->bbox[BOXLEFT] || bbox[BOXLEFT] >= ld->bbox[BOXRIGHT]
1099 || bbox[BOXTOP] <= ld->bbox[BOXBOTTOM] || bbox[BOXBOTTOM] >= ld->bbox[BOXTOP])
1100 continue;
1101
1102 if (P_BoxOnLineSide(bbox, ld) != -1)
1103 continue;
1104
1105 if (lowest)
1106 finalheight = min(finalheight, HighestOnLine(mobj->radius, x, y, ld, slope, true));
1107 else
1108 finalheight = max(finalheight, HighestOnLine(mobj->radius, x, y, ld, slope, false));
1109 }
1110
1111 return finalheight;
1112 }
1113
1114 // If we're just testing for base sector location (no collision line), just go for the center's spot...
1115 // It'll get fixed when we test for collision anyway, and the final result can't be lower than this
1116 if (line == NULL)
1117 return P_GetSlopeZAt(slope, x, y);
1118
1119 return HighestOnLine(mobj->radius, x, y, line, slope, lowest);
1120 } else // Well, that makes it easy. Just get the floor height
1121 return sector->floorheight;
1122 }
1123
P_MobjCeilingZ(mobj_t * mobj,sector_t * sector,sector_t * boundsec,fixed_t x,fixed_t y,line_t * line,boolean lowest,boolean perfect)1124 fixed_t P_MobjCeilingZ(mobj_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, line_t *line, boolean lowest, boolean perfect)
1125 {
1126 I_Assert(mobj != NULL);
1127 I_Assert(sector != NULL);
1128
1129 if (sector->c_slope) {
1130 fixed_t testx, testy;
1131 pslope_t *slope = sector->c_slope;
1132
1133 // Get the corner of the object that should be the highest on the slope
1134 if (slope->d.x < 0)
1135 testx = mobj->radius;
1136 else
1137 testx = -mobj->radius;
1138
1139 if (slope->d.y < 0)
1140 testy = mobj->radius;
1141 else
1142 testy = -mobj->radius;
1143
1144 if ((slope->zdelta > 0) ^ !!(lowest)) {
1145 testx = -testx;
1146 testy = -testy;
1147 }
1148
1149 testx += x;
1150 testy += y;
1151
1152 // If the highest point is in the sector, then we have it easy! Just get the Z at that point
1153 if (R_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector))
1154 return P_GetSlopeZAt(slope, testx, testy);
1155
1156 // If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point
1157 if (perfect) {
1158 size_t i;
1159 line_t *ld;
1160 fixed_t bbox[4];
1161 fixed_t finalheight;
1162
1163 if (lowest)
1164 finalheight = INT32_MAX;
1165 else
1166 finalheight = INT32_MIN;
1167
1168 bbox[BOXLEFT] = x-mobj->radius;
1169 bbox[BOXRIGHT] = x+mobj->radius;
1170 bbox[BOXTOP] = y+mobj->radius;
1171 bbox[BOXBOTTOM] = y-mobj->radius;
1172 for (i = 0; i < boundsec->linecount; i++) {
1173 ld = boundsec->lines[i];
1174
1175 if (bbox[BOXRIGHT] <= ld->bbox[BOXLEFT] || bbox[BOXLEFT] >= ld->bbox[BOXRIGHT]
1176 || bbox[BOXTOP] <= ld->bbox[BOXBOTTOM] || bbox[BOXBOTTOM] >= ld->bbox[BOXTOP])
1177 continue;
1178
1179 if (P_BoxOnLineSide(bbox, ld) != -1)
1180 continue;
1181
1182 if (lowest)
1183 finalheight = min(finalheight, HighestOnLine(mobj->radius, x, y, ld, slope, true));
1184 else
1185 finalheight = max(finalheight, HighestOnLine(mobj->radius, x, y, ld, slope, false));
1186 }
1187
1188 return finalheight;
1189 }
1190
1191 // If we're just testing for base sector location (no collision line), just go for the center's spot...
1192 // It'll get fixed when we test for collision anyway, and the final result can't be lower than this
1193 if (line == NULL)
1194 return P_GetSlopeZAt(slope, x, y);
1195
1196 return HighestOnLine(mobj->radius, x, y, line, slope, lowest);
1197 } else // Well, that makes it easy. Just get the ceiling height
1198 return sector->ceilingheight;
1199 }
1200
1201 // Now do the same as all above, but for cameras because apparently cameras are special?
P_CameraFloorZ(camera_t * mobj,sector_t * sector,sector_t * boundsec,fixed_t x,fixed_t y,line_t * line,boolean lowest,boolean perfect)1202 fixed_t P_CameraFloorZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, line_t *line, boolean lowest, boolean perfect)
1203 {
1204 I_Assert(mobj != NULL);
1205 I_Assert(sector != NULL);
1206
1207 if (sector->f_slope) {
1208 fixed_t testx, testy;
1209 pslope_t *slope = sector->f_slope;
1210
1211 // Get the corner of the object that should be the highest on the slope
1212 if (slope->d.x < 0)
1213 testx = mobj->radius;
1214 else
1215 testx = -mobj->radius;
1216
1217 if (slope->d.y < 0)
1218 testy = mobj->radius;
1219 else
1220 testy = -mobj->radius;
1221
1222 if ((slope->zdelta > 0) ^ !!(lowest)) {
1223 testx = -testx;
1224 testy = -testy;
1225 }
1226
1227 testx += x;
1228 testy += y;
1229
1230 // If the highest point is in the sector, then we have it easy! Just get the Z at that point
1231 if (R_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector))
1232 return P_GetSlopeZAt(slope, testx, testy);
1233
1234 // If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point
1235 if (perfect) {
1236 size_t i;
1237 line_t *ld;
1238 fixed_t bbox[4];
1239 fixed_t finalheight;
1240
1241 if (lowest)
1242 finalheight = INT32_MAX;
1243 else
1244 finalheight = INT32_MIN;
1245
1246 bbox[BOXLEFT] = x-mobj->radius;
1247 bbox[BOXRIGHT] = x+mobj->radius;
1248 bbox[BOXTOP] = y+mobj->radius;
1249 bbox[BOXBOTTOM] = y-mobj->radius;
1250 for (i = 0; i < boundsec->linecount; i++) {
1251 ld = boundsec->lines[i];
1252
1253 if (bbox[BOXRIGHT] <= ld->bbox[BOXLEFT] || bbox[BOXLEFT] >= ld->bbox[BOXRIGHT]
1254 || bbox[BOXTOP] <= ld->bbox[BOXBOTTOM] || bbox[BOXBOTTOM] >= ld->bbox[BOXTOP])
1255 continue;
1256
1257 if (P_BoxOnLineSide(bbox, ld) != -1)
1258 continue;
1259
1260 if (lowest)
1261 finalheight = min(finalheight, HighestOnLine(mobj->radius, x, y, ld, slope, true));
1262 else
1263 finalheight = max(finalheight, HighestOnLine(mobj->radius, x, y, ld, slope, false));
1264 }
1265
1266 return finalheight;
1267 }
1268
1269 // If we're just testing for base sector location (no collision line), just go for the center's spot...
1270 // It'll get fixed when we test for collision anyway, and the final result can't be lower than this
1271 if (line == NULL)
1272 return P_GetSlopeZAt(slope, x, y);
1273
1274 return HighestOnLine(mobj->radius, x, y, line, slope, lowest);
1275 } else // Well, that makes it easy. Just get the floor height
1276 return sector->floorheight;
1277 }
1278
P_CameraCeilingZ(camera_t * mobj,sector_t * sector,sector_t * boundsec,fixed_t x,fixed_t y,line_t * line,boolean lowest,boolean perfect)1279 fixed_t P_CameraCeilingZ(camera_t *mobj, sector_t *sector, sector_t *boundsec, fixed_t x, fixed_t y, line_t *line, boolean lowest, boolean perfect)
1280 {
1281 I_Assert(mobj != NULL);
1282 I_Assert(sector != NULL);
1283
1284 if (sector->c_slope) {
1285 fixed_t testx, testy;
1286 pslope_t *slope = sector->c_slope;
1287
1288 // Get the corner of the object that should be the highest on the slope
1289 if (slope->d.x < 0)
1290 testx = mobj->radius;
1291 else
1292 testx = -mobj->radius;
1293
1294 if (slope->d.y < 0)
1295 testy = mobj->radius;
1296 else
1297 testy = -mobj->radius;
1298
1299 if ((slope->zdelta > 0) ^ !!(lowest)) {
1300 testx = -testx;
1301 testy = -testy;
1302 }
1303
1304 testx += x;
1305 testy += y;
1306
1307 // If the highest point is in the sector, then we have it easy! Just get the Z at that point
1308 if (R_PointInSubsector(testx, testy)->sector == (boundsec ? boundsec : sector))
1309 return P_GetSlopeZAt(slope, testx, testy);
1310
1311 // If boundsec is set, we're looking for specials. In that case, iterate over every line in this sector to find the TRUE highest/lowest point
1312 if (perfect) {
1313 size_t i;
1314 line_t *ld;
1315 fixed_t bbox[4];
1316 fixed_t finalheight;
1317
1318 if (lowest)
1319 finalheight = INT32_MAX;
1320 else
1321 finalheight = INT32_MIN;
1322
1323 bbox[BOXLEFT] = x-mobj->radius;
1324 bbox[BOXRIGHT] = x+mobj->radius;
1325 bbox[BOXTOP] = y+mobj->radius;
1326 bbox[BOXBOTTOM] = y-mobj->radius;
1327 for (i = 0; i < boundsec->linecount; i++) {
1328 ld = boundsec->lines[i];
1329
1330 if (bbox[BOXRIGHT] <= ld->bbox[BOXLEFT] || bbox[BOXLEFT] >= ld->bbox[BOXRIGHT]
1331 || bbox[BOXTOP] <= ld->bbox[BOXBOTTOM] || bbox[BOXBOTTOM] >= ld->bbox[BOXTOP])
1332 continue;
1333
1334 if (P_BoxOnLineSide(bbox, ld) != -1)
1335 continue;
1336
1337 if (lowest)
1338 finalheight = min(finalheight, HighestOnLine(mobj->radius, x, y, ld, slope, true));
1339 else
1340 finalheight = max(finalheight, HighestOnLine(mobj->radius, x, y, ld, slope, false));
1341 }
1342
1343 return finalheight;
1344 }
1345
1346 // If we're just testing for base sector location (no collision line), just go for the center's spot...
1347 // It'll get fixed when we test for collision anyway, and the final result can't be lower than this
1348 if (line == NULL)
1349 return P_GetSlopeZAt(slope, x, y);
1350
1351 return HighestOnLine(mobj->radius, x, y, line, slope, lowest);
1352 } else // Well, that makes it easy. Just get the ceiling height
1353 return sector->ceilingheight;
1354 }
P_PlayerFlip(mobj_t * mo)1355 static void P_PlayerFlip(mobj_t *mo)
1356 {
1357 if (!mo->player)
1358 return;
1359
1360 G_GhostAddFlip();
1361 // Flip aiming to match!
1362
1363 if (mo->player->powers[pw_carry] == CR_NIGHTSMODE) // NiGHTS doesn't use flipcam
1364 ;
1365 else if (mo->player->pflags & PF_FLIPCAM)
1366 {
1367 mo->player->aiming = InvAngle(mo->player->aiming);
1368 if (mo->player-players == displayplayer)
1369 {
1370 localaiming = mo->player->aiming;
1371 if (camera.chase) {
1372 camera.aiming = InvAngle(camera.aiming);
1373 camera.z = mo->z - camera.z + mo->z;
1374 if (mo->eflags & MFE_VERTICALFLIP)
1375 camera.z += FixedMul(20*FRACUNIT, mo->scale);
1376 }
1377 }
1378 else if (mo->player-players == secondarydisplayplayer)
1379 {
1380 localaiming2 = mo->player->aiming;
1381 if (camera2.chase) {
1382 camera2.aiming = InvAngle(camera2.aiming);
1383 camera2.z = mo->z - camera2.z + mo->z;
1384 if (mo->eflags & MFE_VERTICALFLIP)
1385 camera2.z += FixedMul(20*FRACUNIT, mo->scale);
1386 }
1387 }
1388 }
1389 }
1390
1391 //
1392 // P_GetMobjGravity
1393 //
1394 // Returns the current gravity
1395 // value of the object.
1396 //
P_GetMobjGravity(mobj_t * mo)1397 fixed_t P_GetMobjGravity(mobj_t *mo)
1398 {
1399 fixed_t gravityadd = 0;
1400 boolean no3dfloorgrav = true; // Custom gravity
1401 boolean goopgravity = false;
1402 boolean wasflip;
1403
1404 I_Assert(mo != NULL);
1405 I_Assert(!P_MobjWasRemoved(mo));
1406
1407 wasflip = (mo->eflags & MFE_VERTICALFLIP) != 0;
1408
1409 if (mo->type != MT_SPINFIRE)
1410 mo->eflags &= ~MFE_VERTICALFLIP;
1411
1412 if (mo->subsector->sector->ffloors) // Check for 3D floor gravity too.
1413 {
1414 ffloor_t *rover;
1415
1416 for (rover = mo->subsector->sector->ffloors; rover; rover = rover->next)
1417 {
1418 if (!(rover->flags & FF_EXISTS) || !P_InsideANonSolidFFloor(mo, rover)) // P_InsideANonSolidFFloor checks for FF_EXISTS itself, but let's not always call this function
1419 continue;
1420
1421 if ((rover->flags & (FF_SWIMMABLE|FF_GOOWATER)) == (FF_SWIMMABLE|FF_GOOWATER))
1422 goopgravity = true;
1423
1424 if (!(rover->master->frontsector->gravity))
1425 continue;
1426
1427 gravityadd = -FixedMul(gravity,
1428 (FixedDiv(*rover->master->frontsector->gravity>>FRACBITS, 1000)));
1429
1430 if (rover->master->frontsector->verticalflip && gravityadd > 0)
1431 mo->eflags |= MFE_VERTICALFLIP;
1432
1433 no3dfloorgrav = false;
1434 break;
1435 }
1436 }
1437
1438 if (no3dfloorgrav)
1439 {
1440 if (mo->subsector->sector->gravity)
1441 gravityadd = -FixedMul(gravity,
1442 (FixedDiv(*mo->subsector->sector->gravity>>FRACBITS, 1000)));
1443 else
1444 gravityadd = -gravity;
1445
1446 if (mo->subsector->sector->verticalflip && gravityadd > 0)
1447 mo->eflags |= MFE_VERTICALFLIP;
1448 }
1449
1450 // Less gravity underwater.
1451 if (mo->eflags & MFE_UNDERWATER && !goopgravity)
1452 gravityadd = gravityadd/3;
1453
1454 if (mo->player)
1455 {
1456 if ((mo->player->pflags & PF_GLIDING)
1457 || (mo->player->charability == CA_FLY && mo->player->panim == PA_ABILITY))
1458 gravityadd = gravityadd/3; // less gravity while flying/gliding
1459 if (mo->player->climbing || (mo->player->powers[pw_carry] == CR_NIGHTSMODE))
1460 gravityadd = 0;
1461
1462 if (!(mo->flags2 & MF2_OBJECTFLIP) != !(mo->player->powers[pw_gravityboots])) // negated to turn numeric into bool - would be double negated, but not needed if both would be
1463 {
1464 gravityadd = -gravityadd;
1465 mo->eflags ^= MFE_VERTICALFLIP;
1466 }
1467 if (wasflip == !(mo->eflags & MFE_VERTICALFLIP)) // note!! == ! is not equivalent to != here - turns numeric into bool this way
1468 P_PlayerFlip(mo);
1469 }
1470 else
1471 {
1472 // Objects with permanent reverse gravity (MF2_OBJECTFLIP)
1473 if (mo->flags2 & MF2_OBJECTFLIP)
1474 {
1475 mo->eflags |= MFE_VERTICALFLIP;
1476 if (mo->z + mo->height >= mo->ceilingz)
1477 gravityadd = 0;
1478 else if (gravityadd < 0) // Don't sink, only rise up
1479 gravityadd *= -1;
1480 }
1481 else //Otherwise, sort through the other exceptions.
1482 {
1483 switch (mo->type)
1484 {
1485 case MT_FLINGRING:
1486 case MT_FLINGCOIN:
1487 case MT_FLINGBLUESPHERE:
1488 case MT_FLINGNIGHTSCHIP:
1489 case MT_FLINGEMERALD:
1490 case MT_BOUNCERING:
1491 case MT_RAILRING:
1492 case MT_INFINITYRING:
1493 case MT_AUTOMATICRING:
1494 case MT_EXPLOSIONRING:
1495 case MT_SCATTERRING:
1496 case MT_GRENADERING:
1497 case MT_BOUNCEPICKUP:
1498 case MT_RAILPICKUP:
1499 case MT_AUTOPICKUP:
1500 case MT_EXPLODEPICKUP:
1501 case MT_SCATTERPICKUP:
1502 case MT_GRENADEPICKUP:
1503 case MT_REDFLAG:
1504 case MT_BLUEFLAG:
1505 if (mo->target)
1506 {
1507 // Flung items copy the gravity of their tosser.
1508 if ((mo->target->eflags & MFE_VERTICALFLIP) && !(mo->eflags & MFE_VERTICALFLIP))
1509 {
1510 gravityadd = -gravityadd;
1511 mo->eflags |= MFE_VERTICALFLIP;
1512 }
1513 }
1514 break;
1515 case MT_WATERDROP:
1516 case MT_CYBRAKDEMON:
1517 gravityadd >>= 1;
1518 default:
1519 break;
1520 }
1521 }
1522 }
1523
1524 // Goop has slower, reversed gravity
1525 if (goopgravity)
1526 gravityadd = -((gravityadd/5) + (gravityadd/8));
1527
1528 gravityadd = FixedMul(gravityadd, mo->scale);
1529
1530 return gravityadd;
1531 }
1532
1533 //
1534 // P_CheckGravity
1535 //
1536 // Checks the current gravity state
1537 // of the object. If affect is true,
1538 // a gravity force will be applied.
1539 //
P_CheckGravity(mobj_t * mo,boolean affect)1540 void P_CheckGravity(mobj_t *mo, boolean affect)
1541 {
1542 fixed_t gravityadd = P_GetMobjGravity(mo);
1543
1544 if (!mo->momz) // mobj at stop, no floor, so feel the push of gravity!
1545 gravityadd <<= 1;
1546
1547 if (affect)
1548 mo->momz += gravityadd;
1549
1550 if (mo->type == MT_SKIM && mo->z + mo->momz <= mo->watertop && mo->z >= mo->watertop)
1551 {
1552 mo->momz = 0;
1553 mo->flags |= MF_NOGRAVITY;
1554 }
1555 }
1556
1557 #define STOPSPEED (FRACUNIT)
1558
1559 //
1560 // P_SceneryXYFriction
1561 //
P_SceneryXYFriction(mobj_t * mo,fixed_t oldx,fixed_t oldy)1562 static void P_SceneryXYFriction(mobj_t *mo, fixed_t oldx, fixed_t oldy)
1563 {
1564 I_Assert(mo != NULL);
1565 I_Assert(!P_MobjWasRemoved(mo));
1566
1567 if (abs(mo->momx) < FixedMul(STOPSPEED/32, mo->scale)
1568 && abs(mo->momy) < FixedMul(STOPSPEED/32, mo->scale))
1569 {
1570 mo->momx = 0;
1571 mo->momy = 0;
1572 }
1573 else
1574 {
1575 if ((oldx == mo->x) && (oldy == mo->y)) // didn't go anywhere
1576 {
1577 mo->momx = FixedMul(mo->momx,ORIG_FRICTION);
1578 mo->momy = FixedMul(mo->momy,ORIG_FRICTION);
1579 }
1580 else
1581 {
1582 mo->momx = FixedMul(mo->momx,mo->friction);
1583 mo->momy = FixedMul(mo->momy,mo->friction);
1584 }
1585
1586 if (mo->type == MT_CANNONBALLDECOR)
1587 {
1588 // Stolen from P_SpawnFriction
1589 mo->friction = FRACUNIT - 0x100;
1590 }
1591 else
1592 mo->friction = ORIG_FRICTION;
1593 }
1594 }
1595
1596 //
1597 // P_XYFriction
1598 //
1599 // adds friction on the xy plane
1600 //
P_XYFriction(mobj_t * mo,fixed_t oldx,fixed_t oldy)1601 static void P_XYFriction(mobj_t *mo, fixed_t oldx, fixed_t oldy)
1602 {
1603 player_t *player;
1604
1605 I_Assert(mo != NULL);
1606 I_Assert(!P_MobjWasRemoved(mo));
1607
1608 player = mo->player;
1609 if (player) // valid only if player avatar
1610 {
1611 // spinning friction
1612 if (player->pflags & PF_SPINNING && (player->rmomx || player->rmomy) && !(player->pflags & PF_STARTDASH))
1613 {
1614 if (twodlevel || player->mo->flags2 & MF2_TWOD) // Otherwise handled in P_3DMovement
1615 {
1616 const fixed_t ns = FixedDiv(549*ORIG_FRICTION,500*FRACUNIT);
1617 mo->momx = FixedMul(mo->momx, ns);
1618 mo->momy = FixedMul(mo->momy, ns);
1619 }
1620 }
1621 else if (abs(player->rmomx) < FixedMul(STOPSPEED, mo->scale)
1622 && abs(player->rmomy) < FixedMul(STOPSPEED, mo->scale)
1623 && (!(player->cmd.forwardmove && !(twodlevel || mo->flags2 & MF2_TWOD)) && !player->cmd.sidemove && !(player->pflags & PF_SPINNING))
1624 && !(player->mo->standingslope && (!(player->mo->standingslope->flags & SL_NOPHYSICS)) && (abs(player->mo->standingslope->zdelta) >= FRACUNIT/2)))
1625 {
1626 // if in a walking frame, stop moving
1627 if (player->panim == PA_WALK)
1628 P_SetPlayerMobjState(mo, S_PLAY_STND);
1629 mo->momx = player->cmomx;
1630 mo->momy = player->cmomy;
1631 }
1632 else if (!(mo->eflags & MFE_SPRUNG))
1633 {
1634 if (oldx == mo->x && oldy == mo->y) // didn't go anywhere
1635 {
1636 mo->momx = FixedMul(mo->momx, ORIG_FRICTION);
1637 mo->momy = FixedMul(mo->momy, ORIG_FRICTION);
1638 }
1639 else
1640 {
1641 mo->momx = FixedMul(mo->momx, mo->friction);
1642 mo->momy = FixedMul(mo->momy, mo->friction);
1643 }
1644
1645 mo->friction = ORIG_FRICTION;
1646 }
1647 }
1648 else
1649 P_SceneryXYFriction(mo, oldx, oldy);
1650 }
1651
P_PushableCheckBustables(mobj_t * mo)1652 static void P_PushableCheckBustables(mobj_t *mo)
1653 {
1654 msecnode_t *node;
1655 fixed_t oldx;
1656 fixed_t oldy;
1657
1658 I_Assert(mo != NULL);
1659 I_Assert(!P_MobjWasRemoved(mo));
1660
1661 if (netgame && mo->player && mo->player->spectator)
1662 return;
1663
1664 oldx = mo->x;
1665 oldy = mo->y;
1666
1667 P_UnsetThingPosition(mo);
1668 mo->x += mo->momx;
1669 mo->y += mo->momy;
1670 P_SetThingPosition(mo);
1671
1672 for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
1673 {
1674 ffloor_t *rover;
1675 fixed_t topheight, bottomheight;
1676
1677 if (!node->m_sector)
1678 break;
1679
1680 if (!node->m_sector->ffloors)
1681 continue;
1682
1683 for (rover = node->m_sector->ffloors; rover; rover = rover->next)
1684 {
1685 if (!(rover->flags & FF_EXISTS))
1686 continue;
1687
1688 if (!(rover->flags & FF_BUSTUP))
1689 continue;
1690
1691 // Needs ML_EFFECT4 flag for pushables to break it
1692 if (!(rover->master->flags & ML_EFFECT4))
1693 continue;
1694
1695 if (rover->master->frontsector->crumblestate != CRUMBLE_NONE)
1696 continue;
1697
1698 topheight = P_GetFOFTopZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
1699 bottomheight = P_GetFOFBottomZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
1700
1701 // Height checks
1702 if (rover->flags & FF_SHATTERBOTTOM)
1703 {
1704 if (mo->z + mo->momz + mo->height < bottomheight)
1705 continue;
1706
1707 if (mo->z + mo->height > bottomheight)
1708 continue;
1709 }
1710 else if (rover->flags & FF_SPINBUST)
1711 {
1712 if (mo->z + mo->momz > topheight)
1713 continue;
1714
1715 if (mo->z + mo->height < bottomheight)
1716 continue;
1717 }
1718 else if (rover->flags & FF_SHATTER)
1719 {
1720 if (mo->z + mo->momz > topheight)
1721 continue;
1722
1723 if (mo->z + mo->momz + mo->height < bottomheight)
1724 continue;
1725 }
1726 else
1727 {
1728 if (mo->z >= topheight)
1729 continue;
1730
1731 if (mo->z + mo->height < bottomheight)
1732 continue;
1733 }
1734
1735 EV_CrumbleChain(NULL, rover); // node->m_sector
1736
1737 // Run a linedef executor??
1738 if (rover->master->flags & ML_EFFECT5)
1739 P_LinedefExecute((INT16)(P_AproxDistance(rover->master->dx, rover->master->dy)>>FRACBITS), mo, node->m_sector);
1740
1741 goto bustupdone;
1742 }
1743 }
1744 bustupdone:
1745 P_UnsetThingPosition(mo);
1746 mo->x = oldx;
1747 mo->y = oldy;
1748 P_SetThingPosition(mo);
1749 }
1750
1751 //
1752 // P_CheckSkyHit
1753 //
P_CheckSkyHit(mobj_t * mo)1754 static boolean P_CheckSkyHit(mobj_t *mo)
1755 {
1756 if (ceilingline && ceilingline->backsector
1757 && ceilingline->backsector->ceilingpic == skyflatnum
1758 && ceilingline->frontsector
1759 && ceilingline->frontsector->ceilingpic == skyflatnum
1760 && (mo->z >= ceilingline->frontsector->ceilingheight
1761 || mo->z >= ceilingline->backsector->ceilingheight))
1762 return true;
1763 return false;
1764 }
1765
1766 //
1767 // P_XYMovement
1768 //
P_XYMovement(mobj_t * mo)1769 void P_XYMovement(mobj_t *mo)
1770 {
1771 player_t *player;
1772 fixed_t xmove, ymove;
1773 fixed_t oldx, oldy; // reducing bobbing/momentum on ice when up against walls
1774 boolean moved;
1775 pslope_t *oldslope = NULL;
1776 vector3_t slopemom = {0,0,0};
1777 fixed_t predictedz = 0;
1778
1779 I_Assert(mo != NULL);
1780 I_Assert(!P_MobjWasRemoved(mo));
1781
1782 // if it's stopped
1783 if (!mo->momx && !mo->momy)
1784 {
1785 if (mo->flags2 & MF2_SKULLFLY)
1786 {
1787 // the skull slammed into something
1788 mo->flags2 &= ~MF2_SKULLFLY;
1789 mo->momx = mo->momy = mo->momz = 0;
1790
1791 // set in 'search new direction' state?
1792 if (mo->type != MT_EGGMOBILE)
1793 P_SetMobjState(mo, mo->info->spawnstate);
1794
1795 return;
1796 }
1797 }
1798
1799 player = mo->player; //valid only if player avatar
1800
1801 xmove = mo->momx;
1802 ymove = mo->momy;
1803
1804 oldx = mo->x;
1805 oldy = mo->y;
1806
1807 if (mo->flags & MF_NOCLIPHEIGHT)
1808 mo->standingslope = NULL;
1809
1810 // adjust various things based on slope
1811 if (mo->standingslope && abs(mo->standingslope->zdelta) > FRACUNIT>>8) {
1812 if (!P_IsObjectOnGround(mo)) { // We fell off at some point? Do the twisty thing!
1813 P_SlopeLaunch(mo);
1814 xmove = mo->momx;
1815 ymove = mo->momy;
1816 } else { // Still on the ground.
1817 slopemom.x = xmove;
1818 slopemom.y = ymove;
1819 slopemom.z = 0;
1820 P_QuantizeMomentumToSlope(&slopemom, mo->standingslope);
1821
1822 xmove = slopemom.x;
1823 ymove = slopemom.y;
1824
1825 predictedz = mo->z + slopemom.z; // We'll use this later...
1826
1827 oldslope = mo->standingslope;
1828 }
1829 } else if (P_IsObjectOnGround(mo) && !mo->momz)
1830 predictedz = mo->z;
1831
1832 // Pushables can break some blocks
1833 if (CheckForBustableBlocks && ((mo->flags & MF_PUSHABLE) || ((mo->info->flags & MF_PUSHABLE) && mo->fuse)))
1834 P_PushableCheckBustables(mo);
1835
1836 if (!P_TryMove(mo, mo->x + xmove, mo->y + ymove, true)
1837 && !(P_MobjWasRemoved(mo) || mo->eflags & MFE_SPRUNG))
1838 {
1839 // blocked move
1840 moved = false;
1841
1842 if (player) {
1843 if (player->bot)
1844 B_MoveBlocked(player);
1845 }
1846
1847 if (LUAh_MobjMoveBlocked(mo))
1848 {
1849 if (P_MobjWasRemoved(mo))
1850 return;
1851 }
1852 else if (P_MobjWasRemoved(mo))
1853 return;
1854 else if (mo->flags & MF_BOUNCE)
1855 {
1856 P_BounceMove(mo);
1857 xmove = ymove = 0;
1858 S_StartSound(mo, mo->info->activesound);
1859
1860 // Bounce ring algorithm
1861 if (mo->type == MT_THROWNBOUNCE)
1862 {
1863 mo->threshold++;
1864
1865 // Gain lower amounts of time on each bounce.
1866 if (mo->fuse && mo->threshold < 5)
1867 mo->fuse += ((5 - mo->threshold) * TICRATE);
1868
1869 // Check for hit against sky here
1870 if (P_CheckSkyHit(mo))
1871 {
1872 // Hack to prevent missiles exploding
1873 // against the sky.
1874 // Does not handle sky floors.
1875 // Check frontsector as well.
1876
1877 P_RemoveMobj(mo);
1878 return;
1879 }
1880 }
1881 }
1882 else if (mo->flags & MF_STICKY)
1883 {
1884 S_StartSound(mo, mo->info->activesound);
1885 mo->momx = mo->momy = mo->momz = 0; //Full stop!
1886 mo->flags |= MF_NOGRAVITY; //Stay there!
1887 mo->flags &= ~MF_STICKY; //Don't check again!
1888
1889 // Check for hit against sky here
1890 if (P_CheckSkyHit(mo))
1891 {
1892 // Hack to prevent missiles exploding
1893 // against the sky.
1894 // Does not handle sky floors.
1895 // Check frontsector as well.
1896
1897 P_RemoveMobj(mo);
1898 return;
1899 }
1900 }
1901 else if (player || mo->flags & (MF_SLIDEME|MF_PUSHABLE))
1902 { // try to slide along it
1903 // Wall transfer part 1.
1904 pslope_t *transferslope = NULL;
1905 fixed_t transfermomz = 0;
1906 if (oldslope && (P_MobjFlip(mo)*(predictedz - mo->z) > 0)) // Only for moving up (relative to gravity), otherwise there's a failed launch when going down slopes and hitting walls
1907 {
1908 transferslope = ((mo->standingslope) ? mo->standingslope : oldslope);
1909 if (((transferslope->zangle < ANGLE_180) ? transferslope->zangle : InvAngle(transferslope->zangle)) >= ANGLE_45) // Prevent some weird stuff going on on shallow slopes.
1910 transfermomz = P_GetWallTransferMomZ(mo, transferslope);
1911 }
1912
1913 P_SlideMove(mo);
1914 if (player)
1915 player->powers[pw_pushing] = 3;
1916 xmove = ymove = 0;
1917
1918 // Wall transfer part 2.
1919 if (transfermomz && transferslope) // Are we "transferring onto the wall" (really just a disguised vertical launch)?
1920 {
1921 angle_t relation; // Scale transfer momentum based on how head-on it is to the slope.
1922 if (mo->momx || mo->momy) // "Guess" the angle of the wall you hit using new momentum
1923 relation = transferslope->xydirection - R_PointToAngle2(0, 0, mo->momx, mo->momy);
1924 else // Give it for free, I guess.
1925 relation = ANGLE_90;
1926 transfermomz = FixedMul(transfermomz,
1927 abs(FINESINE((relation >> ANGLETOFINESHIFT) & FINEMASK)));
1928 if (P_MobjFlip(mo)*(transfermomz - mo->momz) > 2*FRACUNIT) // Do the actual launch!
1929 {
1930 mo->momz = transfermomz;
1931 mo->standingslope = NULL;
1932 if (player)
1933 {
1934 player->powers[pw_justlaunched] = 2;
1935 if (player->pflags & PF_SPINNING)
1936 player->pflags |= PF_THOKKED;
1937 }
1938 }
1939 }
1940 }
1941 else if (mo->type == MT_SPINFIRE)
1942 {
1943 P_RemoveMobj(mo);
1944 return;
1945 }
1946 else if (mo->flags & MF_MISSILE)
1947 {
1948 // explode a missile
1949 if (P_CheckSkyHit(mo))
1950 {
1951 // Hack to prevent missiles exploding
1952 // against the sky.
1953 // Does not handle sky floors.
1954 // Check frontsector as well.
1955
1956 P_RemoveMobj(mo);
1957 return;
1958 }
1959
1960 P_ExplodeMissile(mo);
1961 return;
1962 }
1963 else
1964 mo->momx = mo->momy = 0;
1965 }
1966 else
1967 moved = true;
1968
1969 if (P_MobjWasRemoved(mo)) // MF_SPECIAL touched a player! O_o;;
1970 return;
1971
1972 if (moved && oldslope && !(mo->flags & MF_NOCLIPHEIGHT)) { // Check to see if we ran off
1973
1974 if (oldslope != mo->standingslope) { // First, compare different slopes
1975 angle_t oldangle, newangle;
1976 angle_t moveangle = R_PointToAngle2(0, 0, mo->momx, mo->momy);
1977
1978 oldangle = FixedMul((signed)oldslope->zangle, FINECOSINE((moveangle - oldslope->xydirection) >> ANGLETOFINESHIFT));
1979
1980 if (mo->standingslope)
1981 newangle = FixedMul((signed)mo->standingslope->zangle, FINECOSINE((moveangle - mo->standingslope->xydirection) >> ANGLETOFINESHIFT));
1982 else
1983 newangle = 0;
1984
1985 // Now compare the Zs of the different quantizations
1986 if (oldangle-newangle > ANG30 && oldangle-newangle < ANGLE_180) { // Allow for a bit of sticking - this value can be adjusted later
1987 mo->standingslope = oldslope;
1988 P_SlopeLaunch(mo);
1989
1990 //CONS_Printf("launched off of slope - ");
1991 }
1992
1993 /*CONS_Printf("old angle %f - new angle %f = %f\n",
1994 FIXED_TO_FLOAT(AngleFixed(oldangle)),
1995 FIXED_TO_FLOAT(AngleFixed(newangle)),
1996 FIXED_TO_FLOAT(AngleFixed(oldangle-newangle))
1997 );*/
1998 } else if (predictedz-mo->z > abs(slopemom.z/2)) { // Now check if we were supposed to stick to this slope
1999 //CONS_Printf("%d-%d > %d\n", (predictedz), (mo->z), (slopemom.z/2));
2000 P_SlopeLaunch(mo);
2001 }
2002 } else if (moved && mo->standingslope && predictedz) {
2003 angle_t moveangle = R_PointToAngle2(0, 0, mo->momx, mo->momy);
2004 angle_t newangle = FixedMul((signed)mo->standingslope->zangle, FINECOSINE((moveangle - mo->standingslope->xydirection) >> ANGLETOFINESHIFT));
2005
2006 /*CONS_Printf("flat to angle %f - predicted z of %f\n",
2007 FIXED_TO_FLOAT(AngleFixed(ANGLE_MAX-newangle)),
2008 FIXED_TO_FLOAT(predictedz)
2009 );*/
2010 if (ANGLE_MAX-newangle > ANG30 && newangle > ANGLE_180) {
2011 mo->momz = P_MobjFlip(mo)*FRACUNIT/2;
2012 mo->z = predictedz + P_MobjFlip(mo);
2013 mo->standingslope = NULL;
2014 //CONS_Printf("Launched off of flat surface running into downward slope\n");
2015 }
2016 }
2017
2018 // Check the gravity status.
2019 P_CheckGravity(mo, false);
2020
2021 if (player && !moved && player->powers[pw_carry] == CR_NIGHTSMODE && mo->target)
2022 {
2023 angle_t fa;
2024
2025 P_UnsetThingPosition(mo);
2026 player->angle_pos = player->old_angle_pos;
2027 player->speed = FixedMul(player->speed, 4*FRACUNIT/5);
2028 if (player->flyangle >= 0 && player->flyangle < 90)
2029 player->flyangle = 135;
2030 else if (player->flyangle >= 90 && player->flyangle < 180)
2031 player->flyangle = 45;
2032 else if (player->flyangle >= 180 && player->flyangle < 270)
2033 player->flyangle = 315;
2034 else
2035 player->flyangle = 225;
2036 player->flyangle %= 360;
2037
2038 if (player->pflags & PF_TRANSFERTOCLOSEST)
2039 {
2040 mo->x -= mo->momx;
2041 mo->y -= mo->momy;
2042 }
2043 else
2044 {
2045 fa = player->old_angle_pos>>ANGLETOFINESHIFT;
2046
2047 mo->x = mo->target->x + FixedMul(FINECOSINE(fa),mo->target->radius);
2048 mo->y = mo->target->y + FixedMul(FINESINE(fa),mo->target->radius);
2049 }
2050
2051 mo->momx = mo->momy = 0;
2052 P_SetThingPosition(mo);
2053 }
2054
2055 if (mo->flags & MF_NOCLIPHEIGHT)
2056 return; // no frictions for objects that can pass through floors
2057
2058 if (mo->flags & MF_MISSILE || mo->flags2 & MF2_SKULLFLY || mo->type == MT_SHELL || mo->type == MT_VULTURE || mo->type == MT_PENGUINATOR)
2059 return; // no friction for missiles ever
2060
2061 if (player && player->homing) // no friction for homing
2062 return;
2063
2064 if (player && player->powers[pw_carry] == CR_NIGHTSMODE)
2065 return; // no friction for NiGHTS players
2066
2067 if ((mo->type == MT_BIGTUMBLEWEED || mo->type == MT_LITTLETUMBLEWEED)
2068 && (mo->standingslope && abs(mo->standingslope->zdelta) > FRACUNIT>>8)) // Special exception for tumbleweeds on slopes
2069 return;
2070
2071 if (((!(mo->eflags & MFE_VERTICALFLIP) && mo->z > mo->floorz) || (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height < mo->ceilingz))
2072 && !(player && player->pflags & PF_SLIDING))
2073 return; // no friction when airborne
2074
2075 P_XYFriction(mo, oldx, oldy);
2076 }
2077
P_RingXYMovement(mobj_t * mo)2078 void P_RingXYMovement(mobj_t *mo)
2079 {
2080 I_Assert(mo != NULL);
2081 I_Assert(!P_MobjWasRemoved(mo));
2082
2083 if (!P_SceneryTryMove(mo, mo->x + mo->momx, mo->y + mo->momy))
2084 P_SlideMove(mo);
2085 }
2086
P_SceneryXYMovement(mobj_t * mo)2087 void P_SceneryXYMovement(mobj_t *mo)
2088 {
2089 fixed_t oldx, oldy; // reducing bobbing/momentum on ice when up against walls
2090
2091 I_Assert(mo != NULL);
2092 I_Assert(!P_MobjWasRemoved(mo));
2093
2094 oldx = mo->x;
2095 oldy = mo->y;
2096
2097 if (!P_SceneryTryMove(mo, mo->x + mo->momx, mo->y + mo->momy))
2098 P_SlideMove(mo);
2099
2100 if ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z > mo->floorz) || (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height < mo->ceilingz))
2101 return; // no friction when airborne
2102
2103 if (mo->flags & MF_NOCLIPHEIGHT)
2104 return; // no frictions for objects that can pass through floors
2105
2106 P_SceneryXYFriction(mo, oldx, oldy);
2107 }
2108
2109 //
2110 // P_AdjustMobjFloorZ_FFloors
2111 //
2112 // Utility function for P_ZMovement and related
2113 // Adjusts mo->floorz/mo->ceiling accordingly for FFloors
2114 //
2115 // "motype" determines what behaviour to use exactly
2116 // This is to keep things consistent in case these various object types NEED to be different
2117 //
2118 // motype options:
2119 // 0 - normal
2120 // 1 - forces false check for water (rings)
2121 // 2 - forces false check for water + different quicksand behaviour (scenery)
2122 //
P_AdjustMobjFloorZ_FFloors(mobj_t * mo,sector_t * sector,UINT8 motype)2123 void P_AdjustMobjFloorZ_FFloors(mobj_t *mo, sector_t *sector, UINT8 motype)
2124 {
2125 ffloor_t *rover;
2126 fixed_t delta1, delta2, thingtop;
2127 fixed_t topheight, bottomheight;
2128
2129 I_Assert(mo != NULL);
2130 I_Assert(!P_MobjWasRemoved(mo));
2131
2132 thingtop = mo->z + mo->height;
2133
2134 for (rover = sector->ffloors; rover; rover = rover->next)
2135 {
2136 if (!(rover->flags & FF_EXISTS))
2137 continue;
2138
2139 topheight = P_GetFOFTopZ(mo, sector, rover, mo->x, mo->y, NULL);
2140 bottomheight = P_GetFOFBottomZ(mo, sector, rover, mo->x, mo->y, NULL);
2141
2142 if (mo->player && (P_CheckSolidLava(rover) || P_CanRunOnWater(mo->player, rover))) // only the player should stand on lava or run on water
2143 ;
2144 else if (motype != 0 && rover->flags & FF_SWIMMABLE) // "scenery" only
2145 continue;
2146 else if (rover->flags & FF_QUICKSAND) // quicksand
2147 ;
2148 else if (!( // if it's not either of the following...
2149 (rover->flags & (FF_BLOCKPLAYER|FF_MARIO) && mo->player) // ...solid to players? (mario blocks are always solid from beneath to players)
2150 || (rover->flags & FF_BLOCKOTHERS && !mo->player) // ...solid to others?
2151 )) // ...don't take it into account.
2152 continue;
2153 if (rover->flags & FF_QUICKSAND)
2154 {
2155 switch (motype)
2156 {
2157 case 2: // scenery does things differently for some reason
2158 if (mo->z < topheight && bottomheight < thingtop)
2159 {
2160 mo->floorz = mo->z;
2161 continue;
2162 }
2163 break;
2164 default:
2165 if (mo->z < topheight && bottomheight < thingtop)
2166 {
2167 if (mo->floorz < mo->z)
2168 mo->floorz = mo->z;
2169 }
2170 continue; // This is so you can jump/spring up through quicksand from below.
2171 }
2172 }
2173
2174 delta1 = mo->z - (bottomheight + ((topheight - bottomheight)/2));
2175 delta2 = thingtop - (bottomheight + ((topheight - bottomheight)/2));
2176
2177 if (topheight > mo->floorz && abs(delta1) < abs(delta2)
2178 && (rover->flags & FF_SOLID) // Non-FF_SOLID Mario blocks are only solid from bottom
2179 && !(rover->flags & FF_REVERSEPLATFORM)
2180 && ((P_MobjFlip(mo)*mo->momz >= 0) || (!(rover->flags & FF_PLATFORM)))) // In reverse gravity, only clip for FOFs that are intangible from their bottom (the "top" you're falling through) if you're coming from above ("below" in your frame of reference)
2181 {
2182 mo->floorz = topheight;
2183 }
2184 if (bottomheight < mo->ceilingz && abs(delta1) >= abs(delta2)
2185 && !(rover->flags & FF_PLATFORM)
2186 && ((P_MobjFlip(mo)*mo->momz >= 0) || ((rover->flags & FF_SOLID) && !(rover->flags & FF_REVERSEPLATFORM)))) // In normal gravity, only clip for FOFs that are intangible from the top if you're coming from below
2187 {
2188 mo->ceilingz = bottomheight;
2189 }
2190 }
2191 }
2192
2193 //
2194 // P_AdjustMobjFloorZ_PolyObjs
2195 //
2196 // Utility function for P_ZMovement and related
2197 // Adjusts mo->floorz/mo->ceiling accordingly for PolyObjs
2198 //
P_AdjustMobjFloorZ_PolyObjs(mobj_t * mo,subsector_t * subsec)2199 static void P_AdjustMobjFloorZ_PolyObjs(mobj_t *mo, subsector_t *subsec)
2200 {
2201 polyobj_t *po = subsec->polyList;
2202 sector_t *polysec;
2203 fixed_t delta1, delta2, thingtop;
2204 fixed_t polytop, polybottom;
2205
2206 I_Assert(mo != NULL);
2207 I_Assert(!P_MobjWasRemoved(mo));
2208
2209 thingtop = mo->z + mo->height;
2210
2211 while(po)
2212 {
2213 if (!P_MobjInsidePolyobj(po, mo) || !(po->flags & POF_SOLID))
2214 {
2215 po = (polyobj_t *)(po->link.next);
2216 continue;
2217 }
2218
2219 // We're inside it! Yess...
2220 polysec = po->lines[0]->backsector;
2221
2222 if (po->flags & POF_CLIPPLANES)
2223 {
2224 polytop = polysec->ceilingheight;
2225 polybottom = polysec->floorheight;
2226 }
2227 else
2228 {
2229 polytop = INT32_MAX;
2230 polybottom = INT32_MIN;
2231 }
2232
2233 delta1 = mo->z - (polybottom + ((polytop - polybottom)/2));
2234 delta2 = thingtop - (polybottom + ((polytop - polybottom)/2));
2235
2236 if (polytop > mo->floorz && abs(delta1) < abs(delta2))
2237 mo->floorz = polytop;
2238
2239 if (polybottom < mo->ceilingz && abs(delta1) >= abs(delta2))
2240 mo->ceilingz = polybottom;
2241
2242 po = (polyobj_t *)(po->link.next);
2243 }
2244 }
2245
P_RingZMovement(mobj_t * mo)2246 void P_RingZMovement(mobj_t *mo)
2247 {
2248 I_Assert(mo != NULL);
2249 I_Assert(!P_MobjWasRemoved(mo));
2250
2251 // Intercept the stupid 'fall through 3dfloors' bug
2252 if (mo->subsector->sector->ffloors)
2253 P_AdjustMobjFloorZ_FFloors(mo, mo->subsector->sector, 1);
2254 if (mo->subsector->polyList)
2255 P_AdjustMobjFloorZ_PolyObjs(mo, mo->subsector);
2256
2257 // adjust height
2258 if (mo->eflags & MFE_APPLYPMOMZ && !P_IsObjectOnGround(mo))
2259 {
2260 mo->momz += mo->pmomz;
2261 mo->pmomz = 0;
2262 mo->eflags &= ~MFE_APPLYPMOMZ;
2263 }
2264 mo->z += mo->momz;
2265
2266 // clip movement
2267 if (mo->z <= mo->floorz && !(mo->flags & MF_NOCLIPHEIGHT))
2268 {
2269 mo->z = mo->floorz;
2270
2271 mo->momz = 0;
2272 }
2273 else if (mo->z + mo->height > mo->ceilingz && !(mo->flags & MF_NOCLIPHEIGHT))
2274 {
2275 mo->z = mo->ceilingz - mo->height;
2276
2277 mo->momz = 0;
2278 }
2279 }
2280
P_CheckDeathPitCollide(mobj_t * mo)2281 boolean P_CheckDeathPitCollide(mobj_t *mo)
2282 {
2283 I_Assert(mo != NULL);
2284 I_Assert(!P_MobjWasRemoved(mo));
2285
2286 if (mo->player && mo->player->pflags & PF_GODMODE)
2287 return false;
2288
2289 if (((mo->z <= mo->subsector->sector->floorheight
2290 && ((mo->subsector->sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || !(mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & SF_FLIPSPECIAL_FLOOR))
2291 || (mo->z + mo->height >= mo->subsector->sector->ceilingheight
2292 && ((mo->subsector->sector->flags & SF_TRIGGERSPECIAL_HEADBUMP) || (mo->eflags & MFE_VERTICALFLIP)) && (mo->subsector->sector->flags & SF_FLIPSPECIAL_CEILING)))
2293 && (GETSECSPECIAL(mo->subsector->sector->special, 1) == 6
2294 || GETSECSPECIAL(mo->subsector->sector->special, 1) == 7))
2295 return true;
2296
2297 return false;
2298 }
2299
P_CheckSolidLava(ffloor_t * rover)2300 boolean P_CheckSolidLava(ffloor_t *rover)
2301 {
2302 if (rover->flags & FF_SWIMMABLE && GETSECSPECIAL(rover->master->frontsector->special, 1) == 3
2303 && !(rover->master->flags & ML_BLOCKMONSTERS))
2304 return true;
2305
2306 return false;
2307 }
2308
2309 //
2310 // P_ZMovement
2311 // Returns false if the mobj was killed/exploded/removed, true otherwise.
2312 //
P_ZMovement(mobj_t * mo)2313 boolean P_ZMovement(mobj_t *mo)
2314 {
2315 fixed_t dist, delta;
2316 boolean onground;
2317
2318 I_Assert(mo != NULL);
2319 I_Assert(!P_MobjWasRemoved(mo));
2320
2321 // Intercept the stupid 'fall through 3dfloors' bug
2322 if (mo->subsector->sector->ffloors)
2323 P_AdjustMobjFloorZ_FFloors(mo, mo->subsector->sector, 0);
2324 if (mo->subsector->polyList)
2325 P_AdjustMobjFloorZ_PolyObjs(mo, mo->subsector);
2326
2327 // adjust height
2328 if (mo->eflags & MFE_APPLYPMOMZ && !P_IsObjectOnGround(mo))
2329 {
2330 mo->momz += mo->pmomz;
2331 mo->pmomz = 0;
2332 mo->eflags &= ~MFE_APPLYPMOMZ;
2333 }
2334 mo->z += mo->momz;
2335 onground = P_IsObjectOnGround(mo);
2336
2337 if (mo->standingslope)
2338 {
2339 if (mo->flags & MF_NOCLIPHEIGHT)
2340 mo->standingslope = NULL;
2341 else if (!onground)
2342 P_SlopeLaunch(mo);
2343 }
2344
2345 switch (mo->type)
2346 {
2347 case MT_THROWNBOUNCE:
2348 if ((mo->flags & MF_BOUNCE) && (mo->z <= mo->floorz || mo->z+mo->height >= mo->ceilingz))
2349 {
2350 mo->momz = -mo->momz;
2351 mo->z += mo->momz;
2352 S_StartSound(mo, mo->info->activesound);
2353 mo->threshold++;
2354
2355 // Be sure to change the XY one too if you change this.
2356 // Gain lower amounts of time on each bounce.
2357 if (mo->fuse && mo->threshold < 5)
2358 mo->fuse += ((5 - mo->threshold) * TICRATE);
2359 }
2360 break;
2361
2362 case MT_SKIM:
2363 // skims don't bounce
2364 if (mo->z > mo->watertop && mo->z - mo->momz <= mo->watertop)
2365 {
2366 mo->z = mo->watertop;
2367 mo->momz = 0;
2368 mo->flags |= MF_NOGRAVITY;
2369 }
2370 break;
2371 case MT_SPINFIRE:
2372 if (P_CheckDeathPitCollide(mo))
2373 {
2374 P_RemoveMobj(mo);
2375 return false;
2376 }
2377 break;
2378 case MT_GOOP:
2379 if (P_CheckDeathPitCollide(mo))
2380 {
2381 P_RemoveMobj(mo);
2382 return false;
2383 }
2384 if (mo->z <= mo->floorz && mo->momz)
2385 {
2386 P_SetMobjState(mo, mo->info->meleestate);
2387 mo->momx = mo->momy = mo->momz = 0;
2388 mo->z = mo->floorz;
2389 if (mo->info->painsound)
2390 S_StartSound(mo, mo->info->painsound);
2391 }
2392 break;
2393 case MT_FALLINGROCK:
2394 case MT_BIGTUMBLEWEED:
2395 case MT_LITTLETUMBLEWEED:
2396 case MT_SHELL:
2397 // Remove stuff from death pits.
2398 if (P_CheckDeathPitCollide(mo))
2399 {
2400 P_RemoveMobj(mo);
2401 return false;
2402 }
2403 break;
2404 case MT_REDFLAG:
2405 case MT_BLUEFLAG:
2406 // Remove from death pits. DON'T FUCKING DESPAWN IT DAMMIT
2407 if (P_CheckDeathPitCollide(mo))
2408 {
2409 mo->fuse = 1;
2410 return false;
2411 }
2412 break;
2413
2414 case MT_RING: // Ignore still rings
2415 case MT_COIN:
2416 case MT_BLUESPHERE:
2417 case MT_BOMBSPHERE:
2418 case MT_NIGHTSCHIP:
2419 case MT_NIGHTSSTAR:
2420 case MT_REDTEAMRING:
2421 case MT_BLUETEAMRING:
2422 case MT_FLINGRING:
2423 case MT_FLINGCOIN:
2424 case MT_FLINGBLUESPHERE:
2425 case MT_FLINGNIGHTSCHIP:
2426 case MT_FLINGEMERALD:
2427 // Remove flinged stuff from death pits.
2428 if (P_CheckDeathPitCollide(mo))
2429 {
2430 P_RemoveMobj(mo);
2431 return false;
2432 }
2433 if (!(mo->momx || mo->momy || mo->momz))
2434 return true;
2435 break;
2436 case MT_BOUNCERING:
2437 case MT_INFINITYRING:
2438 case MT_AUTOMATICRING:
2439 case MT_RAILRING:
2440 case MT_EXPLOSIONRING:
2441 case MT_SCATTERRING:
2442 case MT_GRENADERING:
2443 case MT_BOUNCEPICKUP:
2444 case MT_RAILPICKUP:
2445 case MT_AUTOPICKUP:
2446 case MT_EXPLODEPICKUP:
2447 case MT_SCATTERPICKUP:
2448 case MT_GRENADEPICKUP:
2449 // Remove flinged stuff from death pits.
2450 if (P_CheckDeathPitCollide(mo) && (mo->flags2 & MF2_DONTRESPAWN))
2451 {
2452 P_RemoveMobj(mo);
2453 return false;
2454 }
2455 if (!(mo->momx || mo->momy || mo->momz))
2456 return true;
2457 break;
2458 case MT_FLAMEJET:
2459 case MT_VERTICALFLAMEJET:
2460 if (!(mo->flags & MF_BOUNCE))
2461 return true;
2462 break;
2463 case MT_SPIKE:
2464 case MT_WALLSPIKE:
2465 // Dead spike particles disappear upon ground contact
2466 if (!mo->health && (mo->z <= mo->floorz || mo->z + mo->height >= mo->ceilingz))
2467 {
2468 P_RemoveMobj(mo);
2469 return false;
2470 }
2471 break;
2472 default:
2473 break;
2474 }
2475
2476 if (!mo->player && P_CheckDeathPitCollide(mo))
2477 {
2478 switch (mo->type)
2479 {
2480 case MT_GHOST:
2481 case MT_METALSONIC_RACE:
2482 case MT_EXPLODE:
2483 case MT_BOSSEXPLODE:
2484 case MT_SONIC3KBOSSEXPLODE:
2485 break;
2486 default:
2487 if (mo->flags & MF_ENEMY || mo->flags & MF_BOSS || mo->type == MT_MINECART)
2488 {
2489 // Kill enemies, bosses and minecarts that fall into death pits.
2490 if (mo->health)
2491 {
2492 P_KillMobj(mo, NULL, NULL, 0);
2493 }
2494 return false;
2495 }
2496 else
2497 {
2498 P_RemoveMobj(mo);
2499 return false;
2500 }
2501 break;
2502 }
2503 }
2504
2505 if (P_MobjFlip(mo)*mo->momz < 0
2506 && (mo->flags2 & MF2_CLASSICPUSH))
2507 mo->momx = mo->momy = 0;
2508
2509 if (mo->flags & MF_FLOAT && mo->target && mo->health
2510 && !(mo->type == MT_EGGMOBILE) && mo->target->health > 0)
2511 {
2512 // float down towards target if too close
2513 if (!(mo->flags2 & MF2_SKULLFLY) && !(mo->flags2 & MF2_INFLOAT))
2514 {
2515 dist = P_AproxDistance(mo->x - mo->target->x, mo->y - mo->target->y);
2516
2517 delta = (mo->target->z + (mo->height>>1)) - mo->z;
2518
2519 if (delta < 0 && dist < -(delta*3))
2520 mo->z -= FixedMul(FLOATSPEED, mo->scale);
2521 else if (delta > 0 && dist < (delta*3))
2522 mo->z += FixedMul(FLOATSPEED, mo->scale);
2523
2524 if (mo->type == MT_JETJAW && mo->z + mo->height > mo->watertop)
2525 mo->z = mo->watertop - mo->height;
2526 }
2527
2528 }
2529
2530 // clip movement
2531 if (((mo->z <= mo->floorz && !(mo->eflags & MFE_VERTICALFLIP))
2532 || (mo->z + mo->height >= mo->ceilingz && mo->eflags & MFE_VERTICALFLIP))
2533 && !(mo->flags & MF_NOCLIPHEIGHT))
2534 {
2535 vector3_t mom;
2536 mom.x = mo->momx;
2537 mom.y = mo->momy;
2538 mom.z = mo->momz;
2539
2540 if (mo->eflags & MFE_VERTICALFLIP)
2541 mo->z = mo->ceilingz - mo->height;
2542 else
2543 mo->z = mo->floorz;
2544
2545 if (!(mo->flags & MF_MISSILE) && mo->standingslope) // You're still on the ground; why are we here?
2546 {
2547 mo->momz = 0;
2548 return true;
2549 }
2550
2551 P_CheckPosition(mo, mo->x, mo->y); // Sets mo->standingslope correctly
2552 if (((mo->eflags & MFE_VERTICALFLIP) ? tmceilingslope : tmfloorslope) && (mo->type != MT_STEAM))
2553 {
2554 mo->standingslope = (mo->eflags & MFE_VERTICALFLIP) ? tmceilingslope : tmfloorslope;
2555 P_ReverseQuantizeMomentumToSlope(&mom, mo->standingslope);
2556 }
2557
2558 // hit the floor
2559 if (mo->type == MT_FIREBALL) // special case for the fireball
2560 mom.z = P_MobjFlip(mo)*FixedMul(5*FRACUNIT, mo->scale);
2561 else if (mo->type == MT_SPINFIRE) // elemental shield fire is another exception here
2562 ;
2563 else if (mo->flags & MF_MISSILE)
2564 {
2565 if (!(mo->flags & MF_NOCLIP))
2566 {
2567 // This is a really ugly hard-coded hack to prevent grenades
2568 // from exploding the instant they hit the ground, and then
2569 // another to prevent them from turning into hockey pucks.
2570 // I'm sorry in advance. -SH
2571 // PS: Oh, and Brak's napalm bombs too, now.
2572 if (mo->flags & MF_GRENADEBOUNCE)
2573 {
2574 // Going down? (Or up in reverse gravity?)
2575 if (P_MobjFlip(mo)*mom.z < 0)
2576 {
2577 // If going slower than a fracunit, just stop.
2578 if (abs(mom.z) < FixedMul(FRACUNIT, mo->scale))
2579 {
2580 mom.x = mom.y = mom.z = 0;
2581
2582 // Napalm hack
2583 if (mo->type == MT_CYBRAKDEMON_NAPALM_BOMB_LARGE && mo->fuse)
2584 mo->fuse = 1;
2585 }
2586 // Otherwise bounce up at half speed.
2587 else
2588 mom.z = -mom.z/2;
2589 S_StartSound(mo, mo->info->activesound);
2590 }
2591 }
2592 // Hack over. Back to your regularly scheduled detonation. -SH
2593 else
2594 {
2595 // Don't explode on the sky!
2596 if (!(mo->eflags & MFE_VERTICALFLIP)
2597 && mo->subsector->sector->floorpic == skyflatnum
2598 && mo->subsector->sector->floorheight == mo->floorz)
2599 P_RemoveMobj(mo);
2600 else if (mo->eflags & MFE_VERTICALFLIP
2601 && mo->subsector->sector->ceilingpic == skyflatnum
2602 && mo->subsector->sector->ceilingheight == mo->ceilingz)
2603 P_RemoveMobj(mo);
2604 else
2605 P_ExplodeMissile(mo);
2606 return false;
2607 }
2608 }
2609 }
2610
2611 if (P_MobjFlip(mo)*mom.z < 0) // falling
2612 {
2613 mo->eflags |= MFE_JUSTHITFLOOR;
2614
2615 if (mo->flags2 & MF2_SKULLFLY) // the skull slammed into something
2616 mom.z = -mom.z;
2617 else
2618 // Flingrings bounce
2619 if (mo->type == MT_FLINGRING
2620 || mo->type == MT_FLINGCOIN
2621 || mo->type == MT_FLINGBLUESPHERE
2622 || mo->type == MT_FLINGNIGHTSCHIP
2623 || P_WeaponOrPanel(mo->type)
2624 || mo->type == MT_FLINGEMERALD
2625 || mo->type == MT_BIGTUMBLEWEED
2626 || mo->type == MT_LITTLETUMBLEWEED
2627 || mo->type == MT_CANNONBALLDECOR
2628 || mo->type == MT_FALLINGROCK)
2629 {
2630 if (maptol & TOL_NIGHTS)
2631 mom.z = -FixedDiv(mom.z, 10*FRACUNIT);
2632 else
2633 mom.z = -FixedMul(mom.z, FixedDiv(17*FRACUNIT,20*FRACUNIT));
2634
2635 if (mo->type == MT_BIGTUMBLEWEED || mo->type == MT_LITTLETUMBLEWEED)
2636 {
2637 if (abs(mom.x) < FixedMul(STOPSPEED, mo->scale)
2638 && abs(mom.y) < FixedMul(STOPSPEED, mo->scale)
2639 && abs(mom.z) < FixedMul(STOPSPEED*3, mo->scale))
2640 {
2641 if (mo->flags2 & MF2_AMBUSH)
2642 {
2643 // If deafed, give the tumbleweed another random kick if it runs out of steam.
2644 mom.z += P_MobjFlip(mo)*FixedMul(6*FRACUNIT, mo->scale);
2645
2646 if (P_RandomChance(FRACUNIT/2))
2647 mom.x += FixedMul(6*FRACUNIT, mo->scale);
2648 else
2649 mom.x -= FixedMul(6*FRACUNIT, mo->scale);
2650
2651 if (P_RandomChance(FRACUNIT/2))
2652 mom.y += FixedMul(6*FRACUNIT, mo->scale);
2653 else
2654 mom.y -= FixedMul(6*FRACUNIT, mo->scale);
2655 }
2656 else if (mo->standingslope && abs(mo->standingslope->zdelta) > FRACUNIT>>8)
2657 {
2658 // Pop the object up a bit to encourage bounciness
2659 //mom.z = P_MobjFlip(mo)*mo->scale;
2660 }
2661 else
2662 {
2663 mom.x = mom.y = mom.z = 0;
2664 P_SetMobjState(mo, mo->info->spawnstate);
2665 }
2666 }
2667
2668 // Stolen from P_SpawnFriction
2669 mo->friction = FRACUNIT - 0x100;
2670 }
2671 else if (mo->type == MT_FALLINGROCK)
2672 {
2673 if (P_MobjFlip(mo)*mom.z > FixedMul(2*FRACUNIT, mo->scale))
2674 S_StartSound(mo, mo->info->activesound + P_RandomKey(mo->info->reactiontime));
2675
2676 mom.z /= 2; // Rocks not so bouncy
2677
2678 if (abs(mom.x) < FixedMul(STOPSPEED, mo->scale)
2679 && abs(mom.y) < FixedMul(STOPSPEED, mo->scale)
2680 && abs(mom.z) < FixedMul(STOPSPEED*3, mo->scale))
2681 {
2682 P_RemoveMobj(mo);
2683 return false;
2684 }
2685 }
2686 else if (mo->type == MT_CANNONBALLDECOR)
2687 {
2688 mom.z /= 2;
2689 if (abs(mom.z) < FixedMul(STOPSPEED*3, mo->scale))
2690 mom.z = 0;
2691 }
2692 }
2693 else
2694 mom.z = (tmfloorthing ? tmfloorthing->momz : 0);
2695
2696 }
2697 else if (tmfloorthing)
2698 mom.z = tmfloorthing->momz;
2699
2700 if (mo->standingslope) { // MT_STEAM will never have a standingslope, see above.
2701 P_QuantizeMomentumToSlope(&mom, mo->standingslope);
2702 }
2703
2704 mo->momx = mom.x;
2705 mo->momy = mom.y;
2706 mo->momz = mom.z;
2707
2708 if (mo->type == MT_STEAM)
2709 return true;
2710 }
2711 else if (!(mo->flags & MF_NOGRAVITY)) // Gravity here!
2712 {
2713 /// \todo may not be needed (done in P_MobjThinker normally)
2714 mo->eflags &= ~MFE_JUSTHITFLOOR;
2715
2716 P_CheckGravity(mo, true);
2717 }
2718
2719 if (((mo->z + mo->height > mo->ceilingz && !(mo->eflags & MFE_VERTICALFLIP))
2720 || (mo->z < mo->floorz && mo->eflags & MFE_VERTICALFLIP))
2721 && !(mo->flags & MF_NOCLIPHEIGHT))
2722 {
2723 if (mo->eflags & MFE_VERTICALFLIP)
2724 mo->z = mo->floorz;
2725 else
2726 mo->z = mo->ceilingz - mo->height;
2727
2728 if (mo->type == MT_SPINFIRE)
2729 ;
2730 else if ((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP))
2731 {
2732 // Hack 2: Electric Boogaloo -SH
2733 if (mo->flags & MF_GRENADEBOUNCE)
2734 {
2735 if (P_MobjFlip(mo)*mo->momz >= 0)
2736 {
2737 mo->momz = -mo->momz;
2738 S_StartSound(mo, mo->info->activesound);
2739 }
2740 }
2741 else
2742 {
2743 // Don't explode on the sky!
2744 if (!(mo->eflags & MFE_VERTICALFLIP)
2745 && mo->subsector->sector->ceilingpic == skyflatnum
2746 && mo->subsector->sector->ceilingheight == mo->ceilingz)
2747 P_RemoveMobj(mo);
2748 else if (mo->eflags & MFE_VERTICALFLIP
2749 && mo->subsector->sector->floorpic == skyflatnum
2750 && mo->subsector->sector->floorheight == mo->floorz)
2751 P_RemoveMobj(mo);
2752 else
2753 P_ExplodeMissile(mo);
2754 return false;
2755 }
2756 }
2757
2758 if (P_MobjFlip(mo)*mo->momz > 0) // hit the ceiling
2759 {
2760 if (mo->flags2 & MF2_SKULLFLY) // the skull slammed into something
2761 mo->momz = -mo->momz;
2762 else
2763 // Flags bounce
2764 if (mo->type == MT_REDFLAG || mo->type == MT_BLUEFLAG)
2765 {
2766 if (maptol & TOL_NIGHTS)
2767 mo->momz = -FixedDiv(mo->momz, 10*FRACUNIT);
2768 else
2769 mo->momz = -FixedMul(mo->momz, FixedDiv(17*FRACUNIT,20*FRACUNIT));
2770 }
2771 else
2772 mo->momz = 0;
2773 }
2774 }
2775
2776 return true;
2777 }
2778
2779 // Check for "Mario" blocks to hit and bounce them
P_CheckMarioBlocks(mobj_t * mo)2780 static void P_CheckMarioBlocks(mobj_t *mo)
2781 {
2782 msecnode_t *node;
2783
2784 if (netgame && mo->player->spectator)
2785 return;
2786
2787 for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
2788 {
2789 ffloor_t *rover;
2790
2791 if (!node->m_sector->ffloors)
2792 continue;
2793
2794 for (rover = node->m_sector->ffloors; rover; rover = rover->next)
2795 {
2796 if (!(rover->flags & FF_EXISTS))
2797 continue;
2798
2799 if (!(rover->flags & FF_MARIO))
2800 continue;
2801
2802 if (mo->eflags & MFE_VERTICALFLIP)
2803 continue; // if you were flipped, your head isn't actually hitting your ceilingz is it?
2804
2805 if (*rover->bottomheight != mo->ceilingz)
2806 continue;
2807
2808 if (rover->flags & FF_SHATTERBOTTOM) // Brick block!
2809 EV_CrumbleChain(node->m_sector, rover);
2810 else // Question block!
2811 EV_MarioBlock(rover, node->m_sector, mo);
2812 }
2813 }
2814 }
2815
2816 // Check if we're on a polyobject that triggers a linedef executor.
P_PlayerPolyObjectZMovement(mobj_t * mo)2817 static boolean P_PlayerPolyObjectZMovement(mobj_t *mo)
2818 {
2819 msecnode_t *node;
2820 boolean stopmovecut = false;
2821
2822 for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
2823 {
2824 sector_t *sec = node->m_sector;
2825 subsector_t *newsubsec;
2826 size_t i;
2827
2828 for (i = 0; i < numsubsectors; i++)
2829 {
2830 polyobj_t *po;
2831 sector_t *polysec;
2832 newsubsec = &subsectors[i];
2833
2834 if (newsubsec->sector != sec)
2835 continue;
2836
2837 for (po = newsubsec->polyList; po; po = (polyobj_t *)(po->link.next))
2838 {
2839 if (!(po->flags & POF_SOLID))
2840 continue;
2841
2842 if (!P_MobjInsidePolyobj(po, mo))
2843 continue;
2844
2845 polysec = po->lines[0]->backsector;
2846
2847 // Moving polyobjects should act like conveyors if the player lands on one. (I.E. none of the momentum cut thing below) -Red
2848 if ((mo->z == polysec->ceilingheight || mo->z + mo->height == polysec->floorheight) && po->thinker)
2849 stopmovecut = true;
2850
2851 if (!(po->flags & POF_LDEXEC))
2852 continue;
2853
2854 if (mo->z != polysec->ceilingheight)
2855 continue;
2856
2857 // We're landing on a PO, so check for a linedef executor.
2858 P_LinedefExecute(po->triggertag, mo, NULL);
2859 }
2860 }
2861 }
2862
2863 return stopmovecut;
2864 }
2865
P_PlayerZMovement(mobj_t * mo)2866 void P_PlayerZMovement(mobj_t *mo)
2867 {
2868 boolean onground;
2869
2870 I_Assert(mo != NULL);
2871 I_Assert(!P_MobjWasRemoved(mo));
2872
2873 if (!mo->player)
2874 return;
2875
2876 // Intercept the stupid 'fall through 3dfloors' bug
2877 if (mo->subsector->sector->ffloors)
2878 P_AdjustMobjFloorZ_FFloors(mo, mo->subsector->sector, 0);
2879 if (mo->subsector->polyList)
2880 P_AdjustMobjFloorZ_PolyObjs(mo, mo->subsector);
2881
2882 // check for smooth step up
2883 if ((mo->eflags & MFE_VERTICALFLIP && mo->z + mo->height > mo->ceilingz)
2884 || (!(mo->eflags & MFE_VERTICALFLIP) && mo->z < mo->floorz))
2885 {
2886 if (mo->eflags & MFE_VERTICALFLIP)
2887 mo->player->viewheight -= (mo->z+mo->height) - mo->ceilingz;
2888 else
2889 mo->player->viewheight -= mo->floorz - mo->z;
2890
2891 mo->player->deltaviewheight =
2892 (FixedMul(41*mo->player->height/48, mo->scale) - mo->player->viewheight)>>3;
2893 }
2894
2895 // adjust height
2896 if (mo->eflags & MFE_APPLYPMOMZ && !P_IsObjectOnGround(mo))
2897 {
2898 mo->momz += mo->pmomz;
2899 mo->pmomz = 0;
2900 mo->eflags &= ~MFE_APPLYPMOMZ;
2901 }
2902
2903 mo->z += mo->momz;
2904 onground = P_IsObjectOnGround(mo);
2905
2906 // Have player fall through floor?
2907 if (mo->player->playerstate == PST_DEAD
2908 || mo->player->playerstate == PST_REBORN)
2909 return;
2910
2911 if (mo->standingslope)
2912 {
2913 if (mo->flags & MF_NOCLIPHEIGHT)
2914 mo->standingslope = NULL;
2915 else if (!onground)
2916 P_SlopeLaunch(mo);
2917 }
2918
2919 // clip movement
2920 if (onground && !(mo->flags & MF_NOCLIPHEIGHT))
2921 {
2922 if (mo->eflags & MFE_VERTICALFLIP)
2923 mo->z = mo->ceilingz - mo->height;
2924 else
2925 mo->z = mo->floorz;
2926
2927 if (mo->player->powers[pw_carry] == CR_NIGHTSMODE)
2928 {
2929 // bounce off floor if you were flying towards it
2930 if ((mo->eflags & MFE_VERTICALFLIP && mo->player->flyangle > 0 && mo->player->flyangle < 180)
2931 || (!(mo->eflags & MFE_VERTICALFLIP) && mo->player->flyangle > 180 && mo->player->flyangle <= 359))
2932 {
2933 if (mo->player->flyangle < 90 || mo->player->flyangle >= 270)
2934 mo->player->flyangle += P_MobjFlip(mo)*90;
2935 else
2936 mo->player->flyangle -= P_MobjFlip(mo)*90;
2937 mo->player->speed = FixedMul(mo->player->speed, 4*FRACUNIT/5);
2938 }
2939 goto nightsdone;
2940 }
2941 // Get up if you fell.
2942 if (mo->player->panim == PA_PAIN)
2943 P_SetPlayerMobjState(mo, S_PLAY_WALK);
2944
2945 if (!mo->standingslope && (mo->eflags & MFE_VERTICALFLIP ? tmceilingslope : tmfloorslope)) {
2946 // Handle landing on slope during Z movement
2947 P_HandleSlopeLanding(mo, (mo->eflags & MFE_VERTICALFLIP ? tmceilingslope : tmfloorslope));
2948 }
2949
2950 if (P_MobjFlip(mo)*mo->momz < 0) // falling
2951 {
2952 boolean clipmomz = !(P_CheckDeathPitCollide(mo));
2953
2954 mo->pmomz = 0; // We're on a new floor, don't keep doing platform movement.
2955
2956 // Squat down. Decrease viewheight for a moment after hitting the ground (hard),
2957 if (P_MobjFlip(mo)*mo->momz < -FixedMul(8*FRACUNIT, mo->scale))
2958 mo->player->deltaviewheight = (P_MobjFlip(mo)*mo->momz)>>3; // make sure momz is negative
2959
2960 mo->eflags |= MFE_JUSTHITFLOOR; // Spin Attack
2961
2962 clipmomz = P_PlayerHitFloor(mo->player, true);
2963
2964 if (!P_PlayerPolyObjectZMovement(mo))
2965 {
2966 // Cut momentum in half when you hit the ground and
2967 // aren't pressing any controls.
2968 if (!(mo->player->cmd.forwardmove || mo->player->cmd.sidemove) && !mo->player->cmomx && !mo->player->cmomy && !(mo->player->pflags & PF_SPINNING))
2969 {
2970 mo->momx >>= 1;
2971 mo->momy >>= 1;
2972 }
2973 }
2974
2975 if (!(mo->player->pflags & PF_SPINNING) && mo->player->powers[pw_carry] != CR_NIGHTSMODE)
2976 mo->player->pflags &= ~PF_STARTDASH;
2977
2978 if (clipmomz)
2979 mo->momz = (tmfloorthing ? tmfloorthing->momz : 0);
2980 }
2981 else if (tmfloorthing)
2982 mo->momz = tmfloorthing->momz;
2983 }
2984 else if (!(mo->flags & MF_NOGRAVITY)) // Gravity here!
2985 {
2986 if (P_IsObjectInGoop(mo) && !(mo->flags & MF_NOCLIPHEIGHT))
2987 {
2988 if (mo->z < mo->floorz)
2989 {
2990 mo->z = mo->floorz;
2991 mo->momz = 0;
2992 }
2993 else if (mo->z + mo->height > mo->ceilingz)
2994 {
2995 mo->z = mo->ceilingz - mo->height;
2996 mo->momz = 0;
2997 }
2998 }
2999 /// \todo may not be needed (done in P_MobjThinker normally)
3000 mo->eflags &= ~MFE_JUSTHITFLOOR;
3001 P_CheckGravity(mo, true);
3002 }
3003
3004 nightsdone:
3005
3006 if (((mo->eflags & MFE_VERTICALFLIP && mo->z < mo->floorz) || (!(mo->eflags & MFE_VERTICALFLIP) && mo->z + mo->height > mo->ceilingz))
3007 && !(mo->flags & MF_NOCLIPHEIGHT))
3008 {
3009 if (mo->eflags & MFE_VERTICALFLIP)
3010 mo->z = mo->floorz;
3011 else
3012 mo->z = mo->ceilingz - mo->height;
3013
3014 if (mo->player->powers[pw_carry] == CR_NIGHTSMODE)
3015 {
3016 // bounce off ceiling if you were flying towards it
3017 if ((mo->eflags & MFE_VERTICALFLIP && mo->player->flyangle > 180 && mo->player->flyangle <= 359)
3018 || (!(mo->eflags & MFE_VERTICALFLIP) && mo->player->flyangle > 0 && mo->player->flyangle < 180))
3019 {
3020 if (mo->player->flyangle < 90 || mo->player->flyangle >= 270)
3021 mo->player->flyangle -= P_MobjFlip(mo)*90;
3022 else
3023 mo->player->flyangle += P_MobjFlip(mo)*90;
3024 mo->player->flyangle %= 360;
3025 mo->player->speed = FixedMul(mo->player->speed, 4*FRACUNIT/5);
3026 }
3027 }
3028
3029 if (P_MobjFlip(mo)*mo->momz > 0)
3030 {
3031 if (CheckForMarioBlocks)
3032 P_CheckMarioBlocks(mo);
3033
3034 // hit the ceiling
3035 if (mariomode)
3036 S_StartSound(mo, sfx_mario1);
3037
3038 if (!mo->player->climbing)
3039 mo->momz = 0;
3040 }
3041 }
3042 }
3043
P_SceneryZMovement(mobj_t * mo)3044 boolean P_SceneryZMovement(mobj_t *mo)
3045 {
3046 // Intercept the stupid 'fall through 3dfloors' bug
3047 if (mo->subsector->sector->ffloors)
3048 P_AdjustMobjFloorZ_FFloors(mo, mo->subsector->sector, 2);
3049 if (mo->subsector->polyList)
3050 P_AdjustMobjFloorZ_PolyObjs(mo, mo->subsector);
3051
3052 // adjust height
3053 if (mo->eflags & MFE_APPLYPMOMZ && !P_IsObjectOnGround(mo))
3054 {
3055 mo->momz += mo->pmomz;
3056 mo->pmomz = 0;
3057 mo->eflags &= ~MFE_APPLYPMOMZ;
3058 }
3059 mo->z += mo->momz;
3060
3061 switch (mo->type)
3062 {
3063 case MT_SMALLBUBBLE:
3064 if (mo->z <= mo->floorz || mo->z+mo->height >= mo->ceilingz) // Hit the floor, so POP!
3065 {
3066 // don't sounds stop when you kill the mobj..?
3067 // yes, they do, making this entirely redundant
3068 P_RemoveMobj(mo);
3069 return false;
3070 }
3071 break;
3072 case MT_MEDIUMBUBBLE:
3073 if (P_CheckDeathPitCollide(mo)) // Don't split if you fell in a pit
3074 {
3075 P_RemoveMobj(mo);
3076 return false;
3077 }
3078 if ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z <= mo->floorz)
3079 || (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height >= mo->ceilingz)) // Hit the floor, so split!
3080 {
3081 // split
3082 mobj_t *explodemo = NULL;
3083 UINT8 prandom, i;
3084
3085 for (i = 0; i < 4; ++i) // split into four
3086 {
3087 prandom = P_RandomByte();
3088 explodemo = P_SpawnMobj(mo->x, mo->y, mo->z, MT_SMALLBUBBLE);
3089 explodemo->momx += ((prandom & 0x0F) << (FRACBITS-2)) * (i & 2 ? -1 : 1);
3090 explodemo->momy += ((prandom & 0xF0) << (FRACBITS-6)) * (i & 1 ? -1 : 1);
3091 explodemo->destscale = mo->scale;
3092 P_SetScale(explodemo, mo->scale);
3093 }
3094
3095 if (mo->threshold != 42) // Don't make pop sound if threshold is 42.
3096 S_StartSound(explodemo, sfx_bubbl1 + P_RandomKey(5));
3097 //note that we assign the bubble sound to one of the new bubbles.
3098 // in other words, IT ACTUALLY GETS USED YAAAAAAAY
3099
3100 P_RemoveMobj(mo);
3101 return false;
3102 }
3103 else if (mo->z <= mo->floorz || mo->z+mo->height >= mo->ceilingz) // Hit the ceiling instead? Just disappear anyway
3104 {
3105 P_RemoveMobj(mo);
3106 return false;
3107 }
3108 break;
3109 case MT_SEED: // now scenery
3110 if (P_CheckDeathPitCollide(mo)) // No flowers for death pits
3111 {
3112 P_RemoveMobj(mo);
3113 return false;
3114 }
3115 // Soniccd seed turns into a flower!
3116 if ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z <= mo->floorz)
3117 || (mo->eflags & MFE_VERTICALFLIP && mo->z+mo->height >= mo->ceilingz))
3118 {
3119 mobjtype_t flowertype = ((P_RandomChance(FRACUNIT/2)) ? MT_GFZFLOWER1 : MT_GFZFLOWER3);
3120 mobj_t *flower = P_SpawnMobjFromMobj(mo, 0, 0, 0, flowertype);
3121 if (flower)
3122 {
3123 P_SetScale(flower, mo->scale/16);
3124 flower->destscale = mo->scale;
3125 flower->scalespeed = mo->scale/8;
3126 }
3127
3128 P_RemoveMobj(mo);
3129 return false;
3130 }
3131 default:
3132 break;
3133 }
3134
3135 if (P_CheckDeathPitCollide(mo))
3136 {
3137 P_RemoveMobj(mo);
3138 return false;
3139 }
3140
3141 // clip movement
3142 if (((mo->z <= mo->floorz && !(mo->eflags & MFE_VERTICALFLIP))
3143 || (mo->z + mo->height >= mo->ceilingz && mo->eflags & MFE_VERTICALFLIP))
3144 && !(mo->flags & MF_NOCLIPHEIGHT))
3145 {
3146 if (mo->eflags & MFE_VERTICALFLIP)
3147 mo->z = mo->ceilingz - mo->height;
3148 else
3149 mo->z = mo->floorz;
3150
3151 if (P_MobjFlip(mo)*mo->momz < 0) // falling
3152 {
3153 mo->eflags |= MFE_JUSTHITFLOOR; // Spin Attack
3154
3155 if (tmfloorthing)
3156 mo->momz = tmfloorthing->momz;
3157 else if (!tmfloorthing)
3158 mo->momz = 0;
3159 }
3160 }
3161 else if (!(mo->flags & MF_NOGRAVITY)) // Gravity here!
3162 {
3163 /// \todo may not be needed (done in P_MobjThinker normally)
3164 mo->eflags &= ~MFE_JUSTHITFLOOR;
3165
3166 P_CheckGravity(mo, true);
3167 }
3168
3169 if (((mo->z + mo->height > mo->ceilingz && !(mo->eflags & MFE_VERTICALFLIP))
3170 || (mo->z < mo->floorz && mo->eflags & MFE_VERTICALFLIP))
3171 && !(mo->flags & MF_NOCLIPHEIGHT))
3172 {
3173 if (mo->eflags & MFE_VERTICALFLIP)
3174 mo->z = mo->floorz;
3175 else
3176 mo->z = mo->ceilingz - mo->height;
3177
3178 if (P_MobjFlip(mo)*mo->momz > 0) // hit the ceiling
3179 mo->momz = 0;
3180 }
3181
3182 return true;
3183 }
3184
3185 // P_CanRunOnWater
3186 //
3187 // Returns true if player can waterrun on the 3D floor
3188 //
P_CanRunOnWater(player_t * player,ffloor_t * rover)3189 boolean P_CanRunOnWater(player_t *player, ffloor_t *rover)
3190 {
3191 boolean flip = player->mo->eflags & MFE_VERTICALFLIP;
3192 fixed_t surfaceheight = flip ? P_GetFFloorBottomZAt(rover, player->mo->x, player->mo->y) : P_GetFFloorTopZAt(rover, player->mo->x, player->mo->y);
3193 fixed_t playerbottom = flip ? (player->mo->z + player->mo->height) : player->mo->z;
3194 boolean doifit = flip ? (surfaceheight - player->mo->floorz >= player->mo->height) : (player->mo->ceilingz - surfaceheight >= player->mo->height);
3195
3196 if (!player->powers[pw_carry] && !player->homing
3197 && ((player->powers[pw_super] || player->charflags & SF_RUNONWATER || player->dashmode >= DASHMODE_THRESHOLD) && doifit)
3198 && (rover->flags & FF_SWIMMABLE) && !(player->pflags & PF_SPINNING) && player->speed > FixedMul(player->runspeed, player->mo->scale)
3199 && !(player->pflags & PF_SLIDING)
3200 && abs(playerbottom - surfaceheight) < FixedMul(30*FRACUNIT, player->mo->scale))
3201 return true;
3202
3203 return false;
3204 }
3205
3206 //
3207 // P_MobjCheckWater
3208 //
3209 // Check for water, set stuff in mobj_t struct for movement code later.
3210 // This is called either by P_MobjThinker() or P_PlayerThink()
P_MobjCheckWater(mobj_t * mobj)3211 void P_MobjCheckWater(mobj_t *mobj)
3212 {
3213 boolean waterwasnotset = (mobj->watertop == INT32_MAX);
3214 boolean wasinwater = (mobj->eflags & MFE_UNDERWATER) == MFE_UNDERWATER;
3215 boolean wasingoo = (mobj->eflags & MFE_GOOWATER) == MFE_GOOWATER;
3216 fixed_t thingtop = mobj->z + mobj->height;
3217 sector_t *sector = mobj->subsector->sector;
3218 ffloor_t *rover;
3219 player_t *p = mobj->player; // Will just be null if not a player.
3220 fixed_t height = (p ? P_GetPlayerHeight(p) : mobj->height); // for players, calculation height does not necessarily match actual height for gameplay reasons (spin, etc)
3221 boolean wasgroundpounding = (p && ((p->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (p->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (p->pflags & PF_SHIELDABILITY));
3222
3223 // Default if no water exists.
3224 mobj->watertop = mobj->waterbottom = mobj->z - 1000*FRACUNIT;
3225
3226 // Reset water state.
3227 mobj->eflags &= ~(MFE_UNDERWATER|MFE_TOUCHWATER|MFE_GOOWATER|MFE_TOUCHLAVA);
3228
3229 for (rover = sector->ffloors; rover; rover = rover->next)
3230 {
3231 fixed_t topheight, bottomheight;
3232 if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_SWIMMABLE)
3233 || (((rover->flags & FF_BLOCKPLAYER) && mobj->player)
3234 || ((rover->flags & FF_BLOCKOTHERS) && !mobj->player)))
3235 continue;
3236
3237 topheight = P_GetFFloorTopZAt (rover, mobj->x, mobj->y);
3238 bottomheight = P_GetFFloorBottomZAt(rover, mobj->x, mobj->y);
3239
3240 if (mobj->eflags & MFE_VERTICALFLIP)
3241 {
3242 if (topheight < (thingtop - (height>>1))
3243 || bottomheight > thingtop)
3244 continue;
3245 }
3246 else
3247 {
3248 if (topheight < mobj->z
3249 || bottomheight > (mobj->z + (height>>1)))
3250 continue;
3251 }
3252
3253 // Set the watertop and waterbottom
3254 mobj->watertop = topheight;
3255 mobj->waterbottom = bottomheight;
3256
3257 // Just touching the water?
3258 if (((mobj->eflags & MFE_VERTICALFLIP) && thingtop - height < bottomheight)
3259 || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + height > topheight))
3260 mobj->eflags |= MFE_TOUCHWATER;
3261
3262 // Actually in the water?
3263 if (((mobj->eflags & MFE_VERTICALFLIP) && thingtop - (height>>1) > bottomheight)
3264 || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + (height>>1) < topheight))
3265 mobj->eflags |= MFE_UNDERWATER;
3266
3267 if (mobj->eflags & (MFE_TOUCHWATER|MFE_UNDERWATER))
3268 {
3269 if (GETSECSPECIAL(rover->master->frontsector->special, 1) == 3)
3270 mobj->eflags |= MFE_TOUCHLAVA;
3271
3272 if (rover->flags & FF_GOOWATER && !(mobj->flags & MF_NOGRAVITY))
3273 mobj->eflags |= MFE_GOOWATER;
3274 }
3275 }
3276
3277 // Spectators and dead players don't get to do any of the things after this.
3278 if (p && (p->spectator || p->playerstate != PST_LIVE))
3279 return;
3280
3281 // Specific things for underwater players
3282 if (p && (mobj->eflags & MFE_UNDERWATER) == MFE_UNDERWATER)
3283 {
3284 if (!((p->powers[pw_super]) || (p->powers[pw_invulnerability])))
3285 {
3286 boolean electric = !!(p->powers[pw_shield] & SH_PROTECTELECTRIC);
3287 if (electric || ((p->powers[pw_shield] & SH_PROTECTFIRE) && !(p->powers[pw_shield] & SH_PROTECTWATER) && !(mobj->eflags & MFE_TOUCHLAVA)))
3288 { // Water removes electric and non-water fire shields...
3289 P_FlashPal(p,
3290 electric
3291 ? PAL_WHITE
3292 : PAL_NUKE,
3293 1);
3294 p->powers[pw_shield] = p->powers[pw_shield] & SH_STACK;
3295 }
3296 }
3297
3298 // Drown timer setting
3299 if ((p->powers[pw_shield] & SH_PROTECTWATER) // Has water protection
3300 || (p->exiting) || (p->pflags & PF_FINISHED) // Or finished/exiting
3301 || (maptol & TOL_NIGHTS) // Or in NiGHTS mode
3302 || (mariomode)) // Or in Mario mode...
3303 {
3304 // Can't drown.
3305 p->powers[pw_underwater] = 0;
3306 }
3307 else if (p->powers[pw_underwater] <= 0) // No underwater timer set
3308 {
3309 // Then we'll set it!
3310 p->powers[pw_underwater] = underwatertics + 1;
3311 }
3312
3313 if ((wasgroundpounding = ((mobj->eflags & MFE_GOOWATER) && wasgroundpounding)))
3314 {
3315 p->pflags &= ~PF_SHIELDABILITY;
3316 mobj->momz >>= 1;
3317 }
3318 }
3319
3320 // The rest of this code only executes on a water state change.
3321 if (waterwasnotset || !!(mobj->eflags & MFE_UNDERWATER) == wasinwater)
3322 return;
3323
3324 if ((p) // Players
3325 || (mobj->flags & MF_PUSHABLE) // Pushables
3326 || ((mobj->info->flags & MF_PUSHABLE) && mobj->fuse) // Previously pushable, might be moving still
3327 )
3328 {
3329 // Check to make sure you didn't just cross into a sector to jump out of
3330 // that has shallower water than the block you were originally in.
3331 if ((!(mobj->eflags & MFE_VERTICALFLIP) && mobj->watertop-mobj->floorz <= height>>1)
3332 || ((mobj->eflags & MFE_VERTICALFLIP) && mobj->ceilingz-mobj->waterbottom <= height>>1))
3333 return;
3334
3335 if (mobj->eflags & MFE_GOOWATER || wasingoo) { // Decide what happens to your momentum when you enter/leave goopy water.
3336 if (P_MobjFlip(mobj)*mobj->momz > 0)
3337 {
3338 mobj->momz -= (mobj->momz/8); // cut momentum a little bit to prevent multiple bobs
3339 //CONS_Printf("leaving\n");
3340 }
3341 else
3342 {
3343 if (!wasgroundpounding)
3344 mobj->momz >>= 1; // kill momentum significantly, to make the goo feel thick.
3345 //CONS_Printf("entering\n");
3346 }
3347 }
3348 else if (wasinwater && P_MobjFlip(mobj)*mobj->momz > 0)
3349 mobj->momz = FixedMul(mobj->momz, FixedDiv(780*FRACUNIT, 457*FRACUNIT)); // Give the mobj a little out-of-water boost.
3350
3351 if (P_MobjFlip(mobj)*mobj->momz < 0)
3352 {
3353 if ((mobj->eflags & MFE_VERTICALFLIP && thingtop-(height>>1)-mobj->momz <= mobj->waterbottom)
3354 || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z+(height>>1)-mobj->momz >= mobj->watertop))
3355 {
3356 // Spawn a splash
3357 mobj_t *splish;
3358 mobjtype_t splishtype = (mobj->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH;
3359 if (mobj->eflags & MFE_VERTICALFLIP)
3360 {
3361 splish = P_SpawnMobj(mobj->x, mobj->y, mobj->waterbottom-FixedMul(mobjinfo[splishtype].height, mobj->scale), splishtype);
3362 splish->flags2 |= MF2_OBJECTFLIP;
3363 splish->eflags |= MFE_VERTICALFLIP;
3364 }
3365 else
3366 splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, splishtype);
3367 splish->destscale = mobj->scale;
3368 P_SetScale(splish, mobj->scale);
3369 }
3370
3371 // skipping stone!
3372 if (p && p->speed/2 > abs(mobj->momz)
3373 && ((p->pflags & (PF_SPINNING|PF_JUMPED)) == PF_SPINNING)
3374 && ((!(mobj->eflags & MFE_VERTICALFLIP) && thingtop - mobj->momz > mobj->watertop)
3375 || ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z - mobj->momz < mobj->waterbottom)))
3376 {
3377 mobj->momz = -mobj->momz/2;
3378
3379 if (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->momz > FixedMul(6*FRACUNIT, mobj->scale))
3380 mobj->momz = FixedMul(6*FRACUNIT, mobj->scale);
3381 else if (mobj->eflags & MFE_VERTICALFLIP && mobj->momz < FixedMul(-6*FRACUNIT, mobj->scale))
3382 mobj->momz = FixedMul(-6*FRACUNIT, mobj->scale);
3383 }
3384
3385 }
3386 else if (P_MobjFlip(mobj)*mobj->momz > 0)
3387 {
3388 if (((mobj->eflags & MFE_VERTICALFLIP && thingtop-(height>>1)-mobj->momz > mobj->waterbottom)
3389 || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z+(height>>1)-mobj->momz < mobj->watertop))
3390 && !(mobj->eflags & MFE_UNDERWATER)) // underwater check to prevent splashes on opposite side
3391 {
3392 // Spawn a splash
3393 mobj_t *splish;
3394 mobjtype_t splishtype = (mobj->eflags & MFE_TOUCHLAVA) ? MT_LAVASPLISH : MT_SPLISH;
3395 if (mobj->eflags & MFE_VERTICALFLIP)
3396 {
3397 splish = P_SpawnMobj(mobj->x, mobj->y, mobj->waterbottom-FixedMul(mobjinfo[splishtype].height, mobj->scale), splishtype);
3398 splish->flags2 |= MF2_OBJECTFLIP;
3399 splish->eflags |= MFE_VERTICALFLIP;
3400 }
3401 else
3402 splish = P_SpawnMobj(mobj->x, mobj->y, mobj->watertop, splishtype);
3403 splish->destscale = mobj->scale;
3404 P_SetScale(splish, mobj->scale);
3405 }
3406 }
3407
3408 // Time to spawn the bubbles!
3409 {
3410 INT32 i;
3411 INT32 bubblecount;
3412 UINT8 prandom[4];
3413 mobj_t *bubble;
3414 mobjtype_t bubbletype;
3415
3416 if (mobj->eflags & MFE_GOOWATER || wasingoo)
3417 S_StartSound(mobj, sfx_ghit);
3418 else if (mobj->eflags & MFE_TOUCHLAVA)
3419 S_StartSound(mobj, sfx_splash);
3420 else
3421 S_StartSound(mobj, sfx_splish); // And make a sound!
3422
3423 bubblecount = FixedDiv(abs(mobj->momz), mobj->scale)>>(FRACBITS-1);
3424 // Max bubble count
3425 if (bubblecount > 128)
3426 bubblecount = 128;
3427
3428 // Create tons of bubbles
3429 for (i = 0; i < bubblecount; i++)
3430 {
3431 // P_RandomByte()s are called individually to allow consistency
3432 // across various compilers, since the order of function calls
3433 // in C is not part of the ANSI specification.
3434 prandom[0] = P_RandomByte();
3435 prandom[1] = P_RandomByte();
3436 prandom[2] = P_RandomByte();
3437 prandom[3] = P_RandomByte();
3438
3439 bubbletype = MT_SMALLBUBBLE;
3440 if (!(prandom[0] & 0x3)) // medium bubble chance up to 64 from 32
3441 bubbletype = MT_MEDIUMBUBBLE;
3442
3443 bubble = P_SpawnMobj(
3444 mobj->x + FixedMul((prandom[1]<<(FRACBITS-3)) * (prandom[0]&0x80 ? 1 : -1), mobj->scale),
3445 mobj->y + FixedMul((prandom[2]<<(FRACBITS-3)) * (prandom[0]&0x40 ? 1 : -1), mobj->scale),
3446 mobj->z + FixedMul((prandom[3]<<(FRACBITS-2)), mobj->scale), bubbletype);
3447
3448 if (bubble)
3449 {
3450 if (P_MobjFlip(mobj)*mobj->momz < 0)
3451 bubble->momz = mobj->momz >> 4;
3452 else
3453 bubble->momz = 0;
3454
3455 bubble->destscale = mobj->scale;
3456 P_SetScale(bubble, mobj->scale);
3457 }
3458 }
3459 }
3460 }
3461 }
3462
P_SceneryCheckWater(mobj_t * mobj)3463 static void P_SceneryCheckWater(mobj_t *mobj)
3464 {
3465 sector_t *sector;
3466
3467 // Default if no water exists.
3468 mobj->watertop = mobj->waterbottom = mobj->z - 1000*FRACUNIT;
3469
3470 // see if we are in water, and set some flags for later
3471 sector = mobj->subsector->sector;
3472
3473 if (sector->ffloors)
3474 {
3475 ffloor_t *rover;
3476 fixed_t topheight, bottomheight;
3477
3478 mobj->eflags &= ~(MFE_UNDERWATER|MFE_TOUCHWATER);
3479
3480 for (rover = sector->ffloors; rover; rover = rover->next)
3481 {
3482 if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_SWIMMABLE) || rover->flags & FF_BLOCKOTHERS)
3483 continue;
3484
3485 topheight = P_GetFFloorTopZAt (rover, mobj->x, mobj->y);
3486 bottomheight = P_GetFFloorBottomZAt(rover, mobj->x, mobj->y);
3487
3488 if (topheight <= mobj->z
3489 || bottomheight > (mobj->z + (mobj->height>>1)))
3490 continue;
3491
3492 if (mobj->z + mobj->height > topheight)
3493 mobj->eflags |= MFE_TOUCHWATER;
3494 else
3495 mobj->eflags &= ~MFE_TOUCHWATER;
3496
3497 // Set the watertop and waterbottom
3498 mobj->watertop = topheight;
3499 mobj->waterbottom = bottomheight;
3500
3501 if (mobj->z + (mobj->height>>1) < topheight)
3502 mobj->eflags |= MFE_UNDERWATER;
3503 else
3504 mobj->eflags &= ~MFE_UNDERWATER;
3505 }
3506 }
3507 else
3508 mobj->eflags &= ~(MFE_UNDERWATER|MFE_TOUCHWATER);
3509 }
3510
P_CameraCheckHeat(camera_t * thiscam)3511 static boolean P_CameraCheckHeat(camera_t *thiscam)
3512 {
3513 sector_t *sector;
3514 fixed_t halfheight = thiscam->z + (thiscam->height >> 1);
3515 size_t i;
3516
3517 // see if we are in water
3518 sector = thiscam->subsector->sector;
3519
3520 for (i = 0; i < sector->tags.count; i++)
3521 if (Tag_FindLineSpecial(13, sector->tags.tags[i]) != -1)
3522 return true;
3523
3524 if (sector->ffloors)
3525 {
3526 ffloor_t *rover;
3527 size_t j;
3528
3529 for (rover = sector->ffloors; rover; rover = rover->next)
3530 {
3531 if (!(rover->flags & FF_EXISTS))
3532 continue;
3533
3534 if (halfheight >= P_GetFFloorTopZAt(rover, thiscam->x, thiscam->y))
3535 continue;
3536 if (halfheight <= P_GetFFloorBottomZAt(rover, thiscam->x, thiscam->y))
3537 continue;
3538
3539 for (j = 0; j < rover->master->frontsector->tags.count; j++)
3540 if (Tag_FindLineSpecial(13, rover->master->frontsector->tags.tags[j]) != -1)
3541 return true;
3542 }
3543 }
3544
3545 return false;
3546 }
3547
P_CameraCheckWater(camera_t * thiscam)3548 static boolean P_CameraCheckWater(camera_t *thiscam)
3549 {
3550 sector_t *sector;
3551 fixed_t halfheight = thiscam->z + (thiscam->height >> 1);
3552
3553 // see if we are in water
3554 sector = thiscam->subsector->sector;
3555
3556 if (sector->ffloors)
3557 {
3558 ffloor_t *rover;
3559
3560 for (rover = sector->ffloors; rover; rover = rover->next)
3561 {
3562 if (!(rover->flags & FF_EXISTS) || !(rover->flags & FF_SWIMMABLE) || rover->flags & FF_BLOCKOTHERS)
3563 continue;
3564
3565 if (halfheight >= P_GetFFloorTopZAt(rover, thiscam->x, thiscam->y))
3566 continue;
3567 if (halfheight <= P_GetFFloorBottomZAt(rover, thiscam->x, thiscam->y))
3568 continue;
3569
3570 return true;
3571 }
3572 }
3573
3574 return false;
3575 }
3576
P_DestroyRobots(void)3577 void P_DestroyRobots(void)
3578 {
3579 // Search through all the thinkers for enemies.
3580 mobj_t *mo;
3581 thinker_t *think;
3582
3583 for (think = thlist[THINK_MOBJ].next; think != &thlist[THINK_MOBJ]; think = think->next)
3584 {
3585 if (think->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
3586 continue;
3587
3588 mo = (mobj_t *)think;
3589 if (mo->health <= 0 || !(mo->flags & (MF_ENEMY|MF_BOSS)))
3590 continue; // not a valid enemy
3591
3592 if (mo->type == MT_PLAYER) // Don't chase after other players!
3593 continue;
3594
3595 // Found a target enemy
3596 P_KillMobj(mo, players[consoleplayer].mo, players[consoleplayer].mo, 0);
3597 }
3598 }
3599
3600 // the below is chasecam only, if you're curious. check out P_CalcPostImg in p_user.c for first person
P_CalcChasePostImg(player_t * player,camera_t * thiscam)3601 void P_CalcChasePostImg(player_t *player, camera_t *thiscam)
3602 {
3603 postimg_t postimg = postimg_none;
3604
3605 if (player->pflags & PF_FLIPCAM && !(player->powers[pw_carry] == CR_NIGHTSMODE) && player->mo->eflags & MFE_VERTICALFLIP)
3606 postimg = postimg_flip;
3607 else if (player->awayviewtics && player->awayviewmobj && !P_MobjWasRemoved(player->awayviewmobj)) // Camera must obviously exist
3608 {
3609 camera_t dummycam;
3610 dummycam.subsector = player->awayviewmobj->subsector;
3611 dummycam.x = player->awayviewmobj->x;
3612 dummycam.y = player->awayviewmobj->y;
3613 dummycam.z = player->awayviewmobj->z;
3614 //dummycam.height = 40*FRACUNIT; // alt view height is 20*FRACUNIT
3615 dummycam.height = 0; // Why? Remote viewpoint cameras have no height.
3616 // Are we in water?
3617 if (P_CameraCheckWater(&dummycam))
3618 postimg = postimg_water;
3619 else if (P_CameraCheckHeat(&dummycam))
3620 postimg = postimg_heat;
3621 }
3622 else
3623 {
3624 // Are we in water?
3625 if (P_CameraCheckWater(thiscam))
3626 postimg = postimg_water;
3627 else if (P_CameraCheckHeat(thiscam))
3628 postimg = postimg_heat;
3629 }
3630
3631 if (postimg == postimg_none)
3632 return;
3633
3634 if (splitscreen && player == &players[secondarydisplayplayer])
3635 postimgtype2 = postimg;
3636 else
3637 postimgtype = postimg;
3638 }
3639
3640 // P_CameraThinker
3641 //
3642 // Process the mobj-ish required functions of the camera
P_CameraThinker(player_t * player,camera_t * thiscam,boolean resetcalled)3643 boolean P_CameraThinker(player_t *player, camera_t *thiscam, boolean resetcalled)
3644 {
3645 boolean itsatwodlevel = false;
3646 if (twodlevel
3647 || (thiscam == &camera && players[displayplayer].mo && (players[displayplayer].mo->flags2 & MF2_TWOD))
3648 || (thiscam == &camera2 && players[secondarydisplayplayer].mo && (players[secondarydisplayplayer].mo->flags2 & MF2_TWOD)))
3649 itsatwodlevel = true;
3650
3651 P_CalcChasePostImg(player, thiscam);
3652
3653 if (thiscam->momx || thiscam->momy)
3654 {
3655 if (!P_TryCameraMove(thiscam->x + thiscam->momx, thiscam->y + thiscam->momy, thiscam)) // Thanks for the greatly improved camera, Lach -- Sev
3656 { // Never fails for 2D mode.
3657 mobj_t dummy;
3658 dummy.thinker.function.acp1 = (actionf_p1)P_MobjThinker;
3659 dummy.subsector = thiscam->subsector;
3660 dummy.x = thiscam->x;
3661 dummy.y = thiscam->y;
3662 dummy.z = thiscam->z;
3663 dummy.height = thiscam->height;
3664 if (!resetcalled && !(player->pflags & PF_NOCLIP) && !P_CheckSight(&dummy, player->mo)) // TODO: "P_CheckCameraSight" instead.
3665 P_ResetCamera(player, thiscam);
3666 else
3667 {
3668 fixed_t camspeed = P_AproxDistance(thiscam->momx, thiscam->momy);
3669
3670 P_SlideCameraMove(thiscam);
3671
3672 if (!resetcalled && P_AproxDistance(thiscam->momx, thiscam->momy) == camspeed)
3673 {
3674 P_ResetCamera(player, thiscam);
3675 resetcalled = true;
3676 }
3677 }
3678 if (resetcalled) // Okay this means the camera is fully reset.
3679 return true;
3680 }
3681 }
3682
3683 if (!itsatwodlevel)
3684 P_CheckCameraPosition(thiscam->x, thiscam->y, thiscam);
3685
3686 thiscam->subsector = R_PointInSubsector(thiscam->x, thiscam->y);
3687 thiscam->floorz = tmfloorz;
3688 thiscam->ceilingz = tmceilingz;
3689
3690 if (thiscam->momz || player->mo->pmomz)
3691 {
3692 // adjust height
3693 thiscam->z += thiscam->momz + player->mo->pmomz;
3694
3695 if (!itsatwodlevel && !(player->pflags & PF_NOCLIP))
3696 {
3697 // clip movement
3698 if (thiscam->z <= thiscam->floorz) // hit the floor
3699 {
3700 fixed_t cam_height = cv_cam_height.value;
3701 thiscam->z = thiscam->floorz;
3702
3703 if (player == &players[secondarydisplayplayer])
3704 cam_height = cv_cam2_height.value;
3705 if (thiscam->z > player->mo->z + player->mo->height + FixedMul(cam_height*FRACUNIT + 16*FRACUNIT, player->mo->scale))
3706 {
3707 if (!resetcalled)
3708 P_ResetCamera(player, thiscam);
3709 return true;
3710 }
3711 }
3712
3713 if (thiscam->z + thiscam->height > thiscam->ceilingz)
3714 {
3715 if (thiscam->momz > 0)
3716 {
3717 // hit the ceiling
3718 thiscam->momz = 0;
3719 }
3720
3721 thiscam->z = thiscam->ceilingz - thiscam->height;
3722
3723 if (thiscam->z + thiscam->height < player->mo->z - player->mo->height)
3724 {
3725 if (!resetcalled)
3726 P_ResetCamera(player, thiscam);
3727 return true;
3728 }
3729 }
3730 }
3731 }
3732
3733 if (itsatwodlevel
3734 || (thiscam->ceilingz - thiscam->z < thiscam->height
3735 && thiscam->ceilingz >= thiscam->z))
3736 {
3737 thiscam->ceilingz = thiscam->z + thiscam->height;
3738 thiscam->floorz = thiscam->z;
3739 }
3740 return false;
3741 }
3742
P_CheckCrumblingPlatforms(mobj_t * mobj)3743 static void P_CheckCrumblingPlatforms(mobj_t *mobj)
3744 {
3745 msecnode_t *node;
3746
3747 if (netgame && mobj->player->spectator)
3748 return;
3749
3750 for (node = mobj->touching_sectorlist; node; node = node->m_sectorlist_next)
3751 {
3752 ffloor_t *rover;
3753
3754 for (rover = node->m_sector->ffloors; rover; rover = rover->next)
3755 {
3756 if (!(rover->flags & FF_EXISTS))
3757 continue;
3758
3759 if (!(rover->flags & FF_CRUMBLE))
3760 continue;
3761
3762 if (mobj->eflags & MFE_VERTICALFLIP)
3763 {
3764 if (P_GetSpecialBottomZ(mobj, sectors + rover->secnum, node->m_sector) != mobj->z + mobj->height)
3765 continue;
3766 }
3767 else
3768 {
3769 if (P_GetSpecialTopZ(mobj, sectors + rover->secnum, node->m_sector) != mobj->z)
3770 continue;
3771 }
3772
3773 EV_StartCrumble(rover->master->frontsector, rover, (rover->flags & FF_FLOATBOB), mobj->player, rover->alpha, !(rover->flags & FF_NORETURN));
3774 }
3775 }
3776 }
3777
P_MobjTouchesSectorWithWater(mobj_t * mobj)3778 static boolean P_MobjTouchesSectorWithWater(mobj_t *mobj)
3779 {
3780 msecnode_t *node;
3781
3782 for (node = mobj->touching_sectorlist; node; node = node->m_sectorlist_next)
3783 {
3784 ffloor_t *rover;
3785
3786 if (!node->m_sector->ffloors)
3787 continue;
3788
3789 for (rover = node->m_sector->ffloors; rover; rover = rover->next)
3790 {
3791 if (!(rover->flags & FF_EXISTS))
3792 continue;
3793
3794 if (!(rover->flags & FF_SWIMMABLE))
3795 continue;
3796
3797 return true;
3798 }
3799 }
3800
3801 return false;
3802 }
3803
3804 // Check for floating water platforms and bounce them
P_CheckFloatbobPlatforms(mobj_t * mobj)3805 static void P_CheckFloatbobPlatforms(mobj_t *mobj)
3806 {
3807 msecnode_t *node;
3808
3809 // Can't land on anything if you're not moving downwards
3810 if (P_MobjFlip(mobj)*mobj->momz >= 0)
3811 return;
3812
3813 if (!P_MobjTouchesSectorWithWater(mobj))
3814 return;
3815
3816 for (node = mobj->touching_sectorlist; node; node = node->m_sectorlist_next)
3817 {
3818 ffloor_t *rover;
3819
3820 if (!node->m_sector->ffloors)
3821 continue;
3822
3823 for (rover = node->m_sector->ffloors; rover; rover = rover->next)
3824 {
3825 if (!(rover->flags & FF_EXISTS))
3826 continue;
3827
3828 if (!(rover->flags & FF_FLOATBOB))
3829 continue;
3830
3831
3832 if (mobj->eflags & MFE_VERTICALFLIP)
3833 {
3834 if (abs(*rover->bottomheight - (mobj->z + mobj->height)) > abs(mobj->momz))
3835 continue;
3836 }
3837 else
3838 {
3839 if (abs(*rover->topheight - mobj->z) > abs(mobj->momz))
3840 continue;
3841 }
3842
3843 // Initiate a 'bouncy' elevator function which slowly diminishes.
3844 EV_BounceSector(rover->master->frontsector, -mobj->momz, rover->master);
3845 }
3846 }
3847 }
3848
P_PlayerMobjThinker(mobj_t * mobj)3849 static void P_PlayerMobjThinker(mobj_t *mobj)
3850 {
3851 I_Assert(mobj != NULL);
3852 I_Assert(mobj->player != NULL);
3853 I_Assert(!P_MobjWasRemoved(mobj));
3854
3855 P_MobjCheckWater(mobj);
3856
3857 P_ButteredSlope(mobj);
3858
3859 // momentum movement
3860 mobj->eflags &= ~MFE_JUSTSTEPPEDDOWN;
3861
3862 if (mobj->state-states == S_PLAY_BOUNCE_LANDING)
3863 goto animonly; // no need for checkposition - doesn't move at ALL
3864
3865 // Zoom tube
3866 if (mobj->tracer)
3867 {
3868 if (mobj->player->powers[pw_carry] == CR_ZOOMTUBE || mobj->player->powers[pw_carry] == CR_ROPEHANG)
3869 {
3870 P_UnsetThingPosition(mobj);
3871 mobj->x += mobj->momx;
3872 mobj->y += mobj->momy;
3873 mobj->z += mobj->momz;
3874 P_SetThingPosition(mobj);
3875 P_CheckPosition(mobj, mobj->x, mobj->y);
3876 mobj->floorz = tmfloorz;
3877 mobj->ceilingz = tmceilingz;
3878 goto animonly;
3879 }
3880 else if (mobj->player->powers[pw_carry] == CR_MACESPIN)
3881 {
3882 P_CheckPosition(mobj, mobj->x, mobj->y);
3883 mobj->floorz = tmfloorz;
3884 mobj->ceilingz = tmceilingz;
3885 goto animonly;
3886 }
3887 }
3888
3889 // Needed for gravity boots
3890 P_CheckGravity(mobj, false);
3891
3892 mobj->player->powers[pw_justlaunched] = 0;
3893 if (mobj->momx || mobj->momy)
3894 {
3895 P_XYMovement(mobj);
3896
3897 if (P_MobjWasRemoved(mobj))
3898 return;
3899 }
3900 else
3901 P_TryMove(mobj, mobj->x, mobj->y, true);
3902
3903 P_CheckCrumblingPlatforms(mobj);
3904
3905 if (CheckForFloatBob)
3906 P_CheckFloatbobPlatforms(mobj);
3907
3908 // always do the gravity bit now, that's simpler
3909 // BUT CheckPosition only if wasn't done before.
3910 if (!(mobj->eflags & MFE_ONGROUND) || mobj->momz
3911 || ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height != mobj->ceilingz)
3912 || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z != mobj->floorz)
3913 || P_IsObjectInGoop(mobj))
3914 {
3915 P_PlayerZMovement(mobj);
3916 P_CheckPosition(mobj, mobj->x, mobj->y); // Need this to pick up objects!
3917
3918 if (P_MobjWasRemoved(mobj))
3919 return;
3920 }
3921 else
3922 {
3923 #if 0 // i don't know why this is here, it's causing a few undesired state glitches, and disabling it doesn't appear to negatively affect the game, but i don't want it gone permanently just in case some obscure bug crops up
3924 if (!(mobj->player->powers[pw_carry] == CR_NIGHTSMODE)) // used for drilling
3925 mobj->player->pflags &= ~PF_STARTJUMP;
3926 mobj->player->pflags &= ~(PF_JUMPED|PF_NOJUMPDAMAGE);
3927 if (mobj->player->secondjump || mobj->player->powers[pw_tailsfly])
3928 {
3929 mobj->player->secondjump = 0;
3930 mobj->player->powers[pw_tailsfly] = 0;
3931 P_SetPlayerMobjState(mobj, S_PLAY_WALK);
3932 }
3933 #endif
3934 mobj->eflags &= ~MFE_JUSTHITFLOOR;
3935 }
3936
3937 animonly:
3938 P_CyclePlayerMobjState(mobj);
3939 }
3940
CalculatePrecipFloor(precipmobj_t * mobj)3941 static void CalculatePrecipFloor(precipmobj_t *mobj)
3942 {
3943 // recalculate floorz each time
3944 const sector_t *mobjsecsubsec;
3945 if (mobj && mobj->subsector && mobj->subsector->sector)
3946 mobjsecsubsec = mobj->subsector->sector;
3947 else
3948 return;
3949 mobj->floorz = P_GetSectorFloorZAt(mobjsecsubsec, mobj->x, mobj->y);
3950 if (mobjsecsubsec->ffloors)
3951 {
3952 ffloor_t *rover;
3953 fixed_t topheight;
3954
3955 for (rover = mobjsecsubsec->ffloors; rover; rover = rover->next)
3956 {
3957 // If it exists, it'll get rained on.
3958 if (!(rover->flags & FF_EXISTS))
3959 continue;
3960
3961 if (!(rover->flags & FF_BLOCKOTHERS) && !(rover->flags & FF_SWIMMABLE))
3962 continue;
3963
3964 topheight = P_GetFFloorTopZAt(rover, mobj->x, mobj->y);
3965 if (topheight > mobj->floorz)
3966 mobj->floorz = topheight;
3967 }
3968 }
3969 }
3970
P_RecalcPrecipInSector(sector_t * sector)3971 void P_RecalcPrecipInSector(sector_t *sector)
3972 {
3973 mprecipsecnode_t *psecnode;
3974
3975 if (!sector)
3976 return;
3977
3978 sector->moved = true; // Recalc lighting and things too, maybe
3979
3980 for (psecnode = sector->touching_preciplist; psecnode; psecnode = psecnode->m_thinglist_next)
3981 CalculatePrecipFloor(psecnode->m_thing);
3982 }
3983
3984 //
3985 // P_NullPrecipThinker
3986 //
3987 // For "Blank" precipitation
3988 //
P_NullPrecipThinker(precipmobj_t * mobj)3989 void P_NullPrecipThinker(precipmobj_t *mobj)
3990 {
3991 //(void)mobj;
3992 mobj->precipflags &= ~PCF_THUNK;
3993 }
3994
P_SnowThinker(precipmobj_t * mobj)3995 void P_SnowThinker(precipmobj_t *mobj)
3996 {
3997 P_CycleStateAnimation((mobj_t *)mobj);
3998
3999 // adjust height
4000 if ((mobj->z += mobj->momz) <= mobj->floorz)
4001 mobj->z = mobj->ceilingz;
4002 }
4003
P_RainThinker(precipmobj_t * mobj)4004 void P_RainThinker(precipmobj_t *mobj)
4005 {
4006 P_CycleStateAnimation((mobj_t *)mobj);
4007
4008 if (mobj->state != &states[S_RAIN1])
4009 {
4010 // cycle through states,
4011 // calling action functions at transitions
4012 if (mobj->tics <= 0)
4013 return;
4014
4015 if (--mobj->tics)
4016 return;
4017
4018 if (!P_SetPrecipMobjState(mobj, mobj->state->nextstate))
4019 return;
4020
4021 if (mobj->state != &states[S_RAINRETURN])
4022 return;
4023
4024 mobj->z = mobj->ceilingz;
4025 P_SetPrecipMobjState(mobj, S_RAIN1);
4026
4027 return;
4028 }
4029
4030 // adjust height
4031 if ((mobj->z += mobj->momz) > mobj->floorz)
4032 return;
4033
4034 // no splashes on sky or bottomless pits
4035 if (mobj->precipflags & PCF_PIT)
4036 {
4037 mobj->z = mobj->ceilingz;
4038 return;
4039 }
4040
4041 mobj->z = mobj->floorz;
4042 P_SetPrecipMobjState(mobj, S_SPLASH1);
4043 }
4044
P_KillRingsInLava(mobj_t * mo)4045 static void P_KillRingsInLava(mobj_t *mo)
4046 {
4047 msecnode_t *node;
4048 I_Assert(mo != NULL);
4049 I_Assert(!P_MobjWasRemoved(mo));
4050
4051 // go through all sectors being touched by the ring
4052 for (node = mo->touching_sectorlist; node; node = node->m_sectorlist_next)
4053 {
4054 if (!node->m_sector)
4055 break;
4056
4057 if (node->m_sector->ffloors)
4058 {
4059 ffloor_t *rover;
4060 fixed_t topheight, bottomheight;
4061
4062 for (rover = node->m_sector->ffloors; rover; rover = rover->next) // go through all fofs in the sector
4063 {
4064 if (!(rover->flags & FF_EXISTS)) continue; // fof must be real
4065
4066 if (!(rover->flags & FF_SWIMMABLE // fof must be water
4067 && GETSECSPECIAL(rover->master->frontsector->special, 1) == 3)) // fof must be lava water
4068 continue;
4069
4070 // find heights of FOF
4071 topheight = P_GetFOFTopZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
4072 bottomheight = P_GetFOFBottomZ(mo, node->m_sector, rover, mo->x, mo->y, NULL);
4073
4074 if (mo->z <= topheight && mo->z + mo->height >= bottomheight) // if ring touches it, KILL IT
4075 {
4076 P_KillMobj(mo, NULL, NULL, DMG_FIRE);
4077 return;
4078 }
4079 }
4080 }
4081 }
4082 }
4083
P_RingThinker(mobj_t * mobj)4084 static void P_RingThinker(mobj_t *mobj)
4085 {
4086 if (mobj->momx || mobj->momy)
4087 {
4088 P_RingXYMovement(mobj);
4089
4090 if (P_MobjWasRemoved(mobj))
4091 return;
4092 }
4093
4094 // always do the gravity bit now, that's simpler
4095 // BUT CheckPosition only if wasn't done before.
4096 if (mobj->momz)
4097 {
4098 P_RingZMovement(mobj);
4099 P_CheckPosition(mobj, mobj->x, mobj->y); // Need this to pick up objects!
4100
4101 if (P_MobjWasRemoved(mobj))
4102 return;
4103 }
4104
4105 P_CycleMobjState(mobj);
4106 }
4107
4108 //
4109 // P_BossTargetPlayer
4110 // If closest is true, find the closest player.
4111 // Returns true if a player is targeted.
4112 //
P_BossTargetPlayer(mobj_t * actor,boolean closest)4113 boolean P_BossTargetPlayer(mobj_t *actor, boolean closest)
4114 {
4115 INT32 stop = -1, c = 0;
4116 player_t *player;
4117 fixed_t dist, lastdist = 0;
4118
4119 // first time init, this allow minimum lastlook changes
4120 if (actor->lastlook < 0)
4121 actor->lastlook = P_RandomByte();
4122 actor->lastlook &= PLAYERSMASK;
4123
4124 for( ; ; actor->lastlook = (actor->lastlook+1) & PLAYERSMASK)
4125 {
4126 // save the first look so we stop next time.
4127 if (stop < 0)
4128 stop = actor->lastlook;
4129 // reached the beginning again, done looking.
4130 else if (actor->lastlook == stop)
4131 return (closest && lastdist > 0);
4132
4133 if (!playeringame[actor->lastlook])
4134 continue;
4135
4136 if (!closest && c++ == 2)
4137 return false;
4138
4139 player = &players[actor->lastlook];
4140
4141 if (player->pflags & PF_INVIS || player->bot || player->spectator)
4142 continue; // ignore notarget
4143
4144 if (!player->mo || P_MobjWasRemoved(player->mo))
4145 continue;
4146
4147 if (player->mo->health <= 0)
4148 continue; //dead
4149
4150 if (!P_CheckSight(actor, player->mo))
4151 continue; // out of sight
4152
4153 if (closest)
4154 {
4155 dist = P_AproxDistance(actor->x - player->mo->x, actor->y - player->mo->y);
4156 if (!lastdist || dist < lastdist)
4157 {
4158 lastdist = dist+1;
4159 P_SetTarget(&actor->target, player->mo);
4160 }
4161 continue;
4162 }
4163
4164 P_SetTarget(&actor->target, player->mo);
4165 return true;
4166 }
4167 }
4168
4169 // Finds the player no matter what they're hiding behind (even lead!)
P_SupermanLook4Players(mobj_t * actor)4170 boolean P_SupermanLook4Players(mobj_t *actor)
4171 {
4172 INT32 c, stop = 0;
4173 player_t *playersinthegame[MAXPLAYERS];
4174
4175 for (c = 0; c < MAXPLAYERS; c++)
4176 {
4177 if (playeringame[c] && !players[c].spectator)
4178 {
4179 if (players[c].pflags & PF_INVIS)
4180 continue; // ignore notarget
4181
4182 if (!players[c].mo || players[c].bot)
4183 continue;
4184
4185 if (players[c].mo->health <= 0)
4186 continue; // dead
4187
4188 playersinthegame[stop] = &players[c];
4189 stop++;
4190 }
4191 }
4192
4193 if (!stop)
4194 return false;
4195
4196 P_SetTarget(&actor->target, playersinthegame[P_RandomKey(stop)]->mo);
4197 return true;
4198 }
4199
4200 // AI for a generic boss.
P_GenericBossThinker(mobj_t * mobj)4201 static void P_GenericBossThinker(mobj_t *mobj)
4202 {
4203 if (mobj->state->nextstate == mobj->info->spawnstate && mobj->tics == 1)
4204 mobj->flags2 &= ~MF2_FRET;
4205
4206 if (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE))
4207 {
4208 if (mobj->health <= 0)
4209 return;
4210
4211 // look for a new target
4212 if (P_BossTargetPlayer(mobj, false) && mobj->info->seesound)
4213 S_StartSound(mobj, mobj->info->seesound);
4214
4215 return;
4216 }
4217
4218 // Don't call A_ functions here, let the SOC do the AI!
4219
4220 if (mobj->state == &states[mobj->info->meleestate]
4221 || (mobj->state == &states[mobj->info->missilestate]
4222 && mobj->health > mobj->info->damage))
4223 {
4224 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
4225 }
4226 }
4227
4228 // AI for the first boss.
P_Boss1Thinker(mobj_t * mobj)4229 static void P_Boss1Thinker(mobj_t *mobj)
4230 {
4231 if (mobj->flags2 & MF2_FRET && mobj->state->nextstate == mobj->info->spawnstate && mobj->tics == 1) {
4232 mobj->flags2 &= ~(MF2_FRET|MF2_SKULLFLY);
4233 mobj->momx = mobj->momy = mobj->momz = 0;
4234 }
4235
4236 if (!mobj->tracer)
4237 {
4238 var1 = 0;
4239 A_BossJetFume(mobj);
4240 }
4241
4242 if (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE))
4243 {
4244 if (mobj->target && mobj->target->health
4245 && mobj->target->type == MT_EGGMOBILE_TARGET) // Oh, we're just firing our laser.
4246 return; // It's okay, then.
4247
4248 if (mobj->health <= 0)
4249 return;
4250
4251 // look for a new target
4252 if (P_BossTargetPlayer(mobj, false) && mobj->info->seesound)
4253 S_StartSound(mobj, mobj->info->seesound);
4254
4255 return;
4256 }
4257
4258 if (mobj->flags2 & MF2_SKULLFLY)
4259 {
4260 fixed_t dist = (mobj->eflags & MFE_VERTICALFLIP)
4261 ? ((mobj->ceilingz-(2*mobj->height)) - (mobj->z+mobj->height))
4262 : (mobj->z - (mobj->floorz+(2*mobj->height)));
4263 if (dist > 0 && P_MobjFlip(mobj)*mobj->momz > 0)
4264 mobj->momz = FixedMul(mobj->momz, FRACUNIT - (dist>>12));
4265 }
4266 else if (mobj->state != &states[mobj->info->spawnstate] && mobj->health > 0 && mobj->flags & MF_FLOAT)
4267 mobj->momz = FixedMul(mobj->momz,7*FRACUNIT/8);
4268
4269 if (mobj->state == &states[mobj->info->meleestate]
4270 || (mobj->state == &states[mobj->info->missilestate]
4271 && mobj->health > mobj->info->damage))
4272 {
4273 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
4274 }
4275 }
4276
4277 // AI for the second boss.
4278 // No, it does NOT convert "Boss" to a "Thinker". =P
P_Boss2Thinker(mobj_t * mobj)4279 static void P_Boss2Thinker(mobj_t *mobj)
4280 {
4281 if (mobj->movecount)
4282 mobj->movecount--;
4283
4284 if (!mobj->movecount)
4285 mobj->flags2 &= ~MF2_FRET;
4286
4287 if (!mobj->tracer)
4288 {
4289 var1 = 0;
4290 A_BossJetFume(mobj);
4291 }
4292
4293 if (mobj->health <= mobj->info->damage && (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE)))
4294 {
4295 if (mobj->health <= 0)
4296 return;
4297
4298 // look for a new target
4299 if (P_BossTargetPlayer(mobj, false) && mobj->info->seesound)
4300 S_StartSound(mobj, mobj->info->seesound);
4301
4302 return;
4303 }
4304
4305 if (mobj->state == &states[mobj->info->spawnstate] && mobj->health > mobj->info->damage)
4306 A_Boss2Chase(mobj);
4307 else if (mobj->health > 0 && mobj->state != &states[mobj->info->painstate] && mobj->state != &states[mobjinfo[mobj->info->missilestate].raisestate])
4308 {
4309 mobj->flags &= ~MF_NOGRAVITY;
4310 A_Boss2Pogo(mobj);
4311 P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
4312 }
4313 }
4314
4315 // AI for the third boss.
4316 //
4317 // Notes for reminders:
4318 // movedir = move 2x fast?
4319 // movecount = fire missiles?
4320 // reactiontime = shock the water?
4321 // threshold = next waypoint #
4322 // extravalue1 = previous time shock sound was used
4323 //
P_Boss3Thinker(mobj_t * mobj)4324 static void P_Boss3Thinker(mobj_t *mobj)
4325 {
4326 if (mobj->state == &states[mobj->info->spawnstate])
4327 mobj->flags2 &= ~MF2_FRET;
4328
4329 if (mobj->flags2 & MF2_FRET)
4330 mobj->movedir = 1;
4331
4332 if (mobj->health <= 0)
4333 return;
4334 /*
4335 {
4336 mobj->movecount = 0;
4337 mobj->reactiontime = 0;
4338
4339 if (mobj->state < &states[mobj->info->xdeathstate])
4340 return;
4341
4342 if (mobj->threshold == -1)
4343 {
4344 mobj->momz = mobj->info->speed;
4345 return;
4346 }
4347 else
4348 {
4349 mobj->flags |= MF_NOGRAVITY|MF_NOCLIP;
4350 mobj->flags |= MF_NOCLIPHEIGHT;
4351 mobj->threshold = -1;
4352 return;
4353 }
4354 }*/
4355
4356 if (mobj->reactiontime) // At the bottom of the water
4357 {
4358 UINT32 i;
4359 SINT8 curpath = mobj->threshold;
4360
4361 // Choose one of the paths you're not already on
4362 mobj->threshold = P_RandomKey(8-1);
4363 if (mobj->threshold >= curpath)
4364 mobj->threshold++;
4365
4366 if (mobj->state != &states[mobj->info->spawnstate])
4367 P_SetMobjState(mobj, mobj->info->spawnstate);
4368
4369 mobj->reactiontime--;
4370
4371 if (!mobj->reactiontime && mobj->health <= mobj->info->damage)
4372 { // Spawn pinch dummies from the center when we're leaving it.
4373 thinker_t *th;
4374 mobj_t *mo2;
4375 mobj_t *dummy;
4376 SINT8 way0 = mobj->threshold; // 0 through 4.
4377 SINT8 way1, way2;
4378
4379 i = 0; // reset i to 0 so we can check how many clones we've removed
4380
4381 // scan the thinkers to make sure all the old pinch dummies are gone before making new ones
4382 // this can happen if the boss was hurt earlier than expected
4383 for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
4384 {
4385 if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
4386 continue;
4387
4388 mo2 = (mobj_t *)th;
4389 if (mo2->type != (mobjtype_t)mobj->info->mass)
4390 continue;
4391 if (mo2->tracer != mobj)
4392 continue;
4393
4394 P_RemoveMobj(mo2);
4395 if (++i == 2) // we've already removed 2 of these, let's stop now
4396 break;
4397 }
4398
4399 way1 = P_RandomKey(8-2);
4400 if (way1 >= curpath)
4401 way1++;
4402 if (way1 >= way0)
4403 {
4404 way1++;
4405 if (way1 == curpath)
4406 way1++;
4407 }
4408
4409 dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass);
4410 dummy->angle = mobj->angle;
4411 dummy->threshold = way1;
4412 P_SetTarget(&dummy->tracer, mobj);
4413 dummy->movefactor = mobj->movefactor;
4414 dummy->cusval = mobj->cusval;
4415
4416 way2 = P_RandomKey(8-3);
4417 if (way2 >= curpath)
4418 way2++;
4419 if (way2 >= way0)
4420 {
4421 way2++;
4422 if (way2 == curpath)
4423 way2++;
4424 }
4425 if (way2 >= way1)
4426 {
4427 way2++;
4428 if (way2 == curpath || way2 == way0)
4429 way2++;
4430 }
4431
4432 dummy = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->mass);
4433 dummy->angle = mobj->angle;
4434 dummy->threshold = way2;
4435 P_SetTarget(&dummy->tracer, mobj);
4436 dummy->movefactor = mobj->movefactor;
4437 dummy->cusval = mobj->cusval;
4438
4439 CONS_Debug(DBG_GAMELOGIC, "Eggman path %d - Dummy selected paths %d and %d\n", way0, way1, way2);
4440 P_LinedefExecute(LE_PINCHPHASE+(mobj->cusval*LE_PARAMWIDTH), mobj, NULL);
4441 }
4442 }
4443 else if (mobj->movecount) // Firing mode
4444 {
4445 // Check if the attack animation is running. If not, play it.
4446 if (mobj->state < &states[mobj->info->missilestate] || mobj->state > &states[mobj->info->raisestate])
4447 {
4448 // look for a new target
4449 P_BossTargetPlayer(mobj, true);
4450
4451 if (!mobj->target || !mobj->target->player)
4452 return;
4453
4454 if (mobj->health <= mobj->info->damage) // pinch phase
4455 mobj->movecount--; // limited number of shots before diving again
4456 if (mobj->movecount)
4457 P_SetMobjState(mobj, mobj->info->missilestate+1);
4458 }
4459 else if (mobj->target && mobj->target->player)
4460 {
4461 angle_t diff = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) - mobj->angle;
4462 if (diff > ANGLE_180)
4463 diff = InvAngle(InvAngle(diff)/4);
4464 else
4465 diff /= 4;
4466 mobj->angle += diff;
4467 }
4468 }
4469 else if (mobj->threshold >= 0) // Traveling mode
4470 {
4471 fixed_t dist = 0;
4472 fixed_t speed;
4473
4474 P_SetTarget(&mobj->target, NULL);
4475
4476 if (mobj->state != &states[mobj->info->spawnstate] && mobj->health > 0
4477 && !(mobj->flags2 & MF2_FRET))
4478 P_SetMobjState(mobj, mobj->info->spawnstate);
4479
4480 if (!(mobj->flags2 & MF2_STRONGBOX))
4481 {
4482 thinker_t *th;
4483 mobj_t *mo2;
4484
4485 P_SetTarget(&mobj->tracer, NULL);
4486
4487 // scan the thinkers
4488 // to find a point that matches
4489 // the number
4490 for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
4491 {
4492 if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
4493 continue;
4494
4495 mo2 = (mobj_t *)th;
4496 if (mo2->type != MT_BOSS3WAYPOINT)
4497 continue;
4498 if (!mo2->spawnpoint)
4499 continue;
4500 if (mo2->spawnpoint->angle != mobj->threshold)
4501 continue;
4502 if (mo2->spawnpoint->extrainfo != mobj->cusval)
4503 continue;
4504
4505 P_SetTarget(&mobj->tracer, mo2);
4506 break;
4507 }
4508 }
4509
4510 if (!mobj->tracer) // Should NEVER happen
4511 {
4512 CONS_Debug(DBG_GAMELOGIC, "Error: Boss 3 was unable to find specified waypoint: %d, %d\n", mobj->threshold, mobj->cusval);
4513 return;
4514 }
4515
4516 if ((mobj->movedir) || (mobj->health <= mobj->info->damage))
4517 speed = mobj->info->speed * 2;
4518 else
4519 speed = mobj->info->speed;
4520
4521 if (mobj->tracer->x == mobj->x && mobj->tracer->y == mobj->y)
4522 {
4523 // apply ambush for old routing, otherwise whack a mole only
4524 dist = P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z + mobj->movefactor - mobj->z);
4525
4526 if (dist < 1)
4527 dist = 1;
4528
4529 mobj->momx = FixedMul(FixedDiv(mobj->tracer->x - mobj->x, dist), speed);
4530 mobj->momy = FixedMul(FixedDiv(mobj->tracer->y - mobj->y, dist), speed);
4531 mobj->momz = FixedMul(FixedDiv(mobj->tracer->z + mobj->movefactor - mobj->z, dist), speed);
4532
4533 if (mobj->momx != 0 || mobj->momy != 0)
4534 mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy);
4535 }
4536
4537 if (dist <= speed)
4538 {
4539 // If distance to point is less than travel in that frame, set XYZ of mobj to waypoint location
4540 P_UnsetThingPosition(mobj);
4541 mobj->x = mobj->tracer->x;
4542 mobj->y = mobj->tracer->y;
4543 mobj->z = mobj->tracer->z + mobj->movefactor;
4544 mobj->momx = mobj->momy = mobj->momz = 0;
4545 P_SetThingPosition(mobj);
4546
4547 if (!mobj->movefactor) // to firing mode
4548 {
4549 UINT8 i, numtospawn = 24;
4550 angle_t ang = 0, interval = FixedAngle((360 << FRACBITS) / numtospawn);
4551 mobj_t *shock = NULL, *sfirst = NULL, *sprev = NULL;
4552
4553 mobj->movecount = mobj->health+1;
4554 mobj->movefactor = -512*FRACUNIT;
4555
4556 // shock the water!
4557 for (i = 0; i < numtospawn; i++)
4558 {
4559 shock = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_SHOCKWAVE);
4560 P_SetTarget(&shock->target, mobj);
4561 shock->fuse = shock->info->painchance;
4562
4563 if (i % 2 == 0)
4564 P_SetMobjState(shock, shock->state->nextstate);
4565
4566 if (!sprev)
4567 sfirst = shock;
4568 else
4569 {
4570 if (i == numtospawn - 1)
4571 P_SetTarget(&shock->hnext, sfirst);
4572 P_SetTarget(&sprev->hnext, shock);
4573 }
4574
4575 P_Thrust(shock, ang, shock->info->speed);
4576 ang += interval;
4577 sprev = shock;
4578 }
4579 S_StartSound(mobj, shock->info->seesound);
4580
4581 // look for a new target
4582 P_BossTargetPlayer(mobj, true);
4583
4584 if (mobj->target && mobj->target->player)
4585 P_SetMobjState(mobj, mobj->info->missilestate);
4586 }
4587 else if (mobj->flags2 & (MF2_STRONGBOX|MF2_CLASSICPUSH)) // just hit the bottom of your tube
4588 {
4589 mobj->flags2 &= ~(MF2_STRONGBOX|MF2_CLASSICPUSH);
4590 mobj->reactiontime = 1; // spawn pinch dummies
4591 mobj->movedir = 0;
4592 }
4593 else // just shifted to another tube
4594 {
4595 mobj->flags2 |= MF2_STRONGBOX;
4596 if (mobj->health > 0)
4597 mobj->movefactor = 0;
4598 }
4599 }
4600 }
4601 }
4602
4603 // Move Boss4's sectors by delta.
P_Boss4MoveCage(mobj_t * mobj,fixed_t delta)4604 static boolean P_Boss4MoveCage(mobj_t *mobj, fixed_t delta)
4605 {
4606 const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
4607 INT32 snum;
4608 sector_t *sector;
4609 boolean gotcage = false;
4610 TAG_ITER_DECLARECOUNTER(0);
4611
4612 TAG_ITER_SECTORS(0, tag, snum)
4613 {
4614 sector = §ors[snum];
4615 sector->floorheight += delta;
4616 sector->ceilingheight += delta;
4617 P_CheckSector(sector, true);
4618 gotcage = true;
4619 }
4620 return gotcage;
4621 }
4622
4623 // Move Boss4's arms to angle
P_Boss4MoveSpikeballs(mobj_t * mobj,angle_t angle,fixed_t fz)4624 static void P_Boss4MoveSpikeballs(mobj_t *mobj, angle_t angle, fixed_t fz)
4625 {
4626 INT32 s;
4627 mobj_t *base = mobj, *seg;
4628 fixed_t dist, bz = mobj->watertop+(8<<FRACBITS);
4629 while ((base = base->tracer))
4630 {
4631 for (seg = base, dist = 172*FRACUNIT, s = 9; seg; seg = seg->hnext, dist += 124*FRACUNIT, --s)
4632 P_TeleportMove(seg, mobj->x + P_ReturnThrustX(mobj, angle, dist), mobj->y + P_ReturnThrustY(mobj, angle, dist), bz + FixedMul(fz, FixedDiv(s<<FRACBITS, 9<<FRACBITS)));
4633 angle += ANGLE_MAX/3;
4634 }
4635 }
4636
4637 #define CEZ3TILT
4638
4639 // Pull them closer.
P_Boss4PinchSpikeballs(mobj_t * mobj,angle_t angle,fixed_t dz)4640 static void P_Boss4PinchSpikeballs(mobj_t *mobj, angle_t angle, fixed_t dz)
4641 {
4642 INT32 s;
4643 mobj_t *base = mobj, *seg;
4644 fixed_t workx, worky, dx, dy, bz = mobj->watertop+(8<<FRACBITS);
4645 fixed_t rad = (9*132)<<FRACBITS;
4646 #ifdef CEZ3TILT
4647 fixed_t originx, originy;
4648 if (mobj->spawnpoint)
4649 {
4650 originx = mobj->spawnpoint->x << FRACBITS;
4651 originy = mobj->spawnpoint->y << FRACBITS;
4652 }
4653 else
4654 {
4655 originx = mobj->x;
4656 originy = mobj->y;
4657 }
4658 #else
4659 if (mobj->spawnpoint)
4660 {
4661 rad -= R_PointToDist2(mobj->x, mobj->y,
4662 (mobj->spawnpoint->x<<FRACBITS), (mobj->spawnpoint->y<<FRACBITS));
4663 }
4664 #endif
4665
4666 dz /= 9;
4667
4668 while ((base = base->tracer)) // there are 10 per spoke, remember that
4669 {
4670 #ifdef CEZ3TILT
4671 dx = (originx + P_ReturnThrustX(mobj, angle, rad) - mobj->x)/9;
4672 dy = (originy + P_ReturnThrustY(mobj, angle, rad) - mobj->y)/9;
4673 #else
4674 dx = P_ReturnThrustX(mobj, angle, rad)/9;
4675 dy = P_ReturnThrustY(mobj, angle, rad)/9;
4676 #endif
4677 workx = mobj->x + P_ReturnThrustX(mobj, angle, (112)<<FRACBITS);
4678 worky = mobj->y + P_ReturnThrustY(mobj, angle, (112)<<FRACBITS);
4679 for (seg = base, s = 9; seg; seg = seg->hnext, --s)
4680 {
4681 seg->z = bz + (dz*(9-s));
4682 P_TryMove(seg, workx + (dx*s), worky + (dy*s), true);
4683 }
4684 angle += ANGLE_MAX/3;
4685 }
4686 }
4687
4688 // Destroy cage FOFs.
P_Boss4DestroyCage(mobj_t * mobj)4689 static void P_Boss4DestroyCage(mobj_t *mobj)
4690 {
4691 const UINT16 tag = 65534 + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0);
4692 INT32 snum;
4693 size_t a;
4694 sector_t *sector, *rsec;
4695 ffloor_t *rover;
4696 TAG_ITER_DECLARECOUNTER(0);
4697
4698 TAG_ITER_SECTORS(0, tag, snum)
4699 {
4700 sector = §ors[snum];
4701
4702 // Destroy the FOFs.
4703 for (a = 0; a < sector->numattached; a++)
4704 {
4705 rsec = §ors[sector->attached[a]];
4706 for (rover = rsec->ffloors; rover; rover = rover->next)
4707 if (rover->flags & FF_EXISTS && rover->secnum == (size_t)snum)
4708 {
4709 if (rover->flags & FF_RENDERALL) // checking for FF_RENDERANY.
4710 EV_CrumbleChain(rsec, rover); // This FOF is visible to some extent? Crumble it.
4711 else // Completely invisible FOF
4712 {
4713 // no longer exists (can't collide with again)
4714 rover->flags &= ~FF_EXISTS;
4715 sector->moved = true;
4716 rsec->moved = true;
4717 }
4718 }
4719 }
4720 }
4721 }
4722
4723 // Destroy Boss4's arms
P_Boss4PopSpikeballs(mobj_t * mobj)4724 static void P_Boss4PopSpikeballs(mobj_t *mobj)
4725 {
4726 mobj_t *base = mobj->tracer, *seg, *next;
4727 P_SetTarget(&mobj->tracer, NULL);
4728 while(base)
4729 {
4730 next = base->tracer;
4731 P_SetTarget(&base->tracer, NULL);
4732 for (seg = base; seg; seg = seg->hnext)
4733 if (seg->health)
4734 P_KillMobj(seg, NULL, NULL, 0);
4735 base = next;
4736 }
4737 }
4738
4739 //
4740 // AI for the fourth boss.
4741 //
P_Boss4Thinker(mobj_t * mobj)4742 static void P_Boss4Thinker(mobj_t *mobj)
4743 {
4744 fixed_t movespeed = 0;
4745
4746 if ((statenum_t)(mobj->state-states) == mobj->info->spawnstate)
4747 {
4748 if (mobj->flags2 & MF2_FRET && (mobj->health > mobj->info->damage))
4749 mobj->flags2 &= ~MF2_FRET;
4750 mobj->reactiontime = 0; // Drop the cage immediately.
4751 }
4752
4753 // Oh no, we dead? D:
4754 if (!mobj->health)
4755 {
4756 if (mobj->tracer) // need to clean up!
4757 {
4758 P_Boss4DestroyCage(mobj); // Just in case pinch phase was skipped.
4759 P_Boss4PopSpikeballs(mobj);
4760 }
4761 return;
4762 }
4763
4764 if (mobj->movedir) // only not during init
4765 {
4766 INT32 oldmovecount = mobj->movecount;
4767 if (mobj->movedir == 3) // pinch start
4768 movespeed = -(210<<(FRACBITS>>1));
4769 else if (mobj->movedir > 3) // pinch
4770 {
4771 movespeed = 420<<(FRACBITS>>1);
4772 movespeed += (420*(mobj->info->damage-mobj->health)<<(FRACBITS>>1));
4773 if (mobj->movedir == 4)
4774 movespeed = -movespeed;
4775 }
4776 else // normal
4777 {
4778 movespeed = 170<<(FRACBITS>>1);
4779 movespeed += ((50*(mobj->info->spawnhealth-mobj->health))<<(FRACBITS>>1));
4780 if (mobj->movedir == 2)
4781 movespeed = -movespeed;
4782 if (mobj->movefactor)
4783 movespeed /= 2;
4784 else if (mobj->threshold)
4785 {
4786 // 1 -> 1.5 second timer
4787 INT32 maxtimer = TICRATE+(TICRATE*(mobj->info->spawnhealth-mobj->health)/10);
4788 if (maxtimer < 1)
4789 maxtimer = 1;
4790 maxtimer = ((mobj->threshold*movespeed)/(2*maxtimer));
4791 movespeed -= maxtimer;
4792 }
4793 }
4794
4795 mobj->movecount += movespeed + 360*FRACUNIT;
4796 mobj->movecount %= 360*FRACUNIT;
4797
4798 if (((oldmovecount>>FRACBITS)%120 >= 60) && !((mobj->movecount>>FRACBITS)%120 >= 60))
4799 S_StartSound(NULL, sfx_mswing);
4800 }
4801
4802 // movedir == battle stage:
4803 // 0: initialization
4804 // 1: phase 1 forward
4805 // 2: phase 1 reverse
4806 // 3: pinch rise
4807 // 4: pinch phase
4808 switch(mobj->movedir)
4809 {
4810 // WELCOME to your DOOM!
4811 case 0:
4812 {
4813 // For this stage only:
4814 // movecount == cage height
4815 // threshold == cage momz
4816 if (mobj->movecount == 0) // Initialize stage!
4817 {
4818 fixed_t z;
4819 INT32 i, arm;
4820 mobj_t *seg, *base = mobj;
4821 // First frame init, spawn all the things.
4822 mobj->watertop = mobj->z;
4823 z = mobj->z + mobj->height/2 - mobjinfo[MT_EGGMOBILE4_MACE].height/2;
4824 for (arm = 0; arm <3 ; arm++)
4825 {
4826 seg = P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE);
4827 P_SetTarget(&base->tracer, seg);
4828 base = seg;
4829 P_SetTarget(&seg->target, mobj);
4830 for (i = 0; i < 9; i++)
4831 {
4832 P_SetTarget(&seg->hnext, P_SpawnMobj(mobj->x, mobj->y, z, MT_EGGMOBILE4_MACE));
4833 P_SetTarget(&seg->hnext->hprev, seg);
4834 seg = seg->hnext;
4835 }
4836 }
4837 // Move the cage up to the sky.
4838 mobj->movecount = 800*FRACUNIT;
4839 if (!P_Boss4MoveCage(mobj, mobj->movecount))
4840 {
4841 mobj->movecount = 0;
4842 //mobj->threshold = 3*TICRATE;
4843 mobj->extravalue1 = 1;
4844 mobj->movedir++; // We don't have a cage, just continue.
4845 }
4846 else
4847 P_Boss4MoveSpikeballs(mobj, 0, mobj->movecount);
4848 }
4849 else // Cage slams down over Eggman's head!
4850 {
4851 fixed_t oldz = mobj->movecount;
4852 mobj->threshold -= 5*FRACUNIT;
4853 mobj->movecount += mobj->threshold;
4854 if (mobj->movecount <= 0)
4855 {
4856 mobj->flags2 &= ~MF2_INVERTAIMABLE;
4857 mobj->movecount = 0;
4858 mobj->movedir++; // Initialization complete, next phase!
4859 }
4860 P_Boss4MoveCage(mobj, mobj->movecount - oldz);
4861 P_Boss4MoveSpikeballs(mobj, 0, mobj->movecount);
4862 }
4863 return;
4864 }
4865
4866 // Normal operation
4867 case 1:
4868 case 2:
4869 break;
4870
4871 // Pinch phase init!
4872 case 3:
4873 {
4874 fixed_t z;
4875 if (mobj->z < mobj->watertop+(400<<FRACBITS))
4876 mobj->momz = 8*FRACUNIT;
4877 else
4878 {
4879 mobj->momz = mobj->movefactor = 0;
4880 mobj->threshold = 1110<<FRACBITS;
4881 S_StartSound(NULL, sfx_s3k60);
4882 mobj->movedir++;
4883 }
4884
4885 z = mobj->z - mobj->watertop - mobjinfo[MT_EGGMOBILE4_MACE].height - mobj->height/2;
4886 if (z < (8<<FRACBITS)) // We haven't risen high enough to pull the spikeballs along yet
4887 P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), 0); // So don't pull the spikeballs along yet.
4888 else
4889 P_Boss4PinchSpikeballs(mobj, FixedAngle(mobj->movecount), z);
4890 return;
4891 }
4892 // Pinch phase!
4893 case 4:
4894 case 5:
4895 {
4896 mobj->angle -= FixedAngle(movespeed/8);
4897
4898 if (mobj->movefactor != mobj->threshold)
4899 {
4900 if (mobj->threshold - mobj->movefactor < FRACUNIT)
4901 {
4902 mobj->movefactor = mobj->threshold;
4903 mobj->flags2 &= ~MF2_FRET;
4904 }
4905 else
4906 mobj->movefactor += (mobj->threshold - mobj->movefactor)/8;
4907 }
4908
4909 if (mobj->spawnpoint)
4910 P_TryMove(mobj,
4911 (mobj->spawnpoint->x<<FRACBITS) - P_ReturnThrustX(mobj, mobj->angle, mobj->movefactor),
4912 (mobj->spawnpoint->y<<FRACBITS) - P_ReturnThrustY(mobj, mobj->angle, mobj->movefactor),
4913 true);
4914
4915 P_Boss4PinchSpikeballs(mobj, FixedAngle(mobj->movecount), mobj->z - mobj->watertop - mobjinfo[MT_EGGMOBILE4_MACE].height - mobj->height/2);
4916
4917 if (!mobj->target || !mobj->target->health)
4918 P_SupermanLook4Players(mobj);
4919 //A_FaceTarget(mobj);
4920 return;
4921 }
4922
4923 default: // ?????
4924 return;
4925 }
4926
4927 // Haahahahahaaa, and let the FUN.. BEGIN!
4928 // movedir == arms direction
4929 // movecount == arms angle
4930 // threshold == countdown to next attack
4931 // reactiontime == cage raise, speed burst
4932 // movefactor == cage z
4933 // friction == turns until helm lift
4934
4935 // Raise the cage!
4936 if (mobj->reactiontime == 1)
4937 {
4938 fixed_t oldz = mobj->movefactor;
4939 if (mobj->movefactor != 128*FRACUNIT)
4940 {
4941 if (mobj->movefactor < 128*FRACUNIT)
4942 {
4943 mobj->movefactor += 8*FRACUNIT;
4944 if (!oldz)
4945 {
4946 // 5 -> 2.5 second timer
4947 mobj->threshold = 5*TICRATE-(TICRATE*(mobj->info->spawnhealth-mobj->health)/2);
4948 if (mobj->threshold < 1)
4949 mobj->threshold = 1;
4950 }
4951 }
4952 else
4953 mobj->movefactor = 128*FRACUNIT;
4954 P_Boss4MoveCage(mobj, mobj->movefactor - oldz);
4955 }
4956 }
4957 // Drop the cage!
4958 else if (mobj->movefactor)
4959 {
4960 fixed_t oldz = mobj->movefactor;
4961 mobj->movefactor -= 4*FRACUNIT;
4962 if (mobj->movefactor < 0)
4963 mobj->movefactor = 0;
4964 P_Boss4MoveCage(mobj, mobj->movefactor - oldz);
4965 if (!mobj->movefactor)
4966 {
4967 if (mobj->health <= mobj->info->damage)
4968 { // Proceed to pinch phase!
4969 P_Boss4DestroyCage(mobj);
4970 mobj->movedir = 3;
4971 P_LinedefExecute(LE_PINCHPHASE + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0), mobj, NULL);
4972 P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), 0);
4973 var1 = 3;
4974 A_BossJetFume(mobj);
4975 return;
4976 }
4977 P_LinedefExecute(LE_BOSS4DROP - (mobj->info->spawnhealth-mobj->health) + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0), mobj, NULL);
4978 // 1 -> 1.5 second timer
4979 mobj->threshold = TICRATE+(TICRATE*(mobj->info->spawnhealth-mobj->health)/10);
4980 if (mobj->threshold < 1)
4981 mobj->threshold = 1;
4982 }
4983 }
4984
4985 P_Boss4MoveSpikeballs(mobj, FixedAngle(mobj->movecount), mobj->movefactor);
4986
4987 // Check for attacks, always tick the timer even while animating!!
4988 if (mobj->threshold)
4989 {
4990 if (!(mobj->flags2 & MF2_FRET) && !(--mobj->threshold)) // but pause for pain so we don't interrupt pinch phase, eep!
4991 {
4992 if (mobj->reactiontime == 1) // Cage is raised?
4993 {
4994 P_SetMobjState(mobj, mobj->info->spawnstate);
4995 mobj->reactiontime = 0; // Drop it!
4996 }
4997 }
4998 }
4999
5000 // Leave if animating.
5001 if ((statenum_t)(mobj->state-states) != mobj->info->spawnstate)
5002 return;
5003
5004 // Map allows us to get killed despite cage being down?
5005 if (mobj->health <= mobj->info->damage)
5006 { // Proceed to pinch phase!
5007 P_Boss4DestroyCage(mobj);
5008 mobj->movedir = 3;
5009 P_LinedefExecute(LE_PINCHPHASE + (mobj->spawnpoint ? mobj->spawnpoint->extrainfo*LE_PARAMWIDTH : 0), mobj, NULL);
5010 var1 = 3;
5011 A_BossJetFume(mobj);
5012 return;
5013 }
5014
5015 mobj->reactiontime = 0; // Drop the cage if it hasn't been dropped already.
5016 if (!mobj->target || !mobj->target->health)
5017 P_SupermanLook4Players(mobj);
5018 A_FaceTarget(mobj);
5019 }
5020
5021 //
5022 // AI for the fifth boss.
5023 //
P_Boss5Thinker(mobj_t * mobj)5024 static void P_Boss5Thinker(mobj_t *mobj)
5025 {
5026 if (!mobj->health)
5027 {
5028 if (mobj->fuse)
5029 {
5030 if (mobj->flags2 & MF2_SLIDEPUSH)
5031 {
5032 INT32 trans = 10-((10*mobj->fuse)/70);
5033 if (trans > 9)
5034 trans = 9;
5035 if (trans < 0)
5036 trans = 0;
5037 mobj->frame = (mobj->frame & ~FF_TRANSMASK)|(trans<<FF_TRANSSHIFT);
5038 if (!(mobj->fuse & 1))
5039 {
5040 mobj->colorized = !mobj->colorized;
5041 mobj->frame ^= FF_FULLBRIGHT;
5042 }
5043 }
5044 return;
5045 }
5046 if (mobj->state == &states[mobj->info->xdeathstate])
5047 mobj->momz -= (2*FRACUNIT)/3;
5048 else if (mobj->tracer && P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y) < 2*mobj->radius)
5049 mobj->flags &= ~MF_NOCLIP;
5050 }
5051 else
5052 {
5053 if (mobj->flags2 & MF2_FRET && (leveltime & 1)
5054 && mobj->state != &states[S_FANG_PAIN1] && mobj->state != &states[S_FANG_PAIN2])
5055 mobj->flags2 |= MF2_DONTDRAW;
5056 else
5057 mobj->flags2 &= ~MF2_DONTDRAW;
5058 }
5059
5060 if (mobj->state == &states[S_FANG_BOUNCE3]
5061 || mobj->state == &states[S_FANG_BOUNCE4]
5062 || mobj->state == &states[S_FANG_PINCHBOUNCE3]
5063 || mobj->state == &states[S_FANG_PINCHBOUNCE4])
5064 {
5065 if (P_MobjFlip(mobj)*mobj->momz > 0
5066 && abs(mobj->momx) < FRACUNIT/2 && abs(mobj->momy) < FRACUNIT/2
5067 && !P_IsObjectOnGround(mobj))
5068 {
5069 mobj_t *prevtarget = mobj->target;
5070 P_SetTarget(&mobj->target, NULL);
5071 var1 = var2 = 0;
5072 A_DoNPCPain(mobj);
5073 P_SetTarget(&mobj->target, prevtarget);
5074 P_SetMobjState(mobj, S_FANG_WALLHIT);
5075 mobj->extravalue2++;
5076 }
5077 }
5078 }
5079
5080 //
5081 // AI for Black Eggman
5082 // Note: You CANNOT have more than ONE Black Eggman
5083 // in a level! Just don't try it!
5084 //
P_Boss7Thinker(mobj_t * mobj)5085 static void P_Boss7Thinker(mobj_t *mobj)
5086 {
5087 if (!mobj->target || !(mobj->target->flags & MF_SHOOTABLE))
5088 {
5089 // look for a new target
5090 if (P_BossTargetPlayer(mobj, false))
5091 return; // got a new target
5092
5093 P_SetMobjStateNF(mobj, mobj->info->spawnstate);
5094 return;
5095 }
5096
5097 if (mobj->health >= mobj->info->spawnhealth && (leveltime & 14) == 0)
5098 {
5099 mobj_t *smoke = P_SpawnMobj(mobj->x, mobj->y, mobj->z + mobj->height, MT_SMOKE);
5100 smoke->destscale = mobj->destscale;
5101 P_SetScale(smoke, smoke->destscale);
5102 smoke->momz = FixedMul(FRACUNIT, smoke->scale);
5103 }
5104
5105 if (mobj->state == &states[S_BLACKEGG_STND] && mobj->tics == mobj->state->tics)
5106 {
5107 mobj->reactiontime += P_RandomByte();
5108
5109 if (mobj->health <= mobj->info->damage)
5110 mobj->reactiontime /= 4;
5111 }
5112 else if (mobj->state == &states[S_BLACKEGG_DIE4] && mobj->tics == mobj->state->tics)
5113 A_BossDeath(mobj);
5114 else if (mobj->state >= &states[S_BLACKEGG_WALK1]
5115 && mobj->state <= &states[S_BLACKEGG_WALK6])
5116 A_Boss7Chase(mobj);
5117 else if (mobj->state == &states[S_BLACKEGG_PAIN1] && mobj->tics == mobj->state->tics)
5118 {
5119 if (mobj->health > 0)
5120 mobj->health--;
5121
5122 S_StartSound(0, (mobj->health) ? sfx_behurt : sfx_bedie2);
5123
5124 mobj->reactiontime /= 3;
5125
5126 if (mobj->health <= 0)
5127 {
5128 INT32 i;
5129
5130 P_KillMobj(mobj, NULL, NULL, 0);
5131
5132 // It was a team effort
5133 for (i = 0; i < MAXPLAYERS; i++)
5134 {
5135 if (!playeringame[i])
5136 continue;
5137
5138 P_AddPlayerScore(&players[i], 1000);
5139 }
5140 }
5141 }
5142 else if (mobj->state == &states[S_BLACKEGG_PAIN35] && mobj->tics == 1)
5143 {
5144 if (mobj->health == mobj->info->damage)
5145 {
5146 // Begin platform destruction
5147 mobj->flags2 |= MF2_FRET;
5148 P_SetMobjState(mobj, mobj->info->raisestate);
5149 P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
5150 }
5151 }
5152 else if (mobj->state == &states[S_BLACKEGG_HITFACE4] && mobj->tics == mobj->state->tics)
5153 {
5154 // This is where Black Eggman hits his face.
5155 // If a player is on top of him, the player gets hurt.
5156 // But, if the player has managed to escape,
5157 // Black Eggman gets hurt!
5158 INT32 i;
5159 mobj->state->nextstate = mobj->info->painstate; // Reset
5160
5161 S_StartSound(0, sfx_bedeen);
5162
5163 for (i = 0; i < MAXPLAYERS; i++)
5164 {
5165 if (!playeringame[i] || players[i].spectator)
5166 continue;
5167
5168 if (!players[i].mo)
5169 continue;
5170
5171 if (players[i].mo->health <= 0)
5172 continue;
5173
5174 if (P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y) > (mobj->radius + players[i].mo->radius))
5175 continue;
5176
5177 if (players[i].mo->z > mobj->z + mobj->height - FRACUNIT
5178 && players[i].mo->z < mobj->z + mobj->height + 128*FRACUNIT) // You can't be in the vicinity, either...
5179 {
5180 // Punch him!
5181 P_DamageMobj(players[i].mo, mobj, mobj, 1, 0);
5182 mobj->state->nextstate = mobj->info->spawnstate;
5183
5184 // Laugh
5185 S_StartSound(0, sfx_bewar1 + P_RandomKey(4));
5186 }
5187 }
5188 }
5189 else if (mobj->state == &states[S_BLACKEGG_GOOP])
5190 {
5191 // Lob cannon balls
5192 if (mobj->movecount-- <= 0 || !mobj->target)
5193 {
5194 P_SetMobjState(mobj, mobj->info->spawnstate);
5195 return;
5196 }
5197
5198 if ((leveltime & 15) == 0)
5199 {
5200 var1 = MT_CANNONBALL;
5201
5202 var2 = 2*TICRATE + (80<<16);
5203
5204 A_LobShot(mobj);
5205 S_StartSound(0, sfx_begoop);
5206 }
5207 }
5208 else if (mobj->state == &states[S_BLACKEGG_SHOOT2])
5209 {
5210 // Chaingun goop
5211 mobj_t *missile;
5212
5213 if (mobj->movecount-- <= 0 || !mobj->target)
5214 {
5215 P_SetMobjState(mobj, mobj->info->spawnstate);
5216 return;
5217 }
5218
5219 A_FaceTarget(mobj);
5220
5221 missile = P_SpawnXYZMissile(mobj, mobj->target, MT_BLACKEGGMAN_GOOPFIRE,
5222 mobj->x + P_ReturnThrustX(mobj, mobj->angle-ANGLE_90, FixedDiv(mobj->radius, 3*FRACUNIT/2)+(4*FRACUNIT)),
5223 mobj->y + P_ReturnThrustY(mobj, mobj->angle-ANGLE_90, FixedDiv(mobj->radius, 3*FRACUNIT/2)+(4*FRACUNIT)),
5224 mobj->z + FixedDiv(mobj->height, 3*FRACUNIT/2));
5225
5226 S_StopSound(missile);
5227
5228 if (leveltime & 1)
5229 S_StartSound(0, sfx_beshot);
5230 }
5231 else if (mobj->state == &states[S_BLACKEGG_JUMP1] && mobj->tics == 1)
5232 {
5233 mobj_t *hitspot = NULL, *mo2;
5234 angle_t an;
5235 fixed_t dist, closestdist;
5236 fixed_t vertical, horizontal;
5237 fixed_t airtime = 5*TICRATE;
5238 INT32 waypointNum = 0;
5239 thinker_t *th;
5240 INT32 i;
5241 boolean foundgoop = false;
5242 INT32 closestNum;
5243 UINT8 extrainfo = (mobj->spawnpoint ? mobj->spawnpoint->extrainfo : 0);
5244
5245 // Looks for players in goop. If you find one, try to jump on him.
5246 for (i = 0; i < MAXPLAYERS; i++)
5247 {
5248 if (!playeringame[i] || players[i].spectator)
5249 continue;
5250
5251 if (!players[i].mo)
5252 continue;
5253
5254 if (players[i].mo->health <= 0)
5255 continue;
5256
5257 if (players[i].powers[pw_carry] == CR_BRAKGOOP)
5258 {
5259 closestNum = -1;
5260 closestdist = INT32_MAX; // Just in case...
5261
5262 // Find waypoint he is closest to
5263 for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
5264 {
5265 if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
5266 continue;
5267
5268 mo2 = (mobj_t *)th;
5269 if (mo2->type != MT_BOSS3WAYPOINT)
5270 continue;
5271 if (!mo2->spawnpoint)
5272 continue;
5273 if (mo2->spawnpoint->extrainfo != extrainfo)
5274 continue;
5275 if (mobj->health <= mobj->info->damage && !(mo2->spawnpoint->options & 7))
5276 continue; // don't jump to center
5277
5278 dist = P_AproxDistance(players[i].mo->x - mo2->x, players[i].mo->y - mo2->y);
5279
5280 if (!(closestNum == -1 || dist < closestdist))
5281 continue;
5282
5283 closestNum = (mo2->spawnpoint->options & 7);
5284 closestdist = dist;
5285 foundgoop = true;
5286 }
5287 waypointNum = closestNum;
5288 break;
5289 }
5290 }
5291
5292 if (!foundgoop)
5293 {
5294 // Don't jump to the center when health is low.
5295 // Force the player to beat you with missiles.
5296 if (mobj->z <= 1056*FRACUNIT || mobj->health <= mobj->info->damage)
5297 waypointNum = 1 + P_RandomKey(4);
5298 else
5299 waypointNum = 0;
5300 }
5301
5302 if (mobj->tracer && mobj->tracer->type == MT_BOSS3WAYPOINT
5303 && mobj->tracer->spawnpoint && (mobj->tracer->spawnpoint->options & 7) == waypointNum)
5304 {
5305 if (P_RandomChance(FRACUNIT/2))
5306 waypointNum++;
5307 else
5308 waypointNum--;
5309
5310 if (mobj->health <= mobj->info->damage)
5311 waypointNum = ((waypointNum + 3) % 4) + 1; // plus four to avoid modulo being negative, minus one to avoid waypoint #0
5312 else
5313 waypointNum = ((waypointNum + 5) % 5);
5314 }
5315
5316 // scan the thinkers to find
5317 // the waypoint to use
5318 for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
5319 {
5320 if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
5321 continue;
5322
5323 mo2 = (mobj_t *)th;
5324 if (mo2->type != MT_BOSS3WAYPOINT)
5325 continue;
5326 if (!mo2->spawnpoint)
5327 continue;
5328 if ((mo2->spawnpoint->options & 7) != waypointNum)
5329 continue;
5330 if (mo2->spawnpoint->extrainfo != extrainfo)
5331 continue;
5332
5333 hitspot = mo2;
5334 break;
5335 }
5336
5337 if (hitspot == NULL)
5338 {
5339 CONS_Debug(DBG_GAMELOGIC, "BlackEggman unable to find waypoint #%d!\n", waypointNum);
5340 P_SetMobjState(mobj, mobj->info->spawnstate);
5341 return;
5342 }
5343
5344 P_SetTarget(&mobj->tracer, hitspot);
5345
5346 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, hitspot->x, hitspot->y);
5347
5348 an = mobj->angle;
5349 an >>= ANGLETOFINESHIFT;
5350
5351 dist = P_AproxDistance(hitspot->x - mobj->x, hitspot->y - mobj->y);
5352
5353 horizontal = dist / airtime;
5354 vertical = (gravity*airtime)/2;
5355
5356 mobj->momx = FixedMul(horizontal, FINECOSINE(an));
5357 mobj->momy = FixedMul(horizontal, FINESINE(an));
5358 mobj->momz = vertical;
5359
5360 // mobj->momz = 10*FRACUNIT;
5361 }
5362 else if (mobj->state == &states[S_BLACKEGG_JUMP2] && mobj->z <= mobj->floorz)
5363 {
5364 // BANG! onto the ground
5365 INT32 i,j;
5366 fixed_t ns;
5367 fixed_t x,y,z;
5368 mobj_t *mo2;
5369
5370 S_StartSound(0, sfx_befall);
5371
5372 z = mobj->floorz;
5373 for (j = 0; j < 2; j++)
5374 {
5375 for (i = 0; i < 32; i++)
5376 {
5377 const angle_t fa = (i*FINEANGLES/16) & FINEMASK;
5378 ns = 64 * FRACUNIT;
5379 x = mobj->x + FixedMul(FINESINE(fa),ns);
5380 y = mobj->y + FixedMul(FINECOSINE(fa),ns);
5381
5382 mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
5383 ns = 16 * FRACUNIT;
5384 mo2->momx = FixedMul(FINESINE(fa),ns);
5385 mo2->momy = FixedMul(FINECOSINE(fa),ns);
5386 }
5387 z -= 32*FRACUNIT;
5388 }
5389
5390 // Hurt player??
5391 for (i = 0; i < MAXPLAYERS; i++)
5392 {
5393 if (!playeringame[i] || players[i].spectator)
5394 continue;
5395
5396 if (!players[i].mo)
5397 continue;
5398
5399 if (players[i].mo->health <= 0)
5400 continue;
5401
5402 if (P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y) > mobj->radius*4)
5403 continue;
5404
5405 if (players[i].mo->z > mobj->z + 128*FRACUNIT)
5406 continue;
5407
5408 if (players[i].mo->z < mobj->z - 64*FRACUNIT)
5409 continue;
5410
5411 P_DamageMobj(players[i].mo, mobj, mobj, 1, 0);
5412
5413 // Laugh
5414 S_StartSound(0, sfx_bewar1 + P_RandomKey(4));
5415 }
5416
5417 P_SetMobjState(mobj, mobj->info->spawnstate);
5418 }
5419 else if (mobj->state == &states[mobj->info->deathstate] && mobj->tics == mobj->state->tics)
5420 S_StartSound(0, sfx_bedie1 + (P_RandomFixed() & 1));
5421
5422 }
5423
5424 #define vectorise mobj->movedir = ANGLE_11hh - FixedAngle(FixedMul(AngleFixed(ANGLE_11hh), FixedDiv((mobj->info->spawnhealth - mobj->health)<<FRACBITS, (mobj->info->spawnhealth-1)<<FRACBITS)));\
5425 if (P_RandomChance(FRACUNIT/2))\
5426 mobj->movedir = InvAngle(mobj->movedir);\
5427 mobj->threshold = 6 + (FixedMul(24<<FRACBITS, FixedDiv((mobj->info->spawnhealth - mobj->health)<<FRACBITS, (mobj->info->spawnhealth-1)<<FRACBITS))>>FRACBITS);\
5428 if (mobj->info->activesound)\
5429 S_StartSound(mobj, mobj->info->activesound);\
5430 if (mobj->info->painchance)\
5431 P_SetMobjState(mobj, mobj->info->painchance);\
5432 mobj->flags2 &= ~MF2_INVERTAIMABLE;\
5433
5434 // Metal Sonic battle boss
5435 // You CAN put multiple Metal Sonics in a single map
5436 // because I am a totally competent programmer who can do shit right.
P_Boss9Thinker(mobj_t * mobj)5437 static void P_Boss9Thinker(mobj_t *mobj)
5438 {
5439 if ((statenum_t)(mobj->state-states) == mobj->info->spawnstate)
5440 mobj->flags2 &= ~MF2_FRET;
5441
5442 if (!mobj->tracer)
5443 {
5444 thinker_t *th;
5445 mobj_t *mo2;
5446 mobj_t *last=NULL;
5447
5448 // Initialize the boss, spawn jet fumes, etc.
5449 mobj->threshold = 0;
5450 mobj->reactiontime = 0;
5451 mobj->watertop = mobj->floorz + 32*FRACUNIT;
5452 var1 = 2;
5453 A_BossJetFume(mobj);
5454
5455 // Run through the thinkers ONCE and find all of the MT_BOSS9GATHERPOINT in the map.
5456 // Build a hoop linked list of 'em!
5457 for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
5458 {
5459 if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
5460 continue;
5461
5462 mo2 = (mobj_t *)th;
5463 if (mo2->type == MT_BOSS9GATHERPOINT)
5464 {
5465 if (last)
5466 P_SetTarget(&last->hnext, mo2);
5467 else
5468 P_SetTarget(&mobj->hnext, mo2);
5469 P_SetTarget(&mo2->hprev, last);
5470 last = mo2;
5471 }
5472 }
5473 }
5474
5475 if (mobj->health <= 0)
5476 return;
5477
5478 if ((statenum_t)(mobj->state-states) == mobj->info->meleestate)
5479 {
5480 P_InstaThrust(mobj, mobj->angle, -4*FRACUNIT);
5481 P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true);
5482 mobj->momz -= gravity;
5483 if (mobj->z < mobj->watertop || mobj->z < (mobj->floorz + 16*FRACUNIT))
5484 {
5485 mobj->watertop = mobj->floorz + 32*FRACUNIT;
5486 P_SetMobjState(mobj, mobj->info->spawnstate);
5487 }
5488 return;
5489 }
5490
5491 if ((!mobj->target || !(mobj->target->flags & MF_SHOOTABLE)))
5492 {
5493 if (mobj->hprev)
5494 {
5495 P_RemoveMobj(mobj->hprev);
5496 P_SetTarget(&mobj->hprev, NULL);
5497 }
5498 P_BossTargetPlayer(mobj, false);
5499 if (mobj->target && (!P_IsObjectOnGround(mobj->target) || mobj->target->player->pflags & PF_SPINNING))
5500 P_SetTarget(&mobj->target, NULL); // Wait for them to hit the ground first
5501 if (!mobj->target) // Still no target, aww.
5502 {
5503 // Reset the boss.
5504 if (mobj->hprev)
5505 {
5506 P_RemoveMobj(mobj->hprev);
5507 P_SetTarget(&mobj->hprev, NULL);
5508 }
5509 P_SetMobjState(mobj, mobj->info->spawnstate);
5510 mobj->fuse = 0;
5511 mobj->momx = FixedDiv(mobj->momx, FRACUNIT + (FRACUNIT>>2));
5512 mobj->momy = FixedDiv(mobj->momy, FRACUNIT + (FRACUNIT>>2));
5513 mobj->momz = FixedDiv(mobj->momz, FRACUNIT + (FRACUNIT>>2));
5514 mobj->watertop = mobj->floorz + 32*FRACUNIT;
5515 mobj->momz = (mobj->watertop - mobj->z)>>3;
5516 mobj->threshold = 0;
5517 mobj->movecount = 0;
5518 mobj->flags = mobj->info->flags;
5519 return;
5520 }
5521 else if (!mobj->fuse)
5522 mobj->fuse = 8*TICRATE;
5523 }
5524
5525 // AI goes here.
5526 {
5527 angle_t angle;
5528 if (mobj->threshold || mobj->movecount)
5529 mobj->momz = (mobj->watertop-mobj->z)/16; // Float to your desired position FASTER
5530 else
5531 mobj->momz = (mobj->watertop-mobj->z)/40; // Float to your desired position
5532
5533 if (mobj->movecount == 2)
5534 {
5535 mobj_t *spawner;
5536 fixed_t dist = 0;
5537 angle = 0x06000000*leveltime; // wtf?
5538
5539 // Face your target
5540 P_BossTargetPlayer(mobj, true);
5541 angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); // absolute angle
5542 angle = (angle-mobj->angle); // relative angle
5543 if (angle < ANGLE_180)
5544 mobj->angle += angle/8;
5545 else
5546 mobj->angle -= InvAngle(angle)/8;
5547
5548 // Alter your energy bubble's size/position
5549 if (mobj->health > mobj->info->damage)
5550 {
5551 if (mobj->hprev)
5552 {
5553 mobj->hprev->destscale = FRACUNIT + (2*TICRATE - mobj->fuse)*(FRACUNIT/2)/TICRATE + FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),FRACUNIT/2);
5554 P_SetScale(mobj->hprev, mobj->hprev->destscale);
5555
5556 P_TeleportMove(mobj->hprev, mobj->x, mobj->y, mobj->z + mobj->height/2 - mobj->hprev->height/2);
5557 mobj->hprev->momx = mobj->momx;
5558 mobj->hprev->momy = mobj->momy;
5559 mobj->hprev->momz = mobj->momz;
5560 }
5561
5562 // Firin' mah lazors - INDICATOR
5563 if (mobj->fuse > TICRATE/2)
5564 {
5565 tic_t shoottime, worktime, calctime;
5566 shoottime = (TICRATE/((mobj->extravalue1 == 3) ? 8 : 4));
5567 shoottime += (shoottime>>1);
5568 worktime = shoottime*(mobj->threshold/2);
5569 calctime = mobj->fuse-(TICRATE/2);
5570
5571 if (calctime <= worktime && (calctime % shoottime == 0))
5572 {
5573 mobj_t *missile;
5574
5575 missile = P_SpawnMissile(mobj, mobj->target, MT_MSGATHER);
5576 S_StopSound(missile);
5577 if (mobj->extravalue1 >= 2)
5578 P_SetScale(missile, FRACUNIT>>1);
5579 missile->destscale = missile->scale>>1;
5580 missile->fuse = TICRATE/2;
5581 missile->scalespeed = abs(missile->destscale - missile->scale)/missile->fuse;
5582 missile->z -= missile->height/2;
5583 missile->momx *= -1;
5584 missile->momy *= -1;
5585 missile->momz *= -1;
5586
5587 if (mobj->extravalue1 == 2)
5588 {
5589 UINT8 i;
5590 mobj_t *spread;
5591 for (i = 0; i < 5; i++)
5592 {
5593 if (i == 2)
5594 continue;
5595 spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type);
5596 spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2);
5597 P_InstaThrust(spread,spread->angle,-spread->info->speed);
5598 spread->momz = missile->momz;
5599 P_SetScale(spread, missile->scale);
5600 spread->destscale = missile->destscale;
5601 spread->scalespeed = missile->scalespeed;
5602 spread->fuse = missile->fuse;
5603 P_UnsetThingPosition(spread);
5604 spread->x -= spread->fuse*spread->momx;
5605 spread->y -= spread->fuse*spread->momy;
5606 spread->z -= spread->fuse*spread->momz;
5607 P_SetThingPosition(spread);
5608 }
5609 P_InstaThrust(missile,missile->angle,-missile->info->speed);
5610 }
5611 else if (mobj->extravalue1 >= 3)
5612 {
5613 UINT8 i;
5614 mobj_t *spread;
5615 mobj->target->z -= (4*missile->height);
5616 for (i = 0; i < 5; i++)
5617 {
5618 if (i != 2)
5619 {
5620 spread = P_SpawnMissile(mobj, mobj->target, missile->type);
5621 P_SetScale(spread, missile->scale);
5622 spread->destscale = missile->destscale;
5623 spread->fuse = missile->fuse;
5624 spread->z -= spread->height/2;
5625 spread->momx *= -1;
5626 spread->momy *= -1;
5627 spread->momz *= -1;
5628 P_UnsetThingPosition(spread);
5629 spread->x -= spread->fuse*spread->momx;
5630 spread->y -= spread->fuse*spread->momy;
5631 spread->z -= spread->fuse*spread->momz;
5632 P_SetThingPosition(spread);
5633 }
5634 mobj->target->z += missile->height*2;
5635 }
5636 mobj->target->z -= (6*missile->height);
5637 }
5638
5639 P_UnsetThingPosition(missile);
5640 missile->x -= missile->fuse*missile->momx;
5641 missile->y -= missile->fuse*missile->momy;
5642 missile->z -= missile->fuse*missile->momz;
5643 P_SetThingPosition(missile);
5644
5645 S_StartSound(mobj, sfx_s3kb3);
5646 }
5647 }
5648 }
5649
5650 // up...
5651 mobj->z += mobj->height/2;
5652
5653 // Spawn energy particles
5654 for (spawner = mobj->hnext; spawner; spawner = spawner->hnext)
5655 {
5656 dist = P_AproxDistance(spawner->x - mobj->x, spawner->y - mobj->y);
5657 if (P_RandomRange(1,(dist>>FRACBITS)/16) == 1)
5658 break;
5659 }
5660 if (spawner && dist)
5661 {
5662 mobj_t *missile = P_SpawnMissile(spawner, mobj, MT_MSGATHER);
5663 missile->fuse = (dist/P_AproxDistance(missile->momx, missile->momy));
5664
5665 if (missile->fuse > mobj->fuse)
5666 P_RemoveMobj(missile);
5667
5668 if (mobj->health > mobj->info->damage)
5669 {
5670 P_SetScale(missile, FRACUNIT/3);
5671 missile->color = SKINCOLOR_MAGENTA; // sonic OVA/4 purple power
5672 }
5673 else
5674 {
5675 P_SetScale(missile, FRACUNIT/5);
5676 missile->color = SKINCOLOR_SUNSET; // sonic cd electric power
5677 }
5678 missile->destscale = missile->scale*2;
5679 missile->scalespeed = abs(missile->scale - missile->destscale)/missile->fuse;
5680 missile->colorized = true;
5681 }
5682
5683 // ...then down. easier than changing the missile's momz after-the-fact
5684 mobj->z -= mobj->height/2;
5685 }
5686
5687 // Pre-threshold reactiontime stuff for attack phases
5688 if (mobj->reactiontime && mobj->movecount == 3)
5689 {
5690 mobj->reactiontime--;
5691
5692 if (mobj->movedir == 0 || mobj->movedir == 2) { // Pausing between bounces in the pinball phase
5693 if (mobj->target->player->powers[pw_tailsfly]) // Trying to escape, eh?
5694 mobj->watertop = mobj->target->z + mobj->target->momz*6; // Readjust your aim. >:3
5695 else if (mobj->target->floorz > mobj->floorz)
5696 mobj->watertop = mobj->target->floorz + 16*FRACUNIT;
5697 else
5698 mobj->watertop = mobj->floorz + 16*FRACUNIT;
5699
5700 if (!(mobj->threshold%4)) {
5701 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4);
5702 if (!mobj->reactiontime)
5703 S_StartSound(mobj, sfx_zoom); // zoom!
5704 }
5705 }
5706 // else -- Pausing between energy ball shots
5707
5708 return;
5709 }
5710
5711 // threshold is used for attacks/maneuvers.
5712 if (mobj->threshold && mobj->movecount != 2) {
5713 fixed_t speed = 20*FRACUNIT + FixedMul(40*FRACUNIT, FixedDiv((mobj->info->spawnhealth - mobj->health)<<FRACBITS, mobj->info->spawnhealth<<FRACBITS));
5714 UINT8 tries = 0;
5715
5716 // Firin' mah lazors
5717 if (mobj->movecount == 3 && mobj->movedir == 1)
5718 {
5719 if (!(mobj->threshold & 1))
5720 {
5721 mobj_t *missile;
5722 if (mobj->info->seesound)
5723 S_StartSound(mobj, mobj->info->seesound);
5724 P_SetMobjState(mobj, mobj->info->missilestate);
5725 if (mobj->extravalue1 == 3)
5726 mobj->reactiontime = TICRATE/16;
5727 else
5728 mobj->reactiontime = TICRATE/8;
5729
5730 A_FaceTarget(mobj);
5731 missile = P_SpawnMissile(mobj, mobj->target, mobj->info->speed);
5732 if (mobj->extravalue1 >= 2)
5733 {
5734 missile->destscale = FRACUNIT>>1;
5735 P_SetScale(missile, missile->destscale);
5736 }
5737 missile->fuse = 3*TICRATE;
5738 missile->z -= missile->height/2;
5739
5740 if (mobj->extravalue1 == 2)
5741 {
5742 UINT8 i;
5743 mobj_t *spread;
5744 for (i = 0; i < 5; i++)
5745 {
5746 if (i == 2)
5747 continue;
5748 spread = P_SpawnMobj(missile->x, missile->y, missile->z, missile->type);
5749 spread->angle = missile->angle+(ANGLE_11hh/2)*(i-2);
5750 P_InstaThrust(spread,spread->angle,spread->info->speed);
5751 spread->momz = missile->momz;
5752 spread->destscale = FRACUNIT>>1;
5753 P_SetScale(spread, spread->destscale);
5754 spread->fuse = missile->fuse;
5755 }
5756 P_InstaThrust(missile,missile->angle,missile->info->speed);
5757 }
5758 else if (mobj->extravalue1 >= 3)
5759 {
5760 UINT8 i;
5761 mobj_t *spread;
5762 mobj->target->z -= (2*missile->height);
5763 for (i = 0; i < 5; i++)
5764 {
5765 if (i != 2)
5766 {
5767 spread = P_SpawnMissile(mobj, mobj->target, missile->type);
5768 spread->destscale = FRACUNIT>>1;
5769 P_SetScale(spread, spread->destscale);
5770 spread->fuse = missile->fuse;
5771 spread->z -= spread->height/2;
5772 }
5773 mobj->target->z += missile->height;
5774 }
5775 mobj->target->z -= (3*missile->height);
5776 }
5777 }
5778 else
5779 {
5780 P_SetMobjState(mobj, mobj->state->nextstate);
5781 if (mobj->extravalue1 == 3)
5782 mobj->reactiontime = TICRATE/8;
5783 else
5784 mobj->reactiontime = TICRATE/4;
5785 }
5786 mobj->threshold--;
5787 return;
5788 }
5789
5790 // Pinball attack!
5791 if (mobj->movecount == 3 && (mobj->movedir == 0 || mobj->movedir == 2))
5792 {
5793 if ((statenum_t)(mobj->state-states) != mobj->info->seestate)
5794 P_SetMobjState(mobj, mobj->info->seestate);
5795 if (mobj->movedir == 0) // mobj health == 1
5796 P_InstaThrust(mobj, mobj->angle, 38*FRACUNIT);
5797 else if (mobj->health == 3)
5798 P_InstaThrust(mobj, mobj->angle, 22*FRACUNIT);
5799 else // mobj health == 2
5800 P_InstaThrust(mobj, mobj->angle, 30*FRACUNIT);
5801 if (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true))
5802 { // Hit a wall? Find a direction to bounce
5803 mobj->threshold--;
5804 if (!mobj->threshold) { // failed bounce!
5805 S_StartSound(mobj, sfx_mspogo);
5806 P_BounceMove(mobj);
5807 mobj->angle = R_PointToAngle2(mobj->momx, mobj->momy,0,0);
5808 mobj->momz = 4*FRACUNIT;
5809 mobj->flags &= ~MF_PAIN;
5810 mobj->fuse = 8*TICRATE;
5811 mobj->movecount = 0;
5812 P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_CYBRAKDEMON_VILE_EXPLOSION);
5813 P_SetMobjState(mobj, mobj->info->meleestate);
5814 }
5815 else if (!(mobj->threshold%4))
5816 { // We've decided to lock onto the player this bounce.
5817 P_SetMobjState(mobj, mobj->state->nextstate);
5818 S_StartSound(mobj, sfx_s3k5a);
5819 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx*4, mobj->target->y + mobj->target->momy*4);
5820 mobj->reactiontime = TICRATE - 5*(mobj->info->damage - mobj->health); // targetting time
5821 }
5822 else
5823 { // No homing, just use P_BounceMove
5824 S_StartSound(mobj, sfx_s3kaa); // make the bounces distinct...
5825 P_BounceMove(mobj);
5826 mobj->angle = R_PointToAngle2(0,0,mobj->momx,mobj->momy);
5827 mobj->reactiontime = 1; // TICRATE/4; // just a pause before you bounce away
5828 }
5829 mobj->momx = mobj->momy = 0;
5830 }
5831 return;
5832 }
5833
5834 P_SpawnGhostMobj(mobj)->colorized = false;
5835
5836 // Vector form dodge!
5837 mobj->angle += mobj->movedir;
5838 P_InstaThrust(mobj, mobj->angle, -speed);
5839 while (!P_TryMove(mobj, mobj->x+mobj->momx, mobj->y+mobj->momy, true) && tries++ < 16)
5840 {
5841 S_StartSound(mobj, sfx_mspogo);
5842 P_BounceMove(mobj);
5843 mobj->angle = R_PointToAngle2(mobj->momx, mobj->momy,0,0);
5844 }
5845 mobj->momx = mobj->momy = 0;
5846 mobj->threshold--;
5847 if (!mobj->threshold)
5848 { // Go into stun after dodge.
5849 // from 3*TICRATE down to 1.25*TICRATE
5850 //mobj->reactiontime = 5*TICRATE/4 + (FixedMul((7*TICRATE/4)<<FRACBITS, FixedDiv((mobj->health-1)<<FRACBITS, (mobj->info->spawnhealth-1)<<FRACBITS))>>FRACBITS);
5851 // from 3*TICRATE down to 2*TICRATE
5852 mobj->reactiontime = 2*TICRATE + (FixedMul((1*TICRATE)<<FRACBITS, FixedDiv((mobj->health-1)<<FRACBITS, (mobj->info->spawnhealth-1)<<FRACBITS))>>FRACBITS);
5853 mobj->flags |= MF_SPECIAL|MF_SHOOTABLE;
5854 P_SetMobjState(mobj, mobj->state->nextstate);
5855 }
5856 return;
5857 }
5858
5859 angle = 0x06000000*leveltime;
5860 mobj->momz += FixedMul(FINECOSINE(angle>>ANGLETOFINESHIFT),2*FRACUNIT); // Use that "angle" to bob gently in the air
5861 // This is below threshold because we don't want to bob while zipping around
5862
5863 // Ohh you're in for it now..
5864 if (mobj->flags2 & MF2_FRET && mobj->health <= mobj->info->damage)
5865 mobj->fuse = 0;
5866
5867 // reactiontime is used for delays.
5868 if (mobj->reactiontime)
5869 {
5870 // Stunned after vector form
5871 if (mobj->movedir > ANGLE_180)
5872 mobj->angle -= FixedAngle(FixedMul(AngleFixed(InvAngle(mobj->movedir)),FixedDiv(mobj->reactiontime<<FRACBITS,24<<FRACBITS)));
5873 else
5874 mobj->angle += FixedAngle(FixedMul(AngleFixed(mobj->movedir),FixedDiv(mobj->reactiontime<<FRACBITS,24<<FRACBITS)));
5875 mobj->reactiontime--;
5876 if (!mobj->reactiontime)
5877 // Out of stun.
5878 P_SetMobjState(mobj, mobj->state->nextstate);
5879 return;
5880 }
5881
5882 // Not stunned? Can hit.
5883 // Here because stun won't always get the chance to complete due to pinch phase activating, being hit, etc.
5884 mobj->flags &= ~(MF_SPECIAL|MF_SHOOTABLE);
5885
5886 if (mobj->health <= mobj->info->damage && mobj->fuse && !(mobj->fuse%TICRATE))
5887 {
5888 var1 = 1;
5889 var2 = 0;
5890 A_BossScream(mobj);
5891 }
5892
5893 // Don't move if we're still in pain!
5894 if (mobj->flags2 & MF2_FRET)
5895 return;
5896
5897 if (mobj->movecount == 1 || mobj->movecount == 2)
5898 { // Charging energy
5899 if (mobj->momx != 0 || mobj->momy != 0) { // Apply the air breaks
5900 if (abs(mobj->momx)+abs(mobj->momy) < FRACUNIT)
5901 mobj->momx = mobj->momy = 0;
5902 else
5903 P_Thrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), -6*FRACUNIT/8);
5904 }
5905 if (mobj->state == states+mobj->info->raisestate)
5906 return;
5907 }
5908
5909 if (mobj->fuse == 0)
5910 {
5911 mobj->flags2 &= ~MF2_INVERTAIMABLE;
5912 // It's time to attack! What are we gonna do?!
5913 switch(mobj->movecount)
5914 {
5915 case 0:
5916 default:
5917 // Fly up and prepare for an attack!
5918 // We have to charge up first, so let's go up into the air
5919 S_StartSound(mobj, sfx_beflap);
5920 P_SetMobjState(mobj, mobj->info->raisestate);
5921 if (mobj->floorz >= mobj->target->floorz)
5922 mobj->watertop = mobj->floorz + 256*FRACUNIT;
5923 else
5924 mobj->watertop = mobj->target->floorz + 256*FRACUNIT;
5925 break;
5926
5927 case 1:
5928 // Okay, we're up? Good, time to gather energy...
5929 if (mobj->health > mobj->info->damage)
5930 { // No more bubble if we're broken (pinch phase)
5931 mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT);
5932 P_SetTarget(&mobj->hprev, shield);
5933 P_SetTarget(&shield->target, mobj);
5934
5935 // Attack 2: Energy shot!
5936 switch (mobj->health)
5937 {
5938 case 8: // shoot once
5939 default:
5940 mobj->extravalue1 = 0;
5941 mobj->threshold = 2;
5942 break;
5943 case 7: // spread shot (vertical)
5944 mobj->extravalue1 = 4;
5945 mobj->threshold = 2;
5946 break;
5947 case 6: // three shots
5948 mobj->extravalue1 = 1;
5949 mobj->threshold = 3*2;
5950 break;
5951 case 5: // spread shot (horizontal)
5952 mobj->extravalue1 = 2;
5953 mobj->threshold = 2;
5954 break;
5955 case 4: // machine gun
5956 mobj->extravalue1 = 3;
5957 mobj->threshold = 5*2;
5958 break;
5959 }
5960 }
5961 else
5962 {
5963 /*mobj_t *shield = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_MSSHIELD_FRONT);
5964 P_SetTarget(&mobj->tracer, shield);
5965 P_SetTarget(&shield->target, mobj);
5966 shield->height -= 20*FRACUNIT; // different offset...
5967 P_SetMobjState(shield, S_MSSHIELD_F2);*/
5968 P_SetMobjState(mobj, S_METALSONIC_BOUNCE);
5969 //P_LinedefExecute(LE_PINCHPHASE, mobj, NULL); -- why does this happen twice? see case 2...
5970 }
5971 mobj->fuse = 3*TICRATE;
5972 mobj->flags |= MF_PAIN;
5973 if (mobj->info->attacksound)
5974 S_StartSound(mobj, mobj->info->attacksound);
5975 A_FaceTarget(mobj);
5976
5977 break;
5978
5979 case 2:
5980 {
5981 // We're all charged and ready now! Unleash the fury!!
5982 S_StopSound(mobj);
5983 if (mobj->hprev)
5984 {
5985 P_RemoveMobj(mobj->hprev);
5986 P_SetTarget(&mobj->hprev, NULL);
5987 }
5988 if (mobj->health <= mobj->info->damage)
5989 {
5990 // Attack 1: Pinball dash!
5991 if (mobj->health == 1)
5992 mobj->movedir = 0;
5993 else
5994 mobj->movedir = 2;
5995 if (mobj->info->seesound)
5996 S_StartSound(mobj, mobj->info->seesound);
5997 P_SetMobjState(mobj, mobj->info->seestate);
5998 if (mobj->movedir == 2)
5999 mobj->threshold = 12; // bounce 12 times
6000 else
6001 mobj->threshold = 24; // bounce 24 times
6002 if (mobj->floorz >= mobj->target->floorz)
6003 mobj->watertop = mobj->floorz + 16*FRACUNIT;
6004 else
6005 mobj->watertop = mobj->target->floorz + 16*FRACUNIT;
6006 P_LinedefExecute(LE_PINCHPHASE, mobj, NULL);
6007
6008 #if 0
6009 whoosh = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_GHOST); // done here so the offset is correct
6010 whoosh->frame = FF_FULLBRIGHT;
6011 whoosh->sprite = SPR_ARMA;
6012 whoosh->destscale = whoosh->scale<<1;
6013 whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale);
6014 whoosh->height = 38*whoosh->scale;
6015 whoosh->fuse = 10;
6016 whoosh->color = SKINCOLOR_SUNSET;
6017 whoosh->colorized = true;
6018 whoosh->flags |= MF_NOCLIPHEIGHT;
6019 #endif
6020
6021 P_SetMobjState(mobj->tracer, S_JETFUMEFLASH);
6022 P_SetScale(mobj->tracer, mobj->scale << 1);
6023 }
6024 else
6025 {
6026 // Attack 2: Energy shot!
6027 mobj->movedir = 1;
6028 // looking for the number of things to fire? that's done in case 1 now
6029 }
6030 break;
6031 }
6032 case 3:
6033 // Return to idle.
6034 if (mobj->floorz >= mobj->target->floorz)
6035 mobj->watertop = mobj->floorz + 32*FRACUNIT;
6036 else
6037 mobj->watertop = mobj->target->floorz + 32*FRACUNIT;
6038 P_SetMobjState(mobj, mobj->info->spawnstate);
6039 mobj->flags &= ~MF_PAIN;
6040 mobj->fuse = 8*TICRATE;
6041 break;
6042 }
6043 mobj->movecount++;
6044 mobj->movecount %= 4;
6045 return;
6046 }
6047
6048 // Idle AI
6049 if (mobj->state == &states[mobj->info->spawnstate])
6050 {
6051 fixed_t dist;
6052
6053 // Target the closest player
6054 P_BossTargetPlayer(mobj, true);
6055
6056 // Face your target
6057 angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y); // absolute angle
6058 angle = (angle-mobj->angle); // relative angle
6059 if (angle < ANGLE_180)
6060 mobj->angle += angle/8;
6061 else
6062 mobj->angle -= InvAngle(angle)/8;
6063 //A_FaceTarget(mobj);
6064
6065 if (mobj->flags2 & MF2_CLASSICPUSH)
6066 mobj->flags2 &= ~MF2_CLASSICPUSH; // a missile caught us in PIT_CheckThing!
6067 else
6068 {
6069 // Check if we're being attacked
6070 if (!mobj->target || !mobj->target->player || !P_PlayerCanDamage(mobj->target->player, mobj))
6071 goto nodanger;
6072 if (mobj->target->x+mobj->target->radius+abs(mobj->target->momx*2) < mobj->x-mobj->radius)
6073 goto nodanger;
6074 if (mobj->target->x-mobj->target->radius-abs(mobj->target->momx*2) > mobj->x+mobj->radius)
6075 goto nodanger;
6076 if (mobj->target->y+mobj->target->radius+abs(mobj->target->momy*2) < mobj->y-mobj->radius)
6077 goto nodanger;
6078 if (mobj->target->y-mobj->target->radius-abs(mobj->target->momy*2) > mobj->y+mobj->radius)
6079 goto nodanger;
6080 if (mobj->target->z+mobj->target->height+mobj->target->momz*2 < mobj->z)
6081 goto nodanger;
6082 if (mobj->target->z+mobj->target->momz*2 > mobj->z+mobj->height)
6083 goto nodanger;
6084 }
6085
6086 // An incoming attack is detected! What should we do?!
6087 // Go into vector form!
6088 vectorise;
6089 return;
6090 nodanger:
6091
6092 mobj->flags2 |= MF2_INVERTAIMABLE;
6093
6094 // Move normally: Approach the player using normal thrust and simulated friction.
6095 dist = P_AproxDistance(mobj->x-mobj->target->x, mobj->y-mobj->target->y);
6096 P_Thrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), -3*FRACUNIT/8);
6097 if (dist < 64*FRACUNIT && !(mobj->target->player && mobj->target->player->homing))
6098 P_Thrust(mobj, mobj->angle, -4*FRACUNIT);
6099 else if (dist > 180*FRACUNIT)
6100 P_Thrust(mobj, mobj->angle, FRACUNIT);
6101 else
6102 P_Thrust(mobj, mobj->angle + ANGLE_90, FINECOSINE((((angle_t)(leveltime*ANG1))>>ANGLETOFINESHIFT) & FINEMASK)>>1);
6103 mobj->momz += P_AproxDistance(mobj->momx, mobj->momy)/12; // Move up higher the faster you're going.
6104 }
6105 }
6106 }
6107 #undef vectorise
6108
6109 //
6110 // P_GetClosestAxis
6111 //
6112 // Finds the CLOSEST axis to the source mobj
P_GetClosestAxis(mobj_t * source)6113 mobj_t *P_GetClosestAxis(mobj_t *source)
6114 {
6115 thinker_t *th;
6116 mobj_t *mo2;
6117 mobj_t *closestaxis = NULL;
6118 fixed_t dist1, dist2 = 0;
6119
6120 // scan the thinkers to find the closest axis point
6121 for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
6122 {
6123 if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
6124 continue;
6125
6126 mo2 = (mobj_t *)th;
6127
6128 if (mo2->type == MT_AXIS)
6129 {
6130 if (closestaxis == NULL)
6131 {
6132 closestaxis = mo2;
6133 dist2 = R_PointToDist2(source->x, source->y, mo2->x, mo2->y)-mo2->radius;
6134 }
6135 else
6136 {
6137 dist1 = R_PointToDist2(source->x, source->y, mo2->x, mo2->y)-mo2->radius;
6138
6139 if (dist1 < dist2)
6140 {
6141 closestaxis = mo2;
6142 dist2 = dist1;
6143 }
6144 }
6145 }
6146 }
6147
6148 if (closestaxis == NULL)
6149 CONS_Debug(DBG_NIGHTS, "ERROR: No axis points found!\n");
6150
6151 return closestaxis;
6152 }
6153
P_GimmeAxisXYPos(mobj_t * closestaxis,degenmobj_t * mobj)6154 static void P_GimmeAxisXYPos(mobj_t *closestaxis, degenmobj_t *mobj)
6155 {
6156 const angle_t fa = R_PointToAngle2(closestaxis->x, closestaxis->y, mobj->x, mobj->y)>>ANGLETOFINESHIFT;
6157
6158 mobj->x = closestaxis->x + FixedMul(FINECOSINE(fa),closestaxis->radius);
6159 mobj->y = closestaxis->y + FixedMul(FINESINE(fa),closestaxis->radius);
6160 }
6161
P_MoveHoop(mobj_t * mobj)6162 static void P_MoveHoop(mobj_t *mobj)
6163 {
6164 const fixed_t fuse = (mobj->fuse*mobj->extravalue2);
6165 const angle_t fa = mobj->movedir*(FINEANGLES/mobj->extravalue1);
6166 TVector v;
6167 TVector *res;
6168 fixed_t finalx, finaly, finalz;
6169 fixed_t x, y, z;
6170
6171 //I_Assert(mobj->target != NULL);
6172 if (!mobj->target) /// \todo DEBUG ME! Target was P_RemoveMobj'd at some point, and therefore no longer valid!
6173 return;
6174
6175 x = mobj->target->x;
6176 y = mobj->target->y;
6177 z = mobj->target->z+mobj->target->height/2;
6178
6179 // Make the sprite travel towards the center of the hoop
6180 v[0] = FixedMul(FINECOSINE(fa),fuse);
6181 v[1] = 0;
6182 v[2] = FixedMul(FINESINE(fa),fuse);
6183 v[3] = FRACUNIT;
6184
6185 res = VectorMatrixMultiply(v, *RotateXMatrix(FixedAngle(mobj->target->movedir*FRACUNIT)));
6186 M_Memcpy(&v, res, sizeof (v));
6187 res = VectorMatrixMultiply(v, *RotateZMatrix(FixedAngle(mobj->target->movecount*FRACUNIT)));
6188 M_Memcpy(&v, res, sizeof (v));
6189
6190 finalx = x + v[0];
6191 finaly = y + v[1];
6192 finalz = z + v[2];
6193
6194 P_UnsetThingPosition(mobj);
6195 mobj->x = finalx;
6196 mobj->y = finaly;
6197 P_SetThingPosition(mobj);
6198 mobj->z = finalz - mobj->height/2;
6199 }
6200
P_SpawnHoopOfSomething(fixed_t x,fixed_t y,fixed_t z,fixed_t radius,INT32 number,mobjtype_t type,angle_t rotangle)6201 void P_SpawnHoopOfSomething(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, angle_t rotangle)
6202 {
6203 mobj_t *mobj;
6204 INT32 i;
6205 TVector v;
6206 TVector *res;
6207 fixed_t finalx, finaly, finalz;
6208 mobj_t hoopcenter;
6209 mobj_t *axis;
6210 degenmobj_t xypos;
6211 angle_t degrees, fa, closestangle;
6212
6213 hoopcenter.x = x;
6214 hoopcenter.y = y;
6215 hoopcenter.z = z;
6216
6217 axis = P_GetClosestAxis(&hoopcenter);
6218
6219 if (!axis)
6220 {
6221 CONS_Debug(DBG_NIGHTS, "You forgot to put axis points in the map!\n");
6222 return;
6223 }
6224
6225 xypos.x = x;
6226 xypos.y = y;
6227
6228 P_GimmeAxisXYPos(axis, &xypos);
6229
6230 x = xypos.x;
6231 y = xypos.y;
6232
6233 hoopcenter.z = z - mobjinfo[type].height/2;
6234
6235 hoopcenter.x = x;
6236 hoopcenter.y = y;
6237
6238 closestangle = R_PointToAngle2(x, y, axis->x, axis->y);
6239
6240 degrees = FINEANGLES/number;
6241
6242 radius >>= FRACBITS;
6243
6244 // Create the hoop!
6245 for (i = 0; i < number; i++)
6246 {
6247 fa = (i*degrees);
6248 v[0] = FixedMul(FINECOSINE(fa),radius);
6249 v[1] = 0;
6250 v[2] = FixedMul(FINESINE(fa),radius);
6251 v[3] = FRACUNIT;
6252
6253 res = VectorMatrixMultiply(v, *RotateXMatrix(rotangle));
6254 M_Memcpy(&v, res, sizeof (v));
6255 res = VectorMatrixMultiply(v, *RotateZMatrix(closestangle));
6256 M_Memcpy(&v, res, sizeof (v));
6257
6258 finalx = x + v[0];
6259 finaly = y + v[1];
6260 finalz = z + v[2];
6261
6262 mobj = P_SpawnMobj(finalx, finaly, finalz, type);
6263 mobj->z -= mobj->height/2;
6264 }
6265 }
6266
P_SpawnParaloop(fixed_t x,fixed_t y,fixed_t z,fixed_t radius,INT32 number,mobjtype_t type,statenum_t nstate,angle_t rotangle,boolean spawncenter)6267 void P_SpawnParaloop(fixed_t x, fixed_t y, fixed_t z, fixed_t radius, INT32 number, mobjtype_t type, statenum_t nstate, angle_t rotangle, boolean spawncenter)
6268 {
6269 mobj_t *mobj;
6270 INT32 i;
6271 TVector v;
6272 TVector *res;
6273 fixed_t finalx, finaly, finalz, dist;
6274 angle_t degrees, fa, closestangle;
6275 fixed_t mobjx, mobjy, mobjz;
6276
6277 degrees = FINEANGLES/number;
6278
6279 radius = FixedDiv(radius,5*(FRACUNIT/4));
6280
6281 closestangle = 0;
6282
6283 // Create the hoop!
6284 for (i = 0; i < number; i++)
6285 {
6286 fa = (i*degrees);
6287 v[0] = FixedMul(FINECOSINE(fa),radius);
6288 v[1] = 0;
6289 v[2] = FixedMul(FINESINE(fa),radius);
6290 v[3] = FRACUNIT;
6291
6292 res = VectorMatrixMultiply(v, *RotateXMatrix(rotangle));
6293 M_Memcpy(&v, res, sizeof (v));
6294 res = VectorMatrixMultiply(v, *RotateZMatrix(closestangle));
6295 M_Memcpy(&v, res, sizeof (v));
6296
6297 finalx = x + v[0];
6298 finaly = y + v[1];
6299 finalz = z + v[2];
6300
6301 mobj = P_SpawnMobj(finalx, finaly, finalz, type);
6302
6303 mobj->z -= mobj->height>>1;
6304
6305 // change angle
6306 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, x, y);
6307
6308 // change slope
6309 dist = P_AproxDistance(P_AproxDistance(x - mobj->x, y - mobj->y), z - mobj->z);
6310
6311 if (dist < 1)
6312 dist = 1;
6313
6314 mobjx = mobj->x;
6315 mobjy = mobj->y;
6316 mobjz = mobj->z;
6317
6318 // set to special state
6319 if (nstate != S_NULL)
6320 P_SetMobjState(mobj, nstate);
6321
6322 mobj->momx = FixedMul(FixedDiv(x - mobjx, dist), 5*FRACUNIT);
6323 mobj->momy = FixedMul(FixedDiv(y - mobjy, dist), 5*FRACUNIT);
6324 mobj->momz = FixedMul(FixedDiv(z - mobjz, dist), 5*FRACUNIT);
6325 mobj->fuse = (radius>>(FRACBITS+2)) + 1;
6326
6327 if (spawncenter)
6328 {
6329 mobj->x = x;
6330 mobj->y = y;
6331 mobj->z = z;
6332 }
6333
6334 if (mobj->fuse <= 1)
6335 mobj->fuse = 2;
6336
6337 mobj->flags |= MF_NOCLIPTHING;
6338 mobj->flags &= ~MF_SPECIAL;
6339
6340 if (mobj->fuse > 7)
6341 mobj->tics = mobj->fuse - 7;
6342 else
6343 mobj->tics = 1;
6344 }
6345 }
6346
6347 //
6348 // P_SetScale
6349 //
6350 // Sets the sprite scaling
6351 //
P_SetScale(mobj_t * mobj,fixed_t newscale)6352 void P_SetScale(mobj_t *mobj, fixed_t newscale)
6353 {
6354 player_t *player;
6355 fixed_t oldscale;
6356
6357 if (!mobj)
6358 return;
6359
6360 oldscale = mobj->scale; //keep for adjusting stuff below
6361
6362 mobj->scale = newscale;
6363
6364 mobj->radius = FixedMul(FixedDiv(mobj->radius, oldscale), newscale);
6365 mobj->height = FixedMul(FixedDiv(mobj->height, oldscale), newscale);
6366
6367 player = mobj->player;
6368
6369 if (player)
6370 {
6371 G_GhostAddScale(newscale);
6372 player->viewheight = FixedMul(FixedDiv(player->viewheight, oldscale), newscale); // Nonono don't calculate viewheight elsewhere, this is the best place for it!
6373 }
6374 }
6375
P_Attract(mobj_t * source,mobj_t * dest,boolean nightsgrab)6376 void P_Attract(mobj_t *source, mobj_t *dest, boolean nightsgrab) // Home in on your target
6377 {
6378 fixed_t dist, ndist, speedmul;
6379 angle_t vangle;
6380 fixed_t tx = dest->x;
6381 fixed_t ty = dest->y;
6382 fixed_t tz = dest->z + (dest->height/2); // Aim for center
6383 fixed_t xydist = P_AproxDistance(tx - source->x, ty - source->y);
6384
6385 if (!dest || dest->health <= 0 || !dest->player || !source->tracer)
6386 return;
6387
6388 // change angle
6389 source->angle = R_PointToAngle2(source->x, source->y, tx, ty);
6390
6391 // change slope
6392 dist = P_AproxDistance(xydist, tz - source->z);
6393
6394 if (dist < 1)
6395 dist = 1;
6396
6397 if (nightsgrab && source->movefactor)
6398 {
6399 source->movefactor += FRACUNIT/2;
6400
6401 if (dist < source->movefactor)
6402 {
6403 source->momx = source->momy = source->momz = 0;
6404 P_TeleportMove(source, tx, ty, tz);
6405 }
6406 else
6407 {
6408 vangle = R_PointToAngle2(source->z, 0, tz, xydist);
6409
6410 source->momx = FixedMul(FINESINE(vangle >> ANGLETOFINESHIFT), FixedMul(FINECOSINE(source->angle >> ANGLETOFINESHIFT), source->movefactor));
6411 source->momy = FixedMul(FINESINE(vangle >> ANGLETOFINESHIFT), FixedMul(FINESINE(source->angle >> ANGLETOFINESHIFT), source->movefactor));
6412 source->momz = FixedMul(FINECOSINE(vangle >> ANGLETOFINESHIFT), source->movefactor);
6413 }
6414 }
6415 else
6416 {
6417 if (nightsgrab)
6418 speedmul = P_AproxDistance(dest->momx, dest->momy) + FixedMul(8*FRACUNIT, source->scale);
6419 else
6420 speedmul = P_AproxDistance(dest->momx, dest->momy) + FixedMul(source->info->speed, source->scale);
6421
6422 source->momx = FixedMul(FixedDiv(tx - source->x, dist), speedmul);
6423 source->momy = FixedMul(FixedDiv(ty - source->y, dist), speedmul);
6424 source->momz = FixedMul(FixedDiv(tz - source->z, dist), speedmul);
6425 }
6426
6427 // Instead of just unsetting NOCLIP like an idiot, let's check the distance to our target.
6428 ndist = P_AproxDistance(P_AproxDistance(tx - (source->x+source->momx),
6429 ty - (source->y+source->momy)),
6430 tz - (source->z+source->momz));
6431
6432 if (ndist > dist) // gone past our target
6433 {
6434 // place us on top of them then.
6435 source->momx = source->momy = source->momz = 0;
6436 P_UnsetThingPosition(source);
6437 source->x = tx;
6438 source->y = ty;
6439 source->z = tz;
6440 P_SetThingPosition(source);
6441 }
6442 }
6443
P_NightsItemChase(mobj_t * thing)6444 static void P_NightsItemChase(mobj_t *thing)
6445 {
6446 if (!thing->tracer)
6447 {
6448 P_SetTarget(&thing->tracer, NULL);
6449 thing->flags2 &= ~MF2_NIGHTSPULL;
6450 thing->movefactor = 0;
6451 return;
6452 }
6453
6454 if (!thing->tracer->player)
6455 return;
6456
6457 P_Attract(thing, thing->tracer, true);
6458 }
6459
6460 //
6461 // P_MaceRotate
6462 // Spins a hnext-chain of objects around its centerpoint, side to side or periodically.
6463 //
P_MaceRotate(mobj_t * center,INT32 baserot,INT32 baseprevrot)6464 void P_MaceRotate(mobj_t *center, INT32 baserot, INT32 baseprevrot)
6465 {
6466 TVector unit_lengthways, unit_sideways, pos_lengthways, pos_sideways;
6467 TVector *res;
6468 fixed_t radius, dist, zstore;
6469 angle_t fa;
6470 boolean dosound = false;
6471 mobj_t *mobj = center->hnext, *hnext = NULL;
6472
6473 INT32 lastthreshold = -1; // needs to never be equal at start of loop
6474 fixed_t lastfriction = INT32_MIN; // ditto; almost certainly never, but...
6475
6476 INT32 rot;
6477 INT32 prevrot;
6478
6479 dist = pos_sideways[0] = pos_sideways[1] = pos_sideways[2] = pos_sideways[3] = unit_sideways[3] =\
6480 pos_lengthways[0] = pos_lengthways[1] = pos_lengthways[2] = pos_lengthways[3] = 0;
6481
6482 while (mobj)
6483 {
6484 if (P_MobjWasRemoved(mobj) || !mobj->health)
6485 {
6486 mobj = mobj->hnext;
6487 continue;
6488 }
6489
6490 mobj->momx = mobj->momy = mobj->momz = 0;
6491
6492 if (mobj->threshold != lastthreshold
6493 || mobj->friction != lastfriction)
6494 {
6495 rot = (baserot + mobj->threshold) & FINEMASK;
6496 prevrot = (baseprevrot + mobj->threshold) & FINEMASK;
6497
6498 pos_lengthways[0] = pos_lengthways[1] = pos_lengthways[2] = pos_lengthways[3] = 0;
6499
6500 dist = ((mobj->info->speed) ? mobj->info->speed : mobjinfo[MT_SMALLMACECHAIN].speed);
6501 dist = ((center->scale == FRACUNIT) ? dist : FixedMul(dist, center->scale));
6502
6503 fa = (FixedAngle(center->movefactor*FRACUNIT) >> ANGLETOFINESHIFT);
6504 radius = FixedMul(dist, FINECOSINE(fa));
6505 unit_lengthways[1] = -FixedMul(dist, FINESINE(fa));
6506 unit_lengthways[3] = FRACUNIT;
6507
6508 // Swinging Chain.
6509 if (center->flags2 & MF2_STRONGBOX)
6510 {
6511 fixed_t swingmag = FixedMul(FINECOSINE(rot), center->lastlook << FRACBITS);
6512 fixed_t prevswingmag = FINECOSINE(prevrot);
6513
6514 if ((prevswingmag > 0) != (swingmag > 0)) // just passed its lowest point
6515 dosound = true;
6516
6517 fa = ((FixedAngle(swingmag) >> ANGLETOFINESHIFT) + mobj->friction) & FINEMASK;
6518
6519 unit_lengthways[0] = FixedMul(FINESINE(fa), -radius);
6520 unit_lengthways[2] = FixedMul(FINECOSINE(fa), -radius);
6521 }
6522 // Rotating Chain.
6523 else
6524 {
6525 angle_t prevfa = (prevrot + mobj->friction) & FINEMASK;
6526 fa = (rot + mobj->friction) & FINEMASK;
6527
6528 if (!(prevfa > (FINEMASK/2)) && (fa > (FINEMASK/2))) // completed a full swing
6529 dosound = true;
6530
6531 unit_lengthways[0] = FixedMul(FINECOSINE(fa), radius);
6532 unit_lengthways[2] = FixedMul(FINESINE(fa), radius);
6533 }
6534
6535 // Calculate the angle matrixes for the link.
6536 res = VectorMatrixMultiply(unit_lengthways, *RotateXMatrix(center->threshold << ANGLETOFINESHIFT));
6537 M_Memcpy(&unit_lengthways, res, sizeof(unit_lengthways));
6538 res = VectorMatrixMultiply(unit_lengthways, *RotateZMatrix(center->angle));
6539 M_Memcpy(&unit_lengthways, res, sizeof(unit_lengthways));
6540
6541 lastthreshold = mobj->threshold;
6542 lastfriction = mobj->friction;
6543 }
6544
6545 if (dosound && (mobj->flags2 & MF2_BOSSNOTRAP))
6546 {
6547 S_StartSound(mobj, mobj->info->activesound);
6548 dosound = false;
6549 }
6550
6551 if (pos_sideways[3] != mobj->movefactor)
6552 {
6553 if (!unit_sideways[3])
6554 {
6555 unit_sideways[1] = dist;
6556 unit_sideways[0] = unit_sideways[2] = 0;
6557 unit_sideways[3] = FRACUNIT;
6558
6559 res = VectorMatrixMultiply(unit_sideways, *RotateXMatrix(center->threshold << ANGLETOFINESHIFT));
6560 M_Memcpy(&unit_sideways, res, sizeof(unit_sideways));
6561 res = VectorMatrixMultiply(unit_sideways, *RotateZMatrix(center->angle));
6562 M_Memcpy(&unit_sideways, res, sizeof(unit_sideways));
6563 }
6564
6565 if (pos_sideways[3] > mobj->movefactor)
6566 {
6567 do
6568 {
6569 pos_sideways[0] -= unit_sideways[0];
6570 pos_sideways[1] -= unit_sideways[1];
6571 pos_sideways[2] -= unit_sideways[2];
6572 }
6573 while ((--pos_sideways[3]) != mobj->movefactor);
6574 }
6575 else
6576 {
6577 do
6578 {
6579 pos_sideways[0] += unit_sideways[0];
6580 pos_sideways[1] += unit_sideways[1];
6581 pos_sideways[2] += unit_sideways[2];
6582 }
6583 while ((++pos_sideways[3]) != mobj->movefactor);
6584 }
6585 }
6586
6587 hnext = mobj->hnext; // just in case the mobj is removed
6588
6589 if (pos_lengthways[3] > mobj->movecount)
6590 {
6591 do
6592 {
6593 pos_lengthways[0] -= unit_lengthways[0];
6594 pos_lengthways[1] -= unit_lengthways[1];
6595 pos_lengthways[2] -= unit_lengthways[2];
6596 }
6597 while ((--pos_lengthways[3]) != mobj->movecount);
6598 }
6599 else if (pos_lengthways[3] < mobj->movecount)
6600 {
6601 do
6602 {
6603 pos_lengthways[0] += unit_lengthways[0];
6604 pos_lengthways[1] += unit_lengthways[1];
6605 pos_lengthways[2] += unit_lengthways[2];
6606 }
6607 while ((++pos_lengthways[3]) != mobj->movecount);
6608 }
6609
6610 P_UnsetThingPosition(mobj);
6611
6612 mobj->x = center->x;
6613 mobj->y = center->y;
6614 mobj->z = center->z;
6615
6616 // Add on the appropriate distances to the center's co-ordinates.
6617 if (pos_lengthways[3])
6618 {
6619 mobj->x += pos_lengthways[0];
6620 mobj->y += pos_lengthways[1];
6621 zstore = pos_lengthways[2] + pos_sideways[2];
6622 }
6623 else
6624 zstore = pos_sideways[2];
6625
6626 mobj->x += pos_sideways[0];
6627 mobj->y += pos_sideways[1];
6628
6629 // Cut the height to align the link with the axis.
6630 if (mobj->type == MT_SMALLMACECHAIN || mobj->type == MT_BIGMACECHAIN || mobj->type == MT_SMALLGRABCHAIN || mobj->type == MT_BIGGRABCHAIN)
6631 zstore -= P_MobjFlip(mobj)*mobj->height/4;
6632 else
6633 zstore -= P_MobjFlip(mobj)*mobj->height/2;
6634
6635 mobj->z += zstore;
6636
6637 #if 0 // toaster's testing flashie!
6638 if (!(mobj->movecount & 1) && !(leveltime & TICRATE)) // I had a brainfart and the flashing isn't exactly what I expected it to be, but it's actually much more useful.
6639 mobj->flags2 ^= MF2_DONTDRAW;
6640 #endif
6641
6642 P_SetThingPosition(mobj);
6643
6644 #if 0 // toaster's height-clipping dealie!
6645 if (!pos_lengthways[3] || P_MobjWasRemoved(mobj) || (mobj->flags & MF_NOCLIPHEIGHT))
6646 goto cont;
6647
6648 if ((fa = ((center->threshold & (FINEMASK/2)) << ANGLETOFINESHIFT)) > ANGLE_45 && fa < ANGLE_135) // only move towards center when the motion is towards/away from the ground, rather than alongside it
6649 goto cont;
6650
6651 if (mobj->subsector->sector->ffloors)
6652 P_AdjustMobjFloorZ_FFloors(mobj, mobj->subsector->sector, 2);
6653
6654 if (mobj->floorz > mobj->z)
6655 zstore = (mobj->floorz - zstore);
6656 else if (mobj->ceilingz < mobj->z)
6657 zstore = (mobj->ceilingz - mobj->height - zstore);
6658 else
6659 goto cont;
6660
6661 zstore = FixedDiv(zstore, dist); // Still needs work... scaling factor is wrong!
6662
6663 P_UnsetThingPosition(mobj);
6664
6665 mobj->x -= FixedMul(unit_lengthways[0], zstore);
6666 mobj->y -= FixedMul(unit_lengthways[1], zstore);
6667
6668 P_SetThingPosition(mobj);
6669
6670 cont:
6671 #endif
6672 mobj = hnext;
6673 }
6674 }
6675
P_ShieldLook(mobj_t * thing,shieldtype_t shield)6676 static boolean P_ShieldLook(mobj_t *thing, shieldtype_t shield)
6677 {
6678 if (!thing->target || thing->target->health <= 0 || !thing->target->player
6679 || (thing->target->player->powers[pw_shield] & SH_NOSTACK) == SH_NONE || thing->target->player->powers[pw_super]
6680 || thing->target->player->powers[pw_invulnerability] > 1)
6681 {
6682 P_RemoveMobj(thing);
6683 return false;
6684 }
6685
6686 // TODO: Make an MT_SHIELDORB which changes color/states to always match the appropriate shield,
6687 // instead of having completely seperate mobjtypes.
6688 if (!(shield & SH_FORCE))
6689 { // Regular shields check for themselves only
6690 if ((shieldtype_t)(thing->target->player->powers[pw_shield] & SH_NOSTACK) != shield)
6691 {
6692 P_RemoveMobj(thing);
6693 return false;
6694 }
6695 }
6696 else if (!(thing->target->player->powers[pw_shield] & SH_FORCE))
6697 { // Force shields check for any force shield
6698 P_RemoveMobj(thing);
6699 return false;
6700 }
6701
6702 if (shield & SH_FORCE && thing->movecount != (thing->target->player->powers[pw_shield] & SH_FORCEHP))
6703 {
6704 thing->movecount = (thing->target->player->powers[pw_shield] & SH_FORCEHP);
6705 if (thing->movecount < 1)
6706 {
6707 if (thing->info->painstate)
6708 P_SetMobjState(thing,thing->info->painstate);
6709 else
6710 thing->flags2 |= MF2_SHADOW;
6711 }
6712 else
6713 {
6714 if (thing->info->painstate)
6715 P_SetMobjState(thing,thing->info->spawnstate);
6716 else
6717 thing->flags2 &= ~MF2_SHADOW;
6718 }
6719 }
6720
6721 thing->flags |= MF_NOCLIPHEIGHT;
6722 thing->eflags = (thing->eflags & ~MFE_VERTICALFLIP)|(thing->target->eflags & MFE_VERTICALFLIP);
6723
6724 P_SetScale(thing, FixedMul(thing->target->scale, thing->target->player->shieldscale));
6725 thing->destscale = thing->scale;
6726 P_UnsetThingPosition(thing);
6727 thing->x = thing->target->x;
6728 thing->y = thing->target->y;
6729 if (thing->eflags & MFE_VERTICALFLIP)
6730 thing->z = thing->target->z + (thing->target->height - thing->height + FixedDiv(P_GetPlayerHeight(thing->target->player) - thing->target->height, 3*FRACUNIT)) - FixedMul(2*FRACUNIT, thing->target->scale);
6731 else
6732 thing->z = thing->target->z - (FixedDiv(P_GetPlayerHeight(thing->target->player) - thing->target->height, 3*FRACUNIT)) + FixedMul(2*FRACUNIT, thing->target->scale);
6733 P_SetThingPosition(thing);
6734 P_CheckPosition(thing, thing->x, thing->y);
6735
6736 if (P_MobjWasRemoved(thing))
6737 return false;
6738
6739 // if (thing->z < thing->floorz)
6740 // thing->z = thing->floorz;
6741
6742 return true;
6743 }
6744
6745 mobj_t *shields[MAXPLAYERS*2];
6746 INT32 numshields = 0;
6747
P_RunShields(void)6748 void P_RunShields(void)
6749 {
6750 INT32 i;
6751
6752 // run shields
6753 for (i = 0; i < numshields; i++)
6754 {
6755 P_ShieldLook(shields[i], shields[i]->threshold);
6756 P_SetTarget(&shields[i], NULL);
6757 }
6758 numshields = 0;
6759 }
6760
P_AddShield(mobj_t * thing)6761 static boolean P_AddShield(mobj_t *thing)
6762 {
6763 shieldtype_t shield = thing->threshold;
6764
6765 if (!thing->target || thing->target->health <= 0 || !thing->target->player
6766 || (thing->target->player->powers[pw_shield] & SH_NOSTACK) == SH_NONE || thing->target->player->powers[pw_super]
6767 || thing->target->player->powers[pw_invulnerability] > 1)
6768 {
6769 P_RemoveMobj(thing);
6770 return false;
6771 }
6772
6773 if (!(shield & SH_FORCE))
6774 { // Regular shields check for themselves only
6775 if ((shieldtype_t)(thing->target->player->powers[pw_shield] & SH_NOSTACK) != shield)
6776 {
6777 P_RemoveMobj(thing);
6778 return false;
6779 }
6780 }
6781 else if (!(thing->target->player->powers[pw_shield] & SH_FORCE))
6782 { // Force shields check for any force shield
6783 P_RemoveMobj(thing);
6784 return false;
6785 }
6786
6787 // Queue has been hit... why?!?
6788 if (numshields >= MAXPLAYERS*2)
6789 return P_ShieldLook(thing, thing->info->speed);
6790
6791 P_SetTarget(&shields[numshields++], thing);
6792 return true;
6793 }
6794
P_RunOverlays(void)6795 void P_RunOverlays(void)
6796 {
6797 // run overlays
6798 mobj_t *mo, *next = NULL;
6799 fixed_t destx,desty,zoffs;
6800
6801 for (mo = overlaycap; mo; mo = next)
6802 {
6803 I_Assert(!P_MobjWasRemoved(mo));
6804
6805 // grab next in chain, then unset the chain target
6806 next = mo->hnext;
6807 P_SetTarget(&mo->hnext, NULL);
6808
6809 if (!mo->target)
6810 continue;
6811
6812 if (P_MobjWasRemoved(mo->target))
6813 {
6814 P_RemoveMobj(mo);
6815 continue;
6816 }
6817
6818 if (!splitscreen /*&& rendermode != render_soft*/)
6819 {
6820 angle_t viewingangle;
6821
6822 if (players[displayplayer].awayviewtics && players[displayplayer].awayviewmobj != NULL && !P_MobjWasRemoved(players[displayplayer].awayviewmobj))
6823 viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayer].awayviewmobj->x, players[displayplayer].awayviewmobj->y);
6824 else if (!camera.chase && players[displayplayer].mo)
6825 viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, players[displayplayer].mo->x, players[displayplayer].mo->y);
6826 else
6827 viewingangle = R_PointToAngle2(mo->target->x, mo->target->y, camera.x, camera.y);
6828
6829 if (!(mo->state->frame & FF_ANIMATE) && mo->state->var1)
6830 viewingangle += ANGLE_180;
6831 destx = mo->target->x + P_ReturnThrustX(mo->target, viewingangle, FixedMul(FRACUNIT/4, mo->scale));
6832 desty = mo->target->y + P_ReturnThrustY(mo->target, viewingangle, FixedMul(FRACUNIT/4, mo->scale));
6833 }
6834 else
6835 {
6836 destx = mo->target->x;
6837 desty = mo->target->y;
6838 }
6839
6840 mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP) | (mo->target->eflags & MFE_VERTICALFLIP);
6841 mo->scale = mo->destscale = mo->target->scale;
6842 mo->angle = mo->target->angle + mo->movedir;
6843
6844 if (!(mo->state->frame & FF_ANIMATE))
6845 zoffs = FixedMul(((signed)mo->state->var2)*FRACUNIT, mo->scale);
6846 // if you're using FF_ANIMATE on an overlay,
6847 // then you're on your own.
6848 else
6849 zoffs = 0;
6850
6851 P_UnsetThingPosition(mo);
6852 mo->x = destx;
6853 mo->y = desty;
6854 mo->radius = mo->target->radius;
6855 mo->height = mo->target->height;
6856 if (mo->eflags & MFE_VERTICALFLIP)
6857 mo->z = (mo->target->z + mo->target->height - mo->height) - zoffs;
6858 else
6859 mo->z = mo->target->z + zoffs;
6860 if (mo->state->var1)
6861 P_SetUnderlayPosition(mo);
6862 else
6863 P_SetThingPosition(mo);
6864 P_CheckPosition(mo, mo->x, mo->y);
6865 }
6866 P_SetTarget(&overlaycap, NULL);
6867 }
6868
6869 // Called only when MT_OVERLAY thinks.
P_AddOverlay(mobj_t * thing)6870 static void P_AddOverlay(mobj_t *thing)
6871 {
6872 I_Assert(thing != NULL);
6873
6874 if (overlaycap == NULL)
6875 P_SetTarget(&overlaycap, thing);
6876 else {
6877 mobj_t *mo;
6878 for (mo = overlaycap; mo && mo->hnext; mo = mo->hnext)
6879 ;
6880
6881 I_Assert(mo != NULL);
6882 I_Assert(mo->hnext == NULL);
6883
6884 P_SetTarget(&mo->hnext, thing);
6885 }
6886 P_SetTarget(&thing->hnext, NULL);
6887 }
6888
6889 // Called only when MT_OVERLAY (or anything else in the overlaycap list) is removed.
6890 // Keeps the hnext list from corrupting.
P_RemoveOverlay(mobj_t * thing)6891 static void P_RemoveOverlay(mobj_t *thing)
6892 {
6893 mobj_t *mo;
6894 for (mo = overlaycap; mo; mo = mo->hnext)
6895 {
6896 if (mo->hnext != thing)
6897 continue;
6898
6899 P_SetTarget(&mo->hnext, thing->hnext);
6900 P_SetTarget(&thing->hnext, NULL);
6901 return;
6902 }
6903 }
6904
6905 void A_BossDeath(mobj_t *mo);
6906 // AI for the Koopa boss.
P_KoopaThinker(mobj_t * koopa)6907 static void P_KoopaThinker(mobj_t *koopa)
6908 {
6909 P_MobjCheckWater(koopa);
6910
6911 if (koopa->watertop > koopa->z + koopa->height + FixedMul(128*FRACUNIT, koopa->scale) && koopa->health > 0)
6912 {
6913 A_BossDeath(koopa);
6914 P_RemoveMobj(koopa);
6915 return;
6916 }
6917
6918 // Koopa moves ONLY on the X axis!
6919 if (koopa->threshold > 0)
6920 {
6921 koopa->threshold--;
6922 koopa->momx = FixedMul(FRACUNIT, koopa->scale);
6923
6924 if (!koopa->threshold)
6925 koopa->threshold = -TICRATE*2;
6926 }
6927 else if (koopa->threshold < 0)
6928 {
6929 koopa->threshold++;
6930 koopa->momx = FixedMul(-FRACUNIT, koopa->scale);
6931
6932 if (!koopa->threshold)
6933 koopa->threshold = TICRATE*2;
6934 }
6935 else
6936 koopa->threshold = TICRATE*2;
6937
6938 P_XYMovement(koopa);
6939
6940 if (P_RandomChance(FRACUNIT/32) && koopa->z <= koopa->floorz)
6941 koopa->momz = FixedMul(5*FRACUNIT, koopa->scale);
6942
6943 if (koopa->z > koopa->floorz)
6944 koopa->momz += FixedMul(FRACUNIT/4, koopa->scale);
6945
6946 if (P_RandomChance(FRACUNIT/64))
6947 {
6948 mobj_t *flame;
6949 flame = P_SpawnMobj(koopa->x - koopa->radius + FixedMul(5*FRACUNIT, koopa->scale), koopa->y, koopa->z + (P_RandomByte()<<(FRACBITS-2)), MT_KOOPAFLAME);
6950 flame->momx = -FixedMul(flame->info->speed, flame->scale);
6951 S_StartSound(flame, sfx_koopfr);
6952 }
6953 else if (P_RandomChance(5*FRACUNIT/256))
6954 {
6955 mobj_t *hammer;
6956 hammer = P_SpawnMobj(koopa->x - koopa->radius, koopa->y, koopa->z + koopa->height, MT_HAMMER);
6957 hammer->momx = FixedMul(-5*FRACUNIT, hammer->scale);
6958 hammer->momz = FixedMul(7*FRACUNIT, hammer->scale);
6959 }
6960 }
6961
6962 // Spawns and chains the minecart sides.
P_SpawnMinecartSegments(mobj_t * mobj,boolean mode)6963 static void P_SpawnMinecartSegments(mobj_t *mobj, boolean mode)
6964 {
6965 fixed_t x = mobj->x;
6966 fixed_t y = mobj->y;
6967 fixed_t z = mobj->z;
6968 mobj_t *prevseg = mobj;
6969 mobj_t *seg;
6970 UINT8 i;
6971
6972 for (i = 0; i < 4; i++)
6973 {
6974 seg = P_SpawnMobj(x, y, z, MT_MINECARTSEG);
6975 P_SetMobjState(seg, (statenum_t)(S_MINECARTSEG_FRONT + i));
6976 if (i >= 2)
6977 seg->extravalue1 = (i == 2) ? -18 : 18; // make -20/20 when papersprite projection fixed
6978 else
6979 {
6980 seg->extravalue2 = (i == 0) ? 24 : -24;
6981 seg->cusval = -90;
6982 }
6983 if (!mode)
6984 seg->frame &= ~FF_ANIMATE;
6985 P_SetTarget(&prevseg->tracer, seg);
6986 prevseg = seg;
6987 }
6988 }
6989
6990 // Updates the chained segments.
P_UpdateMinecartSegments(mobj_t * mobj)6991 static void P_UpdateMinecartSegments(mobj_t *mobj)
6992 {
6993 mobj_t *seg = mobj->tracer;
6994 fixed_t x = mobj->x;
6995 fixed_t y = mobj->y;
6996 fixed_t z = mobj->z;
6997 angle_t ang = mobj->angle;
6998 angle_t fa = (ang >> ANGLETOFINESHIFT) & FINEMASK;
6999 fixed_t c = FINECOSINE(fa);
7000 fixed_t s = FINESINE(fa);
7001 INT32 dx, dy;
7002 INT32 sang;
7003
7004 while (seg)
7005 {
7006 dx = seg->extravalue1;
7007 dy = seg->extravalue2;
7008 sang = seg->cusval;
7009 P_TeleportMove(seg, x + s*dx + c*dy, y - c*dx + s*dy, z);
7010 seg->angle = ang + FixedAngle(FRACUNIT*sang);
7011 seg->flags2 = (seg->flags2 & ~MF2_DONTDRAW) | (mobj->flags2 & MF2_DONTDRAW);
7012 seg = seg->tracer;
7013 }
7014 }
7015
P_HandleMinecartSegments(mobj_t * mobj)7016 void P_HandleMinecartSegments(mobj_t *mobj)
7017 {
7018 if (!mobj->tracer)
7019 P_SpawnMinecartSegments(mobj, (mobj->type == MT_MINECART));
7020 P_UpdateMinecartSegments(mobj);
7021 }
7022
P_PyreFlyBurn(mobj_t * mobj,fixed_t hoffs,INT16 vrange,mobjtype_t mobjtype,fixed_t momz)7023 static void P_PyreFlyBurn(mobj_t *mobj, fixed_t hoffs, INT16 vrange, mobjtype_t mobjtype, fixed_t momz)
7024 {
7025 angle_t fa = (FixedAngle(P_RandomKey(360)*FRACUNIT) >> ANGLETOFINESHIFT) & FINEMASK;
7026 fixed_t xoffs = FixedMul(FINECOSINE(fa), mobj->radius + hoffs);
7027 fixed_t yoffs = FixedMul(FINESINE(fa), mobj->radius + hoffs);
7028 fixed_t zoffs = P_RandomRange(-vrange, vrange)*FRACUNIT;
7029 mobj_t *particle = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, zoffs, mobjtype);
7030 particle->momz = momz;
7031 }
7032
P_MobjScaleThink(mobj_t * mobj)7033 static void P_MobjScaleThink(mobj_t *mobj)
7034 {
7035 fixed_t oldheight = mobj->height;
7036 UINT8 correctionType = 0; // Don't correct Z position, just gain height
7037
7038 if (mobj->flags & MF_NOCLIPHEIGHT || (mobj->z > mobj->floorz && mobj->z + mobj->height < mobj->ceilingz))
7039 correctionType = 1; // Correct Z position by centering
7040 else if (mobj->eflags & MFE_VERTICALFLIP)
7041 correctionType = 2; // Correct Z position by moving down
7042
7043 if (abs(mobj->scale - mobj->destscale) < mobj->scalespeed)
7044 P_SetScale(mobj, mobj->destscale);
7045 else if (mobj->scale < mobj->destscale)
7046 P_SetScale(mobj, mobj->scale + mobj->scalespeed);
7047 else if (mobj->scale > mobj->destscale)
7048 P_SetScale(mobj, mobj->scale - mobj->scalespeed);
7049
7050 if (correctionType == 1)
7051 mobj->z -= (mobj->height - oldheight)/2;
7052 else if (correctionType == 2)
7053 mobj->z -= mobj->height - oldheight;
7054
7055 if (mobj->scale == mobj->destscale)
7056 /// \todo Lua hook for "reached destscale"?
7057 switch (mobj->type)
7058 {
7059 default:
7060 break;
7061 }
7062 }
7063
P_MaceSceneryThink(mobj_t * mobj)7064 static void P_MaceSceneryThink(mobj_t *mobj)
7065 {
7066 angle_t oldmovedir = mobj->movedir;
7067
7068 // Always update movedir to prevent desyncing (in the traditional sense, not the netplay sense).
7069 mobj->movedir = (mobj->movedir + mobj->lastlook) & FINEMASK;
7070
7071 // If too far away and not deliberately spitting in the face of optimisation, don't think!
7072 if (!(mobj->flags2 & MF2_BOSSNOTRAP))
7073 {
7074 UINT8 i;
7075 // Quick! Look through players! Don't move unless a player is relatively close by.
7076 // The below is selected based on CEZ2's first room. I promise you it is a coincidence that it looks like the weed number.
7077 for (i = 0; i < MAXPLAYERS; ++i)
7078 if (playeringame[i] && players[i].mo
7079 && P_AproxDistance(P_AproxDistance(mobj->x - players[i].mo->x, mobj->y - players[i].mo->y), mobj->z - players[i].mo->z) < (4200 << FRACBITS))
7080 break; // Stop looking.
7081 if (i == MAXPLAYERS)
7082 {
7083 if (!(mobj->flags2 & MF2_BEYONDTHEGRAVE))
7084 {
7085 mobj_t *ref = mobj;
7086
7087 // stop/hide all your babies
7088 while ((ref = ref->hnext))
7089 {
7090 ref->eflags = (((ref->flags & MF_NOTHINK) ? 0 : 1)
7091 | ((ref->flags & MF_NOCLIPTHING) ? 0 : 2)
7092 | ((ref->flags2 & MF2_DONTDRAW) ? 0 : 4)); // oh my god this is nasty.
7093 ref->flags |= MF_NOTHINK|MF_NOCLIPTHING;
7094 ref->flags2 |= MF2_DONTDRAW;
7095 ref->momx = ref->momy = ref->momz = 0;
7096 }
7097
7098 mobj->flags2 |= MF2_BEYONDTHEGRAVE;
7099 }
7100 return; // don't make bubble!
7101 }
7102 else if (mobj->flags2 & MF2_BEYONDTHEGRAVE)
7103 {
7104 mobj_t *ref = mobj;
7105
7106 // start/show all your babies
7107 while ((ref = ref->hnext))
7108 {
7109 if (ref->eflags & 1)
7110 ref->flags &= ~MF_NOTHINK;
7111 if (ref->eflags & 2)
7112 ref->flags &= ~MF_NOCLIPTHING;
7113 if (ref->eflags & 4)
7114 ref->flags2 &= ~MF2_DONTDRAW;
7115 ref->eflags = 0; // le sign
7116 }
7117
7118 mobj->flags2 &= ~MF2_BEYONDTHEGRAVE;
7119 }
7120 }
7121
7122 // Okay, time to MOVE
7123 P_MaceRotate(mobj, mobj->movedir, oldmovedir);
7124 }
7125
P_DrownNumbersSceneryThink(mobj_t * mobj)7126 static boolean P_DrownNumbersSceneryThink(mobj_t *mobj)
7127 {
7128 if (!mobj->target)
7129 {
7130 P_RemoveMobj(mobj);
7131 return false;
7132 }
7133 if (!mobj->target->player || !(mobj->target->player->powers[pw_underwater] || mobj->target->player->powers[pw_spacetime]))
7134 {
7135 P_RemoveMobj(mobj);
7136 return false;
7137 }
7138 mobj->x = mobj->target->x;
7139 mobj->y = mobj->target->y;
7140
7141 mobj->destscale = mobj->target->destscale;
7142 P_SetScale(mobj, mobj->target->scale);
7143
7144 if (mobj->target->eflags & MFE_VERTICALFLIP)
7145 {
7146 mobj->z = mobj->target->z - FixedMul(16*FRACUNIT, mobj->target->scale) - mobj->height;
7147 if (mobj->target->player->pflags & PF_FLIPCAM)
7148 mobj->eflags |= MFE_VERTICALFLIP;
7149 }
7150 else
7151 mobj->z = mobj->target->z + (mobj->target->height) + FixedMul(8*FRACUNIT, mobj->target->scale); // Adjust height for height changes
7152
7153 if (mobj->threshold <= 35)
7154 mobj->flags2 |= MF2_DONTDRAW;
7155 else
7156 mobj->flags2 &= ~MF2_DONTDRAW;
7157 if (mobj->threshold <= 30)
7158 mobj->threshold = 40;
7159 mobj->threshold--;
7160 return true;
7161 }
7162
P_FlameJetSceneryThink(mobj_t * mobj)7163 static void P_FlameJetSceneryThink(mobj_t *mobj)
7164 {
7165 mobj_t *flame;
7166 fixed_t strength;
7167
7168 if (!(mobj->flags2 & MF2_FIRING))
7169 return;
7170
7171 if ((leveltime & 3) != 0)
7172 return;
7173
7174 // Wave the flames back and forth. Reactiontime determines which direction it's going.
7175 if (mobj->fuse <= -16)
7176 mobj->reactiontime = 1;
7177 else if (mobj->fuse >= 16)
7178 mobj->reactiontime = 0;
7179
7180 if (mobj->reactiontime)
7181 mobj->fuse += 2;
7182 else
7183 mobj->fuse -= 2;
7184
7185 flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME);
7186 P_SetMobjState(flame, S_FLAMEJETFLAME4);
7187
7188 flame->angle = mobj->angle;
7189
7190 if (mobj->flags2 & MF2_AMBUSH) // Wave up and down instead of side-to-side
7191 flame->momz = mobj->fuse << (FRACBITS - 2);
7192 else
7193 flame->angle += FixedAngle(mobj->fuse<<FRACBITS);
7194
7195 strength = 20*FRACUNIT;
7196 strength -= ((20*FRACUNIT)/16)*mobj->movedir;
7197
7198 P_InstaThrust(flame, flame->angle, strength);
7199 S_StartSound(flame, sfx_fire);
7200 }
7201
P_VerticalFlameJetSceneryThink(mobj_t * mobj)7202 static void P_VerticalFlameJetSceneryThink(mobj_t *mobj)
7203 {
7204 mobj_t *flame;
7205 fixed_t strength;
7206
7207 if (!(mobj->flags2 & MF2_FIRING))
7208 return;
7209
7210 if ((leveltime & 3) != 0)
7211 return;
7212
7213 // Wave the flames back and forth. Reactiontime determines which direction it's going.
7214 if (mobj->fuse <= -16)
7215 mobj->reactiontime = 1;
7216 else if (mobj->fuse >= 16)
7217 mobj->reactiontime = 0;
7218
7219 if (mobj->reactiontime)
7220 mobj->fuse++;
7221 else
7222 mobj->fuse--;
7223
7224 flame = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_FLAMEJETFLAME);
7225
7226 strength = 20*FRACUNIT;
7227 strength -= ((20*FRACUNIT)/16)*mobj->movedir;
7228
7229 // If deaf'd, the object spawns on the ceiling.
7230 if (mobj->flags2 & MF2_AMBUSH)
7231 {
7232 mobj->z = mobj->ceilingz - mobj->height;
7233 flame->momz = -strength;
7234 }
7235 else
7236 {
7237 flame->momz = strength;
7238 P_SetMobjState(flame, S_FLAMEJETFLAME7);
7239 }
7240 P_InstaThrust(flame, mobj->angle, FixedDiv(mobj->fuse*FRACUNIT, 3*FRACUNIT));
7241 S_StartSound(flame, sfx_fire);
7242 }
7243
P_ParticleGenSceneryThink(mobj_t * mobj)7244 static boolean P_ParticleGenSceneryThink(mobj_t *mobj)
7245 {
7246 if (!mobj->lastlook)
7247 return false;
7248
7249 if (!mobj->threshold)
7250 return false;
7251
7252 if (--mobj->fuse <= 0)
7253 {
7254 INT32 i = 0;
7255 mobj_t *spawn;
7256 fixed_t bottomheight, topheight;
7257 INT32 type = mobj->threshold, line = mobj->cvmem;
7258
7259 mobj->fuse = (tic_t)mobj->reactiontime;
7260
7261 bottomheight = lines[line].frontsector->floorheight;
7262 topheight = lines[line].frontsector->ceilingheight - mobjinfo[(mobjtype_t)type].height;
7263
7264 if (mobj->waterbottom != bottomheight || mobj->watertop != topheight)
7265 {
7266 if (mobj->movefactor && (topheight > bottomheight))
7267 mobj->health = (tic_t)(FixedDiv((topheight - bottomheight), abs(mobj->movefactor)) >> FRACBITS);
7268 else
7269 mobj->health = 0;
7270
7271 mobj->z = ((mobj->flags2 & MF2_OBJECTFLIP) ? topheight : bottomheight);
7272 }
7273
7274 if (!mobj->health)
7275 return false;
7276
7277 for (i = 0; i < mobj->lastlook; i++)
7278 {
7279 spawn = P_SpawnMobj(
7280 mobj->x + FixedMul(FixedMul(mobj->friction, mobj->scale), FINECOSINE(mobj->angle >> ANGLETOFINESHIFT)),
7281 mobj->y + FixedMul(FixedMul(mobj->friction, mobj->scale), FINESINE(mobj->angle >> ANGLETOFINESHIFT)),
7282 mobj->z,
7283 (mobjtype_t)mobj->threshold);
7284 P_SetScale(spawn, mobj->scale);
7285 spawn->momz = FixedMul(mobj->movefactor, spawn->scale);
7286 spawn->destscale = spawn->scale/100;
7287 spawn->scalespeed = spawn->scale/mobj->health;
7288 spawn->tics = (tic_t)mobj->health;
7289 spawn->flags2 |= (mobj->flags2 & MF2_OBJECTFLIP);
7290 spawn->angle += P_RandomKey(36)*ANG10; // irrelevant for default objects but might make sense for some custom ones
7291
7292 mobj->angle += mobj->movedir;
7293 }
7294
7295 mobj->angle += (angle_t)mobj->movecount;
7296 }
7297
7298 return true;
7299 }
7300
P_RosySceneryThink(mobj_t * mobj)7301 static void P_RosySceneryThink(mobj_t *mobj)
7302 {
7303 UINT8 i;
7304 fixed_t pdist = 1700*mobj->scale, work, actualwork;
7305 player_t *player = NULL;
7306 statenum_t stat = (mobj->state - states);
7307 for (i = 0; i < MAXPLAYERS; i++)
7308 {
7309 if (!playeringame[i])
7310 continue;
7311 if (!players[i].mo)
7312 continue;
7313 if (players[i].bot)
7314 continue;
7315 if (!players[i].mo->health)
7316 continue;
7317 actualwork = work = FixedHypot(mobj->x - players[i].mo->x, mobj->y - players[i].mo->y);
7318 if (player)
7319 {
7320 if (players[i].skin == 0 || players[i].skin == 5)
7321 work = (2*work)/3;
7322 if (work >= pdist)
7323 continue;
7324 }
7325 pdist = actualwork;
7326 player = &players[i];
7327 }
7328
7329 if (stat == S_ROSY_JUMP || stat == S_ROSY_PAIN)
7330 {
7331 if (P_IsObjectOnGround(mobj))
7332 {
7333 mobj->momx = mobj->momy = 0;
7334 if (player && mobj->cvmem < (-2*TICRATE))
7335 stat = S_ROSY_UNHAPPY;
7336 else
7337 stat = S_ROSY_WALK;
7338 P_SetMobjState(mobj, stat);
7339 }
7340 else if (P_MobjFlip(mobj)*mobj->momz < 0)
7341 mobj->frame = mobj->state->frame + mobj->state->var1;
7342 }
7343
7344 if (!player)
7345 {
7346 if ((stat < S_ROSY_IDLE1 || stat > S_ROSY_IDLE4) && stat != S_ROSY_JUMP)
7347 {
7348 mobj->momx = mobj->momy = 0;
7349 P_SetMobjState(mobj, S_ROSY_IDLE1);
7350 }
7351 }
7352 else
7353 {
7354 boolean dojump = false, targonground, love, makeheart = false;
7355 if (mobj->target != player->mo)
7356 P_SetTarget(&mobj->target, player->mo);
7357 // Tatsuru: Don't try to hug them if they're above or below you!
7358 targonground = (P_IsObjectOnGround(mobj->target) && (player->panim == PA_IDLE || player->panim == PA_WALK || player->panim == PA_RUN) && player->mo->z == mobj->z);
7359 love = (player->skin == 0 || player->skin == 5);
7360
7361 switch (stat)
7362 {
7363 case S_ROSY_IDLE1:
7364 case S_ROSY_IDLE2:
7365 case S_ROSY_IDLE3:
7366 case S_ROSY_IDLE4:
7367 dojump = true;
7368 break;
7369 case S_ROSY_JUMP:
7370 case S_ROSY_PAIN:
7371 // handled above
7372 break;
7373 case S_ROSY_WALK:
7374 {
7375 fixed_t x = mobj->x, y = mobj->y, z = mobj->z;
7376 angle_t angletoplayer = R_PointToAngle2(x, y, mobj->target->x, mobj->target->y);
7377 boolean allowed = P_TryMove(mobj, mobj->target->x, mobj->target->y, false);
7378
7379 P_UnsetThingPosition(mobj);
7380 mobj->x = x;
7381 mobj->y = y;
7382 mobj->z = z;
7383 P_SetThingPosition(mobj);
7384
7385 if (allowed)
7386 {
7387 fixed_t mom, max;
7388 P_Thrust(mobj, angletoplayer, (3*FRACUNIT) >> 1);
7389 mom = FixedHypot(mobj->momx, mobj->momy);
7390 max = pdist;
7391 if ((--mobj->extravalue1) <= 0)
7392 {
7393 if (++mobj->frame > mobj->state->frame + mobj->state->var1)
7394 mobj->frame = mobj->state->frame;
7395 if (mom > 12*mobj->scale)
7396 mobj->extravalue1 = 2;
7397 else if (mom > 6*mobj->scale)
7398 mobj->extravalue1 = 3;
7399 else
7400 mobj->extravalue1 = 4;
7401 }
7402 if (max < (mobj->radius + mobj->target->radius))
7403 {
7404 mobj->momx = mobj->target->player->cmomx;
7405 mobj->momy = mobj->target->player->cmomy;
7406 if ((mobj->cvmem > TICRATE && !player->exiting) || !targonground)
7407 P_SetMobjState(mobj, (stat = S_ROSY_STND));
7408 else
7409 {
7410 mobj->target->momx = mobj->momx;
7411 mobj->target->momy = mobj->momy;
7412 P_SetMobjState(mobj, (stat = S_ROSY_HUG));
7413 S_StartSound(mobj, sfx_cdpcm6);
7414 mobj->angle = angletoplayer;
7415 }
7416 }
7417 else
7418 {
7419 max /= 3;
7420 if (max > 30*mobj->scale)
7421 max = 30*mobj->scale;
7422 if (mom > max && max > mobj->scale)
7423 {
7424 max = FixedDiv(max, mom);
7425 mobj->momx = FixedMul(mobj->momx, max);
7426 mobj->momy = FixedMul(mobj->momy, max);
7427 }
7428 if (abs(mobj->momx) > mobj->scale || abs(mobj->momy) > mobj->scale)
7429 mobj->angle = R_PointToAngle2(0, 0, mobj->momx, mobj->momy);
7430 }
7431 }
7432 else
7433 dojump = true;
7434 }
7435 break;
7436 case S_ROSY_HUG:
7437 if (targonground)
7438 {
7439 player->pflags |= PF_STASIS;
7440 if (mobj->cvmem < 5*TICRATE)
7441 mobj->cvmem++;
7442 if (love && !(leveltime & 7))
7443 makeheart = true;
7444 }
7445 else
7446 {
7447 if (mobj->cvmem < (love ? 5*TICRATE : 0))
7448 {
7449 P_SetMobjState(mobj, (stat = S_ROSY_PAIN));
7450 S_StartSound(mobj, sfx_cdpcm7);
7451 }
7452 else
7453 P_SetMobjState(mobj, (stat = S_ROSY_JUMP));
7454 var1 = var2 = 0;
7455 A_DoNPCPain(mobj);
7456 mobj->cvmem -= TICRATE;
7457 }
7458 break;
7459 case S_ROSY_STND:
7460 if ((pdist > (mobj->radius + mobj->target->radius + 3*(mobj->scale + mobj->target->scale))))
7461 P_SetMobjState(mobj, (stat = S_ROSY_WALK));
7462 else if (!targonground)
7463 ;
7464 else
7465 {
7466 if (love && !(leveltime & 15))
7467 makeheart = true;
7468 if (player->exiting || --mobj->cvmem < TICRATE)
7469 {
7470 P_SetMobjState(mobj, (stat = S_ROSY_HUG));
7471 S_StartSound(mobj, sfx_cdpcm6);
7472 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
7473 mobj->target->momx = mobj->momx;
7474 mobj->target->momy = mobj->momy;
7475 }
7476 }
7477 break;
7478 case S_ROSY_UNHAPPY:
7479 default:
7480 break;
7481 }
7482
7483 if (stat == S_ROSY_HUG)
7484 {
7485 if (player->panim != PA_IDLE)
7486 P_SetPlayerMobjState(mobj->target, S_PLAY_STND);
7487 player->pflags |= PF_STASIS;
7488 }
7489
7490 if (dojump)
7491 {
7492 P_SetMobjState(mobj, S_ROSY_JUMP);
7493 mobj->z += P_MobjFlip(mobj);
7494 mobj->momx = mobj->momy = 0;
7495 P_SetObjectMomZ(mobj, 6 << FRACBITS, false);
7496 S_StartSound(mobj, sfx_cdfm02);
7497 }
7498
7499 if (makeheart)
7500 {
7501 mobj_t *cdlhrt = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_CDLHRT);
7502 cdlhrt->destscale = (5*mobj->scale) >> 4;
7503 P_SetScale(cdlhrt, cdlhrt->destscale);
7504 cdlhrt->fuse = (5*TICRATE) >> 1;
7505 cdlhrt->momz = mobj->scale;
7506 P_SetTarget(&cdlhrt->target, mobj);
7507 cdlhrt->extravalue1 = mobj->x;
7508 cdlhrt->extravalue2 = mobj->y;
7509 }
7510 }
7511 }
7512
P_MobjSceneryThink(mobj_t * mobj)7513 static void P_MobjSceneryThink(mobj_t *mobj)
7514 {
7515 if (LUAh_MobjThinker(mobj))
7516 return;
7517 if (P_MobjWasRemoved(mobj))
7518 return;
7519
7520 if ((mobj->flags2 & MF2_SHIELD) && !P_AddShield(mobj))
7521 return;
7522
7523 switch (mobj->type)
7524 {
7525 case MT_BOSSJUNK:
7526 mobj->flags2 ^= MF2_DONTDRAW;
7527 break;
7528 case MT_MACEPOINT:
7529 case MT_CHAINMACEPOINT:
7530 case MT_SPRINGBALLPOINT:
7531 case MT_CHAINPOINT:
7532 case MT_FIREBARPOINT:
7533 case MT_CUSTOMMACEPOINT:
7534 case MT_HIDDEN_SLING:
7535 P_MaceSceneryThink(mobj);
7536 break;
7537 case MT_HOOP:
7538 if (mobj->fuse > 1)
7539 P_MoveHoop(mobj);
7540 else if (mobj->fuse == 1)
7541 mobj->movecount = 1;
7542
7543 if (mobj->movecount)
7544 {
7545 mobj->fuse++;
7546
7547 if (mobj->fuse > 32)
7548 {
7549 // Don't kill the hoop center. For the sake of respawning.
7550 //if (mobj->target)
7551 // P_RemoveMobj(mobj->target);
7552
7553 P_RemoveMobj(mobj);
7554 }
7555 }
7556 else
7557 mobj->fuse--;
7558 return;
7559 case MT_NIGHTSPARKLE:
7560 if (mobj->tics != -1)
7561 {
7562 mobj->tics--;
7563
7564 // you can cycle through multiple states in a tic
7565 if (!mobj->tics)
7566 if (!P_SetMobjState(mobj, mobj->state->nextstate))
7567 return; // freed itself
7568 }
7569
7570 P_UnsetThingPosition(mobj);
7571 mobj->x += mobj->momx;
7572 mobj->y += mobj->momy;
7573 mobj->z += mobj->momz;
7574 P_SetThingPosition(mobj);
7575 return;
7576 case MT_NIGHTSLOOPHELPER:
7577 if (--mobj->tics <= 0)
7578 P_RemoveMobj(mobj);
7579
7580 // Don't touch my fuse!
7581 return;
7582 case MT_OVERLAY:
7583 if (!mobj->target)
7584 {
7585 P_RemoveMobj(mobj);
7586 return;
7587 }
7588 else
7589 P_AddOverlay(mobj);
7590 break;
7591 case MT_PITY_ORB:
7592 case MT_WHIRLWIND_ORB:
7593 case MT_ARMAGEDDON_ORB:
7594 if (!(mobj->flags2 & MF2_SHIELD))
7595 return;
7596 break;
7597 case MT_ATTRACT_ORB:
7598 if (!(mobj->flags2 & MF2_SHIELD))
7599 return;
7600 if (/*(mobj->target) -- the following is implicit by P_AddShield
7601 && (mobj->target->player)
7602 && */ (mobj->target->player->homing) && (mobj->target->player->pflags & PF_SHIELDABILITY))
7603 {
7604 P_SetMobjState(mobj, mobj->info->painstate);
7605 mobj->tics++;
7606 }
7607 break;
7608 case MT_ELEMENTAL_ORB:
7609 if (!(mobj->flags2 & MF2_SHIELD))
7610 return;
7611 if (mobj->tracer
7612 /* && mobj->target -- the following is implicit by P_AddShield
7613 && mobj->target->player
7614 && (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL */
7615 && mobj->target->player->pflags & PF_SHIELDABILITY
7616 && ((statenum_t)(mobj->tracer->state - states) < mobj->info->raisestate
7617 || (mobj->tracer->state->nextstate < mobj->info->raisestate && mobj->tracer->tics == 1)))
7618 {
7619 P_SetMobjState(mobj, mobj->info->painstate);
7620 mobj->tics++;
7621 P_SetMobjState(mobj->tracer, mobj->info->raisestate);
7622 mobj->tracer->tics++;
7623 }
7624 break;
7625 case MT_FORCE_ORB:
7626 if (!(mobj->flags2 & MF2_SHIELD))
7627 return;
7628 if (/*
7629 && mobj->target -- the following is implicit by P_AddShield
7630 && mobj->target->player
7631 && (mobj->target->player->powers[pw_shield] & SH_FORCE)
7632 && */ (mobj->target->player->pflags & PF_SHIELDABILITY))
7633 {
7634 mobj_t *whoosh = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_GHOST); // done here so the offset is correct
7635 P_SetMobjState(whoosh, mobj->info->raisestate);
7636 whoosh->destscale = whoosh->scale << 1;
7637 whoosh->scalespeed = FixedMul(whoosh->scalespeed, whoosh->scale);
7638 whoosh->height = 38*whoosh->scale;
7639 whoosh->fuse = 10;
7640 whoosh->flags |= MF_NOCLIPHEIGHT;
7641 whoosh->momz = mobj->target->momz; // Stay reasonably centered for a few frames
7642 mobj->target->player->pflags &= ~PF_SHIELDABILITY; // prevent eternal whoosh
7643 }
7644 /* FALLTHRU */
7645 case MT_FLAMEAURA_ORB:
7646 if (!(mobj->flags2 & MF2_SHIELD))
7647 return;
7648 if ((statenum_t)(mobj->state - states) < mobj->info->painstate)
7649 mobj->angle = mobj->target->angle; // implicitly okay because of P_AddShield
7650 if (mobj->tracer
7651 /* && mobj->target -- the following is implicit by P_AddShield
7652 && mobj->target->player
7653 && (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_FLAMEAURA */
7654 && mobj->target->player->pflags & PF_SHIELDABILITY
7655 && ((statenum_t)(mobj->tracer->state - states) < mobj->info->raisestate
7656 || (mobj->tracer->state->nextstate < mobj->info->raisestate && mobj->tracer->tics == 1)))
7657 {
7658 P_SetMobjState(mobj, mobj->info->painstate);
7659 mobj->tics++;
7660 P_SetMobjState(mobj->tracer, mobj->info->raisestate);
7661 mobj->tracer->tics++;
7662 }
7663 break;
7664 case MT_BUBBLEWRAP_ORB:
7665 if (!(mobj->flags2 & MF2_SHIELD))
7666 return;
7667 if (mobj->tracer
7668 /* && mobj->target -- the following is implicit by P_AddShield
7669 && mobj->target->player
7670 && (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP */
7671 )
7672 {
7673 if (mobj->target->player->pflags & PF_SHIELDABILITY
7674 && ((statenum_t)(mobj->state - states) < mobj->info->painstate
7675 || (mobj->state->nextstate < mobj->info->painstate && mobj->tics == 1)))
7676 {
7677 P_SetMobjState(mobj, mobj->info->painstate);
7678 mobj->tics++;
7679 P_SetMobjState(mobj->tracer, mobj->info->raisestate);
7680 mobj->tracer->tics++;
7681 }
7682 else if (mobj->target->eflags & MFE_JUSTHITFLOOR
7683 && (statenum_t)(mobj->state - states) == mobj->info->painstate)
7684 {
7685 P_SetMobjState(mobj, mobj->info->painstate + 1);
7686 mobj->tics++;
7687 P_SetMobjState(mobj->tracer, mobj->info->raisestate + 1);
7688 mobj->tracer->tics++;
7689 }
7690 }
7691 break;
7692 case MT_THUNDERCOIN_ORB:
7693 if (!(mobj->flags2 & MF2_SHIELD))
7694 return;
7695 if (mobj->tracer
7696 /* && mobj->target -- the following is implicit by P_AddShield
7697 && mobj->target->player
7698 && (mobj->target->player->powers[pw_shield] & SH_NOSTACK) == SH_THUNDERCOIN */
7699 && (mobj->target->player->pflags & PF_SHIELDABILITY))
7700 {
7701 P_SetMobjState(mobj, mobj->info->painstate);
7702 mobj->tics++;
7703 P_SetMobjState(mobj->tracer, mobj->info->raisestate);
7704 mobj->tracer->tics++;
7705 mobj->target->player->pflags &= ~PF_SHIELDABILITY; // prevent eternal spark
7706 }
7707 break;
7708 case MT_WATERDROP:
7709 P_SceneryCheckWater(mobj);
7710 if ((mobj->z <= mobj->floorz || mobj->z <= mobj->watertop)
7711 && mobj->health > 0)
7712 {
7713 mobj->health = 0;
7714 P_SetMobjState(mobj, mobj->info->deathstate);
7715 S_StartSound(mobj, mobj->info->deathsound + P_RandomKey(mobj->info->mass));
7716 return;
7717 }
7718 break;
7719 case MT_BUBBLES:
7720 P_SceneryCheckWater(mobj);
7721 break;
7722 case MT_SMALLBUBBLE:
7723 case MT_MEDIUMBUBBLE:
7724 case MT_EXTRALARGEBUBBLE: // start bubble dissipate
7725 P_SceneryCheckWater(mobj);
7726 if (P_MobjWasRemoved(mobj)) // bubble was removed by not being in water
7727 return;
7728 if (!(mobj->eflags & MFE_UNDERWATER)
7729 || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height >= mobj->ceilingz)
7730 || (mobj->eflags & MFE_VERTICALFLIP && mobj->z <= mobj->floorz)
7731 || (P_CheckDeathPitCollide(mobj))
7732 || --mobj->fuse <= 0) // Bubbles eventually dissipate if they can't reach the surface.
7733 {
7734 // no playing sound: no point; the object is being removed
7735 P_RemoveMobj(mobj);
7736 return;
7737 }
7738 break;
7739 case MT_LOCKON:
7740 if (!mobj->target)
7741 {
7742 P_RemoveMobj(mobj);
7743 return;
7744 }
7745
7746 mobj->flags2 &= ~MF2_DONTDRAW;
7747
7748 mobj->x = mobj->target->x;
7749 mobj->y = mobj->target->y;
7750
7751 mobj->eflags |= (mobj->target->eflags & MFE_VERTICALFLIP);
7752
7753 mobj->destscale = mobj->target->destscale;
7754 P_SetScale(mobj, mobj->target->scale);
7755
7756 if (!(mobj->eflags & MFE_VERTICALFLIP))
7757 mobj->z = mobj->target->z + mobj->target->height + FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->target->scale);
7758 else
7759 mobj->z = mobj->target->z - FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->target->scale) - mobj->height;
7760 break;
7761 case MT_LOCKONINF:
7762 if (!(mobj->flags2 & MF2_STRONGBOX))
7763 {
7764 mobj->threshold = mobj->z;
7765 mobj->flags2 |= MF2_STRONGBOX;
7766 }
7767 if (!(mobj->eflags & MFE_VERTICALFLIP))
7768 mobj->z = mobj->threshold + FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale);
7769 else
7770 mobj->z = mobj->threshold - FixedMul((16 + abs((signed)(leveltime % TICRATE) - TICRATE/2))*FRACUNIT, mobj->scale);
7771 break;
7772 case MT_DROWNNUMBERS:
7773 if (!P_DrownNumbersSceneryThink(mobj))
7774 return;
7775 break;
7776 case MT_FLAMEJET:
7777 P_FlameJetSceneryThink(mobj);
7778 break;
7779 case MT_VERTICALFLAMEJET:
7780 P_VerticalFlameJetSceneryThink(mobj);
7781 break;
7782 case MT_FLICKY_01_CENTER:
7783 case MT_FLICKY_02_CENTER:
7784 case MT_FLICKY_03_CENTER:
7785 case MT_FLICKY_04_CENTER:
7786 case MT_FLICKY_05_CENTER:
7787 case MT_FLICKY_06_CENTER:
7788 case MT_FLICKY_07_CENTER:
7789 case MT_FLICKY_08_CENTER:
7790 case MT_FLICKY_09_CENTER:
7791 case MT_FLICKY_10_CENTER:
7792 case MT_FLICKY_11_CENTER:
7793 case MT_FLICKY_12_CENTER:
7794 case MT_FLICKY_13_CENTER:
7795 case MT_FLICKY_14_CENTER:
7796 case MT_FLICKY_15_CENTER:
7797 case MT_FLICKY_16_CENTER:
7798 case MT_SECRETFLICKY_01_CENTER:
7799 case MT_SECRETFLICKY_02_CENTER:
7800 if (mobj->tracer && (mobj->flags & MF_NOCLIPTHING)
7801 && (mobj->flags & MF_GRENADEBOUNCE))
7802 // for now: only do this bounce routine if flicky is in-place. \todo allow in all movements
7803 {
7804 if (!(mobj->tracer->flags2 & MF2_OBJECTFLIP) && mobj->tracer->z <= mobj->tracer->floorz)
7805 mobj->tracer->momz = 7*FRACUNIT;
7806 else if ((mobj->tracer->flags2 & MF2_OBJECTFLIP) && mobj->tracer->z >= mobj->tracer->ceilingz - mobj->tracer->height)
7807 mobj->tracer->momz = -7*FRACUNIT;
7808 }
7809 break;
7810 case MT_SEED:
7811 if (P_MobjFlip(mobj)*mobj->momz < mobj->info->speed)
7812 mobj->momz = P_MobjFlip(mobj)*mobj->info->speed;
7813 break;
7814 case MT_ROCKCRUMBLE1:
7815 case MT_ROCKCRUMBLE2:
7816 case MT_ROCKCRUMBLE3:
7817 case MT_ROCKCRUMBLE4:
7818 case MT_ROCKCRUMBLE5:
7819 case MT_ROCKCRUMBLE6:
7820 case MT_ROCKCRUMBLE7:
7821 case MT_ROCKCRUMBLE8:
7822 case MT_ROCKCRUMBLE9:
7823 case MT_ROCKCRUMBLE10:
7824 case MT_ROCKCRUMBLE11:
7825 case MT_ROCKCRUMBLE12:
7826 case MT_ROCKCRUMBLE13:
7827 case MT_ROCKCRUMBLE14:
7828 case MT_ROCKCRUMBLE15:
7829 case MT_ROCKCRUMBLE16:
7830 case MT_WOODDEBRIS:
7831 case MT_BRICKDEBRIS:
7832 case MT_BROKENROBOT:
7833 if (mobj->z <= P_FloorzAtPos(mobj->x, mobj->y, mobj->z, mobj->height)
7834 && mobj->state != &states[mobj->info->deathstate])
7835 {
7836 P_SetMobjState(mobj, mobj->info->deathstate);
7837 return;
7838 }
7839 break;
7840 case MT_PARTICLEGEN:
7841 if (!P_ParticleGenSceneryThink(mobj))
7842 return;
7843 break;
7844 case MT_FSGNA:
7845 if (mobj->movedir)
7846 mobj->angle += mobj->movedir;
7847 break;
7848 case MT_ROSY:
7849 P_RosySceneryThink(mobj);
7850 break;
7851 case MT_CDLHRT:
7852 {
7853 if (mobj->cvmem < 24)
7854 mobj->cvmem++;
7855 mobj->movedir += ANG10;
7856 P_UnsetThingPosition(mobj);
7857 mobj->x = mobj->extravalue1 + P_ReturnThrustX(mobj, mobj->movedir, mobj->cvmem*mobj->scale);
7858 mobj->y = mobj->extravalue2 + P_ReturnThrustY(mobj, mobj->movedir, mobj->cvmem*mobj->scale);
7859 P_SetThingPosition(mobj);
7860
7861 if (!mobj->fuse)
7862 {
7863 if (!LUAh_MobjFuse(mobj))
7864 P_RemoveMobj(mobj);
7865 return;
7866 }
7867 if (mobj->fuse < 0)
7868 return;
7869 if (mobj->fuse < 6)
7870 mobj->frame = (mobj->frame & ~FF_TRANSMASK) | ((10 - (mobj->fuse*2)) << (FF_TRANSSHIFT));
7871 mobj->fuse--;
7872 }
7873 break;
7874 case MT_FINISHFLAG:
7875 {
7876 if (!mobj->target || mobj->target->player->playerstate == PST_DEAD || !cv_exitmove.value)
7877 {
7878 P_RemoveMobj(mobj);
7879 return;
7880 }
7881
7882 if (!camera.chase)
7883 mobj->flags2 |= MF2_DONTDRAW;
7884 else
7885 mobj->flags2 &= ~MF2_DONTDRAW;
7886
7887 P_UnsetThingPosition(mobj);
7888 {
7889 fixed_t radius = FixedMul(10*mobj->info->speed, mobj->target->scale);
7890 angle_t fa;
7891
7892 mobj->angle += FixedAngle(mobj->info->speed);
7893
7894 fa = mobj->angle >> ANGLETOFINESHIFT;
7895
7896 mobj->x = mobj->target->x + FixedMul(FINECOSINE(fa),radius);
7897 mobj->y = mobj->target->y + FixedMul(FINESINE(fa),radius);
7898 mobj->z = mobj->target->z + mobj->target->height/2;
7899 }
7900 P_SetThingPosition(mobj);
7901
7902 P_SetScale(mobj, mobj->target->scale);
7903 }
7904 break;
7905 case MT_VWREF:
7906 case MT_VWREB:
7907 {
7908 INT32 strength;
7909 ++mobj->movedir;
7910 mobj->frame &= ~FF_TRANSMASK;
7911 strength = min(mobj->fuse, (INT32)mobj->movedir)*3;
7912 if (strength < 10)
7913 mobj->frame |= ((10 - strength) << (FF_TRANSSHIFT));
7914 }
7915 /* FALLTHRU */
7916 default:
7917 if (mobj->fuse)
7918 { // Scenery object fuse! Very basic!
7919 mobj->fuse--;
7920 if (!mobj->fuse)
7921 {
7922 if (!LUAh_MobjFuse(mobj))
7923 P_RemoveMobj(mobj);
7924 return;
7925 }
7926 }
7927 break;
7928 }
7929
7930 P_SceneryThinker(mobj);
7931 }
7932
P_MobjPushableThink(mobj_t * mobj)7933 static boolean P_MobjPushableThink(mobj_t *mobj)
7934 {
7935 P_MobjCheckWater(mobj);
7936 P_PushableThinker(mobj);
7937
7938 // Extinguish fire objects in water. (Yes, it's extraordinarily rare to have a pushable flame object, but Brak uses such a case.)
7939 if ((mobj->flags & MF_FIRE) && !(mobj->eflags & MFE_TOUCHLAVA)
7940 && (mobj->eflags & (MFE_UNDERWATER | MFE_TOUCHWATER)))
7941 {
7942 P_KillMobj(mobj, NULL, NULL, 0);
7943 return false;
7944 }
7945
7946 return true;
7947 }
7948
P_MobjBossThink(mobj_t * mobj)7949 static boolean P_MobjBossThink(mobj_t *mobj)
7950 {
7951 if (LUAh_BossThinker(mobj))
7952 {
7953 if (P_MobjWasRemoved(mobj))
7954 return false;
7955 }
7956 else if (P_MobjWasRemoved(mobj))
7957 return false;
7958 else
7959 switch (mobj->type)
7960 {
7961 case MT_EGGMOBILE:
7962 if (mobj->health < mobj->info->damage + 1 && leveltime & 2)
7963 {
7964 fixed_t rad = mobj->radius >> FRACBITS;
7965 fixed_t hei = mobj->height >> FRACBITS;
7966 mobj_t *particle = P_SpawnMobjFromMobj(mobj,
7967 P_RandomRange(rad, -rad) << FRACBITS,
7968 P_RandomRange(rad, -rad) << FRACBITS,
7969 P_RandomRange(hei / 2, hei) << FRACBITS,
7970 MT_SMOKE);
7971 P_SetObjectMomZ(particle, 2 << FRACBITS, false);
7972 particle->momz += mobj->momz;
7973 }
7974 if (mobj->flags2 & MF2_SKULLFLY)
7975 #if 1
7976 P_SpawnGhostMobj(mobj);
7977 #else // all the way back from final demo... MT_THOK isn't even the same size anymore!
7978 {
7979 mobj_t *spawnmobj;
7980 spawnmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->info->painchance);
7981 P_SetTarget(&spawnmobj->target, mobj);
7982 spawnmobj->color = SKINCOLOR_GREY;
7983 }
7984 #endif
7985 P_Boss1Thinker(mobj);
7986 break;
7987 case MT_EGGMOBILE2:
7988 if (mobj->health < mobj->info->damage + 1 && leveltime & 2)
7989 {
7990 fixed_t rad = mobj->radius >> FRACBITS;
7991 fixed_t hei = mobj->height >> FRACBITS;
7992 mobj_t *particle = P_SpawnMobjFromMobj(mobj,
7993 P_RandomRange(rad, -rad) << FRACBITS,
7994 P_RandomRange(rad, -rad) << FRACBITS,
7995 P_RandomRange(hei/2, hei) << FRACBITS,
7996 MT_SMOKE);
7997 P_SetObjectMomZ(particle, 2 << FRACBITS, false);
7998 particle->momz += mobj->momz;
7999 }
8000 P_Boss2Thinker(mobj);
8001 break;
8002 case MT_EGGMOBILE3:
8003 if (mobj->health < mobj->info->damage + 1 && leveltime & 2)
8004 {
8005 fixed_t rad = mobj->radius >> FRACBITS;
8006 fixed_t hei = mobj->height >> FRACBITS;
8007 mobj_t *particle = P_SpawnMobjFromMobj(mobj,
8008 P_RandomRange(rad, -rad) << FRACBITS,
8009 P_RandomRange(rad, -rad) << FRACBITS,
8010 P_RandomRange(hei/2, hei) << FRACBITS,
8011 MT_SMOKE);
8012 P_SetObjectMomZ(particle, 2 << FRACBITS, false);
8013 particle->momz += mobj->momz;
8014 }
8015 P_Boss3Thinker(mobj);
8016 break;
8017 case MT_EGGMOBILE4:
8018 if (mobj->health < mobj->info->damage + 1 && leveltime & 2)
8019 {
8020 fixed_t rad = mobj->radius >> FRACBITS;
8021 fixed_t hei = mobj->height >> FRACBITS;
8022 mobj_t* particle = P_SpawnMobjFromMobj(mobj,
8023 P_RandomRange(rad, -rad) << FRACBITS,
8024 P_RandomRange(rad, -rad) << FRACBITS,
8025 P_RandomRange(hei/2, hei) << FRACBITS,
8026 MT_SMOKE);
8027 P_SetObjectMomZ(particle, 2 << FRACBITS, false);
8028 particle->momz += mobj->momz;
8029 }
8030 P_Boss4Thinker(mobj);
8031 break;
8032 case MT_FANG:
8033 P_Boss5Thinker(mobj);
8034 break;
8035 case MT_BLACKEGGMAN:
8036 P_Boss7Thinker(mobj);
8037 break;
8038 case MT_METALSONIC_BATTLE:
8039 P_Boss9Thinker(mobj);
8040 break;
8041 default: // Generic SOC-made boss
8042 if (mobj->flags2 & MF2_SKULLFLY)
8043 P_SpawnGhostMobj(mobj);
8044 P_GenericBossThinker(mobj);
8045 break;
8046 }
8047 if (mobj->flags2 & MF2_BOSSFLEE)
8048 {
8049 if (mobj->extravalue1)
8050 {
8051 if (!(--mobj->extravalue1))
8052 {
8053 if (mobj->target)
8054 {
8055 mobj->momz = FixedMul(FixedDiv(mobj->target->z - mobj->z, P_AproxDistance(mobj->x - mobj->target->x, mobj->y - mobj->target->y)), mobj->scale << 1);
8056 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
8057 }
8058 else
8059 mobj->momz = 8*mobj->scale;
8060 }
8061 else
8062 mobj->angle += mobj->movedir;
8063 }
8064 else if (mobj->target)
8065 P_InstaThrust(mobj, mobj->angle, FixedMul(12*FRACUNIT, mobj->scale));
8066 }
8067 if (mobj->type == MT_CYBRAKDEMON && !mobj->health)
8068 {
8069 if (!(mobj->tics & 1))
8070 {
8071 var1 = 2;
8072 var2 = 0;
8073 A_BossScream(mobj);
8074 }
8075 if (P_CheckDeathPitCollide(mobj))
8076 {
8077 P_RemoveMobj(mobj);
8078 return false;
8079 }
8080 if (mobj->momz && mobj->z + mobj->momz <= mobj->floorz)
8081 {
8082 S_StartSound(mobj, sfx_befall);
8083 if (mobj->state != states + S_CYBRAKDEMON_DIE8)
8084 P_SetMobjState(mobj, S_CYBRAKDEMON_DIE8);
8085 }
8086 }
8087 return true;
8088 }
8089
P_MobjDeadThink(mobj_t * mobj)8090 static boolean P_MobjDeadThink(mobj_t *mobj)
8091 {
8092 switch (mobj->type)
8093 {
8094 case MT_BLUESPHERE:
8095 if ((mobj->tics >> 2) + 1 > 0 && (mobj->tics >> 2) + 1 <= tr_trans60) // tr_trans50 through tr_trans90, shifting once every second frame
8096 mobj->frame = (NUMTRANSMAPS - ((mobj->tics >> 2) + 1)) << FF_TRANSSHIFT;
8097 else // tr_trans60 otherwise
8098 mobj->frame = tr_trans60 << FF_TRANSSHIFT;
8099 break;
8100 case MT_EGGCAPSULE:
8101 if (mobj->z <= mobj->floorz)
8102 {
8103 P_RemoveMobj(mobj);
8104 return false;
8105 }
8106 break;
8107 case MT_FAKEMOBILE:
8108 if (mobj->scale == mobj->destscale)
8109 {
8110 if (!mobj->fuse)
8111 {
8112 S_StartSound(mobj, sfx_s3k77);
8113 mobj->flags2 |= MF2_DONTDRAW;
8114 mobj->fuse = TICRATE;
8115 }
8116 return false;
8117 }
8118 if (!mobj->reactiontime)
8119 {
8120 if (P_RandomChance(FRACUNIT/2))
8121 mobj->movefactor = FRACUNIT;
8122 else
8123 mobj->movefactor = -FRACUNIT;
8124 if (P_RandomChance(FRACUNIT/2))
8125 mobj->movedir = ANG20;
8126 else
8127 mobj->movedir = -ANG20;
8128 mobj->reactiontime = 5;
8129 }
8130 mobj->momz += mobj->movefactor;
8131 mobj->angle += mobj->movedir;
8132 P_InstaThrust(mobj, mobj->angle, -mobj->info->speed);
8133 mobj->reactiontime--;
8134 break;
8135 case MT_EGGSHIELD:
8136 mobj->flags2 ^= MF2_DONTDRAW;
8137 break;
8138 case MT_EGGTRAP: // Egg Capsule animal release
8139 if (mobj->fuse > 0)// && mobj->fuse < TICRATE-(TICRATE/7))
8140 {
8141 INT32 i;
8142 fixed_t x, y, z;
8143 fixed_t ns;
8144 mobj_t* mo2;
8145 mobj_t* flicky;
8146
8147 z = mobj->subsector->sector->floorheight + FRACUNIT + (P_RandomKey(64) << FRACBITS);
8148 for (i = 0; i < 3; i++)
8149 {
8150 const angle_t fa = P_RandomKey(FINEANGLES) & FINEMASK;
8151 ns = 64*FRACUNIT;
8152 x = mobj->x + FixedMul(FINESINE(fa), ns);
8153 y = mobj->y + FixedMul(FINECOSINE(fa), ns);
8154
8155 mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
8156 P_SetMobjStateNF(mo2, S_XPLD_EGGTRAP); // so the flickies don't lose their target if they spawn
8157 ns = 4*FRACUNIT;
8158 mo2->momx = FixedMul(FINESINE(fa), ns);
8159 mo2->momy = FixedMul(FINECOSINE(fa), ns);
8160 mo2->angle = fa << ANGLETOFINESHIFT;
8161
8162 if (!i && !(mobj->fuse & 2))
8163 S_StartSound(mo2, mobj->info->deathsound);
8164
8165 flicky = P_InternalFlickySpawn(mo2, 0, 8*FRACUNIT, false, -1);
8166 if (!flicky)
8167 break;
8168
8169 P_SetTarget(&flicky->target, mo2);
8170 flicky->momx = mo2->momx;
8171 flicky->momy = mo2->momy;
8172 }
8173
8174 mobj->fuse--;
8175 }
8176 break;
8177 case MT_PLAYER:
8178 /// \todo Have the player's dead body completely finish its animation even if they've already respawned.
8179 if (!mobj->fuse)
8180 { // Go away.
8181 /// \todo Actually go ahead and remove mobj completely, and fix any bugs and crashes doing this creates. Chasecam should stop moving, and F12 should never return to it.
8182 mobj->momz = 0;
8183 if (mobj->player)
8184 mobj->flags2 |= MF2_DONTDRAW;
8185 else // safe to remove, nobody's going to complain!
8186 {
8187 P_RemoveMobj(mobj);
8188 return false;
8189 }
8190 }
8191 else // Apply gravity to fall downwards.
8192 {
8193 if (mobj->player && !(mobj->fuse % 8) && (mobj->player->charflags & SF_MACHINE))
8194 {
8195 fixed_t r = mobj->radius >> FRACBITS;
8196 mobj_t *explosion = P_SpawnMobj(
8197 mobj->x + (P_RandomRange(r, -r) << FRACBITS),
8198 mobj->y + (P_RandomRange(r, -r) << FRACBITS),
8199 mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS),
8200 MT_SONIC3KBOSSEXPLODE);
8201 S_StartSound(explosion, sfx_s3kb4);
8202 }
8203 if (mobj->movedir == DMG_DROWNED)
8204 P_SetObjectMomZ(mobj, -FRACUNIT/2, true); // slower fall from drowning
8205 else
8206 P_SetObjectMomZ(mobj, -2*FRACUNIT/3, true);
8207 }
8208 break;
8209 case MT_METALSONIC_RACE:
8210 {
8211 if (!(mobj->fuse % 8))
8212 {
8213 fixed_t r = mobj->radius >> FRACBITS;
8214 mobj_t *explosion = P_SpawnMobj(
8215 mobj->x + (P_RandomRange(r, -r) << FRACBITS),
8216 mobj->y + (P_RandomRange(r, -r) << FRACBITS),
8217 mobj->z + (P_RandomKey(mobj->height >> FRACBITS) << FRACBITS),
8218 MT_SONIC3KBOSSEXPLODE);
8219 S_StartSound(explosion, sfx_s3kb4);
8220 }
8221 P_SetObjectMomZ(mobj, -2*FRACUNIT/3, true);
8222 }
8223 break;
8224 default:
8225 break;
8226 }
8227 return true;
8228 }
8229
8230 // Angle-to-tracer to trigger a linedef exec
8231 // See Linedef Exec 457 (Track mobj angle to point)
P_TracerAngleThink(mobj_t * mobj)8232 static void P_TracerAngleThink(mobj_t *mobj)
8233 {
8234 angle_t looking;
8235 angle_t ang;
8236
8237 if (!mobj->tracer)
8238 return;
8239
8240 if (!mobj->extravalue2)
8241 return;
8242
8243 // mobj->lastlook - Don't disable behavior after first failure
8244 // mobj->extravalue1 - Angle tolerance
8245 // mobj->extravalue2 - Exec tag upon failure
8246 // mobj->cvval - Allowable failure delay
8247 // mobj->cvmem - Failure timer
8248
8249 if (mobj->player)
8250 looking = ( mobj->player->cmd.angleturn << 16 );/* fixes CS_LMAOGALOG */
8251 else
8252 looking = mobj->angle;
8253
8254 ang = looking - R_PointToAngle2(mobj->x, mobj->y, mobj->tracer->x, mobj->tracer->y);
8255
8256 // \todo account for distance between mobj and tracer
8257 // Because closer mobjs can be facing beyond the angle tolerance
8258 // yet tracer is still in the camera view
8259
8260 // failure state: mobj is not facing tracer
8261 // Reasaonable defaults: ANGLE_67h, ANGLE_292h
8262 if (ang >= (angle_t)mobj->extravalue1 && ang <= ANGLE_MAX - (angle_t)mobj->extravalue1)
8263 {
8264 if (mobj->cvmem)
8265 mobj->cvmem--;
8266 else
8267 {
8268 INT32 exectag = mobj->extravalue2; // remember this before we erase the values
8269
8270 if (mobj->lastlook)
8271 mobj->cvmem = mobj->cusval; // reset timer for next failure
8272 else
8273 {
8274 // disable after first failure
8275 mobj->eflags &= ~MFE_TRACERANGLE;
8276 mobj->lastlook = mobj->extravalue1 = mobj->extravalue2 = mobj->cvmem = mobj->cusval = 0;
8277 }
8278
8279 P_LinedefExecute(exectag, mobj, NULL);
8280 }
8281 }
8282 else
8283 mobj->cvmem = mobj->cusval; // reset failure timer
8284 }
8285
P_ArrowThink(mobj_t * mobj)8286 static void P_ArrowThink(mobj_t *mobj)
8287 {
8288 if (mobj->flags & MF_MISSILE)
8289 {
8290 // Calculate the angle of movement.
8291 /*
8292 momz
8293 / |
8294 / |
8295 / |
8296 0------dist(momx,momy)
8297 */
8298
8299 fixed_t dist = P_AproxDistance(mobj->momx, mobj->momy);
8300 angle_t angle = R_PointToAngle2(0, 0, dist, mobj->momz);
8301
8302 if (angle > ANG20 && angle <= ANGLE_180)
8303 mobj->frame = 2;
8304 else if (angle < ANG340 && angle > ANGLE_180)
8305 mobj->frame = 0;
8306 else
8307 mobj->frame = 1;
8308
8309 if (!(mobj->extravalue1) && (mobj->momz < 0))
8310 {
8311 mobj->extravalue1 = 1;
8312 S_StartSound(mobj, mobj->info->activesound);
8313 }
8314 if (leveltime & 1)
8315 {
8316 mobj_t *dust = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_PARTICLE);
8317 dust->tics = 18;
8318 dust->scalespeed = 4096;
8319 dust->destscale = FRACUNIT/32;
8320 }
8321 }
8322 else
8323 mobj->flags2 ^= MF2_DONTDRAW;
8324 }
8325
P_BumbleboreThink(mobj_t * mobj)8326 static void P_BumbleboreThink(mobj_t *mobj)
8327 {
8328 statenum_t st = mobj->state - states;
8329 if (st == S_BUMBLEBORE_FLY1 || st == S_BUMBLEBORE_FLY2)
8330 {
8331 if (!mobj->target)
8332 P_SetMobjState(mobj, mobj->info->spawnstate);
8333 else if (P_MobjFlip(mobj)*((mobj->z + (mobj->height >> 1)) - (mobj->target->z + (mobj->target->height >> 1))) > 0
8334 && R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) <= 32*FRACUNIT)
8335 {
8336 mobj->momx >>= 1;
8337 mobj->momy >>= 1;
8338 if (++mobj->movefactor == 4)
8339 {
8340 S_StartSound(mobj, mobj->info->seesound);
8341 mobj->momx = mobj->momy = mobj->momz = 0;
8342 mobj->flags = (mobj->flags|MF_PAIN) & ~MF_NOGRAVITY;
8343 P_SetMobjState(mobj, mobj->info->meleestate);
8344 }
8345 }
8346 else
8347 mobj->movefactor = 0;
8348 }
8349 else if (st == S_BUMBLEBORE_RAISE || st == S_BUMBLEBORE_FALL2) // no _FALL1 because it's an 0-tic
8350 {
8351 if (P_IsObjectOnGround(mobj))
8352 {
8353 S_StopSound(mobj);
8354 S_StartSound(mobj, mobj->info->attacksound);
8355 mobj->flags = (mobj->flags | MF_NOGRAVITY) & ~MF_PAIN;
8356 mobj->momx = mobj->momy = mobj->momz = 0;
8357 P_SetMobjState(mobj, mobj->info->painstate);
8358 }
8359 else
8360 {
8361 mobj->angle += ANGLE_22h;
8362 mobj->frame = mobj->state->frame + ((mobj->tics & 2) >> 1);
8363 }
8364 }
8365 else if (st == S_BUMBLEBORE_STUCK2 && mobj->tics < TICRATE)
8366 mobj->frame = mobj->state->frame + ((mobj->tics & 2) >> 1);
8367 }
8368
P_HangsterThink(mobj_t * mobj)8369 static boolean P_HangsterThink(mobj_t *mobj)
8370 {
8371 statenum_t st = mobj->state - states;
8372 //ghost image trail when flying down
8373 if (st == S_HANGSTER_SWOOP1 || st == S_HANGSTER_SWOOP2)
8374 {
8375 P_SpawnGhostMobj(mobj);
8376 //curve when in line with target, otherwise curve to avoid crashing into floor
8377 if ((mobj->z - mobj->floorz <= 80*FRACUNIT) || (mobj->target && (mobj->z - mobj->target->z <= 80*FRACUNIT)))
8378 P_SetMobjState(mobj, (st = S_HANGSTER_ARC1));
8379 }
8380
8381 //swoop arc movement stuff
8382 if (st == S_HANGSTER_ARC1)
8383 {
8384 A_FaceTarget(mobj);
8385 P_Thrust(mobj, mobj->angle, 1*FRACUNIT);
8386 }
8387 else if (st == S_HANGSTER_ARC2)
8388 P_Thrust(mobj, mobj->angle, 2*FRACUNIT);
8389 else if (st == S_HANGSTER_ARC3)
8390 P_Thrust(mobj, mobj->angle, 4*FRACUNIT);
8391 //if movement has stopped while flying (like hitting a wall), fly up immediately
8392 else if (st == S_HANGSTER_FLY1 && !mobj->momx && !mobj->momy)
8393 {
8394 mobj->extravalue1 = 0;
8395 P_SetMobjState(mobj, S_HANGSTER_ARCUP1);
8396 }
8397 //after swooping back up, check for ceiling
8398 else if ((st == S_HANGSTER_RETURN1 || st == S_HANGSTER_RETURN2) && mobj->momz == 0 && mobj->ceilingz == (mobj->z + mobj->height))
8399 P_SetMobjState(mobj, (st = S_HANGSTER_RETURN3));
8400
8401 //should you roost on a ceiling with F_SKY1 as its flat, disappear forever
8402 if (st == S_HANGSTER_RETURN3 && mobj->momz == 0 && mobj->ceilingz == (mobj->z + mobj->height)
8403 && mobj->subsector->sector->ceilingpic == skyflatnum
8404 && mobj->subsector->sector->ceilingheight == mobj->ceilingz)
8405 {
8406 P_RemoveMobj(mobj);
8407 return false;
8408 }
8409
8410 return true;
8411 }
8412
P_JetFume1Think(mobj_t * mobj)8413 static boolean P_JetFume1Think(mobj_t *mobj)
8414 {
8415 fixed_t jetx, jety;
8416
8417 if (!mobj->target // if you have no target
8418 || (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
8419 { // then remove yourself as well!
8420 P_RemoveMobj(mobj);
8421 return false;
8422 }
8423
8424 jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale));
8425 jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, FixedMul(-64*FRACUNIT, mobj->target->scale));
8426
8427 if (mobj->fuse == 56) // First one
8428 {
8429 P_UnsetThingPosition(mobj);
8430 mobj->x = jetx;
8431 mobj->y = jety;
8432 if (mobj->target->eflags & MFE_VERTICALFLIP)
8433 mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(38*FRACUNIT, mobj->target->scale);
8434 else
8435 mobj->z = mobj->target->z + FixedMul(38*FRACUNIT, mobj->target->scale);
8436 mobj->floorz = mobj->z;
8437 mobj->ceilingz = mobj->z + mobj->height;
8438 P_SetThingPosition(mobj);
8439 }
8440 else if (mobj->fuse == 57)
8441 {
8442 P_UnsetThingPosition(mobj);
8443 mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle - ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
8444 mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle - ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
8445 if (mobj->target->eflags & MFE_VERTICALFLIP)
8446 mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale);
8447 else
8448 mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale);
8449 mobj->floorz = mobj->z;
8450 mobj->ceilingz = mobj->z + mobj->height;
8451 P_SetThingPosition(mobj);
8452 }
8453 else if (mobj->fuse == 58)
8454 {
8455 P_UnsetThingPosition(mobj);
8456 mobj->x = jetx + P_ReturnThrustX(mobj->target, mobj->target->angle + ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
8457 mobj->y = jety + P_ReturnThrustY(mobj->target, mobj->target->angle + ANGLE_90, FixedMul(24*FRACUNIT, mobj->target->scale));
8458 if (mobj->target->eflags & MFE_VERTICALFLIP)
8459 mobj->z = mobj->target->z + mobj->target->height - mobj->height - FixedMul(12*FRACUNIT, mobj->target->scale);
8460 else
8461 mobj->z = mobj->target->z + FixedMul(12*FRACUNIT, mobj->target->scale);
8462 mobj->floorz = mobj->z;
8463 mobj->ceilingz = mobj->z + mobj->height;
8464 P_SetThingPosition(mobj);
8465 }
8466 else if (mobj->fuse == 59)
8467 {
8468 boolean dashmod = ((mobj->target->flags & MF_PAIN) && (mobj->target->health <= mobj->target->info->damage));
8469 jetx = mobj->target->x + P_ReturnThrustX(mobj->target, mobj->target->angle, -mobj->target->radius);
8470 jety = mobj->target->y + P_ReturnThrustY(mobj->target, mobj->target->angle, -mobj->target->radius);
8471 P_UnsetThingPosition(mobj);
8472 mobj->x = jetx;
8473 mobj->y = jety;
8474 mobj->destscale = mobj->target->scale;
8475 if (!(dashmod && mobj->target->state == states + S_METALSONIC_BOUNCE))
8476 {
8477 mobj->destscale = (mobj->destscale + FixedDiv(R_PointToDist2(0, 0, mobj->target->momx, mobj->target->momy), 36*mobj->target->scale))/3;
8478 }
8479 if (mobj->target->eflags & MFE_VERTICALFLIP)
8480 mobj->z = mobj->target->z + mobj->target->height/2 + mobj->height/2;
8481 else
8482 mobj->z = mobj->target->z + mobj->target->height/2 - mobj->height/2;
8483 mobj->floorz = mobj->z;
8484 mobj->ceilingz = mobj->z + mobj->height;
8485 P_SetThingPosition(mobj);
8486 if (dashmod)
8487 {
8488 mobj->color = SKINCOLOR_SUNSET;
8489 if (mobj->target->movecount == 3 && !mobj->target->reactiontime && (mobj->target->movedir == 0 || mobj->target->movedir == 2))
8490 P_SpawnGhostMobj(mobj);
8491 }
8492 else
8493 mobj->color = SKINCOLOR_ICY;
8494 }
8495 mobj->fuse++;
8496 return true;
8497 }
8498
P_EggRobo1Think(mobj_t * mobj)8499 static boolean P_EggRobo1Think(mobj_t *mobj)
8500 {
8501 #define SPECTATORRADIUS (96*mobj->scale)
8502 if (!(mobj->flags2 & MF2_STRONGBOX))
8503 {
8504 mobj->cusval = mobj->x; // eat my SOCs, p_mobj.h warning, we have lua now
8505 mobj->cvmem = mobj->y; // ditto
8506 mobj->movedir = mobj->angle;
8507 mobj->threshold = P_MobjFlip(mobj)*10*mobj->scale;
8508 if (mobj->threshold < 0)
8509 mobj->threshold += (mobj->ceilingz - mobj->height);
8510 else
8511 mobj->threshold += mobj->floorz;
8512 var1 = 4;
8513 A_BossJetFume(mobj);
8514 mobj->flags2 |= MF2_STRONGBOX;
8515 }
8516
8517 if (mobj->state == &states[mobj->info->deathstate]) // todo: make map actually set health to 0 for these
8518 {
8519 if (mobj->movecount)
8520 {
8521 if (!(--mobj->movecount))
8522 S_StartSound(mobj, mobj->info->deathsound);
8523 }
8524 else
8525 {
8526 mobj->momz += P_MobjFlip(mobj)*mobj->scale;
8527 if (mobj->momz > 0)
8528 {
8529 if (mobj->z + mobj->momz > mobj->ceilingz + (1000 << FRACBITS))
8530 {
8531 P_RemoveMobj(mobj);
8532 return false;
8533 }
8534 }
8535 else if (mobj->z + mobj->height + mobj->momz < mobj->floorz - (1000 << FRACBITS))
8536 {
8537 P_RemoveMobj(mobj);
8538 return false;
8539 }
8540 }
8541 }
8542 else
8543 {
8544 fixed_t basex = mobj->cusval, basey = mobj->cvmem;
8545
8546 if (mobj->spawnpoint && mobj->spawnpoint->options & (MTF_AMBUSH|MTF_OBJECTSPECIAL))
8547 {
8548 angle_t sideang = mobj->movedir + ((mobj->spawnpoint->options & MTF_AMBUSH) ? ANGLE_90 : -ANGLE_90);
8549 fixed_t oscillate = FixedMul(FINESINE(((leveltime * ANG1) >> (ANGLETOFINESHIFT + 2)) & FINEMASK), 250*mobj->scale);
8550 basex += P_ReturnThrustX(mobj, sideang, oscillate);
8551 basey += P_ReturnThrustY(mobj, sideang, oscillate);
8552 }
8553
8554 mobj->z = mobj->threshold + FixedMul(FINESINE(((leveltime + mobj->movecount)*ANG2 >> (ANGLETOFINESHIFT - 2)) & FINEMASK), 8*mobj->scale);
8555 if (mobj->state != &states[mobj->info->meleestate])
8556 {
8557 boolean didmove = false;
8558
8559 if (mobj->state == &states[mobj->info->spawnstate])
8560 {
8561 UINT8 i;
8562 fixed_t dist = INT32_MAX;
8563
8564 for (i = 0; i < MAXPLAYERS; i++)
8565 {
8566 fixed_t compdist;
8567 if (!playeringame[i])
8568 continue;
8569 if (players[i].spectator)
8570 continue;
8571 if (!players[i].mo)
8572 continue;
8573 if (!players[i].mo->health)
8574 continue;
8575 if (P_PlayerInPain(&players[i]))
8576 continue;
8577 if (players[i].mo->z > mobj->z + mobj->height + 8*mobj->scale)
8578 continue;
8579 if (players[i].mo->z + players[i].mo->height < mobj->z - 8*mobj->scale)
8580 continue;
8581 compdist = P_AproxDistance(
8582 players[i].mo->x + players[i].mo->momx - basex,
8583 players[i].mo->y + players[i].mo->momy - basey);
8584 if (compdist >= dist)
8585 continue;
8586 dist = compdist;
8587 P_SetTarget(&mobj->target, players[i].mo);
8588 }
8589
8590 if (dist < (SPECTATORRADIUS << 1))
8591 {
8592 didmove = true;
8593 mobj->frame = 3 + ((leveltime & 2) >> 1);
8594 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
8595
8596 if (P_AproxDistance(
8597 mobj->x - basex,
8598 mobj->y - basey)
8599 < mobj->scale)
8600 S_StartSound(mobj, mobj->info->seesound);
8601
8602 P_TeleportMove(mobj,
8603 (15*(mobj->x >> 4)) + (basex >> 4) + P_ReturnThrustX(mobj, mobj->angle, SPECTATORRADIUS >> 4),
8604 (15*(mobj->y >> 4)) + (basey >> 4) + P_ReturnThrustY(mobj, mobj->angle, SPECTATORRADIUS >> 4),
8605 mobj->z);
8606 }
8607 else
8608 {
8609 angle_t diff = (mobj->movedir - mobj->angle);
8610 if (diff > ANGLE_180)
8611 diff = InvAngle(InvAngle(diff)/8);
8612 else
8613 diff /= 8;
8614 mobj->angle += diff;
8615
8616 dist = FINECOSINE(((leveltime + mobj->movecount)*ANG2 >> (ANGLETOFINESHIFT - 2)) & FINEMASK);
8617
8618 if (abs(dist) < FRACUNIT/2)
8619 mobj->frame = 0;
8620 else
8621 mobj->frame = (dist > 0) ? 1 : 2;
8622 }
8623 }
8624
8625 if (!didmove)
8626 {
8627 if (P_AproxDistance(mobj->x - basex, mobj->y - basey) < mobj->scale)
8628 P_TeleportMove(mobj, basex, basey, mobj->z);
8629 else
8630 P_TeleportMove(mobj,
8631 (15*(mobj->x >> 4)) + (basex >> 4),
8632 (15*(mobj->y >> 4)) + (basey >> 4),
8633 mobj->z);
8634 }
8635 }
8636 }
8637 return true;
8638 #undef SPECTATORRADIUS
8639 }
8640
P_NiGHTSDroneThink(mobj_t * mobj)8641 static void P_NiGHTSDroneThink(mobj_t *mobj)
8642 {
8643 {
8644 // variable setup
8645 mobj_t *goalpost = NULL;
8646 mobj_t *sparkle = NULL;
8647 mobj_t *droneman = NULL;
8648
8649 boolean flip = mobj->flags2 & MF2_OBJECTFLIP;
8650 boolean topaligned = (mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
8651 boolean middlealigned = (mobj->flags & MF_GRENADEBOUNCE) && !(mobj->flags & MF_SLIDEME);
8652 boolean bottomoffsetted = !(mobj->flags & MF_SLIDEME) && !(mobj->flags & MF_GRENADEBOUNCE);
8653 boolean flipchanged = false;
8654
8655 fixed_t dronemanoffset, goaloffset, sparkleoffset, droneboxmandiff, dronemangoaldiff;
8656
8657 if (mobj->target && mobj->target->type == MT_NIGHTSDRONE_GOAL)
8658 {
8659 goalpost = mobj->target;
8660 if (goalpost->target && goalpost->target->type == MT_NIGHTSDRONE_SPARKLING)
8661 sparkle = goalpost->target;
8662 if (goalpost->tracer && goalpost->tracer->type == MT_NIGHTSDRONE_MAN)
8663 droneman = goalpost->tracer;
8664 }
8665
8666 if (!goalpost || !sparkle || !droneman)
8667 return;
8668
8669 // did NIGHTSDRONE position, scale, flip, or flags change? all elements need to be synced
8670 droneboxmandiff = max(mobj->height - droneman->height, 0);
8671 dronemangoaldiff = max(droneman->height - goalpost->height, 0);
8672
8673 if (!(goalpost->flags2 & MF2_OBJECTFLIP) && (mobj->flags2 & MF2_OBJECTFLIP))
8674 {
8675 goalpost->eflags |= MFE_VERTICALFLIP;
8676 goalpost->flags2 |= MF2_OBJECTFLIP;
8677 sparkle->eflags |= MFE_VERTICALFLIP;
8678 sparkle->flags2 |= MF2_OBJECTFLIP;
8679 droneman->eflags |= MFE_VERTICALFLIP;
8680 droneman->flags2 |= MF2_OBJECTFLIP;
8681 flipchanged = true;
8682 }
8683 else if ((goalpost->flags2 & MF2_OBJECTFLIP) && !(mobj->flags2 & MF2_OBJECTFLIP))
8684 {
8685 goalpost->eflags &= ~MFE_VERTICALFLIP;
8686 goalpost->flags2 &= ~MF2_OBJECTFLIP;
8687 sparkle->eflags &= ~MFE_VERTICALFLIP;
8688 sparkle->flags2 &= ~MF2_OBJECTFLIP;
8689 droneman->eflags &= ~MFE_VERTICALFLIP;
8690 droneman->flags2 &= ~MF2_OBJECTFLIP;
8691 flipchanged = true;
8692 }
8693
8694 if (goalpost->destscale != mobj->destscale
8695 || goalpost->movefactor != mobj->z
8696 || goalpost->friction != mobj->height
8697 || flipchanged
8698 || goalpost->threshold != (INT32)(mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE)))
8699 {
8700 goalpost->destscale = sparkle->destscale = droneman->destscale = mobj->destscale;
8701
8702 // straight copy-pasta from P_SpawnMapThing, case MT_NIGHTSDRONE
8703 if (!flip)
8704 {
8705 if (topaligned) // Align droneman to top of hitbox
8706 {
8707 dronemanoffset = droneboxmandiff;
8708 goaloffset = dronemangoaldiff/2 + dronemanoffset;
8709 }
8710 else if (middlealigned) // Align droneman to center of hitbox
8711 {
8712 dronemanoffset = droneboxmandiff/2;
8713 goaloffset = dronemangoaldiff/2 + dronemanoffset;
8714 }
8715 else if (bottomoffsetted)
8716 {
8717 dronemanoffset = 24*FRACUNIT;
8718 goaloffset = dronemangoaldiff + dronemanoffset;
8719 }
8720 else
8721 {
8722 dronemanoffset = 0;
8723 goaloffset = dronemangoaldiff/2 + dronemanoffset;
8724 }
8725
8726 sparkleoffset = goaloffset - FixedMul(15*FRACUNIT, mobj->scale);
8727 }
8728 else
8729 {
8730 if (topaligned) // Align droneman to top of hitbox
8731 {
8732 dronemanoffset = 0;
8733 goaloffset = dronemangoaldiff/2 + dronemanoffset;
8734 }
8735 else if (middlealigned) // Align droneman to center of hitbox
8736 {
8737 dronemanoffset = droneboxmandiff/2;
8738 goaloffset = dronemangoaldiff/2 + dronemanoffset;
8739 }
8740 else if (bottomoffsetted)
8741 {
8742 dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
8743 goaloffset = dronemangoaldiff + dronemanoffset;
8744 }
8745 else
8746 {
8747 dronemanoffset = droneboxmandiff;
8748 goaloffset = dronemangoaldiff/2 + dronemanoffset;
8749 }
8750
8751 sparkleoffset = goaloffset + FixedMul(15*FRACUNIT, mobj->scale);
8752 }
8753
8754 P_TeleportMove(goalpost, mobj->x, mobj->y, mobj->z + goaloffset);
8755 P_TeleportMove(sparkle, mobj->x, mobj->y, mobj->z + sparkleoffset);
8756 if (goalpost->movefactor != mobj->z || goalpost->friction != mobj->height)
8757 {
8758 P_TeleportMove(droneman, mobj->x, mobj->y, mobj->z + dronemanoffset);
8759 goalpost->movefactor = mobj->z;
8760 goalpost->friction = mobj->height;
8761 }
8762 goalpost->threshold = mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE);
8763 }
8764 else
8765 {
8766 if (goalpost->x != mobj->x || goalpost->y != mobj->y)
8767 {
8768 P_TeleportMove(goalpost, mobj->x, mobj->y, goalpost->z);
8769 P_TeleportMove(sparkle, mobj->x, mobj->y, sparkle->z);
8770 }
8771
8772 if (droneman->x != mobj->x || droneman->y != mobj->y)
8773 P_TeleportMove(droneman, mobj->x, mobj->y,
8774 droneman->z >= mobj->floorz && droneman->z <= mobj->ceilingz ? droneman->z : mobj->z);
8775 }
8776
8777 // now toggle states!
8778 // GOAL mode?
8779 if (sparkle->state >= &states[S_NIGHTSDRONE_SPARKLING1] && sparkle->state <= &states[S_NIGHTSDRONE_SPARKLING16])
8780 {
8781 INT32 i;
8782 boolean bonustime = false;
8783
8784 for (i = 0; i < MAXPLAYERS; i++)
8785 if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
8786 {
8787 bonustime = true;
8788 break;
8789 }
8790
8791 if (!bonustime)
8792 {
8793 CONS_Debug(DBG_NIGHTSBASIC, "Removing goal post\n");
8794 if (goalpost && goalpost->state != &states[S_INVISIBLE])
8795 P_SetMobjState(goalpost, S_INVISIBLE);
8796 if (sparkle && sparkle->state != &states[S_INVISIBLE])
8797 P_SetMobjState(sparkle, S_INVISIBLE);
8798 }
8799 }
8800 // Invisible/bouncing mode.
8801 else
8802 {
8803 INT32 i;
8804 boolean bonustime = false;
8805 fixed_t zcomp;
8806
8807 // Bouncy bouncy!
8808 if (!flip)
8809 {
8810 if (topaligned)
8811 zcomp = droneboxmandiff + mobj->z;
8812 else if (middlealigned)
8813 zcomp = (droneboxmandiff/2) + mobj->z;
8814 else if (bottomoffsetted)
8815 zcomp = mobj->z + FixedMul(24*FRACUNIT, mobj->scale);
8816 else
8817 zcomp = mobj->z;
8818 }
8819 else
8820 {
8821 if (topaligned)
8822 zcomp = mobj->z;
8823 else if (middlealigned)
8824 zcomp = (droneboxmandiff/2) + mobj->z;
8825 else if (bottomoffsetted)
8826 zcomp = mobj->z + droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
8827 else
8828 zcomp = mobj->z + droneboxmandiff;
8829 }
8830
8831 droneman->angle += ANG10;
8832 if (!flip && droneman->z <= zcomp)
8833 droneman->momz = FixedMul(5*FRACUNIT, droneman->scale);
8834 else if (flip && droneman->z >= zcomp)
8835 droneman->momz = FixedMul(-5*FRACUNIT, droneman->scale);
8836
8837 // state switching logic
8838 for (i = 0; i < MAXPLAYERS; i++)
8839 if (playeringame[i] && players[i].bonustime && players[i].powers[pw_carry] == CR_NIGHTSMODE)
8840 {
8841 bonustime = true;
8842 break;
8843 }
8844
8845 if (bonustime)
8846 {
8847 CONS_Debug(DBG_NIGHTSBASIC, "Adding goal post\n");
8848 if (!(droneman->flags2 & MF2_DONTDRAW))
8849 droneman->flags2 |= MF2_DONTDRAW;
8850 if (goalpost->state == &states[S_INVISIBLE])
8851 P_SetMobjState(goalpost, mobjinfo[goalpost->type].meleestate);
8852 if (sparkle->state == &states[S_INVISIBLE])
8853 P_SetMobjState(sparkle, mobjinfo[sparkle->type].meleestate);
8854 }
8855 else if (!G_IsSpecialStage(gamemap))
8856 {
8857 for (i = 0; i < MAXPLAYERS; i++)
8858 if (playeringame[i] && players[i].powers[pw_carry] != CR_NIGHTSMODE)
8859 {
8860 bonustime = true; // variable reuse
8861 break;
8862 }
8863
8864 if (bonustime)
8865 {
8866 // show droneman if at least one player is non-nights
8867 if (goalpost->state != &states[S_INVISIBLE])
8868 P_SetMobjState(goalpost, S_INVISIBLE);
8869 if (sparkle->state != &states[S_INVISIBLE])
8870 P_SetMobjState(sparkle, S_INVISIBLE);
8871 if (droneman->state != &states[mobjinfo[droneman->type].meleestate])
8872 P_SetMobjState(droneman, mobjinfo[droneman->type].meleestate);
8873 if (droneman->flags2 & MF2_DONTDRAW)
8874 droneman->flags2 &= ~MF2_DONTDRAW;
8875 }
8876 else
8877 {
8878 // else, hide it
8879 if (!(droneman->flags2 & MF2_DONTDRAW))
8880 droneman->flags2 |= MF2_DONTDRAW;
8881 }
8882 }
8883 }
8884 }
8885 }
8886
P_TurretThink(mobj_t * mobj)8887 static boolean P_TurretThink(mobj_t *mobj)
8888 {
8889 P_MobjCheckWater(mobj);
8890 P_CheckPosition(mobj, mobj->x, mobj->y);
8891 if (P_MobjWasRemoved(mobj))
8892 return false;
8893 mobj->floorz = tmfloorz;
8894 mobj->ceilingz = tmceilingz;
8895 mobj->floorrover = tmfloorrover;
8896 mobj->ceilingrover = tmceilingrover;
8897
8898 if ((mobj->eflags & MFE_UNDERWATER) && mobj->health > 0)
8899 {
8900 P_SetMobjState(mobj, mobj->info->deathstate);
8901 mobj->health = 0;
8902 mobj->flags2 &= ~MF2_FIRING;
8903 }
8904 else if (mobj->health > 0 && mobj->z + mobj->height > mobj->ceilingz) // Crushed
8905 {
8906 INT32 i, j;
8907 fixed_t ns;
8908 fixed_t x, y, z;
8909 mobj_t* mo2;
8910
8911 z = mobj->subsector->sector->floorheight + FixedMul(64*FRACUNIT, mobj->scale);
8912 for (j = 0; j < 2; j++)
8913 {
8914 for (i = 0; i < 32; i++)
8915 {
8916 const angle_t fa = (i*FINEANGLES/16) & FINEMASK;
8917 ns = FixedMul(64*FRACUNIT, mobj->scale);
8918 x = mobj->x + FixedMul(FINESINE(fa), ns);
8919 y = mobj->y + FixedMul(FINECOSINE(fa), ns);
8920
8921 mo2 = P_SpawnMobj(x, y, z, MT_EXPLODE);
8922 ns = FixedMul(16*FRACUNIT, mobj->scale);
8923 mo2->momx = FixedMul(FINESINE(fa), ns);
8924 mo2->momy = FixedMul(FINECOSINE(fa), ns);
8925 }
8926 z -= FixedMul(32*FRACUNIT, mobj->scale);
8927 }
8928 P_SetMobjState(mobj, mobj->info->deathstate);
8929 mobj->health = 0;
8930 mobj->flags2 &= ~MF2_FIRING;
8931 }
8932 return true;
8933 }
8934
P_SaloonDoorThink(mobj_t * mobj)8935 static void P_SaloonDoorThink(mobj_t *mobj)
8936 {
8937 fixed_t x = mobj->tracer->x;
8938 fixed_t y = mobj->tracer->y;
8939 fixed_t z = mobj->tracer->z;
8940 angle_t oang = FixedAngle(mobj->extravalue1);
8941 angle_t fa = (oang >> ANGLETOFINESHIFT) & FINEMASK;
8942 fixed_t c0 = -96*FINECOSINE(fa);
8943 fixed_t s0 = -96*FINESINE(fa);
8944 angle_t fma;
8945 fixed_t c, s;
8946 angle_t angdiff;
8947
8948 // Adjust angular speed
8949 fixed_t da = AngleFixed(mobj->angle - oang);
8950 if (da > 180*FRACUNIT)
8951 da -= 360*FRACUNIT;
8952 mobj->extravalue2 = 8*(mobj->extravalue2 - da/32)/9;
8953
8954 // Update angle
8955 mobj->angle += FixedAngle(mobj->extravalue2);
8956
8957 angdiff = mobj->angle - FixedAngle(mobj->extravalue1);
8958 if (angdiff > (ANGLE_90 - ANG2) && angdiff < ANGLE_180)
8959 {
8960 mobj->angle = FixedAngle(mobj->extravalue1) + (ANGLE_90 - ANG2);
8961 mobj->extravalue2 /= 2;
8962 }
8963 else if (angdiff < (ANGLE_270 + ANG2) && angdiff >= ANGLE_180)
8964 {
8965 mobj->angle = FixedAngle(mobj->extravalue1) + (ANGLE_270 + ANG2);
8966 mobj->extravalue2 /= 2;
8967 }
8968
8969 // Update position
8970 fma = (mobj->angle >> ANGLETOFINESHIFT) & FINEMASK;
8971 c = 48*FINECOSINE(fma);
8972 s = 48*FINESINE(fma);
8973 P_TeleportMove(mobj, x + c0 + c, y + s0 + s, z);
8974 }
8975
P_PyreFlyThink(mobj_t * mobj)8976 static void P_PyreFlyThink(mobj_t *mobj)
8977 {
8978 fixed_t hdist;
8979
8980 mobj->extravalue1 = (mobj->extravalue1 + 3) % 360;
8981 mobj->z += FINESINE(((mobj->extravalue1 * ANG1) >> ANGLETOFINESHIFT) & FINEMASK);
8982
8983 if (!(mobj->flags2 & MF2_BOSSNOTRAP))
8984 P_LookForPlayers(mobj, true, false, 1500*FRACUNIT);
8985
8986 if (!mobj->target)
8987 return;
8988
8989 if (mobj->extravalue2 == 1)
8990 P_PyreFlyBurn(mobj, 0, 20, MT_SMOKE, 4*FRACUNIT);
8991 else if (mobj->extravalue2 == 2)
8992 {
8993 INT32 fireradius = min(100 - mobj->fuse, 52);
8994 P_PyreFlyBurn(mobj, P_RandomRange(0, fireradius) << FRACBITS, 20, MT_FLAMEPARTICLE, 4*FRACUNIT);
8995 P_PyreFlyBurn(mobj, fireradius*FRACUNIT, 40, MT_PYREFLY_FIRE, 0);
8996 }
8997
8998 hdist = R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
8999
9000 if (hdist > 1500*FRACUNIT)
9001 {
9002 mobj->flags2 &= ~MF2_BOSSNOTRAP;
9003 P_SetTarget(&mobj->target, NULL);
9004 return;
9005 }
9006
9007 if (!(mobj->flags2 & MF2_BOSSNOTRAP) && hdist <= 450*FRACUNIT)
9008 mobj->flags2 |= MF2_BOSSNOTRAP;
9009
9010 if (!(mobj->flags2 & MF2_BOSSNOTRAP))
9011 return;
9012
9013 if (hdist < 1000*FRACUNIT)
9014 {
9015 //Aim for player z position. If too close to floor/ceiling, aim just above/below them.
9016 fixed_t destz = min(max(mobj->target->z, mobj->target->floorz + 70*FRACUNIT), mobj->target->ceilingz - 80*FRACUNIT - mobj->height);
9017 fixed_t dist = P_AproxDistance(hdist, destz - mobj->z);
9018 P_InstaThrust(mobj, R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y), 2*FRACUNIT);
9019 mobj->momz = FixedMul(FixedDiv(destz - mobj->z, dist), 2*FRACUNIT);
9020 }
9021 else
9022 {
9023 mobj->momx = 0;
9024 mobj->momy = 0;
9025 mobj->momz = 0;
9026 }
9027 }
9028
P_PterabyteThink(mobj_t * mobj)9029 static void P_PterabyteThink(mobj_t *mobj)
9030 {
9031 if (mobj->extravalue1 & 4) // Cooldown after grabbing
9032 {
9033 if (mobj->movefactor)
9034 mobj->movefactor--;
9035 else
9036 {
9037 P_SetTarget(&mobj->target, NULL);
9038 mobj->extravalue1 &= 3;
9039 }
9040 }
9041
9042 if ((mobj->extravalue1 & 3) == 0) // Hovering
9043 {
9044 fixed_t vdist, hdist, time;
9045 fixed_t hspeed = 3*mobj->info->speed;
9046 angle_t fa;
9047
9048 var1 = 1;
9049 var2 = 0;
9050 A_CapeChase(mobj);
9051
9052 if (mobj->target)
9053 return; // Still carrying a player or in cooldown
9054
9055 P_LookForPlayers(mobj, true, false, 256*FRACUNIT);
9056
9057 if (!mobj->target)
9058 return;
9059
9060 if (mobj->target->player->powers[pw_flashing])
9061 {
9062 P_SetTarget(&mobj->target, NULL);
9063 return;
9064 }
9065
9066 vdist = mobj->z - mobj->target->z - mobj->target->height;
9067 if (P_MobjFlip(mobj)*vdist <= 0)
9068 {
9069 P_SetTarget(&mobj->target, NULL);
9070 return;
9071 }
9072
9073 hdist = R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
9074 if (hdist > 450*FRACUNIT)
9075 {
9076 P_SetTarget(&mobj->target, NULL);
9077 return;
9078 }
9079
9080 P_SetMobjState(mobj, S_PTERABYTE_SWOOPDOWN);
9081 mobj->extravalue1++;
9082 S_StartSound(mobj, mobj->info->attacksound);
9083 time = FixedDiv(hdist, hspeed);
9084 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
9085 fa = (mobj->angle >> ANGLETOFINESHIFT) & FINEMASK;
9086 mobj->momx = FixedMul(FINECOSINE(fa), hspeed);
9087 mobj->momy = FixedMul(FINESINE(fa), hspeed);
9088 mobj->momz = -2*FixedDiv(vdist, time);
9089 mobj->extravalue2 = -FixedDiv(mobj->momz, time); //Z accel
9090 mobj->movecount = time >> FRACBITS;
9091 mobj->reactiontime = mobj->movecount;
9092 }
9093 else if ((mobj->extravalue1 & 3) == 1) // Swooping
9094 {
9095 mobj->reactiontime--;
9096 mobj->momz += mobj->extravalue2;
9097 if (mobj->reactiontime)
9098 return;
9099
9100 if (mobj->state - states == S_PTERABYTE_SWOOPDOWN)
9101 {
9102 P_SetMobjState(mobj, S_PTERABYTE_SWOOPUP);
9103 mobj->reactiontime = mobj->movecount;
9104 }
9105 else if (mobj->state - states == S_PTERABYTE_SWOOPUP)
9106 {
9107 P_SetMobjState(mobj, S_PTERABYTE_FLY1);
9108 mobj->extravalue1++;
9109 if (mobj->target && mobj->target->tracer != mobj)
9110 P_SetTarget(&mobj->target, NULL); // Failed to grab the target
9111 mobj->momx = mobj->momy = mobj->momz = 0;
9112 }
9113 }
9114 else // Returning
9115 {
9116 var1 = 2*mobj->info->speed;
9117 var2 = 1;
9118 A_HomingChase(mobj);
9119 if (P_AproxDistance(mobj->x - mobj->tracer->x, mobj->y - mobj->tracer->y) <= mobj->info->speed)
9120 {
9121 mobj->extravalue1 -= 2;
9122 mobj->momx = mobj->momy = mobj->momz = 0;
9123 }
9124 }
9125 }
9126
P_DragonbomberThink(mobj_t * mobj)9127 static void P_DragonbomberThink(mobj_t *mobj)
9128 {
9129 #define DRAGONTURNSPEED ANG2
9130 mobj->movecount = (mobj->movecount + 9) % 360;
9131 P_SetObjectMomZ(mobj, 4*FINESINE(((mobj->movecount*ANG1) >> ANGLETOFINESHIFT) & FINEMASK), false);
9132 if (mobj->threshold > 0) // are we dropping mines?
9133 {
9134 mobj->threshold--;
9135 if (mobj->threshold == 0) // if the timer hits 0, look for a mine to drop!
9136 {
9137 mobj_t *segment = mobj;
9138 while (segment->tracer != NULL && !P_MobjWasRemoved(segment->tracer) && segment->tracer->state == &states[segment->tracer->info->spawnstate])
9139 segment = segment->tracer;
9140 if (segment != mobj) // found an unactivated segment?
9141 {
9142 mobj_t *mine = P_SpawnMobjFromMobj(segment, 0, 0, 0, segment->info->painchance);
9143 mine->angle = segment->angle;
9144 P_InstaThrust(mine, mobj->angle, P_AproxDistance(mobj->momx, mobj->momy) >> 1);
9145 P_SetObjectMomZ(mine, -2*FRACUNIT, true);
9146 S_StartSound(mine, mine->info->seesound);
9147 P_SetMobjState(segment, segment->info->raisestate);
9148 mobj->threshold = mobj->info->painchance;
9149 }
9150 }
9151 }
9152 if (mobj->target) // Are we chasing a player?
9153 {
9154 fixed_t dist = P_AproxDistance(mobj->x - mobj->target->x, mobj->y - mobj->target->y);
9155 if (dist > 2000*mobj->scale) // Not anymore!
9156 P_SetTarget(&mobj->target, NULL);
9157 else
9158 {
9159 fixed_t vspeed = FixedMul(mobj->info->speed >> 3, mobj->scale);
9160 fixed_t z = mobj->target->z + (mobj->height >> 1) + (mobj->flags & MFE_VERTICALFLIP ? -128*mobj->scale : 128*mobj->scale + mobj->target->height);
9161 angle_t diff = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) - mobj->angle;
9162 if (diff > ANGLE_180)
9163 mobj->angle -= DRAGONTURNSPEED;
9164 else
9165 mobj->angle += DRAGONTURNSPEED;
9166 if (!mobj->threshold && dist < 512*mobj->scale) // Close enough to drop bombs
9167 {
9168 mobj->threshold = mobj->info->painchance;
9169 }
9170 mobj->momz += max(min(z - mobj->z, vspeed), -vspeed);
9171 }
9172 }
9173 else // Can we find a player to chase?
9174 {
9175 if (mobj->tracer == NULL || mobj->tracer->state != &states[mobj->tracer->info->spawnstate]
9176 || !P_LookForPlayers(mobj, true, false, 2000*mobj->scale)) // if not, circle around the spawnpoint
9177 {
9178 if (!mobj->spawnpoint) // unless we don't have one, in which case uhhh just circle around wherever we currently are I guess??
9179 mobj->angle += DRAGONTURNSPEED;
9180 else
9181 {
9182 boolean flip = mobj->spawnpoint->options & MTF_OBJECTFLIP;
9183 fixed_t vspeed = FixedMul(mobj->info->speed >> 3, mobj->scale);
9184 fixed_t x = mobj->spawnpoint->x << FRACBITS;
9185 fixed_t y = mobj->spawnpoint->y << FRACBITS;
9186 fixed_t z = (flip ? P_GetSectorCeilingZAt : P_GetSectorFloorZAt)(R_PointInSubsector(x, y)->sector, x, y) + (flip ? -1 : 1)*(mobj->spawnpoint->z << FRACBITS);
9187 angle_t diff = R_PointToAngle2(mobj->x, mobj->y, x, y) - mobj->angle;
9188 if (diff > ANGLE_180)
9189 mobj->angle -= DRAGONTURNSPEED;
9190 else
9191 mobj->angle += DRAGONTURNSPEED;
9192 mobj->momz += max(min(z - mobj->z, vspeed), -vspeed);
9193 }
9194 }
9195 }
9196 P_InstaThrust(mobj, mobj->angle, FixedMul(mobj->info->speed, mobj->scale));
9197 #undef DRAGONTURNSPEED
9198 }
9199
P_MobjRegularThink(mobj_t * mobj)9200 static boolean P_MobjRegularThink(mobj_t *mobj)
9201 {
9202 if ((mobj->flags & MF_ENEMY) && (mobj->state->nextstate == mobj->info->spawnstate && mobj->tics == 1))
9203 mobj->flags2 &= ~MF2_FRET;
9204
9205 if (mobj->eflags & MFE_TRACERANGLE)
9206 P_TracerAngleThink(mobj);
9207
9208 switch (mobj->type)
9209 {
9210 case MT_WALLSPIKEBASE:
9211 if (!mobj->target) {
9212 P_RemoveMobj(mobj);
9213 return false;
9214 }
9215 mobj->frame = (mobj->frame & ~FF_FRAMEMASK)|(mobj->target->frame & FF_FRAMEMASK);
9216 #if 0
9217 if (mobj->angle != mobj->target->angle + ANGLE_90) // reposition if not the correct angle
9218 {
9219 mobj_t* target = mobj->target; // shortcut
9220 const fixed_t baseradius = target->radius - (target->scale/2); //FixedMul(FRACUNIT/2, target->scale);
9221 P_UnsetThingPosition(mobj);
9222 mobj->x = target->x - P_ReturnThrustX(target, target->angle, baseradius);
9223 mobj->y = target->y - P_ReturnThrustY(target, target->angle, baseradius);
9224 P_SetThingPosition(mobj);
9225 mobj->angle = target->angle + ANGLE_90;
9226 }
9227 #endif
9228 break;
9229 case MT_FALLINGROCK:
9230 // Despawn rocks here in case zmovement code can't do so (blame slopes)
9231 if (!mobj->momx && !mobj->momy && !mobj->momz
9232 && ((mobj->eflags & MFE_VERTICALFLIP) ?
9233 mobj->z + mobj->height >= mobj->ceilingz
9234 : mobj->z <= mobj->floorz))
9235 {
9236 P_RemoveMobj(mobj);
9237 return false;
9238 }
9239 P_MobjCheckWater(mobj);
9240 break;
9241 case MT_ARROW:
9242 P_ArrowThink(mobj);
9243 break;
9244 case MT_EMERALDSPAWN:
9245 if (mobj->threshold)
9246 {
9247 mobj->threshold--;
9248
9249 if (!mobj->threshold && !mobj->target && mobj->reactiontime)
9250 {
9251 mobj_t *emerald = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->reactiontime);
9252 emerald->threshold = 42;
9253 P_SetTarget(&mobj->target, emerald);
9254 P_SetTarget(&emerald->target, mobj);
9255 }
9256 }
9257 break;
9258 case MT_BUGGLE:
9259 mobj->eflags |= MFE_UNDERWATER; //P_MobjCheckWater(mobj); // solely for MFE_UNDERWATER for A_FlickySpawn
9260 {
9261 if (mobj->tracer && mobj->tracer->player && mobj->tracer->health > 0
9262 && P_AproxDistance(P_AproxDistance(mobj->tracer->x - mobj->x, mobj->tracer->y - mobj->y), mobj->tracer->z - mobj->z) <= mobj->radius*16)
9263 {
9264 var1 = mobj->info->speed;
9265 var2 = 1;
9266
9267 // Home in on the target.
9268 A_HomingChase(mobj);
9269
9270 if (mobj->z < mobj->floorz)
9271 mobj->z = mobj->floorz;
9272
9273 if (leveltime % mobj->info->painchance == 0)
9274 S_StartSound(mobj, mobj->info->activesound);
9275
9276 if ((statenum_t)(mobj->state - states) != mobj->info->seestate)
9277 P_SetMobjState(mobj, mobj->info->seestate);
9278 }
9279 else
9280 {
9281 // Try to find a player
9282 P_LookForPlayers(mobj, true, true, mobj->radius*16);
9283 mobj->momx >>= 1;
9284 mobj->momy >>= 1;
9285 mobj->momz >>= 1;
9286 if ((statenum_t)(mobj->state - states) != mobj->info->spawnstate)
9287 P_SetMobjState(mobj, mobj->info->spawnstate);
9288 }
9289 }
9290 break;
9291 case MT_BUMBLEBORE:
9292 P_BumbleboreThink(mobj);
9293 break;
9294 case MT_BIGMINE:
9295 mobj->extravalue1 += 3;
9296 mobj->extravalue1 %= 360;
9297 P_UnsetThingPosition(mobj);
9298 mobj->z += FINESINE(mobj->extravalue1*(FINEMASK + 1)/360);
9299 P_SetThingPosition(mobj);
9300 break;
9301 case MT_FLAME:
9302 if (mobj->flags2 & MF2_BOSSNOTRAP)
9303 {
9304 if (!mobj->target || P_MobjWasRemoved(mobj->target))
9305 {
9306 if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
9307 P_RemoveMobj(mobj->tracer);
9308 P_RemoveMobj(mobj);
9309 return false;
9310 }
9311 mobj->z = mobj->target->z + mobj->target->momz;
9312 if (!(mobj->eflags & MFE_VERTICALFLIP))
9313 mobj->z += mobj->target->height;
9314 }
9315 if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer))
9316 {
9317 mobj->tracer->z = mobj->z + P_MobjFlip(mobj)*20*mobj->scale;
9318 if (mobj->eflags & MFE_VERTICALFLIP)
9319 mobj->tracer->z += mobj->height;
9320 }
9321 break;
9322 case MT_WAVINGFLAG1:
9323 case MT_WAVINGFLAG2:
9324 {
9325 fixed_t base = (leveltime << (FRACBITS + 1));
9326 mobj_t *seg = mobj->tracer, *prev = mobj;
9327 mobj->movedir = mobj->angle
9328 + ((((FINESINE((FixedAngle(base << 1) >> ANGLETOFINESHIFT) & FINEMASK)
9329 + FINESINE((FixedAngle(base << 4) >> ANGLETOFINESHIFT) & FINEMASK)) >> 1)
9330 + FINESINE((FixedAngle(base*9) >> ANGLETOFINESHIFT) & FINEMASK)
9331 + FINECOSINE(((FixedAngle(base*9)) >> ANGLETOFINESHIFT) & FINEMASK)) << 12); //*2^12
9332 while (seg)
9333 {
9334 seg->movedir = seg->angle;
9335 seg->angle = prev->movedir;
9336 P_UnsetThingPosition(seg);
9337 seg->x = prev->x + P_ReturnThrustX(prev, prev->angle, prev->radius);
9338 seg->y = prev->y + P_ReturnThrustY(prev, prev->angle, prev->radius);
9339 seg->z = prev->z + prev->height - (seg->scale >> 1);
9340 P_SetThingPosition(seg);
9341 prev = seg;
9342 seg = seg->tracer;
9343 }
9344 }
9345 break;
9346 case MT_SPINCUSHION:
9347 if (mobj->target && mobj->state - states >= S_SPINCUSHION_AIM1 && mobj->state - states <= S_SPINCUSHION_AIM5)
9348 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
9349 break;
9350 case MT_CRUSHCLAW:
9351 if (mobj->state - states == S_CRUSHCLAW_STAY && mobj->target)
9352 {
9353 mobj_t *chain = mobj->target->target;
9354 SINT8 sign = ((mobj->tics & 1) ? mobj->tics : -(SINT8)(mobj->tics));
9355 while (chain)
9356 {
9357 chain->z = chain->movefactor + sign*mobj->scale;
9358 sign = -sign;
9359 chain = chain->target;
9360 }
9361 }
9362 break;
9363 case MT_SMASHINGSPIKEBALL:
9364 mobj->momx = mobj->momy = 0;
9365 if (mobj->state - states == S_SMASHSPIKE_FALL && P_IsObjectOnGround(mobj))
9366 {
9367 P_SetMobjState(mobj, S_SMASHSPIKE_STOMP1);
9368 S_StartSound(mobj, sfx_spsmsh);
9369 }
9370 else if (mobj->state - states == S_SMASHSPIKE_RISE2 && P_MobjFlip(mobj)*(mobj->z - mobj->movecount) >= 0)
9371 {
9372 mobj->momz = 0;
9373 P_SetMobjState(mobj, S_SMASHSPIKE_FLOAT);
9374 }
9375 break;
9376 case MT_HANGSTER:
9377 if (!P_HangsterThink(mobj))
9378 return false;
9379 break;
9380 case MT_LHRT:
9381 mobj->momx = FixedMul(mobj->momx, mobj->extravalue2);
9382 mobj->momy = FixedMul(mobj->momy, mobj->extravalue2);
9383 break;
9384 case MT_EGGCAPSULE:
9385 if (!mobj->reactiontime)
9386 {
9387 // Target nearest player on your mare.
9388 // (You can make it float up/down by adding MF_FLOAT,
9389 // but beware level design pitfalls.)
9390 fixed_t shortest = 1024*FRACUNIT;
9391 INT32 i;
9392 P_SetTarget(&mobj->target, NULL);
9393 for (i = 0; i < MAXPLAYERS; i++)
9394 if (playeringame[i] && players[i].mo
9395 && players[i].mare == mobj->threshold && players[i].spheres > 0)
9396 {
9397 fixed_t dist = P_AproxDistance(players[i].mo->x - mobj->x, players[i].mo->y - mobj->y);
9398 if (dist < shortest)
9399 {
9400 P_SetTarget(&mobj->target, players[i].mo);
9401 shortest = dist;
9402 }
9403 }
9404 }
9405 break;
9406 case MT_EGGMOBILE2_POGO:
9407 if (!mobj->target
9408 || !mobj->target->health
9409 || mobj->target->state == &states[mobj->target->info->spawnstate]
9410 || mobj->target->state == &states[mobj->target->info->raisestate])
9411 {
9412 P_RemoveMobj(mobj);
9413 return false;
9414 }
9415 P_TeleportMove(mobj, mobj->target->x, mobj->target->y, mobj->target->z - mobj->height);
9416 break;
9417 case MT_HAMMER:
9418 if (mobj->z <= mobj->floorz)
9419 {
9420 P_RemoveMobj(mobj);
9421 return false;
9422 }
9423 break;
9424 case MT_KOOPA:
9425 P_KoopaThinker(mobj);
9426 break;
9427 case MT_FIREBALL:
9428 if (P_AproxDistance(mobj->momx, mobj->momy) <= 16*FRACUNIT) // Once fireballs lose enough speed, kill them
9429 {
9430 P_KillMobj(mobj, NULL, NULL, 0);
9431 return false;
9432 }
9433 break;
9434 case MT_REDRING:
9435 if (((mobj->z < mobj->floorz) || (mobj->z + mobj->height > mobj->ceilingz))
9436 && mobj->flags & MF_MISSILE)
9437 {
9438 P_ExplodeMissile(mobj);
9439 return false;
9440 }
9441 break;
9442 case MT_BOSSFLYPOINT:
9443 return false;
9444 case MT_NIGHTSCORE:
9445 mobj->color = (UINT16)(leveltime % SKINCOLOR_WHITE);
9446 break;
9447 case MT_JETFUME1:
9448 if (!P_JetFume1Think(mobj))
9449 return false;
9450 break;
9451 case MT_JETFLAME:
9452 {
9453 if (!mobj->target // if you have no target
9454 || (!(mobj->target->flags & MF_BOSS) && mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
9455 { // then remove yourself as well!
9456 P_RemoveMobj(mobj);
9457 return false;
9458 }
9459
9460 P_UnsetThingPosition(mobj);
9461 mobj->x = mobj->target->x;
9462 mobj->y = mobj->target->y;
9463 mobj->z = mobj->target->z - 50*mobj->target->scale;
9464 mobj->floorz = mobj->z;
9465 mobj->ceilingz = mobj->z + mobj->height;
9466 P_SetThingPosition(mobj);
9467 }
9468 break;
9469 case MT_EGGROBO1:
9470 if (!P_EggRobo1Think(mobj))
9471 return false;
9472 break;
9473 case MT_EGGROBO1JET:
9474 {
9475 if (!mobj->target || P_MobjWasRemoved(mobj->target) // if you have no target
9476 || (mobj->target->health <= 0)) // or your target isn't a boss and it's popped now
9477 { // then remove yourself as well!
9478 P_RemoveMobj(mobj);
9479 return false;
9480 }
9481
9482 mobj->flags2 ^= MF2_DONTDRAW;
9483
9484 P_UnsetThingPosition(mobj);
9485 mobj->x = mobj->target->x + P_ReturnThrustX(mobj, mobj->target->angle + ANGLE_90, mobj->movefactor*mobj->target->scale) - P_ReturnThrustX(mobj, mobj->target->angle, 19*mobj->target->scale);
9486 mobj->y = mobj->target->y + P_ReturnThrustY(mobj, mobj->target->angle + ANGLE_90, mobj->movefactor*mobj->target->scale) - P_ReturnThrustY(mobj, mobj->target->angle, 19*mobj->target->scale);
9487 mobj->z = mobj->target->z;
9488 if (mobj->target->eflags & MFE_VERTICALFLIP)
9489 mobj->z += (mobj->target->height - mobj->height);
9490 mobj->floorz = mobj->z;
9491 mobj->ceilingz = mobj->z + mobj->height;
9492 P_SetThingPosition(mobj);
9493 }
9494 break;
9495 case MT_NIGHTSDRONE:
9496 P_NiGHTSDroneThink(mobj);
9497 break;
9498 case MT_PLAYER:
9499 if (mobj->player)
9500 P_PlayerMobjThinker(mobj);
9501 return false;
9502 case MT_SKIM:
9503 // check mobj against possible water content, before movement code
9504 P_MobjCheckWater(mobj);
9505
9506 // Keep Skim at water surface
9507 if (mobj->z <= mobj->watertop)
9508 {
9509 mobj->flags |= MF_NOGRAVITY;
9510 if (mobj->z < mobj->watertop)
9511 {
9512 if (mobj->watertop - mobj->z <= FixedMul(mobj->info->speed*FRACUNIT, mobj->scale))
9513 mobj->z = mobj->watertop;
9514 else
9515 mobj->momz = FixedMul(mobj->info->speed*FRACUNIT, mobj->scale);
9516 }
9517 }
9518 else
9519 {
9520 mobj->flags &= ~MF_NOGRAVITY;
9521 if (mobj->z > mobj->watertop && mobj->z - mobj->watertop < FixedMul(MAXSTEPMOVE, mobj->scale))
9522 mobj->z = mobj->watertop;
9523 }
9524 break;
9525 case MT_RING:
9526 case MT_REDTEAMRING:
9527 case MT_BLUETEAMRING:
9528 P_KillRingsInLava(mobj);
9529 if (P_MobjWasRemoved(mobj))
9530 return false;
9531 /* FALLTHRU */
9532 case MT_COIN:
9533 case MT_BLUESPHERE:
9534 case MT_BOMBSPHERE:
9535 case MT_NIGHTSCHIP:
9536 case MT_NIGHTSSTAR:
9537 // No need to check water. Who cares?
9538 P_RingThinker(mobj);
9539 if (mobj->flags2 & MF2_NIGHTSPULL)
9540 P_NightsItemChase(mobj);
9541 else
9542 A_AttractChase(mobj);
9543 return false;
9544 // Flung items
9545 case MT_FLINGRING:
9546 P_KillRingsInLava(mobj);
9547 if (P_MobjWasRemoved(mobj))
9548 return false;
9549 /* FALLTHRU */
9550 case MT_FLINGCOIN:
9551 case MT_FLINGBLUESPHERE:
9552 case MT_FLINGNIGHTSCHIP:
9553 if (mobj->flags2 & MF2_NIGHTSPULL)
9554 P_NightsItemChase(mobj);
9555 else
9556 A_AttractChase(mobj);
9557 break;
9558 case MT_EMBLEM:
9559 if (mobj->flags2 & MF2_NIGHTSPULL)
9560 P_NightsItemChase(mobj);
9561 break;
9562 case MT_SHELL:
9563 if (mobj->threshold && mobj->threshold != TICRATE)
9564 mobj->threshold--;
9565
9566 if (mobj->threshold >= TICRATE)
9567 {
9568 mobj->angle += ((mobj->movedir == 1) ? ANGLE_22h : ANGLE_337h);
9569 P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), (mobj->info->speed*mobj->scale));
9570 }
9571 break;
9572 case MT_TURRET:
9573 if (!P_TurretThink(mobj))
9574 return false;
9575 break;
9576 case MT_BLUEFLAG:
9577 case MT_REDFLAG:
9578 {
9579 sector_t* sec2;
9580 sec2 = P_ThingOnSpecial3DFloor(mobj);
9581 if ((sec2 && GETSECSPECIAL(sec2->special, 4) == 2) || (GETSECSPECIAL(mobj->subsector->sector->special, 4) == 2))
9582 mobj->fuse = 1; // Return to base.
9583 break;
9584 }
9585 case MT_SPINDUST: // Spindash dust
9586 mobj->momx = FixedMul(mobj->momx, (3*FRACUNIT)/4); // originally 50000
9587 mobj->momy = FixedMul(mobj->momy, (3*FRACUNIT)/4); // same
9588 //mobj->momz = mobj->momz+P_MobjFlip(mobj)/3; // no meaningful change in value to be frank
9589 if (mobj->state >= &states[S_SPINDUST_BUBBLE1] && mobj->state <= &states[S_SPINDUST_BUBBLE4]) // bubble dust!
9590 {
9591 P_MobjCheckWater(mobj);
9592 if (mobj->watertop != mobj->subsector->sector->floorheight - 1000*FRACUNIT
9593 && mobj->z + mobj->height >= mobj->watertop - 5*FRACUNIT)
9594 mobj->flags2 |= MF2_DONTDRAW;
9595 }
9596 break;
9597 case MT_TRAINDUSTSPAWNER:
9598 if (leveltime % 5 == 0) {
9599 mobj_t* traindust = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_PARTICLE);
9600 traindust->flags = MF_SCENERY;
9601 P_SetMobjState(traindust, S_TRAINDUST);
9602 traindust->frame = P_RandomRange(0, 8)|FF_TRANS90;
9603 traindust->angle = mobj->angle;
9604 traindust->tics = TICRATE*4;
9605 traindust->destscale = FRACUNIT*64;
9606 traindust->scalespeed = FRACUNIT/24;
9607 P_SetScale(traindust, FRACUNIT*6);
9608 }
9609 break;
9610 case MT_TRAINSTEAMSPAWNER:
9611 if (leveltime % 5 == 0) {
9612 mobj_t *steam = P_SpawnMobj(mobj->x + FRACUNIT*P_SignedRandom()/2, mobj->y + FRACUNIT*P_SignedRandom()/2, mobj->z, MT_PARTICLE);
9613 P_SetMobjState(steam, S_TRAINSTEAM);
9614 steam->frame = P_RandomRange(0, 1)|FF_TRANS90;
9615 steam->tics = TICRATE*8;
9616 steam->destscale = FRACUNIT*64;
9617 steam->scalespeed = FRACUNIT/8;
9618 P_SetScale(steam, FRACUNIT*16);
9619 steam->momx = P_SignedRandom()*32;
9620 steam->momy = -64*FRACUNIT;
9621 steam->momz = 2*FRACUNIT;
9622 }
9623 break;
9624 case MT_CANARIVORE_GAS:
9625 {
9626 fixed_t momz;
9627
9628 if (mobj->flags2 & MF2_AMBUSH)
9629 {
9630 mobj->momx = FixedMul(mobj->momx, 50*FRACUNIT/51);
9631 mobj->momy = FixedMul(mobj->momy, 50*FRACUNIT/51);
9632 break;
9633 }
9634
9635 if (mobj->eflags & MFE_VERTICALFLIP)
9636 {
9637 if ((mobj->z + mobj->height + mobj->momz) <= mobj->ceilingz)
9638 break;
9639 }
9640 else
9641 {
9642 if ((mobj->z + mobj->momz) >= mobj->floorz)
9643 break;
9644 }
9645
9646 momz = abs(mobj->momz);
9647 if (R_PointToDist2(0, 0, mobj->momx, mobj->momy) < momz)
9648 P_InstaThrust(mobj, R_PointToAngle2(0, 0, mobj->momx, mobj->momy), momz);
9649 mobj->flags2 |= MF2_AMBUSH;
9650 break;
9651 }
9652 case MT_SALOONDOOR:
9653 if (!mobj->tracer) // Door center is gone or not spawned?
9654 {
9655 P_RemoveMobj(mobj); // Die
9656 return false;
9657 }
9658
9659 P_SaloonDoorThink(mobj);
9660 break;
9661 case MT_MINECARTSPAWNER:
9662 P_HandleMinecartSegments(mobj);
9663 if (!mobj->fuse || mobj->fuse > TICRATE)
9664 break;
9665 if (mobj->fuse == 2)
9666 {
9667 mobj->fuse = 0;
9668 break;
9669 }
9670 mobj->flags2 ^= MF2_DONTDRAW;
9671 break;
9672 case MT_LAVAFALLROCK:
9673 if (P_IsObjectOnGround(mobj))
9674 P_RemoveMobj(mobj);
9675 break;
9676 case MT_PYREFLY:
9677 P_PyreFlyThink(mobj);
9678 break;
9679 case MT_PTERABYTE:
9680 P_PterabyteThink(mobj);
9681 break;
9682 case MT_DRAGONBOMBER:
9683 P_DragonbomberThink(mobj);
9684 break;
9685 case MT_MINUS:
9686 if (P_IsObjectOnGround(mobj))
9687 mobj->rollangle = 0;
9688 else
9689 mobj->rollangle = R_PointToAngle2(0, 0, mobj->momz, (mobj->scale << 1) - min(abs(mobj->momz), mobj->scale << 1));
9690 break;
9691 case MT_SPINFIRE:
9692 if (mobj->flags & MF_NOGRAVITY)
9693 {
9694 if (mobj->eflags & MFE_VERTICALFLIP)
9695 mobj->z = mobj->ceilingz - mobj->height;
9696 else
9697 mobj->z = mobj->floorz;
9698 }
9699 else if ((!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z <= mobj->floorz)
9700 || ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height >= mobj->ceilingz))
9701 {
9702 mobj->flags |= MF_NOGRAVITY;
9703 mobj->momx = 8; // this is a hack which is used to ensure it still behaves as a missile and can damage others
9704 mobj->momy = mobj->momz = 0;
9705 mobj->z = ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->ceilingz - mobj->height : mobj->floorz);
9706 }
9707 /* FALLTHRU */
9708 default:
9709 // check mobj against possible water content, before movement code
9710 P_MobjCheckWater(mobj);
9711
9712 // Extinguish fire objects in water
9713 if ((mobj->flags & MF_FIRE) && !(mobj->eflags & MFE_TOUCHLAVA)
9714 && (mobj->eflags & (MFE_UNDERWATER|MFE_TOUCHWATER)))
9715 {
9716 P_KillMobj(mobj, NULL, NULL, 0);
9717 return false;
9718 }
9719 break;
9720 }
9721 return true;
9722 }
9723
P_FiringThink(mobj_t * mobj)9724 static void P_FiringThink(mobj_t *mobj)
9725 {
9726 if (!mobj->target)
9727 return;
9728
9729 if (mobj->health <= 0)
9730 return;
9731
9732 if (mobj->state->action.acp1 == (actionf_p1)A_Boss1Laser)
9733 {
9734 if (mobj->state->tics > 1)
9735 {
9736 var1 = mobj->state->var1;
9737 var2 = mobj->state->var2 & 65535;
9738 mobj->state->action.acp1(mobj);
9739 }
9740 }
9741 else if (leveltime & 1) // Fire mode
9742 {
9743 mobj_t *missile;
9744
9745 if (mobj->target->player && mobj->target->player->powers[pw_carry] == CR_NIGHTSMODE)
9746 {
9747 fixed_t oldval = mobjinfo[mobj->extravalue1].speed;
9748
9749 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x + mobj->target->momx, mobj->target->y + mobj->target->momy);
9750 mobjinfo[mobj->extravalue1].speed = FixedMul(60*FRACUNIT, mobj->scale);
9751 missile = P_SpawnMissile(mobj, mobj->target, mobj->extravalue1);
9752 mobjinfo[mobj->extravalue1].speed = oldval;
9753 }
9754 else
9755 {
9756 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
9757 missile = P_SpawnMissile(mobj, mobj->target, mobj->extravalue1);
9758 }
9759
9760 if (missile)
9761 {
9762 if (mobj->flags2 & MF2_SUPERFIRE)
9763 missile->flags2 |= MF2_SUPERFIRE;
9764
9765 if (mobj->info->attacksound)
9766 S_StartSound(missile, mobj->info->attacksound);
9767 }
9768 }
9769 else
9770 mobj->angle = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
9771 }
9772
P_MonitorFuseThink(mobj_t * mobj)9773 static void P_MonitorFuseThink(mobj_t *mobj)
9774 {
9775 mobj_t *newmobj;
9776
9777 // Special case for ALL monitors.
9778 // If a box's speed is nonzero, it's allowed to respawn as a WRM/SRM.
9779 if (mobj->info->speed != 0 && (mobj->flags2 & (MF2_AMBUSH|MF2_STRONGBOX)))
9780 {
9781 mobjtype_t spawnchance[64];
9782 INT32 numchoices = 0, i = 0;
9783
9784 // This define should make it a lot easier to organize and change monitor weights
9785 #define SETMONITORCHANCES(type, strongboxamt, weakboxamt) \
9786 for (i = ((mobj->flags2 & MF2_STRONGBOX) ? strongboxamt : weakboxamt); i; --i) spawnchance[numchoices++] = type
9787
9788 // Type SRM WRM
9789 SETMONITORCHANCES(MT_SNEAKERS_BOX, 0, 10); // Super Sneakers
9790 SETMONITORCHANCES(MT_INVULN_BOX, 2, 0); // Invincibility
9791 SETMONITORCHANCES(MT_WHIRLWIND_BOX, 3, 8); // Whirlwind Shield
9792 SETMONITORCHANCES(MT_ELEMENTAL_BOX, 3, 8); // Elemental Shield
9793 SETMONITORCHANCES(MT_ATTRACT_BOX, 2, 0); // Attraction Shield
9794 SETMONITORCHANCES(MT_FORCE_BOX, 3, 3); // Force Shield
9795 SETMONITORCHANCES(MT_ARMAGEDDON_BOX, 2, 0); // Armageddon Shield
9796 SETMONITORCHANCES(MT_MIXUP_BOX, 0, 1); // Teleporters
9797 SETMONITORCHANCES(MT_RECYCLER_BOX, 0, 1); // Recycler
9798 SETMONITORCHANCES(MT_1UP_BOX, 1, 1); // 1-Up
9799 // =======================================
9800 // Total 16 32
9801
9802 #undef SETMONITORCHANCES
9803
9804 i = P_RandomKey(numchoices); // Gotta love those random numbers!
9805 newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, spawnchance[i]);
9806 }
9807 else
9808 newmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobj->type);
9809
9810 // Transfer flags2 (ambush, strongbox, objectflip)
9811 newmobj->flags2 = mobj->flags2;
9812 P_RemoveMobj(mobj); // make sure they disappear
9813 }
9814
P_FlagFuseThink(mobj_t * mobj)9815 static void P_FlagFuseThink(mobj_t *mobj)
9816 {
9817 subsector_t *ss;
9818 fixed_t x, y, z;
9819 mobj_t* flagmo;
9820
9821 if (!mobj->spawnpoint)
9822 return;
9823
9824 x = mobj->spawnpoint->x << FRACBITS;
9825 y = mobj->spawnpoint->y << FRACBITS;
9826 z = mobj->spawnpoint->z << FRACBITS;
9827 ss = R_PointInSubsector(x, y);
9828 if (mobj->spawnpoint->options & MTF_OBJECTFLIP)
9829 z = ss->sector->ceilingheight - mobjinfo[mobj->type].height - z;
9830 else
9831 z = ss->sector->floorheight + z;
9832 flagmo = P_SpawnMobj(x, y, z, mobj->type);
9833 flagmo->spawnpoint = mobj->spawnpoint;
9834 if (mobj->spawnpoint->options & MTF_OBJECTFLIP)
9835 {
9836 flagmo->eflags |= MFE_VERTICALFLIP;
9837 flagmo->flags2 |= MF2_OBJECTFLIP;
9838 }
9839
9840 if (mobj->type == MT_REDFLAG)
9841 {
9842 if (!(mobj->flags2 & MF2_JUSTATTACKED))
9843 CONS_Printf(M_GetText("The \205Red flag\200 has returned to base.\n"));
9844
9845 // Assumedly in splitscreen players will be on opposing teams
9846 if (players[consoleplayer].ctfteam == 1 || splitscreen)
9847 S_StartSound(NULL, sfx_hoop1);
9848 else if (players[consoleplayer].ctfteam == 2)
9849 S_StartSound(NULL, sfx_hoop3);
9850
9851 redflag = flagmo;
9852 }
9853 else // MT_BLUEFLAG
9854 {
9855 if (!(mobj->flags2 & MF2_JUSTATTACKED))
9856 CONS_Printf(M_GetText("The \204Blue flag\200 has returned to base.\n"));
9857
9858 // Assumedly in splitscreen players will be on opposing teams
9859 if (players[consoleplayer].ctfteam == 2 || splitscreen)
9860 S_StartSound(NULL, sfx_hoop1);
9861 else if (players[consoleplayer].ctfteam == 1)
9862 S_StartSound(NULL, sfx_hoop3);
9863
9864 blueflag = flagmo;
9865 }
9866 }
9867
P_FuseThink(mobj_t * mobj)9868 static boolean P_FuseThink(mobj_t *mobj)
9869 {
9870 if (mobj->type == MT_SNAPPER_HEAD || mobj->type == MT_SNAPPER_LEG || mobj->type == MT_MINECARTSEG)
9871 mobj->flags2 ^= MF2_DONTDRAW;
9872
9873 mobj->fuse--;
9874
9875 if (mobj->fuse)
9876 return true;
9877
9878 if (LUAh_MobjFuse(mobj) || P_MobjWasRemoved(mobj))
9879 ;
9880 else if (mobj->info->flags & MF_MONITOR)
9881 {
9882 P_MonitorFuseThink(mobj);
9883 return false;
9884 }
9885 else switch (mobj->type)
9886 {
9887 // gargoyle and snowman handled in P_PushableThinker, not here
9888 case MT_THROWNGRENADE:
9889 case MT_CYBRAKDEMON_NAPALM_BOMB_LARGE:
9890 P_SetMobjState(mobj, mobj->info->deathstate);
9891 break;
9892 case MT_LHRT:
9893 P_KillMobj(mobj, NULL, NULL, 0);
9894 break;
9895 case MT_BLUEFLAG:
9896 case MT_REDFLAG:
9897 P_FlagFuseThink(mobj);
9898 P_RemoveMobj(mobj);
9899 return false;
9900 case MT_FANG:
9901 if (mobj->flags2 & MF2_SLIDEPUSH)
9902 {
9903 var1 = 0;
9904 var2 = 0;
9905 A_BossDeath(mobj);
9906 return false;
9907 }
9908 P_SetMobjState(mobj, mobj->state->nextstate);
9909 if (P_MobjWasRemoved(mobj))
9910 return false;
9911 break;
9912 case MT_METALSONIC_BATTLE:
9913 break; // don't remove
9914 case MT_SPIKE:
9915 P_SetMobjState(mobj, mobj->state->nextstate);
9916 mobj->fuse = mobj->info->speed;
9917 if (mobj->spawnpoint)
9918 mobj->fuse += mobj->spawnpoint->angle;
9919 break;
9920 case MT_WALLSPIKE:
9921 P_SetMobjState(mobj, mobj->state->nextstate);
9922 mobj->fuse = mobj->info->speed;
9923 if (mobj->spawnpoint)
9924 mobj->fuse += (mobj->spawnpoint->angle / 360);
9925 break;
9926 case MT_NIGHTSCORE:
9927 P_RemoveMobj(mobj);
9928 return false;
9929 case MT_LAVAFALL:
9930 if (mobj->state - states == S_LAVAFALL_DORMANT)
9931 {
9932 mobj->fuse = 30;
9933 P_SetMobjState(mobj, S_LAVAFALL_TELL);
9934 S_StartSound(mobj, mobj->info->seesound);
9935 }
9936 else if (mobj->state - states == S_LAVAFALL_TELL)
9937 {
9938 mobj->fuse = 40;
9939 P_SetMobjState(mobj, S_LAVAFALL_SHOOT);
9940 S_StopSound(mobj);
9941 S_StartSound(mobj, mobj->info->attacksound);
9942 }
9943 else
9944 {
9945 mobj->fuse = 30;
9946 P_SetMobjState(mobj, S_LAVAFALL_DORMANT);
9947 S_StopSound(mobj);
9948 }
9949 return false;
9950 case MT_PYREFLY:
9951 if (mobj->health <= 0)
9952 break;
9953
9954 mobj->extravalue2 = (mobj->extravalue2 + 1) % 3;
9955 if (mobj->extravalue2 == 0)
9956 {
9957 P_SetMobjState(mobj, mobj->info->spawnstate);
9958 mobj->fuse = 100;
9959 S_StopSound(mobj);
9960 S_StartSound(mobj, sfx_s3k8c);
9961 }
9962 else if (mobj->extravalue2 == 1)
9963 {
9964 mobj->fuse = 50;
9965 S_StartSound(mobj, sfx_s3ka3);
9966 }
9967 else
9968 {
9969 P_SetMobjState(mobj, mobj->info->meleestate);
9970 mobj->fuse = 100;
9971 S_StopSound(mobj);
9972 S_StartSound(mobj, sfx_s3kc2l);
9973 }
9974 return false;
9975 case MT_PLAYER:
9976 break; // don't remove
9977 default:
9978 P_SetMobjState(mobj, mobj->info->xdeathstate); // will remove the mobj if S_NULL.
9979 break;
9980 // Looking for monitors? They moved to a special condition above.
9981 }
9982
9983 return !P_MobjWasRemoved(mobj);
9984 }
9985
9986 //
9987 // P_MobjThinker
9988 //
P_MobjThinker(mobj_t * mobj)9989 void P_MobjThinker(mobj_t *mobj)
9990 {
9991 I_Assert(mobj != NULL);
9992 I_Assert(!P_MobjWasRemoved(mobj));
9993
9994 if (mobj->flags & MF_NOTHINK)
9995 return;
9996
9997 if ((mobj->flags & MF_BOSS) && mobj->spawnpoint && (bossdisabled & (1<<mobj->spawnpoint->extrainfo)))
9998 return;
9999
10000 // Remove dead target/tracer.
10001 if (mobj->target && P_MobjWasRemoved(mobj->target))
10002 P_SetTarget(&mobj->target, NULL);
10003 if (mobj->tracer && P_MobjWasRemoved(mobj->tracer))
10004 P_SetTarget(&mobj->tracer, NULL);
10005 if (mobj->hnext && P_MobjWasRemoved(mobj->hnext))
10006 P_SetTarget(&mobj->hnext, NULL);
10007 if (mobj->hprev && P_MobjWasRemoved(mobj->hprev))
10008 P_SetTarget(&mobj->hprev, NULL);
10009
10010 mobj->eflags &= ~(MFE_PUSHED|MFE_SPRUNG);
10011
10012 tmfloorthing = tmhitthing = NULL;
10013
10014 // Sector special (2,8) allows ANY mobj to trigger a linedef exec
10015 if (mobj->subsector && GETSECSPECIAL(mobj->subsector->sector->special, 2) == 8)
10016 {
10017 sector_t *sec2 = P_ThingOnSpecial3DFloor(mobj);
10018 if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
10019 {
10020 mtag_t tag = Tag_FGet(&sec2->tags);
10021 P_LinedefExecute(tag, mobj, sec2);
10022 }
10023 }
10024
10025 if (mobj->scale != mobj->destscale)
10026 P_MobjScaleThink(mobj); // Slowly scale up/down to reach your destscale.
10027
10028 if ((mobj->type == MT_GHOST || mobj->type == MT_THOK) && mobj->fuse > 0) // Not guaranteed to be MF_SCENERY or not MF_SCENERY!
10029 {
10030 if (mobj->flags2 & MF2_BOSSNOTRAP) // "fast" flag
10031 {
10032 if ((signed)((mobj->frame & FF_TRANSMASK) >> FF_TRANSSHIFT) < (NUMTRANSMAPS-1) - (2*mobj->fuse)/3)
10033 // fade out when nearing the end of fuse...
10034 mobj->frame = (mobj->frame & ~FF_TRANSMASK) | (((NUMTRANSMAPS-1) - (2*mobj->fuse)/3) << FF_TRANSSHIFT);
10035 }
10036 else
10037 {
10038 if ((signed)((mobj->frame & FF_TRANSMASK) >> FF_TRANSSHIFT) < (NUMTRANSMAPS-1) - mobj->fuse / 2)
10039 // fade out when nearing the end of fuse...
10040 mobj->frame = (mobj->frame & ~FF_TRANSMASK) | (((NUMTRANSMAPS-1) - mobj->fuse / 2) << FF_TRANSSHIFT);
10041 }
10042 }
10043
10044 // Special thinker for scenery objects
10045 if (mobj->flags & MF_SCENERY)
10046 {
10047 P_MobjSceneryThink(mobj);
10048 return;
10049 }
10050
10051 // Check for a Lua thinker first
10052 if (!mobj->player)
10053 {
10054 if (LUAh_MobjThinker(mobj) || P_MobjWasRemoved(mobj))
10055 return;
10056 }
10057 else if (!mobj->player->spectator)
10058 {
10059 // You cannot short-circuit the player thinker like you can other thinkers.
10060 LUAh_MobjThinker(mobj);
10061 if (P_MobjWasRemoved(mobj))
10062 return;
10063 }
10064
10065 // if it's pushable, or if it would be pushable other than temporary disablement, use the
10066 // separate thinker
10067 if (mobj->flags & MF_PUSHABLE || (mobj->info->flags & MF_PUSHABLE && mobj->fuse))
10068 {
10069 if (!P_MobjPushableThink(mobj))
10070 return;
10071 }
10072 else if (mobj->flags & MF_BOSS)
10073 {
10074 if (!P_MobjBossThink(mobj))
10075 return;
10076 }
10077 else if (mobj->health <= 0) // Dead things think differently than the living.
10078 {
10079 if (!P_MobjDeadThink(mobj))
10080 return;
10081 }
10082 else
10083 {
10084 if (!P_MobjRegularThink(mobj))
10085 return;
10086 }
10087 if (P_MobjWasRemoved(mobj))
10088 return;
10089
10090 if (mobj->flags2 & MF2_FIRING)
10091 P_FiringThink(mobj);
10092
10093 if (mobj->flags & MF_AMBIENT)
10094 {
10095 if (!(leveltime % mobj->health) && mobj->info->seesound)
10096 S_StartSound(mobj, mobj->info->seesound);
10097 return;
10098 }
10099
10100 // Check fuse
10101 if (mobj->fuse && !P_FuseThink(mobj))
10102 return;
10103
10104 I_Assert(mobj != NULL);
10105 I_Assert(!P_MobjWasRemoved(mobj));
10106
10107 if (mobj->momx || mobj->momy || (mobj->flags2 & MF2_SKULLFLY))
10108 {
10109 P_XYMovement(mobj);
10110 if (P_MobjWasRemoved(mobj))
10111 return;
10112 }
10113
10114 // always do the gravity bit now, that's simpler
10115 // BUT CheckPosition only if wasn't done before.
10116 if (!(mobj->eflags & MFE_ONGROUND) || mobj->momz
10117 || ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height != mobj->ceilingz)
10118 || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z != mobj->floorz)
10119 || P_IsObjectInGoop(mobj))
10120 {
10121 if (!P_ZMovement(mobj))
10122 return; // mobj was removed
10123 P_CheckPosition(mobj, mobj->x, mobj->y); // Need this to pick up objects!
10124 if (P_MobjWasRemoved(mobj))
10125 return;
10126 }
10127 else
10128 {
10129 mobj->pmomz = 0; // to prevent that weird rocketing gargoyle bug
10130 mobj->eflags &= ~MFE_JUSTHITFLOOR;
10131 }
10132
10133 // Sliding physics for slidey mobjs!
10134 if (mobj->type == MT_FLINGRING
10135 || mobj->type == MT_FLINGCOIN
10136 || mobj->type == MT_FLINGBLUESPHERE
10137 || mobj->type == MT_FLINGNIGHTSCHIP
10138 || P_WeaponOrPanel(mobj->type)
10139 || mobj->type == MT_FLINGEMERALD
10140 || mobj->type == MT_BIGTUMBLEWEED
10141 || mobj->type == MT_LITTLETUMBLEWEED
10142 || mobj->type == MT_CANNONBALLDECOR
10143 || mobj->type == MT_FALLINGROCK) {
10144 P_TryMove(mobj, mobj->x, mobj->y, true); // Sets mo->standingslope correctly
10145 //if (mobj->standingslope) CONS_Printf("slope physics on mobj\n");
10146 P_ButteredSlope(mobj);
10147 }
10148
10149 if (mobj->flags & (MF_ENEMY|MF_BOSS) && mobj->health
10150 && P_CheckDeathPitCollide(mobj)) // extra pit check in case these didn't have momz
10151 {
10152 P_KillMobj(mobj, NULL, NULL, DMG_DEATHPIT);
10153 return;
10154 }
10155
10156 // Crush enemies!
10157 if (mobj->ceilingz - mobj->floorz < mobj->height)
10158 {
10159 if ((
10160 (mobj->flags & (MF_ENEMY|MF_BOSS)
10161 && mobj->flags & MF_SHOOTABLE)
10162 || mobj->type == MT_EGGSHIELD)
10163 && !(mobj->flags & MF_NOCLIPHEIGHT)
10164 && mobj->health > 0)
10165 {
10166 P_KillMobj(mobj, NULL, NULL, DMG_CRUSHED);
10167 return;
10168 }
10169 }
10170
10171 // Can end up here if a player dies.
10172 if (mobj->player)
10173 P_CyclePlayerMobjState(mobj);
10174 else
10175 P_CycleMobjState(mobj);
10176
10177 if (P_MobjWasRemoved(mobj))
10178 return;
10179
10180 switch (mobj->type)
10181 {
10182 case MT_BOUNCEPICKUP:
10183 case MT_RAILPICKUP:
10184 case MT_AUTOPICKUP:
10185 case MT_EXPLODEPICKUP:
10186 case MT_SCATTERPICKUP:
10187 case MT_GRENADEPICKUP:
10188 if (mobj->health == 0) // Fading tile
10189 {
10190 INT32 value = mobj->info->damage/10;
10191 value = mobj->fuse/value;
10192 value = 10-value;
10193 value--;
10194
10195 if (value <= 0)
10196 value = 1;
10197
10198 mobj->frame &= ~FF_TRANSMASK;
10199 mobj->frame |= value << FF_TRANSSHIFT;
10200 }
10201 break;
10202 default:
10203 break;
10204 }
10205 }
10206
10207 // Quick, optimized function for the Rail Rings
10208 // Returns true if move failed or mobj was removed by movement (death pit, missile hits wall, etc.)
P_RailThinker(mobj_t * mobj)10209 boolean P_RailThinker(mobj_t *mobj)
10210 {
10211 fixed_t x, y, z;
10212
10213 I_Assert(mobj != NULL);
10214 I_Assert(!P_MobjWasRemoved(mobj));
10215
10216 x = mobj->x, y = mobj->y, z = mobj->z;
10217
10218 if (mobj->momx || mobj->momy)
10219 {
10220 P_XYMovement(mobj);
10221 if (P_MobjWasRemoved(mobj))
10222 return true;
10223 }
10224
10225 if (mobj->momz)
10226 {
10227 if (!P_ZMovement(mobj))
10228 return true; // mobj was removed
10229 //P_CheckPosition(mobj, mobj->x, mobj->y);
10230 }
10231
10232 return P_MobjWasRemoved(mobj) || (x == mobj->x && y == mobj->y && z == mobj->z);
10233 }
10234
10235 // Unquick, unoptimized function for pushables
P_PushableThinker(mobj_t * mobj)10236 void P_PushableThinker(mobj_t *mobj)
10237 {
10238 sector_t *sec;
10239
10240 I_Assert(mobj != NULL);
10241 I_Assert(!P_MobjWasRemoved(mobj));
10242
10243 sec = mobj->subsector->sector;
10244
10245 if (GETSECSPECIAL(sec->special, 2) == 1 && mobj->z == sec->floorheight)
10246 {
10247 mtag_t tag = Tag_FGet(&sec->tags);
10248 P_LinedefExecute(tag, mobj, sec);
10249 }
10250
10251 // else if (GETSECSPECIAL(sec->special, 2) == 8)
10252 {
10253 sector_t *sec2 = P_ThingOnSpecial3DFloor(mobj);
10254 if (sec2 && GETSECSPECIAL(sec2->special, 2) == 1)
10255 {
10256 mtag_t tag = Tag_FGet(&sec2->tags);
10257 P_LinedefExecute(tag, mobj, sec2);
10258 }
10259 }
10260
10261 // it has to be pushable RIGHT NOW for this part to happen
10262 if (mobj->flags & MF_PUSHABLE && !(mobj->momx || mobj->momy))
10263 P_TryMove(mobj, mobj->x, mobj->y, true);
10264
10265 if (mobj->type == MT_MINECART && mobj->health)
10266 {
10267 // If player is ded, remove this minecart
10268 if (!mobj->target || P_MobjWasRemoved(mobj->target) || !mobj->target->health || !mobj->target->player || mobj->target->player->powers[pw_carry] != CR_MINECART)
10269 {
10270 P_KillMobj(mobj, NULL, NULL, 0);
10271 return;
10272 }
10273 }
10274
10275 if (mobj->fuse == 1) // it would explode in the MobjThinker code
10276 {
10277 mobj_t *spawnmo;
10278 fixed_t x, y, z;
10279 subsector_t *ss;
10280
10281 // Left here just in case we'd
10282 // want to make pushable bombs
10283 // or something in the future.
10284 switch (mobj->type)
10285 {
10286 case MT_SNOWMAN:
10287 case MT_GARGOYLE:
10288 x = mobj->spawnpoint->x << FRACBITS;
10289 y = mobj->spawnpoint->y << FRACBITS;
10290
10291 ss = R_PointInSubsector(x, y);
10292
10293 if (mobj->spawnpoint->z != 0)
10294 z = mobj->spawnpoint->z << FRACBITS;
10295 else
10296 z = ss->sector->floorheight;
10297
10298 spawnmo = P_SpawnMobj(x, y, z, mobj->type);
10299 spawnmo->spawnpoint = mobj->spawnpoint;
10300 P_UnsetThingPosition(spawnmo);
10301 spawnmo->flags = mobj->flags;
10302 P_SetThingPosition(spawnmo);
10303 spawnmo->flags2 = mobj->flags2;
10304 spawnmo->flags |= MF_PUSHABLE;
10305 P_RemoveMobj(mobj);
10306 break;
10307 default:
10308 break;
10309 }
10310 }
10311 }
10312
10313 // Quick, optimized function for scenery
P_SceneryThinker(mobj_t * mobj)10314 void P_SceneryThinker(mobj_t *mobj)
10315 {
10316 if (mobj->flags & MF_BOXICON)
10317 {
10318 if (!(mobj->eflags & MFE_VERTICALFLIP))
10319 {
10320 if (mobj->z < mobj->floorz + FixedMul(mobj->info->damage, mobj->scale))
10321 mobj->momz = FixedMul(mobj->info->speed, mobj->scale);
10322 else
10323 mobj->momz = 0;
10324 }
10325 else
10326 {
10327 if (mobj->z + FixedMul(mobj->info->height, mobj->scale) > mobj->ceilingz - FixedMul(mobj->info->damage, mobj->scale))
10328 mobj->momz = -FixedMul(mobj->info->speed, mobj->scale);
10329 else
10330 mobj->momz = 0;
10331 }
10332 }
10333
10334 // momentum movement
10335 if (mobj->momx || mobj->momy)
10336 {
10337 P_SceneryXYMovement(mobj);
10338
10339 if (P_MobjWasRemoved(mobj))
10340 return;
10341 }
10342
10343 // always do the gravity bit now, that's simpler
10344 // BUT CheckPosition only if wasn't done before.
10345 if (!(mobj->eflags & MFE_ONGROUND) || mobj->momz
10346 || ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z + mobj->height != mobj->ceilingz)
10347 || (!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z != mobj->floorz)
10348 || P_IsObjectInGoop(mobj))
10349 {
10350 if (!P_SceneryZMovement(mobj))
10351 return; // mobj was removed
10352 P_CheckPosition(mobj, mobj->x, mobj->y); // Need this to pick up objects!
10353 if (P_MobjWasRemoved(mobj))
10354 return;
10355 mobj->floorz = tmfloorz;
10356 mobj->ceilingz = tmceilingz;
10357 mobj->floorrover = tmfloorrover;
10358 mobj->ceilingrover = tmceilingrover;
10359 }
10360 else
10361 {
10362 mobj->pmomz = 0; // to prevent that weird rocketing gargoyle bug
10363 mobj->eflags &= ~MFE_JUSTHITFLOOR;
10364 }
10365
10366 P_CycleMobjState(mobj);
10367 }
10368
10369 //
10370 // GAME SPAWN FUNCTIONS
10371 //
10372
P_DefaultMobjShadowScale(mobj_t * thing)10373 static fixed_t P_DefaultMobjShadowScale (mobj_t *thing)
10374 {
10375 switch (thing->type)
10376 {
10377 case MT_PLAYER:
10378 case MT_ROLLOUTROCK:
10379
10380 case MT_EGGMOBILE4_MACE:
10381 case MT_SMALLMACE:
10382 case MT_BIGMACE:
10383
10384 case MT_SMALLGRABCHAIN:
10385 case MT_BIGGRABCHAIN:
10386
10387 case MT_YELLOWSPRINGBALL:
10388 case MT_REDSPRINGBALL:
10389
10390 return FRACUNIT;
10391
10392 case MT_RING:
10393 case MT_FLINGRING:
10394
10395 case MT_BLUESPHERE:
10396 case MT_FLINGBLUESPHERE:
10397 case MT_BOMBSPHERE:
10398
10399 case MT_REDTEAMRING:
10400 case MT_BLUETEAMRING:
10401 case MT_REDFLAG:
10402 case MT_BLUEFLAG:
10403
10404 case MT_EMBLEM:
10405
10406 case MT_TOKEN:
10407 case MT_EMERALD1:
10408 case MT_EMERALD2:
10409 case MT_EMERALD3:
10410 case MT_EMERALD4:
10411 case MT_EMERALD5:
10412 case MT_EMERALD6:
10413 case MT_EMERALD7:
10414 case MT_EMERHUNT:
10415 case MT_FLINGEMERALD:
10416
10417 return 2*FRACUNIT/3;
10418
10419 default:
10420
10421 if (thing->flags & (MF_ENEMY|MF_BOSS))
10422 return FRACUNIT;
10423 else
10424 return 0;
10425 }
10426 }
10427
10428 //
10429 // P_SpawnMobj
10430 //
P_SpawnMobj(fixed_t x,fixed_t y,fixed_t z,mobjtype_t type)10431 mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
10432 {
10433 const mobjinfo_t *info = &mobjinfo[type];
10434 SINT8 sc = -1;
10435 state_t *st;
10436 mobj_t *mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
10437
10438 // this is officially a mobj, declared as soon as possible.
10439 mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker;
10440 mobj->type = type;
10441 mobj->info = info;
10442
10443 mobj->x = x;
10444 mobj->y = y;
10445
10446 mobj->radius = info->radius;
10447 mobj->height = info->height;
10448 mobj->flags = info->flags;
10449
10450 mobj->health = (info->spawnhealth ? info->spawnhealth : 1);
10451
10452 mobj->reactiontime = info->reactiontime;
10453
10454 mobj->lastlook = -1; // stuff moved in P_enemy.P_LookForPlayer
10455
10456 // do not set the state with P_SetMobjState,
10457 // because action routines can not be called yet
10458 st = &states[info->spawnstate];
10459
10460 mobj->state = st;
10461 mobj->tics = st->tics;
10462 mobj->sprite = st->sprite;
10463 mobj->frame = st->frame; // FF_FRAMEMASK for frame, and other bits..
10464 P_SetupStateAnimation(mobj, st);
10465
10466 mobj->friction = ORIG_FRICTION;
10467
10468 mobj->movefactor = FRACUNIT;
10469
10470 // All mobjs are created at 100% scale.
10471 mobj->scale = FRACUNIT;
10472 mobj->destscale = mobj->scale;
10473 mobj->scalespeed = FRACUNIT/12;
10474
10475 // TODO: Make this a special map header
10476 if ((maptol & TOL_ERZ3) && !(mobj->type == MT_BLACKEGGMAN))
10477 mobj->destscale = FRACUNIT/2;
10478
10479 // Sprite rendering
10480 mobj->blendmode = AST_TRANSLUCENT;
10481 mobj->spritexscale = mobj->spriteyscale = mobj->scale;
10482 mobj->spritexoffset = mobj->spriteyoffset = 0;
10483 mobj->floorspriteslope = NULL;
10484
10485 // set subsector and/or block links
10486 P_SetThingPosition(mobj);
10487 I_Assert(mobj->subsector != NULL);
10488
10489 mobj->floorz = P_GetSectorFloorZAt (mobj->subsector->sector, x, y);
10490 mobj->ceilingz = P_GetSectorCeilingZAt(mobj->subsector->sector, x, y);
10491
10492 mobj->floorrover = NULL;
10493 mobj->ceilingrover = NULL;
10494
10495 // Tells MobjCheckWater that the water height was not set.
10496 mobj->watertop = INT32_MAX;
10497
10498 if (z == ONFLOORZ)
10499 {
10500 mobj->z = mobj->floorz;
10501
10502 if (mobj->type == MT_UNIDUS)
10503 mobj->z += FixedMul(mobj->info->mass, mobj->scale);
10504
10505 // defaults onground
10506 if (mobj->z == mobj->floorz)
10507 mobj->eflags |= MFE_ONGROUND;
10508 }
10509 else if (z == ONCEILINGZ)
10510 {
10511 mobj->z = mobj->ceilingz - mobj->height;
10512
10513 if (mobj->type == MT_UNIDUS)
10514 mobj->z -= FixedMul(mobj->info->mass, mobj->scale);
10515
10516 // defaults onground
10517 if (mobj->z + mobj->height == mobj->ceilingz)
10518 mobj->eflags |= MFE_ONGROUND;
10519 }
10520 else
10521 mobj->z = z;
10522
10523 // Set shadowscale here, before spawn hook so that Lua can change it
10524 mobj->shadowscale = P_DefaultMobjShadowScale(mobj);
10525
10526 // DANGER! This can cause P_SpawnMobj to return NULL!
10527 // Avoid using P_RemoveMobj on the newly created mobj in "MobjSpawn" Lua hooks!
10528 if (LUAh_MobjSpawn(mobj))
10529 {
10530 if (P_MobjWasRemoved(mobj))
10531 return NULL;
10532 }
10533 else if (P_MobjWasRemoved(mobj))
10534 return NULL;
10535 else
10536 switch (mobj->type)
10537 {
10538 case MT_ALTVIEWMAN:
10539 if (titlemapinaction) mobj->flags &= ~MF_NOTHINK;
10540 break;
10541 case MT_LOCKONINF:
10542 P_SetScale(mobj, (mobj->destscale = 3*mobj->scale));
10543 break;
10544 case MT_CYBRAKDEMON_NAPALM_BOMB_LARGE:
10545 mobj->fuse = mobj->info->painchance;
10546 break;
10547 case MT_BLACKEGGMAN:
10548 {
10549 mobj_t *spawn = P_SpawnMobj(mobj->x, mobj->z, mobj->z+mobj->height-16*FRACUNIT, MT_BLACKEGGMAN_HELPER);
10550 spawn->destscale = mobj->scale;
10551 P_SetScale(spawn, mobj->scale);
10552 P_SetTarget(&spawn->target, mobj);
10553 }
10554 break;
10555 case MT_FAKEMOBILE:
10556 case MT_EGGSHIELD:
10557 mobj->flags2 |= MF2_INVERTAIMABLE;
10558 break;
10559 case MT_DETON:
10560 mobj->movedir = 0;
10561 break;
10562 case MT_EGGGUARD:
10563 {
10564 mobj_t *spawn = P_SpawnMobj(x, y, z, MT_EGGSHIELD);
10565 spawn->destscale = mobj->scale;
10566 P_SetScale(spawn, mobj->scale);
10567 P_SetTarget(&mobj->tracer, spawn);
10568 P_SetTarget(&spawn->target, mobj);
10569 }
10570 break;
10571 case MT_UNIDUS:
10572 {
10573 INT32 i;
10574 mobj_t *ball;
10575 // Spawn "damage" number of "painchance" spikeball mobjs
10576 // threshold is the distance they should keep from the MT_UNIDUS (touching radius + ball painchance)
10577 for (i = 0; i < mobj->info->damage; i++)
10578 {
10579 ball = P_SpawnMobj(x, y, z, mobj->info->painchance);
10580 ball->destscale = mobj->scale;
10581 P_SetScale(ball, mobj->scale);
10582 P_SetTarget(&ball->target, mobj);
10583 ball->movedir = FixedAngle(FixedMul(FixedDiv(i<<FRACBITS, mobj->info->damage<<FRACBITS), 360<<FRACBITS));
10584 ball->threshold = ball->radius + mobj->radius + FixedMul(ball->info->painchance, ball->scale);
10585
10586 var1 = ball->state->var1, var2 = ball->state->var2;
10587 ball->state->action.acp1(ball);
10588 }
10589 }
10590 break;
10591 case MT_POINTY:
10592 {
10593 INT32 q;
10594 mobj_t *ball, *lastball = mobj;
10595
10596 for (q = 0; q < mobj->info->painchance; q++)
10597 {
10598 ball = P_SpawnMobj(x, y, z, mobj->info->mass);
10599 ball->destscale = mobj->scale;
10600 P_SetScale(ball, mobj->scale);
10601 P_SetTarget(&lastball->tracer, ball);
10602 P_SetTarget(&ball->target, mobj);
10603 lastball = ball;
10604 }
10605 }
10606 break;
10607 case MT_CRUSHSTACEAN:
10608 {
10609 mobj_t *bigmeatyclaw = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_CRUSHCLAW);
10610 bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);;
10611 P_SetTarget(&mobj->tracer, bigmeatyclaw);
10612 P_SetTarget(&bigmeatyclaw->tracer, mobj);
10613 mobj->reactiontime >>= 1;
10614 }
10615 break;
10616 case MT_BANPYURA:
10617 {
10618 mobj_t *bigmeatyclaw = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_BANPSPRING);
10619 bigmeatyclaw->angle = mobj->angle + ((mobj->flags2 & MF2_AMBUSH) ? ANGLE_90 : ANGLE_270);;
10620 P_SetTarget(&mobj->tracer, bigmeatyclaw);
10621 P_SetTarget(&bigmeatyclaw->tracer, mobj);
10622 mobj->reactiontime >>= 1;
10623 }
10624 break;
10625 case MT_BIGMINE:
10626 mobj->extravalue1 = FixedHypot(mobj->x, mobj->y)>>FRACBITS;
10627 break;
10628 case MT_WAVINGFLAG1:
10629 case MT_WAVINGFLAG2:
10630 {
10631 mobj_t *prev = mobj, *cur;
10632 UINT8 i;
10633 for (i = 0; i <= 16; i++) // probably should be < but staying authentic to the Lua version
10634 {
10635 cur = P_SpawnMobjFromMobj(mobj, 0, 0, 0, ((mobj->type == MT_WAVINGFLAG1) ? MT_WAVINGFLAGSEG1 : MT_WAVINGFLAGSEG2));;
10636 P_SetTarget(&prev->tracer, cur);
10637 cur->extravalue1 = i;
10638 prev = cur;
10639 }
10640 }
10641 break;
10642 case MT_EGGMOBILE2:
10643 // Special condition for the 2nd boss.
10644 mobj->watertop = mobj->info->speed;
10645 break;
10646 case MT_EGGMOBILE3:
10647 mobj->movefactor = -512*FRACUNIT;
10648 mobj->flags2 |= MF2_CLASSICPUSH;
10649 break;
10650 case MT_EGGMOBILE4:
10651 mobj->flags2 |= MF2_INVERTAIMABLE;
10652 break;
10653 case MT_FLICKY_08:
10654 mobj->color = (P_RandomChance(FRACUNIT/2) ? SKINCOLOR_RED : SKINCOLOR_AQUA);
10655 break;
10656 case MT_BALLOON:
10657 mobj->color = SKINCOLOR_RED;
10658 break;
10659 case MT_EGGROBO1:
10660 mobj->movecount = P_RandomKey(13);
10661 mobj->color = SKINCOLOR_RUBY + P_RandomKey(numskincolors - SKINCOLOR_RUBY);
10662 break;
10663 case MT_HIVEELEMENTAL:
10664 mobj->extravalue1 = 5;
10665 break;
10666 case MT_SMASHINGSPIKEBALL:
10667 mobj->movecount = mobj->z;
10668 break;
10669 case MT_SPINBOBERT:
10670 {
10671 mobj_t *fire;
10672 fire = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_SPINBOBERT_FIRE1);
10673 P_SetTarget(&fire->target, mobj);
10674 P_SetTarget(&mobj->hnext, fire);
10675 fire = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_SPINBOBERT_FIRE2);
10676 P_SetTarget(&fire->target, mobj);
10677 P_SetTarget(&mobj->hprev, fire);
10678 }
10679 break;
10680 case MT_REDRING: // Make MT_REDRING red by default
10681 mobj->color = skincolor_redring;
10682 break;
10683 case MT_SMALLBUBBLE: // Bubbles eventually dissipate, in case they get caught somewhere.
10684 case MT_MEDIUMBUBBLE:
10685 case MT_EXTRALARGEBUBBLE:
10686 mobj->fuse += 30 * TICRATE;
10687 break;
10688 case MT_NIGHTSDRONE:
10689 nummaprings = -1; // no perfect bonus, rings are free
10690 break;
10691 case MT_EGGCAPSULE:
10692 mobj->reactiontime = 0;
10693 mobj->extravalue1 = mobj->cvmem =\
10694 mobj->cusval = mobj->movecount =\
10695 mobj->lastlook = mobj->extravalue2 = -1;
10696 break;
10697 case MT_REDTEAMRING:
10698 mobj->color = skincolor_redteam;
10699 break;
10700 case MT_BLUETEAMRING:
10701 mobj->color = skincolor_blueteam;
10702 break;
10703 case MT_RING:
10704 case MT_COIN:
10705 case MT_NIGHTSSTAR:
10706 if (nummaprings >= 0)
10707 nummaprings++;
10708 break;
10709 case MT_METALSONIC_RACE:
10710 mobj->skin = &skins[5];
10711 /* FALLTHRU */
10712 case MT_METALSONIC_BATTLE:
10713 mobj->color = skins[5].prefcolor;
10714 sc = 5;
10715 break;
10716 case MT_FANG:
10717 sc = 4;
10718 break;
10719 case MT_ROSY:
10720 sc = 3;
10721 break;
10722 case MT_CORK:
10723 mobj->flags2 |= MF2_SUPERFIRE;
10724 break;
10725 case MT_FBOMB:
10726 mobj->flags2 |= MF2_EXPLOSION;
10727 break;
10728 case MT_OILLAMP:
10729 {
10730 mobj_t* overlay = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_OVERLAY);
10731 P_SetTarget(&overlay->target, mobj);
10732 P_SetMobjState(overlay, S_OILLAMPFLARE);
10733 break;
10734 }
10735 case MT_TNTBARREL:
10736 mobj->momx = 1; //stack hack
10737 mobj->flags2 |= MF2_INVERTAIMABLE;
10738 break;
10739 case MT_MINECARTEND:
10740 P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_MINECARTENDSOLID));
10741 mobj->tracer->angle = mobj->angle + ANGLE_90;
10742 break;
10743 case MT_TORCHFLOWER:
10744 {
10745 mobj_t *fire = P_SpawnMobjFromMobj(mobj, 0, 0, 46*FRACUNIT, MT_FLAME);
10746 P_SetTarget(&mobj->target, fire);
10747 break;
10748 }
10749 case MT_PYREFLY:
10750 mobj->extravalue1 = (FixedHypot(mobj->x, mobj->y)/FRACUNIT) % 360;
10751 mobj->extravalue2 = 0;
10752 mobj->fuse = 100;
10753 break;
10754 case MT_SIGN:
10755 P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY));
10756 P_SetTarget(&mobj->tracer->target, mobj);
10757 P_SetMobjState(mobj->tracer, S_SIGNBOARD);
10758 mobj->tracer->movedir = ANGLE_90;
10759 default:
10760 break;
10761 }
10762
10763 if (sc != -1 && !(mobj->flags2 & MF2_SLIDEPUSH))
10764 {
10765 UINT8 i;
10766 for (i = 0; i < MAXPLAYERS; i++)
10767 {
10768 if (!playeringame[i] || players[i].spectator)
10769 continue;
10770
10771 if (players[i].skin == sc)
10772 {
10773 mobj->color = SKINCOLOR_SILVER;
10774 mobj->colorized = true;
10775 mobj->flags2 |= MF2_SLIDEPUSH;
10776 break;
10777 }
10778 }
10779 }
10780
10781 if (!(mobj->flags & MF_NOTHINK))
10782 P_AddThinker(THINK_MOBJ, &mobj->thinker);
10783
10784 if (mobj->skin) // correct inadequecies above.
10785 {
10786 mobj->sprite2 = P_GetSkinSprite2(mobj->skin, (mobj->frame & FF_FRAMEMASK), NULL);
10787 mobj->frame &= ~FF_FRAMEMASK;
10788 }
10789
10790 // Call action functions when the state is set
10791 if (st->action.acp1 && (mobj->flags & MF_RUNSPAWNFUNC))
10792 {
10793 if (levelloading)
10794 {
10795 // Cache actions in a linked list
10796 // with function pointer, and
10797 // var1 & var2, which will be executed
10798 // when the level finishes loading.
10799 P_AddCachedAction(mobj, mobj->info->spawnstate);
10800 }
10801 else
10802 {
10803 var1 = st->var1;
10804 var2 = st->var2;
10805 astate = st;
10806 st->action.acp1(mobj);
10807 // DANGER! This can cause P_SpawnMobj to return NULL!
10808 // Avoid using MF_RUNSPAWNFUNC on mobjs whose spawn state expects target or tracer to already be set!
10809 if (P_MobjWasRemoved(mobj))
10810 return NULL;
10811 }
10812 }
10813
10814 if (CheckForReverseGravity && !(mobj->flags & MF_NOBLOCKMAP))
10815 P_CheckGravity(mobj, false);
10816
10817 return mobj;
10818 }
10819
P_SpawnPrecipMobj(fixed_t x,fixed_t y,fixed_t z,mobjtype_t type)10820 static precipmobj_t *P_SpawnPrecipMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
10821 {
10822 state_t *st;
10823 precipmobj_t *mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL);
10824 fixed_t starting_floorz;
10825
10826 mobj->x = x;
10827 mobj->y = y;
10828 mobj->flags = mobjinfo[type].flags;
10829
10830 // do not set the state with P_SetMobjState,
10831 // because action routines can not be called yet
10832 st = &states[mobjinfo[type].spawnstate];
10833
10834 mobj->state = st;
10835 mobj->tics = st->tics;
10836 mobj->sprite = st->sprite;
10837 mobj->frame = st->frame; // FF_FRAMEMASK for frame, and other bits..
10838 P_SetupStateAnimation((mobj_t*)mobj, st);
10839
10840 // set subsector and/or block links
10841 P_SetPrecipitationThingPosition(mobj);
10842
10843 mobj->floorz = starting_floorz = P_GetSectorFloorZAt (mobj->subsector->sector, x, y);
10844 mobj->ceilingz = P_GetSectorCeilingZAt(mobj->subsector->sector, x, y);
10845
10846 mobj->floorrover = NULL;
10847 mobj->ceilingrover = NULL;
10848
10849 mobj->z = z;
10850 mobj->momz = mobjinfo[type].speed;
10851
10852 mobj->thinker.function.acp1 = (actionf_p1)P_NullPrecipThinker;
10853 P_AddThinker(THINK_PRECIP, &mobj->thinker);
10854
10855 CalculatePrecipFloor(mobj);
10856
10857 if (mobj->floorz != starting_floorz)
10858 mobj->precipflags |= PCF_FOF;
10859 else if (GETSECSPECIAL(mobj->subsector->sector->special, 1) == 7
10860 || GETSECSPECIAL(mobj->subsector->sector->special, 1) == 6
10861 || mobj->subsector->sector->floorpic == skyflatnum)
10862 mobj->precipflags |= PCF_PIT;
10863
10864 return mobj;
10865 }
10866
P_SpawnRainMobj(fixed_t x,fixed_t y,fixed_t z,mobjtype_t type)10867 static inline precipmobj_t *P_SpawnRainMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
10868 {
10869 precipmobj_t *mo = P_SpawnPrecipMobj(x,y,z,type);
10870 mo->precipflags |= PCF_RAIN;
10871 //mo->thinker.function.acp1 = (actionf_p1)P_RainThinker;
10872 return mo;
10873 }
10874
P_SpawnSnowMobj(fixed_t x,fixed_t y,fixed_t z,mobjtype_t type)10875 static inline precipmobj_t *P_SpawnSnowMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
10876 {
10877 precipmobj_t *mo = P_SpawnPrecipMobj(x,y,z,type);
10878 //mo->thinker.function.acp1 = (actionf_p1)P_SnowThinker;
10879 return mo;
10880 }
10881
P_CreateFloorSpriteSlope(mobj_t * mobj)10882 void *P_CreateFloorSpriteSlope(mobj_t *mobj)
10883 {
10884 if (mobj->floorspriteslope)
10885 Z_Free(mobj->floorspriteslope);
10886 mobj->floorspriteslope = Z_Calloc(sizeof(pslope_t), PU_LEVEL, NULL);
10887 mobj->floorspriteslope->normal.z = FRACUNIT;
10888 return (void *)mobj->floorspriteslope;
10889 }
10890
P_RemoveFloorSpriteSlope(mobj_t * mobj)10891 void P_RemoveFloorSpriteSlope(mobj_t *mobj)
10892 {
10893 if (mobj->floorspriteslope)
10894 Z_Free(mobj->floorspriteslope);
10895 mobj->floorspriteslope = NULL;
10896 }
10897
10898 //
10899 // P_RemoveMobj
10900 //
10901 mapthing_t *itemrespawnque[ITEMQUESIZE];
10902 tic_t itemrespawntime[ITEMQUESIZE];
10903 size_t iquehead, iquetail;
10904
10905 #ifdef PARANOIA
10906 #define SCRAMBLE_REMOVED // Force debug build to crash when Removed mobj is accessed
10907 #endif
P_RemoveMobj(mobj_t * mobj)10908 void P_RemoveMobj(mobj_t *mobj)
10909 {
10910 I_Assert(mobj != NULL);
10911 if (P_MobjWasRemoved(mobj))
10912 return; // something already removing this mobj.
10913
10914 mobj->thinker.function.acp1 = (actionf_p1)P_RemoveThinkerDelayed; // shh. no recursing.
10915 LUAh_MobjRemoved(mobj);
10916 mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; // needed for P_UnsetThingPosition, etc. to work.
10917
10918 // Rings only, please!
10919 if (mobj->spawnpoint &&
10920 (mobj->type == MT_RING
10921 || mobj->type == MT_COIN
10922 || mobj->type == MT_NIGHTSSTAR
10923 || mobj->type == MT_REDTEAMRING
10924 || mobj->type == MT_BLUETEAMRING
10925 || P_WeaponOrPanel(mobj->type))
10926 && !(mobj->flags2 & MF2_DONTRESPAWN))
10927 {
10928 itemrespawnque[iquehead] = mobj->spawnpoint;
10929 itemrespawntime[iquehead] = leveltime;
10930 iquehead = (iquehead+1)&(ITEMQUESIZE-1);
10931 // lose one off the end?
10932 if (iquehead == iquetail)
10933 iquetail = (iquetail+1)&(ITEMQUESIZE-1);
10934 }
10935
10936 if (mobj->type == MT_OVERLAY)
10937 P_RemoveOverlay(mobj);
10938
10939 if (mobj->player && mobj->player->followmobj)
10940 {
10941 P_RemoveMobj(mobj->player->followmobj);
10942 P_SetTarget(&mobj->player->followmobj, NULL);
10943 }
10944
10945 mobj->health = 0; // Just because
10946
10947 // unlink from sector and block lists
10948 P_UnsetThingPosition(mobj);
10949 if (sector_list)
10950 {
10951 P_DelSeclist(sector_list);
10952 sector_list = NULL;
10953 }
10954
10955 mobj->flags |= MF_NOSECTOR|MF_NOBLOCKMAP;
10956 mobj->subsector = NULL;
10957 mobj->state = NULL;
10958 mobj->player = NULL;
10959
10960 P_RemoveFloorSpriteSlope(mobj);
10961
10962 // stop any playing sound
10963 S_StopSound(mobj);
10964
10965 // killough 11/98:
10966 //
10967 // Remove any references to other mobjs.
10968 P_SetTarget(&mobj->target, P_SetTarget(&mobj->tracer, NULL));
10969
10970 if (mobj->hnext && !P_MobjWasRemoved(mobj->hnext))
10971 P_SetTarget(&mobj->hnext->hprev, mobj->hprev);
10972 if (mobj->hprev && !P_MobjWasRemoved(mobj->hprev))
10973 P_SetTarget(&mobj->hprev->hnext, mobj->hnext);
10974
10975 P_SetTarget(&mobj->hnext, P_SetTarget(&mobj->hprev, NULL));
10976
10977 // DBG: set everything in mobj_t to 0xFF instead of leaving it. debug memory error.
10978 #ifdef SCRAMBLE_REMOVED
10979 // Invalidate mobj_t data to cause crashes if accessed!
10980 memset((UINT8 *)mobj + sizeof(thinker_t), 0xff, sizeof(mobj_t) - sizeof(thinker_t));
10981 #endif
10982
10983 // free block
10984 if (!mobj->thinker.next)
10985 { // Uh-oh, the mobj doesn't think, P_RemoveThinker would never go through!
10986 INT32 prevreferences;
10987 if (!mobj->thinker.references)
10988 {
10989 Z_Free(mobj); // No refrrences? Can be removed immediately! :D
10990 return;
10991 }
10992
10993 prevreferences = mobj->thinker.references;
10994 P_AddThinker(THINK_MOBJ, (thinker_t *)mobj);
10995 mobj->thinker.references = prevreferences;
10996 }
10997
10998 P_RemoveThinker((thinker_t *)mobj);
10999 }
11000
11001 // This does not need to be added to Lua.
11002 // To test it in Lua, check mobj.valid
P_MobjWasRemoved(mobj_t * mobj)11003 boolean P_MobjWasRemoved(mobj_t *mobj)
11004 {
11005 if (mobj && mobj->thinker.function.acp1 == (actionf_p1)P_MobjThinker)
11006 return false;
11007 return true;
11008 }
11009
P_RemovePrecipMobj(precipmobj_t * mobj)11010 void P_RemovePrecipMobj(precipmobj_t *mobj)
11011 {
11012 // unlink from sector and block lists
11013 P_UnsetPrecipThingPosition(mobj);
11014
11015 if (precipsector_list)
11016 {
11017 P_DelPrecipSeclist(precipsector_list);
11018 precipsector_list = NULL;
11019 }
11020
11021 // free block
11022 P_RemoveThinker((thinker_t *)mobj);
11023 }
11024
11025 // Clearing out stuff for savegames
P_RemoveSavegameMobj(mobj_t * mobj)11026 void P_RemoveSavegameMobj(mobj_t *mobj)
11027 {
11028 // unlink from sector and block lists
11029 P_UnsetThingPosition(mobj);
11030
11031 // Remove touching_sectorlist from mobj.
11032 if (sector_list)
11033 {
11034 P_DelSeclist(sector_list);
11035 sector_list = NULL;
11036 }
11037
11038 // stop any playing sound
11039 S_StopSound(mobj);
11040
11041 // free block
11042 P_RemoveThinker((thinker_t *)mobj);
11043 }
11044
11045 static CV_PossibleValue_t respawnitemtime_cons_t[] = {{1, "MIN"}, {300, "MAX"}, {0, NULL}};
11046 consvar_t cv_itemrespawntime = CVAR_INIT ("respawnitemtime", "30", CV_SAVE|CV_NETVAR|CV_CHEAT, respawnitemtime_cons_t, NULL);
11047 consvar_t cv_itemrespawn = CVAR_INIT ("respawnitem", "On", CV_SAVE|CV_NETVAR, CV_OnOff, NULL);
11048 static CV_PossibleValue_t flagtime_cons_t[] = {{0, "MIN"}, {300, "MAX"}, {0, NULL}};
11049 consvar_t cv_flagtime = CVAR_INIT ("flagtime", "30", CV_SAVE|CV_NETVAR|CV_CHEAT, flagtime_cons_t, NULL);
11050
P_SpawnPrecipitation(void)11051 void P_SpawnPrecipitation(void)
11052 {
11053 INT32 i, mrand;
11054 fixed_t basex, basey, x, y, height;
11055 subsector_t *precipsector = NULL;
11056 precipmobj_t *rainmo = NULL;
11057
11058 if (dedicated || !(cv_drawdist_precip.value) || curWeather == PRECIP_NONE)
11059 return;
11060
11061 // Use the blockmap to narrow down our placing patterns
11062 for (i = 0; i < bmapwidth*bmapheight; ++i)
11063 {
11064 basex = bmaporgx + (i % bmapwidth) * MAPBLOCKSIZE;
11065 basey = bmaporgy + (i / bmapwidth) * MAPBLOCKSIZE;
11066
11067 x = basex + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
11068 y = basey + ((M_RandomKey(MAPBLOCKUNITS<<3)<<FRACBITS)>>3);
11069
11070 precipsector = R_PointInSubsectorOrNull(x, y);
11071
11072 // No sector? Stop wasting time,
11073 // move on to the next entry in the blockmap
11074 if (!precipsector)
11075 continue;
11076
11077 // Exists, but is too small for reasonable precipitation.
11078 if (!(precipsector->sector->floorheight <= precipsector->sector->ceilingheight - (32<<FRACBITS)))
11079 continue;
11080
11081 // Don't set height yet...
11082 height = precipsector->sector->ceilingheight;
11083
11084 if (curWeather == PRECIP_SNOW)
11085 {
11086 // Not in a sector with visible sky -- exception for NiGHTS.
11087 if ((!(maptol & TOL_NIGHTS) && (precipsector->sector->ceilingpic != skyflatnum)) == !(precipsector->sector->flags & SF_INVERTPRECIP))
11088 continue;
11089
11090 rainmo = P_SpawnSnowMobj(x, y, height, MT_SNOWFLAKE);
11091 mrand = M_RandomByte();
11092 if (mrand < 64)
11093 P_SetPrecipMobjState(rainmo, S_SNOW3);
11094 else if (mrand < 144)
11095 P_SetPrecipMobjState(rainmo, S_SNOW2);
11096 }
11097 else // everything else.
11098 {
11099 // Not in a sector with visible sky.
11100 if ((precipsector->sector->ceilingpic != skyflatnum) == !(precipsector->sector->flags & SF_INVERTPRECIP))
11101 continue;
11102
11103 rainmo = P_SpawnRainMobj(x, y, height, MT_RAIN);
11104 }
11105
11106 // Randomly assign a height, now that floorz is set.
11107 rainmo->z = M_RandomRange(rainmo->floorz>>FRACBITS, rainmo->ceilingz>>FRACBITS)<<FRACBITS;
11108 }
11109
11110 if (curWeather == PRECIP_BLANK)
11111 {
11112 curWeather = PRECIP_RAIN;
11113 P_SwitchWeather(PRECIP_BLANK);
11114 }
11115 else if (curWeather == PRECIP_STORM_NORAIN)
11116 {
11117 curWeather = PRECIP_RAIN;
11118 P_SwitchWeather(PRECIP_STORM_NORAIN);
11119 }
11120 }
11121
11122 //
11123 // P_PrecipitationEffects
11124 //
P_PrecipitationEffects(void)11125 void P_PrecipitationEffects(void)
11126 {
11127 INT16 thunderchance = INT16_MAX;
11128 INT32 volume;
11129 size_t i;
11130
11131 boolean sounds_rain = true;
11132 boolean sounds_thunder = true;
11133 boolean effects_lightning = true;
11134 boolean lightningStrike = false;
11135
11136 // No thunder except every other tic.
11137 if (leveltime & 1);
11138 // Before, consistency failures were possible if a level started
11139 // with global rain and switched players to anything else ...
11140 // If the global weather has lightning strikes,
11141 // EVERYONE gets them at the SAME time!
11142 else if (globalweather == PRECIP_STORM
11143 || globalweather == PRECIP_STORM_NORAIN)
11144 thunderchance = (P_RandomKey(8192));
11145 // But on the other hand, if the global weather is ANYTHING ELSE,
11146 // don't sync lightning strikes.
11147 // It doesn't matter whatever curWeather is, we'll only use
11148 // the variable if we care about it.
11149 else
11150 thunderchance = (M_RandomKey(8192));
11151
11152 if (thunderchance < 70)
11153 lightningStrike = true;
11154
11155 switch (curWeather)
11156 {
11157 case PRECIP_RAIN: // no lightning or thunder whatsoever
11158 sounds_thunder = false;
11159 /* FALLTHRU */
11160 case PRECIP_STORM_NOSTRIKES: // no lightning strikes specifically
11161 effects_lightning = false;
11162 break;
11163 case PRECIP_STORM_NORAIN: // no rain, lightning and thunder allowed
11164 sounds_rain = false;
11165 case PRECIP_STORM: // everything.
11166 break;
11167 default:
11168 // Other weathers need not apply.
11169 return;
11170 }
11171
11172 // Currently thunderstorming with lightning, and we're sounding the thunder...
11173 // and where there's thunder, there's gotta be lightning!
11174 if (effects_lightning && lightningStrike)
11175 {
11176 sector_t *ss = sectors;
11177
11178 for (i = 0; i < numsectors; i++, ss++)
11179 if (ss->ceilingpic == skyflatnum) // Only for the sky.
11180 P_SpawnLightningFlash(ss); // Spawn a quick flash thinker
11181 }
11182
11183 // Local effects from here on out!
11184 // If we're not in game fully yet, we don't worry about them.
11185 if (!playeringame[displayplayer] || !players[displayplayer].mo)
11186 return;
11187
11188 if (sound_disabled)
11189 return; // Sound off? D'aw, no fun.
11190
11191 if (players[displayplayer].mo->subsector->sector->ceilingpic == skyflatnum)
11192 volume = 255; // Sky above? We get it full blast.
11193 else
11194 {
11195 fixed_t x, y, yl, yh, xl, xh;
11196 fixed_t closedist, newdist;
11197
11198 // Essentially check in a 1024 unit radius of the player for an outdoor area.
11199 yl = players[displayplayer].mo->y - 1024*FRACUNIT;
11200 yh = players[displayplayer].mo->y + 1024*FRACUNIT;
11201 xl = players[displayplayer].mo->x - 1024*FRACUNIT;
11202 xh = players[displayplayer].mo->x + 1024*FRACUNIT;
11203 closedist = 2048*FRACUNIT;
11204 for (y = yl; y <= yh; y += FRACUNIT*64)
11205 for (x = xl; x <= xh; x += FRACUNIT*64)
11206 {
11207 if (R_PointInSubsector(x, y)->sector->ceilingpic == skyflatnum) // Found the outdoors!
11208 {
11209 newdist = S_CalculateSoundDistance(players[displayplayer].mo->x, players[displayplayer].mo->y, 0, x, y, 0);
11210 if (newdist < closedist)
11211 closedist = newdist;
11212 }
11213 }
11214
11215 volume = 255 - (closedist>>(FRACBITS+2));
11216 }
11217
11218 if (volume < 0)
11219 volume = 0;
11220 else if (volume > 255)
11221 volume = 255;
11222
11223 if (sounds_rain && (!leveltime || leveltime % 80 == 1))
11224 S_StartSoundAtVolume(players[displayplayer].mo, sfx_rainin, volume);
11225
11226 if (!sounds_thunder)
11227 return;
11228
11229 if (effects_lightning && lightningStrike && volume)
11230 {
11231 // Large, close thunder sounds to go with our lightning.
11232 S_StartSoundAtVolume(players[displayplayer].mo, sfx_litng1 + M_RandomKey(4), volume);
11233 }
11234 else if (thunderchance < 20)
11235 {
11236 // You can always faintly hear the thunder...
11237 if (volume < 80)
11238 volume = 80;
11239
11240 S_StartSoundAtVolume(players[displayplayer].mo, sfx_athun1 + M_RandomKey(2), volume);
11241 }
11242 }
11243
11244 /** Returns corresponding mobj type from mapthing number.
11245 * \param mthingtype Mapthing number in question.
11246 * \return Mobj type; MT_UNKNOWN if nothing found.
11247 */
P_GetMobjtype(UINT16 mthingtype)11248 mobjtype_t P_GetMobjtype(UINT16 mthingtype)
11249 {
11250 mobjtype_t i;
11251 for (i = 0; i < NUMMOBJTYPES; i++)
11252 if (mthingtype == mobjinfo[i].doomednum)
11253 return i;
11254 return MT_UNKNOWN;
11255 }
11256
11257 //
11258 // P_RespawnSpecials
11259 //
P_RespawnSpecials(void)11260 void P_RespawnSpecials(void)
11261 {
11262 mapthing_t *mthing = NULL;
11263
11264 // only respawn items when cv_itemrespawn is on
11265 if (!(netgame || multiplayer) // Never respawn in single player
11266 || (maptol & TOL_NIGHTS) // Never respawn in NiGHTs
11267 || !cv_itemrespawn.value) // cvar is turned off
11268 return;
11269
11270 // Don't respawn in special stages!
11271 if (G_IsSpecialStage(gamemap))
11272 return;
11273
11274 // nothing left to respawn?
11275 if (iquehead == iquetail)
11276 return;
11277
11278 // the first item in the queue is the first to respawn
11279 // wait at least 30 seconds
11280 if (leveltime - itemrespawntime[iquetail] < (tic_t)cv_itemrespawntime.value*TICRATE)
11281 return;
11282
11283 mthing = itemrespawnque[iquetail];
11284
11285 #ifdef PARANOIA
11286 if (!mthing)
11287 I_Error("itemrespawnque[iquetail] is NULL!");
11288 #endif
11289
11290 if (mthing)
11291 P_SpawnMapThing(mthing);
11292
11293 // pull it from the que
11294 iquetail = (iquetail+1)&(ITEMQUESIZE-1);
11295 }
11296
11297 //
11298 // P_SpawnPlayer
11299 // Called when a player is spawned on the level.
11300 // Most of the player structure stays unchanged between levels.
11301 //
P_SpawnPlayer(INT32 playernum)11302 void P_SpawnPlayer(INT32 playernum)
11303 {
11304 player_t *p = &players[playernum];
11305 mobj_t *mobj;
11306
11307 if (p->playerstate == PST_REBORN)
11308 G_PlayerReborn(playernum, false);
11309
11310 // spawn as spectator determination
11311 if (!G_GametypeHasSpectators())
11312 {
11313 p->spectator = p->outofcoop =
11314 (((multiplayer || netgame) && G_CoopGametype()) // only question status in coop
11315 && ((leveltime > 0
11316 && ((G_IsSpecialStage(gamemap)) // late join special stage
11317 || (cv_coopstarposts.value == 2 && (p->jointime < 1 || p->outofcoop)))) // late join or die in new coop
11318 || (!P_GetLives(p) && p->lives <= 0))); // game over and can't redistribute lives
11319 }
11320 else
11321 {
11322 p->outofcoop = false;
11323 if (netgame && p->jointime < 1)
11324 {
11325 // Averted by GTR_NOSPECTATORSPAWN.
11326 p->spectator = (gametyperules & GTR_NOSPECTATORSPAWN) ? false : true;
11327 }
11328 else if (multiplayer && !netgame)
11329 {
11330 // If you're in a team game and you don't have a team assigned yet...
11331 if (G_GametypeHasTeams() && p->ctfteam == 0)
11332 {
11333 changeteam_union NetPacket;
11334 UINT16 usvalue;
11335 NetPacket.value.l = NetPacket.value.b = 0;
11336
11337 // Spawn as a spectator,
11338 // yes even in splitscreen mode
11339 p->spectator = true;
11340 if (playernum&1) p->skincolor = skincolor_redteam;
11341 else p->skincolor = skincolor_blueteam;
11342
11343 // but immediately send a team change packet.
11344 NetPacket.packet.playernum = playernum;
11345 NetPacket.packet.verification = true;
11346 NetPacket.packet.newteam = !(playernum&1) + 1;
11347
11348 usvalue = SHORT(NetPacket.value.l|NetPacket.value.b);
11349 SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue));
11350 }
11351 else // Otherwise, never spectator.
11352 p->spectator = false;
11353 }
11354 }
11355
11356 if (G_GametypeHasTeams())
11357 {
11358 // Fix stupid non spectator spectators.
11359 if (!p->spectator && !p->ctfteam)
11360 p->spectator = true;
11361
11362 // Fix team colors.
11363 // This code isn't being done right somewhere else. Oh well.
11364 if (p->ctfteam == 1)
11365 p->skincolor = skincolor_redteam;
11366 else if (p->ctfteam == 2)
11367 p->skincolor = skincolor_blueteam;
11368 }
11369
11370 if ((netgame || multiplayer) && ((gametyperules & GTR_SPAWNINVUL) || leveltime) && !p->spectator && !(maptol & TOL_NIGHTS))
11371 p->powers[pw_flashing] = flashingtics-1; // Babysitting deterrent
11372
11373 mobj = P_SpawnMobj(0, 0, 0, MT_PLAYER);
11374 (mobj->player = p)->mo = mobj;
11375
11376 mobj->angle = 0;
11377
11378 // set color translations for player sprites
11379 mobj->color = p->skincolor;
11380
11381 // set 'spritedef' override in mobj for player skins.. (see ProjectSprite)
11382 // (usefulness: when body mobj is detached from player (who respawns),
11383 // the dead body mobj retains the skin through the 'spritedef' override).
11384 mobj->skin = &skins[p->skin];
11385 P_SetupStateAnimation(mobj, mobj->state);
11386
11387 mobj->health = 1;
11388 p->playerstate = PST_LIVE;
11389
11390 p->bonustime = false;
11391 p->realtime = leveltime;
11392 p->followitem = skins[p->skin].followitem;
11393
11394 // Make sure player's stats are reset if they were in dashmode!
11395 if (p->dashmode)
11396 {
11397 p->dashmode = 0;
11398 p->normalspeed = skins[p->skin].normalspeed;
11399 p->jumpfactor = skins[p->skin].jumpfactor;
11400 }
11401
11402 // Clear lastlinehit and lastsidehit
11403 p->lastsidehit = -1;
11404 p->lastlinehit = -1;
11405
11406 //awayview stuff
11407 p->awayviewmobj = NULL;
11408 p->awayviewtics = 0;
11409
11410 // set the scale to the mobj's destscale so settings get correctly set. if we don't, they sometimes don't.
11411 P_SetScale(mobj, mobj->destscale);
11412 P_FlashPal(p, 0, 0); // Resets
11413
11414 // Set bounds accurately.
11415 mobj->radius = FixedMul(skins[p->skin].radius, mobj->scale);
11416 mobj->height = P_GetPlayerHeight(p);
11417
11418 if (!leveltime && !p->spectator && ((maptol & TOL_NIGHTS) == TOL_NIGHTS) != (G_IsSpecialStage(gamemap))) // non-special NiGHTS stage or special non-NiGHTS stage
11419 {
11420 if (maptol & TOL_NIGHTS)
11421 {
11422 if (p == players) // this is totally the wrong place to do this aaargh.
11423 {
11424 mobj_t *idya = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_GOTEMERALD);
11425 idya->health = 0; // for identification
11426 P_SetTarget(&idya->target, mobj);
11427 P_SetMobjState(idya, mobjinfo[MT_GOTEMERALD].missilestate);
11428 P_SetTarget(&mobj->tracer, idya);
11429 }
11430 }
11431 else if (sstimer)
11432 p->nightstime = sstimer;
11433 }
11434
11435 // Spawn with a pity shield if necessary.
11436 P_DoPityCheck(p);
11437 }
11438
P_AfterPlayerSpawn(INT32 playernum)11439 void P_AfterPlayerSpawn(INT32 playernum)
11440 {
11441 player_t *p = &players[playernum];
11442 mobj_t *mobj = p->mo;
11443
11444 P_SetPlayerAngle(p, mobj->angle);
11445
11446 p->viewheight = 41*p->height/48;
11447
11448 if (p->mo->eflags & MFE_VERTICALFLIP)
11449 p->viewz = p->mo->z + p->mo->height - p->viewheight;
11450 else
11451 p->viewz = p->mo->z + p->viewheight;
11452
11453 if (playernum == consoleplayer)
11454 {
11455 // wake up the status bar
11456 ST_Start();
11457 // wake up the heads up text
11458 HU_Start();
11459 }
11460
11461 p->drawangle = mobj->angle;
11462
11463 if (camera.chase)
11464 {
11465 if (displayplayer == playernum)
11466 P_ResetCamera(p, &camera);
11467 }
11468 if (camera2.chase && splitscreen)
11469 {
11470 if (secondarydisplayplayer == playernum)
11471 P_ResetCamera(p, &camera2);
11472 }
11473
11474 if (CheckForReverseGravity)
11475 P_CheckGravity(mobj, false);
11476
11477 if (p->pflags & PF_FINISHED)
11478 P_GiveFinishFlags(p);
11479 }
11480
11481 // spawn it at a playerspawn mapthing
P_MovePlayerToSpawn(INT32 playernum,mapthing_t * mthing)11482 void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing)
11483 {
11484 fixed_t x = 0, y = 0;
11485 angle_t angle = 0;
11486
11487 fixed_t z;
11488 sector_t *sector;
11489 fixed_t floor, ceiling, ceilingspawn;
11490
11491 player_t *p = &players[playernum];
11492 mobj_t *mobj = p->mo;
11493 I_Assert(mobj != NULL);
11494
11495 if (mthing)
11496 {
11497 x = mthing->x << FRACBITS;
11498 y = mthing->y << FRACBITS;
11499 angle = FixedAngle(mthing->angle<<FRACBITS);
11500 }
11501 //spawn at the origin as a desperation move if there is no mapthing
11502
11503 // set Z height
11504 sector = R_PointInSubsector(x, y)->sector;
11505
11506 floor = P_GetSectorFloorZAt (sector, x, y);
11507 ceiling = P_GetSectorCeilingZAt(sector, x, y);
11508 ceilingspawn = ceiling - mobjinfo[MT_PLAYER].height;
11509
11510 if (mthing)
11511 {
11512 fixed_t offset = mthing->z << FRACBITS;
11513
11514 // Flagging a player's ambush will make them start on the ceiling
11515 // Objectflip inverts
11516 if (!!(mthing->options & MTF_AMBUSH) ^ !!(mthing->options & MTF_OBJECTFLIP))
11517 z = ceilingspawn - offset;
11518 else
11519 z = floor + offset;
11520
11521 if (mthing->options & MTF_OBJECTFLIP) // flip the player!
11522 {
11523 mobj->eflags |= MFE_VERTICALFLIP;
11524 mobj->flags2 |= MF2_OBJECTFLIP;
11525 }
11526 if (mthing->options & MTF_AMBUSH)
11527 P_SetPlayerMobjState(mobj, S_PLAY_FALL);
11528 else if (metalrecording)
11529 P_SetPlayerMobjState(mobj, S_PLAY_WAIT);
11530 }
11531 else
11532 z = floor;
11533
11534 if (z < floor)
11535 z = floor;
11536 else if (z > ceilingspawn)
11537 z = ceilingspawn;
11538
11539 mobj->floorz = floor;
11540 mobj->ceilingz = ceiling;
11541
11542 P_UnsetThingPosition(mobj);
11543 mobj->x = x;
11544 mobj->y = y;
11545 P_SetThingPosition(mobj);
11546
11547 mobj->z = z;
11548 if (mobj->flags2 & MF2_OBJECTFLIP)
11549 {
11550 if (mobj->z + mobj->height == mobj->ceilingz)
11551 mobj->eflags |= MFE_ONGROUND;
11552 }
11553 else if (mobj->z == mobj->floorz)
11554 mobj->eflags |= MFE_ONGROUND;
11555
11556 mobj->angle = angle;
11557
11558 P_AfterPlayerSpawn(playernum);
11559 }
11560
P_MovePlayerToStarpost(INT32 playernum)11561 void P_MovePlayerToStarpost(INT32 playernum)
11562 {
11563 fixed_t z;
11564 sector_t *sector;
11565 fixed_t floor, ceiling;
11566
11567 player_t *p = &players[playernum];
11568 mobj_t *mobj = p->mo;
11569 I_Assert(mobj != NULL);
11570
11571 P_UnsetThingPosition(mobj);
11572 mobj->x = p->starpostx << FRACBITS;
11573 mobj->y = p->starposty << FRACBITS;
11574 P_SetThingPosition(mobj);
11575 sector = R_PointInSubsector(mobj->x, mobj->y)->sector;
11576
11577 floor = P_GetSectorFloorZAt (sector, mobj->x, mobj->y);
11578 ceiling = P_GetSectorCeilingZAt(sector, mobj->x, mobj->y);
11579
11580 z = p->starpostz << FRACBITS;
11581
11582 P_SetScale(mobj, (mobj->destscale = abs(p->starpostscale)));
11583
11584 if (p->starpostscale < 0)
11585 {
11586 mobj->flags2 |= MF2_OBJECTFLIP;
11587 if (z >= ceiling)
11588 {
11589 mobj->eflags |= MFE_ONGROUND;
11590 z = ceiling;
11591 }
11592 z -= mobj->height;
11593 }
11594 else if (z <= floor)
11595 {
11596 mobj->eflags |= MFE_ONGROUND;
11597 z = floor;
11598 }
11599
11600 mobj->floorz = floor;
11601 mobj->ceilingz = ceiling;
11602
11603 mobj->z = z;
11604
11605 mobj->angle = p->starpostangle;
11606
11607 P_AfterPlayerSpawn(playernum);
11608
11609 if (!(netgame || multiplayer))
11610 leveltime = p->starposttime;
11611 }
11612
11613 #define MAXHUNTEMERALDS 64
11614 mapthing_t *huntemeralds[MAXHUNTEMERALDS];
11615 INT32 numhuntemeralds;
11616
P_GetMobjSpawnHeight(const mobjtype_t mobjtype,const fixed_t x,const fixed_t y,const fixed_t dz,const fixed_t offset,const boolean flip,const fixed_t scale)11617 fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const fixed_t y, const fixed_t dz, const fixed_t offset, const boolean flip, const fixed_t scale)
11618 {
11619 const subsector_t *ss = R_PointInSubsector(x, y);
11620
11621 // Axis objects snap to the floor.
11622 if (mobjtype == MT_AXIS || mobjtype == MT_AXISTRANSFER || mobjtype == MT_AXISTRANSFERLINE)
11623 return ONFLOORZ;
11624
11625 // Establish height.
11626 if (flip)
11627 return P_GetSectorCeilingZAt(ss->sector, x, y) - dz - FixedMul(scale, offset + mobjinfo[mobjtype].height);
11628 else
11629 return P_GetSectorFloorZAt(ss->sector, x, y) + dz + FixedMul(scale, offset);
11630 }
11631
P_GetMapThingSpawnHeight(const mobjtype_t mobjtype,const mapthing_t * mthing,const fixed_t x,const fixed_t y)11632 fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y)
11633 {
11634 fixed_t dz = mthing->z << FRACBITS; // Base offset from the floor.
11635 fixed_t offset = 0; // Specific scaling object offset.
11636 boolean flip = (!!(mobjinfo[mobjtype].flags & MF_SPAWNCEILING) ^ !!(mthing->options & MTF_OBJECTFLIP));
11637
11638 switch (mobjtype)
11639 {
11640 // Bumpers never spawn flipped.
11641 case MT_NIGHTSBUMPER:
11642 flip = false;
11643 break;
11644
11645 // Objects with a non-zero default height.
11646 case MT_CRAWLACOMMANDER:
11647 case MT_DETON:
11648 case MT_JETTBOMBER:
11649 case MT_JETTGUNNER:
11650 case MT_EGGMOBILE2:
11651 if (!dz)
11652 dz = 33*FRACUNIT;
11653 break;
11654 case MT_EGGMOBILE:
11655 if (!dz)
11656 dz = 128*FRACUNIT;
11657 break;
11658 case MT_GOLDBUZZ:
11659 case MT_REDBUZZ:
11660 if (!dz)
11661 dz = 288*FRACUNIT;
11662 break;
11663
11664 // Horizontal springs, may float additional units with MTF_AMBUSH.
11665 case MT_YELLOWHORIZ:
11666 case MT_REDHORIZ:
11667 case MT_BLUEHORIZ:
11668 offset += mthing->options & MTF_AMBUSH ? 16*FRACUNIT : 0;
11669 break;
11670
11671 // Ring-like items, may float additional units with MTF_AMBUSH.
11672 case MT_SPIKEBALL:
11673 case MT_EMERHUNT:
11674 case MT_EMERALDSPAWN:
11675 case MT_TOKEN:
11676 case MT_EMBLEM:
11677 case MT_RING:
11678 case MT_REDTEAMRING:
11679 case MT_BLUETEAMRING:
11680 case MT_COIN:
11681 case MT_BLUESPHERE:
11682 case MT_BOMBSPHERE:
11683 case MT_NIGHTSCHIP:
11684 case MT_NIGHTSSTAR:
11685 offset += mthing->options & MTF_AMBUSH ? 24*FRACUNIT : 0;
11686 break;
11687
11688 // Remaining objects.
11689 default:
11690 if (P_WeaponOrPanel(mobjtype))
11691 offset += mthing->options & MTF_AMBUSH ? 24*FRACUNIT : 0;
11692 }
11693
11694 if (!(dz + offset)) // Snap to the surfaces when there's no offset set.
11695 {
11696 if (flip)
11697 return ONCEILINGZ;
11698 else
11699 return ONFLOORZ;
11700 }
11701
11702 return P_GetMobjSpawnHeight(mobjtype, x, y, dz, offset, flip, mthing->scale);
11703 }
11704
P_SpawnNonMobjMapThing(mapthing_t * mthing)11705 static boolean P_SpawnNonMobjMapThing(mapthing_t *mthing)
11706 {
11707 #if MAXPLAYERS > 32
11708 You should think about modifying the deathmatch starts to take full advantage of this!
11709 #endif
11710 if (mthing->type <= MAXPLAYERS) // Player starts
11711 {
11712 // save spots for respawning in network games
11713 if (!metalrecording)
11714 playerstarts[mthing->type - 1] = mthing;
11715 return true;
11716 }
11717 else if (mthing->type == 33) // Match starts
11718 {
11719 if (numdmstarts < MAX_DM_STARTS)
11720 {
11721 deathmatchstarts[numdmstarts] = mthing;
11722 mthing->type = 0;
11723 numdmstarts++;
11724 }
11725 return true;
11726 }
11727 else if (mthing->type == 34) // Red CTF starts
11728 {
11729 if (numredctfstarts < MAXPLAYERS)
11730 {
11731 redctfstarts[numredctfstarts] = mthing;
11732 mthing->type = 0;
11733 numredctfstarts++;
11734 }
11735 return true;
11736 }
11737 else if (mthing->type == 35) // Blue CTF starts
11738 {
11739 if (numbluectfstarts < MAXPLAYERS)
11740 {
11741 bluectfstarts[numbluectfstarts] = mthing;
11742 mthing->type = 0;
11743 numbluectfstarts++;
11744 }
11745 return true;
11746 }
11747 else if (metalrecording && mthing->type == mobjinfo[MT_METALSONIC_RACE].doomednum)
11748 { // If recording, you ARE Metal Sonic. Do not spawn it, do not save normal spawnpoints.
11749 playerstarts[0] = mthing;
11750 return true;
11751 }
11752 else if (mthing->type == 750 // Slope vertex point (formerly chaos spawn)
11753 || (mthing->type >= 600 && mthing->type <= 609) // Special placement patterns
11754 || mthing->type == 1705 || mthing->type == 1713) // Hoops
11755 return true; // These are handled elsewhere.
11756 else if (mthing->type == mobjinfo[MT_EMERHUNT].doomednum)
11757 {
11758 // Emerald Hunt is Coop only. Don't spawn the emerald yet, but save the spawnpoint for later.
11759 if ((gametyperules & GTR_EMERALDHUNT) && numhuntemeralds < MAXHUNTEMERALDS)
11760 huntemeralds[numhuntemeralds++] = mthing;
11761 return true;
11762 }
11763
11764 return false;
11765 }
11766
P_AllowMobjSpawn(mapthing_t * mthing,mobjtype_t i)11767 static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i)
11768 {
11769 switch (i)
11770 {
11771 case MT_EMERALD1:
11772 case MT_EMERALD2:
11773 case MT_EMERALD3:
11774 case MT_EMERALD4:
11775 case MT_EMERALD5:
11776 case MT_EMERALD6:
11777 case MT_EMERALD7:
11778 if (!G_CoopGametype()) // Don't place emeralds in non-coop modes
11779 return false;
11780
11781 if (metalrecording)
11782 return false; // Metal Sonic isn't for collecting emeralds.
11783
11784 if (emeralds & mobjinfo[i].speed) // You already have this emerald!
11785 return false;
11786
11787 break;
11788 case MT_EMERALDSPAWN:
11789 if (!cv_powerstones.value)
11790 return false;
11791
11792 if (!(gametyperules & GTR_POWERSTONES))
11793 return false;
11794
11795 runemeraldmanager = true;
11796 break;
11797 case MT_ROSY:
11798 if (!(G_CoopGametype() || (mthing->options & MTF_EXTRA)))
11799 return false; // she doesn't hang out here
11800
11801 if (!(netgame || multiplayer) && players[consoleplayer].skin == 3)
11802 return false; // no doubles
11803
11804 break;
11805 case MT_TOKEN:
11806 if (!(gametyperules & GTR_EMERALDTOKENS))
11807 return false; // Gametype's not right
11808
11809 if (tokenbits == 30)
11810 return false; // Too many tokens
11811
11812 if (tokenlist & (1 << tokenbits++))
11813 return false; // You already got this token
11814
11815 break;
11816 case MT_EMBLEM:
11817 if (netgame || multiplayer)
11818 return false; // Single player
11819
11820 if (modifiedgame && !savemoddata)
11821 return false; // No cheating!!
11822
11823 break;
11824 default:
11825 break;
11826 }
11827
11828 if (metalrecording) // Metal Sonic can't use these things.
11829 {
11830 if ((mobjinfo[i].flags & (MF_ENEMY|MF_BOSS)) || i == MT_TOKEN || i == MT_STARPOST
11831 || i == MT_RING || i == MT_BLUETEAMRING || i == MT_REDTEAMRING || i == MT_COIN
11832 || i == MT_BLUESPHERE || i == MT_BOMBSPHERE || i == MT_NIGHTSCHIP || i == MT_NIGHTSSTAR)
11833 return false;
11834 }
11835
11836 if (((mobjinfo[i].flags & MF_ENEMY) || (mobjinfo[i].flags & MF_BOSS)) && !(gametyperules & GTR_SPAWNENEMIES))
11837 return false; // No enemies in ringslinger modes
11838
11839 if (!(gametyperules & GTR_ALLOWEXIT) && (i == MT_SIGN))
11840 return false; // Don't spawn exit signs in wrong game modes
11841
11842 if (!G_PlatformGametype() && (i == MT_STARPOST))
11843 return false; // Don't spawn starposts in wrong game modes
11844
11845 if (!G_RingSlingerGametype() || !cv_specialrings.value)
11846 if (P_WeaponOrPanel(i))
11847 return false; // Don't place weapons/panels in non-ringslinger modes
11848
11849 if (!(gametyperules & GTR_TEAMFLAGS)) // CTF specific things
11850 {
11851 if (i == MT_BLUEFLAG || i == MT_REDFLAG)
11852 return false; // No flags in non-CTF modes!
11853 }
11854 else
11855 {
11856 if ((i == MT_BLUEFLAG && blueflag) || (i == MT_REDFLAG && redflag))
11857 {
11858 CONS_Alert(CONS_ERROR, M_GetText("Only one flag per team allowed in CTF!\n"));
11859 return false;
11860 }
11861 }
11862
11863 if (modeattacking) // Record Attack special stuff
11864 {
11865 // Don't spawn starposts that wouldn't be usable
11866 if (i == MT_STARPOST)
11867 return false;
11868 }
11869
11870 if (ultimatemode)
11871 {
11872 if (i == MT_RING || i == MT_REDTEAMRING || i == MT_BLUETEAMRING
11873 || i == MT_COIN || i == MT_NIGHTSSTAR || i == MT_NIGHTSCHIP
11874 || i == MT_PITY_BOX || i == MT_ELEMENTAL_BOX || i == MT_ATTRACT_BOX
11875 || i == MT_FORCE_BOX || i == MT_ARMAGEDDON_BOX || i == MT_WHIRLWIND_BOX
11876 || i == MT_FLAMEAURA_BOX || i == MT_BUBBLEWRAP_BOX || i == MT_THUNDERCOIN_BOX
11877 || i == MT_RING_BOX || i == MT_STARPOST)
11878 return false; // No rings or shields in Ultimate mode
11879
11880 // Don't include the gold repeating boxes here please.
11881 // They're likely facets of the level's design and therefore required to progress.
11882 }
11883
11884 return true;
11885 }
11886
11887 #define nightsreplace ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap))
11888
P_GetMobjtypeSubstitute(mapthing_t * mthing,mobjtype_t i)11889 static mobjtype_t P_GetMobjtypeSubstitute(mapthing_t *mthing, mobjtype_t i)
11890 {
11891 // Altering monitor spawns via cvars
11892 // If MF_GRENADEBOUNCE is set in the monitor's info,
11893 // skip this step. (Used for gold monitors)
11894 // Yeah, this is a dirty hack.
11895 if ((mobjinfo[i].flags & (MF_MONITOR|MF_GRENADEBOUNCE)) == MF_MONITOR)
11896 {
11897 if (gametyperules & GTR_RACE)
11898 {
11899 // Set powerup boxes to user settings for competition.
11900 switch (cv_competitionboxes.value)
11901 {
11902 case 1: // Mystery
11903 return MT_MYSTERY_BOX;
11904 case 2: // Teleport
11905 return MT_MIXUP_BOX;
11906 case 3: // None
11907 return MT_NULL; // Don't spawn!
11908 default:
11909 return i;
11910 }
11911 }
11912 // Set powerup boxes to user settings for other netplay modes
11913 else if (!G_CoopGametype())
11914 {
11915 switch (cv_matchboxes.value)
11916 {
11917 case 1: // Mystery
11918 return MT_MYSTERY_BOX;
11919 case 2: // Unchanging
11920 if (i == MT_MYSTERY_BOX)
11921 return MT_NULL; // don't spawn
11922 mthing->options &= ~(MTF_AMBUSH|MTF_OBJECTSPECIAL); // no random respawning!
11923 return i;
11924 case 3: // Don't spawn
11925 return MT_NULL;
11926 default:
11927 return i;
11928 }
11929 }
11930 }
11931
11932 if (nightsreplace)
11933 {
11934 if (i == MT_RING || i == MT_REDTEAMRING || i == MT_BLUETEAMRING || i == MT_COIN)
11935 return MT_NIGHTSSTAR;
11936
11937 if (i == MT_BLUESPHERE)
11938 return MT_NIGHTSCHIP;
11939 }
11940
11941 if (!(gametyperules & GTR_TEAMS))
11942 {
11943 if (i == MT_BLUETEAMRING || i == MT_REDTEAMRING)
11944 return MT_RING;
11945
11946 if (i == MT_RING_BLUEBOX || i == MT_RING_REDBOX)
11947 return MT_RING_BOX;
11948 }
11949
11950 if (modeattacking && i == MT_1UP_BOX) // 1UPs -->> Score TVs
11951 {
11952 // Either or, doesn't matter which.
11953 if (mthing->options & (MTF_AMBUSH | MTF_OBJECTSPECIAL))
11954 return MT_SCORE10K_BOX; // 10,000
11955 else
11956 return MT_SCORE1K_BOX; // 1,000
11957 }
11958
11959 return i;
11960 }
11961
P_SetupEmblem(mapthing_t * mthing,mobj_t * mobj)11962 static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
11963 {
11964 INT32 j;
11965 emblem_t* emblem = M_GetLevelEmblems(gamemap);
11966 skincolornum_t emcolor;
11967
11968 while (emblem)
11969 {
11970 if ((emblem->type == ET_GLOBAL || emblem->type == ET_SKIN) && emblem->tag == mthing->angle)
11971 break;
11972
11973 emblem = M_GetLevelEmblems(-1);
11974 }
11975
11976 if (!emblem)
11977 {
11978 CONS_Debug(DBG_GAMELOGIC, "No map emblem for map %d with tag %d found!\n", gamemap, mthing->angle);
11979 return false;
11980 }
11981
11982 j = emblem - emblemlocations;
11983
11984 I_Assert(emblemlocations[j].sprite >= 'A' && emblemlocations[j].sprite <= 'Z');
11985 P_SetMobjState(mobj, mobj->info->spawnstate + (emblemlocations[j].sprite - 'A'));
11986
11987 mobj->health = j + 1;
11988 emcolor = M_GetEmblemColor(&emblemlocations[j]); // workaround for compiler complaint about bad function casting
11989 mobj->color = (UINT16)emcolor;
11990
11991 if (emblemlocations[j].collected
11992 || (emblemlocations[j].type == ET_SKIN && emblemlocations[j].var != players[0].skin))
11993 {
11994 P_UnsetThingPosition(mobj);
11995 mobj->flags |= MF_NOCLIP;
11996 mobj->flags &= ~MF_SPECIAL;
11997 mobj->flags |= MF_NOBLOCKMAP;
11998 mobj->frame |= (tr_trans50 << FF_TRANSSHIFT);
11999 P_SetThingPosition(mobj);
12000 }
12001 else
12002 {
12003 mobj->frame &= ~FF_TRANSMASK;
12004
12005 if (emblemlocations[j].type == ET_GLOBAL)
12006 {
12007 mobj->reactiontime = emblemlocations[j].var;
12008 if (emblemlocations[j].var & GE_NIGHTSITEM)
12009 {
12010 mobj->flags |= MF_NIGHTSITEM;
12011 mobj->flags &= ~MF_SPECIAL;
12012 mobj->flags2 |= MF2_DONTDRAW;
12013 }
12014 }
12015 }
12016 return true;
12017 }
12018
P_SetupMace(mapthing_t * mthing,mobj_t * mobj,boolean * doangle)12019 static boolean P_SetupMace(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
12020 {
12021 fixed_t mlength, mmaxlength, mlengthset, mspeed, mphase, myaw, mpitch, mminlength, mnumspokes, mpinch, mroll, mnumnospokes, mwidth, mwidthset, mmin, msound, radiusfactor, widthfactor;
12022 angle_t mspokeangle;
12023 mobjtype_t chainlink, macetype, firsttype, linktype;
12024 boolean mdosound, mdocenter, mchainlike = false;
12025 mobj_t *spawnee = NULL, *hprev = mobj;
12026 mobjflag_t mflagsapply;
12027 mobjflag2_t mflags2apply;
12028 mobjeflag_t meflagsapply;
12029 INT32 line;
12030 const size_t mthingi = (size_t)(mthing - mapthings);
12031
12032 // Find the corresponding linedef special, using angle as tag
12033 line = Tag_FindLineSpecial(9, mthing->angle);
12034
12035 if (line == -1)
12036 {
12037 CONS_Debug(DBG_GAMELOGIC, "Mace chain (mapthing #%s) needs to be tagged to a #9 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
12038 return false;
12039 }
12040 /*
12041 mapthing -
12042 MTF_AMBUSH :
12043 MT_SPRINGBALLPOINT - upgrade from yellow to red spring
12044 anything else - bigger mace/chain theory
12045 MTF_OBJECTSPECIAL - force silent
12046 MTF_GRAVFLIP - flips objects, doesn't affect chain arrangements
12047 Parameter value : number of "spokes"
12048
12049 linedef -
12050 ML_NOCLIMB :
12051 MT_CHAINPOINT/MT_CHAINMACEPOINT with ML_EFFECT1 applied - Direction not controllable
12052 anything else - no functionality
12053 ML_EFFECT1 : Swings instead of spins
12054 ML_EFFECT2 : Linktype is replaced with macetype for all spokes not ending in chains (inverted for MT_FIREBARPOINT)
12055 ML_EFFECT3 : Spawn a bonus linktype at the hinge point
12056 ML_EFFECT4 : Don't clip inside the ground
12057 ML_EFFECT5 : Don't stop thinking when too far away
12058 */
12059 mlength = abs(lines[line].dx >> FRACBITS);
12060 mspeed = abs(lines[line].dy >> (FRACBITS - 4));
12061 mphase = (sides[lines[line].sidenum[0]].textureoffset >> FRACBITS) % 360;
12062 if ((mminlength = -sides[lines[line].sidenum[0]].rowoffset >> FRACBITS) < 0)
12063 mminlength = 0;
12064 else if (mminlength > mlength - 1)
12065 mminlength = mlength - 1;
12066 mpitch = (lines[line].frontsector->floorheight >> FRACBITS) % 360;
12067 myaw = (lines[line].frontsector->ceilingheight >> FRACBITS) % 360;
12068
12069 mnumspokes = mthing->extrainfo + 1;
12070 mspokeangle = FixedAngle((360*FRACUNIT)/mnumspokes) >> ANGLETOFINESHIFT;
12071
12072 if (lines[line].backsector)
12073 {
12074 mpinch = (lines[line].backsector->floorheight >> FRACBITS) % 360;
12075 mroll = (lines[line].backsector->ceilingheight >> FRACBITS) % 360;
12076 mnumnospokes = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS);
12077 if ((mwidth = sides[lines[line].sidenum[1]].rowoffset >> FRACBITS) < 0)
12078 mwidth = 0;
12079 }
12080 else
12081 mpinch = mroll = mnumnospokes = mwidth = 0;
12082
12083 CONS_Debug(DBG_GAMELOGIC, "Mace/Chain (mapthing #%s):\n"
12084 "Length is %d (minus %d)\n"
12085 "Speed is %d\n"
12086 "Phase is %d\n"
12087 "Yaw is %d\n"
12088 "Pitch is %d\n"
12089 "No. of spokes is %d (%d antispokes)\n"
12090 "Pinch is %d\n"
12091 "Roll is %d\n"
12092 "Width is %d\n",
12093 sizeu1(mthingi), mlength, mminlength, mspeed, mphase, myaw, mpitch, mnumspokes, mnumnospokes, mpinch, mroll, mwidth);
12094
12095 if (mnumnospokes > 0 && (mnumnospokes < mnumspokes))
12096 mnumnospokes = mnumspokes/mnumnospokes;
12097 else
12098 mnumnospokes = ((mobj->type == MT_CHAINMACEPOINT) ? (mnumspokes) : 0);
12099
12100 mobj->lastlook = mspeed;
12101 mobj->movecount = mobj->lastlook;
12102 mobj->angle = FixedAngle(myaw << FRACBITS);
12103 *doangle = false;
12104 mobj->threshold = (FixedAngle(mpitch << FRACBITS) >> ANGLETOFINESHIFT);
12105 mobj->movefactor = mpinch;
12106 mobj->movedir = 0;
12107
12108 // Mobjtype selection
12109 switch (mobj->type)
12110 {
12111 case MT_SPRINGBALLPOINT:
12112 macetype = ((mthing->options & MTF_AMBUSH)
12113 ? MT_REDSPRINGBALL
12114 : MT_YELLOWSPRINGBALL);
12115 chainlink = MT_SMALLMACECHAIN;
12116 break;
12117 case MT_FIREBARPOINT:
12118 macetype = ((mthing->options & MTF_AMBUSH)
12119 ? MT_BIGFIREBAR
12120 : MT_SMALLFIREBAR);
12121 chainlink = MT_NULL;
12122 break;
12123 case MT_CUSTOMMACEPOINT:
12124 macetype = (mobjtype_t)sides[lines[line].sidenum[0]].toptexture;
12125 if (lines[line].backsector)
12126 chainlink = (mobjtype_t)sides[lines[line].sidenum[1]].toptexture;
12127 else
12128 chainlink = MT_NULL;
12129 break;
12130 case MT_CHAINPOINT:
12131 if (mthing->options & MTF_AMBUSH)
12132 {
12133 macetype = MT_BIGGRABCHAIN;
12134 chainlink = MT_BIGMACECHAIN;
12135 }
12136 else
12137 {
12138 macetype = MT_SMALLGRABCHAIN;
12139 chainlink = MT_SMALLMACECHAIN;
12140 }
12141 mchainlike = true;
12142 break;
12143 default:
12144 if (mthing->options & MTF_AMBUSH)
12145 {
12146 macetype = MT_BIGMACE;
12147 chainlink = MT_BIGMACECHAIN;
12148 }
12149 else
12150 {
12151 macetype = MT_SMALLMACE;
12152 chainlink = MT_SMALLMACECHAIN;
12153 }
12154 break;
12155 }
12156
12157 if (!macetype && !chainlink)
12158 return true;
12159
12160 if (mobj->type == MT_CHAINPOINT)
12161 {
12162 if (!mlength)
12163 return true;
12164 }
12165 else
12166 mlength++;
12167
12168 firsttype = macetype;
12169
12170 // Adjustable direction
12171 if (lines[line].flags & ML_NOCLIMB)
12172 mobj->flags |= MF_SLIDEME;
12173
12174 // Swinging
12175 if (lines[line].flags & ML_EFFECT1)
12176 {
12177 mobj->flags2 |= MF2_STRONGBOX;
12178 mmin = ((mnumnospokes > 1) ? 1 : 0);
12179 }
12180 else
12181 mmin = mnumspokes;
12182
12183 // If over distance away, don't move UNLESS this flag is applied
12184 if (lines[line].flags & ML_EFFECT5)
12185 mobj->flags2 |= MF2_BOSSNOTRAP;
12186
12187 // Make the links the same type as the end - repeated below
12188 if ((mobj->type != MT_CHAINPOINT) && (((lines[line].flags & ML_EFFECT2) == ML_EFFECT2) != (mobj->type == MT_FIREBARPOINT))) // exclusive or
12189 {
12190 linktype = macetype;
12191 radiusfactor = 2; // Double the radius.
12192 }
12193 else
12194 radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1);
12195
12196 if (!mchainlike)
12197 mchainlike = (firsttype == chainlink);
12198 widthfactor = (mchainlike ? 1 : 2);
12199
12200 mflagsapply = ((lines[line].flags & ML_EFFECT4) ? 0 : (MF_NOCLIP | MF_NOCLIPHEIGHT));
12201 mflags2apply = ((mthing->options & MTF_OBJECTFLIP) ? MF2_OBJECTFLIP : 0);
12202 meflagsapply = ((mthing->options & MTF_OBJECTFLIP) ? MFE_VERTICALFLIP : 0);
12203
12204 msound = (mchainlike ? 0 : (mwidth & 1));
12205
12206 // Quick and easy preparatory variable setting
12207 mphase = (FixedAngle(mphase << FRACBITS) >> ANGLETOFINESHIFT);
12208 mroll = (FixedAngle(mroll << FRACBITS) >> ANGLETOFINESHIFT);
12209
12210 #define makemace(mobjtype, dist, moreflags2) {\
12211 spawnee = P_SpawnMobj(mobj->x, mobj->y, mobj->z, mobjtype);\
12212 P_SetTarget(&spawnee->tracer, mobj);\
12213 spawnee->threshold = mphase;\
12214 spawnee->friction = mroll;\
12215 spawnee->movefactor = mwidthset;\
12216 spawnee->movecount = dist;\
12217 spawnee->angle = myaw;\
12218 spawnee->flags |= (MF_NOGRAVITY|mflagsapply);\
12219 spawnee->flags2 |= (mflags2apply|moreflags2);\
12220 spawnee->eflags |= meflagsapply;\
12221 P_SetTarget(&hprev->hnext, spawnee);\
12222 P_SetTarget(&spawnee->hprev, hprev);\
12223 hprev = spawnee;\
12224 }
12225
12226 mdosound = (mspeed && !(mthing->options & MTF_OBJECTSPECIAL));
12227 mdocenter = (macetype && (lines[line].flags & ML_EFFECT3));
12228
12229 // The actual spawning of spokes
12230 while (mnumspokes-- > 0)
12231 {
12232 // Offsets
12233 if (lines[line].flags & ML_EFFECT1) // Swinging
12234 mroll = (mroll - mspokeangle) & FINEMASK;
12235 else // Spinning
12236 mphase = (mphase - mspokeangle) & FINEMASK;
12237
12238 if (mnumnospokes && !(mnumspokes % mnumnospokes)) // Skipping a "missing" spoke
12239 {
12240 if (mobj->type != MT_CHAINMACEPOINT)
12241 continue;
12242
12243 linktype = chainlink;
12244 firsttype = ((mthing->options & MTF_AMBUSH) ? MT_BIGGRABCHAIN : MT_SMALLGRABCHAIN);
12245 mmaxlength = 1 + (mlength - 1) * radiusfactor;
12246 radiusfactor = widthfactor = 1;
12247 }
12248 else
12249 {
12250 if (mobj->type == MT_CHAINMACEPOINT)
12251 {
12252 // Make the links the same type as the end - repeated above
12253 if (lines[line].flags & ML_EFFECT2)
12254 {
12255 linktype = macetype;
12256 radiusfactor = 2;
12257 }
12258 else
12259 radiusfactor = (((linktype = chainlink) == MT_NULL) ? 2 : 1);
12260
12261 firsttype = macetype;
12262 widthfactor = 2;
12263 }
12264
12265 mmaxlength = mlength;
12266 }
12267
12268 mwidthset = mwidth;
12269 mlengthset = mminlength;
12270
12271 if (mdocenter) // Innermost link
12272 makemace(linktype, 0, 0);
12273
12274 // Out from the center...
12275 if (linktype)
12276 {
12277 while ((++mlengthset) < mmaxlength)
12278 makemace(linktype, radiusfactor*mlengthset, 0);
12279 }
12280 else
12281 mlengthset = mmaxlength;
12282
12283 // Outermost mace/link
12284 if (firsttype)
12285 makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
12286
12287 if (!mwidth)
12288 {
12289 if (mdosound && mnumspokes <= mmin) // Can it make a sound?
12290 spawnee->flags2 |= MF2_BOSSNOTRAP;
12291 }
12292 else
12293 {
12294 // Across the bar!
12295 if (!firsttype)
12296 mwidthset = -mwidth;
12297 else if (mwidth > 0)
12298 {
12299 while ((mwidthset -= widthfactor) > -mwidth)
12300 {
12301 makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
12302 if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
12303 spawnee->flags2 |= MF2_BOSSNOTRAP;
12304 }
12305 }
12306 else
12307 {
12308 while ((mwidthset += widthfactor) < -mwidth)
12309 {
12310 makemace(firsttype, radiusfactor*mlengthset, MF2_AMBUSH);
12311 if (mdosound && (mwidthset == msound) && mnumspokes <= mmin) // Can it make a sound?
12312 spawnee->flags2 |= MF2_BOSSNOTRAP;
12313 }
12314 }
12315 mwidth = -mwidth;
12316
12317 // Outermost mace/link again!
12318 if (firsttype)
12319 makemace(firsttype, radiusfactor*(mlengthset--), MF2_AMBUSH);
12320
12321 // ...and then back into the center!
12322 if (linktype)
12323 while (mlengthset > mminlength)
12324 makemace(linktype, radiusfactor*(mlengthset--), 0);
12325
12326 if (mdocenter) // Innermost link
12327 makemace(linktype, 0, 0);
12328 }
12329 }
12330 #undef makemace
12331 return true;
12332 }
12333
P_SetupParticleGen(mapthing_t * mthing,mobj_t * mobj)12334 static boolean P_SetupParticleGen(mapthing_t *mthing, mobj_t *mobj)
12335 {
12336 fixed_t radius, speed;
12337 INT32 type, numdivisions, anglespeed, ticcount;
12338 angle_t angledivision;
12339 INT32 line;
12340 const size_t mthingi = (size_t)(mthing - mapthings);
12341
12342 // Find the corresponding linedef special, using angle as tag
12343 line = Tag_FindLineSpecial(15, mthing->angle);
12344
12345 if (line == -1)
12346 {
12347 CONS_Debug(DBG_GAMELOGIC, "Particle generator (mapthing #%s) needs to be tagged to a #15 parameter line (trying to find tag %d).\n", sizeu1(mthingi), mthing->angle);
12348 return false;
12349 }
12350
12351 if (sides[lines[line].sidenum[0]].toptexture)
12352 type = sides[lines[line].sidenum[0]].toptexture; // Set as object type in p_setup.c...
12353 else
12354 type = (INT32)MT_PARTICLE;
12355
12356 if (!lines[line].backsector
12357 || (ticcount = (sides[lines[line].sidenum[1]].textureoffset >> FRACBITS)) < 1)
12358 ticcount = 3;
12359
12360 numdivisions = mthing->z;
12361
12362 if (numdivisions)
12363 {
12364 radius = R_PointToDist2(lines[line].v1->x, lines[line].v1->y, lines[line].v2->x, lines[line].v2->y);
12365 anglespeed = (sides[lines[line].sidenum[0]].rowoffset >> FRACBITS) % 360;
12366 angledivision = 360/numdivisions;
12367 }
12368 else
12369 {
12370 numdivisions = 1; // Simple trick to make A_ParticleSpawn simpler.
12371 radius = 0;
12372 anglespeed = 0;
12373 angledivision = 0;
12374 }
12375
12376 speed = abs(sides[lines[line].sidenum[0]].textureoffset);
12377 if (mthing->options & MTF_OBJECTFLIP)
12378 speed *= -1;
12379
12380 CONS_Debug(DBG_GAMELOGIC, "Particle Generator (mapthing #%s):\n"
12381 "Radius is %d\n"
12382 "Speed is %d\n"
12383 "Anglespeed is %d\n"
12384 "Numdivisions is %d\n"
12385 "Angledivision is %d\n"
12386 "Type is %d\n"
12387 "Tic seperation is %d\n",
12388 sizeu1(mthingi), radius, speed, anglespeed, numdivisions, angledivision, type, ticcount);
12389
12390 mobj->angle = 0;
12391 mobj->movefactor = speed;
12392 mobj->lastlook = numdivisions;
12393 mobj->movedir = angledivision*ANG1;
12394 mobj->movecount = anglespeed*ANG1;
12395 mobj->friction = radius;
12396 mobj->threshold = type;
12397 mobj->reactiontime = ticcount;
12398 mobj->cvmem = line;
12399 mobj->watertop = mobj->waterbottom = 0;
12400 return true;
12401 }
12402
P_SetupNiGHTSDrone(mapthing_t * mthing,mobj_t * mobj)12403 static boolean P_SetupNiGHTSDrone(mapthing_t* mthing, mobj_t* mobj)
12404 {
12405 boolean flip = mthing->options & MTF_OBJECTFLIP;
12406 boolean topaligned = (mthing->options & MTF_OBJECTSPECIAL) && !(mthing->options & MTF_EXTRA);
12407 boolean middlealigned = (mthing->options & MTF_EXTRA) && !(mthing->options & MTF_OBJECTSPECIAL);
12408 boolean bottomoffsetted = !(mthing->options & MTF_OBJECTSPECIAL) && !(mthing->options & MTF_EXTRA);
12409
12410 INT16 timelimit = mthing->angle & 0xFFF;
12411 fixed_t hitboxradius = ((mthing->angle & 0xF000) >> 12)*32*FRACUNIT;
12412 fixed_t hitboxheight = mthing->extrainfo*32*FRACUNIT;
12413 fixed_t oldheight = mobj->height;
12414 fixed_t dronemanoffset, goaloffset, sparkleoffset, droneboxmandiff, dronemangoaldiff;
12415
12416 if (timelimit > 0)
12417 mobj->health = timelimit;
12418
12419 if (hitboxradius > 0)
12420 mobj->radius = hitboxradius;
12421
12422 if (hitboxheight > 0)
12423 mobj->height = hitboxheight;
12424 else
12425 mobj->height = mobjinfo[MT_NIGHTSDRONE].height;
12426
12427 droneboxmandiff = max(mobj->height - mobjinfo[MT_NIGHTSDRONE_MAN].height, 0);
12428 dronemangoaldiff = max(mobjinfo[MT_NIGHTSDRONE_MAN].height - mobjinfo[MT_NIGHTSDRONE_GOAL].height, 0);
12429
12430 if (flip && mobj->height != oldheight)
12431 P_TeleportMove(mobj, mobj->x, mobj->y, mobj->z - (mobj->height - oldheight));
12432
12433 if (!flip)
12434 {
12435 if (topaligned) // Align droneman to top of hitbox
12436 {
12437 dronemanoffset = droneboxmandiff;
12438 goaloffset = dronemangoaldiff/2 + dronemanoffset;
12439 }
12440 else if (middlealigned) // Align droneman to center of hitbox
12441 {
12442 dronemanoffset = droneboxmandiff/2;
12443 goaloffset = dronemangoaldiff/2 + dronemanoffset;
12444 }
12445 else if (bottomoffsetted)
12446 {
12447 dronemanoffset = 24*FRACUNIT;
12448 goaloffset = dronemangoaldiff + dronemanoffset;
12449 }
12450 else
12451 {
12452 dronemanoffset = 0;
12453 goaloffset = dronemangoaldiff/2 + dronemanoffset;
12454 }
12455
12456 sparkleoffset = goaloffset - FixedMul(15*FRACUNIT, mobj->scale);
12457 }
12458 else
12459 {
12460 mobj->eflags |= MFE_VERTICALFLIP;
12461 mobj->flags2 |= MF2_OBJECTFLIP;
12462
12463 if (topaligned) // Align droneman to top of hitbox
12464 {
12465 dronemanoffset = 0;
12466 goaloffset = dronemangoaldiff/2 + dronemanoffset;
12467 }
12468 else if (middlealigned) // Align droneman to center of hitbox
12469 {
12470 dronemanoffset = droneboxmandiff/2;
12471 goaloffset = dronemangoaldiff/2 + dronemanoffset;
12472 }
12473 else if (bottomoffsetted)
12474 {
12475 dronemanoffset = droneboxmandiff - FixedMul(24*FRACUNIT, mobj->scale);
12476 goaloffset = dronemangoaldiff + dronemanoffset;
12477 }
12478 else
12479 {
12480 dronemanoffset = droneboxmandiff;
12481 goaloffset = dronemangoaldiff/2 + dronemanoffset;
12482 }
12483
12484 sparkleoffset = goaloffset + FixedMul(15*FRACUNIT, mobj->scale);
12485 }
12486
12487 // spawn visual elements
12488 {
12489 mobj_t* goalpost = P_SpawnMobjFromMobj(mobj, 0, 0, goaloffset, MT_NIGHTSDRONE_GOAL);
12490 mobj_t* sparkle = P_SpawnMobjFromMobj(mobj, 0, 0, sparkleoffset, MT_NIGHTSDRONE_SPARKLING);
12491 mobj_t* droneman = P_SpawnMobjFromMobj(mobj, 0, 0, dronemanoffset, MT_NIGHTSDRONE_MAN);
12492
12493 P_SetTarget(&mobj->target, goalpost);
12494 P_SetTarget(&goalpost->target, sparkle);
12495 P_SetTarget(&goalpost->tracer, droneman);
12496
12497 // correct Z position
12498 if (flip)
12499 {
12500 P_TeleportMove(goalpost, goalpost->x, goalpost->y, mobj->z + goaloffset);
12501 P_TeleportMove(sparkle, sparkle->x, sparkle->y, mobj->z + sparkleoffset);
12502 P_TeleportMove(droneman, droneman->x, droneman->y, mobj->z + dronemanoffset);
12503 }
12504
12505 // Remember position preference for later
12506 mobj->flags &= ~(MF_SLIDEME|MF_GRENADEBOUNCE);
12507 if (topaligned)
12508 mobj->flags |= MF_SLIDEME;
12509 else if (middlealigned)
12510 mobj->flags |= MF_GRENADEBOUNCE;
12511 else if (!bottomoffsetted)
12512 mobj->flags |= MF_SLIDEME|MF_GRENADEBOUNCE;
12513
12514 // Remember old Z position and flags for correction detection
12515 goalpost->movefactor = mobj->z;
12516 goalpost->friction = mobj->height;
12517 goalpost->threshold = mobj->flags & (MF_SLIDEME|MF_GRENADEBOUNCE);
12518 }
12519 return true;
12520 }
12521
P_SetupBooster(mapthing_t * mthing,mobj_t * mobj,boolean strong)12522 static boolean P_SetupBooster(mapthing_t* mthing, mobj_t* mobj, boolean strong)
12523 {
12524 angle_t angle = FixedAngle(mthing->angle << FRACBITS);
12525 fixed_t x1 = FINECOSINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
12526 fixed_t y1 = FINESINE((angle >> ANGLETOFINESHIFT) & FINEMASK);
12527 fixed_t x2 = FINECOSINE(((angle + ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
12528 fixed_t y2 = FINESINE(((angle + ANGLE_90) >> ANGLETOFINESHIFT) & FINEMASK);
12529 statenum_t facestate = strong ? S_REDBOOSTERSEG_FACE : S_YELLOWBOOSTERSEG_FACE;
12530 statenum_t leftstate = strong ? S_REDBOOSTERSEG_LEFT : S_YELLOWBOOSTERSEG_LEFT;
12531 statenum_t rightstate = strong ? S_REDBOOSTERSEG_RIGHT : S_YELLOWBOOSTERSEG_RIGHT;
12532 statenum_t rollerstate = strong ? S_REDBOOSTERROLLER : S_YELLOWBOOSTERROLLER;
12533
12534 mobj_t *seg = P_SpawnMobjFromMobj(mobj, 26*x1, 26*y1, 0, MT_BOOSTERSEG);
12535 seg->angle = angle - ANGLE_90;
12536 P_SetMobjState(seg, facestate);
12537 seg = P_SpawnMobjFromMobj(mobj, -26*x1, -26*y1, 0, MT_BOOSTERSEG);
12538 seg->angle = angle + ANGLE_90;
12539 P_SetMobjState(seg, facestate);
12540 seg = P_SpawnMobjFromMobj(mobj, 21*x2, 21*y2, 0, MT_BOOSTERSEG);
12541 seg->angle = angle;
12542 P_SetMobjState(seg, leftstate);
12543 seg = P_SpawnMobjFromMobj(mobj, -21*x2, -21*y2, 0, MT_BOOSTERSEG);
12544 seg->angle = angle;
12545 P_SetMobjState(seg, rightstate);
12546
12547 seg = P_SpawnMobjFromMobj(mobj, 13*(x1 + x2), 13*(y1 + y2), 0, MT_BOOSTERROLLER);
12548 seg->angle = angle;
12549 P_SetMobjState(seg, rollerstate);
12550 seg = P_SpawnMobjFromMobj(mobj, 13*(x1 - x2), 13*(y1 - y2), 0, MT_BOOSTERROLLER);
12551 seg->angle = angle;
12552 P_SetMobjState(seg, rollerstate);
12553 seg = P_SpawnMobjFromMobj(mobj, -13*(x1 + x2), -13*(y1 + y2), 0, MT_BOOSTERROLLER);
12554 seg->angle = angle;
12555 P_SetMobjState(seg, rollerstate);
12556 seg = P_SpawnMobjFromMobj(mobj, -13*(x1 - x2), -13*(y1 - y2), 0, MT_BOOSTERROLLER);
12557 seg->angle = angle;
12558 P_SetMobjState(seg, rollerstate);
12559
12560 return true;
12561 }
12562
P_SetupSpawnedMapThing(mapthing_t * mthing,mobj_t * mobj,boolean * doangle)12563 static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean *doangle)
12564 {
12565 boolean override = LUAh_MapThingSpawn(mobj, mthing);
12566
12567 if (P_MobjWasRemoved(mobj))
12568 return false;
12569
12570 if (override)
12571 return true;
12572
12573 switch (mobj->type)
12574 {
12575 case MT_EMBLEM:
12576 {
12577 if (!P_SetupEmblem(mthing, mobj))
12578 return false;
12579 break;
12580 }
12581 case MT_SKYBOX:
12582 {
12583 mtag_t tag = Tag_FGet(&mthing->tags);
12584 if (tag < 0 || tag > 15)
12585 {
12586 CONS_Debug(DBG_GAMELOGIC, "P_SetupSpawnedMapThing: Skybox ID %d of mapthing %s is not between 0 and 15!\n", tag, sizeu1((size_t)(mthing - mapthings)));
12587 break;
12588 }
12589
12590 if (mthing->options & MTF_OBJECTSPECIAL)
12591 skyboxcenterpnts[tag] = mobj;
12592 else
12593 skyboxviewpnts[tag] = mobj;
12594 break;
12595 }
12596 case MT_EGGSTATUE:
12597 if (mthing->options & MTF_EXTRA)
12598 {
12599 mobj->color = SKINCOLOR_GOLD;
12600 mobj->colorized = true;
12601 }
12602 break;
12603 case MT_EGGMOBILE3:
12604 mobj->cusval = mthing->extrainfo;
12605 break;
12606 case MT_FAN:
12607 if (mthing->options & MTF_OBJECTSPECIAL)
12608 {
12609 P_UnsetThingPosition(mobj);
12610 if (sector_list)
12611 {
12612 P_DelSeclist(sector_list);
12613 sector_list = NULL;
12614 }
12615 mobj->flags |= MF_NOSECTOR; // this flag basically turns it invisible
12616 P_SetThingPosition(mobj);
12617 }
12618 if (mthing->angle)
12619 mobj->health = mthing->angle;
12620 else
12621 mobj->health = FixedMul(mobj->subsector->sector->ceilingheight - mobj->subsector->sector->floorheight, 3*(FRACUNIT/4)) >> FRACBITS;
12622 break;
12623 case MT_METALSONIC_RACE:
12624 case MT_METALSONIC_BATTLE:
12625 case MT_FANG:
12626 case MT_ROSY:
12627 if (mthing->options & MTF_EXTRA)
12628 {
12629 mobj->color = SKINCOLOR_SILVER;
12630 mobj->colorized = true;
12631 mobj->flags2 |= MF2_SLIDEPUSH;
12632 }
12633 break;
12634 case MT_BALLOON:
12635 if (mthing->angle > 0)
12636 mobj->color = ((mthing->angle - 1) % (numskincolors - 1)) + 1;
12637 break;
12638 #define makesoftwarecorona(mo, h) \
12639 corona = P_SpawnMobjFromMobj(mo, 0, 0, h<<FRACBITS, MT_PARTICLE);\
12640 corona->sprite = SPR_FLAM;\
12641 corona->frame = (FF_FULLBRIGHT|FF_TRANS90|12);\
12642 corona->tics = -1
12643 case MT_FLAME:
12644 if (mthing->options & MTF_EXTRA)
12645 {
12646 mobj_t *corona;
12647 makesoftwarecorona(mobj, 20);
12648 P_SetScale(corona, (corona->destscale = mobj->scale*3));
12649 P_SetTarget(&mobj->tracer, corona);
12650 }
12651 break;
12652 case MT_FLAMEHOLDER:
12653 if (!(mthing->options & MTF_OBJECTSPECIAL)) // Spawn the fire
12654 {
12655 mobj_t *flame = P_SpawnMobjFromMobj(mobj, 0, 0, mobj->height, MT_FLAME);
12656 P_SetTarget(&flame->target, mobj);
12657 flame->flags2 |= MF2_BOSSNOTRAP;
12658 if (mthing->options & MTF_EXTRA)
12659 {
12660 mobj_t *corona;
12661 makesoftwarecorona(flame, 20);
12662 P_SetScale(corona, (corona->destscale = flame->scale*3));
12663 P_SetTarget(&flame->tracer, corona);
12664 }
12665 }
12666 break;
12667 case MT_CANDLE:
12668 case MT_CANDLEPRICKET:
12669 if (mthing->options & MTF_EXTRA)
12670 {
12671 mobj_t *corona;
12672 makesoftwarecorona(mobj, ((mobj->type == MT_CANDLE) ? 42 : 176));
12673 }
12674 break;
12675 #undef makesoftwarecorona
12676 case MT_JACKO1:
12677 case MT_JACKO2:
12678 case MT_JACKO3:
12679 if (!(mthing->options & MTF_EXTRA)) // take the torch out of the crafting recipe
12680 {
12681 mobj_t *overlay = P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY);
12682 P_SetTarget(&overlay->target, mobj);
12683 P_SetMobjState(overlay, mobj->info->raisestate);
12684 }
12685 break;
12686 case MT_WATERDRIP:
12687 mobj->tics = 3*TICRATE + mthing->angle;
12688 break;
12689 case MT_FLAMEJET:
12690 case MT_VERTICALFLAMEJET:
12691 mobj->threshold = (mthing->angle >> 10) & 7;
12692 mobj->movecount = (mthing->angle >> 13);
12693
12694 mobj->threshold *= (TICRATE/2);
12695 mobj->movecount *= (TICRATE/2);
12696
12697 mobj->movedir = mthing->extrainfo;
12698 break;
12699 case MT_MACEPOINT:
12700 case MT_CHAINMACEPOINT:
12701 case MT_SPRINGBALLPOINT:
12702 case MT_CHAINPOINT:
12703 case MT_FIREBARPOINT:
12704 case MT_CUSTOMMACEPOINT:
12705 if (!P_SetupMace(mthing, mobj, doangle))
12706 return false;
12707 break;
12708 case MT_PARTICLEGEN:
12709 if (!P_SetupParticleGen(mthing, mobj))
12710 return false;
12711 break;
12712 case MT_ROCKSPAWNER:
12713 mobj->threshold = mthing->angle;
12714 mobj->movecount = mthing->extrainfo;
12715 break;
12716 case MT_POPUPTURRET:
12717 if (mthing->angle)
12718 mobj->threshold = mthing->angle;
12719 else
12720 mobj->threshold = (TICRATE*2)-1;
12721 break;
12722 case MT_NIGHTSBUMPER:
12723 // Lower 4 bits specify the angle of
12724 // the bumper in 30 degree increments.
12725 mobj->threshold = (mthing->options & 15) % 12; // It loops over, etc
12726 P_SetMobjState(mobj, mobj->info->spawnstate + mobj->threshold);
12727 break;
12728 case MT_EGGCAPSULE:
12729 if (mthing->angle <= 0)
12730 mthing->angle = 20; // prevent 0 health
12731
12732 mobj->health = mthing->angle;
12733 mobj->threshold = min(mthing->extrainfo, 7);
12734 break;
12735 case MT_TUBEWAYPOINT:
12736 {
12737 UINT8 sequence = mthing->angle >> 8;
12738 UINT8 id = mthing->angle & 255;
12739 mobj->health = id;
12740 mobj->threshold = sequence;
12741 P_AddWaypoint(sequence, id, mobj);
12742 break;
12743 }
12744 case MT_IDEYAANCHOR:
12745 mobj->health = mthing->extrainfo;
12746 break;
12747 case MT_NIGHTSDRONE:
12748 if (!P_SetupNiGHTSDrone(mthing, mobj))
12749 return false;
12750 break;
12751 case MT_HIVEELEMENTAL:
12752 if (mthing->extrainfo)
12753 mobj->extravalue1 = mthing->extrainfo;
12754 break;
12755 case MT_GLAREGOYLE:
12756 case MT_GLAREGOYLEUP:
12757 case MT_GLAREGOYLEDOWN:
12758 case MT_GLAREGOYLELONG:
12759 if (mthing->angle >= 360)
12760 mobj->tics += 7*(mthing->angle/360) + 1; // starting delay
12761 break;
12762 case MT_DSZSTALAGMITE:
12763 case MT_DSZ2STALAGMITE:
12764 case MT_KELP:
12765 if (mthing->options & MTF_OBJECTSPECIAL) { // make mobj twice as big as normal
12766 P_SetScale(mobj, 2*mobj->scale); // not 2*FRACUNIT in case of something like the old ERZ3 mode
12767 mobj->destscale = mobj->scale;
12768 }
12769 break;
12770 case MT_THZTREE:
12771 { // Spawn the branches
12772 angle_t mobjangle = FixedAngle((mthing->angle % 113) << FRACBITS);
12773 P_SpawnMobjFromMobj(mobj, FRACUNIT, 0, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_22h;
12774 P_SpawnMobjFromMobj(mobj, 0, FRACUNIT, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_157h;
12775 P_SpawnMobjFromMobj(mobj, -FRACUNIT, 0, 0, MT_THZTREEBRANCH)->angle = mobjangle + ANGLE_270;
12776 }
12777 break;
12778 case MT_CEZPOLE1:
12779 case MT_CEZPOLE2:
12780 { // Spawn the banner
12781 angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS);
12782 P_SpawnMobjFromMobj(mobj,
12783 P_ReturnThrustX(mobj, mobjangle, 4 << FRACBITS),
12784 P_ReturnThrustY(mobj, mobjangle, 4 << FRACBITS),
12785 0, ((mobj->type == MT_CEZPOLE1) ? MT_CEZBANNER1 : MT_CEZBANNER2))->angle = mobjangle + ANGLE_90;
12786 }
12787 break;
12788 case MT_HHZTREE_TOP:
12789 { // Spawn the branches
12790 angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS) & (ANGLE_90 - 1);
12791 mobj_t* leaf;
12792 #define doleaf(x, y) \
12793 leaf = P_SpawnMobjFromMobj(mobj, x, y, 0, MT_HHZTREE_PART);\
12794 leaf->angle = mobjangle;\
12795 P_SetMobjState(leaf, leaf->info->seestate);\
12796 mobjangle += ANGLE_90
12797 doleaf(FRACUNIT, 0);
12798 doleaf(0, FRACUNIT);
12799 doleaf(-FRACUNIT, 0);
12800 doleaf(0, -FRACUNIT);
12801 #undef doleaf
12802 }
12803 break;
12804 case MT_SMASHINGSPIKEBALL:
12805 if (mthing->angle > 0)
12806 mobj->tics += mthing->angle;
12807 break;
12808 case MT_LAVAFALL:
12809 mobj->fuse = 30 + mthing->angle;
12810 if (mthing->options & MTF_AMBUSH)
12811 {
12812 P_SetScale(mobj, 2*mobj->scale);
12813 mobj->destscale = mobj->scale;
12814 }
12815 break;
12816 case MT_PYREFLY:
12817 //start on fire if Ambush flag is set, otherwise behave normally
12818 if (mthing->options & MTF_AMBUSH)
12819 {
12820 P_SetMobjState(mobj, mobj->info->meleestate);
12821 mobj->extravalue2 = 2;
12822 S_StartSound(mobj, sfx_s3kc2l);
12823 }
12824 break;
12825 case MT_BIGFERN:
12826 {
12827 angle_t angle = FixedAngle(mthing->angle << FRACBITS);
12828 UINT8 j;
12829 for (j = 0; j < 8; j++)
12830 {
12831 angle_t fa = (angle >> ANGLETOFINESHIFT) & FINEMASK;
12832 fixed_t xoffs = FINECOSINE(fa);
12833 fixed_t yoffs = FINESINE(fa);
12834 mobj_t* leaf = P_SpawnMobjFromMobj(mobj, xoffs, yoffs, 0, MT_BIGFERNLEAF);
12835 leaf->angle = angle;
12836 angle += ANGLE_45;
12837 }
12838 break;
12839 }
12840 case MT_REDBOOSTER:
12841 case MT_YELLOWBOOSTER:
12842 if (!P_SetupBooster(mthing, mobj, mobj->type == MT_REDBOOSTER))
12843 return false;
12844 break;
12845 case MT_AXIS:
12846 // Inverted if uppermost bit is set
12847 if (mthing->angle & 16384)
12848 mobj->flags2 |= MF2_AMBUSH;
12849
12850 if (mthing->angle > 0)
12851 mobj->radius = (mthing->angle & 16383) << FRACBITS;
12852 // FALLTHRU
12853 case MT_AXISTRANSFER:
12854 case MT_AXISTRANSFERLINE:
12855 // Mare it belongs to
12856 mobj->threshold = min(mthing->extrainfo, 7);
12857
12858 // # in the mare
12859 mobj->health = mthing->options;
12860
12861 mobj->flags2 |= MF2_AXIS;
12862 break;
12863 case MT_TOKEN:
12864 // We advanced tokenbits earlier due to the return check.
12865 // Subtract 1 here for the correct value.
12866 mobj->health = 1 << (tokenbits - 1);
12867 break;
12868 case MT_CYBRAKDEMON:
12869 if (mthing->options & MTF_AMBUSH)
12870 {
12871 mobj_t* elecmobj;
12872 elecmobj = P_SpawnMobj(mobj->x, mobj->y, mobj->z, MT_CYBRAKDEMON_ELECTRIC_BARRIER);
12873 P_SetTarget(&elecmobj->target, mobj);
12874 elecmobj->angle = FixedAngle(mthing->angle << FRACBITS);
12875 elecmobj->destscale = mobj->scale*2;
12876 P_SetScale(elecmobj, elecmobj->destscale);
12877 }
12878 break;
12879 case MT_STARPOST:
12880 {
12881 thinker_t* th;
12882 mobj_t* mo2;
12883 boolean foundanother = false;
12884
12885 if (mthing->extrainfo)
12886 // Allow thing Parameter to define star post num too!
12887 // For starposts above param 15 (the 16th), add 360 to the angle like before and start parameter from 1 (NOT 0)!
12888 // So the 16th starpost is angle=0 param=15, the 17th would be angle=360 param=1.
12889 // This seems more intuitive for mappers to use until UDMF is ready, since most SP maps won't have over 16 consecutive star posts.
12890 mobj->health = mthing->extrainfo + (mthing->angle/360)*15 + 1;
12891 else
12892 // Old behavior if Parameter is 0; add 360 to the angle for each consecutive star post.
12893 mobj->health = (mthing->angle/360) + 1;
12894
12895 // See if other starposts exist in this level that have the same value.
12896 for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
12897 {
12898 if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
12899 continue;
12900
12901 mo2 = (mobj_t*)th;
12902
12903 if (mo2 == mobj)
12904 continue;
12905
12906 if (mo2->type == MT_STARPOST && mo2->health == mobj->health)
12907 {
12908 foundanother = true;
12909 break;
12910 }
12911 }
12912
12913 if (!foundanother)
12914 numstarposts++;
12915 break;
12916 }
12917 case MT_SPIKE:
12918 // Pop up spikes!
12919 if (mthing->options & MTF_OBJECTSPECIAL)
12920 {
12921 mobj->flags &= ~MF_SCENERY;
12922 mobj->fuse = (16 - mthing->extrainfo)*(mthing->angle + mobj->info->speed)/16;
12923 if (mthing->options & MTF_EXTRA)
12924 P_SetMobjState(mobj, mobj->info->meleestate);
12925 }
12926 // Use per-thing collision for spikes if the deaf flag isn't checked.
12927 if (!(mthing->options & MTF_AMBUSH) && !metalrecording)
12928 {
12929 P_UnsetThingPosition(mobj);
12930 mobj->flags &= ~(MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIPHEIGHT);
12931 mobj->flags |= MF_SOLID;
12932 P_SetThingPosition(mobj);
12933 }
12934 break;
12935 case MT_WALLSPIKE:
12936 // Pop up spikes!
12937 if (mthing->options & MTF_OBJECTSPECIAL)
12938 {
12939 mobj->flags &= ~MF_SCENERY;
12940 mobj->fuse = (16 - mthing->extrainfo)*((mthing->angle/360) + mobj->info->speed)/16;
12941 if (mthing->options & MTF_EXTRA)
12942 P_SetMobjState(mobj, mobj->info->meleestate);
12943 }
12944 // Use per-thing collision for spikes if the deaf flag isn't checked.
12945 if (!(mthing->options & MTF_AMBUSH) && !metalrecording)
12946 {
12947 P_UnsetThingPosition(mobj);
12948 mobj->flags &= ~(MF_NOBLOCKMAP | MF_NOCLIPHEIGHT);
12949 mobj->flags |= MF_SOLID;
12950 P_SetThingPosition(mobj);
12951 }
12952
12953 // spawn base
12954 {
12955 const angle_t mobjangle = FixedAngle(mthing->angle << FRACBITS); // the mobj's own angle hasn't been set quite yet so...
12956 const fixed_t baseradius = mobj->radius - mobj->scale;
12957 mobj_t* base = P_SpawnMobj(
12958 mobj->x - P_ReturnThrustX(mobj, mobjangle, baseradius),
12959 mobj->y - P_ReturnThrustY(mobj, mobjangle, baseradius),
12960 mobj->z, MT_WALLSPIKEBASE);
12961 base->angle = mobjangle + ANGLE_90;
12962 base->destscale = mobj->destscale;
12963 P_SetScale(base, mobj->scale);
12964 P_SetTarget(&base->target, mobj);
12965 P_SetTarget(&mobj->tracer, base);
12966 }
12967 break;
12968 case MT_RING_BOX:
12969 //count 10 ring boxes into the number of rings equation too.
12970 if (nummaprings >= 0)
12971 nummaprings += 10;
12972 break;
12973 case MT_BIGTUMBLEWEED:
12974 case MT_LITTLETUMBLEWEED:
12975 if (mthing->options & MTF_AMBUSH)
12976 {
12977 fixed_t offset = FixedMul(16*FRACUNIT, mobj->scale);
12978 mobj->momx += P_RandomChance(FRACUNIT/2) ? offset : -offset;
12979 mobj->momy += P_RandomChance(FRACUNIT/2) ? offset : -offset;
12980 mobj->momz += offset;
12981 }
12982 break;
12983 case MT_REDFLAG:
12984 redflag = mobj;
12985 rflagpoint = mobj->spawnpoint;
12986 break;
12987 case MT_BLUEFLAG:
12988 blueflag = mobj;
12989 bflagpoint = mobj->spawnpoint;
12990 break;
12991 case MT_PUSH:
12992 case MT_PULL:
12993 mobj->health = 0; // Default behaviour: pushing uses XY, fading uses XYZ
12994
12995 if (mthing->options & MTF_AMBUSH)
12996 mobj->health |= 1; // If ambush is set, push using XYZ
12997 if (mthing->options & MTF_OBJECTSPECIAL)
12998 mobj->health |= 2; // If object special is set, fade using XY
12999
13000 if (G_IsSpecialStage(gamemap))
13001 P_SetMobjState(mobj, (mobj->type == MT_PUSH) ? S_GRAVWELLGREEN : S_GRAVWELLRED);
13002 break;
13003 case MT_NIGHTSSTAR:
13004 if (maptol & TOL_XMAS)
13005 P_SetMobjState(mobj, mobj->info->seestate);
13006 break;
13007 default:
13008 break;
13009 }
13010
13011 if (mobj->flags & MF_BOSS)
13012 {
13013 if (mthing->options & MTF_OBJECTSPECIAL) // No egg trap for this boss
13014 mobj->flags2 |= MF2_BOSSNOTRAP;
13015 }
13016
13017 return true;
13018 }
13019
P_SetAmbush(mobj_t * mobj)13020 static void P_SetAmbush(mobj_t *mobj)
13021 {
13022 if (mobj->type == MT_YELLOWDIAG || mobj->type == MT_REDDIAG || mobj->type == MT_BLUEDIAG)
13023 mobj->angle += ANGLE_22h;
13024
13025 if (mobj->flags & MF_NIGHTSITEM)
13026 {
13027 // Spawn already displayed
13028 mobj->flags |= MF_SPECIAL;
13029 mobj->flags &= ~MF_NIGHTSITEM;
13030 }
13031
13032 if (mobj->flags & MF_PUSHABLE)
13033 mobj->flags &= ~MF_PUSHABLE;
13034
13035 if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
13036 {
13037 // flag for strong/weak random boxes
13038 // any monitor with nonzero speed is allowed to respawn like this
13039 mobj->flags2 |= MF2_AMBUSH;
13040 }
13041
13042 else if (mobj->type != MT_AXIS &&
13043 mobj->type != MT_AXISTRANSFER &&
13044 mobj->type != MT_AXISTRANSFERLINE &&
13045 mobj->type != MT_NIGHTSBUMPER &&
13046 mobj->type != MT_STARPOST)
13047 mobj->flags2 |= MF2_AMBUSH;
13048 }
13049
P_SetObjectSpecial(mobj_t * mobj)13050 static void P_SetObjectSpecial(mobj_t *mobj)
13051 {
13052 if (mobj->type == MT_YELLOWDIAG || mobj->type == MT_REDDIAG || mobj->type == MT_BLUEDIAG)
13053 mobj->flags |= MF_NOGRAVITY;
13054
13055 if ((mobj->flags & MF_MONITOR) && mobj->info->speed != 0)
13056 {
13057 // flag for strong/weak random boxes
13058 // any monitor with nonzero speed is allowed to respawn like this
13059 mobj->flags2 |= MF2_STRONGBOX;
13060 }
13061
13062 // Requires you to be in bonus time to activate
13063 if (mobj->flags & MF_NIGHTSITEM)
13064 mobj->flags2 |= MF2_STRONGBOX;
13065
13066 // Pushables bounce and slide coolly with object special flag set
13067 if (mobj->flags & MF_PUSHABLE)
13068 {
13069 mobj->flags2 |= MF2_SLIDEPUSH;
13070 mobj->flags |= MF_BOUNCE;
13071 }
13072 }
13073
P_SpawnMobjFromMapThing(mapthing_t * mthing,fixed_t x,fixed_t y,fixed_t z,mobjtype_t i)13074 static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, fixed_t z, mobjtype_t i)
13075 {
13076 mobj_t *mobj = NULL;
13077 boolean doangle = true;
13078
13079 mobj = P_SpawnMobj(x, y, z, i);
13080 mobj->spawnpoint = mthing;
13081
13082 P_SetScale(mobj, FixedMul(mobj->scale, mthing->scale));
13083 mobj->destscale = FixedMul(mobj->destscale, mthing->scale);
13084
13085 if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle))
13086 return mobj;
13087
13088 if (doangle)
13089 mobj->angle = FixedAngle(mthing->angle << FRACBITS);
13090
13091 mobj->pitch = FixedAngle(mthing->pitch << FRACBITS);
13092 mobj->roll = FixedAngle(mthing->roll << FRACBITS);
13093
13094 mthing->mobj = mobj;
13095
13096 // ignore MTF_ flags and return early
13097 if (i == MT_NIGHTSBUMPER)
13098 return mobj;
13099
13100 if ((mthing->options & MTF_AMBUSH)
13101 && (mthing->options & MTF_OBJECTSPECIAL)
13102 && (mobj->flags & MF_PUSHABLE))
13103 mobj->flags2 |= MF2_CLASSICPUSH;
13104 else
13105 {
13106 if (mthing->options & MTF_AMBUSH)
13107 P_SetAmbush(mobj);
13108
13109 if (mthing->options & MTF_OBJECTSPECIAL)
13110 P_SetObjectSpecial(mobj);
13111 }
13112
13113 // Generic reverse gravity for individual objects flag.
13114 if (mthing->options & MTF_OBJECTFLIP)
13115 {
13116 mobj->eflags |= MFE_VERTICALFLIP;
13117 mobj->flags2 |= MF2_OBJECTFLIP;
13118 }
13119
13120 // Extra functionality
13121 if (mthing->options & MTF_EXTRA)
13122 {
13123 if (mobj->flags & MF_MONITOR && (mthing->angle & 16384))
13124 {
13125 // Store line exec tag to run upon popping
13126 mobj->lastlook = (mthing->angle & 16383);
13127 }
13128 }
13129
13130 // Final set of not being able to draw nightsitems.
13131 if (mobj->flags & MF_NIGHTSITEM)
13132 mobj->flags2 |= MF2_DONTDRAW;
13133
13134 return mobj;
13135 }
13136
13137 //
13138 // P_SpawnMapThing
13139 // The fields of the mapthing should
13140 // already be in host byte order.
13141 //
P_SpawnMapThing(mapthing_t * mthing)13142 mobj_t *P_SpawnMapThing(mapthing_t *mthing)
13143 {
13144 mobjtype_t i;
13145 mobj_t *mobj = NULL;
13146 fixed_t x, y, z;
13147
13148 if (!mthing->type)
13149 return mobj; // Ignore type-0 things as NOPs
13150
13151 if (mthing->type == 3328) // 3D Mode start Thing
13152 return mobj;
13153
13154 if (!objectplacing && P_SpawnNonMobjMapThing(mthing))
13155 return mobj;
13156
13157 i = P_GetMobjtype(mthing->type);
13158 if (i == MT_UNKNOWN)
13159 CONS_Alert(CONS_WARNING, M_GetText("Unknown thing type %d placed at (%d, %d)\n"), mthing->type, mthing->x, mthing->y);
13160
13161 // Skip all returning/substitution code in objectplace.
13162 if (!objectplacing)
13163 {
13164 if (!P_AllowMobjSpawn(mthing, i))
13165 return mobj;
13166
13167 i = P_GetMobjtypeSubstitute(mthing, i);
13168 if (i == MT_NULL) // Don't spawn mobj
13169 return mobj;
13170 }
13171
13172 x = mthing->x << FRACBITS;
13173 y = mthing->y << FRACBITS;
13174 z = P_GetMapThingSpawnHeight(i, mthing, x, y);
13175 return P_SpawnMobjFromMapThing(mthing, x, y, z, i);
13176 }
13177
P_SpawnHoopInternal(mapthing_t * mthing,INT32 hoopsize,fixed_t sizefactor)13178 static void P_SpawnHoopInternal(mapthing_t *mthing, INT32 hoopsize, fixed_t sizefactor)
13179 {
13180 mobj_t *mobj = NULL;
13181 mobj_t *nextmobj = NULL;
13182 mobj_t *hoopcenter;
13183 TMatrix *pitchmatrix, *yawmatrix;
13184 fixed_t radius = hoopsize*sizefactor;
13185 INT32 i;
13186 angle_t fa;
13187 TVector v, *res;
13188 fixed_t x = mthing->x << FRACBITS;
13189 fixed_t y = mthing->y << FRACBITS;
13190 fixed_t z = P_GetMobjSpawnHeight(MT_HOOP, x, y, mthing->z << FRACBITS, 0, false, mthing->scale);
13191
13192 hoopcenter = P_SpawnMobj(x, y, z, MT_HOOPCENTER);
13193 hoopcenter->spawnpoint = mthing;
13194 hoopcenter->z -= hoopcenter->height/2;
13195
13196 P_UnsetThingPosition(hoopcenter);
13197 hoopcenter->x = x;
13198 hoopcenter->y = y;
13199 P_SetThingPosition(hoopcenter);
13200
13201 // Scale 0-255 to 0-359 =(
13202 hoopcenter->movedir = ((mthing->angle & 255)*360)/256; // Pitch
13203 pitchmatrix = RotateXMatrix(FixedAngle(hoopcenter->movedir << FRACBITS));
13204 hoopcenter->movecount = (((UINT16)mthing->angle >> 8)*360)/256; // Yaw
13205 yawmatrix = RotateZMatrix(FixedAngle(hoopcenter->movecount << FRACBITS));
13206
13207 // For the hoop when it flies away
13208 hoopcenter->extravalue1 = hoopsize;
13209 hoopcenter->extravalue2 = radius/12;
13210
13211 // Create the hoop!
13212 for (i = 0; i < hoopsize; i++)
13213 {
13214 fa = i*(FINEANGLES/hoopsize);
13215 v[0] = FixedMul(FINECOSINE(fa), radius);
13216 v[1] = 0;
13217 v[2] = FixedMul(FINESINE(fa), radius);
13218 v[3] = FRACUNIT;
13219
13220 res = VectorMatrixMultiply(v, *pitchmatrix);
13221 M_Memcpy(&v, res, sizeof(v));
13222 res = VectorMatrixMultiply(v, *yawmatrix);
13223 M_Memcpy(&v, res, sizeof(v));
13224
13225 mobj = P_SpawnMobj(x + v[0], y + v[1], z + v[2], MT_HOOP);
13226 mobj->z -= mobj->height/2;
13227
13228 if (maptol & TOL_XMAS)
13229 P_SetMobjState(mobj, mobj->info->seestate + (i & 1));
13230
13231 P_SetTarget(&mobj->target, hoopcenter); // Link the sprite to the center.
13232 mobj->fuse = 0;
13233
13234 // Link all the sprites in the hoop together
13235 if (nextmobj)
13236 {
13237 P_SetTarget(&mobj->hprev, nextmobj);
13238 P_SetTarget(&mobj->hprev->hnext, mobj);
13239 }
13240 else
13241 P_SetTarget(&mobj->hprev, P_SetTarget(&mobj->hnext, NULL));
13242
13243 nextmobj = mobj;
13244 }
13245
13246 // Create the collision detectors!
13247 // Create them until the size is less than 8
13248 // But always create at least ONE set of collision detectors
13249 do
13250 {
13251 if (hoopsize >= 32)
13252 hoopsize -= 16;
13253 else
13254 hoopsize /= 2;
13255
13256 radius = hoopsize*sizefactor;
13257
13258 for (i = 0; i < hoopsize; i++)
13259 {
13260 fa = i*(FINEANGLES/hoopsize);
13261 v[0] = FixedMul(FINECOSINE(fa), radius);
13262 v[1] = 0;
13263 v[2] = FixedMul(FINESINE(fa), radius);
13264 v[3] = FRACUNIT;
13265
13266 res = VectorMatrixMultiply(v, *pitchmatrix);
13267 M_Memcpy(&v, res, sizeof(v));
13268 res = VectorMatrixMultiply(v, *yawmatrix);
13269 M_Memcpy(&v, res, sizeof(v));
13270
13271 mobj = P_SpawnMobj(x + v[0], y + v[1], z + v[2], MT_HOOPCOLLIDE);
13272 mobj->z -= mobj->height/2;
13273
13274 // Link all the collision sprites together.
13275 P_SetTarget(&mobj->hnext, NULL);
13276 P_SetTarget(&mobj->hprev, nextmobj);
13277 P_SetTarget(&mobj->hprev->hnext, mobj);
13278
13279 nextmobj = mobj;
13280 }
13281 } while (hoopsize >= 8);
13282 }
13283
P_SpawnHoop(mapthing_t * mthing)13284 void P_SpawnHoop(mapthing_t *mthing)
13285 {
13286 if (metalrecording)
13287 return;
13288
13289 if (mthing->type == 1705) // Generic hoop
13290 P_SpawnHoopInternal(mthing, 24, 4*FRACUNIT);
13291 else // Customizable hoop
13292 // For each flag add 16 fracunits to the size
13293 // Default (0 flags) is 32 fracunits
13294 P_SpawnHoopInternal(mthing, 8 + (4*(mthing->options & 0xF)), 4*FRACUNIT);
13295 }
13296
P_SetBonusTime(mobj_t * mobj)13297 void P_SetBonusTime(mobj_t *mobj)
13298 {
13299 if (!mobj)
13300 return;
13301
13302 if (mobj->type != MT_BLUESPHERE && mobj->type != MT_NIGHTSCHIP)
13303 return;
13304
13305 P_SetMobjState(mobj, mobj->info->raisestate);
13306 }
13307
P_SpawnItemRow(mapthing_t * mthing,mobjtype_t * itemtypes,UINT8 numitemtypes,INT32 numitems,fixed_t horizontalspacing,fixed_t verticalspacing,INT16 fixedangle,boolean bonustime)13308 static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t* itemtypes, UINT8 numitemtypes, INT32 numitems, fixed_t horizontalspacing, fixed_t verticalspacing, INT16 fixedangle, boolean bonustime)
13309 {
13310 mapthing_t dummything;
13311 mobj_t *mobj = NULL;
13312 fixed_t x = mthing->x << FRACBITS;
13313 fixed_t y = mthing->y << FRACBITS;
13314 fixed_t z = mthing->z << FRACBITS;
13315 INT32 r;
13316 angle_t angle = FixedAngle(fixedangle << FRACBITS);
13317 angle_t fineangle = (angle >> ANGLETOFINESHIFT) & FINEMASK;
13318
13319 for (r = 0; r < numitemtypes; r++)
13320 {
13321 dummything = *mthing;
13322 dummything.type = mobjinfo[itemtypes[r]].doomednum;
13323 // Skip all returning/substitution code in objectplace.
13324 if (!objectplacing)
13325 {
13326 if (!P_AllowMobjSpawn(&dummything, itemtypes[r]))
13327 {
13328 itemtypes[r] = MT_NULL;
13329 continue;
13330 }
13331
13332 itemtypes[r] = P_GetMobjtypeSubstitute(&dummything, itemtypes[r]);
13333 }
13334 }
13335 z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, mthing->options & MTF_OBJECTFLIP, mthing->scale);
13336
13337 for (r = 0; r < numitems; r++)
13338 {
13339 mobjtype_t itemtype = itemtypes[r % numitemtypes];
13340 if (itemtype == MT_NULL)
13341 continue;
13342 dummything.type = mobjinfo[itemtype].doomednum;
13343
13344 x += FixedMul(horizontalspacing, FINECOSINE(fineangle));
13345 y += FixedMul(horizontalspacing, FINESINE(fineangle));
13346 z += (mthing->options & MTF_OBJECTFLIP) ? -verticalspacing : verticalspacing;
13347
13348 mobj = P_SpawnMobjFromMapThing(&dummything, x, y, z, itemtype);
13349
13350 if (!mobj)
13351 continue;
13352
13353 mobj->spawnpoint = NULL;
13354 if (bonustime)
13355 P_SetBonusTime(mobj);
13356 }
13357 }
13358
P_SpawnSingularItemRow(mapthing_t * mthing,mobjtype_t itemtype,INT32 numitems,fixed_t horizontalspacing,fixed_t verticalspacing,INT16 fixedangle,boolean bonustime)13359 static void P_SpawnSingularItemRow(mapthing_t* mthing, mobjtype_t itemtype, INT32 numitems, fixed_t horizontalspacing, fixed_t verticalspacing, INT16 fixedangle, boolean bonustime)
13360 {
13361 mobjtype_t itemtypes[1] = { itemtype };
13362 return P_SpawnItemRow(mthing, itemtypes, 1, numitems, horizontalspacing, verticalspacing, fixedangle, bonustime);
13363 }
13364
P_SpawnItemCircle(mapthing_t * mthing,mobjtype_t * itemtypes,UINT8 numitemtypes,INT32 numitems,fixed_t size,boolean bonustime)13365 static void P_SpawnItemCircle(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numitemtypes, INT32 numitems, fixed_t size, boolean bonustime)
13366 {
13367 mapthing_t dummything;
13368 mobj_t* mobj = NULL;
13369 fixed_t x = mthing->x << FRACBITS;
13370 fixed_t y = mthing->y << FRACBITS;
13371 fixed_t z = mthing->z << FRACBITS;
13372 angle_t angle = FixedAngle(mthing->angle << FRACBITS);
13373 angle_t fa;
13374 INT32 i;
13375 TVector v, *res;
13376
13377 for (i = 0; i < numitemtypes; i++)
13378 {
13379 dummything = *mthing;
13380 dummything.type = mobjinfo[itemtypes[i]].doomednum;
13381 // Skip all returning/substitution code in objectplace.
13382 if (!objectplacing)
13383 {
13384 if (!P_AllowMobjSpawn(&dummything, itemtypes[i]))
13385 {
13386 itemtypes[i] = MT_NULL;
13387 continue;
13388 }
13389
13390 itemtypes[i] = P_GetMobjtypeSubstitute(&dummything, itemtypes[i]);
13391 }
13392 }
13393 z = P_GetMobjSpawnHeight(itemtypes[0], x, y, z, 0, false, mthing->scale);
13394
13395 for (i = 0; i < numitems; i++)
13396 {
13397 mobjtype_t itemtype = itemtypes[i % numitemtypes];
13398 if (itemtype == MT_NULL)
13399 continue;
13400 dummything.type = mobjinfo[itemtype].doomednum;
13401
13402 fa = i*FINEANGLES/numitems;
13403 v[0] = FixedMul(FINECOSINE(fa), size);
13404 v[1] = 0;
13405 v[2] = FixedMul(FINESINE(fa), size);
13406 v[3] = FRACUNIT;
13407
13408 res = VectorMatrixMultiply(v, *RotateZMatrix(angle));
13409 M_Memcpy(&v, res, sizeof(v));
13410
13411 mobj = P_SpawnMobjFromMapThing(&dummything, x + v[0], y + v[1], z + v[2], itemtype);
13412
13413 if (!mobj)
13414 continue;
13415
13416 mobj->z -= mobj->height/2;
13417 mobj->spawnpoint = NULL;
13418 if (bonustime)
13419 P_SetBonusTime(mobj);
13420 }
13421 }
13422
P_SpawnItemPattern(mapthing_t * mthing,boolean bonustime)13423 void P_SpawnItemPattern(mapthing_t *mthing, boolean bonustime)
13424 {
13425 switch (mthing->type)
13426 {
13427 // Special placement patterns
13428 case 600: // 5 vertical rings (yellow spring)
13429 P_SpawnSingularItemRow(mthing, MT_RING, 5, 0, 64*FRACUNIT, 0, bonustime);
13430 return;
13431 case 601: // 5 vertical rings (red spring)
13432 P_SpawnSingularItemRow(mthing, MT_RING, 5, 0, 128*FRACUNIT, 0, bonustime);
13433 return;
13434 case 602: // 5 diagonal rings (yellow spring)
13435 P_SpawnSingularItemRow(mthing, MT_RING, 5, 64*FRACUNIT, 64*FRACUNIT, mthing->angle, bonustime);
13436 return;
13437 case 603: // 10 diagonal rings (red spring)
13438 P_SpawnSingularItemRow(mthing, MT_RING, 10, 64*FRACUNIT, 64*FRACUNIT, mthing->angle, bonustime);
13439 return;
13440 case 604: // Circle of rings (8 items)
13441 case 605: // Circle of rings (16 items)
13442 case 606: // Circle of blue spheres (8 items)
13443 case 607: // Circle of blue spheres (16 items)
13444 {
13445 INT32 numitems = (mthing->type & 1) ? 16 : 8;
13446 fixed_t size = (mthing->type & 1) ? 192*FRACUNIT : 96*FRACUNIT;
13447 mobjtype_t itemtypes[1] = { (mthing->type < 606) ? MT_RING : MT_BLUESPHERE };
13448 P_SpawnItemCircle(mthing, itemtypes, 1, numitems, size, bonustime);
13449 return;
13450 }
13451 case 608: // Circle of rings and blue spheres (8 items)
13452 case 609: // Circle of rings and blue spheres (16 items)
13453 {
13454 INT32 numitems = (mthing->type & 1) ? 16 : 8;
13455 fixed_t size = (mthing->type & 1) ? 192*FRACUNIT : 96*FRACUNIT;
13456 mobjtype_t itemtypes[2] = { MT_RING, MT_BLUESPHERE };
13457 P_SpawnItemCircle(mthing, itemtypes, 2, numitems, size, bonustime);
13458 return;
13459 }
13460 default:
13461 return;
13462 }
13463 }
13464
13465 //
13466 // P_CheckMissileSpawn
13467 // Moves the missile forward a bit and possibly explodes it right there.
13468 //
P_CheckMissileSpawn(mobj_t * th)13469 boolean P_CheckMissileSpawn(mobj_t *th)
13470 {
13471 // move a little forward so an angle can be computed if it immediately explodes
13472 if (!(th->flags & MF_GRENADEBOUNCE)) // hack: bad! should be a flag.
13473 {
13474 th->x += th->momx>>1;
13475 th->y += th->momy>>1;
13476 th->z += th->momz>>1;
13477 }
13478
13479 if (!P_TryMove(th, th->x, th->y, true))
13480 {
13481 P_ExplodeMissile(th);
13482 return false;
13483 }
13484 return true;
13485 }
13486
13487 //
13488 // P_SpawnXYZMissile
13489 //
13490 // Spawns missile at specific coords
13491 //
P_SpawnXYZMissile(mobj_t * source,mobj_t * dest,mobjtype_t type,fixed_t x,fixed_t y,fixed_t z)13492 mobj_t *P_SpawnXYZMissile(mobj_t *source, mobj_t *dest, mobjtype_t type,
13493 fixed_t x, fixed_t y, fixed_t z)
13494 {
13495 mobj_t *th;
13496 angle_t an;
13497 INT32 dist;
13498 fixed_t speed;
13499
13500 I_Assert(source != NULL);
13501 I_Assert(dest != NULL);
13502
13503 if (source->eflags & MFE_VERTICALFLIP)
13504 z -= FixedMul(mobjinfo[type].height, source->scale);
13505
13506 th = P_SpawnMobj(x, y, z, type);
13507
13508 if (source->eflags & MFE_VERTICALFLIP)
13509 th->flags2 |= MF2_OBJECTFLIP;
13510
13511 th->destscale = source->scale;
13512 P_SetScale(th, source->scale);
13513
13514 speed = FixedMul(th->info->speed, th->scale);
13515
13516 if (speed == 0)
13517 {
13518 CONS_Debug(DBG_GAMELOGIC, "P_SpawnXYZMissile - projectile has 0 speed! (mobj type %d)\n", type);
13519 speed = mobjinfo[MT_ROCKET].speed;
13520 }
13521
13522 if (th->info->seesound)
13523 S_StartSound(th, th->info->seesound);
13524
13525 P_SetTarget(&th->target, source); // where it came from
13526 an = R_PointToAngle2(x, y, dest->x, dest->y);
13527
13528 th->angle = an;
13529 an >>= ANGLETOFINESHIFT;
13530 th->momx = FixedMul(speed, FINECOSINE(an));
13531 th->momy = FixedMul(speed, FINESINE(an));
13532
13533 dist = P_AproxDistance(dest->x - x, dest->y - y);
13534 dist = dist / speed;
13535
13536 if (dist < 1)
13537 dist = 1;
13538
13539 th->momz = (dest->z - z) / dist;
13540
13541 if (th->flags & MF_MISSILE)
13542 dist = P_CheckMissileSpawn(th);
13543 else
13544 dist = 1;
13545
13546 return dist ? th : NULL;
13547 }
13548
13549 //
13550 // P_SpawnAlteredDirectionMissile
13551 //
13552 // Spawns a missile with same source as spawning missile yet an altered direction
13553 //
P_SpawnAlteredDirectionMissile(mobj_t * source,mobjtype_t type,fixed_t x,fixed_t y,fixed_t z,INT32 shiftingAngle)13554 mobj_t *P_SpawnAlteredDirectionMissile(mobj_t *source, mobjtype_t type, fixed_t x, fixed_t y, fixed_t z, INT32 shiftingAngle)
13555 {
13556 mobj_t *th;
13557 angle_t an;
13558 fixed_t dist, speed;
13559
13560 I_Assert(source != NULL);
13561
13562 if (!(source->target) || !(source->flags & MF_MISSILE))
13563 return NULL;
13564
13565 if (source->eflags & MFE_VERTICALFLIP)
13566 z -= FixedMul(mobjinfo[type].height, source->scale);
13567
13568 th = P_SpawnMobj(x, y, z, type);
13569
13570 if (source->eflags & MFE_VERTICALFLIP)
13571 th->flags2 |= MF2_OBJECTFLIP;
13572
13573 th->destscale = source->scale;
13574 P_SetScale(th, source->scale);
13575
13576 speed = FixedMul(th->info->speed, th->scale);
13577
13578 if (speed == 0) // Backwards compatibility with 1.09.2
13579 {
13580 CONS_Printf("P_SpawnAlteredDirectionMissile - projectile has 0 speed! (mobj type %d)\nPlease update this SOC.", type);
13581 speed = mobjinfo[MT_ROCKET].speed;
13582 }
13583
13584 if (th->info->seesound)
13585 S_StartSound(th, th->info->seesound);
13586
13587 P_SetTarget(&th->target, source->target); // where it came from
13588 an = R_PointToAngle2(0, 0, source->momx, source->momy) + (ANG1*shiftingAngle);
13589
13590 th->angle = an;
13591 an >>= ANGLETOFINESHIFT;
13592 th->momx = FixedMul(speed, FINECOSINE(an));
13593 th->momy = FixedMul(speed, FINESINE(an));
13594
13595 dist = P_AproxDistance(source->momx*800, source->momy*800);
13596 dist = dist / speed;
13597
13598 if (dist < 1)
13599 dist = 1;
13600
13601 th->momz = (source->momz*800) / dist;
13602
13603 if (th->flags & MF_MISSILE)
13604 {
13605 dist = P_CheckMissileSpawn(th);
13606 th->x -= th->momx>>1;
13607 th->y -= th->momy>>1;
13608 th->z -= th->momz>>1;
13609 }
13610 else
13611 dist = 1;
13612
13613 return dist ? th : NULL;
13614 }
13615
13616 //
13617 // P_SpawnPointMissile
13618 //
13619 // Spawns a missile at specific coords
13620 //
P_SpawnPointMissile(mobj_t * source,fixed_t xa,fixed_t ya,fixed_t za,mobjtype_t type,fixed_t x,fixed_t y,fixed_t z)13621 mobj_t *P_SpawnPointMissile(mobj_t *source, fixed_t xa, fixed_t ya, fixed_t za, mobjtype_t type,
13622 fixed_t x, fixed_t y, fixed_t z)
13623 {
13624 mobj_t *th;
13625 angle_t an;
13626 fixed_t dist, speed;
13627
13628 I_Assert(source != NULL);
13629
13630 if (source->eflags & MFE_VERTICALFLIP)
13631 z -= FixedMul(mobjinfo[type].height, source->scale);
13632
13633 th = P_SpawnMobj(x, y, z, type);
13634
13635 if (source->eflags & MFE_VERTICALFLIP)
13636 th->flags2 |= MF2_OBJECTFLIP;
13637
13638 th->destscale = source->scale;
13639 P_SetScale(th, source->scale);
13640
13641 speed = FixedMul(th->info->speed, th->scale);
13642
13643 if (speed == 0) // Backwards compatibility with 1.09.2
13644 {
13645 CONS_Printf("P_SpawnPointMissile - projectile has 0 speed! (mobj type %d)\nPlease update this SOC.", type);
13646 speed = mobjinfo[MT_ROCKET].speed;
13647 }
13648
13649 if (th->info->seesound)
13650 S_StartSound(th, th->info->seesound);
13651
13652 P_SetTarget(&th->target, source); // where it came from
13653 an = R_PointToAngle2(x, y, xa, ya);
13654
13655 th->angle = an;
13656 an >>= ANGLETOFINESHIFT;
13657 th->momx = FixedMul(speed, FINECOSINE(an));
13658 th->momy = FixedMul(speed, FINESINE(an));
13659
13660 dist = P_AproxDistance(xa - x, ya - y);
13661 dist = dist / speed;
13662
13663 if (dist < 1)
13664 dist = 1;
13665
13666 th->momz = (za - z) / dist;
13667
13668 if (th->flags & MF_MISSILE)
13669 dist = P_CheckMissileSpawn(th);
13670 else
13671 dist = 1;
13672
13673 return dist ? th : NULL;
13674 }
13675
13676 //
13677 // P_SpawnMissile
13678 //
P_SpawnMissile(mobj_t * source,mobj_t * dest,mobjtype_t type)13679 mobj_t *P_SpawnMissile(mobj_t *source, mobj_t *dest, mobjtype_t type)
13680 {
13681 mobj_t *th;
13682 angle_t an;
13683 INT32 dist;
13684 fixed_t z;
13685 const fixed_t gsf = (fixed_t)6;
13686 fixed_t speed;
13687
13688 I_Assert(source != NULL);
13689 I_Assert(dest != NULL);
13690 if (source->type == MT_JETTGUNNER)
13691 {
13692 if (source->eflags & MFE_VERTICALFLIP)
13693 z = source->z + source->height - FixedMul(4*FRACUNIT, source->scale);
13694 else
13695 z = source->z + FixedMul(4*FRACUNIT, source->scale);
13696 }
13697 else
13698 z = source->z + source->height/2;
13699
13700 if (source->eflags & MFE_VERTICALFLIP)
13701 z -= FixedMul(mobjinfo[type].height, source->scale);
13702
13703 th = P_SpawnMobj(source->x, source->y, z, type);
13704
13705 if (source->eflags & MFE_VERTICALFLIP)
13706 th->flags2 |= MF2_OBJECTFLIP;
13707
13708 th->destscale = source->scale;
13709 P_SetScale(th, source->scale);
13710
13711 if (source->type == MT_METALSONIC_BATTLE && source->health < 4)
13712 speed = FixedMul(FixedMul(th->info->speed, 3*FRACUNIT/2), th->scale);
13713 else
13714 speed = FixedMul(th->info->speed, th->scale);
13715
13716 if (speed == 0)
13717 {
13718 CONS_Debug(DBG_GAMELOGIC, "P_SpawnMissile - projectile has 0 speed! (mobj type %d)\n", type);
13719 speed = FixedMul(mobjinfo[MT_TURRETLASER].speed, th->scale);
13720 }
13721
13722 if (th->info->seesound)
13723 S_StartSound(source, th->info->seesound);
13724
13725 P_SetTarget(&th->target, source); // where it came from
13726
13727 if (type == MT_TURRETLASER || type == MT_ENERGYBALL) // More accurate!
13728 an = R_PointToAngle2(source->x, source->y,
13729 dest->x + (dest->momx*gsf),
13730 dest->y + (dest->momy*gsf));
13731 else
13732 an = R_PointToAngle2(source->x, source->y, dest->x, dest->y);
13733
13734 th->angle = an;
13735 an >>= ANGLETOFINESHIFT;
13736 th->momx = FixedMul(speed, FINECOSINE(an));
13737 th->momy = FixedMul(speed, FINESINE(an));
13738
13739 if (type == MT_TURRETLASER || type == MT_ENERGYBALL) // More accurate!
13740 dist = P_AproxDistance(dest->x+(dest->momx*gsf) - source->x, dest->y+(dest->momy*gsf) - source->y);
13741 else
13742 dist = P_AproxDistance(dest->x - source->x, dest->y - source->y);
13743
13744 dist = dist / speed;
13745
13746 if (dist < 1)
13747 dist = 1;
13748
13749 if (type == MT_TURRETLASER || type == MT_ENERGYBALL) // More accurate!
13750 th->momz = (dest->z + (dest->momz*gsf) - z) / dist;
13751 else
13752 th->momz = (dest->z - z) / dist;
13753
13754 if (th->flags & MF_MISSILE)
13755 dist = P_CheckMissileSpawn(th);
13756 else
13757 dist = 1;
13758 return dist ? th : NULL;
13759 }
13760
13761 //
13762 // P_ColorTeamMissile
13763 // Colors a player's ring based on their team
13764 //
P_ColorTeamMissile(mobj_t * missile,player_t * source)13765 void P_ColorTeamMissile(mobj_t *missile, player_t *source)
13766 {
13767 if (G_GametypeHasTeams())
13768 {
13769 if (source->ctfteam == 2)
13770 missile->color = skincolor_bluering;
13771 else if (source->ctfteam == 1)
13772 missile->color = skincolor_redring;
13773 }
13774 /*
13775 else
13776 missile->color = player->mo->color; //copy color
13777 */
13778 }
13779
13780 //
13781 // P_SPMAngle
13782 // Fires missile at angle from what is presumably a player
13783 //
P_SPMAngle(mobj_t * source,mobjtype_t type,angle_t angle,UINT8 allowaim,UINT32 flags2)13784 mobj_t *P_SPMAngle(mobj_t *source, mobjtype_t type, angle_t angle, UINT8 allowaim, UINT32 flags2)
13785 {
13786 mobj_t *th;
13787 angle_t an;
13788 fixed_t x, y, z, slope = 0, speed;
13789
13790 // angle at which you fire, is player angle
13791 an = angle;
13792
13793 if (allowaim) // aiming allowed?
13794 slope = AIMINGTOSLOPE(source->player->aiming);
13795
13796 x = source->x;
13797 y = source->y;
13798
13799 if (source->eflags & MFE_VERTICALFLIP)
13800 z = source->z + 2*source->height/3 - FixedMul(mobjinfo[type].height, source->scale);
13801 else
13802 z = source->z + source->height/3;
13803
13804 th = P_SpawnMobj(x, y, z, type);
13805
13806 if (source->eflags & MFE_VERTICALFLIP)
13807 th->flags2 |= MF2_OBJECTFLIP;
13808
13809 th->destscale = source->scale;
13810 P_SetScale(th, source->scale);
13811
13812 th->flags2 |= flags2;
13813
13814 // The rail ring has no unique thrown object, so we must do this.
13815 if (th->info->seesound && !(th->flags2 & MF2_RAILRING))
13816 S_StartSound(source, th->info->seesound);
13817
13818 P_SetTarget(&th->target, source);
13819
13820 speed = th->info->speed;
13821 if (source->player && source->player->charability == CA_FLY)
13822 speed = FixedMul(speed, 3*FRACUNIT/2);
13823
13824 th->angle = an;
13825 th->momx = FixedMul(speed, FINECOSINE(an>>ANGLETOFINESHIFT));
13826 th->momy = FixedMul(speed, FINESINE(an>>ANGLETOFINESHIFT));
13827
13828 if (allowaim)
13829 {
13830 th->momx = FixedMul(th->momx,FINECOSINE(source->player->aiming>>ANGLETOFINESHIFT));
13831 th->momy = FixedMul(th->momy,FINECOSINE(source->player->aiming>>ANGLETOFINESHIFT));
13832 }
13833
13834 th->momz = FixedMul(speed, slope);
13835
13836 //scaling done here so it doesn't clutter up the code above
13837 th->momx = FixedMul(th->momx, th->scale);
13838 th->momy = FixedMul(th->momy, th->scale);
13839 th->momz = FixedMul(th->momz, th->scale);
13840
13841 slope = P_CheckMissileSpawn(th);
13842
13843 return slope ? th : NULL;
13844 }
13845
13846 //
13847 // P_FlashPal
13848 // Flashes a player's palette. ARMAGEDDON BLASTS!
13849 //
P_FlashPal(player_t * pl,UINT16 type,UINT16 duration)13850 void P_FlashPal(player_t *pl, UINT16 type, UINT16 duration)
13851 {
13852 if (!pl)
13853 return;
13854 pl->flashcount = duration;
13855 pl->flashpal = type;
13856 }
13857
13858 //
13859 // P_SpawnMobjFromMobj
13860 // Spawns an object with offsets relative to the position of another object.
13861 // Scale, gravity flip, etc. is taken into account automatically.
13862 //
P_SpawnMobjFromMobj(mobj_t * mobj,fixed_t xofs,fixed_t yofs,fixed_t zofs,mobjtype_t type)13863 mobj_t *P_SpawnMobjFromMobj(mobj_t *mobj, fixed_t xofs, fixed_t yofs, fixed_t zofs, mobjtype_t type)
13864 {
13865 mobj_t *newmobj;
13866
13867 xofs = FixedMul(xofs, mobj->scale);
13868 yofs = FixedMul(yofs, mobj->scale);
13869 zofs = FixedMul(zofs, mobj->scale);
13870
13871 newmobj = P_SpawnMobj(mobj->x + xofs, mobj->y + yofs, mobj->z + zofs, type);
13872 if (!newmobj)
13873 return NULL;
13874
13875 if (mobj->eflags & MFE_VERTICALFLIP)
13876 {
13877 fixed_t elementheight = FixedMul(newmobj->info->height, mobj->scale);
13878
13879 newmobj->eflags |= MFE_VERTICALFLIP;
13880 newmobj->flags2 |= MF2_OBJECTFLIP;
13881 newmobj->z = mobj->z + mobj->height - zofs - elementheight;
13882 }
13883
13884 newmobj->destscale = mobj->destscale;
13885 P_SetScale(newmobj, mobj->scale);
13886 return newmobj;
13887 }
13888