Introducing Marathon Run. (I was going to call it Marathon Mode, but NiGHTS Mode being right next to it on the menu looked terrible.)

Basically a dedicated Record Attack-like experience for speedrunning the game as a continuous chunk rather than ILs. Has several quality of life features.

Benefits include:
* An unambiguous real-time bar across the bottom of the screen, always displaying the current time, ticking up until you reach the ending.
* Disable the console (pausing is still allowed, but the timer will still increment).
* Automatically skip intermissions as if you're holding down the spin button.
* Show centiseconds on HUD automatically, like record attack.
* "Live Event Backups" - a category of run fit for major events like GDQ, where recovery from crashes or chokes makes for better entertainment. Essentially a modified SP savefile, down to using the same basic functions, but has its own filename and tweaked internal layout.
* "spmarathon_start" MainCfg block parameter and "marathonnext" mapheader parameter, allowing for a customised flow (makes this fit for purpose for an eventual SUGOI port).
* Disabling inter-level custom cutscenes by default with a menu option to toggle this (won't show up if the mod doesn't *have* any custom cutscenes), although either way ending cutscenes (vanilla or custom) remain intact since is time is called before them.
* Won't show up if you have a mod that consists of only one level (determined by spmarathon_start's nextlevel; this won't trip if you manually set its marathonnext).
* Unconditional gratitude on the evaluation screen, instead of a negging "Try again..." if you didn't get all the emeralds (which you may not have been aiming for).
* Gorgeous new menu (no new assets required, unless you wanna give it a header later).

Changes which were required for the above but affect other areas of the game include:
* "useBlackRock" MainCFG block parameter, which can be used to disable the presence of the Black Rock or Egg Rock in both the Evaluation screen and the Marathon Run menu (for total conversions with different stories).
* Disabling Continues in NiGHTS mode, to match the most common singleplayer experience post 2.2.4's release (is reverted if useContinues is set to true).
* Hiding the exitmove "powerup" outside of multiplayer. (Okay, this isn't really related, I just saw this bug in action a lot while doing test runs and got annoyed enough to fix it here.)
* The ability to use V_DrawPromptBack (in hardcode only at the moment, but) to draw in terms of pixels rather than rows of text, by providing negative instead of positive inputs).
* A refactoring of redundant game saves smattered across the ending, credits, and evaluation - in addition to saving the game slightly earlier.
* Minor m_menu.c touchups and refactorings here and there.

Built using feedback from the official server's #speedruns channel, among other places.
This commit is contained in:
toaster 2020-05-14 23:10:00 +01:00
parent feced5ec3c
commit d593e2e1bb
22 changed files with 692 additions and 96 deletions

View File

@ -770,7 +770,7 @@ boolean CON_Responder(event_t *ev)
// check for console toggle key // check for console toggle key
if (ev->type != ev_console) if (ev->type != ev_console)
{ {
if (modeattacking || metalrecording) if (modeattacking || metalrecording || marathonmode)
return false; return false;
if (key == gamecontrol[gc_console][0] || key == gamecontrol[gc_console][1]) if (key == gamecontrol[gc_console][0] || key == gamecontrol[gc_console][1])

View File

@ -815,6 +815,7 @@ void D_StartTitle(void)
// In case someone exits out at the same time they start a time attack run, // In case someone exits out at the same time they start a time attack run,
// reset modeattacking // reset modeattacking
modeattacking = ATTACKING_NONE; modeattacking = ATTACKING_NONE;
marathonmode = 0;
// empty maptol so mario/etc sounds don't play in sound test when they shouldn't // empty maptol so mario/etc sounds don't play in sound test when they shouldn't
maptol = 0; maptol = 0;
@ -1131,6 +1132,7 @@ void D_SRB2Main(void)
// default savegame // default savegame
strcpy(savegamename, SAVEGAMENAME"%u.ssg"); strcpy(savegamename, SAVEGAMENAME"%u.ssg");
strcpy(liveeventbackup,"liveevent.bkp"); // intentionally not ending with .ssg
{ {
const char *userhome = D_Home(); //Alam: path to home const char *userhome = D_Home(); //Alam: path to home
@ -1159,6 +1161,7 @@ void D_SRB2Main(void)
// can't use sprintf since there is %u in savegamename // can't use sprintf since there is %u in savegamename
strcatbf(savegamename, srb2home, PATHSEP); strcatbf(savegamename, srb2home, PATHSEP);
strcatbf(liveeventbackup, srb2home, PATHSEP);
snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", srb2home); snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", srb2home);
#else // DEFAULTDIR #else // DEFAULTDIR
@ -1171,6 +1174,7 @@ void D_SRB2Main(void)
// can't use sprintf since there is %u in savegamename // can't use sprintf since there is %u in savegamename
strcatbf(savegamename, userhome, PATHSEP); strcatbf(savegamename, userhome, PATHSEP);
strcatbf(liveeventbackup, userhome, PATHSEP);
snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", userhome); snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", userhome);
#endif // DEFAULTDIR #endif // DEFAULTDIR

View File

@ -1578,6 +1578,22 @@ static void readlevelheader(MYFILE *f, INT32 num)
mapheaderinfo[num-1]->nextlevel = (INT16)i; mapheaderinfo[num-1]->nextlevel = (INT16)i;
} }
else if (fastcmp(word, "MARATHONNEXT"))
{
if (fastcmp(word2, "TITLE")) i = 1100;
else if (fastcmp(word2, "EVALUATION")) i = 1101;
else if (fastcmp(word2, "CREDITS")) i = 1102;
else if (fastcmp(word2, "ENDING")) i = 1103;
else
// Support using the actual map name,
// i.e., MarathonNext = AB, MarathonNext = FZ, etc.
// Convert to map number
if (word2[0] >= 'A' && word2[0] <= 'Z' && word2[2] == '\0')
i = M_MapNumber(word2[0], word2[1]);
mapheaderinfo[num-1]->marathonnext = (INT16)i;
}
else if (fastcmp(word, "TYPEOFLEVEL")) else if (fastcmp(word, "TYPEOFLEVEL"))
{ {
if (i) // it's just a number if (i) // it's just a number
@ -3920,7 +3936,20 @@ static void readmaincfg(MYFILE *f)
else else
value = get_number(word2); value = get_number(word2);
spstage_start = (INT16)value; spstage_start = spmarathon_start = (INT16)value;
}
else if (fastcmp(word, "SPMARATHON_START"))
{
// Support using the actual map name,
// i.e., Level AB, Level FZ, etc.
// Convert to map number
if (word2[0] >= 'A' && word2[0] <= 'Z')
value = M_MapNumber(word2[0], word2[1]);
else
value = get_number(word2);
spmarathon_start = (INT16)value;
} }
else if (fastcmp(word, "SSTAGE_START")) else if (fastcmp(word, "SSTAGE_START"))
{ {
@ -4014,6 +4043,17 @@ static void readmaincfg(MYFILE *f)
introtoplay = 128; introtoplay = 128;
introchanged = true; introchanged = true;
} }
else if (fastcmp(word, "CREDITSCUTSCENE"))
{
creditscutscene = (UINT8)get_number(word2);
// range check, you morons.
if (creditscutscene > 128)
creditscutscene = 128;
}
else if (fastcmp(word, "USEBLACKROCK"))
{
useBlackRock = (UINT8)(value || word2[0] == 'T' || word2[0] == 'Y');
}
else if (fastcmp(word, "LOOPTITLE")) else if (fastcmp(word, "LOOPTITLE"))
{ {
looptitle = (value || word2[0] == 'T' || word2[0] == 'Y'); looptitle = (value || word2[0] == 'T' || word2[0] == 'Y');
@ -4115,13 +4155,6 @@ static void readmaincfg(MYFILE *f)
titlescrollyspeed = get_number(word2); titlescrollyspeed = get_number(word2);
titlechanged = true; titlechanged = true;
} }
else if (fastcmp(word, "CREDITSCUTSCENE"))
{
creditscutscene = (UINT8)get_number(word2);
// range check, you morons.
if (creditscutscene > 128)
creditscutscene = 128;
}
else if (fastcmp(word, "DISABLESPEEDADJUST")) else if (fastcmp(word, "DISABLESPEEDADJUST"))
{ {
disableSpeedAdjust = (value || word2[0] == 'T' || word2[0] == 'Y'); disableSpeedAdjust = (value || word2[0] == 'T' || word2[0] == 'Y');
@ -9895,6 +9928,11 @@ struct {
{"TC_BLINK",TC_BLINK}, {"TC_BLINK",TC_BLINK},
{"TC_DASHMODE",TC_DASHMODE}, {"TC_DASHMODE",TC_DASHMODE},
// marathonmode flags
//{"MA_INIT",MA_INIT}, -- should never see this
{"MA_RUNNING",MA_RUNNING},
{"MA_NOCUTSCENES",MA_NOCUTSCENES},
{NULL,0} {NULL,0}
}; };

View File

@ -101,6 +101,9 @@ void I_FinishUpdate (void)
if (cv_showping.value && netgame && consoleplayer != serverplayer) if (cv_showping.value && netgame && consoleplayer != serverplayer)
SCR_DisplayLocalPing(); SCR_DisplayLocalPing();
if (marathonmode)
SCR_DisplayMarathonInfo();
//blast it to the screen //blast it to the screen
// this code sucks // this code sucks
//memcpy(dascreen,screens[0],screenwidth*screenheight); //memcpy(dascreen,screens[0],screenwidth*screenheight);

View File

@ -459,6 +459,7 @@ void CONS_Debug(INT32 debugflags, const char *fmt, ...) FUNCDEBUG;
// Things that used to be in dstrings.h // Things that used to be in dstrings.h
#define SAVEGAMENAME "srb2sav" #define SAVEGAMENAME "srb2sav"
char savegamename[256]; char savegamename[256];
char liveeventbackup[256];
// m_misc.h // m_misc.h
#ifdef GETTEXT #ifdef GETTEXT

View File

@ -45,7 +45,18 @@ extern INT32 curWeather;
extern INT32 cursaveslot; extern INT32 cursaveslot;
//extern INT16 lastmapsaved; //extern INT16 lastmapsaved;
extern INT16 lastmaploaded; extern INT16 lastmaploaded;
extern boolean gamecomplete; extern UINT8 gamecomplete;
// Extra abilities/settings for skins (combinable stuff)
typedef enum
{
MA_RUNNING = 1, // In action
MA_INIT = 1<<1, // Initialisation
MA_NOCUTSCENES = 1<<2 // No cutscenes
} marathonmode_t;
extern marathonmode_t marathonmode;
extern tic_t marathontime;
#define maxgameovers 13 #define maxgameovers 13
extern UINT8 numgameovers; extern UINT8 numgameovers;
@ -127,7 +138,7 @@ extern INT32 displayplayer;
extern INT32 secondarydisplayplayer; // for splitscreen extern INT32 secondarydisplayplayer; // for splitscreen
// Maps of special importance // Maps of special importance
extern INT16 spstage_start; extern INT16 spstage_start, spmarathon_start;
extern INT16 sstage_start, sstage_end, smpstage_start, smpstage_end; extern INT16 sstage_start, sstage_end, smpstage_start, smpstage_end;
extern INT16 titlemap; extern INT16 titlemap;
@ -289,6 +300,7 @@ typedef struct
UINT8 actnum; ///< Act number or 0 for none. UINT8 actnum; ///< Act number or 0 for none.
UINT32 typeoflevel; ///< Combination of typeoflevel flags. UINT32 typeoflevel; ///< Combination of typeoflevel flags.
INT16 nextlevel; ///< Map number of next level, or 1100-1102 to end. INT16 nextlevel; ///< Map number of next level, or 1100-1102 to end.
INT16 marathonnext; ///< See nextlevel, but for Marathon mode. Necessary to support hub worlds ala SUGOI.
char keywords[33]; ///< Keywords separated by space to search for. 32 characters. char keywords[33]; ///< Keywords separated by space to search for. 32 characters.
char musname[7]; ///< Music track to play. "" for no music. char musname[7]; ///< Music track to play. "" for no music.
UINT16 mustrack; ///< Subsong to play. Only really relevant for music modules and specific formats supported by GME. 0 to ignore. UINT16 mustrack; ///< Subsong to play. Only really relevant for music modules and specific formats supported by GME. 0 to ignore.
@ -575,11 +587,12 @@ extern UINT16 nightslinktics;
extern UINT8 introtoplay; extern UINT8 introtoplay;
extern UINT8 creditscutscene; extern UINT8 creditscutscene;
extern UINT8 useBlackRock;
extern UINT8 use1upSound; extern UINT8 use1upSound;
extern UINT8 maxXtraLife; // Max extra lives from rings extern UINT8 maxXtraLife; // Max extra lives from rings
extern UINT8 useContinues; extern UINT8 useContinues;
#define continuesInSession (!multiplayer && (useContinues || ultimatemode || !(cursaveslot > 0))) #define continuesInSession (!multiplayer && (ultimatemode || (useContinues && !marathonmode) || (!modeattacking && !(cursaveslot > 0))))
extern mobj_t *hunt1, *hunt2, *hunt3; // Emerald hunt locations extern mobj_t *hunt1, *hunt2, *hunt3; // Emerald hunt locations

View File

@ -1330,10 +1330,6 @@ void F_StartCredits(void)
// Just in case they're open ... somehow // Just in case they're open ... somehow
M_ClearMenus(true); M_ClearMenus(true);
// Save the second we enter the credits
if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
G_SaveGame((UINT32)cursaveslot);
if (creditscutscene) if (creditscutscene)
{ {
F_StartCustomCutscene(creditscutscene - 1, false, false); F_StartCustomCutscene(creditscutscene - 1, false, false);
@ -1529,12 +1525,6 @@ void F_StartGameEvaluation(void)
// Just in case they're open ... somehow // Just in case they're open ... somehow
M_ClearMenus(true); M_ClearMenus(true);
// Save the second we enter the evaluation
// We need to do this again! Remember, it's possible a mod designed skipped
// the credits sequence!
if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
G_SaveGame((UINT32)cursaveslot);
goodending = (ALL7EMERALDS(emeralds)); goodending = (ALL7EMERALDS(emeralds));
gameaction = ga_nothing; gameaction = ga_nothing;
@ -1551,13 +1541,20 @@ void F_GameEvaluationDrawer(void)
angle_t fa; angle_t fa;
INT32 eemeralds_cur; INT32 eemeralds_cur;
char patchname[7] = "CEMGx0"; char patchname[7] = "CEMGx0";
const char* endingtext = (goodending ? "CONGRATULATIONS!" : "TRY AGAIN..."); const char* endingtext;
if (marathonmode)
endingtext = "THANKS FOR THE RUN!";
else if (goodending)
endingtext = "CONGRATULATIONS!";
else
endingtext = "TRY AGAIN...";
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31); V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
// Draw all the good crap here. // Draw all the good crap here.
if (finalecount > 0) if (finalecount > 0 && useBlackRock)
{ {
INT32 scale = FRACUNIT; INT32 scale = FRACUNIT;
patch_t *rockpat; patch_t *rockpat;
@ -1841,10 +1838,6 @@ void F_StartEnding(void)
// Just in case they're open ... somehow // Just in case they're open ... somehow
M_ClearMenus(true); M_ClearMenus(true);
// Save before the credits sequence.
if ((!modifiedgame || savemoddata) && !(netgame || multiplayer) && cursaveslot > 0)
G_SaveGame((UINT32)cursaveslot);
gameaction = ga_nothing; gameaction = ga_nothing;
paused = false; paused = false;
CON_ToggleOff(); CON_ToggleOff();
@ -3959,6 +3952,7 @@ static void F_AdvanceToNextScene(void)
animtimer = pictime = cutscenes[cutnum]->scene[scenenum].picduration[picnum]; animtimer = pictime = cutscenes[cutnum]->scene[scenenum].picduration[picnum];
} }
// See also G_AfterIntermission, the only other place which handles intra-map/ending transitions
void F_EndCutScene(void) void F_EndCutScene(void)
{ {
cutsceneover = true; // do this first, just in case G_EndGame or something wants to turn it back false later cutsceneover = true; // do this first, just in case G_EndGame or something wants to turn it back false later

View File

@ -82,7 +82,10 @@ INT32 curWeather = PRECIP_NONE;
INT32 cursaveslot = 0; // Auto-save 1p savegame slot INT32 cursaveslot = 0; // Auto-save 1p savegame slot
//INT16 lastmapsaved = 0; // Last map we auto-saved at //INT16 lastmapsaved = 0; // Last map we auto-saved at
INT16 lastmaploaded = 0; // Last map the game loaded INT16 lastmaploaded = 0; // Last map the game loaded
boolean gamecomplete = false; UINT8 gamecomplete = 0;
marathonmode_t marathonmode = 0;
tic_t marathontime = 0;
UINT8 numgameovers = 0; // for startinglives balance UINT8 numgameovers = 0; // for startinglives balance
SINT8 startinglivesbalance[maxgameovers+1] = {3, 5, 7, 9, 12, 15, 20, 25, 30, 40, 50, 75, 99, 0x7F}; SINT8 startinglivesbalance[maxgameovers+1] = {3, 5, 7, 9, 12, 15, 20, 25, 30, 40, 50, 75, 99, 0x7F};
@ -118,7 +121,7 @@ UINT32 ssspheres; // old special stage
INT16 lastmap; // last level you were at (returning from special stages) INT16 lastmap; // last level you were at (returning from special stages)
tic_t timeinmap; // Ticker for time spent in level (used for levelcard display) tic_t timeinmap; // Ticker for time spent in level (used for levelcard display)
INT16 spstage_start; INT16 spstage_start, spmarathon_start;
INT16 sstage_start, sstage_end, smpstage_start, smpstage_end; INT16 sstage_start, sstage_end, smpstage_start, smpstage_end;
INT16 titlemap = 0; INT16 titlemap = 0;
@ -223,6 +226,7 @@ UINT8 useContinues = 0; // Set to 1 to enable continues outside of no-save scena
UINT8 introtoplay; UINT8 introtoplay;
UINT8 creditscutscene; UINT8 creditscutscene;
UINT8 useBlackRock = 1;
// Emerald locations // Emerald locations
mobj_t *hunt1; mobj_t *hunt1;
@ -769,6 +773,8 @@ void G_SetGameModified(boolean silent)
// If in record attack recording, cancel it. // If in record attack recording, cancel it.
if (modeattacking) if (modeattacking)
M_EndModeAttackRun(); M_EndModeAttackRun();
else if (marathonmode)
Command_ExitGame_f();
} }
/** Builds an original game map name from a map number. /** Builds an original game map name from a map number.
@ -3662,8 +3668,14 @@ static void G_DoCompleted(void)
// nextmap is 0-based, unlike gamemap // nextmap is 0-based, unlike gamemap
if (nextmapoverride != 0) if (nextmapoverride != 0)
nextmap = (INT16)(nextmapoverride-1); nextmap = (INT16)(nextmapoverride-1);
else if (marathonmode && mapheaderinfo[gamemap-1]->marathonnext)
nextmap = (INT16)(mapheaderinfo[gamemap-1]->marathonnext-1);
else else
{
nextmap = (INT16)(mapheaderinfo[gamemap-1]->nextlevel-1); nextmap = (INT16)(mapheaderinfo[gamemap-1]->nextlevel-1);
if (marathonmode && nextmap == spmarathon_start-1)
nextmap = 1100-1; // No infinite loop for you
}
// Remember last map for when you come out of the special stage. // Remember last map for when you come out of the special stage.
if (!spec) if (!spec)
@ -3687,10 +3699,12 @@ static void G_DoCompleted(void)
visitedmap[cm/8] |= (1<<(cm&7)); visitedmap[cm/8] |= (1<<(cm&7));
if (!mapheaderinfo[cm]) if (!mapheaderinfo[cm])
cm = -1; // guarantee error execution cm = -1; // guarantee error execution
else if (marathonmode && mapheaderinfo[cm]->marathonnext)
cm = (INT16)(mapheaderinfo[cm]->marathonnext-1);
else else
cm = (INT16)(mapheaderinfo[cm]->nextlevel-1); cm = (INT16)(mapheaderinfo[cm]->nextlevel-1);
if (cm >= NUMMAPS || cm < 0) // out of range (either 1100-1102 or error) 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. cm = nextmap; //Start the loop again so that the error checking below is executed.
@ -3759,6 +3773,25 @@ static void G_DoCompleted(void)
if (nextmap < NUMMAPS && !mapheaderinfo[nextmap]) if (nextmap < NUMMAPS && !mapheaderinfo[nextmap])
P_AllocMapHeader(nextmap); P_AllocMapHeader(nextmap);
// do this before going to the intermission or starting a custom cutscene, mostly for the sake of marathon mode but it also massively reduces redundant file save events in f_finale.c
if (nextmap >= 1100-1)
{
if (!gamecomplete)
gamecomplete = 2; // special temporary mode to prevent using SP level select in pause menu until the intermission is over without restricting it in every intermission
if (cursaveslot > 0)
{
if (marathonmode)
{
// don't keep a backup around when the run is done!
if (FIL_FileExists(liveeventbackup))
remove(liveeventbackup);
cursaveslot = 0;
}
else if ((!modifiedgame || savemoddata) && !(netgame || multiplayer))
G_SaveGame((UINT32)cursaveslot);
}
}
if ((skipstats && !modeattacking) || (spec && modeattacking && stagefailed)) if ((skipstats && !modeattacking) || (spec && modeattacking && stagefailed))
{ {
G_UpdateVisited(); G_UpdateVisited();
@ -3772,6 +3805,7 @@ static void G_DoCompleted(void)
} }
} }
// See also F_EndCutscene, the only other place which handles intra-map/ending transitions
void G_AfterIntermission(void) void G_AfterIntermission(void)
{ {
Y_CleanupScreenBuffer(); Y_CleanupScreenBuffer();
@ -3782,9 +3816,12 @@ void G_AfterIntermission(void)
return; return;
} }
if (gamecomplete == 2) // special temporary mode to prevent using SP level select in pause menu until the intermission is over without restricting it in every intermission
gamecomplete = 1;
HU_ClearCEcho(); HU_ClearCEcho();
if ((gametyperules & GTR_CUTSCENES) && mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking && skipstats <= 1) // Start a custom cutscene. if ((gametyperules & GTR_CUTSCENES) && mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking && skipstats <= 1 && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene.
F_StartCustomCutscene(mapheaderinfo[gamemap-1]->cutscenenum-1, false, false); F_StartCustomCutscene(mapheaderinfo[gamemap-1]->cutscenenum-1, false, false);
else else
{ {
@ -3925,7 +3962,7 @@ void G_EndGame(void)
void G_LoadGameSettings(void) void G_LoadGameSettings(void)
{ {
// defaults // defaults
spstage_start = 1; spstage_start = spmarathon_start = 1;
sstage_start = 50; sstage_start = 50;
sstage_end = 56; // 7 special stages in vanilla SRB2 sstage_end = 56; // 7 special stages in vanilla SRB2
sstage_end++; // plus one weirdo sstage_end++; // plus one weirdo
@ -4245,7 +4282,10 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride)
startonmapnum = mapoverride; startonmapnum = mapoverride;
#endif #endif
sprintf(savename, savegamename, slot); if (marathonmode)
strcpy(savename, liveeventbackup);
else
sprintf(savename, savegamename, slot);
length = FIL_ReadFile(savename, &savebuffer); length = FIL_ReadFile(savename, &savebuffer);
if (!length) if (!length)
@ -4257,7 +4297,7 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride)
save_p = savebuffer; save_p = savebuffer;
memset(vcheck, 0, sizeof (vcheck)); memset(vcheck, 0, sizeof (vcheck));
sprintf(vcheck, "version %d", VERSION); sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION);
if (strcmp((const char *)save_p, (const char *)vcheck)) if (strcmp((const char *)save_p, (const char *)vcheck))
{ {
#ifdef SAVEGAME_OTHERVERSIONS #ifdef SAVEGAME_OTHERVERSIONS
@ -4297,6 +4337,11 @@ void G_LoadGame(UINT32 slot, INT16 mapoverride)
memset(&savedata, 0, sizeof(savedata)); memset(&savedata, 0, sizeof(savedata));
return; return;
} }
if (marathonmode)
{
marathontime = READUINT32(save_p);
marathonmode |= READUINT8(save_p);
}
// done // done
Z_Free(savebuffer); Z_Free(savebuffer);
@ -4325,13 +4370,12 @@ void G_SaveGame(UINT32 slot)
char savename[256] = ""; char savename[256] = "";
const char *backup; const char *backup;
sprintf(savename, savegamename, slot); if (marathonmode)
strcpy(savename, liveeventbackup);
else
sprintf(savename, savegamename, slot);
backup = va("%s",savename); backup = va("%s",savename);
// save during evaluation or credits? game's over, folks!
if (gamestate == GS_ENDING || gamestate == GS_CREDITS || gamestate == GS_EVALUATION)
gamecomplete = true;
gameaction = ga_nothing; gameaction = ga_nothing;
{ {
char name[VERSIONSIZE]; char name[VERSIONSIZE];
@ -4345,10 +4389,15 @@ void G_SaveGame(UINT32 slot)
} }
memset(name, 0, sizeof (name)); memset(name, 0, sizeof (name));
sprintf(name, "version %d", VERSION); sprintf(name, (marathonmode ? "back-up %d" : "version %d"), VERSION);
WRITEMEM(save_p, name, VERSIONSIZE); WRITEMEM(save_p, name, VERSIONSIZE);
P_SaveGame(); P_SaveGame();
if (marathonmode)
{
WRITEUINT32(save_p, marathontime);
WRITEUINT8(save_p, (marathonmode & ~MA_INIT));
}
length = save_p - savebuffer; length = save_p - savebuffer;
saved = FIL_WriteFile(backup, savebuffer, length); saved = FIL_WriteFile(backup, savebuffer, length);
@ -4361,7 +4410,7 @@ void G_SaveGame(UINT32 slot)
if (cv_debug && saved) if (cv_debug && saved)
CONS_Printf(M_GetText("Game saved.\n")); CONS_Printf(M_GetText("Game saved.\n"));
else if (!saved) else if (!saved)
CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, savegamename); CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename));
} }
#define BADSAVE goto cleanup; #define BADSAVE goto cleanup;
@ -4374,7 +4423,10 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives)
char savename[255]; char savename[255];
const char *backup; const char *backup;
sprintf(savename, savegamename, slot); if (marathonmode)
strcpy(savename, liveeventbackup);
else
sprintf(savename, savegamename, slot);
backup = va("%s",savename); backup = va("%s",savename);
length = FIL_ReadFile(savename, &savebuffer); length = FIL_ReadFile(savename, &savebuffer);
@ -4393,7 +4445,7 @@ void G_SaveGameOver(UINT32 slot, boolean modifylives)
save_p = savebuffer; save_p = savebuffer;
// Version check // Version check
memset(vcheck, 0, sizeof (vcheck)); memset(vcheck, 0, sizeof (vcheck));
sprintf(vcheck, "version %d", VERSION); sprintf(vcheck, (marathonmode ? "back-up %d" : "version %d"), VERSION);
if (strcmp((const char *)save_p, (const char *)vcheck)) BADSAVE if (strcmp((const char *)save_p, (const char *)vcheck)) BADSAVE
save_p += VERSIONSIZE; save_p += VERSIONSIZE;
@ -4460,7 +4512,7 @@ cleanup:
if (cv_debug && saved) if (cv_debug && saved)
CONS_Printf(M_GetText("Game saved.\n")); CONS_Printf(M_GetText("Game saved.\n"));
else if (!saved) else if (!saved)
CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, savegamename); CONS_Alert(CONS_ERROR, M_GetText("Error while writing to %s for save slot %u, base: %s\n"), backup, slot, (marathonmode ? liveeventbackup : savegamename));
Z_Free(savebuffer); Z_Free(savebuffer);
save_p = savebuffer = NULL; save_p = savebuffer = NULL;
@ -4598,7 +4650,7 @@ void G_InitNew(UINT8 pultmode, const char *mapname, boolean resetplayer, boolean
automapactive = false; automapactive = false;
imcontinuing = false; imcontinuing = false;
if ((gametyperules & GTR_CUTSCENES) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking) // Start a custom cutscene. if ((gametyperules & GTR_CUTSCENES) && !skipprecutscene && mapheaderinfo[gamemap-1]->precutscenenum && !modeattacking && !(marathonmode & MA_NOCUTSCENES)) // Start a custom cutscene.
F_StartCustomCutscene(mapheaderinfo[gamemap-1]->precutscenenum-1, true, resetplayer); F_StartCustomCutscene(mapheaderinfo[gamemap-1]->precutscenenum-1, true, resetplayer);
else else
G_DoLoadLevel(resetplayer); G_DoLoadLevel(resetplayer);

View File

@ -904,7 +904,11 @@ void HWR_DrawTutorialBack(UINT32 color, INT32 boxheight)
{ {
FOutVector v[4]; FOutVector v[4];
FSurfaceInfo Surf; FSurfaceInfo Surf;
INT32 height = (boxheight * 4) + (boxheight/2)*5; // 4 lines of space plus gaps between and some leeway INT32 height;
if (boxheight < 0)
height = -boxheight;
else
height = (boxheight * 4) + (boxheight/2)*5; // 4 lines of space plus gaps between and some leeway
// setup some neat-o translucency effect // setup some neat-o translucency effect

View File

@ -2009,6 +2009,8 @@ static int mapheaderinfo_get(lua_State *L)
lua_pushinteger(L, header->typeoflevel); lua_pushinteger(L, header->typeoflevel);
else if (fastcmp(field,"nextlevel")) else if (fastcmp(field,"nextlevel"))
lua_pushinteger(L, header->nextlevel); lua_pushinteger(L, header->nextlevel);
else if (fastcmp(field,"marathonnext"))
lua_pushinteger(L, header->marathonnext);
else if (fastcmp(field,"keywords")) else if (fastcmp(field,"keywords"))
lua_pushstring(L, header->keywords); lua_pushstring(L, header->keywords);
else if (fastcmp(field,"musname")) else if (fastcmp(field,"musname"))

View File

@ -115,7 +115,10 @@ int LUA_PushGlobals(lua_State *L, const char *word)
lua_pushboolean(L, splitscreen); lua_pushboolean(L, splitscreen);
return 1; return 1;
} else if (fastcmp(word,"gamecomplete")) { } else if (fastcmp(word,"gamecomplete")) {
lua_pushboolean(L, gamecomplete); lua_pushboolean(L, (gamecomplete != 0));
return 1;
} else if (fastcmp(word,"marathonmode")) {
lua_pushinteger(L, marathonmode);
return 1; return 1;
} else if (fastcmp(word,"devparm")) { } else if (fastcmp(word,"devparm")) {
lua_pushboolean(L, devparm); lua_pushboolean(L, devparm);
@ -145,6 +148,9 @@ int LUA_PushGlobals(lua_State *L, const char *word)
} else if (fastcmp(word,"spstage_start")) { } else if (fastcmp(word,"spstage_start")) {
lua_pushinteger(L, spstage_start); lua_pushinteger(L, spstage_start);
return 1; return 1;
} else if (fastcmp(word,"spmarathon_start")) {
lua_pushinteger(L, spmarathon_start);
return 1;
} else if (fastcmp(word,"sstage_start")) { } else if (fastcmp(word,"sstage_start")) {
lua_pushinteger(L, sstage_start); lua_pushinteger(L, sstage_start);
return 1; return 1;

View File

@ -182,6 +182,7 @@ static tic_t keydown = 0;
static void M_GoBack(INT32 choice); static void M_GoBack(INT32 choice);
static void M_StopMessage(INT32 choice); static void M_StopMessage(INT32 choice);
static boolean stopstopmessage = false;
#ifndef NONET #ifndef NONET
static void M_HandleServerPage(INT32 choice); static void M_HandleServerPage(INT32 choice);
@ -252,6 +253,7 @@ static void M_ConfirmTeamScramble(INT32 choice);
static void M_ConfirmTeamChange(INT32 choice); static void M_ConfirmTeamChange(INT32 choice);
static void M_SecretsMenu(INT32 choice); static void M_SecretsMenu(INT32 choice);
static void M_SetupChoosePlayer(INT32 choice); static void M_SetupChoosePlayer(INT32 choice);
static UINT8 M_SetupChoosePlayerDirect(INT32 choice);
static void M_QuitSRB2(INT32 choice); static void M_QuitSRB2(INT32 choice);
menu_t SP_MainDef, OP_MainDef; menu_t SP_MainDef, OP_MainDef;
menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef; menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef;
@ -272,9 +274,14 @@ static void M_ModeAttackEndGame(INT32 choice);
static void M_SetGuestReplay(INT32 choice); static void M_SetGuestReplay(INT32 choice);
static void M_HandleChoosePlayerMenu(INT32 choice); static void M_HandleChoosePlayerMenu(INT32 choice);
static void M_ChoosePlayer(INT32 choice); static void M_ChoosePlayer(INT32 choice);
static void M_MarathonLiveEventBackup(INT32 choice);
static void M_Marathon(INT32 choice);
static void M_HandleMarathonChoosePlayer(INT32 choice);
static void M_StartMarathon(INT32 choice);
menu_t SP_LevelStatsDef; menu_t SP_LevelStatsDef;
static menu_t SP_TimeAttackDef, SP_ReplayDef, SP_GuestReplayDef, SP_GhostDef; static menu_t SP_TimeAttackDef, SP_ReplayDef, SP_GuestReplayDef, SP_GhostDef;
static menu_t SP_NightsAttackDef, SP_NightsReplayDef, SP_NightsGuestReplayDef, SP_NightsGhostDef; static menu_t SP_NightsAttackDef, SP_NightsReplayDef, SP_NightsGuestReplayDef, SP_NightsGhostDef;
static menu_t SP_MarathonDef;
// Multiplayer // Multiplayer
static void M_SetupMultiPlayer(INT32 choice); static void M_SetupMultiPlayer(INT32 choice);
@ -354,6 +361,7 @@ static void M_DrawLoad(void);
static void M_DrawLevelStats(void); static void M_DrawLevelStats(void);
static void M_DrawTimeAttackMenu(void); static void M_DrawTimeAttackMenu(void);
static void M_DrawNightsAttackMenu(void); static void M_DrawNightsAttackMenu(void);
static void M_DrawMarathon(void);
static void M_DrawSetupChoosePlayerMenu(void); static void M_DrawSetupChoosePlayerMenu(void);
static void M_DrawControlsDefMenu(void); static void M_DrawControlsDefMenu(void);
static void M_DrawCameraOptionsMenu(void); static void M_DrawCameraOptionsMenu(void);
@ -476,6 +484,11 @@ static consvar_t cv_dummylives = {"dummylives", "0", CV_HIDEN, liveslimit_cons_t
static consvar_t cv_dummycontinues = {"dummycontinues", "0", CV_HIDEN, contlimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL}; static consvar_t cv_dummycontinues = {"dummycontinues", "0", CV_HIDEN, contlimit_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
static consvar_t cv_dummymares = {"dummymares", "Overall", CV_HIDEN|CV_CALL, dummymares_cons_t, Dummymares_OnChange, 0, NULL, NULL, 0, 0, NULL}; static consvar_t cv_dummymares = {"dummymares", "Overall", CV_HIDEN|CV_CALL, dummymares_cons_t, Dummymares_OnChange, 0, NULL, NULL, 0, 0, NULL};
CV_PossibleValue_t marathon_cons_t[] = {{0, "Standard"}, {1, "Live Event Backup"}, {2, "Ultimate"}, {0, NULL}};
consvar_t cv_dummymarathon = {"dummymarathon", "Standard", CV_HIDEN, marathon_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
consvar_t cv_dummycutscenes = {"dummycutscenes", "Off", CV_HIDEN, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
// ========================================================================== // ==========================================================================
// ORGANIZATION START. // ORGANIZATION START.
// ========================================================================== // ==========================================================================
@ -746,10 +759,11 @@ static menuitem_t SR_EmblemHintMenu[] =
// Single Player Main // Single Player Main
static menuitem_t SP_MainMenu[] = static menuitem_t SP_MainMenu[] =
{ {
{IT_CALL | IT_STRING, NULL, "Start Game", M_LoadGame, 84}, {IT_CALL | IT_STRING, NULL, "Start Game", M_LoadGame, 76},
{IT_SECRET, NULL, "Record Attack", M_TimeAttack, 92}, {IT_SECRET, NULL, "Record Attack", M_TimeAttack, 84},
{IT_SECRET, NULL, "NiGHTS Mode", M_NightsAttack, 100}, {IT_SECRET, NULL, "NiGHTS Mode", M_NightsAttack, 92},
{IT_CALL | IT_STRING, NULL, "Tutorial", M_StartTutorial, 108}, {IT_CALL | IT_STRING, NULL, "Tutorial", M_StartTutorial, 100},
{IT_CALL | IT_STRING | IT_CALL_NOTMODIFIED, NULL, "Marathon Run", M_Marathon, 108},
{IT_CALL | IT_STRING | IT_CALL_NOTMODIFIED, NULL, "Statistics", M_Statistics, 116} {IT_CALL | IT_STRING | IT_CALL_NOTMODIFIED, NULL, "Statistics", M_Statistics, 116}
}; };
@ -759,6 +773,7 @@ enum
sprecordattack, sprecordattack,
spnightsmode, spnightsmode,
sptutorial, sptutorial,
spmarathon,
spstatistics spstatistics
}; };
@ -901,6 +916,23 @@ enum
nastart nastart
}; };
// Marathon
static menuitem_t SP_MarathonMenu[] =
{
{IT_STRING|IT_KEYHANDLER, NULL, "Character", M_HandleMarathonChoosePlayer, 100},
{IT_STRING|IT_CVAR, NULL, "Category", &cv_dummymarathon, 110},
{IT_STRING|IT_CVAR, NULL, "Cutscenes", &cv_dummycutscenes, 120},
{IT_WHITESTRING|IT_CALL, NULL, "Start", M_StartMarathon, 130},
};
enum
{
marathonplayer,
marathonultimate,
marathoncutscenes,
marathonstart
};
// Statistics // Statistics
static menuitem_t SP_LevelStatsMenu[] = static menuitem_t SP_LevelStatsMenu[] =
{ {
@ -1908,6 +1940,18 @@ static menu_t SP_NightsGhostDef =
NULL NULL
}; };
static menu_t SP_MarathonDef =
{
MTREE2(MN_SP_MAIN, MN_SP_MARATHON),
"M_ATTACK", // temporary
sizeof(SP_MarathonMenu)/sizeof(menuitem_t),
&MainDef, // Doesn't matter.
SP_MarathonMenu,
M_DrawMarathon,
32, 40,
0,
NULL
};
menu_t SP_PlayerDef = menu_t SP_PlayerDef =
{ {
@ -2524,6 +2568,8 @@ void M_InitMenuPresTables(void)
strncpy(menupres[i].musname, "_recat", 7); strncpy(menupres[i].musname, "_recat", 7);
else if (i == MN_SP_NIGHTSATTACK) else if (i == MN_SP_NIGHTSATTACK)
strncpy(menupres[i].musname, "_nitat", 7); strncpy(menupres[i].musname, "_nitat", 7);
else if (i == MN_SP_MARATHON)
strncpy(menupres[i].musname, "spec8", 6);
else if (i == MN_SP_PLAYER || i == MN_SR_PLAYER) else if (i == MN_SP_PLAYER || i == MN_SR_PLAYER)
strncpy(menupres[i].musname, "_chsel", 7); strncpy(menupres[i].musname, "_chsel", 7);
else if (i == MN_SR_SOUNDTEST) else if (i == MN_SR_SOUNDTEST)
@ -3018,7 +3064,7 @@ static void M_GoBack(INT32 choice)
netgame = multiplayer = false; netgame = multiplayer = false;
} }
if ((currentMenu->prevMenu == &MainDef) && (currentMenu == &SP_TimeAttackDef || currentMenu == &SP_NightsAttackDef)) if ((currentMenu->prevMenu == &MainDef) && (currentMenu == &SP_TimeAttackDef || currentMenu == &SP_NightsAttackDef || currentMenu == &SP_MarathonDef))
{ {
// D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate. // D_StartTitle does its own wipe, since GS_TIMEATTACK is now a complete gamestate.
@ -3403,11 +3449,14 @@ boolean M_Responder(event_t *ev)
{ {
if (currentMenu->menuitems[itemOn].alphaKey != MM_EVENTHANDLER) if (currentMenu->menuitems[itemOn].alphaKey != MM_EVENTHANDLER)
{ {
if (ch == ' ' || ch == 'n' || ch == 'y' || ch == KEY_ESCAPE || ch == KEY_ENTER) if (ch == ' ' || ch == 'n' || ch == 'y' || ch == KEY_ESCAPE || ch == KEY_ENTER || ch == KEY_DEL)
{ {
if (routine) if (routine)
routine(ch); routine(ch);
M_StopMessage(0); if (stopstopmessage)
stopstopmessage = false;
else
M_StopMessage(0);
noFurtherInput = true; noFurtherInput = true;
return true; return true;
} }
@ -3657,7 +3706,7 @@ void M_StartControlPanel(void)
{ {
INT32 numlives = 2; INT32 numlives = 2;
SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA)) ? (IT_STRING | IT_CALL) : (IT_DISABLED); SPauseMenu[spause_pandora].status = (M_SecretUnlocked(SECRET_PANDORA) && !marathonmode) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
if (&players[consoleplayer]) if (&players[consoleplayer])
{ {
@ -3675,7 +3724,7 @@ void M_StartControlPanel(void)
} }
// We can always use level select though. :33 // We can always use level select though. :33
SPauseMenu[spause_levelselect].status = (gamecomplete) ? (IT_STRING | IT_CALL) : (IT_DISABLED); SPauseMenu[spause_levelselect].status = (gamecomplete == 1) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
// And emblem hints. // And emblem hints.
SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS)) ? (IT_STRING | IT_CALL) : (IT_DISABLED); SPauseMenu[spause_hints].status = (M_SecretUnlocked(SECRET_EMBLEMHINTS)) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
@ -3859,6 +3908,8 @@ void M_Init(void)
CV_RegisterVar(&cv_dummylives); CV_RegisterVar(&cv_dummylives);
CV_RegisterVar(&cv_dummycontinues); CV_RegisterVar(&cv_dummycontinues);
CV_RegisterVar(&cv_dummymares); CV_RegisterVar(&cv_dummymares);
CV_RegisterVar(&cv_dummymarathon);
CV_RegisterVar(&cv_dummycutscenes);
quitmsg[QUITMSG] = M_GetText("Eggman's tied explosives\nto your girlfriend, and\nwill activate them if\nyou press the 'Y' key!\nPress 'N' to save her!\n\n(Press 'Y' to quit)"); quitmsg[QUITMSG] = M_GetText("Eggman's tied explosives\nto your girlfriend, and\nwill activate them if\nyou press the 'Y' key!\nPress 'N' to save her!\n\n(Press 'Y' to quit)");
quitmsg[QUITMSG1] = M_GetText("What would Tails say if\nhe saw you quitting the game?\n\n(Press 'Y' to quit)"); quitmsg[QUITMSG1] = M_GetText("What would Tails say if\nhe saw you quitting the game?\n\n(Press 'Y' to quit)");
@ -6321,8 +6372,8 @@ static char *M_AddonsHeaderPath(void)
static void M_AddonsClearName(INT32 choice) static void M_AddonsClearName(INT32 choice)
{ {
(void)choice;
CLEARNAME; CLEARNAME;
M_StopMessage(choice);
} }
// returns whether to do message draw // returns whether to do message draw
@ -6354,7 +6405,7 @@ static boolean M_AddonsRefresh(void)
if (message) if (message)
{ {
M_StartMessage(message,M_AddonsClearName,MM_EVENTHANDLER); M_StartMessage(message,M_AddonsClearName,MM_NOTHING);
return true; return true;
} }
@ -8011,6 +8062,15 @@ static void M_SinglePlayerMenu(INT32 choice)
SP_MainMenu[sptutorial].status = tutorialmap ? IT_CALL|IT_STRING : IT_NOTHING|IT_DISABLED; SP_MainMenu[sptutorial].status = tutorialmap ? IT_CALL|IT_STRING : IT_NOTHING|IT_DISABLED;
// If the FIRST stage immediately leads to the ending, or itself (which gets converted to the title screen in G_DoCompleted for marathonmode only), there's no point in having this option on the menu. You should use Record Attack in that circumstance, although if marathonnext is set this behaviour can be overridden if you make some weird mod that requires multiple playthroughs of the same map in sequence and has some in-level mechanism to break the cycle.
if (mapheaderinfo[spmarathon_start-1]
&& !mapheaderinfo[spmarathon_start-1]->marathonnext
&& (mapheaderinfo[spmarathon_start-1]->nextlevel == spmarathon_start
|| mapheaderinfo[spmarathon_start-1]->nextlevel >= 1100))
SP_MainMenu[spmarathon].status = IT_NOTHING|IT_DISABLED;
else
SP_MainMenu[spmarathon].status = IT_CALL|IT_STRING|IT_CALL_NOTMODIFIED;
M_SetupNextMenu(&SP_MainDef); M_SetupNextMenu(&SP_MainDef);
} }
@ -8101,7 +8161,7 @@ static void M_StartTutorial(INT32 choice)
emeralds = 0; emeralds = 0;
memset(&luabanks, 0, sizeof(luabanks)); memset(&luabanks, 0, sizeof(luabanks));
M_ClearMenus(true); M_ClearMenus(true);
gamecomplete = false; gamecomplete = 0;
cursaveslot = 0; cursaveslot = 0;
G_DeferedInitNew(false, G_BuildMapName(tutorialmap), 0, false, false); G_DeferedInitNew(false, G_BuildMapName(tutorialmap), 0, false, false);
} }
@ -8864,7 +8924,7 @@ static void M_CacheCharacterSelect(void)
{ {
INT32 i, skinnum; INT32 i, skinnum;
for (i = 0; i < 32; i++) for (i = 0; i < MAXSKINS; i++)
{ {
if (!description[i].used) if (!description[i].used)
continue; continue;
@ -8876,7 +8936,7 @@ static void M_CacheCharacterSelect(void)
} }
} }
static void M_SetupChoosePlayer(INT32 choice) static UINT8 M_SetupChoosePlayerDirect(INT32 choice)
{ {
INT32 skinnum; INT32 skinnum;
UINT8 i; UINT8 i;
@ -8887,7 +8947,7 @@ static void M_SetupChoosePlayer(INT32 choice)
if (!mapheaderinfo[startmap-1] || mapheaderinfo[startmap-1]->forcecharacter[0] == '\0') if (!mapheaderinfo[startmap-1] || mapheaderinfo[startmap-1]->forcecharacter[0] == '\0')
{ {
for (i = 0; i < 32; i++) // Handle charsels, availability, and unlocks. for (i = 0; i < MAXSKINS; i++) // Handle charsels, availability, and unlocks.
{ {
if (description[i].used) // If the character's disabled through SOC, there's nothing we can do for it. if (description[i].used) // If the character's disabled through SOC, there's nothing we can do for it.
{ {
@ -8895,7 +8955,7 @@ static void M_SetupChoosePlayer(INT32 choice)
if (and) if (and)
{ {
char firstskin[SKINNAMESIZE+1]; char firstskin[SKINNAMESIZE+1];
if (mapheaderinfo[startmap-1]->typeoflevel & TOL_NIGHTS) // skip tagteam characters for NiGHTS levels if (mapheaderinfo[startmap-1] && mapheaderinfo[startmap-1]->typeoflevel & TOL_NIGHTS) // skip tagteam characters for NiGHTS levels
continue; continue;
strncpy(firstskin, description[i].skinname, (and - description[i].skinname)); strncpy(firstskin, description[i].skinname, (and - description[i].skinname));
firstskin[(and - description[i].skinname)] = '\0'; firstskin[(and - description[i].skinname)] = '\0';
@ -8932,14 +8992,36 @@ static void M_SetupChoosePlayer(INT32 choice)
if (firstvalid == lastvalid) // We're being forced into a specific character, so might as well just skip it. if (firstvalid == lastvalid) // We're being forced into a specific character, so might as well just skip it.
{ {
M_ChoosePlayer(firstvalid); return firstvalid;
return;
} }
// One last bit of order we can't do in the iteration above. // One last bit of order we can't do in the iteration above.
description[firstvalid].prev = lastvalid; description[firstvalid].prev = lastvalid;
description[lastvalid].next = firstvalid; description[lastvalid].next = firstvalid;
if (!allowed)
{
char_on = firstvalid;
if (startchar > 0 && startchar < MAXSKINS)
{
INT16 workchar = startchar;
while (workchar--)
char_on = description[char_on].next;
}
}
return MAXSKINS;
}
static void M_SetupChoosePlayer(INT32 choice)
{
UINT8 skinset = M_SetupChoosePlayerDirect(choice);
if (skinset != MAXSKINS)
{
M_ChoosePlayer(skinset);
return;
}
M_ChangeMenuMusic("_chsel", true); M_ChangeMenuMusic("_chsel", true);
/* the menus suck -James */ /* the menus suck -James */
@ -8954,16 +9036,6 @@ static void M_SetupChoosePlayer(INT32 choice)
SP_PlayerDef.prevMenu = currentMenu; SP_PlayerDef.prevMenu = currentMenu;
M_SetupNextMenu(&SP_PlayerDef); M_SetupNextMenu(&SP_PlayerDef);
if (!allowed)
{
char_on = firstvalid;
if (startchar > 0 && startchar < 32)
{
INT16 workchar = startchar;
while (workchar--)
char_on = description[char_on].next;
}
}
// finish scrolling the menu // finish scrolling the menu
char_scroll = 0; char_scroll = 0;
@ -9022,6 +9094,10 @@ static void M_HandleChoosePlayerMenu(INT32 choice)
case KEY_ENTER: case KEY_ENTER:
S_StartSound(NULL, sfx_menu1); S_StartSound(NULL, sfx_menu1);
char_scroll = 0; // finish scrolling the menu
M_DrawSetupChoosePlayerMenu(); // draw the finally selected character one last time for the fadeout
// Is this a hack?
charseltimer = 0;
M_ChoosePlayer(char_on); M_ChoosePlayer(char_on);
break; break;
@ -9261,7 +9337,7 @@ static void M_DrawSetupChoosePlayerMenu(void)
// Chose the player you want to use Tails 03-02-2002 // Chose the player you want to use Tails 03-02-2002
static void M_ChoosePlayer(INT32 choice) static void M_ChoosePlayer(INT32 choice)
{ {
boolean ultmode = (ultimate_selectable && SP_PlayerDef.prevMenu == &SP_LoadDef && saveSlotSelected == NOSAVESLOT); boolean ultmode = (currentMenu == &SP_MarathonDef) ? (cv_dummymarathon.value == 2) : (ultimate_selectable && SP_PlayerDef.prevMenu == &SP_LoadDef && saveSlotSelected == NOSAVESLOT);
UINT8 skinnum; UINT8 skinnum;
// skip this if forcecharacter or no characters available // skip this if forcecharacter or no characters available
@ -9273,11 +9349,6 @@ static void M_ChoosePlayer(INT32 choice)
// M_SetupChoosePlayer didn't call us directly, that means we've been properly set up. // M_SetupChoosePlayer didn't call us directly, that means we've been properly set up.
else else
{ {
char_scroll = 0; // finish scrolling the menu
M_DrawSetupChoosePlayerMenu(); // draw the finally selected character one last time for the fadeout
// Is this a hack?
charseltimer = 0;
skinnum = description[choice].skinnum[0]; skinnum = description[choice].skinnum[0];
if ((botingame = (description[choice].skinnum[1] != -1))) { if ((botingame = (description[choice].skinnum[1] != -1))) {
@ -9291,11 +9362,11 @@ static void M_ChoosePlayer(INT32 choice)
M_ClearMenus(true); M_ClearMenus(true);
if (startmap != spstage_start) if (!marathonmode && startmap != spstage_start)
cursaveslot = 0; cursaveslot = 0;
//lastmapsaved = 0; //lastmapsaved = 0;
gamecomplete = false; gamecomplete = 0;
G_DeferedInitNew(ultmode, G_BuildMapName(startmap), skinnum, false, fromlevelselect); G_DeferedInitNew(ultmode, G_BuildMapName(startmap), skinnum, false, fromlevelselect);
COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this
@ -10294,6 +10365,364 @@ static void M_ModeAttackEndGame(INT32 choice)
Nextmap_OnChange(); Nextmap_OnChange();
} }
static void M_MarathonLiveEventBackup(INT32 choice)
{
if (choice == 'y' || choice == KEY_ENTER)
{
marathonmode = MA_INIT;
G_LoadGame(MARATHONSLOT, 0);
cursaveslot = MARATHONSLOT;
if (!(marathonmode & MA_RUNNING))
marathonmode = 0;
return;
}
else if (choice == KEY_DEL)
{
M_StopMessage(0);
if (FIL_FileExists(liveeventbackup)) // just in case someone deleted it while we weren't looking.
remove(liveeventbackup);
BwehHehHe();
M_StartMessage("Live event backup erased.\n",M_Marathon,MM_NOTHING);
stopstopmessage = true;
return;
}
M_Marathon(-1);
}
// Going to Marathon menu...
static void M_Marathon(INT32 choice)
{
UINT8 skinset;
INT32 mapnum = 0;
if (choice != -1 && FIL_FileExists(liveeventbackup))
{
M_StartMessage(\
"\x82Live event backup detected.\n\x80\
Do you want to resurrect the last run?\n\
(Fs in chat if we crashed on stream.)\n\
\n\
Press 'Y' or 'Enter' to resume,\n\
'Del' to delete, or any other\n\
key to continue to Marathon Run.",M_MarathonLiveEventBackup,MM_YESNO);
return;
}
fromlevelselect = false;
startmap = spmarathon_start;
CV_SetValue(&cv_newgametype, GT_COOP); // Graue 09-08-2004
skinset = M_SetupChoosePlayerDirect(-1);
SP_MarathonMenu[marathonplayer].status = (skinset == MAXSKINS) ? IT_KEYHANDLER|IT_STRING : IT_NOTHING|IT_DISABLED;
while (mapnum < NUMMAPS)
{
if (mapheaderinfo[mapnum])
{
if (mapheaderinfo[mapnum]->cutscenenum || mapheaderinfo[mapnum]->precutscenenum)
break;
}
mapnum++;
}
SP_MarathonMenu[marathoncutscenes].status = (mapnum < NUMMAPS) ? IT_CVAR |IT_STRING : IT_NOTHING|IT_DISABLED;
M_ChangeMenuMusic("spec8", true);
SP_MarathonDef.prevMenu = &MainDef;
G_SetGamestate(GS_TIMEATTACK); // do this before M_SetupNextMenu so that menu meta state knows that we're switching
titlemapinaction = TITLEMAP_OFF; // Nope don't give us HOMs please
M_SetupNextMenu(&SP_MarathonDef);
itemOn = marathonstart; // "Start" is selected.
recatkdrawtimer = 50-8;
char_scroll = 0;
}
static void M_HandleMarathonChoosePlayer(INT32 choice)
{
INT32 selectval;
if (keydown > 1)
return;
switch (choice)
{
case KEY_DOWNARROW:
M_NextOpt();
break;
case KEY_UPARROW:
M_PrevOpt();
break;
case KEY_LEFTARROW:
if ((selectval = description[char_on].prev) == char_on)
return;
char_on = selectval;
break;
case KEY_RIGHTARROW:
if ((selectval = description[char_on].next) == char_on)
return;
char_on = selectval;
break;
case KEY_ESCAPE:
noFurtherInput = true;
M_GoBack(0);
return;
default:
return;
}
S_StartSound(NULL, sfx_menu1);
}
static void M_StartMarathon(INT32 choice)
{
(void)choice;
marathontime = 0;
marathonmode = MA_RUNNING|MA_INIT;
if (cv_dummymarathon.value == 1)
cursaveslot = MARATHONSLOT;
if (!cv_dummycutscenes.value)
marathonmode |= MA_NOCUTSCENES;
M_ChoosePlayer(char_on);
}
// Drawing function for Marathon menu
void M_DrawMarathon(void)
{
INT32 i, x, y, cursory = 0, cnt, soffset = 0, w;
UINT16 dispstatus;
consvar_t *cv;
const char *cvstring;
char *work;
angle_t fa;
INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy), xspan = (vid.width/dupz), yspan = (vid.height/dupz), diffx = (xspan - BASEVIDWIDTH)/2, diffy = (yspan - BASEVIDHEIGHT)/2, maxy = BASEVIDHEIGHT + diffy;
// lactozilla: the renderer changed so recache patches
if (needpatchrecache)
M_CacheCharacterSelect();
curbgxspeed = 0;
curbgyspeed = 18;
M_ChangeMenuMusic("spec8", true); // Eww, but needed for when user hits escape during demo playback
V_DrawFill(-diffx, -diffy, diffx+(BASEVIDWIDTH-190)/2, yspan, 158);
V_DrawFill((BASEVIDWIDTH-190)/2, -diffy, 190, yspan, 31);
V_DrawFill((BASEVIDWIDTH+190)/2, -diffy, diffx+(BASEVIDWIDTH-190)/2, yspan, 158);
//M_DrawRecordAttackForeground();
if (curfadevalue)
V_DrawFadeScreen(0xFF00, curfadevalue);
x = (((BASEVIDWIDTH-82)/2)+11)<<FRACBITS;
y = (((BASEVIDHEIGHT-82)/2)+12-10)<<FRACBITS;
cnt = (36*(recatkdrawtimer<<FRACBITS))/TICRATE;
fa = (FixedAngle(cnt)>>ANGLETOFINESHIFT) & FINEMASK;
y -= (10*FINECOSINE(fa));
recatkdrawtimer++;
soffset = cnt = (recatkdrawtimer%50);
if (!useBlackRock)
{
if (cnt > 8)
cnt = 8;
V_DrawFixedPatch(x+(6<<FRACBITS), y, FRACUNIT/2, (cnt&~1)<<(V_ALPHASHIFT-1), W_CachePatchName("RECCLOCK", PU_PATCH), NULL);
}
else if (cnt > 8)
{
cnt = 8;
V_DrawFixedPatch(x, y, FRACUNIT, V_TRANSLUCENT, W_CachePatchName("ENDEGRK5", PU_PATCH), NULL);
}
else
{
V_DrawFixedPatch(x, y, FRACUNIT, cnt<<V_ALPHASHIFT, W_CachePatchName("ROID0000", PU_PATCH), NULL);
V_DrawFixedPatch(x, y, FRACUNIT, V_TRANSLUCENT, W_CachePatchName("ENDEGRK5", PU_PATCH), NULL);
V_DrawFixedPatch(x, y, FRACUNIT, cnt<<V_ALPHASHIFT, W_CachePatchName("ENDEGRK0", PU_PATCH), NULL);
}
{
UINT8 col;
i = 0;
w = (((8-cnt)+1)/3)+1;
w *= w;
cursory = 0;
while (i < cnt)
{
i++;
col = 158+((cnt-i)/3);
if (col >= 160)
col = 253;
V_DrawFill(((BASEVIDWIDTH-190)/2)-cursory-w, -diffy, w, yspan, col);
V_DrawFill(((BASEVIDWIDTH+190)/2)+cursory, -diffy, w, yspan, col);
cursory += w;
w *= 2;
}
}
w = char_scroll + (((8-cnt)*(8-cnt))<<(FRACBITS-5));
if (soffset == 50-1)
w += FRACUNIT/2;
{
patch_t *fg = W_CachePatchName("RECATKFG", PU_PATCH);
INT32 trans = V_60TRANS+((cnt&~3)<<(V_ALPHASHIFT-2));
INT32 height = (SHORT(fg->height)/2);
char patchname[7] = "CEMGx0";
dupz = (w*7)/6; //(w*42*120)/(360*6); -- I don't know why this works but I'm not going to complain.
dupz = ((dupz>>FRACBITS) % height);
y = height/2;
while (y+dupz >= -diffy)
y -= height;
while (y-2-dupz < maxy)
{
V_DrawFixedPatch(((BASEVIDWIDTH-190)<<(FRACBITS-1)), (y-2-dupz)<<FRACBITS, FRACUNIT/2, trans, fg, NULL);
V_DrawFixedPatch(((BASEVIDWIDTH+190)<<(FRACBITS-1)), (y+dupz)<<FRACBITS, FRACUNIT/2, trans|V_FLIP, fg, NULL);
y += height;
}
trans = V_40TRANS+((cnt&~1)<<(V_ALPHASHIFT-1));
for (i = 0; i < 7; ++i)
{
fa = (FixedAngle(w)>>ANGLETOFINESHIFT) & FINEMASK;
x = (BASEVIDWIDTH<<(FRACBITS-1)) + (60*FINESINE(fa));
y = ((BASEVIDHEIGHT+16-20)<<(FRACBITS-1)) - (60*FINECOSINE(fa));
w += (360<<FRACBITS)/7;
patchname[4] = 'A'+(char)i;
V_DrawFixedPatch(x, y, FRACUNIT, trans, W_CachePatchName(patchname, PU_PATCH), NULL);
}
height = 18; // prevents the need for the next line
//dupz = (w*height)/18;
dupz = ((w>>FRACBITS) % height);
y = dupz+(height/4);
x = 105+dupz;
while (y >= -diffy)
{
x -= height;
y -= height;
}
while (y-dupz < maxy && x < (xspan/2))
{
V_DrawFill((BASEVIDWIDTH/2)-x-height, -diffy, height, diffy+y+height, 153);
V_DrawFill((BASEVIDWIDTH/2)+x, (maxy-y)-height, height, height+y, 153);
y += height;
x += height;
}
}
if (!soffset)
{
char_scroll += (360<<FRACBITS)/42; // like a clock, ticking at 42bpm!
if (char_scroll >= 360<<FRACBITS)
char_scroll -= 360<<FRACBITS;
if (recatkdrawtimer > (10*TICRATE))
recatkdrawtimer -= (10*TICRATE);
}
//M_DrawMenuTitle();
// draw menu (everything else goes on top of it)
// Sadly we can't just use generic mode menus because we need some extra hacks
x = currentMenu->x;
y = currentMenu->y;
dispstatus = (currentMenu->menuitems[marathonplayer].status & IT_DISPLAY);
if (dispstatus == IT_STRING || dispstatus == IT_WHITESTRING)
{
soffset = 68;
if (description[char_on].charpic->width >= 256)
V_DrawTinyScaledPatch(224, 120, 0, description[char_on].charpic);
else
V_DrawSmallScaledPatch(224, 120, 0, description[char_on].charpic);
}
else
soffset = 0;
for (i = 0; i < currentMenu->numitems; ++i)
{
dispstatus = (currentMenu->menuitems[i].status & IT_DISPLAY);
if (dispstatus != IT_STRING && dispstatus != IT_WHITESTRING)
continue;
y = currentMenu->y+currentMenu->menuitems[i].alphaKey;
if (i == itemOn)
cursory = y;
V_DrawString(x, y, (dispstatus == IT_WHITESTRING) ? V_YELLOWMAP : 0 , currentMenu->menuitems[i].text);
cv = NULL;
cvstring = NULL;
work = NULL;
if ((currentMenu->menuitems[i].status & IT_TYPE) == IT_CVAR)
{
cv = (consvar_t *)currentMenu->menuitems[i].itemaction;
cvstring = cv->string;
}
else if (i == marathonplayer)
{
if (description[char_on].displayname[0])
{
work = Z_StrDup(description[char_on].displayname);
cnt = 0;
while (work[cnt])
{
if (work[cnt] == '\n')
work[cnt] = ' ';
cnt++;
}
cvstring = work;
}
else
cvstring = description[char_on].skinname;
}
// Cvar specific handling
if (cvstring)
{
INT32 flags = V_YELLOWMAP;
if (cv == &cv_dummymarathon && cv->value == 2) // ultimate_selectable
flags = V_REDMAP;
// Should see nothing but strings
if (cv == &cv_dummymarathon && cv->value == 1)
{
w = V_ThinStringWidth(cvstring, 0);
V_DrawThinString(BASEVIDWIDTH - x - soffset - w, y+1, flags, cvstring);
}
else
{
w = V_StringWidth(cvstring, 0);
V_DrawString(BASEVIDWIDTH - x - soffset - w, y, flags, cvstring);
}
if (i == itemOn)
{
V_DrawCharacter(BASEVIDWIDTH - x - soffset - 10 - w - (skullAnimCounter/5), y,
'\x1C' | V_YELLOWMAP, false);
V_DrawCharacter(BASEVIDWIDTH - x - soffset + 2 + (skullAnimCounter/5), y,
'\x1D' | V_YELLOWMAP, false);
}
if (work)
Z_Free(work);
}
}
// DRAW THE SKULL CURSOR
V_DrawScaledPatch(currentMenu->x - 24, cursory, 0, W_CachePatchName("M_CURSOR", PU_PATCH));
V_DrawString(currentMenu->x, cursory, V_YELLOWMAP, currentMenu->menuitems[itemOn].text);
// Draw press ESC to exit string on main record attack menu
V_DrawString(104-72, 180, V_TRANSLUCENT, M_GetText("Press ESC to exit"));
}
// ======== // ========
// END GAME // END GAME
// ======== // ========
@ -12578,6 +13007,7 @@ void M_QuitResponse(INT32 ch)
if (!(netgame || cv_debug)) if (!(netgame || cv_debug))
{ {
S_ResetCaptions(); S_ResetCaptions();
marathonmode = 0;
mrand = M_RandomKey(sizeof(quitsounds)/sizeof(INT32)); mrand = M_RandomKey(sizeof(quitsounds)/sizeof(INT32));
if (quitsounds[mrand]) S_StartSound(NULL, quitsounds[mrand]); if (quitsounds[mrand]) S_StartSound(NULL, quitsounds[mrand]);

View File

@ -61,6 +61,8 @@ typedef enum
MN_SP_NIGHTS_REPLAY, MN_SP_NIGHTS_REPLAY,
MN_SP_NIGHTS_GHOST, MN_SP_NIGHTS_GHOST,
MN_SP_MARATHON,
// Multiplayer // Multiplayer
MN_MP_MAIN, MN_MP_MAIN,
MN_MP_SPLITSCREEN, // SplitServer MN_MP_SPLITSCREEN, // SplitServer
@ -419,6 +421,7 @@ extern INT16 char_on, startchar;
#define MAXSAVEGAMES 31 #define MAXSAVEGAMES 31
#define NOSAVESLOT 0 //slot where Play Without Saving appears #define NOSAVESLOT 0 //slot where Play Without Saving appears
#define MARATHONSLOT 420 // just has to be nonzero, but let's use one that'll show up as an obvious error if something goes wrong while not using our existing saves
#define BwehHehHe() S_StartSound(NULL, sfx_bewar1+M_RandomKey(4)) // Bweh heh he #define BwehHehHe() S_StartSound(NULL, sfx_bewar1+M_RandomKey(4)) // Bweh heh he

View File

@ -3807,10 +3807,10 @@ static inline void P_UnArchiveSPGame(INT16 mapoverride)
if (mapoverride != 0) if (mapoverride != 0)
{ {
gamemap = mapoverride; gamemap = mapoverride;
gamecomplete = true; gamecomplete = 1;
} }
else else
gamecomplete = false; gamecomplete = 0;
// gamemap changed; we assume that its map header is always valid, // gamemap changed; we assume that its map header is always valid,
// so make it so // so make it so

View File

@ -217,6 +217,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 i)
mapheaderinfo[num]->actnum = 0; mapheaderinfo[num]->actnum = 0;
mapheaderinfo[num]->typeoflevel = 0; mapheaderinfo[num]->typeoflevel = 0;
mapheaderinfo[num]->nextlevel = (INT16)(i + 1); mapheaderinfo[num]->nextlevel = (INT16)(i + 1);
mapheaderinfo[num]->marathonnext = 0;
mapheaderinfo[num]->startrings = 0; mapheaderinfo[num]->startrings = 0;
mapheaderinfo[num]->sstimer = 90; mapheaderinfo[num]->sstimer = 90;
mapheaderinfo[num]->ssspheres = 1; mapheaderinfo[num]->ssspheres = 1;
@ -3181,7 +3182,7 @@ static boolean CanSaveLevel(INT32 mapnum)
// Any levels that have the savegame flag can save normally. // Any levels that have the savegame flag can save normally.
// If the game is complete for this save slot, then any level can save! // If the game is complete for this save slot, then any level can save!
// On the other side of the spectrum, if lastmaploaded is 0, then the save file has only just been created and needs to save ASAP! // On the other side of the spectrum, if lastmaploaded is 0, then the save file has only just been created and needs to save ASAP!
return (mapheaderinfo[mapnum-1]->levelflags & LF_SAVEGAME || gamecomplete || !lastmaploaded); return (mapheaderinfo[mapnum-1]->levelflags & LF_SAVEGAME || (gamecomplete != 0) || marathonmode || !lastmaploaded);
} }
static void P_RunSpecialStageWipe(void) static void P_RunSpecialStageWipe(void)

View File

@ -622,3 +622,35 @@ void SCR_ClosedCaptions(void)
va("%c [%s]", dot, (closedcaptions[i].s->caption[0] ? closedcaptions[i].s->caption : closedcaptions[i].s->name))); va("%c [%s]", dot, (closedcaptions[i].s->caption[0] ? closedcaptions[i].s->caption : closedcaptions[i].s->name)));
} }
} }
void SCR_DisplayMarathonInfo(void)
{
INT32 flags = V_SNAPTOBOTTOM;
static tic_t entertic, oldentertics = 0;
const char *str;
#if 0 // eh, this probably isn't going to be a problem
if (((signed)marathontime) < 0)
{
flags |= V_REDMAP;
str = "No waiting out the clock to submit a bogus time.";
}
else
#endif
{
entertic = I_GetTime();
if (gamecomplete)
flags |= V_YELLOWMAP;
else if (marathonmode & MA_INIT)
marathonmode &= ~MA_INIT;
else
marathontime += entertic - oldentertics;
str = va("%i:%02i:%02i.%02i",
G_TicsToHours(marathontime),
G_TicsToMinutes(marathontime, false),
G_TicsToSeconds(marathontime),
G_TicsToCentiseconds(marathontime));
oldentertics = entertic;
}
V_DrawPromptBack(-8, cons_backcolor.value);
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-8, flags, str);
}

View File

@ -206,5 +206,6 @@ FUNCMATH boolean SCR_IsAspectCorrect(INT32 width, INT32 height);
void SCR_DisplayTicRate(void); void SCR_DisplayTicRate(void);
void SCR_ClosedCaptions(void); void SCR_ClosedCaptions(void);
void SCR_DisplayLocalPing(void); void SCR_DisplayLocalPing(void);
void SCR_DisplayMarathonInfo(void);
#undef DNWH #undef DNWH
#endif //__SCREEN_H__ #endif //__SCREEN_H__

View File

@ -1214,6 +1214,9 @@ void I_FinishUpdate(void)
if (cv_showping.value && netgame && consoleplayer != serverplayer) if (cv_showping.value && netgame && consoleplayer != serverplayer)
SCR_DisplayLocalPing(); SCR_DisplayLocalPing();
if (marathonmode)
SCR_DisplayMarathonInfo();
if (rendermode == render_soft && screens[0]) if (rendermode == render_soft && screens[0])
{ {
SDL_Rect rect; SDL_Rect rect;

View File

@ -763,7 +763,7 @@ static void ST_drawTime(void)
ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon, V_HUDTRANS); // Colon ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon, V_HUDTRANS); // Colon
ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2, V_HUDTRANS); // Seconds ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2, V_HUDTRANS); // Seconds
if (cv_timetic.value == 1 || cv_timetic.value == 2 || modeattacking) // there's not enough room for tics in splitscreen, don't even bother trying! if (cv_timetic.value == 1 || cv_timetic.value == 2 || modeattacking || marathonmode)
{ {
ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod, V_HUDTRANS); // Period ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod, V_HUDTRANS); // Period
ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2, V_HUDTRANS); // Tics ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2, V_HUDTRANS); // Tics
@ -1445,7 +1445,7 @@ static void ST_drawPowerupHUD(void)
// --------- // ---------
// Let's have a power-like icon to represent finishing the level! // Let's have a power-like icon to represent finishing the level!
if (stplyr->pflags & PF_FINISHED && cv_exitmove.value) if (stplyr->pflags & PF_FINISHED && cv_exitmove.value && multiplayer)
{ {
finishoffs[q] = ICONSEP; finishoffs[q] = ICONSEP;
V_DrawSmallScaledPatch(offs, hudinfo[HUD_POWERUPS].y, V_PERPLAYER|hudinfo[HUD_POWERUPS].f|V_HUDTRANS, fnshico); V_DrawSmallScaledPatch(offs, hudinfo[HUD_POWERUPS].y, V_PERPLAYER|hudinfo[HUD_POWERUPS].f|V_HUDTRANS, fnshico);

View File

@ -1870,7 +1870,10 @@ void V_DrawPromptBack(INT32 boxheight, INT32 color)
if (color >= 256 && color < 512) if (color >= 256 && color < 512)
{ {
boxheight = ((boxheight * 4) + (boxheight/2)*5); if (boxheight < 0)
boxheight = -boxheight;
else // 4 lines of space plus gaps between and some leeway
boxheight = ((boxheight * 4) + (boxheight/2)*5);
V_DrawFill((BASEVIDWIDTH-(vid.width/vid.dupx))/2, BASEVIDHEIGHT-boxheight, (vid.width/vid.dupx),boxheight, (color-256)|V_SNAPTOBOTTOM); V_DrawFill((BASEVIDWIDTH-(vid.width/vid.dupx))/2, BASEVIDHEIGHT-boxheight, (vid.width/vid.dupx),boxheight, (color-256)|V_SNAPTOBOTTOM);
return; return;
} }
@ -1917,8 +1920,11 @@ void V_DrawPromptBack(INT32 boxheight, INT32 color)
// heavily simplified -- we don't need to know x or y position, // heavily simplified -- we don't need to know x or y position,
// just the start and stop positions // just the start and stop positions
deststop = screens[0] + vid.rowbytes * vid.height; buf = deststop = screens[0] + vid.rowbytes * vid.height;
buf = deststop - vid.rowbytes * ((boxheight * 4) + (boxheight/2)*5); // 4 lines of space plus gaps between and some leeway if (boxheight < 0)
buf += vid.rowbytes * boxheight;
else // 4 lines of space plus gaps between and some leeway
buf -= vid.rowbytes * ((boxheight * 4) + (boxheight/2)*5);
for (; buf < deststop; ++buf) for (; buf < deststop; ++buf)
*buf = promptbgmap[*buf]; *buf = promptbgmap[*buf];
} }

View File

@ -377,6 +377,9 @@ void I_FinishUpdate(void)
if (cv_showping.value && netgame && consoleplayer != serverplayer) if (cv_showping.value && netgame && consoleplayer != serverplayer)
SCR_DisplayLocalPing(); SCR_DisplayLocalPing();
if (marathonmode)
SCR_DisplayMarathonInfo();
// //
if (bDIBMode) if (bDIBMode)
{ {

View File

@ -392,7 +392,7 @@ dontdrawbg:
if (gottoken) // first to be behind everything else if (gottoken) // first to be behind everything else
Y_IntermissionTokenDrawer(); Y_IntermissionTokenDrawer();
if (!splitscreen) if (!splitscreen) // there's not enough room in splitscreen, don't even bother trying!
{ {
// draw score // draw score
ST_DrawPatchFromHud(HUD_SCORE, sboscore); ST_DrawPatchFromHud(HUD_SCORE, sboscore);
@ -414,7 +414,7 @@ dontdrawbg:
ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon); // Colon ST_DrawPatchFromHud(HUD_TIMECOLON, sbocolon); // Colon
ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2); // Seconds ST_DrawPadNumFromHud(HUD_SECONDS, seconds, 2); // Seconds
if (cv_timetic.value == 1 || cv_timetic.value == 2 || modeattacking) // there's not enough room for tics in splitscreen, don't even bother trying! if (cv_timetic.value == 1 || cv_timetic.value == 2 || modeattacking || marathonmode)
{ {
ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period ST_DrawPatchFromHud(HUD_TIMETICCOLON, sboperiod); // Period
ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics ST_DrawPadNumFromHud(HUD_TICS, tictrn, 2); // Tics
@ -1004,7 +1004,7 @@ void Y_Ticker(void)
{ {
INT32 i; INT32 i;
UINT32 oldscore = data.coop.score; UINT32 oldscore = data.coop.score;
boolean skip = false; boolean skip = (marathonmode) ? true : false;
boolean anybonuses = false; boolean anybonuses = false;
if (!intertic) // first time only if (!intertic) // first time only
@ -1080,7 +1080,7 @@ void Y_Ticker(void)
{ {
INT32 i; INT32 i;
UINT32 oldscore = data.spec.score; UINT32 oldscore = data.spec.score;
boolean skip = false, super = false, anybonuses = false; boolean skip = (marathonmode) ? true : false, super = false, anybonuses = false;
if (!intertic) // first time only if (!intertic) // first time only
{ {