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_inter.c
12 /// \brief Handling interactions (i.e., collisions)
13 
14 #include "doomdef.h"
15 #include "i_system.h"
16 #include "am_map.h"
17 #include "g_game.h"
18 #include "m_random.h"
19 #include "p_local.h"
20 #include "s_sound.h"
21 #include "r_main.h"
22 #include "st_stuff.h"
23 #include "hu_stuff.h"
24 #include "lua_hook.h"
25 #include "m_cond.h" // unlockables, emblems, etc
26 #include "p_setup.h"
27 #include "m_cheat.h" // objectplace
28 #include "m_misc.h"
29 #include "v_video.h" // video flags for CEchos
30 #include "f_finale.h"
31 
32 // CTF player names
33 #define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : ""
34 #define CTFTEAMENDCODE(pl) pl->ctfteam ? "\x80" : ""
35 
P_ForceFeed(const player_t * player,INT32 attack,INT32 fade,tic_t duration,INT32 period)36 void P_ForceFeed(const player_t *player, INT32 attack, INT32 fade, tic_t duration, INT32 period)
37 {
38 	BasicFF_t Basicfeed;
39 	if (!player)
40 		return;
41 	Basicfeed.Duration = (UINT32)(duration * (100L/TICRATE));
42 	Basicfeed.ForceX = Basicfeed.ForceY = 1;
43 	Basicfeed.Gain = 25000;
44 	Basicfeed.Magnitude = period*10;
45 	Basicfeed.player = player;
46 	/// \todo test FFB
47 	P_RampConstant(&Basicfeed, attack, fade);
48 }
49 
P_ForceConstant(const BasicFF_t * FFInfo)50 void P_ForceConstant(const BasicFF_t *FFInfo)
51 {
52 	JoyFF_t ConstantQuake;
53 	if (!FFInfo || !FFInfo->player)
54 		return;
55 	ConstantQuake.ForceX    = FFInfo->ForceX;
56 	ConstantQuake.ForceY    = FFInfo->ForceY;
57 	ConstantQuake.Duration  = FFInfo->Duration;
58 	ConstantQuake.Gain      = FFInfo->Gain;
59 	ConstantQuake.Magnitude = FFInfo->Magnitude;
60 	if (FFInfo->player == &players[consoleplayer])
61 		I_Tactile(ConstantForce, &ConstantQuake);
62 	else if (splitscreen && FFInfo->player == &players[secondarydisplayplayer])
63 		I_Tactile2(ConstantForce, &ConstantQuake);
64 }
P_RampConstant(const BasicFF_t * FFInfo,INT32 Start,INT32 End)65 void P_RampConstant(const BasicFF_t *FFInfo, INT32 Start, INT32 End)
66 {
67 	JoyFF_t RampQuake;
68 	if (!FFInfo || !FFInfo->player)
69 		return;
70 	RampQuake.ForceX    = FFInfo->ForceX;
71 	RampQuake.ForceY    = FFInfo->ForceY;
72 	RampQuake.Duration  = FFInfo->Duration;
73 	RampQuake.Gain      = FFInfo->Gain;
74 	RampQuake.Magnitude = FFInfo->Magnitude;
75 	RampQuake.Start     = Start;
76 	RampQuake.End       = End;
77 	if (FFInfo->player == &players[consoleplayer])
78 		I_Tactile(ConstantForce, &RampQuake);
79 	else if (splitscreen && FFInfo->player == &players[secondarydisplayplayer])
80 		I_Tactile2(ConstantForce, &RampQuake);
81 }
82 
83 
84 //
85 // GET STUFF
86 //
87 
88 /** Makes sure all previous starposts are cleared.
89   * For instance, hitting starpost 5 will clear starposts 1 through 4, even if
90   * you didn't touch them. This is how the classic games work, although it can
91   * lead to bizarre situations on levels that allow you to make a circuit.
92   *
93   * \param postnum The number of the starpost just touched.
94   */
P_ClearStarPost(INT32 postnum)95 void P_ClearStarPost(INT32 postnum)
96 {
97 	thinker_t *th;
98 	mobj_t *mo2;
99 
100 	// scan the thinkers
101 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
102 	{
103 		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
104 			continue;
105 
106 		mo2 = (mobj_t *)th;
107 
108 		if (mo2->type != MT_STARPOST)
109 			continue;
110 
111 		if (mo2->health > postnum)
112 			continue;
113 
114 		P_SetMobjState(mo2, mo2->info->seestate);
115 	}
116 	return;
117 }
118 
119 //
120 // P_ResetStarposts
121 //
122 // Resets all starposts back to their spawn state, used on A_Mixup and some other things.
123 //
P_ResetStarposts(void)124 void P_ResetStarposts(void)
125 {
126 	// Search through all the thinkers.
127 	thinker_t *th;
128 	mobj_t *post;
129 
130 	for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
131 	{
132 		if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
133 			continue;
134 
135 		post = (mobj_t *)th;
136 
137 		if (post->type != MT_STARPOST)
138 			continue;
139 
140 		P_SetMobjState(post, post->info->spawnstate);
141 	}
142 }
143 
144 //
145 // P_CanPickupItem
146 //
147 // Returns true if the player is in a state where they can pick up items.
148 //
P_CanPickupItem(player_t * player,boolean weapon)149 boolean P_CanPickupItem(player_t *player, boolean weapon)
150 {
151 	if (!player->mo || player->mo->health <= 0)
152 		return false;
153 
154 	if (player->bot)
155 	{
156 		if (weapon)
157 			return false;
158 		return P_CanPickupItem(&players[consoleplayer], true); // weapon is true to prevent infinite recursion if p1 is bot - doesn't occur in vanilla, but may be relevant for mods
159 	}
160 
161 	if (player->powers[pw_flashing] > (flashingtics/4)*3 && player->powers[pw_flashing] < UINT16_MAX)
162 		return false;
163 
164 	return true;
165 }
166 
167 //
168 // P_DoNightsScore
169 //
170 // When you pick up some items in nights, it displays
171 // a score sign, and awards you some drill time.
172 //
P_DoNightsScore(player_t * player)173 void P_DoNightsScore(player_t *player)
174 {
175 	mobj_t *dummymo;
176 
177 	if (player->exiting)
178 		return; // Don't do any fancy shit for failures.
179 
180 	dummymo = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z+player->mo->height/2, MT_NIGHTSCORE);
181 	if (player->bot)
182 		player = &players[consoleplayer];
183 
184 	if (G_IsSpecialStage(gamemap)) // Global link count? Maybe not a good idea...
185 	{
186 		INT32 i;
187 		for (i = 0; i < MAXPLAYERS; i++)
188 			if (playeringame[i])
189 			{
190 				if (++players[i].linkcount > players[i].maxlink)
191 					players[i].maxlink = players[i].linkcount;
192 				players[i].linktimer = nightslinktics;
193 			}
194 	}
195 	else // Individual link counts
196 	{
197 		if (++player->linkcount > player->maxlink)
198 			player->maxlink = player->linkcount;
199 		player->linktimer = nightslinktics;
200 	}
201 
202 	if (player->linkcount < 10)
203 	{
204 		if (player->bonustime)
205 		{
206 			P_AddPlayerScore(player, player->linkcount*20);
207 			P_SetMobjState(dummymo, dummymo->info->xdeathstate+player->linkcount-1);
208 		}
209 		else
210 		{
211 			P_AddPlayerScore(player, player->linkcount*10);
212 			P_SetMobjState(dummymo, dummymo->info->spawnstate+player->linkcount-1);
213 		}
214 	}
215 	else
216 	{
217 		if (player->bonustime)
218 		{
219 			P_AddPlayerScore(player, 200);
220 			P_SetMobjState(dummymo, dummymo->info->xdeathstate+9);
221 		}
222 		else
223 		{
224 			P_AddPlayerScore(player, 100);
225 			P_SetMobjState(dummymo, dummymo->info->spawnstate+9);
226 		}
227 	}
228 
229 	// Hoops are the only things that should add to your drill meter
230 	//player->drillmeter += TICRATE;
231 	dummymo->momz = FRACUNIT;
232 	dummymo->fuse = 3*TICRATE;
233 
234 	// What?! NO, don't use the camera! Scale up instead!
235 	//P_InstaThrust(dummymo, R_PointToAngle2(dummymo->x, dummymo->y, camera.x, camera.y), 3*FRACUNIT);
236 	dummymo->scalespeed = FRACUNIT/25;
237 	dummymo->destscale = 2*FRACUNIT;
238 }
239 
240 //
241 // P_DoMatchSuper
242 //
243 // Checks if you have all 7 pw_emeralds, then turns you "super". =P
244 //
P_DoMatchSuper(player_t * player)245 void P_DoMatchSuper(player_t *player)
246 {
247 	UINT16 match_emeralds = player->powers[pw_emeralds];
248 	boolean doteams = false;
249 	int i;
250 
251 	// If this gametype has teams, check every player on your team for emeralds.
252 	if (G_GametypeHasTeams())
253 	{
254 		doteams = true;
255 		for (i = 0; i < MAXPLAYERS; i++)
256 			if (players[i].ctfteam == player->ctfteam)
257 				match_emeralds |= players[i].powers[pw_emeralds];
258 	}
259 
260 	if (!ALL7EMERALDS(match_emeralds))
261 		return;
262 
263 	// Got 'em all? Turn "super"!
264 	emeraldspawndelay = invulntics + 1;
265 	player->powers[pw_emeralds] = 0;
266 	player->powers[pw_invulnerability] = emeraldspawndelay;
267 	player->powers[pw_sneakers] = emeraldspawndelay;
268 	if (P_IsLocalPlayer(player) && !player->powers[pw_super])
269 	{
270 		S_StopMusic();
271 		if (mariomode)
272 			G_GhostAddColor(GHC_INVINCIBLE);
273 		strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14);
274 		S_StartCaption(sfx_None, -1, player->powers[pw_invulnerability]);
275 		S_ChangeMusicInternal((mariomode) ? "_minv" : "_inv", false);
276 	}
277 
278 	// Also steal 50 points from every enemy, sealing your victory.
279 	P_StealPlayerScore(player, 50);
280 
281 	// In a team game?
282 	// Check everyone else on your team for emeralds, and turn those helpful assisting players invincible too.
283 	if (doteams)
284 		for (i = 0; i < MAXPLAYERS; i++)
285 			if (playeringame[i] && players[i].ctfteam == player->ctfteam
286 			&& players[i].powers[pw_emeralds] != 0)
287 			{
288 				players[i].powers[pw_emeralds] = 0;
289 				player->powers[pw_invulnerability] = invulntics + 1;
290 				player->powers[pw_sneakers] = player->powers[pw_invulnerability];
291 				if (P_IsLocalPlayer(player) && !player->powers[pw_super])
292 				{
293 					S_StopMusic();
294 					if (mariomode)
295 						G_GhostAddColor(GHC_INVINCIBLE);
296 					strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14);
297 					S_StartCaption(sfx_None, -1, player->powers[pw_invulnerability]);
298 					S_ChangeMusicInternal((mariomode) ? "_minv" : "_inv", false);
299 				}
300 			}
301 }
302 
303 /** Takes action based on a ::MF_SPECIAL thing touched by a player.
304   * Actually, this just checks a few things (heights, toucher->player, no
305   * objectplace, no dead or disappearing things)
306   *
307   * The special thing may be collected and disappear, or a sound may play, or
308   * both.
309   *
310   * \param special     The special thing.
311   * \param toucher     The player's mobj.
312   * \param heightcheck Whether or not to make sure the player and the object
313   *                    are actually touching.
314   */
P_TouchSpecialThing(mobj_t * special,mobj_t * toucher,boolean heightcheck)315 void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
316 {
317 	player_t *player;
318 	INT32 i;
319 	UINT8 elementalpierce;
320 
321 	if (objectplacing)
322 		return;
323 
324 	I_Assert(special != NULL);
325 	I_Assert(toucher != NULL);
326 
327 	// Dead thing touching.
328 	// Can happen with a sliding player corpse.
329 	if (toucher->health <= 0)
330 		return;
331 	if (special->health <= 0)
332 		return;
333 
334 	if (heightcheck)
335 	{
336 		if (special->type == MT_FLINGEMERALD) // little hack here...
337 		{ // flingemerald sprites are low to the ground, so extend collision radius down some.
338 			if (toucher->z > (special->z + special->height))
339 				return;
340 			if (special->z - special->height > (toucher->z + toucher->height))
341 				return;
342 		}
343 		else
344 		{
345 			if (toucher->momz < 0) {
346 				if (toucher->z + toucher->momz > special->z + special->height)
347 					return;
348 			} else if (toucher->z > special->z + special->height)
349 				return;
350 			if (toucher->momz > 0) {
351 				if (toucher->z + toucher->height + toucher->momz < special->z)
352 					return;
353 			} else if (toucher->z + toucher->height < special->z)
354 				return;
355 		}
356 	}
357 
358 	player = toucher->player;
359 	I_Assert(player != NULL); // Only players can touch stuff!
360 
361 	if (player->spectator)
362 		return;
363 
364 	// Ignore multihits in "ouchie" mode
365 	if (special->flags & (MF_ENEMY|MF_BOSS) && special->flags2 & MF2_FRET)
366 		return;
367 
368 	if (LUAh_TouchSpecial(special, toucher) || P_MobjWasRemoved(special))
369 		return;
370 
371 	// 0 = none, 1 = elemental pierce, 2 = bubble bounce
372 	elementalpierce = (((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (player->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (player->pflags & PF_SHIELDABILITY)
373 	? (((player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL) ? 1 : 2)
374 	: 0);
375 
376 	if ((special->flags & (MF_ENEMY|MF_BOSS)) && !(special->flags & MF_MISSILE))
377 	{
378 		////////////////////////////////////////////////////////
379 		/////ENEMIES & BOSSES!!/////////////////////////////////
380 		////////////////////////////////////////////////////////
381 
382 		switch (special->type)
383 		{
384 			case MT_BLACKEGGMAN:
385 			{
386 				P_DamageMobj(toucher, special, special, 1, 0); // ouch
387 				return;
388 			}
389 			 case MT_BIGMINE:
390 			{
391 				special->momx = toucher->momx/3;
392 				special->momy = toucher->momy/3;
393 				special->momz = toucher->momz/3;
394 				toucher->momx /= -8;
395 				toucher->momy /= -8;
396 				toucher->momz /= -8;
397 				special->flags &= ~MF_SPECIAL;
398 				if (special->info->activesound)
399 					S_StartSound(special, special->info->activesound);
400 				P_SetTarget(&special->tracer, toucher);
401 				player->homing = 0;
402 				return;
403 			}
404 			case MT_GSNAPPER:
405 				if (!elementalpierce
406 				&& toucher->z < special->z + special->height
407 				&& toucher->z + toucher->height > special->z
408 				&& P_DamageMobj(toucher, special, special, 1, DMG_SPIKE))
409 					return; // Can only hit snapper from above
410 				break;
411 
412 			case MT_SPINCUSHION:
413 				if (P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0)
414 				{
415 					if (player->pflags & PF_BOUNCING)
416 					{
417 						toucher->momz = -toucher->momz;
418 						P_DoAbilityBounce(player, false);
419 						return;
420 					}
421 					else if (P_DamageMobj(toucher, special, special, 1, DMG_SPIKE))
422 						return; // Cannot hit sharp from above
423 				}
424 				break;
425 			case MT_FANG:
426 				if (!player->powers[pw_flashing]
427 				&& !(player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
428 				&& !(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
429 				{
430 					if ((special->state == &states[S_FANG_BOUNCE3]
431 					  || special->state == &states[S_FANG_BOUNCE4]
432 					  || special->state == &states[S_FANG_PINCHBOUNCE3]
433 					  || special->state == &states[S_FANG_PINCHBOUNCE4])
434 					&& P_MobjFlip(special)*((special->z + special->height/2) - (toucher->z + toucher->height/2)) > (toucher->height/2))
435 					{
436 						P_DamageMobj(toucher, special, special, 1, 0);
437 						P_SetTarget(&special->tracer, toucher);
438 
439 						if (special->state == &states[S_FANG_PINCHBOUNCE3]
440 						 || special->state == &states[S_FANG_PINCHBOUNCE4])
441 							P_SetMobjState(special, S_FANG_PINCHPATHINGSTART2);
442 						else
443 						{
444 							var1 = var2 = 4;
445 							A_Boss5ExtraRepeat(special);
446 							P_SetMobjState(special, S_FANG_PATHINGCONT2); //S_FANG_PATHINGCONT1 if you want him to drop a bomb on the player
447 						}
448 						if (special->eflags & MFE_VERTICALFLIP)
449 							special->z = toucher->z - special->height;
450 						else
451 							special->z = toucher->z + toucher->height;
452 						return;
453 					}
454 				}
455 				break;
456 			case MT_PYREFLY:
457 				if (special->extravalue2 == 2 && P_DamageMobj(player->mo, special, special, 1, DMG_FIRE))
458 					return;
459 			default:
460 				break;
461 		}
462 
463 		if (P_PlayerCanDamage(player, special)) // Do you possess the ability to subdue the object?
464 		{
465 			if (special->type == MT_PTERABYTE && special->target == player->mo && special->extravalue1 == 1)
466 				return; // Can't hurt a Pterabyte if it's trying to pick you up
467 
468 			if ((P_MobjFlip(toucher)*toucher->momz < 0) && (elementalpierce != 1))
469 			{
470 				if (!(player->charability2 == CA2_MELEE && player->panim == PA_ABILITY2))
471 				{
472 					fixed_t setmomz = -toucher->momz; // Store this, momz get changed by P_DoJump within P_DoBubbleBounce
473 
474 					if (elementalpierce == 2) // Reset bubblewrap, part 1
475 						P_DoBubbleBounce(player);
476 					toucher->momz = setmomz;
477 					if (elementalpierce == 2) // Reset bubblewrap, part 2
478 					{
479 						boolean underwater = toucher->eflags & MFE_UNDERWATER;
480 
481 						if (underwater)
482 							toucher->momz /= 2;
483 						toucher->momz -= (toucher->momz/(underwater ? 8 : 4)); // Cap the height!
484 					}
485 				}
486 			}
487 			if (player->pflags & PF_BOUNCING)
488 				P_DoAbilityBounce(player, false);
489 			if (special->info->spawnhealth > 1) // Multi-hit? Bounce back!
490 			{
491 				toucher->momx = -toucher->momx;
492 				toucher->momy = -toucher->momy;
493 				if (player->charability == CA_FLY && player->panim == PA_ABILITY)
494 					toucher->momz = -toucher->momz/2;
495 				else if (player->pflags & PF_GLIDING && !P_IsObjectOnGround(toucher))
496 				{
497 					player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
498 					P_SetPlayerMobjState(toucher, S_PLAY_FALL);
499 					toucher->momz += P_MobjFlip(toucher) * (player->speed >> 3);
500 					toucher->momx = 7*toucher->momx>>3;
501 					toucher->momy = 7*toucher->momy>>3;
502 				}
503 				else if (player->dashmode >= DASHMODE_THRESHOLD && (player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE)
504 					&& player->panim == PA_DASH)
505 					P_DoPlayerPain(player, special, special);
506 			}
507 			P_DamageMobj(special, toucher, toucher, 1, 0);
508 			if (player->charability == CA_TWINSPIN && player->panim == PA_ABILITY)
509 				P_TwinSpinRejuvenate(player, player->thokitem);
510 		}
511 		else
512 		{
513 			if (special->type == MT_PTERABYTE && special->target == player->mo)
514 				return; // Don't hurt the player you're trying to grab
515 
516 			P_DamageMobj(toucher, special, special, 1, 0);
517 		}
518 
519 		return;
520 	}
521 	else if (special->flags & MF_FIRE)
522 	{
523 		P_DamageMobj(toucher, special, special, 1, DMG_FIRE);
524 		return;
525 	}
526 	else
527 	{
528 	// We now identify by object type, not sprite! Tails 04-11-2001
529 	switch (special->type)
530 	{
531 // ***************************************** //
532 // Rings, coins, spheres, weapon panels, etc //
533 // ***************************************** //
534 		case MT_REDTEAMRING:
535 			if (player->ctfteam != 1)
536 				return;
537 			/* FALLTHRU */
538 		case MT_BLUETEAMRING: // Yes, I'm lazy. Oh well, deal with it.
539 			if (special->type == MT_BLUETEAMRING && player->ctfteam != 2)
540 				return;
541 			/* FALLTHRU */
542 		case MT_RING:
543 		case MT_FLINGRING:
544 		case MT_COIN:
545 		case MT_FLINGCOIN:
546 		case MT_NIGHTSSTAR:
547 			if (!(P_CanPickupItem(player, false)) && !(special->flags2 & MF2_NIGHTSPULL))
548 				return;
549 
550 			special->momx = special->momy = special->momz = 0;
551 			P_GivePlayerRings(player, 1);
552 
553 			if ((maptol & TOL_NIGHTS) && special->type != MT_FLINGRING && special->type != MT_FLINGCOIN)
554 				P_DoNightsScore(player);
555 			break;
556 		case MT_BLUESPHERE:
557 		case MT_FLINGBLUESPHERE:
558 		case MT_NIGHTSCHIP:
559 		case MT_FLINGNIGHTSCHIP:
560 			if (!(P_CanPickupItem(player, false)) && !(special->flags2 & MF2_NIGHTSPULL))
561 				return;
562 
563 			special->momx = special->momy = special->momz = 0;
564 			P_GivePlayerSpheres(player, 1);
565 
566 			if (special->type == MT_BLUESPHERE)
567 			{
568 				special->destscale = ((player->powers[pw_carry] == CR_NIGHTSMODE) ? 4 : 2)*special->scale;
569 				if (states[special->info->deathstate].tics > 0)
570 					special->scalespeed = FixedDiv(FixedDiv(special->destscale, special->scale), states[special->info->deathstate].tics<<FRACBITS);
571 				else
572 					special->scalespeed = 4*FRACUNIT/5;
573 			}
574 
575 			if (maptol & TOL_NIGHTS)
576 				P_DoNightsScore(player);
577 			break;
578 		case MT_BOMBSPHERE:
579 			if (!(P_CanPickupItem(player, false)) && !(special->flags2 & MF2_NIGHTSPULL))
580 				return;
581 
582 			special->momx = special->momy = special->momz = 0;
583 			P_DamageMobj(toucher, special, special, 1, 0);
584 			break;
585 		case MT_AUTOPICKUP:
586 		case MT_BOUNCEPICKUP:
587 		case MT_SCATTERPICKUP:
588 		case MT_GRENADEPICKUP:
589 		case MT_EXPLODEPICKUP:
590 		case MT_RAILPICKUP:
591 			if (!(P_CanPickupItem(player, true)))
592 				return;
593 
594 			// Give the power and ringweapon
595 			if (special->info->mass >= pw_infinityring && special->info->mass <= pw_railring)
596 			{
597 				INT32 pindex = special->info->mass - (INT32)pw_infinityring;
598 
599 				player->powers[special->info->mass] += (UINT16)special->reactiontime;
600 				player->ringweapons |= 1 << (pindex-1);
601 
602 				if (player->powers[special->info->mass] > rw_maximums[pindex])
603 					player->powers[special->info->mass] = rw_maximums[pindex];
604 			}
605 			break;
606 
607 		// Ammo pickups
608 		case MT_INFINITYRING:
609 		case MT_AUTOMATICRING:
610 		case MT_BOUNCERING:
611 		case MT_SCATTERRING:
612 		case MT_GRENADERING:
613 		case MT_EXPLOSIONRING:
614 		case MT_RAILRING:
615 			if (!(P_CanPickupItem(player, true)))
616 				return;
617 
618 			if (special->info->mass >= pw_infinityring && special->info->mass <= pw_railring)
619 			{
620 				INT32 pindex = special->info->mass - (INT32)pw_infinityring;
621 
622 				player->powers[special->info->mass] += (UINT16)special->health;
623 				if (player->powers[special->info->mass] > rw_maximums[pindex])
624 					player->powers[special->info->mass] = rw_maximums[pindex];
625 			}
626 			break;
627 
628 // ***************************** //
629 // Gameplay related collectibles //
630 // ***************************** //
631 		// Special Stage Token
632 		case MT_TOKEN:
633 			if (player->bot)
634 				return;
635 
636 			P_AddPlayerScore(player, 1000);
637 
638 			if (!(gametyperules & GTR_SPECIALSTAGES) || modeattacking) // score only?
639 			{
640 				S_StartSound(toucher, sfx_chchng);
641 				break;
642 			}
643 
644 			tokenlist += special->health;
645 
646 			if (ALL7EMERALDS(emeralds)) // Got all 7
647 			{
648 				if (continuesInSession)
649 				{
650 					player->continues += 1;
651 					player->gotcontinue = true;
652 					if (P_IsLocalPlayer(player))
653 						S_StartSound(NULL, sfx_s3kac);
654 					else
655 						S_StartSound(toucher, sfx_chchng);
656 				}
657 				else
658 				{
659 					P_GiveCoopLives(player, 1, true); // if continues are disabled, a life is a reasonable substitute
660 					S_StartSound(toucher, sfx_chchng);
661 				}
662 			}
663 			else
664 			{
665 				token++;
666 				S_StartSound(toucher, sfx_token);
667 			}
668 
669 			break;
670 
671 		// Emerald Hunt
672 		case MT_EMERHUNT:
673 			if (player->bot)
674 				return;
675 
676 			if (hunt1 == special)
677 				hunt1 = NULL;
678 			else if (hunt2 == special)
679 				hunt2 = NULL;
680 			else if (hunt3 == special)
681 				hunt3 = NULL;
682 
683 			if (!hunt1 && !hunt2 && !hunt3)
684 			{
685 				for (i = 0; i < MAXPLAYERS; i++)
686 				{
687 					if (!playeringame[i] || players[i].spectator)
688 						continue;
689 
690 					players[i].exiting = (14*TICRATE)/5 + 1;
691 				}
692 				//S_StartSound(NULL, sfx_lvpass);
693 			}
694 			break;
695 
696 		// Collectible emeralds
697 		case MT_EMERALD1:
698 		case MT_EMERALD2:
699 		case MT_EMERALD3:
700 		case MT_EMERALD4:
701 		case MT_EMERALD5:
702 		case MT_EMERALD6:
703 		case MT_EMERALD7:
704 			if (player->bot)
705 				return;
706 
707 			if (special->threshold)
708 			{
709 				player->powers[pw_emeralds] |= special->info->speed;
710 				P_DoMatchSuper(player);
711 			}
712 			else
713 			{
714 				emeralds |= special->info->speed;
715 				stagefailed = false;
716 			}
717 
718 			if (special->target && special->target->type == MT_EMERALDSPAWN)
719 			{
720 				if (special->target->target)
721 					P_SetTarget(&special->target->target, NULL);
722 
723 				special->target->threshold = 0;
724 
725 				P_SetTarget(&special->target, NULL);
726 			}
727 			break;
728 
729 		// Power stones / Match emeralds
730 		case MT_FLINGEMERALD:
731 			if (!(P_CanPickupItem(player, true)) || player->tossdelay)
732 				return;
733 
734 			player->powers[pw_emeralds] |= special->threshold;
735 			P_DoMatchSuper(player);
736 			break;
737 
738 		// Secret emblem thingy
739 		case MT_EMBLEM:
740 			{
741 				if (demoplayback || player->bot)
742 					return;
743 				emblemlocations[special->health-1].collected = true;
744 
745 				M_UpdateUnlockablesAndExtraEmblems();
746 
747 				G_SaveGameData();
748 				break;
749 			}
750 
751 		// CTF Flags
752 		case MT_REDFLAG:
753 		case MT_BLUEFLAG:
754 			if (player->bot)
755 				return;
756 			if (player->powers[pw_flashing] || player->tossdelay)
757 				return;
758 			if (!special->spawnpoint)
759 				return;
760 			if (special->fuse == 1)
761 				return;
762 //			if (special->momz > 0)
763 //				return;
764 			{
765 				UINT8 flagteam = (special->type == MT_REDFLAG) ? 1 : 2;
766 				const char *flagtext;
767 				char flagcolor;
768 				char plname[MAXPLAYERNAME+4];
769 
770 				if (special->type == MT_REDFLAG)
771 				{
772 					flagtext = M_GetText("Red flag");
773 					flagcolor = '\x85';
774 				}
775 				else
776 				{
777 					flagtext = M_GetText("Blue flag");
778 					flagcolor = '\x84';
779 				}
780 				snprintf(plname, sizeof(plname), "%s%s%s",
781 						 CTFTEAMCODE(player),
782 						 player_names[player - players],
783 						 CTFTEAMENDCODE(player));
784 
785 				if (player->ctfteam == flagteam) // Player is on the same team as the flag
786 				{
787 					// Ignore height, only check x/y for now
788 					// avoids stupid problems with some flags constantly returning
789 					if (special->x>>FRACBITS != special->spawnpoint->x
790 					    || special->y>>FRACBITS != special->spawnpoint->y)
791 					{
792 						special->fuse = 1;
793 						special->flags2 |= MF2_JUSTATTACKED;
794 
795 						if (!P_PlayerTouchingSectorSpecial(player, 4, 2 + flagteam))
796 						{
797 							CONS_Printf(M_GetText("%s returned the %c%s%c to base.\n"), plname, flagcolor, flagtext, 0x80);
798 
799 							// The fuse code plays this sound effect
800 							//if (players[consoleplayer].ctfteam == player->ctfteam)
801 							//	S_StartSound(NULL, sfx_hoop1);
802 						}
803 					}
804 				}
805 				else if (player->ctfteam) // Player is on the other team (and not a spectator)
806 				{
807 					UINT16 flagflag   = (special->type == MT_REDFLAG) ? GF_REDFLAG : GF_BLUEFLAG;
808 					mobj_t **flagmobj = (special->type == MT_REDFLAG) ? &redflag : &blueflag;
809 
810 					if (player->powers[pw_super])
811 						return;
812 
813 					player->gotflag |= flagflag;
814 					CONS_Printf(M_GetText("%s picked up the %c%s%c!\n"), plname, flagcolor, flagtext, 0x80);
815 					(*flagmobj) = NULL;
816 					// code for dealing with abilities is handled elsewhere now
817 					break;
818 				}
819 			}
820 			return;
821 
822 // ********************************** //
823 // NiGHTS gameplay items and powerups //
824 // ********************************** //
825 		case MT_NIGHTSDRONE:
826 			{
827 				boolean spec = G_IsSpecialStage(gamemap);
828 				boolean cangiveemmy = false;
829 				if (player->bot)
830 					return;
831 				if (player->exiting)
832 					return;
833 				if (player->bonustime)
834 				{
835 					if (spec) //After-mare bonus time/emerald reward in special stages.
836 					{
837 						// only allow the player with the emerald in-hand to leave.
838 						if (toucher->tracer
839 						&& toucher->tracer->type == MT_GOTEMERALD)
840 						{}
841 						else // Make sure that SOMEONE has the emerald, at least!
842 						{
843 							for (i = 0; i < MAXPLAYERS; i++)
844 								if (playeringame[i] && players[i].playerstate == PST_LIVE
845 								&& players[i].mo->tracer
846 								&& players[i].mo->tracer->type == MT_GOTEMERALD)
847 									return;
848 							// Well no one has an emerald, so exit anyway!
849 						}
850 						cangiveemmy = true;
851 						// Don't play Ideya sound in special stage mode
852 					}
853 					else
854 						S_StartSound(toucher, special->info->activesound);
855 				}
856 				else //Initial transformation. Don't allow second chances in special stages!
857 				{
858 					if (player->powers[pw_carry] == CR_NIGHTSMODE)
859 						return;
860 
861 					S_StartSound(toucher, sfx_supert);
862 				}
863 				P_SwitchSpheresBonusMode(false);
864 				if (!(netgame || multiplayer) && !(player->powers[pw_carry] == CR_NIGHTSMODE))
865 					P_SetTarget(&special->tracer, toucher);
866 				P_SetTarget(&player->drone, special); // Mark the player as 'center into the drone'
867 				P_NightserizePlayer(player, special->health); // Transform!
868 				if (!spec)
869 				{
870 					if (toucher->tracer) // Move the Ideya to an anchor!
871 					{
872 						mobj_t *orbittarget = special->target ? special->target : special;
873 						mobj_t *hnext = orbittarget->hnext, *anchorpoint = NULL, *anchorpoint2 = NULL;
874 						mobj_t *mo2;
875 						thinker_t *th;
876 
877 						// The player might have two Ideyas: toucher->tracer and toucher->tracer->hnext
878 						// so handle their anchorpoints accordingly.
879 						// scan the thinkers to find the corresponding anchorpoint
880 						for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
881 						{
882 							if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
883 								continue;
884 
885 							mo2 = (mobj_t *)th;
886 
887 							if (mo2->type != MT_IDEYAANCHOR)
888 								continue;
889 
890 							if (mo2->health == toucher->tracer->health) // do ideya numberes match?
891 								anchorpoint = mo2;
892 							else if (toucher->tracer->hnext && mo2->health == toucher->tracer->hnext->health)
893 								anchorpoint2 = mo2;
894 
895 							if ((!toucher->tracer->hnext && anchorpoint)
896 								|| (toucher->tracer->hnext && anchorpoint && anchorpoint2))
897 								break;
898 						}
899 
900 						if (anchorpoint)
901 						{
902 							toucher->tracer->flags |= MF_GRENADEBOUNCE; // custom radius factors
903 							toucher->tracer->threshold = 8 << 20; // X factor 0, Y factor 0, Z factor 8
904 						}
905 
906 						if (anchorpoint2)
907 						{
908 							toucher->tracer->hnext->flags |= MF_GRENADEBOUNCE; // custom radius factors
909 							toucher->tracer->hnext->threshold = 8 << 20; // X factor 0, Y factor 0, Z factor 8
910 						}
911 
912 						P_SetTarget(&orbittarget->hnext, toucher->tracer);
913 						if (!orbittarget->hnext->hnext)
914 							P_SetTarget(&orbittarget->hnext->hnext, hnext); // Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo.
915 						else
916 							P_SetTarget(&orbittarget->hnext->hnext->target, anchorpoint2 ? anchorpoint2 : orbittarget);
917 						P_SetTarget(&orbittarget->hnext->target, anchorpoint ? anchorpoint : orbittarget);
918 						P_SetTarget(&toucher->tracer, NULL);
919 
920 						if (hnext)
921 						{
922 							orbittarget->hnext->extravalue1 = (angle_t)(hnext->extravalue1 - 72*ANG1);
923 							if (orbittarget->hnext->extravalue1 > hnext->extravalue1)
924 								orbittarget->hnext->extravalue1 -= (72*ANG1)/orbittarget->hnext->extravalue1;
925 						}
926 					}
927 					if (player->exiting) // ...then move it back?
928 					{
929 						mobj_t *hnext = special->target ? special->target : special; // goalpost
930 						while ((hnext = hnext->hnext))
931 						{
932 							hnext->flags &= ~MF_GRENADEBOUNCE;
933 							hnext->threshold = 0;
934 							P_SetTarget(&hnext->target, toucher);
935 						}
936 					}
937 					return;
938 				}
939 
940 				if (!cangiveemmy)
941 					return;
942 
943 				if (player->exiting)
944 					P_GiveEmerald(false);
945 				else if (player->mo->tracer && player->mare)
946 				{
947 					P_KillMobj(toucher->tracer, NULL, NULL, 0); // No emerald for you just yet!
948 					S_StartSound(NULL, sfx_ghosty);
949 					special->flags2 |= MF2_DONTDRAW;
950 				}
951 
952 				return;
953 			}
954 		case MT_NIGHTSLOOPHELPER:
955 			// One second delay
956 			if (special->fuse < toucher->fuse - TICRATE)
957 			{
958 				thinker_t *th;
959 				mobj_t *mo2;
960 				INT32 count;
961 				fixed_t x,y,z, gatherradius;
962 				angle_t d;
963 				statenum_t sparklestate = S_NULL;
964 
965 				if (special->target != toucher) // These ain't your helpers, pal!
966 					return;
967 
968 				x = special->x>>FRACBITS;
969 				y = special->y>>FRACBITS;
970 				z = special->z>>FRACBITS;
971 				count = 1;
972 
973 				// scan the remaining thinkers
974 				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
975 				{
976 					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
977 						continue;
978 
979 					mo2 = (mobj_t *)th;
980 
981 					if (mo2 == special)
982 						continue;
983 
984 					// Not our stuff!
985 					if (mo2->target != toucher)
986 						continue;
987 
988 					if (mo2->type == MT_NIGHTSPARKLE)
989 						mo2->tics = 1;
990 					else if (mo2->type == MT_NIGHTSLOOPHELPER)
991 					{
992 						if (mo2->fuse >= special->fuse)
993 						{
994 							count++;
995 							x += mo2->x>>FRACBITS;
996 							y += mo2->y>>FRACBITS;
997 							z += mo2->z>>FRACBITS;
998 						}
999 						P_RemoveMobj(mo2);
1000 					}
1001 				}
1002 				x = (x/count)<<FRACBITS;
1003 				y = (y/count)<<FRACBITS;
1004 				z = (z/count)<<FRACBITS;
1005 				gatherradius = P_AproxDistance(P_AproxDistance(special->x - x, special->y - y), special->z - z);
1006 				P_RemoveMobj(special);
1007 
1008 				if (player->powers[pw_nights_superloop])
1009 				{
1010 					gatherradius *= 2;
1011 					sparklestate = mobjinfo[MT_NIGHTSPARKLE].seestate;
1012 				}
1013 
1014 				if (gatherradius < 30*FRACUNIT) // Player is probably just sitting there.
1015 					return;
1016 
1017 				for (d = 0; d < 16; d++)
1018 					P_SpawnParaloop(x, y, z, gatherradius, 16, MT_NIGHTSPARKLE, sparklestate, d*ANGLE_22h, false);
1019 
1020 				S_StartSound(toucher, sfx_prloop);
1021 
1022 				// Now we RE-scan all the thinkers to find close objects to pull
1023 				// in from the paraloop. Isn't this just so efficient?
1024 				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
1025 				{
1026 					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
1027 						continue;
1028 
1029 					mo2 = (mobj_t *)th;
1030 
1031 					if (P_AproxDistance(P_AproxDistance(mo2->x - x, mo2->y - y), mo2->z - z) > gatherradius)
1032 						continue;
1033 
1034 					if (mo2->flags & MF_SHOOTABLE)
1035 					{
1036 						P_DamageMobj(mo2, toucher, toucher, 1, 0);
1037 						continue;
1038 					}
1039 
1040 					// Make these APPEAR!
1041 					// Tails 12-15-2003
1042 					if (mo2->flags & MF_NIGHTSITEM)
1043 					{
1044 						// Requires Bonus Time
1045 						if ((mo2->flags2 & MF2_STRONGBOX) && !player->bonustime)
1046 							continue;
1047 
1048 						if (!(mo2->flags & MF_SPECIAL) && mo2->health)
1049 						{
1050 							mo2->flags2 &= ~MF2_DONTDRAW;
1051 							mo2->flags |= MF_SPECIAL;
1052 							mo2->flags &= ~MF_NIGHTSITEM;
1053 							S_StartSound(toucher, sfx_hidden);
1054 							continue;
1055 						}
1056 					}
1057 
1058 					if (!(mo2->type == MT_RING || mo2->type == MT_COIN
1059 						|| mo2->type == MT_BLUESPHERE || mo2->type == MT_BOMBSPHERE
1060 						|| mo2->type == MT_NIGHTSCHIP || mo2->type == MT_NIGHTSSTAR
1061 						|| ((mo2->type == MT_EMBLEM) && (mo2->reactiontime & GE_NIGHTSPULL))))
1062 						continue;
1063 
1064 					// Yay! The thing's in reach! Pull it in!
1065 					mo2->flags |= MF_NOCLIP|MF_NOCLIPHEIGHT;
1066 					mo2->flags2 |= MF2_NIGHTSPULL;
1067 					// New NiGHTS attract speed dummied out because the older behavior
1068 					// is exploited as a mechanic. Uncomment to enable.
1069 					mo2->movefactor = 0; // 32*FRACUNIT; // initialize the NightsItemChase timer
1070 					P_SetTarget(&mo2->tracer, toucher);
1071 				}
1072 			}
1073 			return;
1074 		case MT_EGGCAPSULE:
1075 			if (player->bot)
1076 				return;
1077 
1078 			// make sure everything is as it should be, THEN take rings from players in special stages
1079 			if (player->powers[pw_carry] == CR_NIGHTSMODE && !toucher->target)
1080 				return;
1081 
1082 			if (toucher->tracer && toucher->tracer->health > 0)
1083 				return; // Don't have multiple ideya, unless it's the first one given (health = 0)
1084 
1085 			if (player->mare != special->threshold) // wrong mare
1086 				return;
1087 
1088 			if (special->reactiontime > 0) // capsule already has a player attacking it, ignore
1089 				return;
1090 
1091 			if (G_IsSpecialStage(gamemap) && !player->exiting)
1092 			{ // In special stages, share spheres. Everyone gives up theirs to the player who touched the capsule
1093 				for (i = 0; i < MAXPLAYERS; i++)
1094 					if (playeringame[i] && (&players[i] != player) && players[i].spheres > 0)
1095 					{
1096 						player->spheres += players[i].spheres;
1097 						players[i].spheres = 0;
1098 					}
1099 			}
1100 
1101 			if (player->spheres <= 0 || player->exiting)
1102 				return;
1103 
1104 			// Mark the player as 'pull into the capsule'
1105 			P_SetTarget(&player->capsule, special);
1106 			special->reactiontime = (player-players)+1;
1107 			P_SetTarget(&special->target, NULL);
1108 
1109 			// Clear text
1110 			player->texttimer = 0;
1111 			return;
1112 		case MT_NIGHTSBUMPER:
1113 			// Don't trigger if the stage is ended/failed
1114 			if (player->exiting)
1115 				return;
1116 
1117 			if (player->bumpertime <= (TICRATE/2)-5)
1118 			{
1119 				S_StartSound(toucher, special->info->seesound);
1120 				if (player->powers[pw_carry] == CR_NIGHTSMODE)
1121 				{
1122 					player->bumpertime = TICRATE/2;
1123 					if (special->threshold > 0)
1124 						player->flyangle = (special->threshold*30)-1;
1125 					else
1126 						player->flyangle = special->threshold;
1127 
1128 					player->speed = FixedMul(special->info->speed, special->scale);
1129 					P_SetTarget(&player->mo->hnext, special); // Reference bumper for position correction on next tic
1130 				}
1131 				else // More like a spring
1132 				{
1133 					angle_t fa;
1134 					fixed_t xspeed, yspeed;
1135 					const fixed_t speed = FixedMul(FixedDiv(special->info->speed*FRACUNIT,75*FRACUNIT), FixedSqrt(FixedMul(toucher->scale,special->scale)));
1136 
1137 					player->bumpertime = TICRATE/2;
1138 
1139 					P_UnsetThingPosition(toucher);
1140 					toucher->x = special->x;
1141 					toucher->y = special->y;
1142 					P_SetThingPosition(toucher);
1143 					toucher->z = special->z+(special->height/4);
1144 
1145 					if (special->threshold > 0)
1146 						fa = (FixedAngle(((special->threshold*30)-1)*FRACUNIT)>>ANGLETOFINESHIFT) & FINEMASK;
1147 					else
1148 						fa = 0;
1149 
1150 					xspeed = FixedMul(FINECOSINE(fa),speed);
1151 					yspeed = FixedMul(FINESINE(fa),speed);
1152 
1153 					P_InstaThrust(toucher, special->angle, xspeed/10);
1154 					toucher->momz = yspeed/11;
1155 
1156 					toucher->angle = special->angle;
1157 
1158 					P_SetPlayerAngle(player, toucher->angle);
1159 
1160 					P_ResetPlayer(player);
1161 
1162 					P_SetPlayerMobjState(toucher, S_PLAY_FALL);
1163 				}
1164 			}
1165 			return;
1166 		case MT_NIGHTSSUPERLOOP:
1167 			if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
1168 				return;
1169 			if (!G_IsSpecialStage(gamemap))
1170 				player->powers[pw_nights_superloop] = (UINT16)special->info->speed;
1171 			else
1172 			{
1173 				for (i = 0; i < MAXPLAYERS; i++)
1174 					if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
1175 						players[i].powers[pw_nights_superloop] = (UINT16)special->info->speed;
1176 				if (special->info->deathsound != sfx_None)
1177 					S_StartSound(NULL, special->info->deathsound);
1178 			}
1179 
1180 			// CECHO showing you what this item is
1181 			if (player == &players[displayplayer] || G_IsSpecialStage(gamemap))
1182 			{
1183 				HU_SetCEchoFlags(V_AUTOFADEOUT);
1184 				HU_SetCEchoDuration(4);
1185 				HU_DoCEcho(M_GetText("\\\\\\\\\\\\\\\\Super Paraloop"));
1186 			}
1187 			break;
1188 		case MT_NIGHTSDRILLREFILL:
1189 			if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
1190 				return;
1191 			if (!G_IsSpecialStage(gamemap))
1192 				player->drillmeter = special->info->speed;
1193 			else
1194 			{
1195 				for (i = 0; i < MAXPLAYERS; i++)
1196 					if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
1197 						players[i].drillmeter = special->info->speed;
1198 				if (special->info->deathsound != sfx_None)
1199 					S_StartSound(NULL, special->info->deathsound);
1200 			}
1201 
1202 			// CECHO showing you what this item is
1203 			if (player == &players[displayplayer] || G_IsSpecialStage(gamemap))
1204 			{
1205 				HU_SetCEchoFlags(V_AUTOFADEOUT);
1206 				HU_SetCEchoDuration(4);
1207 				HU_DoCEcho(M_GetText("\\\\\\\\\\\\\\\\Drill Refill"));
1208 			}
1209 			break;
1210 		case MT_NIGHTSHELPER:
1211 			if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
1212 				return;
1213 			if (!G_IsSpecialStage(gamemap))
1214 			{
1215 				// A flicky orbits us now
1216 				mobj_t *flickyobj = P_SpawnMobj(toucher->x, toucher->y, toucher->z + toucher->info->height, MT_NIGHTOPIANHELPER);
1217 				P_SetTarget(&flickyobj->target, toucher);
1218 
1219 				player->powers[pw_nights_helper] = (UINT16)special->info->speed;
1220 			}
1221 			else
1222 			{
1223 				mobj_t *flickyobj;
1224 				for (i = 0; i < MAXPLAYERS; i++)
1225 					if (playeringame[i] && players[i].mo && players[i].powers[pw_carry] == CR_NIGHTSMODE) {
1226 						players[i].powers[pw_nights_helper] = (UINT16)special->info->speed;
1227 						flickyobj = P_SpawnMobj(players[i].mo->x, players[i].mo->y, players[i].mo->z + players[i].mo->info->height, MT_NIGHTOPIANHELPER);
1228 						P_SetTarget(&flickyobj->target, players[i].mo);
1229 					}
1230 				if (special->info->deathsound != sfx_None)
1231 					S_StartSound(NULL, special->info->deathsound);
1232 			}
1233 
1234 			// CECHO showing you what this item is
1235 			if (player == &players[displayplayer] || G_IsSpecialStage(gamemap))
1236 			{
1237 				HU_SetCEchoFlags(V_AUTOFADEOUT);
1238 				HU_SetCEchoDuration(4);
1239 				HU_DoCEcho(M_GetText("\\\\\\\\\\\\\\\\Nightopian Helper"));
1240 			}
1241 			break;
1242 		case MT_NIGHTSEXTRATIME:
1243 			if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
1244 				return;
1245 			if (!G_IsSpecialStage(gamemap))
1246 			{
1247 				player->nightstime += special->info->speed;
1248 				player->startedtime += special->info->speed;
1249 				player->lapstartedtime += special->info->speed;
1250 				P_RestoreMusic(player);
1251 			}
1252 			else
1253 			{
1254 				for (i = 0; i < MAXPLAYERS; i++)
1255 					if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
1256 					{
1257 						players[i].nightstime += special->info->speed;
1258 						players[i].startedtime += special->info->speed;
1259 						players[i].lapstartedtime += special->info->speed;
1260 						P_RestoreMusic(&players[i]);
1261 					}
1262 				if (special->info->deathsound != sfx_None)
1263 					S_StartSound(NULL, special->info->deathsound);
1264 			}
1265 
1266 			// CECHO showing you what this item is
1267 			if (player == &players[displayplayer] || G_IsSpecialStage(gamemap))
1268 			{
1269 				HU_SetCEchoFlags(V_AUTOFADEOUT);
1270 				HU_SetCEchoDuration(4);
1271 				HU_DoCEcho(M_GetText("\\\\\\\\\\\\\\\\Extra Time"));
1272 			}
1273 			break;
1274 		case MT_NIGHTSLINKFREEZE:
1275 			if (player->bot || !(player->powers[pw_carry] == CR_NIGHTSMODE))
1276 				return;
1277 			if (!G_IsSpecialStage(gamemap))
1278 			{
1279 				player->powers[pw_nights_linkfreeze] = (UINT16)special->info->speed;
1280 				player->linktimer = nightslinktics;
1281 			}
1282 			else
1283 			{
1284 				for (i = 0; i < MAXPLAYERS; i++)
1285 					if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
1286 					{
1287 						players[i].powers[pw_nights_linkfreeze] += (UINT16)special->info->speed;
1288 						players[i].linktimer = nightslinktics;
1289 					}
1290 				if (special->info->deathsound != sfx_None)
1291 					S_StartSound(NULL, special->info->deathsound);
1292 			}
1293 
1294 			// CECHO showing you what this item is
1295 			if (player == &players[displayplayer] || G_IsSpecialStage(gamemap))
1296 			{
1297 				HU_SetCEchoFlags(V_AUTOFADEOUT);
1298 				HU_SetCEchoDuration(4);
1299 				HU_DoCEcho(M_GetText("\\\\\\\\\\\\\\\\Link Freeze"));
1300 			}
1301 			break;
1302 		case MT_HOOPCOLLIDE:
1303 			// This produces a kind of 'domino effect' with the hoop's pieces.
1304 			for (; special->hprev != NULL; special = special->hprev); // Move to the first sprite in the hoop
1305 			i = 0;
1306 			for (; special->type == MT_HOOP; special = special->hnext)
1307 			{
1308 				special->fuse = 11;
1309 				special->movedir = i;
1310 				special->extravalue1 = special->target->extravalue1;
1311 				special->extravalue2 = special->target->extravalue2;
1312 				special->target->threshold = 4242;
1313 				i++;
1314 			}
1315 			// Make the collision detectors disappear.
1316 			{
1317 				mobj_t *hnext;
1318 				for (; special != NULL; special = hnext)
1319 				{
1320 					hnext = special->hnext;
1321 					P_RemoveMobj(special);
1322 				}
1323 			}
1324 
1325 			P_DoNightsScore(player);
1326 
1327 			// Hoops are the only things that should add to the drill meter
1328 			// Also, one tic's worth of drill is too much.
1329 			if (G_IsSpecialStage(gamemap))
1330 			{
1331 				for (i = 0; i < MAXPLAYERS; i++)
1332 					if (playeringame[i] && players[i].powers[pw_carry] == CR_NIGHTSMODE)
1333 						players[i].drillmeter += TICRATE/2;
1334 			}
1335 			else if (player->bot)
1336 				players[consoleplayer].drillmeter += TICRATE/2;
1337 			else
1338 				player->drillmeter += TICRATE/2;
1339 
1340 			// Play hoop sound -- pick one depending on the current link.
1341 			if (player->linkcount <= 5)
1342 				S_StartSound(toucher, sfx_hoop1);
1343 			else if (player->linkcount <= 10)
1344 				S_StartSound(toucher, sfx_hoop2);
1345 			else
1346 				S_StartSound(toucher, sfx_hoop3);
1347 			return;
1348 
1349 // ***** //
1350 // Mario //
1351 // ***** //
1352 		case MT_SHELL:
1353 			{
1354 				boolean bounceon = ((P_MobjFlip(toucher)*(toucher->z - (special->z + special->height/2)) > 0) && (P_MobjFlip(toucher)*toucher->momz < 0));
1355 				if (special->threshold == TICRATE) // it's moving
1356 				{
1357 					if (bounceon)
1358 					{
1359 						// Stop it!
1360 						special->momx = special->momy = 0;
1361 						S_StartSound(toucher, sfx_mario2);
1362 						P_SetTarget(&special->target, NULL);
1363 						special->threshold = TICRATE - 1;
1364 						toucher->momz = -toucher->momz;
1365 					}
1366 					else // can't handle in PIT_CheckThing because of landing-on causing it to stop
1367 						P_DamageMobj(toucher, special, special->target, 1, 0);
1368 				}
1369 				else if (special->threshold == 0)
1370 				{
1371 					// Kick that sucker around!
1372 					special->movedir = ((special->movedir == 1) ? -1 : 1);
1373 					P_InstaThrust(special, toucher->angle, (special->info->speed*special->scale));
1374 					S_StartSound(toucher, sfx_mario2);
1375 					P_SetTarget(&special->target, toucher);
1376 					special->threshold = (3*TICRATE)/2;
1377 					if (bounceon)
1378 						toucher->momz = -toucher->momz;
1379 				}
1380 			}
1381 			return;
1382 		case MT_AXE:
1383 			{
1384 				line_t junk;
1385 				thinker_t  *th;
1386 				mobj_t *mo2;
1387 
1388 				if (player->bot)
1389 					return;
1390 
1391 				// Initialize my junk
1392 				junk.tags.tags = NULL;
1393 				junk.tags.count = 0;
1394 
1395 				Tag_FSet(&junk.tags, LE_AXE);
1396 				EV_DoElevator(&junk, bridgeFall, false);
1397 
1398 				// scan the remaining thinkers to find koopa
1399 				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
1400 				{
1401 					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
1402 						continue;
1403 
1404 					mo2 = (mobj_t *)th;
1405 
1406 					if (mo2->type != MT_KOOPA)
1407 						continue;
1408 
1409 					mo2->momz = 5*FRACUNIT;
1410 					break;
1411 				}
1412 			}
1413 			break;
1414 		case MT_LETTER:
1415 		{
1416 			if (special->health && !player->bot)
1417 			{
1418 				F_StartTextPrompt(199, 0, toucher, 0, true, false);
1419 				special->health = 0;
1420 				if (ultimatemode && player->continues < 99)
1421 					player->continues++;
1422 			}
1423 			return;
1424 		}
1425 		case MT_FIREFLOWER:
1426 			if (player->bot)
1427 				return;
1428 
1429 			S_StartSound(toucher, sfx_mario3);
1430 
1431 			player->powers[pw_shield] = (player->powers[pw_shield] & SH_NOSTACK)|SH_FIREFLOWER;
1432 
1433 			if (!(player->powers[pw_super] || (mariomode && player->powers[pw_invulnerability])))
1434 			{
1435 				player->mo->color = SKINCOLOR_WHITE;
1436 				G_GhostAddColor(GHC_FIREFLOWER);
1437 			}
1438 
1439 			break;
1440 
1441 // *************** //
1442 // Misc touchables //
1443 // *************** //
1444 		case MT_STARPOST:
1445 			P_TouchStarPost(special, player, special->spawnpoint && (special->spawnpoint->options & MTF_OBJECTSPECIAL));
1446 			return;
1447 
1448 		case MT_FAKEMOBILE:
1449 			{
1450 				fixed_t touchx, touchy, touchspeed;
1451 				angle_t angle;
1452 
1453 				if (P_AproxDistance(toucher->x-special->x, toucher->y-special->y) >
1454 					P_AproxDistance((toucher->x-toucher->momx)-special->x, (toucher->y-toucher->momy)-special->y))
1455 				{
1456 					touchx = toucher->x + toucher->momx;
1457 					touchy = toucher->y + toucher->momy;
1458 				}
1459 				else
1460 				{
1461 					touchx = toucher->x;
1462 					touchy = toucher->y;
1463 				}
1464 
1465 				angle = R_PointToAngle2(special->x, special->y, touchx, touchy);
1466 				touchspeed = P_AproxDistance(toucher->momx, toucher->momy);
1467 
1468 				toucher->momx = P_ReturnThrustX(special, angle, touchspeed);
1469 				toucher->momy = P_ReturnThrustY(special, angle, touchspeed);
1470 				toucher->momz = -toucher->momz;
1471 				if (player->pflags & PF_GLIDING && !P_IsObjectOnGround(toucher))
1472 				{
1473 					player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
1474 					P_SetPlayerMobjState(toucher, S_PLAY_FALL);
1475 					toucher->momz += P_MobjFlip(toucher) * (player->speed >> 3);
1476 					toucher->momx = 7*toucher->momx>>3;
1477 					toucher->momy = 7*toucher->momy>>3;
1478 				}
1479 				player->homing = 0;
1480 
1481 				// Play a bounce sound?
1482 				S_StartSound(toucher, special->info->painsound);
1483 			}
1484 			return;
1485 
1486 		case MT_BLACKEGGMAN_GOOPFIRE:
1487 			if (!player->powers[pw_flashing] && !(player->powers[pw_ignorelatch] & (1<<15)))
1488 			{
1489 				toucher->momx = 0;
1490 				toucher->momy = 0;
1491 
1492 				if (toucher->momz != 0)
1493 					special->momz = toucher->momz;
1494 
1495 				player->powers[pw_carry] = CR_BRAKGOOP;
1496 				P_SetTarget(&toucher->tracer, special);
1497 
1498 				P_ResetPlayer(player);
1499 
1500 				if (special->target && special->target->state == &states[S_BLACKEGG_SHOOT1])
1501 				{
1502 					if (special->target->health <= 2 && P_RandomChance(FRACUNIT/2))
1503 						P_SetMobjState(special->target, special->target->info->missilestate);
1504 					else
1505 						P_SetMobjState(special->target, special->target->info->raisestate);
1506 				}
1507 			}
1508 			return;
1509 		case MT_EGGSHIELD:
1510 			{
1511 				angle_t angle = R_PointToAngle2(special->x, special->y, toucher->x, toucher->y) - special->angle;
1512 				fixed_t touchspeed = P_AproxDistance(toucher->momx, toucher->momy);
1513 				if (touchspeed < special->scale)
1514 					touchspeed = special->scale;
1515 
1516 				// Blocked by the shield?
1517 				if (!(angle > ANGLE_90 && angle < ANGLE_270))
1518 				{
1519 					toucher->momx = P_ReturnThrustX(special, special->angle, touchspeed);
1520 					toucher->momy = P_ReturnThrustY(special, special->angle, touchspeed);
1521 					toucher->momz = -toucher->momz;
1522 					if (player->pflags & PF_GLIDING && !P_IsObjectOnGround(toucher))
1523 					{
1524 						player->pflags &= ~(PF_GLIDING|PF_JUMPED|PF_NOJUMPDAMAGE);
1525 						P_SetPlayerMobjState(toucher, S_PLAY_FALL);
1526 						toucher->momz += P_MobjFlip(toucher) * (player->speed >> 3);
1527 						toucher->momx = 7*toucher->momx>>3;
1528 						toucher->momy = 7*toucher->momy>>3;
1529 					}
1530 					player->homing = 0;
1531 
1532 					// Play a bounce sound?
1533 					S_StartSound(toucher, special->info->painsound);
1534 
1535 					// experimental bounce
1536 					if (special->target)
1537 						special->target->extravalue1 = -special->target->info->speed;
1538 				}
1539 				else
1540 				{
1541 					// Shatter the shield!
1542 					toucher->momx = -toucher->momx/2;
1543 					toucher->momy = -toucher->momy/2;
1544 					toucher->momz = -toucher->momz;
1545 					break;
1546 				}
1547 			}
1548 			return;
1549 
1550 		case MT_EGGROBO1:
1551 			if (special->state == &states[special->info->deathstate])
1552 				return;
1553 			if (P_PlayerInPain(player))
1554 				return;
1555 
1556 			P_SetMobjState(special, special->info->meleestate);
1557 			special->angle = special->movedir;
1558 			special->momx = special->momy = 0;
1559 
1560 			// Buenos Dias Mandy
1561 			P_SetPlayerMobjState(toucher, S_PLAY_STUN);
1562 			player->pflags &= ~PF_APPLYAUTOBRAKE;
1563 			P_ResetPlayer(player);
1564 			player->drawangle = special->angle + ANGLE_180;
1565 			P_InstaThrust(toucher, special->angle, FixedMul(3*special->info->speed, special->scale/2));
1566 			toucher->z += P_MobjFlip(toucher);
1567 			if (toucher->eflags & MFE_UNDERWATER) // unlikely.
1568 				P_SetObjectMomZ(toucher, FixedDiv(10511*FRACUNIT,2600*FRACUNIT), false);
1569 			else
1570 				P_SetObjectMomZ(toucher, FixedDiv(69*FRACUNIT,10*FRACUNIT), false);
1571 			if (P_IsLocalPlayer(player))
1572 			{
1573 				quake.intensity = 9*FRACUNIT;
1574 				quake.time = TICRATE/2;
1575 				quake.epicenter = NULL;
1576 			}
1577 
1578 #if 0 // camera redirection - deemed unnecessary
1579 			toucher->angle = special->angle;
1580 			P_SetPlayerAngle(player, toucher->angle);
1581 #endif
1582 
1583 			S_StartSound(toucher, special->info->attacksound); // home run
1584 
1585 			return;
1586 
1587 		case MT_BIGTUMBLEWEED:
1588 		case MT_LITTLETUMBLEWEED:
1589 			if (toucher->momx || toucher->momy)
1590 			{
1591 				special->momx = toucher->momx;
1592 				special->momy = toucher->momy;
1593 				special->momz = P_AproxDistance(toucher->momx, toucher->momy)/4;
1594 
1595 				if (toucher->momz > 0)
1596 					special->momz += toucher->momz/8;
1597 
1598 				P_SetMobjState(special, special->info->seestate);
1599 			}
1600 			return;
1601 		case MT_SMALLGRABCHAIN:
1602 		case MT_BIGGRABCHAIN:
1603 			{
1604 				boolean macespin = false;
1605 				if (P_MobjFlip(toucher)*toucher->momz > 0
1606 					|| (player->powers[pw_carry]))
1607 					return;
1608 
1609 				if (toucher->z > special->z + special->height/2)
1610 					return;
1611 
1612 				if (toucher->z + toucher->height/2 < special->z)
1613 					return;
1614 
1615 				if (player->powers[pw_flashing])
1616 					return;
1617 
1618 				if (special->tracer && !(special->tracer->flags2 & MF2_STRONGBOX))
1619 					macespin = true;
1620 
1621 				if (macespin ? (player->powers[pw_ignorelatch] & (1<<15)) : (player->powers[pw_ignorelatch]))
1622 					return;
1623 
1624 				if (special->movefactor && special->tracer && special->tracer->angle != ANGLE_90 && special->tracer->angle != ANGLE_270)
1625 				{ // I don't expect you to understand this, Mr Bond...
1626 					angle_t ang = R_PointToAngle2(special->x, special->y, toucher->x, toucher->y) - special->tracer->angle;
1627 					if ((special->movefactor > 0) == (special->tracer->angle > ANGLE_90 && special->tracer->angle < ANGLE_270))
1628 						ang += ANGLE_180;
1629 					if (ang < ANGLE_180)
1630 						return; // I expect you to die.
1631 				}
1632 
1633 				P_ResetPlayer(player);
1634 				P_SetTarget(&toucher->tracer, special);
1635 
1636 				if (macespin)
1637 				{
1638 					player->powers[pw_carry] = CR_MACESPIN;
1639 					S_StartSound(toucher, sfx_spin);
1640 					P_SetPlayerMobjState(toucher, S_PLAY_ROLL);
1641 				}
1642 				else
1643 					player->powers[pw_carry] = CR_GENERIC;
1644 
1645 				// Can't jump first frame
1646 				player->pflags |= PF_JUMPSTASIS;
1647 
1648 				return;
1649 			}
1650 		case MT_EGGMOBILE2_POGO:
1651 			// sanity checks
1652 			if (!special->target || !special->target->health)
1653 				return;
1654 			// Goomba Stomp'd!
1655 			if (special->target->momz < 0)
1656 			{
1657 				P_DamageMobj(toucher, special, special->target, 1, 0);
1658 				//special->target->momz = -special->target->momz;
1659 				special->target->momx = special->target->momy = 0;
1660 				special->target->momz = 0;
1661 				special->target->flags |= MF_NOGRAVITY;
1662 				P_SetMobjState(special->target, special->info->raisestate);
1663 				S_StartSound(special->target, special->info->activesound);
1664 				P_RemoveMobj(special);
1665 			}
1666 			return;
1667 
1668 		case MT_EXTRALARGEBUBBLE:
1669 			if (player->powers[pw_shield] & SH_PROTECTWATER)
1670 				return;
1671 			if (maptol & TOL_NIGHTS)
1672 				return;
1673 			if (mariomode)
1674 				return;
1675 			if (special->state-states != S_EXTRALARGEBUBBLE)
1676 				return; // Don't grab the bubble during its spawn animation
1677 			else if (toucher->eflags & MFE_VERTICALFLIP)
1678 			{
1679 				if (special->z+special->height < toucher->z
1680 					|| special->z+special->height > toucher->z + (toucher->height*2/3))
1681 					return; // Only go in the mouth
1682 			}
1683 			else if (special->z < toucher->z
1684 				|| special->z > toucher->z + (toucher->height*2/3))
1685 				return; // Only go in the mouth
1686 
1687 			// Eaten by player!
1688 			if ((!player->bot) && (player->powers[pw_underwater] && player->powers[pw_underwater] <= 12*TICRATE + 1))
1689 			{
1690 				player->powers[pw_underwater] = underwatertics + 1;
1691 				P_RestoreMusic(player);
1692 			}
1693 
1694 			if (player->powers[pw_underwater] < underwatertics + 1)
1695 				player->powers[pw_underwater] = underwatertics + 1;
1696 
1697 			if (!player->climbing)
1698 			{
1699 				if (player->bot && toucher->state-states != S_PLAY_GASP)
1700 					S_StartSound(toucher, special->info->deathsound); // Force it to play a sound for bots
1701 				P_SetPlayerMobjState(toucher, S_PLAY_GASP);
1702 				P_ResetPlayer(player);
1703 			}
1704 
1705 			toucher->momx = toucher->momy = toucher->momz = 0;
1706 
1707 			if (player->bot)
1708 				return;
1709 			else
1710 				break;
1711 
1712 		case MT_WATERDROP:
1713 			if (special->state == &states[special->info->spawnstate])
1714 			{
1715 				special->z = toucher->z+toucher->height-FixedMul(8*FRACUNIT, special->scale);
1716 				special->momz = 0;
1717 				special->flags |= MF_NOGRAVITY;
1718 				P_SetMobjState (special, special->info->deathstate);
1719 				S_StartSound (special, special->info->deathsound+(P_RandomKey(special->info->mass)));
1720 			}
1721 			return;
1722 
1723 		case MT_CANARIVORE_GAS:
1724 			// if player and gas touch, attach gas to player (overriding any gas that already attached) and apply slowdown effect
1725 			special->flags |= MF_NOGRAVITY|MF_NOCLIPHEIGHT;
1726 			P_UnsetThingPosition(special);
1727 			special->x = toucher->x - toucher->momx/2;
1728 			special->y = toucher->y - toucher->momy/2;
1729 			special->z = toucher->z - toucher->momz/2;
1730 			P_SetThingPosition(special);
1731 			toucher->momx = FixedMul(toucher->momx, 50*FRACUNIT/51);
1732 			toucher->momy = FixedMul(toucher->momy, 50*FRACUNIT/51);
1733 			special->momx = toucher->momx;
1734 			special->momy = toucher->momy;
1735 			special->momz = toucher->momz;
1736 			return;
1737 
1738 		case MT_MINECARTSPAWNER:
1739 			if (!player->bot && special->fuse <= TICRATE && player->powers[pw_carry] != CR_MINECART && !(player->powers[pw_ignorelatch] & (1<<15)))
1740 			{
1741 				mobj_t *mcart = P_SpawnMobj(special->x, special->y, special->z, MT_MINECART);
1742 				P_SetTarget(&mcart->target, toucher);
1743 				mcart->angle = toucher->angle = player->drawangle = special->angle;
1744 				mcart->friction = FRACUNIT;
1745 
1746 				P_ResetPlayer(player);
1747 				player->pflags |= PF_JUMPDOWN;
1748 				player->powers[pw_carry] = CR_MINECART;
1749 				player->pflags &= ~PF_APPLYAUTOBRAKE;
1750 				P_SetTarget(&toucher->tracer, mcart);
1751 				toucher->momx = toucher->momy = toucher->momz = 0;
1752 
1753 				special->fuse = 3*TICRATE;
1754 				special->flags2 |= MF2_DONTDRAW;
1755 			}
1756 			return;
1757 
1758 		case MT_MINECARTEND:
1759 			if (player->powers[pw_carry] == CR_MINECART && toucher->tracer && !P_MobjWasRemoved(toucher->tracer) && toucher->tracer->health)
1760 			{
1761 				fixed_t maxz = max(toucher->z, special->z + 35*special->scale);
1762 
1763 				toucher->momx = toucher->tracer->momx/2;
1764 				toucher->momy = toucher->tracer->momy/2;
1765 				toucher->momz = toucher->tracer->momz + P_AproxDistance(toucher->tracer->momx, toucher->tracer->momy)/2;
1766 				P_ResetPlayer(player);
1767 				player->pflags &= ~PF_APPLYAUTOBRAKE;
1768 				P_SetPlayerMobjState(toucher, S_PLAY_FALL);
1769 				P_SetTarget(&toucher->tracer->target, NULL);
1770 				P_KillMobj(toucher->tracer, toucher, special, 0);
1771 				P_SetTarget(&toucher->tracer, NULL);
1772 				player->powers[pw_carry] = CR_NONE;
1773 				P_UnsetThingPosition(toucher);
1774 				toucher->x = special->x;
1775 				toucher->y = special->y;
1776 				toucher->z = maxz;
1777 				P_SetThingPosition(toucher);
1778 			}
1779 			return;
1780 
1781 		case MT_MINECARTSWITCHPOINT:
1782 			if (player->powers[pw_carry] == CR_MINECART && toucher->tracer && !P_MobjWasRemoved(toucher->tracer) && toucher->tracer->health)
1783 			{
1784 				mobjflag2_t destambush = special->flags2 & MF2_AMBUSH;
1785 				angle_t angdiff = toucher->tracer->angle - special->angle;
1786 				if (angdiff > ANGLE_90 && angdiff < ANGLE_270)
1787 					destambush ^= MF2_AMBUSH;
1788 				toucher->tracer->flags2 = (toucher->tracer->flags2 & ~MF2_AMBUSH) | destambush;
1789 			}
1790 			return;
1791 		default: // SOC or script pickup
1792 			if (player->bot)
1793 				return;
1794 			P_SetTarget(&special->target, toucher);
1795 			break;
1796 		}
1797 	}
1798 
1799 	S_StartSound(toucher, special->info->deathsound); // was NULL, but changed to player so you could hear others pick up rings
1800 	P_KillMobj(special, NULL, toucher, 0);
1801 	special->shadowscale = 0;
1802 }
1803 
1804 /** Saves a player's level progress at a star post
1805   *
1806   * \param post The star post to trigger
1807   * \param player The player that should receive the checkpoint
1808   * \param snaptopost If true, the respawn point will use the star post's position, otherwise player x/y and star post z
1809   */
P_TouchStarPost(mobj_t * post,player_t * player,boolean snaptopost)1810 void P_TouchStarPost(mobj_t *post, player_t *player, boolean snaptopost)
1811 {
1812 	size_t i;
1813 	mobj_t *toucher = player->mo;
1814 	mobj_t *checkbase = snaptopost ? post : toucher;
1815 
1816 	if (player->bot)
1817 		return;
1818 	// In circuit, player must have touched all previous starposts
1819 	if (circuitmap
1820 		&& post->health - player->starpostnum > 1)
1821 	{
1822 		// blatant reuse of a variable that's normally unused in circuit
1823 		if (!player->tossdelay)
1824 			S_StartSound(toucher, sfx_lose);
1825 		player->tossdelay = 3;
1826 		return;
1827 	}
1828 
1829 	// With the parameter + angle setup, we can go up to 1365 star posts. Who needs that many?
1830 	if (post->health > 1365)
1831 	{
1832 		CONS_Debug(DBG_GAMELOGIC, "Bad Starpost Number!\n");
1833 		return;
1834 	}
1835 
1836 	if (player->starpostnum >= post->health)
1837 		return; // Already hit this post
1838 
1839 	if (cv_coopstarposts.value && G_GametypeUsesCoopStarposts() && (netgame || multiplayer))
1840 	{
1841 		for (i = 0; i < MAXPLAYERS; i++)
1842 		{
1843 			if (playeringame[i])
1844 			{
1845 				if (players[i].bot) // ignore dumb, stupid tails
1846 					continue;
1847 
1848 				players[i].starposttime = leveltime;
1849 				players[i].starpostx = checkbase->x>>FRACBITS;
1850 				players[i].starposty = checkbase->y>>FRACBITS;
1851 				players[i].starpostz = post->z>>FRACBITS;
1852 				players[i].starpostangle = post->angle;
1853 				players[i].starpostscale = player->mo->destscale;
1854 				if (post->flags2 & MF2_OBJECTFLIP)
1855 				{
1856 					players[i].starpostscale *= -1;
1857 					players[i].starpostz += post->height>>FRACBITS;
1858 				}
1859 				players[i].starpostnum = post->health;
1860 
1861 				if (cv_coopstarposts.value == 2 && (players[i].playerstate == PST_DEAD || players[i].spectator) && P_GetLives(&players[i]))
1862 					P_SpectatorJoinGame(&players[i]); //players[i].playerstate = PST_REBORN;
1863 			}
1864 		}
1865 		S_StartSound(NULL, post->info->painsound);
1866 	}
1867 	else
1868 	{
1869 		// Save the player's time and position.
1870 		player->starposttime = leveltime;
1871 		player->starpostx = checkbase->x>>FRACBITS;
1872 		player->starposty = checkbase->y>>FRACBITS;
1873 		player->starpostz = post->z>>FRACBITS;
1874 		player->starpostangle = post->angle;
1875 		player->starpostscale = player->mo->destscale;
1876 		if (post->flags2 & MF2_OBJECTFLIP)
1877 		{
1878 			player->starpostscale *= -1;
1879 			player->starpostz += post->height>>FRACBITS;
1880 		}
1881 		player->starpostnum = post->health;
1882 		S_StartSound(toucher, post->info->painsound);
1883 	}
1884 
1885 	P_ClearStarPost(post->health);
1886 
1887 	// Find all starposts in the level with this value - INCLUDING this one!
1888 	if (!(netgame && circuitmap && player != &players[consoleplayer]))
1889 	{
1890 		thinker_t *th;
1891 		mobj_t *mo2;
1892 
1893 		for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
1894 		{
1895 			if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
1896 				continue;
1897 
1898 			mo2 = (mobj_t *)th;
1899 
1900 			if (mo2->type != MT_STARPOST)
1901 				continue;
1902 			if (mo2->health != post->health)
1903 				continue;
1904 
1905 			P_SetMobjState(mo2, mo2->info->painstate);
1906 		}
1907 	}
1908 }
1909 
1910 /** Prints death messages relating to a dying or hit player.
1911   *
1912   * \param player    Affected player.
1913   * \param inflictor The attack weapon used, can be NULL.
1914   * \param source    The attacker, can be NULL.
1915   * \param damagetype The type of damage dealt to the player. If bit 7 (0x80) is set, this was an instant-kill.
1916   */
P_HitDeathMessages(player_t * player,mobj_t * inflictor,mobj_t * source,UINT8 damagetype)1917 static void P_HitDeathMessages(player_t *player, mobj_t *inflictor, mobj_t *source, UINT8 damagetype)
1918 {
1919 	const char *str = NULL;
1920 	boolean deathonly = false;
1921 	boolean deadsource = false;
1922 	boolean deadtarget = false;
1923 	// player names complete with control codes
1924 	char targetname[MAXPLAYERNAME+4];
1925 	char sourcename[MAXPLAYERNAME+4];
1926 
1927 	if (!(gametyperules & (GTR_RINGSLINGER|GTR_HURTMESSAGES)))
1928 		return; // Not in coop, etc.
1929 
1930 	if (!player)
1931 		return; // Impossible!
1932 
1933 	if (!player->mo)
1934 		return; // Also impossible!
1935 
1936 	if (player->spectator)
1937 		return; // No messages for dying (crushed) spectators.
1938 
1939 	if (!netgame)
1940 		return; // Presumably it's obvious what's happening in splitscreen.
1941 
1942 	if (LUAh_HurtMsg(player, inflictor, source, damagetype))
1943 		return;
1944 
1945 	deadtarget = (player->mo->health <= 0);
1946 
1947 	// Don't log every hazard hit if they don't want us to.
1948 	if (!deadtarget && !cv_hazardlog.value)
1949 		return;
1950 
1951 	// Target's name
1952 	snprintf(targetname, sizeof(targetname), "%s%s%s",
1953 	         CTFTEAMCODE(player),
1954 	         player_names[player - players],
1955 	         CTFTEAMENDCODE(player));
1956 
1957 	if (source)
1958 	{
1959 		// inflictor shouldn't be NULL if source isn't
1960 		I_Assert(inflictor != NULL);
1961 
1962 		if (source->player)
1963 		{
1964 			// Source's name (now that we know there is one)
1965 			snprintf(sourcename, sizeof(sourcename), "%s%s%s",
1966 					 CTFTEAMCODE(source->player),
1967 					 player_names[source->player - players],
1968 					 CTFTEAMENDCODE(source->player));
1969 
1970 			// We don't care if it's us.
1971 			// "Player 1's [redacted] killed Player 1."
1972 			if (source->player->playerstate == PST_DEAD && source->player != player &&
1973 			 (inflictor->flags2 & MF2_BEYONDTHEGRAVE))
1974 				deadsource = true;
1975 
1976 			if (inflictor->flags & MF_PUSHABLE)
1977 			{
1978 				str = M_GetText("%s%s's playtime with heavy objects %s %s.\n");
1979 			}
1980 			else switch (inflictor->type)
1981 			{
1982 				case MT_PLAYER:
1983 					if (damagetype == DMG_NUKE) // SH_ARMAGEDDON, armageddon shield
1984 						str = M_GetText("%s%s's armageddon blast %s %s.\n");
1985 					else if ((inflictor->player->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL && (inflictor->player->pflags & PF_SHIELDABILITY))
1986 						str = M_GetText("%s%s's elemental stomp %s %s.\n");
1987 					else if (inflictor->player->powers[pw_invulnerability])
1988 						str = M_GetText("%s%s's invincibility aura %s %s.\n");
1989 					else if (inflictor->player->powers[pw_super])
1990 						str = M_GetText("%s%s's super aura %s %s.\n");
1991 					else
1992 						str = M_GetText("%s%s's tagging hand %s %s.\n");
1993 					break;
1994 				case MT_SPINFIRE:
1995 					str = M_GetText("%s%s's elemental fire trail %s %s.\n");
1996 					break;
1997 				case MT_THROWNBOUNCE:
1998 					str = M_GetText("%s%s's bounce ring %s %s.\n");
1999 					break;
2000 				case MT_THROWNINFINITY:
2001 					str = M_GetText("%s%s's infinity ring %s %s.\n");
2002 					break;
2003 				case MT_THROWNAUTOMATIC:
2004 					str = M_GetText("%s%s's automatic ring %s %s.\n");
2005 					break;
2006 				case MT_THROWNSCATTER:
2007 					str = M_GetText("%s%s's scatter ring %s %s.\n");
2008 					break;
2009 				// TODO: For next two, figure out how to determine if it was a direct hit or splash damage. -SH
2010 				case MT_THROWNEXPLOSION:
2011 					str = M_GetText("%s%s's explosion ring %s %s.\n");
2012 					break;
2013 				case MT_THROWNGRENADE:
2014 					str = M_GetText("%s%s's grenade ring %s %s.\n");
2015 					break;
2016 				case MT_REDRING:
2017 					if (inflictor->flags2 & MF2_RAILRING)
2018 						str = M_GetText("%s%s's rail ring %s %s.\n");
2019 					else
2020 						str = M_GetText("%s%s's thrown ring %s %s.\n");
2021 					break;
2022 				default:
2023 					str = M_GetText("%s%s %s %s.\n");
2024 					break;
2025 			}
2026 
2027 			CONS_Printf(str,
2028 				deadsource ? M_GetText("The late ") : "",
2029 				sourcename,
2030 				deadtarget ? M_GetText("killed") : M_GetText("hit"),
2031 				targetname);
2032 			return;
2033 		}
2034 		else switch (source->type)
2035 		{
2036 			case MT_EGGMAN_ICON:
2037 				str = M_GetText("%s was %s by Eggman's nefarious TV magic.\n");
2038 				break;
2039 			case MT_SPIKE:
2040 			case MT_WALLSPIKE:
2041 				str = M_GetText("%s was %s by spikes.\n");
2042 				break;
2043 			default:
2044 				str = M_GetText("%s was %s by an environmental hazard.\n");
2045 				break;
2046 		}
2047 	}
2048 	else
2049 	{
2050 		// null source, environment kills
2051 		switch (damagetype)
2052 		{
2053 			case DMG_WATER:
2054 				str = M_GetText("%s was %s by dangerous water.\n");
2055 				break;
2056 			case DMG_FIRE:
2057 				str = M_GetText("%s was %s by molten lava.\n");
2058 				break;
2059 			case DMG_ELECTRIC:
2060 				str = M_GetText("%s was %s by electricity.\n");
2061 				break;
2062 			case DMG_SPIKE:
2063 				str = M_GetText("%s was %s by spikes.\n");
2064 				break;
2065 			case DMG_DROWNED:
2066 				deathonly = true;
2067 				str = M_GetText("%s drowned.\n");
2068 				break;
2069 			case DMG_CRUSHED:
2070 				deathonly = true;
2071 				str = M_GetText("%s was crushed.\n");
2072 				break;
2073 			case DMG_DEATHPIT:
2074 				if (deadtarget)
2075 				{
2076 					deathonly = true;
2077 					str = M_GetText("%s fell into a bottomless pit.\n");
2078 				}
2079 				break;
2080 			case DMG_SPACEDROWN:
2081 				if (deadtarget)
2082 				{
2083 					deathonly = true;
2084 					str = M_GetText("%s asphyxiated in space.\n");
2085 				}
2086 				break;
2087 			default:
2088 				if (deadtarget)
2089 				{
2090 					deathonly = true;
2091 					str = M_GetText("%s died.\n");
2092 				}
2093 				break;
2094 		}
2095 		if (!str)
2096 			str = M_GetText("%s was %s by an environmental hazard.\n");
2097 	}
2098 
2099 	if (!str) // Should not happen! Unless we missed catching something above.
2100 		return;
2101 
2102 	if (deathonly)
2103 	{
2104 		if (!deadtarget)
2105 			return;
2106 		CONS_Printf(str, targetname);
2107 	}
2108 	else
2109 		CONS_Printf(str, targetname, deadtarget ? M_GetText("killed") : M_GetText("hit"));
2110 }
2111 
2112 /** Checks if the level timer is over the timelimit and the round should end,
2113   * unless you are in overtime. In which case leveltime may stretch out beyond
2114   * timelimitintics and overtime's status will be checked here each tick.
2115   * Verify that the value of ::cv_timelimit is greater than zero before
2116   * calling this function.
2117   *
2118   * \sa cv_timelimit, P_CheckPointLimit, P_UpdateSpecials
2119   */
P_CheckTimeLimit(void)2120 void P_CheckTimeLimit(void)
2121 {
2122 	INT32 i, k;
2123 
2124 	if (!cv_timelimit.value)
2125 		return;
2126 
2127 	if (!(multiplayer || netgame))
2128 		return;
2129 
2130 	if (!(gametyperules & GTR_TIMELIMIT))
2131 		return;
2132 
2133 	if (leveltime < timelimitintics)
2134 		return;
2135 
2136 	if (gameaction == ga_completed)
2137 		return;
2138 
2139 	//Tagmode round end but only on the tic before the
2140 	//XD_EXITLEVEL packet is received by all players.
2141 	if (G_TagGametype())
2142 	{
2143 		if (leveltime == (timelimitintics + 1))
2144 		{
2145 			for (i = 0; i < MAXPLAYERS; i++)
2146 			{
2147 				if (!playeringame[i] || players[i].spectator
2148 				 || (players[i].pflags & PF_GAMETYPEOVER) || (players[i].pflags & PF_TAGIT))
2149 					continue;
2150 
2151 				CONS_Printf(M_GetText("%s received double points for surviving the round.\n"), player_names[i]);
2152 				P_AddPlayerScore(&players[i], players[i].score);
2153 			}
2154 		}
2155 
2156 		if (server)
2157 			SendNetXCmd(XD_EXITLEVEL, NULL, 0);
2158 	}
2159 
2160 	//Optional tie-breaker for Match/CTF
2161 	else if ((cv_overtime.value) && (gametyperules & GTR_OVERTIME))
2162 	{
2163 		INT32 playerarray[MAXPLAYERS];
2164 		INT32 tempplayer = 0;
2165 		INT32 spectators = 0;
2166 		INT32 playercount = 0;
2167 
2168 		//Figure out if we have enough participating players to care.
2169 		for (i = 0; i < MAXPLAYERS; i++)
2170 		{
2171 			if (playeringame[i] && players[i].spectator)
2172 				spectators++;
2173 		}
2174 
2175 		if ((D_NumPlayers() - spectators) > 1)
2176 		{
2177 			// Play the starpost sfx after the first second of overtime.
2178 			if (gamestate == GS_LEVEL && (leveltime == (timelimitintics + TICRATE)))
2179 				S_StartSound(NULL, sfx_strpst);
2180 
2181 			// Normal Match
2182 			if (!G_GametypeHasTeams())
2183 			{
2184 				//Store the nodes of participating players in an array.
2185 				for (i = 0; i < MAXPLAYERS; i++)
2186 				{
2187 					if (playeringame[i] && !players[i].spectator)
2188 					{
2189 						playerarray[playercount] = i;
2190 						playercount++;
2191 					}
2192 				}
2193 
2194 				//Sort 'em.
2195 				for (i = 1; i < playercount; i++)
2196 				{
2197 					for (k = i; k < playercount; k++)
2198 					{
2199 						if (players[playerarray[i-1]].score < players[playerarray[k]].score)
2200 						{
2201 							tempplayer = playerarray[i-1];
2202 							playerarray[i-1] = playerarray[k];
2203 							playerarray[k] = tempplayer;
2204 						}
2205 					}
2206 				}
2207 
2208 				//End the round if the top players aren't tied.
2209 				if (players[playerarray[0]].score == players[playerarray[1]].score)
2210 					return;
2211 			}
2212 			else
2213 			{
2214 				//In team match and CTF, determining a tie is much simpler. =P
2215 				if (redscore == bluescore)
2216 					return;
2217 			}
2218 		}
2219 		if (server)
2220 			SendNetXCmd(XD_EXITLEVEL, NULL, 0);
2221 	}
2222 
2223 	if (server)
2224 		SendNetXCmd(XD_EXITLEVEL, NULL, 0);
2225 }
2226 
2227 /** Checks if a player's score is over the pointlimit and the round should end.
2228   * Verify that the value of ::cv_pointlimit is greater than zero before
2229   * calling this function.
2230   *
2231   * \sa cv_pointlimit, P_CheckTimeLimit, P_UpdateSpecials
2232   */
P_CheckPointLimit(void)2233 void P_CheckPointLimit(void)
2234 {
2235 	INT32 i;
2236 
2237 	if (!cv_pointlimit.value)
2238 		return;
2239 
2240 	if (!(multiplayer || netgame))
2241 		return;
2242 
2243 	if (!(gametyperules & GTR_POINTLIMIT))
2244 		return;
2245 
2246 	// pointlimit is nonzero, check if it's been reached by this player
2247 	if (G_GametypeHasTeams())
2248 	{
2249 		// Just check both teams
2250 		if ((UINT32)cv_pointlimit.value <= redscore || (UINT32)cv_pointlimit.value <= bluescore)
2251 		{
2252 			if (server)
2253 				SendNetXCmd(XD_EXITLEVEL, NULL, 0);
2254 		}
2255 	}
2256 	else
2257 	{
2258 		for (i = 0; i < MAXPLAYERS; i++)
2259 		{
2260 			if (!playeringame[i] || players[i].spectator)
2261 				continue;
2262 
2263 			if ((UINT32)cv_pointlimit.value <= players[i].score)
2264 			{
2265 				if (server)
2266 					SendNetXCmd(XD_EXITLEVEL, NULL, 0);
2267 				return;
2268 			}
2269 		}
2270 	}
2271 }
2272 
2273 /*Checks for untagged remaining players in both tag derivitave modes.
2274  *If no untagged players remain, end the round.
2275  *Also serves as error checking if the only IT player leaves.*/
P_CheckSurvivors(void)2276 void P_CheckSurvivors(void)
2277 {
2278 	INT32 i;
2279 	INT32 survivors = 0;
2280 	INT32 taggers = 0;
2281 	INT32 spectators = 0;
2282 	INT32 survivorarray[MAXPLAYERS];
2283 
2284 	if (!D_NumPlayers()) //no players in the game, no check performed.
2285 		return;
2286 
2287 	for (i=0; i < MAXPLAYERS; i++) //figure out counts of taggers, survivors and spectators.
2288 	{
2289 		if (playeringame[i])
2290 		{
2291 			if (players[i].spectator)
2292 				spectators++;
2293 			else if ((players[i].pflags & PF_TAGIT) && players[i].quittime < 30 * TICRATE)
2294 				taggers++;
2295 			else if (!(players[i].pflags & PF_GAMETYPEOVER) && players[i].quittime < 30 * TICRATE)
2296 			{
2297 				survivorarray[survivors] = i;
2298 				survivors++;
2299 			}
2300 		}
2301 	}
2302 
2303 	if (!taggers) //If there are no taggers, pick a survivor at random to be it.
2304 	{
2305 		// Exception for hide and seek. If a round has started and the IT player leaves, end the round.
2306 		if ((gametyperules & GTR_HIDEFROZEN) && (leveltime >= (hidetime * TICRATE)))
2307 		{
2308 			CONS_Printf(M_GetText("The IT player has left the game.\n"));
2309 			if (server)
2310 				SendNetXCmd(XD_EXITLEVEL, NULL, 0);
2311 
2312 			return;
2313 		}
2314 
2315 		if (survivors)
2316 		{
2317 			INT32 newtagger = survivorarray[P_RandomKey(survivors)];
2318 
2319 			CONS_Printf(M_GetText("%s is now IT!\n"), player_names[newtagger]); // Tell everyone who is it!
2320 			players[newtagger].pflags |= PF_TAGIT;
2321 
2322 			survivors--; //Get rid of the guy we just made IT.
2323 
2324 			//Yeah, we have an eligible tagger, but we may not have anybody for him to tag!
2325 			//If there is only one guy waiting on the game to fill or spectators to enter game, don't bother.
2326 			if (!survivors && (D_NumPlayers() - spectators) > 1)
2327 			{
2328 				CONS_Printf(M_GetText("All players have been tagged!\n"));
2329 				if (server)
2330 					SendNetXCmd(XD_EXITLEVEL, NULL, 0);
2331 			}
2332 
2333 			return;
2334 		}
2335 
2336 		//If we reach this point, no player can replace the one that was IT.
2337 		//Unless it is one player waiting on a game, end the round.
2338 		if ((D_NumPlayers() - spectators) > 1)
2339 		{
2340 			CONS_Printf(M_GetText("There are no players able to become IT.\n"));
2341 			if (server)
2342 				SendNetXCmd(XD_EXITLEVEL, NULL, 0);
2343 		}
2344 
2345 		return;
2346 	}
2347 
2348 	//If there are taggers, but no survivors, end the round.
2349 	//Except when the tagger is by himself and the rest of the game are spectators.
2350 	if (!survivors && (D_NumPlayers() - spectators) > 1)
2351 	{
2352 		CONS_Printf(M_GetText("All players have been tagged!\n"));
2353 		if (server)
2354 			SendNetXCmd(XD_EXITLEVEL, NULL, 0);
2355 	}
2356 }
2357 
2358 // Checks whether or not to end a race netgame.
P_CheckRacers(void)2359 boolean P_CheckRacers(void)
2360 {
2361 	INT32 i;
2362 
2363 	// Check if all the players in the race have finished. If so, end the level.
2364 	for (i = 0; i < MAXPLAYERS; i++)
2365 	{
2366 		if (playeringame[i] && !players[i].exiting && players[i].lives > 0)
2367 			break;
2368 	}
2369 
2370 	if (i == MAXPLAYERS) // finished
2371 	{
2372 		countdown = 0;
2373 		countdown2 = 0;
2374 		return true;
2375 	}
2376 
2377 	return false;
2378 }
2379 
2380 /** Kills an object.
2381   *
2382   * \param target    The victim.
2383   * \param inflictor The attack weapon. May be NULL (environmental damage).
2384   * \param source    The attacker. May be NULL.
2385   * \param damagetype The type of damage dealt that killed the target. If bit 7 (0x80) was set, this was an instant-death.
2386   * \todo Cleanup, refactor, split up.
2387   * \sa P_DamageMobj
2388   */
P_KillMobj(mobj_t * target,mobj_t * inflictor,mobj_t * source,UINT8 damagetype)2389 void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damagetype)
2390 {
2391 	mobj_t *mo;
2392 
2393 	if (inflictor && (inflictor->type == MT_SHELL || inflictor->type == MT_FIREBALL))
2394 		P_SetTarget(&target->tracer, inflictor);
2395 
2396 	if (!(maptol & TOL_NIGHTS) && G_IsSpecialStage(gamemap) && target->player && target->player->nightstime > 6)
2397 		target->player->nightstime = 6; // Just let P_Ticker take care of the rest.
2398 
2399 	if (target->flags & (MF_ENEMY|MF_BOSS))
2400 		target->momx = target->momy = target->momz = 0;
2401 
2402 	if (target->type != MT_PLAYER && !(target->flags & MF_MONITOR))
2403 		target->flags |= MF_NOGRAVITY|MF_NOCLIP|MF_NOCLIPHEIGHT; // Don't drop Tails 03-08-2000
2404 
2405 	if (target->flags2 & MF2_NIGHTSPULL)
2406 	{
2407 		P_SetTarget(&target->tracer, NULL);
2408 		target->movefactor = 0; // reset NightsItemChase timer
2409 	}
2410 
2411 	// dead target is no more shootable
2412 	target->flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SPECIAL);
2413 	target->flags2 &= ~(MF2_SKULLFLY|MF2_NIGHTSPULL);
2414 	target->health = 0; // This makes it easy to check if something's dead elsewhere.
2415 
2416 	if (LUAh_MobjDeath(target, inflictor, source, damagetype) || P_MobjWasRemoved(target))
2417 		return;
2418 
2419 	// Let EVERYONE know what happened to a player! 01-29-2002 Tails
2420 	if (target->player && !target->player->spectator)
2421 	{
2422 		if (metalrecording) // Ack! Metal Sonic shouldn't die! Cut the tape, end recording!
2423 			G_StopMetalRecording(true);
2424 		if ((gametyperules & GTR_DEATHPENALTY) // note, no team match suicide penalty
2425 			&& ((target == source) || (source == NULL && inflictor == NULL) || (source && !source->player)))
2426 		{ // Suicide penalty
2427 			if (target->player->score >= 50)
2428 				target->player->score -= 50;
2429 			else
2430 				target->player->score = 0;
2431 		}
2432 
2433 		target->flags2 &= ~MF2_DONTDRAW;
2434 	}
2435 
2436 	// if killed by a player
2437 	if (source && source->player)
2438 	{
2439 		if (target->flags & MF_MONITOR)
2440 		{
2441 			P_SetTarget(&target->target, source);
2442 			source->player->numboxes++;
2443 			if (cv_itemrespawn.value && gametype != GT_COOP && (modifiedgame || netgame || multiplayer))
2444 				target->fuse = cv_itemrespawntime.value*TICRATE + 2; // Random box generation
2445 		}
2446 
2447 		// Award Score Tails
2448 		{
2449 			INT32 score = 0;
2450 
2451 			if (maptol & TOL_NIGHTS) // Enemies always worth 200, bosses don't do anything.
2452 			{
2453 				if ((target->flags & MF_ENEMY) && !(target->flags & (MF_MISSILE|MF_BOSS)))
2454 				{
2455 					score = 200;
2456 
2457 					if (source->player->bonustime)
2458 						score *= 2;
2459 
2460 					// Also, add to the link.
2461 					// I don't know if NiGHTS did this, but
2462 					// Sonic Time Attacked did and it seems like a good enough incentive
2463 					// to make people want to actually dash towards/paraloop enemies
2464 					if (++source->player->linkcount > source->player->maxlink)
2465 						source->player->maxlink = source->player->linkcount;
2466 					source->player->linktimer = nightslinktics;
2467 				}
2468 			}
2469 			else
2470 			{
2471 				if (target->flags & MF_BOSS)
2472 					score = 1000;
2473 				else if ((target->flags & MF_ENEMY) && !(target->flags & MF_MISSILE) && target->info->spawnhealth)
2474 				{
2475 					UINT8 locscoreadd = source->player->scoreadd + target->info->spawnhealth;
2476 					mobj_t *scoremobj;
2477 					UINT32 scorestate = mobjinfo[MT_SCORE].spawnstate;
2478 
2479 					scoremobj = P_SpawnMobj(target->x, target->y, target->z + (target->height / 2), MT_SCORE);
2480 
2481 					// More Sonic-like point system
2482 					if (!mariomode) switch (locscoreadd)
2483 					{
2484 						case 1:  score = 100;   break;
2485 						case 2:  score = 200;   scorestate += 1; break;
2486 						case 3:  score = 500;   scorestate += 2; break;
2487 						case 4: case 5: case 6: case 7: case 8: case 9:
2488 						case 10: case 11: case 12: case 13: case 14:
2489 						         score = 1000;  scorestate += 3; break;
2490 						default: score = 10000; scorestate += 4; break;
2491 					}
2492 					// Mario Mode has Mario-like chain point values
2493 					else switch (locscoreadd)
2494 					{
2495 						case 1: score = 100;  break;
2496 						case 2: score = 200;  scorestate += 1; break;
2497 						case 3: score = 400;  scorestate += 5; break;
2498 						case 4: score = 800;  scorestate += 6; break;
2499 						case 5: score = 1000; scorestate += 3; break;
2500 						case 6: score = 2000; scorestate += 7; break;
2501 						case 7: score = 4000; scorestate += 8; break;
2502 						case 8: score = 8000; scorestate += 9; break;
2503 						default: // 1up for a chain this long
2504 							if (modeattacking) // but 1ups don't exist in record attack!
2505 							{ // So we just go back to 10k points.
2506 								score = 10000; scorestate += 4; break;
2507 							}
2508 							P_GivePlayerLives(source->player, 1);
2509 							P_PlayLivesJingle(source->player);
2510 							scorestate += 10;
2511 							break;
2512 					}
2513 
2514 					P_SetMobjState(scoremobj, scorestate);
2515 
2516 					source->player->scoreadd = locscoreadd;
2517 				}
2518 			}
2519 
2520 			P_AddPlayerScore(source->player, score);
2521 		}
2522 	}
2523 
2524 	// if a player avatar dies...
2525 	if (target->player)
2526 	{
2527 		target->flags &= ~(MF_SOLID|MF_SHOOTABLE); // does not block
2528 		P_UnsetThingPosition(target);
2529 		target->flags |= MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY;
2530 		P_SetThingPosition(target);
2531 		target->standingslope = NULL;
2532 		target->pmomz = 0;
2533 
2534 		if (target->player->powers[pw_super])
2535 		{
2536 			target->player->powers[pw_super] = 0;
2537 			if (P_IsLocalPlayer(target->player))
2538 			{
2539 				music_stack_noposition = true; // HACK: Do not reposition next music
2540 				music_stack_fadeout = MUSICRATE/2; // HACK: Fade out current music
2541 			}
2542 			P_RestoreMusic(target->player);
2543 
2544 			if (!G_CoopGametype())
2545 			{
2546 				HU_SetCEchoFlags(0);
2547 				HU_SetCEchoDuration(5);
2548 				HU_DoCEcho(va("%s\\is no longer super.\\\\\\\\", player_names[target->player-players]));
2549 			}
2550 		}
2551 
2552 		target->color = target->player->skincolor;
2553 		target->colorized = false;
2554 		G_GhostAddColor(GHC_NORMAL);
2555 
2556 		if ((target->player->lives <= 1) && (netgame || multiplayer) && G_GametypeUsesCoopLives() && (cv_cooplives.value == 0))
2557 			;
2558 		else if (!target->player->bot && !target->player->spectator && (target->player->lives != INFLIVES)
2559 		 && G_GametypeUsesLives())
2560 		{
2561 			if (!(target->player->pflags & PF_FINISHED))
2562 				target->player->lives -= 1; // Lose a life Tails 03-11-2000
2563 
2564 			if (target->player->lives <= 0) // Tails 03-14-2000
2565 			{
2566 				boolean gameovermus = false;
2567 				if ((netgame || multiplayer) && G_GametypeUsesCoopLives() && (cv_cooplives.value != 1))
2568 				{
2569 					INT32 i;
2570 					for (i = 0; i < MAXPLAYERS; i++)
2571 					{
2572 						if (!playeringame[i])
2573 							continue;
2574 
2575 						if (players[i].lives > 0)
2576 							break;
2577 					}
2578 					if (i == MAXPLAYERS)
2579 						gameovermus = true;
2580 				}
2581 				else if (P_IsLocalPlayer(target->player))
2582 					gameovermus = true;
2583 
2584 				if (gameovermus) // Yousa dead now, Okieday? Tails 03-14-2000
2585 					S_ChangeMusicEx("_gover", 0, 0, 0, (2*MUSICRATE) - (MUSICRATE/25), 0); // 1.96 seconds
2586 					//P_PlayJingle(target->player, JT_GOVER); // can't be used because incompatible with track fadeout
2587 
2588 				if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) && numgameovers < maxgameovers)
2589 				{
2590 					numgameovers++;
2591 					if ((!modifiedgame || savemoddata) && cursaveslot > 0)
2592 						G_SaveGameOver((UINT32)cursaveslot, (target->player->continues <= 0));
2593 				}
2594 			}
2595 		}
2596 		target->player->playerstate = PST_DEAD;
2597 
2598 		if (target->player == &players[consoleplayer])
2599 		{
2600 			// don't die in auto map,
2601 			// switch view prior to dying
2602 			if (automapactive)
2603 				AM_Stop();
2604 
2605 			//added : 22-02-98: recenter view for next life...
2606 			localaiming = 0;
2607 		}
2608 		if (target->player == &players[secondarydisplayplayer])
2609 		{
2610 			// added : 22-02-98: recenter view for next life...
2611 			localaiming2 = 0;
2612 		}
2613 
2614 		//tag deaths handled differently in suicide cases. Don't count spectators!
2615 		if (G_TagGametype()
2616 		 && !(target->player->pflags & PF_TAGIT) && (!source || !source->player) && !(target->player->spectator))
2617 		{
2618 			// if you accidentally die before you run out of time to hide, ignore it.
2619 			// allow them to try again, rather than sitting the whole thing out.
2620 			if (leveltime >= hidetime * TICRATE)
2621 			{
2622 				if (!(gametyperules & GTR_HIDEFROZEN))//suiciding in survivor makes you IT.
2623 				{
2624 					target->player->pflags |= PF_TAGIT;
2625 					CONS_Printf(M_GetText("%s is now IT!\n"), player_names[target->player-players]); // Tell everyone who is it!
2626 					P_CheckSurvivors();
2627 				}
2628 				else
2629 				{
2630 					if (!(target->player->pflags & PF_GAMETYPEOVER))
2631 					{
2632 						//otherwise, increment the tagger's score.
2633 						//in hide and seek, suiciding players are counted as found.
2634 						INT32 w;
2635 
2636 						for (w=0; w < MAXPLAYERS; w++)
2637 						{
2638 							if (players[w].pflags & PF_TAGIT)
2639 								P_AddPlayerScore(&players[w], 100);
2640 						}
2641 
2642 						target->player->pflags |= PF_GAMETYPEOVER;
2643 						CONS_Printf(M_GetText("%s was found!\n"), player_names[target->player-players]);
2644 						P_CheckSurvivors();
2645 					}
2646 				}
2647 			}
2648 		}
2649 	}
2650 
2651 	if (source && target && target->player && source->player)
2652 		P_PlayVictorySound(source); // Killer laughs at you. LAUGHS! BWAHAHAHA!
2653 
2654 	// Other death animation effects
2655 	switch(target->type)
2656 	{
2657 		case MT_BOUNCEPICKUP:
2658 		case MT_RAILPICKUP:
2659 		case MT_AUTOPICKUP:
2660 		case MT_EXPLODEPICKUP:
2661 		case MT_SCATTERPICKUP:
2662 		case MT_GRENADEPICKUP:
2663 			P_SetObjectMomZ(target, FRACUNIT, false);
2664 			target->fuse = target->info->damage;
2665 			break;
2666 
2667 		case MT_BUGGLE:
2668 			if (inflictor && inflictor->player // did a player kill you? Spawn relative to the player so they're bound to get it
2669 			&& P_AproxDistance(inflictor->x - target->x, inflictor->y - target->y) <= inflictor->radius + target->radius + FixedMul(8*FRACUNIT, inflictor->scale) // close enough?
2670 			&& inflictor->z <= target->z + target->height + FixedMul(8*FRACUNIT, inflictor->scale)
2671 			&& inflictor->z + inflictor->height >= target->z - FixedMul(8*FRACUNIT, inflictor->scale))
2672 				mo = P_SpawnMobj(inflictor->x + inflictor->momx, inflictor->y + inflictor->momy, inflictor->z + (inflictor->height / 2) + inflictor->momz, MT_EXTRALARGEBUBBLE);
2673 			else
2674 				mo = P_SpawnMobj(target->x, target->y, target->z, MT_EXTRALARGEBUBBLE);
2675 			mo->destscale = target->scale;
2676 			P_SetScale(mo, mo->destscale);
2677 			P_SetMobjState(mo, mo->info->raisestate);
2678 			break;
2679 
2680 		case MT_YELLOWSHELL:
2681 			P_SpawnMobjFromMobj(target, 0, 0, 0, MT_YELLOWSPRING);
2682 			break;
2683 
2684 		case MT_CRAWLACOMMANDER:
2685 			target->momx = target->momy = target->momz = 0;
2686 			break;
2687 
2688 		case MT_CRUSHSTACEAN:
2689 			if (target->tracer)
2690 			{
2691 				mobj_t *chain = target->tracer->target, *chainnext;
2692 				while (chain)
2693 				{
2694 					chainnext = chain->target;
2695 					P_RemoveMobj(chain);
2696 					chain = chainnext;
2697 				}
2698 				S_StopSound(target->tracer);
2699 				P_KillMobj(target->tracer, inflictor, source, damagetype);
2700 			}
2701 			break;
2702 
2703 		case MT_BANPYURA:
2704 			if (target->tracer)
2705 			{
2706 				S_StopSound(target->tracer);
2707 				P_KillMobj(target->tracer, inflictor, source, damagetype);
2708 			}
2709 			break;
2710 
2711 		case MT_EGGSHIELD:
2712 			P_SetObjectMomZ(target, 4*target->scale, false);
2713 			P_InstaThrust(target, target->angle, 3*target->scale);
2714 			target->flags = (target->flags|MF_NOCLIPHEIGHT) & ~MF_NOGRAVITY;
2715 			break;
2716 
2717 		case MT_DRAGONBOMBER:
2718 			{
2719 				mobj_t *segment = target;
2720 				while (segment->tracer != NULL)
2721 				{
2722 					P_KillMobj(segment->tracer, NULL, NULL, 0);
2723 					segment = segment->tracer;
2724 				}
2725 				break;
2726 			}
2727 
2728 		case MT_EGGMOBILE3:
2729 			{
2730 				mobj_t *mo2;
2731 				thinker_t *th;
2732 				UINT32 i = 0; // to check how many clones we've removed
2733 
2734 				// scan the thinkers to make sure all the old pinch dummies are gone on death
2735 				for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
2736 				{
2737 					if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
2738 						continue;
2739 
2740 					mo = (mobj_t *)th;
2741 					if (mo->type != (mobjtype_t)target->info->mass)
2742 						continue;
2743 					if (mo->tracer != target)
2744 						continue;
2745 
2746 					P_KillMobj(mo, inflictor, source, damagetype);
2747 					mo->destscale = mo->scale/8;
2748 					mo->scalespeed = (mo->scale - mo->destscale)/(2*TICRATE);
2749 					mo->momz = mo->info->speed;
2750 					mo->angle = FixedAngle((P_RandomKey(36)*10)<<FRACBITS);
2751 
2752 					mo2 = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_BOSSJUNK);
2753 					mo2->angle = mo->angle;
2754 					P_SetMobjState(mo2, S_BOSSSEBH2);
2755 
2756 					if (++i == 2) // we've already removed 2 of these, let's stop now
2757 						break;
2758 					else
2759 						S_StartSound(mo, mo->info->deathsound); // done once to prevent sound stacking
2760 				}
2761 			}
2762 			break;
2763 
2764 		case MT_BIGMINE:
2765 			if (inflictor)
2766 			{
2767 				fixed_t dx = target->x - inflictor->x, dy = target->y - inflictor->y, dz = target->z - inflictor->z;
2768 				fixed_t dm = FixedHypot(dz, FixedHypot(dy, dx));
2769 				target->momx = FixedDiv(FixedDiv(dx, dm), dm)*512;
2770 				target->momy = FixedDiv(FixedDiv(dy, dm), dm)*512;
2771 				target->momz = FixedDiv(FixedDiv(dz, dm), dm)*512;
2772 			}
2773 			if (source)
2774 				P_SetTarget(&target->tracer, source);
2775 			break;
2776 
2777 		case MT_BLASTEXECUTOR:
2778 			if (target->spawnpoint)
2779 				P_LinedefExecute(target->spawnpoint->angle, (source ? source : inflictor), target->subsector->sector);
2780 			break;
2781 
2782 		case MT_SPINBOBERT:
2783 			if (target->hnext)
2784 				P_KillMobj(target->hnext, inflictor, source, damagetype);
2785 			if (target->hprev)
2786 				P_KillMobj(target->hprev, inflictor, source, damagetype);
2787 			break;
2788 
2789 		case MT_EGGTRAP:
2790 			// Time for birdies! Yaaaaaaaay!
2791 			target->fuse = TICRATE;
2792 			break;
2793 
2794 		case MT_MINECART:
2795 			A_Scream(target);
2796 			target->momx = target->momy = target->momz = 0;
2797 			if (target->target && target->target->health)
2798 				P_KillMobj(target->target, target, source, 0);
2799 			break;
2800 
2801 		case MT_PLAYER:
2802 			{
2803 				target->fuse = TICRATE*3; // timer before mobj disappears from view (even if not an actual player)
2804 				target->momx = target->momy = target->momz = 0;
2805 
2806 				if (damagetype == DMG_DROWNED) // drowned
2807 				{
2808 					target->movedir = damagetype; // we're MOVING the Damage Into anotheR function... Okay, this is a bit of a hack.
2809 					if (target->player->charflags & SF_MACHINE)
2810 						S_StartSound(target, sfx_fizzle);
2811 					else
2812 						S_StartSound(target, sfx_drown);
2813 					// Don't jump up when drowning
2814 				}
2815 				else
2816 				{
2817 					P_SetObjectMomZ(target, 14*FRACUNIT, false);
2818 					if (damagetype == DMG_SPIKE) // Spikes
2819 						S_StartSound(target, sfx_spkdth);
2820 					else
2821 						P_PlayDeathSound(target);
2822 				}
2823 			}
2824 			break;
2825 		case MT_METALSONIC_RACE:
2826 			target->fuse = TICRATE*3;
2827 			target->momx = target->momy = target->momz = 0;
2828 			P_SetObjectMomZ(target, 14*FRACUNIT, false);
2829 			target->flags = (target->flags & ~MF_NOGRAVITY)|(MF_NOCLIP|MF_NOCLIPTHING);
2830 			break;
2831 		default:
2832 			break;
2833 	}
2834 
2835 	// Final state setting - do something instead of P_SetMobjState;
2836 	if (target->type == MT_SPIKE && target->info->deathstate != S_NULL)
2837 	{
2838 		const angle_t ang = ((inflictor) ? inflictor->angle : 0) + ANGLE_90;
2839 		const fixed_t scale = target->scale;
2840 		const fixed_t xoffs = P_ReturnThrustX(target, ang, 8*scale), yoffs = P_ReturnThrustY(target, ang, 8*scale);
2841 		const UINT16 flip = (target->eflags & MFE_VERTICALFLIP);
2842 		mobj_t *chunk;
2843 		fixed_t momz;
2844 
2845 		S_StartSound(target, target->info->deathsound);
2846 
2847 		if (target->info->xdeathstate != S_NULL)
2848 		{
2849 			momz = 6*scale;
2850 			if (flip)
2851 				momz *= -1;
2852 #define makechunk(angtweak, xmov, ymov) \
2853 			chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_SPIKE);\
2854 			P_SetMobjState(chunk, target->info->xdeathstate);\
2855 			chunk->health = 0;\
2856 			chunk->angle = angtweak;\
2857 			P_UnsetThingPosition(chunk);\
2858 			chunk->flags = MF_NOCLIP;\
2859 			chunk->x += xmov;\
2860 			chunk->y += ymov;\
2861 			P_SetThingPosition(chunk);\
2862 			P_InstaThrust(chunk,chunk->angle, 4*scale);\
2863 			chunk->momz = momz
2864 
2865 			makechunk(ang + ANGLE_180, -xoffs, -yoffs);
2866 			makechunk(ang, xoffs, yoffs);
2867 
2868 #undef makechunk
2869 		}
2870 
2871 		momz = 7*scale;
2872 		if (flip)
2873 			momz *= -1;
2874 
2875 		chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_SPIKE);
2876 		P_SetMobjState(chunk, target->info->deathstate);
2877 		chunk->health = 0;
2878 		chunk->angle = ang + ANGLE_180;
2879 		P_UnsetThingPosition(chunk);
2880 		chunk->flags = MF_NOCLIP;
2881 		chunk->x -= xoffs;
2882 		chunk->y -= yoffs;
2883 		if (flip)
2884 			chunk->z -= 12*scale;
2885 		else
2886 			chunk->z += 12*scale;
2887 		P_SetThingPosition(chunk);
2888 		P_InstaThrust(chunk, chunk->angle, 2*scale);
2889 		chunk->momz = momz;
2890 
2891 		P_SetMobjState(target, target->info->deathstate);
2892 		target->health = 0;
2893 		target->angle = ang;
2894 		P_UnsetThingPosition(target);
2895 		target->flags = MF_NOCLIP;
2896 		target->x += xoffs;
2897 		target->y += yoffs;
2898 		target->z = chunk->z;
2899 		P_SetThingPosition(target);
2900 		P_InstaThrust(target, target->angle, 2*scale);
2901 		target->momz = momz;
2902 	}
2903 	else if (target->type == MT_WALLSPIKE && target->info->deathstate != S_NULL)
2904 	{
2905 		const angle_t ang = (/*(inflictor) ? inflictor->angle : */target->angle) + ANGLE_90;
2906 		const fixed_t scale = target->scale;
2907 		const fixed_t xoffs = P_ReturnThrustX(target, ang, 8*scale), yoffs = P_ReturnThrustY(target, ang, 8*scale), forwardxoffs = P_ReturnThrustX(target, target->angle, 7*scale), forwardyoffs = P_ReturnThrustY(target, target->angle, 7*scale);
2908 		const UINT16 flip = (target->eflags & MFE_VERTICALFLIP);
2909 		mobj_t *chunk;
2910 		boolean sprflip;
2911 
2912 		S_StartSound(target, target->info->deathsound);
2913 		if (!P_MobjWasRemoved(target->tracer))
2914 			P_RemoveMobj(target->tracer);
2915 
2916 		if (target->info->xdeathstate != S_NULL)
2917 		{
2918 			sprflip = P_RandomChance(FRACUNIT/2);
2919 
2920 #define makechunk(angtweak, xmov, ymov) \
2921 			chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_WALLSPIKE);\
2922 			P_SetMobjState(chunk, target->info->xdeathstate);\
2923 			chunk->health = 0;\
2924 			chunk->angle = target->angle;\
2925 			P_UnsetThingPosition(chunk);\
2926 			chunk->flags = MF_NOCLIP;\
2927 			chunk->x += xmov - forwardxoffs;\
2928 			chunk->y += ymov - forwardyoffs;\
2929 			P_SetThingPosition(chunk);\
2930 			P_InstaThrust(chunk, angtweak, 4*scale);\
2931 			chunk->momz = P_RandomRange(5, 7)*scale;\
2932 			if (flip)\
2933 				chunk->momz *= -1;\
2934 			if (sprflip)\
2935 				chunk->frame |= FF_VERTICALFLIP
2936 
2937 			makechunk(ang + ANGLE_180, -xoffs, -yoffs);
2938 			sprflip = !sprflip;
2939 			makechunk(ang, xoffs, yoffs);
2940 
2941 #undef makechunk
2942 		}
2943 
2944 		sprflip = P_RandomChance(FRACUNIT/2);
2945 
2946 		chunk = P_SpawnMobjFromMobj(target, 0, 0, 0, MT_WALLSPIKE);
2947 
2948 		P_SetMobjState(chunk, target->info->deathstate);
2949 		chunk->health = 0;
2950 		chunk->angle = target->angle;
2951 		P_UnsetThingPosition(chunk);
2952 		chunk->flags = MF_NOCLIP;
2953 		chunk->x += forwardxoffs - xoffs;
2954 		chunk->y += forwardyoffs - yoffs;
2955 		P_SetThingPosition(chunk);
2956 		P_InstaThrust(chunk, ang + ANGLE_180, 2*scale);
2957 		chunk->momz = P_RandomRange(5, 7)*scale;
2958 		if (flip)
2959 			chunk->momz *= -1;
2960 		if (sprflip)
2961 			chunk->frame |= FF_VERTICALFLIP;
2962 
2963 		P_SetMobjState(target, target->info->deathstate);
2964 		target->health = 0;
2965 		P_UnsetThingPosition(target);
2966 		target->flags = MF_NOCLIP;
2967 		target->x += forwardxoffs + xoffs;
2968 		target->y += forwardyoffs + yoffs;
2969 		P_SetThingPosition(target);
2970 		P_InstaThrust(target, ang, 2*scale);
2971 		target->momz = P_RandomRange(5, 7)*scale;
2972 		if (flip)
2973 			target->momz *= -1;
2974 		if (!sprflip)
2975 			target->frame |= FF_VERTICALFLIP;
2976 	}
2977 	else if (target->player)
2978 	{
2979 		if (damagetype == DMG_DROWNED || damagetype == DMG_SPACEDROWN)
2980 			P_SetPlayerMobjState(target, target->info->xdeathstate);
2981 		else
2982 			P_SetPlayerMobjState(target, target->info->deathstate);
2983 	}
2984 	else
2985 #ifdef DEBUG_NULL_DEATHSTATE
2986 		P_SetMobjState(target, S_NULL);
2987 #else
2988 		P_SetMobjState(target, target->info->deathstate);
2989 #endif
2990 
2991 	/** \note For player, the above is redundant because of P_SetMobjState (target, S_PLAY_DIE1)
2992 	   in P_DamageMobj()
2993 	   Graue 12-22-2003 */
2994 }
2995 
P_NiGHTSDamage(mobj_t * target,mobj_t * source)2996 static void P_NiGHTSDamage(mobj_t *target, mobj_t *source)
2997 {
2998 	player_t *player = target->player;
2999 	tic_t oldnightstime = player->nightstime;
3000 
3001 	(void)source; // unused
3002 
3003 	if (!player->powers[pw_flashing])
3004 	{
3005 		angle_t fa;
3006 
3007 		player->angle_pos = player->old_angle_pos;
3008 		player->speed /= 5;
3009 		player->flyangle += 180; // Shuffle's BETTERNIGHTSMOVEMENT?
3010 		player->flyangle %= 360;
3011 
3012 		if (gametyperules & GTR_RACE)
3013 			player->drillmeter -= 5*20;
3014 		else
3015 		{
3016 			if (player->nightstime > 5*TICRATE)
3017 				player->nightstime -= 5*TICRATE;
3018 			else
3019 				player->nightstime = 1;
3020 		}
3021 
3022 		if (player->pflags & PF_TRANSFERTOCLOSEST)
3023 		{
3024 			target->momx = -target->momx;
3025 			target->momy = -target->momy;
3026 		}
3027 		else
3028 		{
3029 			fa = player->old_angle_pos>>ANGLETOFINESHIFT;
3030 
3031 			target->momx = FixedMul(FINECOSINE(fa),target->target->radius);
3032 			target->momy = FixedMul(FINESINE(fa),target->target->radius);
3033 		}
3034 
3035 		player->powers[pw_flashing] = flashingtics;
3036 		P_SetPlayerMobjState(target, S_PLAY_NIGHTS_STUN);
3037 		S_StartSound(target, sfx_nghurt);
3038 
3039 		player->mo->rollangle = 0;
3040 
3041 		if (oldnightstime > 10*TICRATE
3042 			&& player->nightstime < 10*TICRATE)
3043 		{
3044 			if ((mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
3045 #ifdef _WIN32
3046 				// win32 MIDI volume hack means we cannot fade down the music
3047 				&& S_MusicType() != MU_MID
3048 #endif
3049 			)
3050 			{
3051 				S_FadeMusic(0, 10*MUSICRATE);
3052 				S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS.
3053 			}
3054 			else
3055 				P_PlayJingle(player, ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap)) ? JT_NIGHTSTIMEOUT : JT_SSTIMEOUT);
3056 		}
3057 	}
3058 }
3059 
P_TagDamage(mobj_t * target,mobj_t * inflictor,mobj_t * source,INT32 damage,UINT8 damagetype)3060 static boolean P_TagDamage(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
3061 {
3062 	player_t *player = target->player;
3063 	(void)damage; //unused parm
3064 
3065 	// If flashing or invulnerable, ignore the tag,
3066 	if (player->powers[pw_flashing] || player->powers[pw_invulnerability])
3067 		return false;
3068 
3069 	// Don't allow any damage before the round starts.
3070 	if (leveltime <= hidetime * TICRATE)
3071 		return false;
3072 
3073 	// Ignore IT players shooting each other, unless friendlyfire is on.
3074 	if ((player->pflags & PF_TAGIT && !((cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE) || (damagetype & DMG_CANHURTSELF)) &&
3075 		source && source->player && source->player->pflags & PF_TAGIT)))
3076 	{
3077 		if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
3078 		{
3079 			if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
3080 			{
3081 				P_SwitchShield(player, SH_PINK);
3082 				S_StartSound(target, mobjinfo[MT_PITY_ICON].seesound);
3083 			}
3084 		}
3085 		return false;
3086 	}
3087 
3088 	// Don't allow players on the same team to hurt one another,
3089 	// unless cv_friendlyfire is on.
3090 	if (!(cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE) || (damagetype & DMG_CANHURTSELF)) && (player->pflags & PF_TAGIT) == (source->player->pflags & PF_TAGIT))
3091 	{
3092 		if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
3093 		{
3094 			if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
3095 			{
3096 				P_SwitchShield(player, SH_PINK);
3097 				S_StartSound(target, mobjinfo[MT_PITY_ICON].seesound);
3098 			}
3099 		}
3100 		else if (!(inflictor->flags & MF_FIRE))
3101 			P_GivePlayerRings(player, 1);
3102 		if (inflictor->flags2 & MF2_BOUNCERING)
3103 			inflictor->fuse = 0; // bounce ring disappears at -1 not 0
3104 		return false;
3105 	}
3106 
3107 	if (inflictor->type == MT_LHRT)
3108 		return false;
3109 
3110 	// The tag occurs so long as you aren't shooting another tagger with friendlyfire on.
3111 	if (source->player->pflags & PF_TAGIT && !(player->pflags & PF_TAGIT))
3112 	{
3113 		P_AddPlayerScore(source->player, 100); //award points to tagger.
3114 		P_HitDeathMessages(player, inflictor, source, 0);
3115 
3116 		if (!(gametyperules & GTR_HIDEFROZEN)) //survivor
3117 		{
3118 			player->pflags |= PF_TAGIT; //in survivor, the player becomes IT and helps hunt down the survivors.
3119 			CONS_Printf(M_GetText("%s is now IT!\n"), player_names[player-players]); // Tell everyone who is it!
3120 		}
3121 		else
3122 		{
3123 			player->pflags |= PF_GAMETYPEOVER; //in hide and seek, the player is tagged and stays stationary.
3124 			CONS_Printf(M_GetText("%s was found!\n"), player_names[player-players]); // Tell everyone who is it!
3125 		}
3126 
3127 		//checks if tagger has tagged all players, if so, end round early.
3128 		P_CheckSurvivors();
3129 	}
3130 
3131 	P_DoPlayerPain(player, source, inflictor);
3132 
3133 	// Check for a shield
3134 	if (player->powers[pw_shield])
3135 	{
3136 		P_RemoveShield(player);
3137 		S_StartSound(target, sfx_shldls);
3138 		return true;
3139 	}
3140 
3141 	if (player->powers[pw_carry] == CR_NIGHTSFALL)
3142 	{
3143 		if (player->spheres > 0)
3144 		{
3145 			P_PlayRinglossSound(target);
3146 			P_PlayerRingBurst(player, player->spheres);
3147 			player->spheres = 0;
3148 		}
3149 	}
3150 	else if (player->rings > 0) // Ring loss
3151 	{
3152 		P_PlayRinglossSound(target);
3153 		P_PlayerRingBurst(player, player->rings);
3154 		player->rings = 0;
3155 	}
3156 	else // Death
3157 	{
3158 		P_PlayDeathSound(target);
3159 		P_PlayVictorySound(source); // Killer laughs at you! LAUGHS! BWAHAHAHHAHAA!!
3160 	}
3161 	return true;
3162 }
3163 
P_PlayerHitsPlayer(mobj_t * target,mobj_t * inflictor,mobj_t * source,INT32 damage,UINT8 damagetype)3164 static boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
3165 {
3166 	player_t *player = target->player;
3167 
3168 	if (!(damagetype & DMG_CANHURTSELF))
3169 	{
3170 		// You can't kill yourself, idiot...
3171 		if (source == target)
3172 			return false;
3173 
3174 		// In COOP/RACE, you can't hurt other players unless cv_friendlyfire is on
3175 		if (!(cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE)) && (gametyperules & GTR_FRIENDLY))
3176 		{
3177 			if ((gametyperules & GTR_FRIENDLY) && inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK)) // co-op only
3178 			{
3179 				if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
3180 				{
3181 					P_SwitchShield(player, SH_PINK);
3182 					S_StartSound(target, mobjinfo[MT_PITY_ICON].seesound);
3183 				}
3184 			}
3185 			return false;
3186 		}
3187 	}
3188 
3189 	// Tag handling
3190 	if (G_TagGametype())
3191 		return P_TagDamage(target, inflictor, source, damage, damagetype);
3192 	else if (damagetype & DMG_CANHURTSELF)
3193 		return true;
3194 	else if (G_GametypeHasTeams()) // CTF + Team Match
3195 	{
3196 		// Don't allow players on the same team to hurt one another,
3197 		// unless cv_friendlyfire is on.
3198 		if (!(cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE)) && target->player->ctfteam == source->player->ctfteam)
3199 		{
3200 			if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
3201 			{
3202 				if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
3203 				{
3204 					P_SwitchShield(player, SH_PINK);
3205 					S_StartSound(target, mobjinfo[MT_PITY_ICON].seesound);
3206 				}
3207 			}
3208 			else if (!(inflictor->flags & MF_FIRE))
3209 				P_GivePlayerRings(target->player, 1);
3210 			if (inflictor->flags2 & MF2_BOUNCERING)
3211 				inflictor->fuse = 0; // bounce ring disappears at -1 not 0
3212 
3213 			return false;
3214 		}
3215 	}
3216 
3217 	if (inflictor->type == MT_LHRT)
3218 		return false;
3219 
3220 	// Add pity.
3221 	if (!player->powers[pw_flashing] && !player->powers[pw_invulnerability] && !player->powers[pw_super]
3222 	&& source->player->score > player->score)
3223 		player->pity++;
3224 
3225 	return true;
3226 }
3227 
P_KillPlayer(player_t * player,mobj_t * source,INT32 damage)3228 static void P_KillPlayer(player_t *player, mobj_t *source, INT32 damage)
3229 {
3230 	player->pflags &= ~PF_SLIDING;
3231 
3232 	player->powers[pw_carry] = CR_NONE;
3233 
3234 	// Burst weapons and emeralds in Match/CTF only
3235 	if (source)
3236 	{
3237 		if ((gametyperules & GTR_RINGSLINGER) && !(gametyperules & GTR_TAG))
3238 			P_PlayerRingBurst(player, player->rings);
3239 		if (gametyperules & GTR_POWERSTONES)
3240 			P_PlayerEmeraldBurst(player, false);
3241 	}
3242 
3243 	// Get rid of shield
3244 	player->powers[pw_shield] = SH_NONE;
3245 	player->mo->color = player->skincolor;
3246 
3247 	// Get rid of emeralds
3248 	player->powers[pw_emeralds] = 0;
3249 
3250 	P_ForceFeed(player, 40, 10, TICRATE, 40 + min(damage, 100)*2);
3251 
3252 	P_ResetPlayer(player);
3253 
3254 	if (!player->spectator)
3255 		player->mo->flags2 &= ~MF2_DONTDRAW;
3256 
3257 	P_SetPlayerMobjState(player->mo, player->mo->info->deathstate);
3258 	if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
3259 	{
3260 		P_PlayerFlagBurst(player, false);
3261 		if (source && source->player)
3262 		{
3263 			// Award no points when players shoot each other when cv_friendlyfire is on.
3264 			if (!G_GametypeHasTeams() || !(source->player->ctfteam == player->ctfteam && source != player->mo))
3265 				P_AddPlayerScore(source->player, 25);
3266 		}
3267 	}
3268 	if (source && source->player && !player->powers[pw_super]) //don't score points against super players
3269 	{
3270 		// Award no points when players shoot each other when cv_friendlyfire is on.
3271 		if (!G_GametypeHasTeams() || !(source->player->ctfteam == player->ctfteam && source != player->mo))
3272 			P_AddPlayerScore(source->player, 100);
3273 	}
3274 
3275 	// If the player was super, tell them he/she ain't so super nomore.
3276 	if (!G_CoopGametype() && player->powers[pw_super])
3277 	{
3278 		S_StartSound(NULL, sfx_s3k66); //let all players hear it.
3279 		HU_SetCEchoFlags(0);
3280 		HU_SetCEchoDuration(5);
3281 		HU_DoCEcho(va("%s\\is no longer super.\\\\\\\\", player_names[player-players]));
3282 	}
3283 }
3284 
P_SuperDamage(player_t * player,mobj_t * inflictor,mobj_t * source,INT32 damage)3285 static void P_SuperDamage(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 damage)
3286 {
3287 	fixed_t fallbackspeed;
3288 	angle_t ang;
3289 
3290 	P_ForceFeed(player, 40, 10, TICRATE, 40 + min(damage, 100)*2);
3291 
3292 	if (player->mo->eflags & MFE_VERTICALFLIP)
3293 		player->mo->z--;
3294 	else
3295 		player->mo->z++;
3296 
3297 	if (player->mo->eflags & MFE_UNDERWATER)
3298 		P_SetObjectMomZ(player->mo, FixedDiv(10511*FRACUNIT,2600*FRACUNIT), false);
3299 	else
3300 		P_SetObjectMomZ(player->mo, FixedDiv(69*FRACUNIT,10*FRACUNIT), false);
3301 
3302 	ang = R_PointToAngle2(inflictor->x,	inflictor->y, player->mo->x, player->mo->y);
3303 
3304 	// explosion and rail rings send you farther back, making it more difficult
3305 	// to recover
3306 	if (inflictor->flags2 & MF2_SCATTER && source)
3307 	{
3308 		fixed_t dist = P_AproxDistance(P_AproxDistance(source->x-player->mo->x, source->y-player->mo->y), source->z-player->mo->z);
3309 
3310 		dist = FixedMul(128*FRACUNIT, inflictor->scale) - dist/4;
3311 
3312 		if (dist < FixedMul(4*FRACUNIT, inflictor->scale))
3313 			dist = FixedMul(4*FRACUNIT, inflictor->scale);
3314 
3315 		fallbackspeed = dist;
3316 	}
3317 	else if (inflictor->flags2 & MF2_EXPLOSION)
3318 	{
3319 		if (inflictor->flags2 & MF2_RAILRING)
3320 			fallbackspeed = FixedMul(28*FRACUNIT, inflictor->scale); // 7x
3321 		else
3322 			fallbackspeed = FixedMul(20*FRACUNIT, inflictor->scale); // 5x
3323 	}
3324 	else if (inflictor->flags2 & MF2_RAILRING)
3325 		fallbackspeed = FixedMul(16*FRACUNIT, inflictor->scale); // 4x
3326 	else
3327 		fallbackspeed = FixedMul(4*FRACUNIT, inflictor->scale); // the usual amount of force
3328 
3329 	P_InstaThrust(player->mo, ang, fallbackspeed);
3330 
3331 	P_SetPlayerMobjState(player->mo, S_PLAY_STUN);
3332 
3333 	P_ResetPlayer(player);
3334 
3335 	if (player->timeshit != UINT8_MAX)
3336 		++player->timeshit;
3337 }
3338 
P_RemoveShield(player_t * player)3339 void P_RemoveShield(player_t *player)
3340 {
3341 	if (player->powers[pw_shield] & SH_FORCE)
3342 	{ // Multi-hit
3343 		if (player->powers[pw_shield] & SH_FORCEHP)
3344 			player->powers[pw_shield]--;
3345 		else
3346 			player->powers[pw_shield] &= SH_STACK;
3347 	}
3348 	else if (player->powers[pw_shield] & SH_NOSTACK)
3349 	{ // First layer shields
3350 		if ((player->powers[pw_shield] & SH_NOSTACK) == SH_ARMAGEDDON) // Give them what's coming to them!
3351 		{
3352 			P_BlackOw(player); // BAM!
3353 			player->pflags |= PF_JUMPDOWN;
3354 		}
3355 		else
3356 			player->powers[pw_shield] &= SH_STACK;
3357 	}
3358 	else
3359 	{ // Second layer shields
3360 		if (((player->powers[pw_shield] & SH_STACK) == SH_FIREFLOWER) && !(player->powers[pw_super] || (mariomode && player->powers[pw_invulnerability])))
3361 		{
3362 			player->mo->color = player->skincolor;
3363 			G_GhostAddColor(GHC_NORMAL);
3364 		}
3365 		player->powers[pw_shield] = SH_NONE;
3366 	}
3367 }
3368 
P_ShieldDamage(player_t * player,mobj_t * inflictor,mobj_t * source,INT32 damage,UINT8 damagetype)3369 static void P_ShieldDamage(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
3370 {
3371 	// Must do pain first to set flashing -- P_RemoveShield can cause damage
3372 	P_DoPlayerPain(player, source, inflictor);
3373 
3374 	P_RemoveShield(player);
3375 
3376 	P_ForceFeed(player, 40, 10, TICRATE, 40 + min(damage, 100)*2);
3377 
3378 	if (damagetype == DMG_SPIKE) // spikes
3379 		S_StartSound(player->mo, sfx_spkdth);
3380 	else
3381 		S_StartSound (player->mo, sfx_shldls); // Ba-Dum! Shield loss.
3382 
3383 	if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
3384 	{
3385 		P_PlayerFlagBurst(player, false);
3386 		if (source && source->player)
3387 		{
3388 			// Award no points when players shoot each other when cv_friendlyfire is on.
3389 			if (!G_GametypeHasTeams() || !(source->player->ctfteam == player->ctfteam && source != player->mo))
3390 				P_AddPlayerScore(source->player, 25);
3391 		}
3392 	}
3393 	if (source && source->player && !player->powers[pw_super]) //don't score points against super players
3394 	{
3395 		// Award no points when players shoot each other when cv_friendlyfire is on.
3396 		if (!G_GametypeHasTeams() || !(source->player->ctfteam == player->ctfteam && source != player->mo))
3397 			P_AddPlayerScore(source->player, 50);
3398 	}
3399 }
3400 
P_RingDamage(player_t * player,mobj_t * inflictor,mobj_t * source,INT32 damage,UINT8 damagetype,boolean dospheres)3401 static void P_RingDamage(player_t *player, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype, boolean dospheres)
3402 {
3403 	P_DoPlayerPain(player, source, inflictor);
3404 
3405 	P_ForceFeed(player, 40, 10, TICRATE, 40 + min(damage, 100)*2);
3406 
3407 	if (damagetype == DMG_SPIKE) // spikes
3408 		S_StartSound(player->mo, sfx_spkdth);
3409 
3410 	if (source && source->player && !player->powers[pw_super]) //don't score points against super players
3411 	{
3412 		// Award no points when players shoot each other when cv_friendlyfire is on.
3413 		if (!G_GametypeHasTeams() || !(source->player->ctfteam == player->ctfteam && source != player->mo))
3414 			P_AddPlayerScore(source->player, 50);
3415 	}
3416 
3417 	if ((gametyperules & GTR_TEAMFLAGS) && (player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
3418 	{
3419 		P_PlayerFlagBurst(player, false);
3420 		if (source && source->player)
3421 		{
3422 			// Award no points when players shoot each other when cv_friendlyfire is on.
3423 			if (!G_GametypeHasTeams() || !(source->player->ctfteam == player->ctfteam && source != player->mo))
3424 				P_AddPlayerScore(source->player, 25);
3425 		}
3426 	}
3427 
3428 	// Ring loss sound plays despite hitting spikes
3429 	P_PlayRinglossSound(player->mo); // Ringledingle!
3430 	P_PlayerRingBurst(player, damage);
3431 
3432 	if (dospheres)
3433 	{
3434 		player->spheres -= damage;
3435 		if (player->spheres < 0)
3436 			player->spheres = 0;
3437 	}
3438 	else
3439 	{
3440 		player->rings -= damage;
3441 		if (player->rings < 0)
3442 			player->rings = 0;
3443 	}
3444 }
3445 
3446 //
3447 // P_SpecialStageDamage
3448 //
3449 // Do old special stage-style damaging
3450 // Removes 5 seconds from the player, or knocks off their shield if they have one.
3451 // If they don't have anything, just knock the player back anyway (this doesn't kill them).
3452 //
P_SpecialStageDamage(player_t * player,mobj_t * inflictor,mobj_t * source)3453 void P_SpecialStageDamage(player_t *player, mobj_t *inflictor, mobj_t *source)
3454 {
3455 	tic_t oldnightstime = player->nightstime;
3456 
3457 	if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super])
3458 		return;
3459 
3460 	if (!cv_friendlyfire.value && source && source->player)
3461 	{
3462 		if (inflictor->type == MT_LHRT && !(player->powers[pw_shield] & SH_NOSTACK))
3463 		{
3464 			if (player->revitem != MT_LHRT && player->spinitem != MT_LHRT && player->thokitem != MT_LHRT) // Healers do not get to heal other healers.
3465 			{
3466 				P_SwitchShield(player, SH_PINK);
3467 				S_StartSound(player->mo, mobjinfo[MT_PITY_ICON].seesound);
3468 			}
3469 		}
3470 
3471 		if (source->player->ctfteam == player->ctfteam)
3472 			return;
3473 	}
3474 
3475 	if (inflictor && inflictor->type == MT_LHRT)
3476 		return;
3477 
3478 	if (player->powers[pw_shield] || player->bot)  //If One-Hit Shield
3479 	{
3480 		P_RemoveShield(player);
3481 		S_StartSound(player->mo, sfx_shldls); // Ba-Dum! Shield loss.
3482 	}
3483 	else
3484 	{
3485 		S_StartSound(player->mo, sfx_nghurt);
3486 		if (player->nightstime > 5*TICRATE)
3487 			player->nightstime -= 5*TICRATE;
3488 		else
3489 			player->nightstime = 0;
3490 	}
3491 
3492 	P_DoPlayerPain(player, inflictor, source);
3493 
3494 	if ((gametyperules & GTR_TEAMFLAGS) && player->gotflag & (GF_REDFLAG|GF_BLUEFLAG))
3495 		P_PlayerFlagBurst(player, false);
3496 
3497 	if (oldnightstime > 10*TICRATE
3498 		&& player->nightstime < 10*TICRATE)
3499 	{
3500 		if (mapheaderinfo[gamemap-1]->levelflags & LF_MIXNIGHTSCOUNTDOWN)
3501 		{
3502 			S_FadeMusic(0, 10*MUSICRATE);
3503 			S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS.
3504 		}
3505 		else
3506 			S_ChangeMusicInternal((((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap)) ? "_ntime" : "_drown"), false);
3507 	}
3508 }
3509 
3510 /** Damages an object, which may or may not be a player.
3511   * For melee attacks, source and inflictor are the same.
3512   *
3513   * \param target     The object being damaged.
3514   * \param inflictor  The thing that caused the damage: creature, missile,
3515   *                   gargoyle, and so forth. Can be NULL in the case of
3516   *                   environmental damage, such as slime or crushing.
3517   * \param source     The creature or person responsible. For example, if a
3518   *                   player is hit by a ring, the player who shot it. In some
3519   *                   cases, the target will go after this object after
3520   *                   receiving damage. This can be NULL.
3521   * \param damage     Amount of damage to be dealt.
3522   * \param damagetype Type of damage to be dealt. If bit 7 (0x80) is set, this is an instant-kill.
3523   * \return True if the target sustained damage, otherwise false.
3524   * \todo Clean up this mess, split into multiple functions.
3525   * \sa P_KillMobj
3526   */
P_DamageMobj(mobj_t * target,mobj_t * inflictor,mobj_t * source,INT32 damage,UINT8 damagetype)3527 boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 damage, UINT8 damagetype)
3528 {
3529 	player_t *player;
3530 	boolean force = false;
3531 
3532 	if (objectplacing)
3533 		return false;
3534 
3535 	if (target->health <= 0)
3536 		return false;
3537 
3538 	// Spectator handling
3539 	if (multiplayer)
3540 	{
3541 		if (damagetype != DMG_SPECTATOR && target->player && target->player->spectator)
3542 			return false;
3543 
3544 		if (source && source->player && source->player->spectator)
3545 			return false;
3546 	}
3547 
3548 	// Everything above here can't be forced.
3549 	if (!metalrecording)
3550 	{
3551 		UINT8 shouldForce = LUAh_ShouldDamage(target, inflictor, source, damage, damagetype);
3552 		if (P_MobjWasRemoved(target))
3553 			return (shouldForce == 1); // mobj was removed
3554 		if (shouldForce == 1)
3555 			force = true;
3556 		else if (shouldForce == 2)
3557 			return false;
3558 	}
3559 
3560 	if (!force)
3561 	{
3562 		if (!(target->flags & MF_SHOOTABLE))
3563 			return false; // shouldn't happen...
3564 
3565 		if (target->type == MT_BLACKEGGMAN)
3566 			return false;
3567 
3568 		// Make sure that boxes cannot be popped by enemies, red rings, etc.
3569 		if (target->flags & MF_MONITOR && ((!source || !source->player || source->player->bot)
3570 		|| (inflictor && (inflictor->type == MT_REDRING || (inflictor->type >= MT_THROWNBOUNCE && inflictor->type <= MT_THROWNGRENADE)))))
3571 			return false;
3572 	}
3573 
3574 	if (target->flags2 & MF2_SKULLFLY)
3575 		target->momx = target->momy = target->momz = 0;
3576 
3577 	if (!force)
3578 	{
3579 		// Special case for team ring boxes
3580 		if (target->type == MT_RING_REDBOX && !(source->player->ctfteam == 1))
3581 			return false;
3582 
3583 		if (target->type == MT_RING_BLUEBOX && !(source->player->ctfteam == 2))
3584 			return false;
3585 	}
3586 
3587 	if (target->flags & (MF_ENEMY|MF_BOSS))
3588 	{
3589 		if (!force && target->flags2 & MF2_FRET) // Currently flashing from being hit
3590 			return false;
3591 
3592 		if (LUAh_MobjDamage(target, inflictor, source, damage, damagetype) || P_MobjWasRemoved(target))
3593 			return true;
3594 
3595 		if (target->health > 1)
3596 			target->flags2 |= MF2_FRET;
3597 	}
3598 
3599 	player = target->player;
3600 
3601 	if (player) // Player is the target
3602 	{
3603 		if (!force)
3604 		{
3605 			if (player->exiting)
3606 				return false;
3607 
3608 			if (player->pflags & PF_GODMODE)
3609 				return false;
3610 
3611 			if ((maptol & TOL_NIGHTS) && target->player->powers[pw_carry] != CR_NIGHTSMODE && target->player->powers[pw_carry] != CR_NIGHTSFALL)
3612 				return false;
3613 
3614 			switch (damagetype)
3615 			{
3616 #define DAMAGECASE(type)\
3617 				case DMG_##type:\
3618 					if (player->powers[pw_shield] & SH_PROTECT##type)\
3619 						return false;\
3620 					break
3621 				DAMAGECASE(WATER);
3622 				DAMAGECASE(FIRE);
3623 				DAMAGECASE(ELECTRIC);
3624 				DAMAGECASE(SPIKE);
3625 #undef DAMAGECASE
3626 				default:
3627 					break;
3628 			}
3629 		}
3630 
3631 		if (player->powers[pw_carry] == CR_NIGHTSMODE) // NiGHTS damage handling
3632 		{
3633 			if (!force)
3634 			{
3635 				if (source == target)
3636 					return false; // Don't hit yourself with your own paraloop, baka
3637 				if (source && source->player && !(cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE))
3638 				&& ((gametyperules & GTR_FRIENDLY)
3639 				|| (G_GametypeHasTeams() && player->ctfteam == source->player->ctfteam)))
3640 					return false; // Don't run eachother over in special stages and team games and such
3641 			}
3642 			if (LUAh_MobjDamage(target, inflictor, source, damage, damagetype))
3643 				return true;
3644 			P_NiGHTSDamage(target, source); // -5s :(
3645 			return true;
3646 		}
3647 
3648 		if (G_IsSpecialStage(gamemap) && !(damagetype & DMG_DEATHMASK))
3649 		{
3650 			P_SpecialStageDamage(player, inflictor, source);
3651 			return true;
3652 		}
3653 
3654 		if (!force && inflictor && inflictor->flags & MF_FIRE)
3655 		{
3656 			if (player->powers[pw_shield] & SH_PROTECTFIRE)
3657 				return false; // Invincible to fire objects
3658 
3659 			if (G_PlatformGametype() && inflictor && source && source->player)
3660 				return false; // Don't get hurt by fire generated from friends.
3661 		}
3662 
3663 		// Player hits another player
3664 		if (!force && source && source->player)
3665 		{
3666 			if (!P_PlayerHitsPlayer(target, inflictor, source, damage, damagetype))
3667 				return false;
3668 		}
3669 
3670 		// Instant-Death
3671 		if (damagetype & DMG_DEATHMASK)
3672 			P_KillPlayer(player, source, damage);
3673 		else if (metalrecording)
3674 		{
3675 			if (!inflictor)
3676 				inflictor = source;
3677 			if (inflictor && inflictor->flags & MF_ENEMY)
3678 			{ // Metal Sonic destroy enemy !!
3679 				P_KillMobj(inflictor, NULL, target, damagetype);
3680 				return false;
3681 			}
3682 			else if (inflictor && inflictor->flags & MF_MISSILE)
3683 				return false; // Metal Sonic walk through flame !!
3684 			else if (!player->powers[pw_flashing])
3685 			{ // Oh no! Metal Sonic is hit !!
3686 				P_ShieldDamage(player, inflictor, source, damage, damagetype);
3687 				return true;
3688 			}
3689 			return false;
3690 		}
3691 		else if (player->powers[pw_invulnerability] || player->powers[pw_flashing] || player->powers[pw_super]) // ignore bouncing & such in invulnerability
3692 		{
3693 			if (force
3694 			|| (inflictor && inflictor->flags & MF_MISSILE && inflictor->flags2 & MF2_SUPERFIRE)) // Super Sonic is stunned!
3695 			{
3696 				if (!LUAh_MobjDamage(target, inflictor, source, damage, damagetype))
3697 					P_SuperDamage(player, inflictor, source, damage);
3698 				return true;
3699 			}
3700 			return false;
3701 		}
3702 		else if (LUAh_MobjDamage(target, inflictor, source, damage, damagetype))
3703 			return true;
3704 		else if (player->powers[pw_shield] || (player->bot && !ultimatemode))  //If One-Hit Shield
3705 		{
3706 			P_ShieldDamage(player, inflictor, source, damage, damagetype);
3707 			damage = 0;
3708 		}
3709 		else if (player->powers[pw_carry] == CR_NIGHTSFALL)
3710 		{
3711 			// always damage so we can recoil upon losing points
3712 			damage = player->spheres;
3713 			P_RingDamage(player, inflictor, source, damage, damagetype, true);
3714 			damage = 0;
3715 		}
3716 		else if (player->rings > 0) // No shield but have rings.
3717 		{
3718 			damage = player->rings;
3719 			P_RingDamage(player, inflictor, source, damage, damagetype, false);
3720 			damage = 0;
3721 		}
3722 		// To reduce griefing potential, don't allow players to be killed
3723 		// by friendly fire. Spilling their rings and other items is enough.
3724 		else if (!force && G_GametypeHasTeams()
3725 			&& source && source->player && (source->player->ctfteam == player->ctfteam)
3726 			&& (cv_friendlyfire.value || (gametyperules & GTR_FRIENDLYFIRE)))
3727 		{
3728 			damage = 0;
3729 			P_ShieldDamage(player, inflictor, source, damage, damagetype);
3730 		}
3731 		else // No shield, no rings, no invincibility.
3732 		{
3733 			damage = 1;
3734 			P_KillPlayer(player, source, damage);
3735 		}
3736 
3737 		P_ForceFeed(player, 40, 10, TICRATE, 40 + min(damage, 100)*2);
3738 	}
3739 
3740 	// Killing dead. Just for kicks.
3741 	// Require source and inflictor be player.  Don't hurt for firing rings.
3742 	if (cv_killingdead.value && (source && source->player) && (inflictor && inflictor->player) && P_RandomChance(5*FRACUNIT/16))
3743 		P_DamageMobj(source, target, target, 1, 0);
3744 
3745 	// do the damage
3746 	if (damagetype & DMG_DEATHMASK)
3747 		target->health = 0;
3748 	else
3749 		target->health -= damage;
3750 
3751 	if (player)
3752 		P_HitDeathMessages(player, inflictor, source, damagetype);
3753 
3754 	if (source && source->player && target)
3755 		G_GhostAddHit(target);
3756 
3757 	if (target->health <= 0)
3758 	{
3759 		P_KillMobj(target, inflictor, source, damagetype);
3760 		return true;
3761 	}
3762 
3763 	if (player)
3764 		P_ResetPlayer(target->player);
3765 	else if ((target->type == MT_EGGMOBILE2) // egg slimer
3766 	&& (target->health < target->info->damage)) // in pinch phase
3767 		P_SetMobjState(target, target->info->meleestate); // go to pinch pain state
3768 	else
3769 		P_SetMobjState(target, target->info->painstate);
3770 
3771 	if (target->type == MT_HIVEELEMENTAL)
3772 		target->extravalue1 += 3;
3773 
3774 	target->reactiontime = 0; // we're awake now...
3775 
3776 	if (source && source != target)
3777 	{
3778 		// if not intent on another player,
3779 		// chase after this one
3780 		P_SetTarget(&target->target, source);
3781 	}
3782 
3783 	return true;
3784 }
3785 
3786 /** Spills an injured player's rings.
3787   *
3788   * \param player    The player who is losing rings.
3789   * \param num_rings Number of rings lost. A maximum of 32 rings will be
3790   *                  spawned.
3791   * \sa P_PlayerFlagBurst
3792   */
P_PlayerRingBurst(player_t * player,INT32 num_rings)3793 void P_PlayerRingBurst(player_t *player, INT32 num_rings)
3794 {
3795 	INT32 i;
3796 	mobj_t *mo;
3797 	angle_t fa, va;
3798 	fixed_t ns;
3799 	fixed_t z;
3800 	boolean nightsreplace = ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap));
3801 
3802 	// Better safe than sorry.
3803 	if (!player)
3804 		return;
3805 
3806 	// If no health, don't spawn ring!
3807 	if (((maptol & TOL_NIGHTS) && player->spheres <= 0) || (!(maptol & TOL_NIGHTS) && player->rings <= 0))
3808 		num_rings = 0;
3809 
3810 	if (num_rings > 32 && player->powers[pw_carry] != CR_NIGHTSFALL)
3811 		num_rings = 32;
3812 
3813 	if (player->powers[pw_emeralds])
3814 		P_PlayerEmeraldBurst(player, false);
3815 
3816 	// Spill weapons first
3817 	P_PlayerWeaponPanelOrAmmoBurst(player);
3818 
3819 	if (abs(player->mo->momx) > player->mo->scale || abs(player->mo->momy) > player->mo->scale)
3820 		va = R_PointToAngle2(player->mo->momx, player->mo->momy, 0, 0)>>ANGLETOFINESHIFT;
3821 	else
3822 		va = player->mo->angle>>ANGLETOFINESHIFT;
3823 
3824 	for (i = 0; i < num_rings; i++)
3825 	{
3826 		INT32 objType = mobjinfo[MT_RING].reactiontime;
3827 		if (mariomode)
3828 			objType = mobjinfo[MT_COIN].reactiontime;
3829 		else if (player->powers[pw_carry] == CR_NIGHTSFALL)
3830 			objType = mobjinfo[(nightsreplace ? MT_NIGHTSCHIP : MT_BLUESPHERE)].reactiontime;
3831 
3832 		z = player->mo->z;
3833 		if (player->mo->eflags & MFE_VERTICALFLIP)
3834 			z += player->mo->height - mobjinfo[objType].height;
3835 
3836 		mo = P_SpawnMobj(player->mo->x, player->mo->y, z, objType);
3837 
3838 		mo->fuse = 8*TICRATE;
3839 		P_SetTarget(&mo->target, player->mo);
3840 
3841 		mo->destscale = player->mo->scale;
3842 		P_SetScale(mo, player->mo->scale);
3843 
3844 		// Angle offset by player angle, then slightly offset by amount of rings
3845 		fa = ((i*FINEANGLES/16) + va - ((num_rings-1)*FINEANGLES/32)) & FINEMASK;
3846 
3847 		// Make rings spill out around the player in 16 directions like SA, but spill like Sonic 2.
3848 		// Technically a non-SA way of spilling rings. They just so happen to be a little similar.
3849 		if (player->powers[pw_carry] == CR_NIGHTSFALL)
3850 		{
3851 			ns = FixedMul(((i*FRACUNIT)/16)+2*FRACUNIT, mo->scale);
3852 			mo->momx = FixedMul(FINECOSINE(fa),ns);
3853 
3854 			if (!(twodlevel || (player->mo->flags2 & MF2_TWOD)))
3855 				mo->momy = FixedMul(FINESINE(fa),ns);
3856 
3857 			P_SetObjectMomZ(mo, 8*FRACUNIT, false);
3858 			mo->fuse = 20*TICRATE; // Adjust fuse for NiGHTS
3859 
3860 			// Toggle bonus time colors
3861 			P_SetMobjState(mo, (player->bonustime ? mo->info->raisestate : mo->info->spawnstate));
3862 		}
3863 		else
3864 		{
3865 			fixed_t momxy, momz; // base horizonal/vertical thrusts
3866 
3867 			if (i > 15)
3868 			{
3869 				momxy = 3*FRACUNIT;
3870 				momz = 4*FRACUNIT;
3871 			}
3872 			else
3873 			{
3874 				momxy = 2*FRACUNIT;
3875 				momz = 3*FRACUNIT;
3876 			}
3877 
3878 			ns = FixedMul(FixedMul(momxy, FRACUNIT + FixedDiv(player->losstime<<FRACBITS, 10*TICRATE<<FRACBITS)), mo->scale);
3879 			mo->momx = FixedMul(FINECOSINE(fa),ns);
3880 
3881 			if (!(twodlevel || (player->mo->flags2 & MF2_TWOD)))
3882 				mo->momy = FixedMul(FINESINE(fa),ns);
3883 
3884 			ns = FixedMul(momz, FRACUNIT + FixedDiv(player->losstime<<FRACBITS, 10*TICRATE<<FRACBITS));
3885 			P_SetObjectMomZ(mo, ns, false);
3886 
3887 			if (i & 1)
3888 				P_SetObjectMomZ(mo, ns, true);
3889 		}
3890 		if (player->mo->eflags & MFE_VERTICALFLIP)
3891 			mo->momz *= -1;
3892 	}
3893 
3894 	player->losstime += 10*TICRATE;
3895 
3896 	return;
3897 }
3898 
P_PlayerWeaponPanelBurst(player_t * player)3899 void P_PlayerWeaponPanelBurst(player_t *player)
3900 {
3901 	mobj_t *mo;
3902 	angle_t fa;
3903 	fixed_t ns;
3904 	INT32 i;
3905 	fixed_t z;
3906 
3907 	INT32 num_weapons = M_CountBits((UINT32)player->ringweapons, NUM_WEAPONS-1);
3908 	UINT16 ammoamt = 0;
3909 
3910 	for (i = 0; i < num_weapons; i++)
3911 	{
3912 		mobjtype_t weptype = 0;
3913 		powertype_t power = 0;
3914 
3915 		if (player->ringweapons & RW_BOUNCE) // Bounce
3916 		{
3917 			weptype = MT_BOUNCEPICKUP;
3918 			player->ringweapons &= ~RW_BOUNCE;
3919 			power = pw_bouncering;
3920 		}
3921 		else if (player->ringweapons & RW_RAIL) // Rail
3922 		{
3923 			weptype = MT_RAILPICKUP;
3924 			player->ringweapons &= ~RW_RAIL;
3925 			power = pw_railring;
3926 		}
3927 		else if (player->ringweapons & RW_AUTO) // Auto
3928 		{
3929 			weptype = MT_AUTOPICKUP;
3930 			player->ringweapons &= ~RW_AUTO;
3931 			power = pw_automaticring;
3932 		}
3933 		else if (player->ringweapons & RW_EXPLODE) // Explode
3934 		{
3935 			weptype = MT_EXPLODEPICKUP;
3936 			player->ringweapons &= ~RW_EXPLODE;
3937 			power = pw_explosionring;
3938 		}
3939 		else if (player->ringweapons & RW_SCATTER) // Scatter
3940 		{
3941 			weptype = MT_SCATTERPICKUP;
3942 			player->ringweapons &= ~RW_SCATTER;
3943 			power = pw_scatterring;
3944 		}
3945 		else if (player->ringweapons & RW_GRENADE) // Grenade
3946 		{
3947 			weptype = MT_GRENADEPICKUP;
3948 			player->ringweapons &= ~RW_GRENADE;
3949 			power = pw_grenadering;
3950 		}
3951 
3952 		if (!weptype) // ???
3953 			continue;
3954 
3955 		if (player->powers[power] >= mobjinfo[weptype].reactiontime)
3956 			ammoamt = (UINT16)mobjinfo[weptype].reactiontime;
3957 		else
3958 			ammoamt = player->powers[power];
3959 
3960 		player->powers[power] -= ammoamt;
3961 
3962 		z = player->mo->z;
3963 		if (player->mo->eflags & MFE_VERTICALFLIP)
3964 			z += player->mo->height - mobjinfo[weptype].height;
3965 
3966 		mo = P_SpawnMobj(player->mo->x, player->mo->y, z, weptype);
3967 		mo->reactiontime = ammoamt;
3968 		mo->flags2 |= MF2_DONTRESPAWN;
3969 		mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT);
3970 		P_SetTarget(&mo->target, player->mo);
3971 		mo->fuse = 12*TICRATE;
3972 		mo->destscale = player->mo->scale;
3973 		P_SetScale(mo, player->mo->scale);
3974 
3975 		// Angle offset by player angle
3976 		fa = ((i*FINEANGLES/16) + (player->mo->angle>>ANGLETOFINESHIFT)) & FINEMASK;
3977 
3978 		// Make rings spill out around the player in 16 directions like SA, but spill like Sonic 2.
3979 		// Technically a non-SA way of spilling rings. They just so happen to be a little similar.
3980 
3981 		// >16 ring type spillout
3982 		ns = FixedMul(3*FRACUNIT, mo->scale);
3983 		mo->momx = FixedMul(FINECOSINE(fa),ns);
3984 
3985 		if (!(twodlevel || (player->mo->flags2 & MF2_TWOD)))
3986 			mo->momy = FixedMul(FINESINE(fa),ns);
3987 
3988 		P_SetObjectMomZ(mo, 4*FRACUNIT, false);
3989 
3990 		if (i & 1)
3991 			P_SetObjectMomZ(mo, 4*FRACUNIT, true);
3992 	}
3993 }
3994 
P_PlayerWeaponAmmoBurst(player_t * player)3995 void P_PlayerWeaponAmmoBurst(player_t *player)
3996 {
3997 	mobj_t *mo;
3998 	angle_t fa;
3999 	fixed_t ns;
4000 	INT32 i = 0;
4001 	fixed_t z;
4002 
4003 	mobjtype_t weptype = 0;
4004 	powertype_t power = 0;
4005 
4006 	while (true)
4007 	{
4008 		if (player->powers[pw_bouncering])
4009 		{
4010 			weptype = MT_BOUNCERING;
4011 			power = pw_bouncering;
4012 		}
4013 		else if (player->powers[pw_railring])
4014 		{
4015 			weptype = MT_RAILRING;
4016 			power = pw_railring;
4017 		}
4018 		else if (player->powers[pw_infinityring])
4019 		{
4020 			weptype = MT_INFINITYRING;
4021 			power = pw_infinityring;
4022 		}
4023 		else if (player->powers[pw_automaticring])
4024 		{
4025 			weptype = MT_AUTOMATICRING;
4026 			power = pw_automaticring;
4027 		}
4028 		else if (player->powers[pw_explosionring])
4029 		{
4030 			weptype = MT_EXPLOSIONRING;
4031 			power = pw_explosionring;
4032 		}
4033 		else if (player->powers[pw_scatterring])
4034 		{
4035 			weptype = MT_SCATTERRING;
4036 			power = pw_scatterring;
4037 		}
4038 		else if (player->powers[pw_grenadering])
4039 		{
4040 			weptype = MT_GRENADERING;
4041 			power = pw_grenadering;
4042 		}
4043 		else
4044 			break; // All done!
4045 
4046 		z = player->mo->z;
4047 		if (player->mo->eflags & MFE_VERTICALFLIP)
4048 			z += player->mo->height - mobjinfo[weptype].height;
4049 
4050 		mo = P_SpawnMobj(player->mo->x, player->mo->y, z, weptype);
4051 		mo->health = player->powers[power];
4052 		mo->flags2 |= MF2_DONTRESPAWN;
4053 		mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT);
4054 		P_SetTarget(&mo->target, player->mo);
4055 
4056 		player->powers[power] = 0;
4057 		mo->fuse = 12*TICRATE;
4058 
4059 		mo->destscale = player->mo->scale;
4060 		P_SetScale(mo, player->mo->scale);
4061 
4062 		// Angle offset by player angle
4063 		fa = ((i*FINEANGLES/16) + (player->mo->angle>>ANGLETOFINESHIFT)) & FINEMASK;
4064 
4065 		// Spill them!
4066 		ns = FixedMul(2*FRACUNIT, mo->scale);
4067 		mo->momx = FixedMul(FINECOSINE(fa), ns);
4068 
4069 		if (!(twodlevel || (player->mo->flags2 & MF2_TWOD)))
4070 			mo->momy = FixedMul(FINESINE(fa),ns);
4071 
4072 		P_SetObjectMomZ(mo, 3*FRACUNIT, false);
4073 
4074 		if (i & 1)
4075 			P_SetObjectMomZ(mo, 3*FRACUNIT, true);
4076 
4077 		i++;
4078 	}
4079 }
4080 
P_PlayerWeaponPanelOrAmmoBurst(player_t * player)4081 void P_PlayerWeaponPanelOrAmmoBurst(player_t *player)
4082 {
4083 	mobj_t *mo;
4084 	angle_t fa;
4085 	fixed_t ns;
4086 	INT32 i = 0;
4087 	fixed_t z;
4088 
4089 	#define SETUP_DROP(thingtype) \
4090 		z = player->mo->z; \
4091 		if (player->mo->eflags & MFE_VERTICALFLIP) \
4092 			z += player->mo->height - mobjinfo[thingtype].height; \
4093 		fa = ((i*FINEANGLES/16) + (player->mo->angle>>ANGLETOFINESHIFT)) & FINEMASK; \
4094 		ns = FixedMul(3*FRACUNIT, player->mo->scale); \
4095 
4096 	#define DROP_WEAPON(rwflag, pickup, ammo, power) \
4097 	if (player->ringweapons & rwflag) \
4098 	{ \
4099 		player->ringweapons &= ~rwflag; \
4100 		SETUP_DROP(pickup) \
4101 		mo = P_SpawnMobj(player->mo->x, player->mo->y, z, pickup); \
4102 		mo->reactiontime = 0; \
4103 		mo->flags2 |= MF2_DONTRESPAWN; \
4104 		mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); \
4105 		P_SetTarget(&mo->target, player->mo); \
4106 		mo->fuse = 12*TICRATE; \
4107 		mo->destscale = player->mo->scale; \
4108 		P_SetScale(mo, player->mo->scale); \
4109 		mo->momx = FixedMul(FINECOSINE(fa),ns); \
4110 		if (!(twodlevel || (player->mo->flags2 & MF2_TWOD))) \
4111 			mo->momy = FixedMul(FINESINE(fa),ns); \
4112 		P_SetObjectMomZ(mo, 4*FRACUNIT, false); \
4113 		if (i & 1) \
4114 			P_SetObjectMomZ(mo, 4*FRACUNIT, true); \
4115 		++i; \
4116 	} \
4117 	else if (player->powers[power] > 0) \
4118 	{ \
4119 		SETUP_DROP(ammo) \
4120 		mo = P_SpawnMobj(player->mo->x, player->mo->y, z, ammo); \
4121 		mo->health = player->powers[power]; \
4122 		mo->flags2 |= MF2_DONTRESPAWN; \
4123 		mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); \
4124 		P_SetTarget(&mo->target, player->mo); \
4125 		mo->fuse = 12*TICRATE; \
4126 		mo->destscale = player->mo->scale; \
4127 		P_SetScale(mo, player->mo->scale); \
4128 		mo->momx = FixedMul(FINECOSINE(fa),ns); \
4129 		if (!(twodlevel || (player->mo->flags2 & MF2_TWOD))) \
4130 			mo->momy = FixedMul(FINESINE(fa),ns); \
4131 		P_SetObjectMomZ(mo, 3*FRACUNIT, false); \
4132 		if (i & 1) \
4133 			P_SetObjectMomZ(mo, 3*FRACUNIT, true); \
4134 		player->powers[power] = 0; \
4135 		++i; \
4136 	}
4137 
4138 	DROP_WEAPON(RW_BOUNCE, MT_BOUNCEPICKUP, MT_BOUNCERING, pw_bouncering);
4139 	DROP_WEAPON(RW_RAIL, MT_RAILPICKUP, MT_RAILRING, pw_railring);
4140 	DROP_WEAPON(RW_AUTO, MT_AUTOPICKUP, MT_AUTOMATICRING, pw_automaticring);
4141 	DROP_WEAPON(RW_EXPLODE, MT_EXPLODEPICKUP, MT_EXPLOSIONRING, pw_explosionring);
4142 	DROP_WEAPON(RW_SCATTER, MT_SCATTERPICKUP, MT_SCATTERRING, pw_scatterring);
4143 	DROP_WEAPON(RW_GRENADE, MT_GRENADEPICKUP, MT_GRENADERING, pw_grenadering);
4144 	DROP_WEAPON(0, 0, MT_INFINITYRING, pw_infinityring);
4145 
4146 	#undef DROP_WEAPON
4147 	#undef SETUP_DROP
4148 }
4149 
4150 //
4151 // P_PlayerEmeraldBurst
4152 //
4153 // Spills ONLY emeralds.
4154 //
P_PlayerEmeraldBurst(player_t * player,boolean toss)4155 void P_PlayerEmeraldBurst(player_t *player, boolean toss)
4156 {
4157 	INT32 i;
4158 	angle_t fa;
4159 	fixed_t ns;
4160 	fixed_t z = 0, momx = 0, momy = 0;
4161 
4162 	// Better safe than sorry.
4163 	if (!player)
4164 		return;
4165 
4166 	// Spill power stones
4167 	if (player->powers[pw_emeralds])
4168 	{
4169 		INT32 num_stones = 0;
4170 
4171 		if (player->powers[pw_emeralds] & EMERALD1)
4172 			num_stones++;
4173 		if (player->powers[pw_emeralds] & EMERALD2)
4174 			num_stones++;
4175 		if (player->powers[pw_emeralds] & EMERALD3)
4176 			num_stones++;
4177 		if (player->powers[pw_emeralds] & EMERALD4)
4178 			num_stones++;
4179 		if (player->powers[pw_emeralds] & EMERALD5)
4180 			num_stones++;
4181 		if (player->powers[pw_emeralds] & EMERALD6)
4182 			num_stones++;
4183 		if (player->powers[pw_emeralds] & EMERALD7)
4184 			num_stones++;
4185 
4186 		for (i = 0; i < num_stones; i++)
4187 		{
4188 			INT32 stoneflag = 0;
4189 			statenum_t statenum = S_CEMG1;
4190 			mobj_t *mo;
4191 
4192 			if (player->powers[pw_emeralds] & EMERALD1)
4193 			{
4194 				stoneflag = EMERALD1;
4195 				statenum = S_CEMG1;
4196 			}
4197 			else if (player->powers[pw_emeralds] & EMERALD2)
4198 			{
4199 				stoneflag = EMERALD2;
4200 				statenum = S_CEMG2;
4201 			}
4202 			else if (player->powers[pw_emeralds] & EMERALD3)
4203 			{
4204 				stoneflag = EMERALD3;
4205 				statenum = S_CEMG3;
4206 			}
4207 			else if (player->powers[pw_emeralds] & EMERALD4)
4208 			{
4209 				stoneflag = EMERALD4;
4210 				statenum = S_CEMG4;
4211 			}
4212 			else if (player->powers[pw_emeralds] & EMERALD5)
4213 			{
4214 				stoneflag = EMERALD5;
4215 				statenum = S_CEMG5;
4216 			}
4217 			else if (player->powers[pw_emeralds] & EMERALD6)
4218 			{
4219 				stoneflag = EMERALD6;
4220 				statenum = S_CEMG6;
4221 			}
4222 			else if (player->powers[pw_emeralds] & EMERALD7)
4223 			{
4224 				stoneflag = EMERALD7;
4225 				statenum = S_CEMG7;
4226 			}
4227 
4228 			if (!stoneflag) // ???
4229 				continue;
4230 
4231 			player->powers[pw_emeralds] &= ~stoneflag;
4232 
4233 			if (toss)
4234 			{
4235 				fa = player->mo->angle>>ANGLETOFINESHIFT;
4236 
4237 				z = player->mo->z + player->mo->height;
4238 				if (player->mo->eflags & MFE_VERTICALFLIP)
4239 					z -= mobjinfo[MT_FLINGEMERALD].height + player->mo->height;
4240 				ns = FixedMul(8*FRACUNIT, player->mo->scale);
4241 			}
4242 			else
4243 			{
4244 				fa = ((255 / num_stones) * i) * FINEANGLES/256;
4245 
4246 				z = player->mo->z + (player->mo->height / 2);
4247 				if (player->mo->eflags & MFE_VERTICALFLIP)
4248 					z -= mobjinfo[MT_FLINGEMERALD].height;
4249 				ns = FixedMul(4*FRACUNIT, player->mo->scale);
4250 			}
4251 
4252 			momx = FixedMul(FINECOSINE(fa), ns);
4253 
4254 			if (!(twodlevel || (player->mo->flags2 & MF2_TWOD)))
4255 				momy = FixedMul(FINESINE(fa),ns);
4256 			else
4257 				momy = 0;
4258 
4259 			mo = P_SpawnMobj(player->mo->x, player->mo->y, z, MT_FLINGEMERALD);
4260 			mo->health = 1;
4261 			mo->threshold = stoneflag;
4262 			mo->flags2 |= (MF2_DONTRESPAWN|MF2_SLIDEPUSH);
4263 			mo->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT);
4264 			P_SetTarget(&mo->target, player->mo);
4265 			mo->fuse = 12*TICRATE;
4266 			P_SetMobjState(mo, statenum);
4267 
4268 			mo->momx = momx;
4269 			mo->momy = momy;
4270 
4271 			P_SetObjectMomZ(mo, 3*FRACUNIT, false);
4272 
4273 			if (player->mo->eflags & MFE_VERTICALFLIP)
4274 				mo->momz = -mo->momz;
4275 
4276 			if (toss)
4277 				player->tossdelay = 2*TICRATE;
4278 		}
4279 	}
4280 }
4281 
4282 /** Makes an injured or dead player lose possession of the flag.
4283   *
4284   * \param player The player with the flag, about to lose it.
4285   * \sa P_PlayerRingBurst
4286   */
P_PlayerFlagBurst(player_t * player,boolean toss)4287 void P_PlayerFlagBurst(player_t *player, boolean toss)
4288 {
4289 	mobj_t *flag;
4290 	mobjtype_t type;
4291 
4292 	if (!(player->gotflag & (GF_REDFLAG|GF_BLUEFLAG)))
4293 		return;
4294 
4295 	if (player->gotflag & GF_REDFLAG)
4296 		type = MT_REDFLAG;
4297 	else
4298 		type = MT_BLUEFLAG;
4299 
4300 	flag = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, type);
4301 
4302 	if (player->mo->eflags & MFE_VERTICALFLIP)
4303 		flag->z += player->mo->height - flag->height;
4304 
4305 	if (toss)
4306 		P_InstaThrust(flag, player->mo->angle, FixedMul(6*FRACUNIT, player->mo->scale));
4307 	else
4308 	{
4309 		angle_t fa = P_RandomByte()*FINEANGLES/256;
4310 		flag->momx = FixedMul(FINECOSINE(fa), FixedMul(6*FRACUNIT, player->mo->scale));
4311 		if (!(twodlevel || (player->mo->flags2 & MF2_TWOD)))
4312 			flag->momy = FixedMul(FINESINE(fa), FixedMul(6*FRACUNIT, player->mo->scale));
4313 	}
4314 
4315 	flag->momz = FixedMul(8*FRACUNIT, player->mo->scale);
4316 	if (player->mo->eflags & MFE_VERTICALFLIP)
4317 		flag->momz = -flag->momz;
4318 
4319 	if (type == MT_REDFLAG)
4320 		flag->spawnpoint = rflagpoint;
4321 	else
4322 		flag->spawnpoint = bflagpoint;
4323 
4324 	flag->fuse = cv_flagtime.value * TICRATE;
4325 	P_SetTarget(&flag->target, player->mo);
4326 
4327 	// Flag text
4328 	{
4329 		char plname[MAXPLAYERNAME+4];
4330 		const char *flagtext;
4331 		char flagcolor;
4332 
4333 		snprintf(plname, sizeof(plname), "%s%s%s",
4334 				 CTFTEAMCODE(player),
4335 				 player_names[player - players],
4336 				 CTFTEAMENDCODE(player));
4337 
4338 		if (type == MT_REDFLAG)
4339 		{
4340 			flagtext = M_GetText("Red flag");
4341 			flagcolor = '\x85';
4342 		}
4343 		else
4344 		{
4345 			flagtext = M_GetText("Blue flag");
4346 			flagcolor = '\x84';
4347 		}
4348 
4349 		if (toss)
4350 			CONS_Printf(M_GetText("%s tossed the %c%s%c.\n"), plname, flagcolor, flagtext, 0x80);
4351 		else
4352 			CONS_Printf(M_GetText("%s dropped the %c%s%c.\n"), plname, flagcolor, flagtext, 0x80);
4353 	}
4354 
4355 	player->gotflag = 0;
4356 
4357 	// Pointers set for displaying time value and for consistency restoration.
4358 	if (type == MT_REDFLAG)
4359 		redflag = flag;
4360 	else
4361 		blueflag = flag;
4362 
4363 	if (toss)
4364 		player->tossdelay = 2*TICRATE;
4365 
4366 	return;
4367 }
4368