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 = &sectors[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 = &sectors[snum];
4701 
4702 		// Destroy the FOFs.
4703 		for (a = 0; a < sector->numattached; a++)
4704 		{
4705 			rsec = &sectors[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