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