Lots of death stuff.

* Genesis-style love and attention to the death event.
	* Only visibly decrement lives/rings when you're respawning (or game over, see below).
	* Faster no-button-press respawn.
* Game Over specific love.
	* Animation of Level Title font coming in from the sides.
	* https://cdn.discordapp.com/attachments/428262628893261828/617692325438554132/srb20067.gif
	* Change gameovertics to 10 seconds instead of 15.
	* Make the minimum time before you can force going to the Continue screen longer.
* Accomodate death in MP special stages as a form of exit.
	* Don't have your rings or spheres reset when you die in a special stage, so that the stage isn't softlocked with the new harder limits.
* Fix a bug with CoopLives_OnChange where changing to infinite lives didn't force a game-overed player to respawn.

Also, two not-quite death things which nonetheless were relevant to change:

* Fix quitting a special stage having some of the shared spheres/rings disappear into the aether.
* Fix a warning during compilation for the Ring Penalty print.
This commit is contained in:
toaster 2019-09-01 15:55:23 +01:00
parent 602154fe8b
commit f07309707d
7 changed files with 77 additions and 60 deletions

View File

@ -2415,7 +2415,7 @@ static void CL_RemovePlayer(INT32 playernum, INT32 reason)
// the remaining players. // the remaining players.
if (G_IsSpecialStage(gamemap)) if (G_IsSpecialStage(gamemap))
{ {
INT32 i, count, increment, spheres; INT32 i, count, sincrement, spheres, rincrement, rings;
for (i = 0, count = 0; i < MAXPLAYERS; i++) for (i = 0, count = 0; i < MAXPLAYERS; i++)
{ {
@ -2425,18 +2425,35 @@ static void CL_RemovePlayer(INT32 playernum, INT32 reason)
count--; count--;
spheres = players[playernum].spheres; spheres = players[playernum].spheres;
increment = spheres/count; rings = players[playernum].rings;
sincrement = spheres/count;
rincrement = rings/count;
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
if (playeringame[i] && i != playernum) if (playeringame[i] && i != playernum)
{ {
if (spheres < increment) if (spheres < 2*sincrement)
{
P_GivePlayerSpheres(&players[i], spheres); P_GivePlayerSpheres(&players[i], spheres);
spheres = 0;
}
else else
P_GivePlayerSpheres(&players[i], increment); {
P_GivePlayerSpheres(&players[i], sincrement);
spheres -= sincrement;
}
spheres -= increment; if (rings < 2*rincrement)
{
P_GivePlayerRings(&players[i], rings);
rings = 0;
}
else
{
P_GivePlayerRings(&players[i], rincrement);
rings -= rincrement;
}
} }
} }
} }

View File

@ -2706,14 +2706,6 @@ static void Got_Teamchange(UINT8 **cp, INT32 playernum)
} }
} }
// Clear player score and rings if a spectator.
if (players[playernum].spectator)
{
players[playernum].score = players[playernum].rings = 0;
if (players[playernum].mo)
players[playernum].mo->health = 1;
}
// In tag, check to see if you still have a game. // In tag, check to see if you still have a game.
if (G_TagGametype()) if (G_TagGametype())
P_CheckSurvivors(); P_CheckSurvivors();
@ -3600,7 +3592,7 @@ static void CoopLives_OnChange(void)
{ {
case 0: case 0:
CONS_Printf(M_GetText("Players can now respawn indefinitely.\n")); CONS_Printf(M_GetText("Players can now respawn indefinitely.\n"));
return; break;
case 1: case 1:
CONS_Printf(M_GetText("Lives are now per-player.\n")); CONS_Printf(M_GetText("Lives are now per-player.\n"));
return; return;

View File

@ -215,7 +215,7 @@ UINT16 spacetimetics = 11*TICRATE + (TICRATE/2);
UINT16 extralifetics = 4*TICRATE; UINT16 extralifetics = 4*TICRATE;
UINT16 nightslinktics = 2*TICRATE; UINT16 nightslinktics = 2*TICRATE;
INT32 gameovertics = 15*TICRATE; INT32 gameovertics = 10*TICRATE;
UINT8 ammoremovaltics = 2*TICRATE; UINT8 ammoremovaltics = 2*TICRATE;
@ -2145,6 +2145,8 @@ void G_PlayerReborn(INT32 player)
boolean outofcoop; boolean outofcoop;
INT16 bot; INT16 bot;
SINT8 pity; SINT8 pity;
INT16 rings;
INT16 spheres;
score = players[player].score; score = players[player].score;
lives = players[player].lives; lives = players[player].lives;
@ -2199,6 +2201,17 @@ void G_PlayerReborn(INT32 player)
bot = players[player].bot; bot = players[player].bot;
pity = players[player].pity; pity = players[player].pity;
if (!G_IsSpecialStage(gamemap))
{
rings = (ultimatemode ? 0 : mapheaderinfo[gamemap-1]->startrings);
spheres = 0;
}
else
{
rings = players[player].rings;
spheres = players[player].spheres;
}
p = &players[player]; p = &players[player];
memset(p, 0, sizeof (*p)); memset(p, 0, sizeof (*p));
@ -2252,6 +2265,8 @@ void G_PlayerReborn(INT32 player)
if (bot) if (bot)
p->bot = 1; // reset to AI-controlled p->bot = 1; // reset to AI-controlled
p->pity = pity; p->pity = pity;
p->rings = rings;
p->spheres = spheres;
// Don't do anything immediately // Don't do anything immediately
p->pflags |= PF_USEDOWN; p->pflags |= PF_USEDOWN;
@ -2259,7 +2274,6 @@ void G_PlayerReborn(INT32 player)
p->pflags |= PF_JUMPDOWN; p->pflags |= PF_JUMPDOWN;
p->playerstate = PST_LIVE; p->playerstate = PST_LIVE;
p->rings = p->spheres = 0; // 0 rings
p->panim = PA_IDLE; // standing animation p->panim = PA_IDLE; // standing animation
//if ((netgame || multiplayer) && !p->spectator) -- moved into P_SpawnPlayer to account for forced changes there //if ((netgame || multiplayer) && !p->spectator) -- moved into P_SpawnPlayer to account for forced changes there
@ -2370,8 +2384,6 @@ void G_SpawnPlayer(INT32 playernum, boolean starpost)
P_SpawnPlayer(playernum); P_SpawnPlayer(playernum);
players[playernum].rings = mapheaderinfo[gamemap-1]->startrings;
if (starpost) //Don't even bother with looking for a place to spawn. if (starpost) //Don't even bother with looking for a place to spawn.
{ {
P_MovePlayerToStarpost(playernum); P_MovePlayerToStarpost(playernum);

View File

@ -3500,7 +3500,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
return true; return true;
} }
if (G_IsSpecialStage(gamemap)) if (G_IsSpecialStage(gamemap) && !(damagetype & DMG_DEATHMASK))
{ {
P_SpecialStageDamage(player, inflictor, source); P_SpecialStageDamage(player, inflictor, source);
return true; return true;
@ -3524,10 +3524,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
// Instant-Death // Instant-Death
if (damagetype & DMG_DEATHMASK) if (damagetype & DMG_DEATHMASK)
{
P_KillPlayer(player, source, damage); P_KillPlayer(player, source, damage);
player->rings = player->spheres = 0;
}
else if (metalrecording) else if (metalrecording)
{ {
if (!inflictor) if (!inflictor)

View File

@ -10397,7 +10397,7 @@ void P_SpawnPlayer(INT32 playernum)
&& ((leveltime > 0 && ((leveltime > 0
&& ((G_IsSpecialStage(gamemap) && (maptol & TOL_NIGHTS)) // late join special stage && ((G_IsSpecialStage(gamemap) && (maptol & TOL_NIGHTS)) // late join special stage
|| (cv_coopstarposts.value == 2 && (p->jointime < 1 || p->outofcoop)))) // late join or die in new coop || (cv_coopstarposts.value == 2 && (p->jointime < 1 || p->outofcoop)))) // late join or die in new coop
|| (((cv_cooplives.value == 1) || !P_GetLives(p)) && p->lives <= 0))); // game over and can't redistribute lives || (!P_GetLives(p) && p->lives <= 0))); // game over and can't redistribute lives
} }
else else
{ {
@ -10464,7 +10464,6 @@ void P_SpawnPlayer(INT32 playernum)
P_SetupStateAnimation(mobj, mobj->state); P_SetupStateAnimation(mobj, mobj->state);
mobj->health = 1; mobj->health = 1;
p->rings = p->spheres = 0;
p->playerstate = PST_LIVE; p->playerstate = PST_LIVE;
p->bonustime = false; p->bonustime = false;

View File

@ -9027,19 +9027,22 @@ boolean P_GetLives(player_t *player)
INT32 i, maxlivesplayer = -1, livescheck = 1; INT32 i, maxlivesplayer = -1, livescheck = 1;
if (!(netgame || multiplayer) if (!(netgame || multiplayer)
|| (gametype != GT_COOP) || (gametype != GT_COOP)
|| (cv_cooplives.value == 1)
|| (player->lives == INFLIVES)) || (player->lives == INFLIVES))
return true; return true;
if ((cv_cooplives.value == 2 || cv_cooplives.value == 0) && player->lives > 0)
return true;
if (cv_cooplives.value == 0) // infinite lives if (cv_cooplives.value == 0) // infinite lives
{ {
player->lives++; if (player->lives < 1)
player->lives = 1;
return true; return true;
} }
if ((cv_cooplives.value == 2 || cv_cooplives.value == 1) && player->lives > 0)
return true;
if (cv_cooplives.value == 1)
return false;
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
if (!playeringame[i]) if (!playeringame[i])
@ -9146,7 +9149,7 @@ static void P_DeathThink(player_t *player)
// continue logic // continue logic
if (!(netgame || multiplayer) && player->lives <= 0) if (!(netgame || multiplayer) && player->lives <= 0)
{ {
if (player->deadtimer > TICRATE && (cmd->buttons & BT_USE || cmd->buttons & BT_JUMP) && player->continues > 0) if (player->deadtimer > (3*TICRATE) && (cmd->buttons & BT_USE || cmd->buttons & BT_JUMP) && player->continues > 0)
G_UseContinue(); G_UseContinue();
else if (player->deadtimer >= gameovertics) else if (player->deadtimer >= gameovertics)
G_UseContinue(); // Even if we don't have one this handles ending the game G_UseContinue(); // Even if we don't have one this handles ending the game
@ -9170,12 +9173,12 @@ static void P_DeathThink(player_t *player)
// Force respawn if idle for more than 30 seconds in shooter modes. // Force respawn if idle for more than 30 seconds in shooter modes.
if (player->deadtimer > 30*TICRATE && !G_PlatformGametype()) if (player->deadtimer > 30*TICRATE && !G_PlatformGametype())
player->playerstate = PST_REBORN; player->playerstate = PST_REBORN;
else if ((player->lives > 0 || j != MAXPLAYERS) && !G_IsSpecialStage(gamemap)) // Don't allow "click to respawn" in special stages! else if ((player->lives > 0 || j != MAXPLAYERS) && !(G_IsSpecialStage(gamemap))) // Don't allow "click to respawn" in special stages!
{ {
if (gametype == GT_COOP && (netgame || multiplayer) && cv_coopstarposts.value == 2) if (gametype == GT_COOP && (netgame || multiplayer) && cv_coopstarposts.value == 2)
{ {
P_ConsiderAllGone(); P_ConsiderAllGone();
if ((player->deadtimer > 5*TICRATE) || ((cmd->buttons & BT_JUMP) && (player->deadtimer > TICRATE))) if ((player->deadtimer > TICRATE<<1) || ((cmd->buttons & BT_JUMP) && (player->deadtimer > TICRATE)))
{ {
//player->spectator = true; //player->spectator = true;
player->outofcoop = true; player->outofcoop = true;
@ -9191,16 +9194,11 @@ static void P_DeathThink(player_t *player)
player->playerstate = PST_REBORN; player->playerstate = PST_REBORN;
else switch(gametype) { else switch(gametype) {
case GT_COOP: case GT_COOP:
if (player->deadtimer > TICRATE)
player->playerstate = PST_REBORN;
break;
case GT_COMPETITION: case GT_COMPETITION:
case GT_RACE:
if (player->deadtimer > TICRATE) if (player->deadtimer > TICRATE)
player->playerstate = PST_REBORN; player->playerstate = PST_REBORN;
break; break;
case GT_RACE:
player->playerstate = PST_REBORN;
break;
default: default:
if (player->deadtimer > cv_respawntime.value*TICRATE) if (player->deadtimer > cv_respawntime.value*TICRATE)
player->playerstate = PST_REBORN; player->playerstate = PST_REBORN;
@ -9209,7 +9207,7 @@ static void P_DeathThink(player_t *player)
} }
// Single player auto respawn // Single player auto respawn
if (!(netgame || multiplayer) && player->deadtimer > 5*TICRATE) if (!(netgame || multiplayer) && player->deadtimer > TICRATE<<1)
player->playerstate = PST_REBORN; player->playerstate = PST_REBORN;
} }
} }
@ -11011,8 +11009,6 @@ void P_PlayerThink(player_t *player)
{ {
if (gametype != GT_COOP) if (gametype != GT_COOP)
player->score = 0; player->score = 0;
player->mo->health = 1;
player->rings = player->spheres = 0;
} }
else if ((netgame || multiplayer) && player->lives <= 0 && gametype != GT_COOP) else if ((netgame || multiplayer) && player->lives <= 0 && gametype != GT_COOP)
{ {

View File

@ -66,8 +66,6 @@ patch_t *sboperiod; // Period for time centiseconds
patch_t *livesback; // Lives icon background patch_t *livesback; // Lives icon background
static patch_t *nrec_timer; // Timer for NiGHTS records static patch_t *nrec_timer; // Timer for NiGHTS records
static patch_t *sborings; static patch_t *sborings;
static patch_t *sboover;
static patch_t *timeover;
static patch_t *stlivex; static patch_t *stlivex;
static patch_t *sboredrings; static patch_t *sboredrings;
static patch_t *sboredtime; static patch_t *sboredtime;
@ -253,8 +251,6 @@ void ST_LoadGraphics(void)
sbocolon = W_CachePatchName("STTCOLON", PU_HUDGFX); // Colon for time sbocolon = W_CachePatchName("STTCOLON", PU_HUDGFX); // Colon for time
sboperiod = W_CachePatchName("STTPERIO", PU_HUDGFX); // Period for time centiseconds sboperiod = W_CachePatchName("STTPERIO", PU_HUDGFX); // Period for time centiseconds
sboover = W_CachePatchName("SBOOVER", PU_HUDGFX);
timeover = W_CachePatchName("TIMEOVER", PU_HUDGFX);
stlivex = W_CachePatchName("STLIVEX", PU_HUDGFX); stlivex = W_CachePatchName("STLIVEX", PU_HUDGFX);
livesback = W_CachePatchName("STLIVEBK", PU_HUDGFX); livesback = W_CachePatchName("STLIVEBK", PU_HUDGFX);
nrec_timer = W_CachePatchName("NGRTIMER", PU_HUDGFX); // Timer for NiGHTS nrec_timer = W_CachePatchName("NGRTIMER", PU_HUDGFX); // Timer for NiGHTS
@ -768,7 +764,12 @@ static inline void ST_drawRings(void)
ST_DrawPatchFromHud(HUD_RINGS, ((!stplyr->spectator && stplyr->rings <= 0 && leveltime/5 & 1) ? sboredrings : sborings), ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS)); ST_DrawPatchFromHud(HUD_RINGS, ((!stplyr->spectator && stplyr->rings <= 0 && leveltime/5 & 1) ? sboredrings : sborings), ((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
ringnum = ((objectplacing) ? op_currentdoomednum : max(stplyr->rings, 0)); if (objectplacing)
ringnum = op_currentdoomednum;
else if (stplyr->rings < 0 || stplyr->spectator || stplyr->playerstate == PST_REBORN)
ringnum = 0;
else
ringnum = stplyr->rings;
if (cv_timetic.value == 2) // Yes, even in modeattacking if (cv_timetic.value == 2) // Yes, even in modeattacking
ST_DrawNumFromHud(HUD_RINGSNUMTICS, ringnum, V_PERPLAYER|((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS)); ST_DrawNumFromHud(HUD_RINGSNUMTICS, ringnum, V_PERPLAYER|((stplyr->spectator) ? V_HUDTRANSHALF : V_HUDTRANS));
@ -877,6 +878,8 @@ static void ST_drawLivesArea(void)
'\x16' | 0x80 | hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, false); '\x16' | 0x80 | hudinfo[HUD_LIVES].f|V_PERPLAYER|V_HUDTRANS, false);
else else
{ {
if (stplyr->playerstate == PST_DEAD && !(stplyr->spectator) && (livescount || stplyr->deadtimer < (TICRATE<<1)))
livescount++;
if (livescount > 99) if (livescount > 99)
livescount = 99; livescount = 99;
V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8, V_DrawRightAlignedString(hudinfo[HUD_LIVES].x+58, hudinfo[HUD_LIVES].y+8,
@ -1960,7 +1963,7 @@ static void ST_drawWeaponRing(powertype_t weapon, INT32 rwflag, INT32 wepflag, I
static void ST_drawMatchHUD(void) static void ST_drawMatchHUD(void)
{ {
char penaltystr[5]; char penaltystr[7];
const INT32 y = 176; // HUD_LIVES const INT32 y = 176; // HUD_LIVES
INT32 offset = (BASEVIDWIDTH / 2) - (NUM_WEAPONS * 10) - 6; INT32 offset = (BASEVIDWIDTH / 2) - (NUM_WEAPONS * 10) - 6;
@ -2409,25 +2412,20 @@ static void ST_overlayDrawer(void)
} }
} }
// GAME OVER pic // GAME OVER hud
if ((gametype == GT_COOP) if ((gametype == GT_COOP)
&& (netgame || multiplayer) && (netgame || multiplayer)
&& (cv_cooplives.value == 0)) && (cv_cooplives.value == 0))
; ;
else if (G_GametypeUsesLives() && stplyr->lives <= 0 && !(hu_showscores && (netgame || multiplayer))) else if (G_GametypeUsesLives() && stplyr->lives <= 0 && !(hu_showscores && (netgame || multiplayer)))
{ {
patch_t *p; INT32 i = MAXPLAYERS;
INT32 deadtimer = stplyr->spectator ? TICRATE : (stplyr->deadtimer-(TICRATE<<1));
if (countdown == 1)
p = timeover;
else
p = sboover;
if ((gametype == GT_COOP) if ((gametype == GT_COOP)
&& (netgame || multiplayer) && (netgame || multiplayer)
&& (cv_cooplives.value != 1)) && (cv_cooplives.value != 1))
{ {
INT32 i;
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
if (!playeringame[i]) if (!playeringame[i])
@ -2437,15 +2435,21 @@ static void ST_overlayDrawer(void)
continue; continue;
if (players[i].lives > 0) if (players[i].lives > 0)
{
p = NULL;
break; break;
}
} }
} }
if (p) if (i == MAXPLAYERS && deadtimer >= 0)
V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, BASEVIDHEIGHT/2 - (SHORT(p->height)/2), V_PERPLAYER|(stplyr->spectator ? V_HUDTRANSHALF : V_HUDTRANS), p); {
const char *first = (countdown == 1) ? "TIME" : "GAME";
const char *second = "OVER";
INT32 w1 = V_LevelNameWidth(first), w2 = (w1 + 16 + V_LevelNameWidth(second))>>1;
INT32 lvlttlx1 = min(6*deadtimer, BASEVIDWIDTH/2), lvlttlx2 = BASEVIDWIDTH - lvlttlx1;
UINT32 flags = V_PERPLAYER|(stplyr->spectator ? V_HUDTRANSHALF : V_HUDTRANS);
V_DrawLevelTitle(lvlttlx1 - w2, (BASEVIDHEIGHT-16)>>1, flags, first);
V_DrawLevelTitle(lvlttlx2 + w1 + 16 - w2, (BASEVIDHEIGHT-16)>>1, flags, "OVER");
}
} }
if (G_GametypeHasTeams()) if (G_GametypeHasTeams())