Merge branch 'exitfix' into 'next'

Only Consider ExitLevel a Cheat When Used to Cheat

See merge request STJr/SRB2!2159
This commit is contained in:
Logan Aerl Arias 2024-01-02 03:05:24 +00:00
commit 4dd2415262
7 changed files with 183 additions and 148 deletions

View file

@ -2151,7 +2151,7 @@ boolean G_Responder(event_t *ev)
if (! netgame) if (! netgame)
F_StartGameEvaluation(); F_StartGameEvaluation();
else if (server || IsPlayerAdmin(consoleplayer)) else if (server || IsPlayerAdmin(consoleplayer))
D_SendExitLevel(false); SendNetXCmd(XD_EXITLEVEL, NULL, 0);
return true; return true;
} }
} }
@ -4000,14 +4000,137 @@ static void G_HandleSaveLevel(void)
} }
} }
//
// G_GetNextMap
//
INT16 G_GetNextMap(boolean ignoretokens, boolean silent)
{
INT32 i;
INT16 newmapnum;
boolean spec = G_IsSpecialStage(gamemap);
// go to next level
// newmapnum is 0-based, unlike gamemap
if (nextmapoverride != 0)
newmapnum = (INT16)(nextmapoverride-1);
else if (marathonmode && mapheaderinfo[gamemap-1]->marathonnext)
newmapnum = (INT16)(mapheaderinfo[gamemap-1]->marathonnext-1);
else
{
newmapnum = (INT16)(mapheaderinfo[gamemap-1]->nextlevel-1);
if (marathonmode && newmapnum == spmarathon_start-1)
newmapnum = 1100-1; // No infinite loop for you
}
INT16 gametype_to_use;
if (nextgametype >= 0 && nextgametype < gametypecount)
gametype_to_use = nextgametype;
else
gametype_to_use = gametype;
// If newmapnum is actually going to get used, make sure it points to
// a map of the proper gametype -- skip levels that don't support
// the current gametype. (Helps avoid playing boss levels in Race,
// for instance).
if (!spec || nextmapoverride)
{
if (newmapnum >= 0 && newmapnum < NUMMAPS)
{
INT16 cm = newmapnum;
UINT32 tolflag = G_TOLFlag(gametype_to_use);
UINT8 visitedmap[(NUMMAPS+7)/8];
memset(visitedmap, 0, sizeof (visitedmap));
while (!mapheaderinfo[cm] || !(mapheaderinfo[cm]->typeoflevel & tolflag))
{
visitedmap[cm/8] |= (1<<(cm&7));
if (!mapheaderinfo[cm])
cm = -1; // guarantee error execution
else if (marathonmode && mapheaderinfo[cm]->marathonnext)
cm = (INT16)(mapheaderinfo[cm]->marathonnext-1);
else
cm = (INT16)(mapheaderinfo[cm]->nextlevel-1);
if (cm >= NUMMAPS || cm < 0) // out of range (either 1100ish or error)
{
cm = newmapnum; //Start the loop again so that the error checking below is executed.
//Make sure the map actually exists before you try to go to it!
if ((W_CheckNumForName(G_BuildMapName(cm + 1)) == LUMPERROR))
{
if (!silent)
CONS_Alert(CONS_ERROR, M_GetText("Next map given (MAP %d) doesn't exist! Reverting to MAP01.\n"), cm+1);
cm = 0;
break;
}
}
if (visitedmap[cm/8] & (1<<(cm&7))) // smells familiar
{
// We got stuck in a loop, came back to the map we started on
// without finding one supporting the current gametype.
// Thus, print a warning, and just use this map anyways.
if (!silent)
CONS_Alert(CONS_WARNING, M_GetText("Can't find a compatible map after map %d; using map %d anyway\n"), prevmap+1, cm+1);
break;
}
}
newmapnum = cm;
}
// wrap around in race
if (newmapnum >= 1100-1 && newmapnum <= 1102-1 && !(gametyperules & GTR_CAMPAIGN))
newmapnum = (INT16)(spstage_start-1);
if (newmapnum < 0 || (newmapnum >= NUMMAPS && newmapnum < 1100-1) || newmapnum > 1103-1)
I_Error("Followed map %d to invalid map %d\n", prevmap + 1, newmapnum + 1);
if (!spec)
lastmap = newmapnum; // Remember last map for when you come out of the special stage.
}
if (!ignoretokens && (gottoken = ((gametyperules & GTR_SPECIALSTAGES) && token)))
{
token--;
// if (!nextmapoverride) // Having a token should pull the player into the special stage before going to the overridden map (Issue #933)
for (i = 0; i < 7; i++)
if (!(emeralds & (1<<i)))
{
newmapnum = ((netgame || multiplayer) ? smpstage_start : sstage_start) + i - 1; // to special stage!
break;
}
if (i == 7)
{
gottoken = false;
token = 0;
}
}
if (spec && (!gottoken || ignoretokens) && !nextmapoverride)
newmapnum = lastmap; // Exiting from a special stage? Go back to the game. Tails 08-11-2001
if (!(gametyperules & GTR_CAMPAIGN))
{
if (cv_advancemap.value == 0) // Stay on same map.
newmapnum = prevmap;
else if (cv_advancemap.value == 2) // Go to random map.
newmapnum = RandMap(G_TOLFlag(gametype_to_use), prevmap);
}
return newmapnum;
}
// //
// G_DoCompleted // G_DoCompleted
// //
static void G_DoCompleted(void) static void G_DoCompleted(void)
{ {
INT32 i; INT32 i;
boolean spec = G_IsSpecialStage(gamemap);
tokenlist = 0; // Reset the list tokenlist = 0; // Reset the list
if (modeattacking && pausedelay) if (modeattacking && pausedelay)
@ -4032,120 +4155,12 @@ static void G_DoCompleted(void)
S_StopSounds(); S_StopSounds();
//Get and set prevmap/nextmap
prevmap = (INT16)(gamemap-1); prevmap = (INT16)(gamemap-1);
nextmap = G_GetNextMap(false, false);
// go to next level
// nextmap is 0-based, unlike gamemap
if (nextmapoverride != 0)
nextmap = (INT16)(nextmapoverride-1);
else if (marathonmode && mapheaderinfo[gamemap-1]->marathonnext)
nextmap = (INT16)(mapheaderinfo[gamemap-1]->marathonnext-1);
else
{
nextmap = (INT16)(mapheaderinfo[gamemap-1]->nextlevel-1);
if (marathonmode && nextmap == spmarathon_start-1)
nextmap = 1100-1; // No infinite loop for you
}
INT16 gametype_to_use;
if (nextgametype >= 0 && nextgametype < gametypecount)
gametype_to_use = nextgametype;
else
gametype_to_use = gametype;
// If nextmap is actually going to get used, make sure it points to
// a map of the proper gametype -- skip levels that don't support
// the current gametype. (Helps avoid playing boss levels in Race,
// for instance).
if (!spec || nextmapoverride)
{
if (nextmap >= 0 && nextmap < NUMMAPS)
{
INT16 cm = nextmap;
UINT32 tolflag = G_TOLFlag(gametype_to_use);
UINT8 visitedmap[(NUMMAPS+7)/8];
memset(visitedmap, 0, sizeof (visitedmap));
while (!mapheaderinfo[cm] || !(mapheaderinfo[cm]->typeoflevel & tolflag))
{
visitedmap[cm/8] |= (1<<(cm&7));
if (!mapheaderinfo[cm])
cm = -1; // guarantee error execution
else if (marathonmode && mapheaderinfo[cm]->marathonnext)
cm = (INT16)(mapheaderinfo[cm]->marathonnext-1);
else
cm = (INT16)(mapheaderinfo[cm]->nextlevel-1);
if (cm >= NUMMAPS || cm < 0) // out of range (either 1100ish or error)
{
cm = nextmap; //Start the loop again so that the error checking below is executed.
//Make sure the map actually exists before you try to go to it!
if ((W_CheckNumForName(G_BuildMapName(cm + 1)) == LUMPERROR))
{
CONS_Alert(CONS_ERROR, M_GetText("Next map given (MAP %d) doesn't exist! Reverting to MAP01.\n"), cm+1);
cm = 0;
break;
}
}
if (visitedmap[cm/8] & (1<<(cm&7))) // smells familiar
{
// We got stuck in a loop, came back to the map we started on
// without finding one supporting the current gametype.
// Thus, print a warning, and just use this map anyways.
CONS_Alert(CONS_WARNING, M_GetText("Can't find a compatible map after map %d; using map %d anyway\n"), prevmap+1, cm+1);
break;
}
}
nextmap = cm;
}
// wrap around in race
if (nextmap >= 1100-1 && nextmap <= 1102-1 && !(gametyperules & GTR_CAMPAIGN))
nextmap = (INT16)(spstage_start-1);
if (nextmap < 0 || (nextmap >= NUMMAPS && nextmap < 1100-1) || nextmap > 1103-1)
I_Error("Followed map %d to invalid map %d\n", prevmap + 1, nextmap + 1);
if (!spec)
lastmap = nextmap; // Remember last map for when you come out of the special stage.
}
if ((gottoken = ((gametyperules & GTR_SPECIALSTAGES) && token)))
{
token--;
// if (!nextmapoverride) // Having a token should pull the player into the special stage before going to the overridden map (Issue #933)
for (i = 0; i < 7; i++)
if (!(emeralds & (1<<i)))
{
nextmap = ((netgame || multiplayer) ? smpstage_start : sstage_start) + i - 1; // to special stage!
break;
}
if (i == 7)
{
gottoken = false;
token = 0;
}
}
if (spec && !gottoken && !nextmapoverride)
nextmap = lastmap; // Exiting from a special stage? Go back to the game. Tails 08-11-2001
automapactive = false; automapactive = false;
if (!(gametyperules & GTR_CAMPAIGN))
{
if (cv_advancemap.value == 0) // Stay on same map.
nextmap = prevmap;
else if (cv_advancemap.value == 2) // Go to random map.
nextmap = RandMap(G_TOLFlag(gametype_to_use), prevmap);
}
// We are committed to this map now. // We are committed to this map now.
// We may as well allocate its header if it doesn't exist // We may as well allocate its header if it doesn't exist
// (That is, if it's a real map) // (That is, if it's a real map)

View file

@ -216,6 +216,7 @@ boolean G_CoopGametype(void);
boolean G_TagGametype(void); boolean G_TagGametype(void);
boolean G_CompetitionGametype(void); boolean G_CompetitionGametype(void);
boolean G_EnoughPlayersFinished(void); boolean G_EnoughPlayersFinished(void);
INT16 G_GetNextMap(boolean ignoretokens, boolean silent);
void G_ExitLevel(void); void G_ExitLevel(void);
void G_NextLevel(void); void G_NextLevel(void);
void G_Continue(void); void G_Continue(void);

View file

@ -4495,37 +4495,62 @@ static void Command_Mapmd5_f(void)
CONS_Printf(M_GetText("You must be in a level to use this.\n")); CONS_Printf(M_GetText("You must be in a level to use this.\n"));
} }
void D_SendExitLevel(boolean cheat)
{
UINT8 buf[8];
UINT8 *buf_p = buf;
WRITEUINT8(buf_p, cheat);
SendNetXCmd(XD_EXITLEVEL, &buf, buf_p - buf);
}
static void Command_ExitLevel_f(void) static void Command_ExitLevel_f(void)
{ {
if (!(server || (IsPlayerAdmin(consoleplayer)))) if (!(server || (IsPlayerAdmin(consoleplayer))))
CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n"));
else if (( gamestate != GS_LEVEL && gamestate != GS_CREDITS ) || demoplayback) else if (( gamestate != GS_LEVEL && gamestate != GS_CREDITS ) || demoplayback)
CONS_Printf(M_GetText("You must be in a level to use this.\n")); CONS_Printf(M_GetText("You must be in a level to use this.\n"));
else if (usedCheats)
SendNetXCmd(XD_EXITLEVEL, NULL, 0); // Does it matter if it's a cheat if we've used one already?
else if (!(splitscreen || multiplayer || cv_debug))
CONS_Printf(M_GetText("Cheats must be enabled to force a level exit in single player.\n"));
else else
D_SendExitLevel(true); {
if (G_IsSpecialStage(gamemap) // If you wanna give up that emerald then go right ahead!
|| gamestate == GS_CREDITS) // Somebody hasn't heard of the credits skip button...
{
SendNetXCmd(XD_EXITLEVEL, NULL, 0);
return;
}
// Allow exiting without cheating if at least one player beat the level
// Consistent with just setting playersforexit to one
if (splitscreen || multiplayer)
{
INT32 i;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].spectator || players[i].bot)
continue;
if (players[i].quittime > 30 * TICRATE)
continue;
if (players[i].lives <= 0)
continue;
if ((players[i].pflags & PF_FINISHED) || players[i].exiting)
{
SendNetXCmd(XD_EXITLEVEL, NULL, 0);
return;
}
}
}
// Only consider it a cheat if we're not allowed to go to the next map
if (M_CampaignWarpIsCheat(gametype, G_GetNextMap(true, true) + 1, serverGamedata))
CONS_Alert(CONS_NOTICE, M_GetText("Cheats must be enabled to force exit to a locked level!\n"));
else
SendNetXCmd(XD_EXITLEVEL, NULL, 0);
}
} }
static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum) static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum)
{ {
boolean cheat = false; (void)cp;
cheat = (boolean)READUINT8(*cp);
// Ignore duplicate XD_EXITLEVEL commands. // Ignore duplicate XD_EXITLEVEL commands.
if (gameaction == ga_completed) if (gameaction == ga_completed)
{
return; return;
}
if (playernum != serverplayer && !IsPlayerAdmin(playernum)) if (playernum != serverplayer && !IsPlayerAdmin(playernum))
{ {
@ -4535,11 +4560,6 @@ static void Got_ExitLevelcmd(UINT8 **cp, INT32 playernum)
return; return;
} }
if (G_CoopGametype() && cheat)
{
G_SetUsedCheats(false);
}
G_ExitLevel(); G_ExitLevel();
} }

View file

@ -201,7 +201,6 @@ void D_SendPlayerConfig(void);
void Command_ExitGame_f(void); void Command_ExitGame_f(void);
void Command_Retry_f(void); void Command_Retry_f(void);
void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore void D_GameTypeChanged(INT32 lastgametype); // not a real _OnChange function anymore
void D_SendExitLevel(boolean cheat);
void D_MapChange(INT32 pmapnum, INT32 pgametype, boolean pultmode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pfromlevelselect); void D_MapChange(INT32 pmapnum, INT32 pgametype, boolean pultmode, boolean presetplayers, INT32 pdelay, boolean pskipprecutscene, boolean pfromlevelselect);
boolean IsPlayerAdmin(INT32 playernum); boolean IsPlayerAdmin(INT32 playernum);
void SetAdminPlayer(INT32 playernum); void SetAdminPlayer(INT32 playernum);

View file

@ -2244,7 +2244,7 @@ void P_CheckTimeLimit(void)
} }
if (server) if (server)
D_SendExitLevel(false); SendNetXCmd(XD_EXITLEVEL, NULL, 0);
} }
//Optional tie-breaker for Match/CTF //Optional tie-breaker for Match/CTF
@ -2307,11 +2307,11 @@ void P_CheckTimeLimit(void)
} }
} }
if (server) if (server)
D_SendExitLevel(false); SendNetXCmd(XD_EXITLEVEL, NULL, 0);
} }
if (server) if (server)
D_SendExitLevel(false); SendNetXCmd(XD_EXITLEVEL, NULL, 0);
} }
/** Checks if a player's score is over the pointlimit and the round should end. /** Checks if a player's score is over the pointlimit and the round should end.
@ -2340,7 +2340,7 @@ void P_CheckPointLimit(void)
if ((UINT32)cv_pointlimit.value <= redscore || (UINT32)cv_pointlimit.value <= bluescore) if ((UINT32)cv_pointlimit.value <= redscore || (UINT32)cv_pointlimit.value <= bluescore)
{ {
if (server) if (server)
D_SendExitLevel(false); SendNetXCmd(XD_EXITLEVEL, NULL, 0);
} }
} }
else else
@ -2353,7 +2353,7 @@ void P_CheckPointLimit(void)
if ((UINT32)cv_pointlimit.value <= players[i].score) if ((UINT32)cv_pointlimit.value <= players[i].score)
{ {
if (server) if (server)
D_SendExitLevel(false); SendNetXCmd(XD_EXITLEVEL, NULL, 0);
return; return;
} }
} }
@ -2397,7 +2397,7 @@ void P_CheckSurvivors(void)
{ {
CONS_Printf(M_GetText("The IT player has left the game.\n")); CONS_Printf(M_GetText("The IT player has left the game.\n"));
if (server) if (server)
D_SendExitLevel(false); SendNetXCmd(XD_EXITLEVEL, NULL, 0);
return; return;
} }
@ -2417,7 +2417,7 @@ void P_CheckSurvivors(void)
{ {
CONS_Printf(M_GetText("All players have been tagged!\n")); CONS_Printf(M_GetText("All players have been tagged!\n"));
if (server) if (server)
D_SendExitLevel(false); SendNetXCmd(XD_EXITLEVEL, NULL, 0);
} }
return; return;
@ -2429,7 +2429,7 @@ void P_CheckSurvivors(void)
{ {
CONS_Printf(M_GetText("There are no players able to become IT.\n")); CONS_Printf(M_GetText("There are no players able to become IT.\n"));
if (server) if (server)
D_SendExitLevel(false); SendNetXCmd(XD_EXITLEVEL, NULL, 0);
} }
return; return;
@ -2441,7 +2441,7 @@ void P_CheckSurvivors(void)
{ {
CONS_Printf(M_GetText("All players have been tagged!\n")); CONS_Printf(M_GetText("All players have been tagged!\n"));
if (server) if (server)
D_SendExitLevel(false); SendNetXCmd(XD_EXITLEVEL, NULL, 0);
} }
} }

View file

@ -8315,7 +8315,7 @@ static boolean P_LoadAddon(UINT16 numlumps)
{ {
CONS_Printf(M_GetText("Current map %d replaced by added file, ending the level to ensure consistency.\n"), gamemap); CONS_Printf(M_GetText("Current map %d replaced by added file, ending the level to ensure consistency.\n"), gamemap);
if (server) if (server)
D_SendExitLevel(false); SendNetXCmd(XD_EXITLEVEL, NULL, 0);
} }
return true; return true;

View file

@ -11904,7 +11904,7 @@ void P_PlayerThink(player_t *player)
if (!total || ((4*exiting)/total) >= numneeded) if (!total || ((4*exiting)/total) >= numneeded)
{ {
if (server) if (server)
D_SendExitLevel(false); SendNetXCmd(XD_EXITLEVEL, NULL, 0);
} }
else else
player->exiting = 3; player->exiting = 3;
@ -11912,7 +11912,7 @@ void P_PlayerThink(player_t *player)
else else
{ {
if (server) if (server)
D_SendExitLevel(false); SendNetXCmd(XD_EXITLEVEL, NULL, 0);
} }
} }