Merge branch 'other-progression' into 'master'

Other Progression

Closes #561

See merge request KartKrew/Kart!1606
This commit is contained in:
Oni 2023-11-19 05:14:39 +00:00
commit 47a01d0699
44 changed files with 609 additions and 135 deletions

View file

@ -350,10 +350,13 @@ ACSVM::Word Environment::callSpecImpl
activator_t *activator = static_cast<activator_t *>(Z_Calloc(sizeof(activator_t), PU_LEVEL, nullptr)); activator_t *activator = static_cast<activator_t *>(Z_Calloc(sizeof(activator_t), PU_LEVEL, nullptr));
auto __ = srb2::finally( auto __ = srb2::finally(
[activator]() [info, activator]()
{ {
P_SetTarget(&activator->mo, NULL); if (info->thread_era == thinker_era)
Z_Free(activator); {
P_SetTarget(&activator->mo, NULL);
Z_Free(activator);
}
} }
); );

View file

@ -70,6 +70,28 @@ void ACS_Shutdown(void)
#endif #endif
} }
/*--------------------------------------------------
void ACS_InvalidateMapScope(size_t mapID)
See header file for description.
--------------------------------------------------*/
void ACS_InvalidateMapScope(void)
{
Environment *env = &ACSEnv;
ACSVM::GlobalScope *const global = env->getGlobalScope(0);
ACSVM::HubScope *hub = NULL;
ACSVM::MapScope *map = NULL;
// Conclude hub scope, even if we are not using it.
hub = global->getHubScope(0);
hub->reset();
// Conclude current map scope.
map = hub->getMapScope(0); // This is where you'd put in mapID if you add hub support.
map->reset();
}
/*-------------------------------------------------- /*--------------------------------------------------
void ACS_LoadLevelScripts(size_t mapID) void ACS_LoadLevelScripts(size_t mapID)
@ -103,14 +125,20 @@ void ACS_LoadLevelScripts(size_t mapID)
// hubs are to be implemented, this logic would need // hubs are to be implemented, this logic would need
// to be far more sophisticated. // to be far more sophisticated.
// Reset hub scope, even if we are not using it. // Extra note regarding the commented out ->reset()'s:
// This is too late! That needs to be done before
// PU_LEVEL is purged. Call ACS_InvalidateMapScope
// to take care of that. Those lines are left in
// only as a warning to future code spelunkers.
// Restart hub scope, even if we are not using it.
hub = global->getHubScope(0); hub = global->getHubScope(0);
hub->reset(); //hub->reset();
hub->active = true; hub->active = true;
// Start up new map scope. // Start up new map scope.
map = hub->getMapScope(0); // This is where you'd put in mapID if you add hub support. map = hub->getMapScope(0); // This is where you'd put in mapID if you add hub support.
map->reset(); //map->reset();
map->active = true; map->active = true;
// Insert BEHAVIOR lump into the list. // Insert BEHAVIOR lump into the list.

View file

@ -44,6 +44,23 @@ void ACS_Init(void);
void ACS_Shutdown(void); void ACS_Shutdown(void);
/*--------------------------------------------------
void ACS_InvalidateMapScope(size_t mapID);
Resets the ACS hub and map scopes to remove
existing running scripts, without starting
any new scripts.
Input Arguments:-
None
Return:-
None
--------------------------------------------------*/
void ACS_InvalidateMapScope(void);
/*-------------------------------------------------- /*--------------------------------------------------
void ACS_LoadLevelScripts(size_t mapID); void ACS_LoadLevelScripts(size_t mapID);

View file

@ -59,6 +59,7 @@ enum acs_tagType_e
class ThreadInfo : public ACSVM::ThreadInfo class ThreadInfo : public ACSVM::ThreadInfo
{ {
public: public:
UINT32 thread_era; // If equal to thinker_era, mobj pointers are safe.
mobj_t *mo; // Object that activated this thread. mobj_t *mo; // Object that activated this thread.
line_t *line; // Linedef that activated this thread. line_t *line; // Linedef that activated this thread.
UINT8 side; // Front / back side of said linedef. UINT8 side; // Front / back side of said linedef.
@ -67,6 +68,7 @@ public:
bool fromLineSpecial; // Called from P_ProcessLineSpecial. bool fromLineSpecial; // Called from P_ProcessLineSpecial.
ThreadInfo() : ThreadInfo() :
thread_era { thinker_era },
mo{ nullptr }, mo{ nullptr },
line{ nullptr }, line{ nullptr },
side{ 0 }, side{ 0 },
@ -77,6 +79,7 @@ public:
} }
ThreadInfo(const ThreadInfo &info) : ThreadInfo(const ThreadInfo &info) :
thread_era { thinker_era },
mo{ nullptr }, mo{ nullptr },
line{ info.line }, line{ info.line },
side{ info.side }, side{ info.side },
@ -88,6 +91,7 @@ public:
} }
ThreadInfo(const activator_t *activator) : ThreadInfo(const activator_t *activator) :
thread_era { thinker_era },
mo{ nullptr }, mo{ nullptr },
line{ activator->line }, line{ activator->line },
side{ activator->side }, side{ activator->side },
@ -100,11 +104,15 @@ public:
~ThreadInfo() ~ThreadInfo()
{ {
P_SetTarget(&mo, nullptr); if (thread_era == thinker_era)
{
P_SetTarget(&mo, nullptr);
}
} }
ThreadInfo &operator = (const ThreadInfo &info) ThreadInfo &operator = (const ThreadInfo &info)
{ {
thread_era = thinker_era;
P_SetTarget(&mo, info.mo); P_SetTarget(&mo, info.mo);
line = info.line; line = info.line;
side = info.side; side = info.side;

View file

@ -1110,7 +1110,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
M_Memcpy(netbuffer->u.serverinfo.mapmd5, mapmd5, 16); M_Memcpy(netbuffer->u.serverinfo.mapmd5, mapmd5, 16);
if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE) && !(mapheaderinfo[prevmap]->zonttl[0])) if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE) && !(mapheaderinfo[gamemap-1]->zonttl[0]))
netbuffer->u.serverinfo.iszone = 1; netbuffer->u.serverinfo.iszone = 1;
else else
netbuffer->u.serverinfo.iszone = 0; netbuffer->u.serverinfo.iszone = 0;
@ -1443,6 +1443,7 @@ static void CL_LoadReceivedSavegame(boolean reloading)
demo.playback = false; demo.playback = false;
demo.title = false; demo.title = false;
titlemapinaction = false; titlemapinaction = false;
tutorialchallenge = TUTORIALSKIP_NONE;
automapactive = false; automapactive = false;
// load a base level // load a base level
@ -3992,7 +3993,9 @@ void SV_StopServer(void)
Y_EndIntermission(); Y_EndIntermission();
if (gamestate == GS_VOTING) if (gamestate == GS_VOTING)
Y_EndVote(); Y_EndVote();
gamestate = wipegamestate = GS_NULL;
G_SetGamestate(GS_NULL);
wipegamestate = GS_NULL;
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
((UINT16*)localtextcmd[i])[0] = 0; ((UINT16*)localtextcmd[i])[0] = 0;

View file

@ -1062,8 +1062,11 @@ void D_ClearState(void)
memset(&grandprixinfo, 0, sizeof(struct grandprixinfo)); memset(&grandprixinfo, 0, sizeof(struct grandprixinfo));
memset(&roundqueue, 0, sizeof(struct roundqueue)); memset(&roundqueue, 0, sizeof(struct roundqueue));
// empty maptol so mario/etc sounds don't play in sound test when they shouldn't // empty some other semi-important state
maptol = 0; maptol = 0;
nextmapoverride = 0;
skipstats = 0;
tutorialchallenge = TUTORIALSKIP_NONE;
gameaction = ga_nothing; gameaction = ga_nothing;
memset(displayplayers, 0, sizeof(displayplayers)); memset(displayplayers, 0, sizeof(displayplayers));

View file

@ -2587,7 +2587,7 @@ static void Command_Map_f(void)
SplitScreen_OnChange(); SplitScreen_OnChange();
} }
if (!newnetgame && option_match == 0) if (!newnetgame && (newgametype != GT_TUTORIAL) && option_match == 0)
{ {
grandprixinfo.gp = true; grandprixinfo.gp = true;
grandprixinfo.initalize = true; grandprixinfo.initalize = true;
@ -3022,7 +3022,7 @@ static void Command_QueueMap_f(void)
return; return;
} }
if ((/*newmapnum != 1 &&*/ M_MapLocked(newmapnum))) if (/*newmapnum != 1 &&*/ M_MapLocked(newmapnum))
{ {
ischeating = true; ischeating = true;
} }

View file

@ -355,6 +355,7 @@ struct respawnvars_t
fixed_t pointx; // Respawn position coords to go towards fixed_t pointx; // Respawn position coords to go towards
fixed_t pointy; fixed_t pointy;
fixed_t pointz; fixed_t pointz;
angle_t pointangle; // Only used when wp is NULL
boolean flip; // Flip upside down or not boolean flip; // Flip upside down or not
tic_t timer; // Time left on respawn animation once you're there tic_t timer; // Time left on respawn animation once you're there
tic_t airtimer; // Time spent in the air before respawning tic_t airtimer; // Time spent in the air before respawning

View file

@ -2791,7 +2791,8 @@ static void readcondition(UINT16 set, UINT32 id, char *word2)
else if ((offset=0) || fastcmp(params[0], "ADDON") else if ((offset=0) || fastcmp(params[0], "ADDON")
|| (++offset && fastcmp(params[0], "CREDITS")) || (++offset && fastcmp(params[0], "CREDITS"))
|| (++offset && fastcmp(params[0], "REPLAY")) || (++offset && fastcmp(params[0], "REPLAY"))
|| (++offset && fastcmp(params[0], "CRASH"))) || (++offset && fastcmp(params[0], "CRASH"))
|| (++offset && fastcmp(params[0], "TUTORIALSKIP")))
{ {
//PARAMCHECK(1); //PARAMCHECK(1);
ty = UC_ADDON + offset; ty = UC_ADDON + offset;
@ -3469,6 +3470,11 @@ void readmaincfg(MYFILE *f, boolean mainfile)
titlemap = Z_StrDup(word2); titlemap = Z_StrDup(word2);
titlechanged = true; titlechanged = true;
} }
else if (fastcmp(word, "TUTORIALCHALLENGEMAP"))
{
Z_Free(tutorialchallengemap);
tutorialchallengemap = Z_StrDup(word2);
}
else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "TITLEPICSHIDE")) else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "TITLEPICSHIDE"))
{ {
hidetitlepics = (boolean)(value != 0 || word2[0] == 'T' || word2[0] == 'Y'); hidetitlepics = (boolean)(value != 0 || word2[0] == 'T' || word2[0] == 'Y');

View file

@ -246,11 +246,16 @@ extern INT32 g_localplayers[MAXSPLITSCREENPLAYERS];
extern char * titlemap; extern char * titlemap;
extern boolean hidetitlepics; extern boolean hidetitlepics;
extern char * bootmap; //bootmap for loading a map on startup extern boolean looptitle;
extern char * bootmap; //bootmap for loading a map on startup
extern char * podiummap; // map to load for podium extern char * podiummap; // map to load for podium
extern boolean looptitle; extern char * tutorialchallengemap; // map to load for tutorial skip
extern UINT8 tutorialchallenge;
#define TUTORIALSKIP_NONE 0
#define TUTORIALSKIP_FAILED 1
#define TUTORIALSKIP_INPROGRESS 2
// CTF colors. // CTF colors.
extern UINT16 skincolor_redteam, skincolor_blueteam, skincolor_redring, skincolor_bluering; extern UINT16 skincolor_redteam, skincolor_blueteam, skincolor_redring, skincolor_bluering;

View file

@ -83,7 +83,7 @@
gameaction_t gameaction; gameaction_t gameaction;
gamestate_t gamestate = GS_NULL; gamestate_t gamestate = GS_NULL;
UINT8 ultimatemode = false; boolean ultimatemode = false;
JoyType_t Joystick[MAXSPLITSCREENPLAYERS]; JoyType_t Joystick[MAXSPLITSCREENPLAYERS];
@ -170,11 +170,13 @@ tic_t timeinmap; // Ticker for time spent in level (used for levelcard display)
char * titlemap = NULL; char * titlemap = NULL;
boolean hidetitlepics = false; boolean hidetitlepics = false;
char * bootmap = NULL; //bootmap for loading a map on startup boolean looptitle = true;
char * bootmap = NULL; //bootmap for loading a map on startup
char * podiummap = NULL; // map to load for podium char * podiummap = NULL; // map to load for podium
boolean looptitle = true; char * tutorialchallengemap = NULL; // map to load for tutorial skip
UINT8 tutorialchallenge = TUTORIALSKIP_NONE;
UINT16 skincolor_redteam = SKINCOLOR_RED; UINT16 skincolor_redteam = SKINCOLOR_RED;
UINT16 skincolor_blueteam = SKINCOLOR_BLUE; UINT16 skincolor_blueteam = SKINCOLOR_BLUE;
@ -717,14 +719,18 @@ INT32 G_MapNumber(const char * name)
name += 8; name += 8;
if (strcasecmp("TITLE", name) == 0)
return NEXTMAP_TITLE;
if (strcasecmp("EVALUATION", name) == 0) if (strcasecmp("EVALUATION", name) == 0)
return NEXTMAP_EVALUATION; return NEXTMAP_EVALUATION;
if (strcasecmp("CREDITS", name) == 0) if (strcasecmp("CREDITS", name) == 0)
return NEXTMAP_CREDITS; return NEXTMAP_CREDITS;
if (strcasecmp("CEREMONY", name) == 0) if (strcasecmp("CEREMONY", name) == 0)
return NEXTMAP_CEREMONY; return NEXTMAP_CEREMONY;
if (strcasecmp("TITLE", name) == 0) if (strcasecmp("VOTING", name) == 0)
return NEXTMAP_TITLE; return NEXTMAP_VOTING;
if (strcasecmp("TUTORIALCHALLENGE", name) == 0)
return NEXTMAP_TUTORIALCHALLENGE;
return NEXTMAP_INVALID; return NEXTMAP_INVALID;
} }
@ -2499,8 +2505,7 @@ void G_MovePlayerToSpawnOrCheatcheck(INT32 playernum)
rsp->pointx = pos.x; rsp->pointx = pos.x;
rsp->pointy = pos.y; rsp->pointy = pos.y;
rsp->pointz = pos.z; rsp->pointz = pos.z;
rsp->pointangle = Obj_GetCheckpointRespawnAngle(checkpoint);
players[playernum].mo->angle = Obj_GetCheckpointRespawnAngle(checkpoint);
Obj_ActivateCheckpointInstantly(checkpoint); Obj_ActivateCheckpointInstantly(checkpoint);
@ -3354,7 +3359,7 @@ UINT32 G_TOLFlag(INT32 pgametype)
return 0; return 0;
} }
UINT16 G_GetFirstMapOfGametype(UINT8 pgametype) UINT16 G_GetFirstMapOfGametype(UINT16 pgametype)
{ {
UINT8 i = 0; UINT8 i = 0;
UINT16 mapnum = NEXTMAP_INVALID; UINT16 mapnum = NEXTMAP_INVALID;
@ -3364,7 +3369,7 @@ UINT16 G_GetFirstMapOfGametype(UINT8 pgametype)
templevelsearch.typeoflevel = G_TOLFlag(pgametype); templevelsearch.typeoflevel = G_TOLFlag(pgametype);
templevelsearch.cupmode = (!(gametypes[pgametype]->rules & GTR_NOCUPSELECT)); templevelsearch.cupmode = (!(gametypes[pgametype]->rules & GTR_NOCUPSELECT));
templevelsearch.timeattack = false; templevelsearch.timeattack = false;
templevelsearch.tutorial = false; templevelsearch.tutorial = (pgametype == GT_TUTORIAL);
templevelsearch.checklocked = true; templevelsearch.checklocked = true;
if (templevelsearch.cupmode) if (templevelsearch.cupmode)
@ -3618,6 +3623,14 @@ void G_UpdateVisited(void)
if (demo.playback) if (demo.playback)
return; return;
// For some reason, we don't want to update visitation flags.
if (prevmap != gamemap-1)
return;
// Neither for tutorial skip material
if (nextmapoverride == NEXTMAP_TUTORIALCHALLENGE+1 || tutorialchallenge != TUTORIALSKIP_NONE)
return;
// Check if every local player wiped out. // Check if every local player wiped out.
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
@ -3627,7 +3640,7 @@ void G_UpdateVisited(void)
if (!P_IsLocalPlayer(&players[i])) // Not local. if (!P_IsLocalPlayer(&players[i])) // Not local.
continue; continue;
if (players[i].spectator) // Not playing. if (players[i].spectator == true) // Not playing.
continue; continue;
if (players[i].pflags & PF_NOCONTEST) // Sonic after not surviving. if (players[i].pflags & PF_NOCONTEST) // Sonic after not surviving.
@ -3658,7 +3671,7 @@ void G_UpdateVisited(void)
CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : ""); CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
M_UpdateUnlockablesAndExtraEmblems(true, true); M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData(); gamedata->deferredsave = true;
} }
void G_HandleSaveLevel(boolean removecondition) void G_HandleSaveLevel(boolean removecondition)
@ -4148,11 +4161,6 @@ static void G_DoCompleted(void)
gamedata->deferredsave = true; gamedata->deferredsave = true;
} }
// This isn't in the above block because other
// mechanisms can queue up a gamedata save.
if (gamedata->deferredsave)
G_SaveGameData();
// Then, update some important game state. // Then, update some important game state.
{ {
legitimateexit = false; legitimateexit = false;
@ -4172,14 +4180,18 @@ static void G_DoCompleted(void)
G_SetGamestate(GS_NULL); G_SetGamestate(GS_NULL);
wipegamestate = GS_NULL; wipegamestate = GS_NULL;
prevmap = (INT16)(gamemap-1);
} }
// Finally, if you're not exiting, guarantee NO CONTEST. // Finally, if you're not exiting, guarantee NO CONTEST.
// We do this seperately from the loop above Challenges, // We do this seperately from the loop above Challenges,
// so NOCONTEST-related Challenges don't fire on exitlevel. // so NOCONTEST-related Challenges don't fire on exitlevel.
for (i = 0; i < MAXPLAYERS; i++) if (gametype == GT_TUTORIAL)
{
// Maybe one day there'll be another context in which
// there's no way to progress other than ACS, but for
// now, Tutorial is a hardcoded exception.
}
else for (i = 0; i < MAXPLAYERS; i++)
{ {
if (playeringame[i] == false) if (playeringame[i] == false)
{ {
@ -4195,6 +4207,40 @@ static void G_DoCompleted(void)
} }
// And lastly, everything in anticipation for Intermission/level change. // And lastly, everything in anticipation for Intermission/level change.
if (tutorialchallenge == TUTORIALSKIP_INPROGRESS)
{
if (
!legitimateexit
|| !players[consoleplayer].exiting
|| K_IsPlayerLosing(&players[consoleplayer])
)
{
// Return to whence you came with your tail between your legs
tutorialchallenge = TUTORIALSKIP_FAILED;
G_SetGametype(GT_TUTORIAL);
nextmapoverride = prevmap+1;
}
else
{
// Proceed.
nextmapoverride = NEXTMAP_TITLE+1;
gamedata->finishedtutorialchallenge = true;
M_UpdateUnlockablesAndExtraEmblems(true, true);
gamedata->deferredsave = true;
}
}
else
{
// The "else" might not be strictly needed, but I don't
// want the "challenge" map to be considered visited before it's your time.
// ~toast 161123 (5 years of srb2kart, woooouuuu)
prevmap = gamemap-1;
tutorialchallenge = TUTORIALSKIP_NONE;
}
if (!demo.playback) if (!demo.playback)
{ {
// Set up power level gametype scrambles // Set up power level gametype scrambles
@ -4219,6 +4265,11 @@ static void G_DoCompleted(void)
Y_StartIntermission(); Y_StartIntermission();
G_UpdateVisited(); G_UpdateVisited();
} }
// This isn't in the above blocks because many
// mechanisms can queue up a gamedata save.
if (gamedata->deferredsave)
G_SaveGameData();
} }
// See also F_EndCutscene, the only other place which handles intra-map/ending transitions // See also F_EndCutscene, the only other place which handles intra-map/ending transitions
@ -4265,6 +4316,27 @@ void G_AfterIntermission(void)
// //
void G_NextLevel(void) void G_NextLevel(void)
{ {
if (
gametype == GT_TUTORIAL
&& nextmap == NEXTMAP_TUTORIALCHALLENGE
&& !(gamedata && gamedata->enteredtutorialchallenge)
)
{
nextmap = G_MapNumber(tutorialchallengemap);
if (
nextmap < nummapheaders
&& mapheaderinfo[nextmap] != NULL
&& mapheaderinfo[nextmap]->typeoflevel != 0
)
{
tutorialchallenge = TUTORIALSKIP_INPROGRESS;
G_SetGametype(G_GuessGametypeByTOL(mapheaderinfo[nextmap]->typeoflevel));
gamedata->enteredtutorialchallenge = true;
// A gamedata save will happen on successful level enter
}
}
if (nextmap >= NEXTMAP_SPECIAL) if (nextmap >= NEXTMAP_SPECIAL)
{ {
G_EndGame(); G_EndGame();
@ -4467,6 +4539,8 @@ typedef enum
GDEVER_SPECIAL = 1<<3, GDEVER_SPECIAL = 1<<3,
GDEVER_KEYTUTORIAL = 1<<4, GDEVER_KEYTUTORIAL = 1<<4,
GDEVER_KEYMAJORSKIP = 1<<5, GDEVER_KEYMAJORSKIP = 1<<5,
GDEVER_TUTORIALSKIP = 1<<6,
GDEVER_ENTERTUTSKIP = 1<<7,
} gdeverdone_t; } gdeverdone_t;
static const char *G_GameDataFolder(void) static const char *G_GameDataFolder(void)
@ -4606,6 +4680,8 @@ void G_LoadGameData(void)
gamedata->everseenspecial = !!(everflags & GDEVER_SPECIAL); gamedata->everseenspecial = !!(everflags & GDEVER_SPECIAL);
gamedata->chaokeytutorial = !!(everflags & GDEVER_KEYTUTORIAL); gamedata->chaokeytutorial = !!(everflags & GDEVER_KEYTUTORIAL);
gamedata->majorkeyskipattempted = !!(everflags & GDEVER_KEYMAJORSKIP); gamedata->majorkeyskipattempted = !!(everflags & GDEVER_KEYMAJORSKIP);
gamedata->finishedtutorialchallenge = !!(everflags & GDEVER_TUTORIALSKIP);
gamedata->enteredtutorialchallenge = !!(everflags & GDEVER_ENTERTUTSKIP);
} }
else else
{ {
@ -5297,6 +5373,10 @@ void G_SaveGameData(void)
everflags |= GDEVER_KEYTUTORIAL; everflags |= GDEVER_KEYTUTORIAL;
if (gamedata->majorkeyskipattempted) if (gamedata->majorkeyskipattempted)
everflags |= GDEVER_KEYMAJORSKIP; everflags |= GDEVER_KEYMAJORSKIP;
if (gamedata->finishedtutorialchallenge)
everflags |= GDEVER_TUTORIALSKIP;
if (gamedata->enteredtutorialchallenge)
everflags |= GDEVER_ENTERTUTSKIP;
WRITEUINT32(save.p, everflags); // 4 WRITEUINT32(save.p, everflags); // 4
} }
@ -5990,7 +6070,9 @@ INT32 G_FindMap(const char *mapname, char **foundmapnamep,
aprop = realmapname; aprop = realmapname;
/* Now that we found a perfect match no need to fucking guess. */ /* Now that we found a perfect match no need to fucking guess. */
if (strnicmp(realmapname, mapname, mapnamelen) == 0) if (strnicmp(realmapname, mapname, mapnamelen) == 0
|| (mapheaderinfo[i]->menuttl[0]
&& strnicmp(mapheaderinfo[i]->menuttl, mapname, mapnamelen) == 0))
{ {
if (wanttable) if (wanttable)
{ {
@ -6024,15 +6106,42 @@ INT32 G_FindMap(const char *mapname, char **foundmapnamep,
realmapname = 0; realmapname = 0;
} }
} }
else
if (mapheaderinfo[i]->menuttl[0] && ( aprop = strcasestr(mapheaderinfo[i]->menuttl, mapname) ))
{
if (wanttable)
{
writesimplefreq(freq, &freqc,
mapnum, aprop - mapheaderinfo[i]->menuttl, mapnamelen);
}
if (apromapnum == 0)
{
apromapnum = mapnum;
apromapname = realmapname;
realmapname = 0;
}
}
else/* ...match individual keywords */ else/* ...match individual keywords */
{ {
freq[freqc].mapnum = mapnum; freq[freqc].mapnum = mapnum;
measurekeywords(&freq[freqc], measurekeywords(&freq[freqc],
&freq[freqc].matchd, &freq[freqc].matchc, &freq[freqc].matchd, &freq[freqc].matchc,
realmapname, mapname, wanttable); realmapname, mapname, wanttable);
measurekeywords(&freq[freqc],
&freq[freqc].keywhd, &freq[freqc].keywhc, if (mapheaderinfo[i]->menuttl[0])
mapheaderinfo[i]->keywords, mapname, wanttable); {
measurekeywords(&freq[freqc],
&freq[freqc].keywhd, &freq[freqc].keywhc,
mapheaderinfo[i]->menuttl, mapname, wanttable);
}
if (mapheaderinfo[i]->keywords[0])
{
measurekeywords(&freq[freqc],
&freq[freqc].keywhd, &freq[freqc].keywhc,
mapheaderinfo[i]->keywords, mapname, wanttable);
}
if (freq[freqc].total) if (freq[freqc].total)
freqc++; freqc++;
} }

View file

@ -49,7 +49,8 @@ typedef enum
NEXTMAP_CREDITS = INT16_MAX-3, NEXTMAP_CREDITS = INT16_MAX-3,
NEXTMAP_CEREMONY = INT16_MAX-4, NEXTMAP_CEREMONY = INT16_MAX-4,
NEXTMAP_VOTING = INT16_MAX-5, NEXTMAP_VOTING = INT16_MAX-5,
NEXTMAP_INVALID = INT16_MAX-6, // Always last NEXTMAP_TUTORIALCHALLENGE = INT16_MAX-6,
NEXTMAP_INVALID = INT16_MAX-7, // Always last
NEXTMAP_SPECIAL = NEXTMAP_INVALID NEXTMAP_SPECIAL = NEXTMAP_INVALID
} nextmapspecial_t; } nextmapspecial_t;
@ -279,7 +280,7 @@ FUNCMATH INT32 G_TicsToMilliseconds(tic_t tics);
// Don't split up TOL handling // Don't split up TOL handling
UINT32 G_TOLFlag(INT32 pgametype); UINT32 G_TOLFlag(INT32 pgametype);
UINT16 G_GetFirstMapOfGametype(UINT8 pgametype); UINT16 G_GetFirstMapOfGametype(UINT16 pgametype);
UINT16 G_RandMap(UINT32 tolflags, UINT16 pprevmap, boolean ignoreBuffers, boolean callAgainSoon, UINT16 *extBuffer); UINT16 G_RandMap(UINT32 tolflags, UINT16 pprevmap, boolean ignoreBuffers, boolean callAgainSoon, UINT16 *extBuffer);
void G_AddMapToBuffer(UINT16 map); void G_AddMapToBuffer(UINT16 map);

View file

@ -58,7 +58,7 @@ typedef enum
extern gamestate_t gamestate; extern gamestate_t gamestate;
extern boolean titlemapinaction; extern boolean titlemapinaction;
extern UINT8 ultimatemode; // was sk_insane extern boolean ultimatemode; // was sk_insane
extern gameaction_t gameaction; extern gameaction_t gameaction;
void G_SetGamestate(gamestate_t newstate); void G_SetGamestate(gamestate_t newstate);

View file

@ -39,6 +39,9 @@ UINT8 numtargets = 0; // Capsules busted
INT32 K_StartingBumperCount(void) INT32 K_StartingBumperCount(void)
{ {
if (tutorialchallenge == TUTORIALSKIP_INPROGRESS)
return 0;
if (battleprisons || K_CheckBossIntro()) if (battleprisons || K_CheckBossIntro())
{ {
if (grandprixinfo.gp) if (grandprixinfo.gp)

View file

@ -71,8 +71,50 @@ void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e st
// For each subsequent round of GP, K_UpdateGrandPrixBots will handle this. // For each subsequent round of GP, K_UpdateGrandPrixBots will handle this.
players[newplayernum].spectator = grandprixinfo.gp && grandprixinfo.initalize && K_BotDefaultSpectator(); players[newplayernum].spectator = grandprixinfo.gp && grandprixinfo.initalize && K_BotDefaultSpectator();
players[newplayernum].skincolor = skins[skinnum].prefcolor; skincolornum_t color = static_cast<skincolornum_t>(skins[skinnum].prefcolor);
sprintf(player_names[newplayernum], "%s", skins[skinnum].realname); const char *realname = skins[skinnum].realname;
if (tutorialchallenge == TUTORIALSKIP_INPROGRESS)
{
// The ROYGBIV Rangers
switch (newplayernum)
{
case 1:
color = SKINCOLOR_RED;
realname = "Champ";
break;
case 2:
color = SKINCOLOR_ORANGE;
realname = "Pharaoh";
break;
case 3:
color = SKINCOLOR_YELLOW;
realname = "Caesar";
break;
case 4:
color = SKINCOLOR_GREEN;
realname = "General";
break;
case 5:
color = SKINCOLOR_CYAN; // blue (lighter than _BLUE)
realname = "Shogun";
break;
case 6:
color = SKINCOLOR_BLUEBERRY; // indigo
realname = "Emperor";
break;
case 7:
color = SKINCOLOR_VIOLET;
realname = "King";
break;
default:
color = SKINCOLOR_BLACK;
realname = "Vizier"; // working in the shadows
break;
}
}
players[newplayernum].skincolor = color;
sprintf(player_names[newplayernum], "%s", realname);
SetPlayerSkinByNum(newplayernum, skinnum); SetPlayerSkinByNum(newplayernum, skinnum);
playerconsole[newplayernum] = newplayernum; playerconsole[newplayernum] = newplayernum;
@ -128,8 +170,8 @@ boolean K_AddBot(UINT8 skin, UINT8 difficulty, botStyle_e style, UINT8 *p)
void K_UpdateMatchRaceBots(void) void K_UpdateMatchRaceBots(void)
{ {
const UINT8 defaultbotskin = R_BotDefaultSkin(); const UINT8 defaultbotskin = R_BotDefaultSkin();
const UINT8 difficulty = cv_kartbot.value; UINT8 difficulty;
UINT8 pmax = std::min<UINT8>((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), static_cast<UINT8>(cv_maxconnections.value)); UINT8 pmax = (dedicated ? MAXPLAYERS-1 : MAXPLAYERS);
UINT8 numplayers = 0; UINT8 numplayers = 0;
UINT8 numbots = 0; UINT8 numbots = 0;
UINT8 numwaiting = 0; UINT8 numwaiting = 0;
@ -145,9 +187,27 @@ void K_UpdateMatchRaceBots(void)
} }
grabskins[usableskins] = MAXSKINS; grabskins[usableskins] = MAXSKINS;
if (cv_maxplayers.value > 0) if ((gametyperules & GTR_BOTS) == 0)
{ {
pmax = std::min<UINT8>(pmax, static_cast<UINT8>(cv_maxplayers.value)); difficulty = 0;
}
else if (tutorialchallenge == TUTORIALSKIP_INPROGRESS)
{
pmax = 8; // can you believe this is a nerf
difficulty = MAXBOTDIFFICULTY;
}
else if (K_CanChangeRules(true) == false)
{
difficulty = 0;
}
else
{
difficulty = cv_kartbot.value;
pmax = std::min<UINT8>(pmax, static_cast<UINT8>(cv_maxconnections.value));
if (cv_maxplayers.value > 0)
{
pmax = std::min<UINT8>(pmax, static_cast<UINT8>(cv_maxplayers.value));
}
} }
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
@ -180,9 +240,7 @@ void K_UpdateMatchRaceBots(void)
} }
} }
if (K_CanChangeRules(true) == false if (difficulty == 0)
|| (gametyperules & GTR_BOTS) == 0
|| difficulty == 0)
{ {
// Remove bots if there are any. // Remove bots if there are any.
wantedbots = 0; wantedbots = 0;
@ -209,7 +267,11 @@ void K_UpdateMatchRaceBots(void)
} }
// Rearrange usable bot skins list to prevent gaps for randomised selection // Rearrange usable bot skins list to prevent gaps for randomised selection
for (i = 0; i < usableskins; i++) if (tutorialchallenge == TUTORIALSKIP_INPROGRESS)
{
usableskins = 0; // force a crack team of Eggrobo
}
else for (i = 0; i < usableskins; i++)
{ {
if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true))) if (!(grabskins[i] == MAXSKINS || !R_SkinUsable(-1, grabskins[i], true)))
{ {

View file

@ -831,7 +831,7 @@ boolean K_CanChangeRules(boolean allowdemos)
return false; return false;
} }
if (gametype == GT_TUTORIAL) if (gametype == GT_TUTORIAL || tutorialchallenge == TUTORIALSKIP_INPROGRESS)
{ {
// Tutorials are locked down. // Tutorials are locked down.
return false; return false;

View file

@ -2352,6 +2352,8 @@ static boolean K_drawKartPositionFaces(void)
if ((gametyperules & GTR_BUMPERS) && (players[rankplayer[i]].pflags & PF_ELIMINATED)) if ((gametyperules & GTR_BUMPERS) && (players[rankplayer[i]].pflags & PF_ELIMINATED))
V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_ranknobumpers); V_DrawScaledPatch(FACE_X-4, Y-3, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_ranknobumpers);
else if (K_Cooperative())
;
else if (gametyperules & GTR_CIRCUIT) else if (gametyperules & GTR_CIRCUIT)
{ {
INT32 pos = players[rankplayer[i]].position; INT32 pos = players[rankplayer[i]].position;
@ -2360,7 +2362,7 @@ static boolean K_drawKartPositionFaces(void)
// Draws the little number over the face // Draws the little number over the face
V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facenum[pos]); V_DrawScaledPatch(FACE_X-5, Y+10, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, kp_facenum[pos]);
} }
else else if (gametyperules & GTR_POINTLIMIT)
{ {
INT32 flags = V_HUDTRANS | V_SLIDEIN | V_SNAPTOLEFT; INT32 flags = V_HUDTRANS | V_SLIDEIN | V_SNAPTOLEFT;
@ -5526,7 +5528,7 @@ void K_drawKartHUD(void)
if (!battleprisons) if (!battleprisons)
K_drawKartEmeralds(); K_drawKartEmeralds();
} }
else if (!islonesome) else if (!islonesome && !K_Cooperative())
K_DrawKartPositionNum(stplyr->position); K_DrawKartPositionNum(stplyr->position);
} }

View file

@ -396,6 +396,9 @@ boolean K_IsPlayerLosing(player_t *player)
if (specialstageinfo.valid == true) if (specialstageinfo.valid == true)
return false; // anything short of DNF is COOL return false; // anything short of DNF is COOL
if (tutorialchallenge == TUTORIALSKIP_INPROGRESS)
return true; // anything short of perfect is SUCK
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
if (!playeringame[i] || players[i].spectator) if (!playeringame[i] || players[i].spectator)
@ -582,6 +585,15 @@ boolean K_TimeAttackRules(void)
return true; return true;
} }
if (gametype == GT_TUTORIAL)
{
// Tutorials are special. By default only one
// player will be playing... but sometimes bots
// can be spawned! So we still guarantee the
// changed behaviour for consistency.
return true;
}
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
if (playeringame[i] == false || players[i].spectator == true) if (playeringame[i] == false || players[i].spectator == true)
@ -13076,6 +13088,12 @@ boolean K_Cooperative(void)
return true; return true;
} }
if (gametype == GT_TUTORIAL)
{
// Maybe this should be a rule. Eventually?
return true;
}
return false; return false;
} }

View file

@ -451,6 +451,8 @@ typedef enum
#endif #endif
mpause_admin, mpause_admin,
mpause_callvote, mpause_callvote,
mpause_giveup,
mpause_restartmap, mpause_restartmap,
mpause_tryagain, mpause_tryagain,
@ -1104,6 +1106,7 @@ extern consvar_t cv_dummyspectator;
// Bunch of funny functions for the pause menu...~ // Bunch of funny functions for the pause menu...~
void M_RestartMap(INT32 choice); // Restart level (MP) void M_RestartMap(INT32 choice); // Restart level (MP)
void M_TryAgain(INT32 choice); // Try again (SP) void M_TryAgain(INT32 choice); // Try again (SP)
void M_GiveUp(INT32 choice); // Give up (SP)
void M_ConfirmSpectate(INT32 choice); // Spectate confirm when you're alone void M_ConfirmSpectate(INT32 choice); // Spectate confirm when you're alone
void M_ConfirmEnterGame(INT32 choice); // Enter game confirm when you're alone void M_ConfirmEnterGame(INT32 choice); // Enter game confirm when you're alone
void M_ConfirmSpectateChange(INT32 choice); // Splitscreen spectate/play menu func void M_ConfirmSpectateChange(INT32 choice); // Splitscreen spectate/play menu func
@ -1133,7 +1136,9 @@ char *M_AddonsHeaderPath(void);
extern consvar_t cv_dummyaddonsearch; extern consvar_t cv_dummyaddonsearch;
extern consvar_t cv_dummyextraspassword; extern consvar_t cv_dummyextraspassword;
#ifdef TODONEWMANUAL
void M_Manual(INT32 choice); void M_Manual(INT32 choice);
#endif
void M_HandleImageDef(INT32 choice); void M_HandleImageDef(INT32 choice);
// K_MENUDRAW.C // K_MENUDRAW.C

View file

@ -775,6 +775,7 @@ void M_Drawer(void)
if (menuwipe) if (menuwipe)
F_WipeStartScreen(); F_WipeStartScreen();
// background layer
if (menuactive) if (menuactive)
{ {
if (gamestate == GS_MENU) if (gamestate == GS_MENU)
@ -785,7 +786,17 @@ void M_Drawer(void)
{ {
V_DrawFadeScreen(122, 3); V_DrawFadeScreen(122, 3);
} }
}
// draw pause pic
if (paused && !demo.playback && (menuactive || cv_showhud.value))
{
M_DrawPausedText(0);
}
// foreground layer
if (menuactive)
{
if (currentMenu->drawroutine) if (currentMenu->drawroutine)
currentMenu->drawroutine(); // call current menu Draw routine currentMenu->drawroutine(); // call current menu Draw routine
@ -813,18 +824,6 @@ void M_Drawer(void)
menuwipe = false; menuwipe = false;
} }
// draw pause pic
if (paused && !demo.playback && (menuactive || cv_showhud.value))
{
// Don't cover the Stereo player!
boolean stereo_open = menuactive && currentMenu == &MISC_SoundTestDef;
if (stereo_open == false)
{
M_DrawPausedText(0);
}
}
if (netgame && Playing()) if (netgame && Playing())
{ {
boolean mainpause_open = menuactive && currentMenu == &PAUSE_MainDef; boolean mainpause_open = menuactive && currentMenu == &PAUSE_MainDef;
@ -2909,7 +2908,7 @@ static void M_DrawHighLowLevelTitle(INT16 x, INT16 y, INT16 map)
V_DrawLSTitleLowString(x2, y+28, 0, word2); V_DrawLSTitleLowString(x2, y+28, 0, word2);
} }
static void M_DrawLevelSelectBlock(INT16 x, INT16 y, INT16 map, boolean redblink, boolean greyscale) static void M_DrawLevelSelectBlock(INT16 x, INT16 y, UINT16 map, boolean redblink, boolean greyscale)
{ {
UINT8 *colormap = NULL; UINT8 *colormap = NULL;
@ -2928,6 +2927,20 @@ static void M_DrawLevelSelectBlock(INT16 x, INT16 y, INT16 map, boolean redblink
map, map,
colormap); colormap);
M_DrawHighLowLevelTitle(98+x, y+8, map); M_DrawHighLowLevelTitle(98+x, y+8, map);
if (levellist.levelsearch.tutorial && !(mapheaderinfo[map]->records.mapvisited & MV_BEATEN))
{
V_DrawScaledPatch(
x + 80 + 3, y + 50, 0,
W_CachePatchName(
va(
"CUPBKUP%c",
(greyscale ? '1' : '2')
),
PU_CACHE
)
);
}
} }
void M_DrawLevelSelect(void) void M_DrawLevelSelect(void)

View file

@ -980,9 +980,11 @@ void K_FinishCeremony(void)
g_podiumData.ranking = true; g_podiumData.ranking = true;
// Play the noise now (via G_UpdateVisited's concluding gamedata save) // Play the noise now (via G_UpdateVisited's concluding challenge check)
prevmap = gamemap-1; prevmap = gamemap-1;
G_UpdateVisited(); G_UpdateVisited();
if (gamedata->deferredsave)
G_SaveGameData();
} }
/*-------------------------------------------------- /*--------------------------------------------------

View file

@ -14,6 +14,7 @@
#include "d_player.h" #include "d_player.h"
#include "k_kart.h" #include "k_kart.h"
#include "k_battle.h" #include "k_battle.h"
#include "k_objects.h" // Obj_FindCheckpoint, etc
#include "g_game.h" #include "g_game.h"
#include "p_local.h" #include "p_local.h"
#include "p_tick.h" #include "p_tick.h"
@ -163,6 +164,9 @@ void K_DoIngameRespawn(player_t *player)
K_TumbleInterrupt(player); K_TumbleInterrupt(player);
P_ResetPlayer(player); P_ResetPlayer(player);
mobj_t *checkpoint;
vector3_t pos;
// Set up respawn position if invalid // Set up respawn position if invalid
if (player->respawn.manual == true) if (player->respawn.manual == true)
{ {
@ -194,6 +198,21 @@ void K_DoIngameRespawn(player_t *player)
K_RespawnAtWaypoint(player, player->respawn.wp); K_RespawnAtWaypoint(player, player->respawn.wp);
} }
} }
else if ((gametyperules & GTR_CHECKPOINTS)
&& player->checkpointId
&& (checkpoint = Obj_FindCheckpoint(player->checkpointId))
&& Obj_GetCheckpointRespawnPosition(checkpoint, &pos))
{
player->respawn.wp = NULL;
player->respawn.flip = (checkpoint->flags2 & MF2_OBJECTFLIP) ? true : false; // K_RespawnOffset wants a boolean!
player->respawn.pointx = pos.x;
player->respawn.pointy = pos.y;
player->respawn.pointz = pos.z + K_RespawnOffset(player, player->respawn.flip);
player->respawn.pointangle = Obj_GetCheckpointRespawnAngle(checkpoint);
player->respawn.distanceleft = 0;
}
else else
{ {
UINT32 bestdist = UINT32_MAX; UINT32 bestdist = UINT32_MAX;
@ -244,6 +263,7 @@ void K_DoIngameRespawn(player_t *player)
player->respawn.pointx = 0; player->respawn.pointx = 0;
player->respawn.pointy = 0; player->respawn.pointy = 0;
player->respawn.pointz = 0; player->respawn.pointz = 0;
player->respawn.pointangle = 0;
player->respawn.flip = false; player->respawn.flip = false;
} }
else else
@ -254,7 +274,7 @@ void K_DoIngameRespawn(player_t *player)
player->respawn.pointx = beststart->x << FRACBITS; player->respawn.pointx = beststart->x << FRACBITS;
player->respawn.pointy = beststart->y << FRACBITS; player->respawn.pointy = beststart->y << FRACBITS;
player->mo->angle = ( beststart->angle * ANG1 ); player->respawn.pointangle = ( beststart->angle * ANG1 );
s = R_PointInSubsector(beststart->x << FRACBITS, beststart->y << FRACBITS)->sector; s = R_PointInSubsector(beststart->x << FRACBITS, beststart->y << FRACBITS)->sector;
@ -476,6 +496,11 @@ static void K_MovePlayerToRespawnPoint(player_t *player)
else else
{ {
// We can now drop! // We can now drop!
if (gametyperules & GTR_CHECKPOINTS)
{
// Of course, in gametypes where there's a clear and intended progression, set our direction.
P_SetPlayerAngle(player, (player->drawangle = player->respawn.pointangle));
}
player->respawn.state = RESPAWNST_DROP; player->respawn.state = RESPAWNST_DROP;
return; return;
} }

View file

@ -183,7 +183,7 @@ static kartitems_t K_KartItemReelSpecialEnd[] =
KITEM_NONE KITEM_NONE
}; };
static kartitems_t K_KartItemReelTimeAttack[] = static kartitems_t K_KartItemReelRingSneaker[] =
{ {
KITEM_SNEAKER, KITEM_SNEAKER,
KITEM_SUPERRING, KITEM_SUPERRING,
@ -1228,7 +1228,7 @@ static void K_CalculateRouletteSpeed(itemroulette_t *const roulette)
fixed_t progress = 0; fixed_t progress = 0;
fixed_t total = 0; fixed_t total = 0;
if (bossinfo.valid == true) if (K_CheckBossIntro() == true)
{ {
// Boss in action, use a speed controlled by boss health // Boss in action, use a speed controlled by boss health
total = FixedDiv(bossinfo.healthbar, BOSSHEALTHBARLEN); total = FixedDiv(bossinfo.healthbar, BOSSHEALTHBARLEN);
@ -1345,7 +1345,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet
return; return;
} }
} }
else if (gametyperules & GTR_BOSS) else if (K_CheckBossIntro() == true)
{ {
for (i = 0; K_KartItemReelBoss[i] != KITEM_NONE; i++) for (i = 0; K_KartItemReelBoss[i] != KITEM_NONE; i++)
{ {
@ -1356,10 +1356,10 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet
} }
else if (K_TimeAttackRules() == true) else if (K_TimeAttackRules() == true)
{ {
kartitems_t *presetlist = K_KartItemReelTimeAttack; kartitems_t *presetlist = K_KartItemReelRingSneaker;
// If the objective is not to go fast, it's to cause serious damage. // If the objective is not to go fast, it's to cause serious damage.
if (gametyperules & GTR_PRISONS) if (battleprisons == true)
{ {
presetlist = K_KartItemReelBreakTheCapsules; presetlist = K_KartItemReelBreakTheCapsules;
} }

View file

@ -291,7 +291,11 @@ void level_tally_t::Init(player_t *player)
owner = player; owner = player;
gt = gametype; gt = gametype;
const boolean game_over = ((player->pflags & PF_LOSTLIFE) == PF_LOSTLIFE); const boolean game_over = (
G_GametypeUsesLives()
? ((player->pflags & PF_LOSTLIFE) == PF_LOSTLIFE)
: (tutorialchallenge == TUTORIALSKIP_INPROGRESS && K_IsPlayerLosing(player))
);
time = std::min(static_cast<INT32>(player->realtime), (100 * 60 * TICRATE) - 1); time = std::min(static_cast<INT32>(player->realtime), (100 * 60 * TICRATE) - 1);
ringPool = player->totalring; ringPool = player->totalring;
@ -384,7 +388,14 @@ void level_tally_t::Init(player_t *player)
{ {
if (game_over == true) if (game_over == true)
{ {
if (player->lives <= 0) if (tutorialchallenge == TUTORIALSKIP_INPROGRESS)
{
snprintf(
header, sizeof header,
"NICE TRY"
);
}
else if (G_GametypeUsesLives() && player->lives <= 0)
{ {
snprintf( snprintf(
header, sizeof header, header, sizeof header,
@ -991,7 +1002,7 @@ void level_tally_t::Draw(void)
|| state == TALLY_ST_GAMEOVER_LIVES || state == TALLY_ST_GAMEOVER_LIVES
|| state == TALLY_ST_GAMEOVER_DONE) || state == TALLY_ST_GAMEOVER_DONE)
{ {
if (owner->lives > 0) if (G_GametypeUsesLives() && owner->lives > 0)
{ {
srb2::Draw lives_drawer = drawer srb2::Draw lives_drawer = drawer
.xy( .xy(

View file

@ -219,6 +219,9 @@ int LUA_PushGlobals(lua_State *L, const char *word)
} else if (fastcmp(word,"podiummap")) { } else if (fastcmp(word,"podiummap")) {
lua_pushstring(L, podiummap); lua_pushstring(L, podiummap);
return 1; return 1;
} else if (fastcmp(word,"tutorialchallengemap")) {
lua_pushstring(L, tutorialchallengemap);
return 1;
// end map vars // end map vars
// begin CTF colors // begin CTF colors
} else if (fastcmp(word,"skincolor_redteam")) { } else if (fastcmp(word,"skincolor_redteam")) {

View file

@ -661,6 +661,8 @@ void M_ClearStats(void)
gamedata->evercrashed = false; gamedata->evercrashed = false;
gamedata->chaokeytutorial = false; gamedata->chaokeytutorial = false;
gamedata->majorkeyskipattempted = false; gamedata->majorkeyskipattempted = false;
gamedata->enteredtutorialchallenge = false;
gamedata->finishedtutorialchallenge = false;
gamedata->musicstate = GDMUSIC_NONE; gamedata->musicstate = GDMUSIC_NONE;
gamedata->importprofilewins = false; gamedata->importprofilewins = false;
@ -1499,6 +1501,8 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
return true; return true;
} }
return false; return false;
case UC_TUTORIALSKIP:
return (gamedata->finishedtutorialchallenge == true);
case UC_PASSWORD: case UC_PASSWORD:
return (cn->stringvar == NULL); return (cn->stringvar == NULL);
@ -2307,6 +2311,8 @@ static const char *M_GetConditionString(condition_t *cn)
if (gamedata->evercrashed) if (gamedata->evercrashed)
return "launch \"Dr. Robotnik's Ring Racers\" again after a game crash"; return "launch \"Dr. Robotnik's Ring Racers\" again after a game crash";
return NULL; return NULL;
case UC_TUTORIALSKIP:
return "successfully skip the Tutorial";
case UC_PASSWORD: case UC_PASSWORD:
return "enter a secret password"; return "enter a secret password";

View file

@ -63,6 +63,7 @@ typedef enum
UC_CREDITS, // Finish watching the credits UC_CREDITS, // Finish watching the credits
UC_REPLAY, // Save a replay UC_REPLAY, // Save a replay
UC_CRASH, // Hee ho ! UC_CRASH, // Hee ho !
UC_TUTORIALSKIP, // Complete the Tutorial Challenge
UC_PASSWORD, // Type in something funny UC_PASSWORD, // Type in something funny
@ -363,6 +364,8 @@ struct gamedata_t
boolean evercrashed; boolean evercrashed;
boolean chaokeytutorial; boolean chaokeytutorial;
boolean majorkeyskipattempted; boolean majorkeyskipattempted;
boolean enteredtutorialchallenge;
boolean finishedtutorialchallenge;
gdmusic_t musicstate; gdmusic_t musicstate;
// BACKWARDS COMPAT ASSIST // BACKWARDS COMPAT ASSIST

View file

@ -90,18 +90,7 @@ void M_InitExtras(INT32 choice)
// Tutorial // Tutorial
{ {
levelsearch_t templevelsearch; UINT16 map = G_GetFirstMapOfGametype(GT_TUTORIAL);
UINT8 i = 0;
INT16 map;
templevelsearch.cup = NULL;
templevelsearch.typeoflevel = G_TOLFlag(GT_TUTORIAL);
templevelsearch.cupmode = false;
templevelsearch.timeattack = false;
templevelsearch.tutorial = true;
templevelsearch.checklocked = true;
map = M_GetFirstLevelInList(&i, &templevelsearch);
EXTRAS_Main[extras_tutorial].status = (IT_STRING | EXTRAS_Main[extras_tutorial].status = (IT_STRING |
((map == NEXTMAP_INVALID) ? IT_TRANSTEXT : IT_CALL)); ((map == NEXTMAP_INVALID) ? IT_TRANSTEXT : IT_CALL));

View file

@ -80,7 +80,11 @@ static void M_StatisticsMaps(void)
headerexists = false; headerexists = false;
for (i = 0; i < nummapheaders; i++) for (i = 0; i < nummapheaders; i++)
{ {
M_StatisticsAddMap(i, NULL, &headerexists, true); if (M_StatisticsAddMap(i, NULL, &headerexists, true))
{
if (!(mapheaderinfo[i]->records.mapvisited & MV_BEATEN))
break;
}
} }
if ((i = statisticsmenu.numextramedals) != 0) if ((i = statisticsmenu.numextramedals) != 0)

View file

@ -31,8 +31,10 @@ menuitem_t OPTIONS_Main[] =
{IT_STRING | IT_SUBMENU, "Data Options", "Miscellaneous data options such as the screenshot format.", {IT_STRING | IT_SUBMENU, "Data Options", "Miscellaneous data options such as the screenshot format.",
NULL, {.submenu = &OPTIONS_DataDef}, 0, 0}, NULL, {.submenu = &OPTIONS_DataDef}, 0, 0},
#ifdef TODONEWMANUAL
{IT_STRING | IT_CALL, "Tricks & Secrets", "Those who bother reading a game manual always get the edge over those who don't!", {IT_STRING | IT_CALL, "Tricks & Secrets", "Those who bother reading a game manual always get the edge over those who don't!",
NULL, {.routine = M_Manual}, 0, 0}, NULL, {.routine = M_Manual}, 0, 0},
#endif
}; };
// For options menu, the 'extra1' field will determine the background colour to use for... the background! (What a concept!) // For options menu, the 'extra1' field will determine the background colour to use for... the background! (What a concept!)

View file

@ -110,7 +110,6 @@ void M_MPSetupNetgameMapSelect(INT32 choice)
levellist.netgame = true; levellist.netgame = true;
// Make sure we reset those // Make sure we reset those
levellist.levelsearch.timeattack = false; levellist.levelsearch.timeattack = false;
levellist.levelsearch.tutorial = false;
levellist.levelsearch.checklocked = true; levellist.levelsearch.checklocked = true;
cupgrid.grandprix = false; cupgrid.grandprix = false;

View file

@ -125,9 +125,25 @@ UINT16 M_CountLevelsToShowInList(levelsearch_t *levelsearch)
} }
for (i = 0; i < nummapheaders; i++) for (i = 0; i < nummapheaders; i++)
{
if (M_CanShowLevelInList(i, levelsearch)) if (M_CanShowLevelInList(i, levelsearch))
{
count++; count++;
// Tutorial will only show what you've made your way to
if (!levelsearch->checklocked)
continue;
if (!levelsearch->tutorial)
continue;
if (i >= basenummapheaders)
continue;
if (mapheaderinfo[i]->records.mapvisited & MV_BEATEN)
continue;
break;
}
}
return count; return count;
} }
@ -188,6 +204,13 @@ UINT16 M_GetNextLevelInList(UINT16 mapnum, UINT8 *i, levelsearch_t *levelsearch)
} }
else else
{ {
// Tutorial will only show what you've made your way to
if (levelsearch->checklocked
&& levelsearch->tutorial
&& mapnum < basenummapheaders
&& !(mapheaderinfo[mapnum]->records.mapvisited & MV_BEATEN))
return NEXTMAP_INVALID;
mapnum++; mapnum++;
while (!M_CanShowLevelInList(mapnum, levelsearch) && mapnum < nummapheaders) while (!M_CanShowLevelInList(mapnum, levelsearch) && mapnum < nummapheaders)
mapnum++; mapnum++;
@ -215,6 +238,7 @@ boolean M_LevelListFromGametype(INT16 gt)
{ {
static boolean first = true; static boolean first = true;
UINT8 temp = 0; UINT8 temp = 0;
boolean invalidatedcursor = false;
if (gt != -1) if (gt != -1)
{ {
@ -244,6 +268,15 @@ boolean M_LevelListFromGametype(INT16 gt)
} }
levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT)); levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT));
if (!levellist.levelsearch.cupmode)
{
invalidatedcursor = (
levellist.levelsearch.cup != NULL
|| levellist.levelsearch.tutorial != (gt == GT_TUTORIAL)
);
}
levellist.levelsearch.tutorial = (gt == GT_TUTORIAL);
CV_SetValue(&cv_dummyspbattack, 0); CV_SetValue(&cv_dummyspbattack, 0);
} }
@ -412,19 +445,36 @@ boolean M_LevelListFromGametype(INT16 gt)
// Okay, just a list of maps then. // Okay, just a list of maps then.
if (M_GetFirstLevelInList(&temp, &levellist.levelsearch) == NEXTMAP_INVALID) levellist.levelsearch.cup = NULL;
UINT16 test = M_GetFirstLevelInList(&temp, &levellist.levelsearch);
if (test == NEXTMAP_INVALID)
{ {
return false; return false;
} }
// Reset position properly if you go back & forth between gametypes // Reset position properly if you go back & forth between gametypes
if (levellist.levelsearch.cup) levellist.mapcount = M_CountLevelsToShowInList(&levellist.levelsearch);
if (levellist.levelsearch.tutorial && levellist.levelsearch.checklocked)
{
// Find the first level we haven't played.
UINT16 possiblecursor = 0;
while (test < nummapheaders && (mapheaderinfo[test]->records.mapvisited & MV_BEATEN))
{
test = M_GetNextLevelInList(test, &temp, &levellist.levelsearch);
possiblecursor++;
}
if (test != NEXTMAP_INVALID)
levellist.cursor = possiblecursor;
}
else if (invalidatedcursor)
{ {
levellist.cursor = 0; levellist.cursor = 0;
levellist.levelsearch.cup = NULL;
} }
levellist.mapcount = M_CountLevelsToShowInList(&levellist.levelsearch);
M_LevelSelectScrollDest(); M_LevelSelectScrollDest();
levellist.y = levellist.dest; levellist.y = levellist.dest;
@ -450,7 +500,6 @@ void M_LevelSelectInit(INT32 choice)
// Make sure this is reset as we'll only be using this function for offline games! // Make sure this is reset as we'll only be using this function for offline games!
levellist.netgame = false; levellist.netgame = false;
levellist.levelsearch.checklocked = true; levellist.levelsearch.checklocked = true;
levellist.levelsearch.tutorial = (gt == GT_TUTORIAL);
switch (currentMenu->menuitems[itemOn].mvar1) switch (currentMenu->menuitems[itemOn].mvar1)
{ {

View file

@ -64,6 +64,7 @@ void M_HandleImageDef(INT32 choice)
} }
// Opening manual // Opening manual
#ifdef TODONEWMANUAL
void M_Manual(INT32 choice) void M_Manual(INT32 choice)
{ {
(void)choice; (void)choice;
@ -71,3 +72,4 @@ void M_Manual(INT32 choice)
MISC_ManualDef.prevMenu = (choice == INT32_MAX ? NULL : currentMenu); MISC_ManualDef.prevMenu = (choice == INT32_MAX ? NULL : currentMenu);
M_SetupNextMenu(&MISC_ManualDef, true); M_SetupNextMenu(&MISC_ManualDef, true);
} }
#endif

View file

@ -40,6 +40,9 @@ menuitem_t PAUSE_Main[] =
{IT_STRING | IT_ARROWS, "CALL VOTE", "M_ICOVOT", {IT_STRING | IT_ARROWS, "CALL VOTE", "M_ICOVOT",
NULL, {.routine = M_HandlePauseMenuCallVote}, 0, 0}, NULL, {.routine = M_HandlePauseMenuCallVote}, 0, 0},
{IT_STRING | IT_CALL, "GIVE UP", "M_ICOGUP",
NULL, {.routine = M_GiveUp}, 0, 0},
{IT_STRING | IT_CALL, "RESTART MAP", "M_ICORE", {IT_STRING | IT_CALL, "RESTART MAP", "M_ICORE",
NULL, {.routine = M_RestartMap}, 0, 0}, NULL, {.routine = M_RestartMap}, 0, 0},
@ -130,6 +133,8 @@ void M_OpenPauseMenu(void)
#ifdef HAVE_DISCORDRPC #ifdef HAVE_DISCORDRPC
PAUSE_Main[mpause_discordrequests].status = IT_DISABLED; PAUSE_Main[mpause_discordrequests].status = IT_DISABLED;
#endif #endif
PAUSE_Main[mpause_giveup].status = IT_DISABLED;
PAUSE_Main[mpause_restartmap].status = IT_DISABLED; PAUSE_Main[mpause_restartmap].status = IT_DISABLED;
PAUSE_Main[mpause_tryagain].status = IT_DISABLED; PAUSE_Main[mpause_tryagain].status = IT_DISABLED;
@ -172,19 +177,33 @@ void M_OpenPauseMenu(void)
} }
else if (!netgame && !demo.playback) else if (!netgame && !demo.playback)
{ {
boolean retryallowed = (modeattacking != ATTACKING_NONE || gametype == GT_TUTORIAL); boolean retryallowed = (modeattacking != ATTACKING_NONE);
if ( boolean giveup = (
retryallowed == false grandprixinfo.gp == true
&& gamestate == GS_LEVEL && grandprixinfo.eventmode != GPEVENT_NONE
&& G_GametypeUsesLives() && roundqueue.size != 0
) );
if (tutorialchallenge == TUTORIALSKIP_INPROGRESS)
{ {
for (i = 0; i <= splitscreen; i++) // NO RETRY, ONLY GIVE UP
giveup = true;
}
else if (gamestate == GS_LEVEL && !retryallowed)
{
if (gametype == GT_TUTORIAL)
{ {
if (players[g_localplayers[i]].lives <= 1)
continue;
retryallowed = true; retryallowed = true;
break; }
else if (G_GametypeUsesLives())
{
for (i = 0; i <= splitscreen; i++)
{
if (players[g_localplayers[i]].lives <= 1)
continue;
retryallowed = true;
break;
}
} }
} }
@ -192,6 +211,11 @@ void M_OpenPauseMenu(void)
{ {
PAUSE_Main[mpause_tryagain].status = IT_STRING | IT_CALL; PAUSE_Main[mpause_tryagain].status = IT_STRING | IT_CALL;
} }
if (giveup)
{
PAUSE_Main[mpause_giveup].status = IT_STRING | IT_CALL;
}
} }
if (netgame) // && (PAUSE_Main[mpause_admin].status == IT_DISABLED)) if (netgame) // && (PAUSE_Main[mpause_admin].status == IT_DISABLED))
@ -398,6 +422,35 @@ void M_TryAgain(INT32 choice)
} }
} }
static void M_GiveUpResponse(INT32 ch)
{
if (ch != MA_YES)
return;
if (exitcountdown != 1)
{
G_BeginLevelExit();
exitcountdown = 1;
if (server)
SendNetXCmd(XD_EXITLEVEL, NULL, 0);
}
M_ClearMenus(false);
}
void M_GiveUp(INT32 choice)
{
(void)choice;
if (demo.playback)
return;
if (!Playing())
return;
M_StartMessage("Give up", M_GetText("Are you sure you want to\ngive up on this challenge?\n"), &M_GiveUpResponse, MM_YESNO, NULL, NULL);
}
// Pause spectate / join functions // Pause spectate / join functions
void M_ConfirmSpectate(INT32 choice) void M_ConfirmSpectate(INT32 choice)
{ {

View file

@ -1913,7 +1913,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
// spectating. Because in Free Play, this player // spectating. Because in Free Play, this player
// can enter the game again, and these flags would // can enter the game again, and these flags would
// make them intangible. // make them intangible.
if (K_Cooperative() && !target->player->spectator) if (!(gametyperules & GTR_CHECKPOINTS) && K_Cooperative() && !target->player->spectator)
{ {
target->player->pflags |= PF_ELIMINATED; target->player->pflags |= PF_ELIMINATED;
@ -2570,7 +2570,7 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source,
player->roundconditions.checkthisframe = true; player->roundconditions.checkthisframe = true;
} }
if (gametyperules & GTR_BUMPERS) if (gametyperules & (GTR_BUMPERS|GTR_CHECKPOINTS))
{ {
player->mo->health--; player->mo->health--;
} }

View file

@ -80,6 +80,7 @@ extern thinker_t thlist[];
extern mobj_t *mobjcache; extern mobj_t *mobjcache;
void P_InitThinkers(void); void P_InitThinkers(void);
void P_InvalidateThinkersWithoutInit(void);
void P_AddThinker(const thinklistnum_t n, thinker_t *thinker); void P_AddThinker(const thinklistnum_t n, thinker_t *thinker);
void P_RemoveThinker(thinker_t *thinker); void P_RemoveThinker(thinker_t *thinker);
void P_UnlinkThinker(thinker_t *thinker); void P_UnlinkThinker(thinker_t *thinker);

View file

@ -12812,7 +12812,7 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing)
else if (mobj->z == mobj->floorz) else if (mobj->z == mobj->floorz)
mobj->eflags |= MFE_ONGROUND; mobj->eflags |= MFE_ONGROUND;
mobj->angle = angle; mobj->angle = p->drawangle = angle;
// FAULT // FAULT
if (gamestate == GS_LEVEL && leveltime > introtime && !p->spectator) if (gamestate == GS_LEVEL && leveltime > introtime && !p->spectator)
@ -12827,6 +12827,7 @@ void P_MovePlayerToSpawn(INT32 playernum, mapthing_t *mthing)
p->respawn.pointx = x; p->respawn.pointx = x;
p->respawn.pointy = y; p->respawn.pointy = y;
p->respawn.pointz = z; p->respawn.pointz = z;
p->respawn.pointangle = angle;
} }
P_AfterPlayerSpawn(playernum); P_AfterPlayerSpawn(playernum);
@ -12881,7 +12882,7 @@ void P_MovePlayerToCheatcheck(INT32 playernum)
} }
} }
else else
p->drawangle = mobj->angle; // default to the camera angle p->drawangle = mobj->angle = p->respawn.pointangle;
K_DoIngameRespawn(p); K_DoIngameRespawn(p);
p->respawn.truedeath = true; p->respawn.truedeath = true;
@ -13055,6 +13056,9 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj)
skincolornum_t emcolor; skincolornum_t emcolor;
INT16 tagnum = mthing->tid; INT16 tagnum = mthing->tid;
if (tutorialchallenge == TUTORIALSKIP_INPROGRESS)
return false; // No out-of-sequence goodies
while (emblem) while (emblem)
{ {
if (emblem->type == ET_GLOBAL && emblem->tag == tagnum) if (emblem->type == ET_GLOBAL && emblem->tag == tagnum)
@ -13622,7 +13626,7 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj)
} }
case MT_SPRAYCAN: case MT_SPRAYCAN:
{ {
if (nummapspraycans == UINT8_MAX) if (nummapspraycans == UINT8_MAX || tutorialchallenge == TUTORIALSKIP_INPROGRESS)
{ {
P_RemoveMobj(mobj); P_RemoveMobj(mobj);
return false; return false;

View file

@ -7609,7 +7609,10 @@ static void P_InitLevelSettings(void)
gamespeed = grandprixinfo.gamespeed; gamespeed = grandprixinfo.gamespeed;
} }
} }
else if (modeattacking) else if (
modeattacking != ATTACKING_NONE
|| tutorialchallenge == TUTORIALSKIP_INPROGRESS
)
{ {
if (gametyperules & GTR_CIRCUIT) if (gametyperules & GTR_CIRCUIT)
{ {
@ -8286,7 +8289,14 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
} }
} }
if (K_PodiumHasEmerald()) // Default
levelfadecol = 31;
if (gamestate == GS_TITLESCREEN)
{
;
}
else if (K_PodiumHasEmerald())
{ {
// Special Stage out // Special Stage out
if (ranspecialwipe != 2) if (ranspecialwipe != 2)
@ -8316,11 +8326,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
levelfadecol = 0; levelfadecol = 0;
wipetype = wipe_encore_towhite; wipetype = wipe_encore_towhite;
} }
else
{
// Default
levelfadecol = 31;
}
if (rendermode != render_none) if (rendermode != render_none)
{ {
@ -8343,6 +8348,8 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
K_UnsetDialogue(); K_UnsetDialogue();
ACS_InvalidateMapScope();
LUA_InvalidateLevel(); LUA_InvalidateLevel();
for (ss = sectors; sectors+numsectors != ss; ss++) for (ss = sectors; sectors+numsectors != ss; ss++)

View file

@ -4132,14 +4132,17 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha
} }
else else
{ {
// args[2]: cap rings to -20 instead of 0
SINT8 baseline = (args[2] ? -20 : 0);
// Don't push you below baseline // Don't push you below baseline
if (mo->player->rings <= 0) if (mo->player->rings <= baseline)
return false; return false;
rings = -(rings); rings = -(rings);
if (rings > mo->player->rings) if (rings > (mo->player->rings - baseline))
rings = mo->player->rings; rings = (mo->player->rings - baseline);
mo->player->rings -= rings; mo->player->rings -= rings;
S_StartSound(mo, sfx_antiri); S_StartSound(mo, sfx_antiri);

View file

@ -55,6 +55,8 @@
tic_t leveltime; tic_t leveltime;
boolean thinkersCompleted; boolean thinkersCompleted;
UINT32 thinker_era = 0;
static boolean g_freezeCheat; static boolean g_freezeCheat;
static boolean g_freezeLevel; static boolean g_freezeLevel;
@ -278,6 +280,8 @@ void P_InitThinkers(void)
{ {
UINT8 i; UINT8 i;
P_InvalidateThinkersWithoutInit();
for (i = 0; i < NUM_THINKERLISTS; i++) for (i = 0; i < NUM_THINKERLISTS; i++)
{ {
thlist[i].prev = thlist[i].next = &thlist[i]; thlist[i].prev = thlist[i].next = &thlist[i];
@ -299,6 +303,15 @@ void P_InitThinkers(void)
Obj_ResetCheckpoints(); Obj_ResetCheckpoints();
} }
//
// P_InvalidateThinkersWithoutInit
//
void P_InvalidateThinkersWithoutInit(void)
{
thinker_era++;
}
// Adds a new thinker at the end of the list. // Adds a new thinker at the end of the list.
void P_AddThinker(const thinklistnum_t n, thinker_t *thinker) void P_AddThinker(const thinklistnum_t n, thinker_t *thinker)
{ {
@ -855,15 +868,22 @@ void P_Ticker(boolean run)
ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time; ps_playerthink_time = I_GetPreciseTime() - ps_playerthink_time;
// TODO would this be laggy with more conditions in play... if (gamedata && gamestate == GS_LEVEL && !demo.playback)
if (((!demo.playback && leveltime > introtime && M_UpdateUnlockablesAndExtraEmblems(true, false)) {
|| (gamedata && gamedata->deferredsave))) // Keep track of how long they've been playing!
G_SaveGameData(); gamedata->totalplaytime++;
}
// Keep track of how long they've been playing! // TODO would this be laggy with more conditions in play...
if (!demo.playback) // Don't increment if a demo is playing. if (
gamedata->totalplaytime++; (leveltime > introtime
&& M_UpdateUnlockablesAndExtraEmblems(true, false))
|| gamedata->deferredsave
)
{
G_SaveGameData();
}
}
}
if (run) if (run)
{ {
@ -1049,7 +1069,7 @@ void P_Ticker(boolean run)
} }
player_t *player = &players[i]; player_t *player = &players[i];
if (K_PlayerTallyActive(player) == true && player->tally.done == false) if (player->spectator == false && K_PlayerTallyActive(player) == true && player->tally.done == false)
{ {
run_exit_countdown = false; run_exit_countdown = false;
break; break;
@ -1153,7 +1173,7 @@ void P_Ticker(boolean run)
P_RunChaseCameras(); P_RunChaseCameras();
} }
if (run) if (run && !levelloading && leveltime)
{ {
K_TickDialogue(); K_TickDialogue();
} }

View file

@ -43,6 +43,8 @@ void P_PreTicker(INT32 frames);
void P_DoTeamscrambling(void); void P_DoTeamscrambling(void);
void P_RemoveThinkerDelayed(thinker_t *thinker); //killed void P_RemoveThinkerDelayed(thinker_t *thinker); //killed
extern UINT32 thinker_era;
mobj_t *P_SetTarget2(mobj_t **mo, mobj_t *target mobj_t *P_SetTarget2(mobj_t **mo, mobj_t *target
#ifdef PARANOIA #ifdef PARANOIA
, const char *source_file, int source_line , const char *source_file, int source_line

View file

@ -2824,7 +2824,7 @@ static void P_DeathThink(player_t *player)
} }
} }
if ((player->pflags & PF_ELIMINATED) && (gametyperules & GTR_BUMPERS)) if ((player->pflags & PF_ELIMINATED) /*&& (gametyperules & GTR_BUMPERS)*/)
{ {
playerGone = true; playerGone = true;
} }

View file

@ -1250,8 +1250,6 @@ static void ST_overlayDrawer(void)
} }
K_DrawMidVote(); K_DrawMidVote();
K_DrawDialogue();
} }
void ST_DrawDemoTitleEntry(void) void ST_DrawDemoTitleEntry(void)
@ -1558,6 +1556,8 @@ void ST_Drawer(void)
if (stagetitle) if (stagetitle)
ST_drawTitleCard(); ST_drawTitleCard();
K_DrawDialogue();
// Replay manual-save stuff // Replay manual-save stuff
if (demo.recording && multiplayer && demo.savebutton && demo.savebutton + 3*TICRATE < leveltime) if (demo.recording && multiplayer && demo.savebutton && demo.savebutton + 3*TICRATE < leveltime)
{ {

View file

@ -1905,7 +1905,9 @@ void Y_DetermineIntermissionType(void)
// or for failing in time attack mode // or for failing in time attack mode
|| (modeattacking && (players[consoleplayer].pflags & PF_NOCONTEST)) || (modeattacking && (players[consoleplayer].pflags & PF_NOCONTEST))
// or for explicit requested skip (outside of modeattacking) // or for explicit requested skip (outside of modeattacking)
|| (modeattacking == ATTACKING_NONE && skipstats != 0)) || (modeattacking == ATTACKING_NONE && skipstats != 0)
// or tutorial skip material
|| (nextmapoverride == NEXTMAP_TUTORIALCHALLENGE+1 || tutorialchallenge != TUTORIALSKIP_NONE))
{ {
intertype = int_none; intertype = int_none;
return; return;