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.
if (G_IsSpecialStage(gamemap))
{
INT32 i, count, increment, spheres;
INT32 i, count, sincrement, spheres, rincrement, rings;
for (i = 0, count = 0; i < MAXPLAYERS; i++)
{
@ -2425,18 +2425,35 @@ static void CL_RemovePlayer(INT32 playernum, INT32 reason)
count--;
spheres = players[playernum].spheres;
increment = spheres/count;
rings = players[playernum].rings;
sincrement = spheres/count;
rincrement = rings/count;
for (i = 0; i < MAXPLAYERS; i++)
{
if (playeringame[i] && i != playernum)
{
if (spheres < increment)
if (spheres < 2*sincrement)
{
P_GivePlayerSpheres(&players[i], spheres);
spheres = 0;
}
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.
if (G_TagGametype())
P_CheckSurvivors();
@ -3600,7 +3592,7 @@ static void CoopLives_OnChange(void)
{
case 0:
CONS_Printf(M_GetText("Players can now respawn indefinitely.\n"));
return;
break;
case 1:
CONS_Printf(M_GetText("Lives are now per-player.\n"));
return;

View File

@ -215,7 +215,7 @@ UINT16 spacetimetics = 11*TICRATE + (TICRATE/2);
UINT16 extralifetics = 4*TICRATE;
UINT16 nightslinktics = 2*TICRATE;
INT32 gameovertics = 15*TICRATE;
INT32 gameovertics = 10*TICRATE;
UINT8 ammoremovaltics = 2*TICRATE;
@ -2145,6 +2145,8 @@ void G_PlayerReborn(INT32 player)
boolean outofcoop;
INT16 bot;
SINT8 pity;
INT16 rings;
INT16 spheres;
score = players[player].score;
lives = players[player].lives;
@ -2199,6 +2201,17 @@ void G_PlayerReborn(INT32 player)
bot = players[player].bot;
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];
memset(p, 0, sizeof (*p));
@ -2252,6 +2265,8 @@ void G_PlayerReborn(INT32 player)
if (bot)
p->bot = 1; // reset to AI-controlled
p->pity = pity;
p->rings = rings;
p->spheres = spheres;
// Don't do anything immediately
p->pflags |= PF_USEDOWN;
@ -2259,7 +2274,6 @@ void G_PlayerReborn(INT32 player)
p->pflags |= PF_JUMPDOWN;
p->playerstate = PST_LIVE;
p->rings = p->spheres = 0; // 0 rings
p->panim = PA_IDLE; // standing animation
//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);
players[playernum].rings = mapheaderinfo[gamemap-1]->startrings;
if (starpost) //Don't even bother with looking for a place to spawn.
{
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;
}
if (G_IsSpecialStage(gamemap))
if (G_IsSpecialStage(gamemap) && !(damagetype & DMG_DEATHMASK))
{
P_SpecialStageDamage(player, inflictor, source);
return true;
@ -3524,10 +3524,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da
// Instant-Death
if (damagetype & DMG_DEATHMASK)
{
P_KillPlayer(player, source, damage);
player->rings = player->spheres = 0;
}
else if (metalrecording)
{
if (!inflictor)

View File

@ -10397,7 +10397,7 @@ void P_SpawnPlayer(INT32 playernum)
&& ((leveltime > 0
&& ((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_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
{
@ -10464,7 +10464,6 @@ void P_SpawnPlayer(INT32 playernum)
P_SetupStateAnimation(mobj, mobj->state);
mobj->health = 1;
p->rings = p->spheres = 0;
p->playerstate = PST_LIVE;
p->bonustime = false;

View File

@ -9027,19 +9027,22 @@ boolean P_GetLives(player_t *player)
INT32 i, maxlivesplayer = -1, livescheck = 1;
if (!(netgame || multiplayer)
|| (gametype != GT_COOP)
|| (cv_cooplives.value == 1)
|| (player->lives == INFLIVES))
return true;
if ((cv_cooplives.value == 2 || cv_cooplives.value == 0) && player->lives > 0)
return true;
if (cv_cooplives.value == 0) // infinite lives
{
player->lives++;
if (player->lives < 1)
player->lives = 1;
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++)
{
if (!playeringame[i])
@ -9146,7 +9149,7 @@ static void P_DeathThink(player_t *player)
// continue logic
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();
else if (player->deadtimer >= gameovertics)
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.
if (player->deadtimer > 30*TICRATE && !G_PlatformGametype())
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)
{
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->outofcoop = true;
@ -9191,16 +9194,11 @@ static void P_DeathThink(player_t *player)
player->playerstate = PST_REBORN;
else switch(gametype) {
case GT_COOP:
if (player->deadtimer > TICRATE)
player->playerstate = PST_REBORN;
break;
case GT_COMPETITION:
case GT_RACE:
if (player->deadtimer > TICRATE)
player->playerstate = PST_REBORN;
break;
case GT_RACE:
player->playerstate = PST_REBORN;
break;
default:
if (player->deadtimer > cv_respawntime.value*TICRATE)
player->playerstate = PST_REBORN;
@ -9209,7 +9207,7 @@ static void P_DeathThink(player_t *player)
}
// Single player auto respawn
if (!(netgame || multiplayer) && player->deadtimer > 5*TICRATE)
if (!(netgame || multiplayer) && player->deadtimer > TICRATE<<1)
player->playerstate = PST_REBORN;
}
}
@ -11011,8 +11009,6 @@ void P_PlayerThink(player_t *player)
{
if (gametype != GT_COOP)
player->score = 0;
player->mo->health = 1;
player->rings = player->spheres = 0;
}
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
static patch_t *nrec_timer; // Timer for NiGHTS records
static patch_t *sborings;
static patch_t *sboover;
static patch_t *timeover;
static patch_t *stlivex;
static patch_t *sboredrings;
static patch_t *sboredtime;
@ -253,8 +251,6 @@ void ST_LoadGraphics(void)
sbocolon = W_CachePatchName("STTCOLON", PU_HUDGFX); // Colon for time
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);
livesback = W_CachePatchName("STLIVEBK", PU_HUDGFX);
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));
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
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);
else
{
if (stplyr->playerstate == PST_DEAD && !(stplyr->spectator) && (livescount || stplyr->deadtimer < (TICRATE<<1)))
livescount++;
if (livescount > 99)
livescount = 99;
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)
{
char penaltystr[5];
char penaltystr[7];
const INT32 y = 176; // HUD_LIVES
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)
&& (netgame || multiplayer)
&& (cv_cooplives.value == 0))
;
else if (G_GametypeUsesLives() && stplyr->lives <= 0 && !(hu_showscores && (netgame || multiplayer)))
{
patch_t *p;
if (countdown == 1)
p = timeover;
else
p = sboover;
INT32 i = MAXPLAYERS;
INT32 deadtimer = stplyr->spectator ? TICRATE : (stplyr->deadtimer-(TICRATE<<1));
if ((gametype == GT_COOP)
&& (netgame || multiplayer)
&& (cv_cooplives.value != 1))
{
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i])
@ -2437,15 +2435,21 @@ static void ST_overlayDrawer(void)
continue;
if (players[i].lives > 0)
{
p = NULL;
break;
}
}
}
if (p)
V_DrawScaledPatch((BASEVIDWIDTH - SHORT(p->width))/2, BASEVIDHEIGHT/2 - (SHORT(p->height)/2), V_PERPLAYER|(stplyr->spectator ? V_HUDTRANSHALF : V_HUDTRANS), p);
if (i == MAXPLAYERS && deadtimer >= 0)
{
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())