Merge branch 'tutorial-access' into 'master'

Tutorial access

See merge request KartKrew/Kart!1102
This commit is contained in:
toaster 2023-04-06 17:02:59 +00:00
commit bbf2fc0b5f
21 changed files with 287 additions and 131 deletions

View file

@ -1047,9 +1047,6 @@ void D_ClearState(void)
if (rendermode != render_none) if (rendermode != render_none)
V_SetPaletteLump("PLAYPAL"); V_SetPaletteLump("PLAYPAL");
// The title screen is obviously not a tutorial! (Unless I'm mistaken)
tutorialmode = false;
cursongcredit.def = NULL; cursongcredit.def = NULL;
S_StopSounds(); S_StopSounds();

View file

@ -3014,8 +3014,6 @@ static void Command_Map_f(void)
} }
} }
tutorialmode = false; // warping takes us out of tutorial mode
D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, fromlevelselect); D_MapChange(newmapnum, newgametype, newencoremode, newresetplayers, 0, false, fromlevelselect);
Z_Free(realmapname); Z_Free(realmapname);

View file

@ -1122,6 +1122,11 @@ void readlevelheader(MYFILE *f, char * name)
deh_strlcpy(mapheaderinfo[num]->zonttl, word2, deh_strlcpy(mapheaderinfo[num]->zonttl, word2,
sizeof(mapheaderinfo[num]->zonttl), va("Level header %d: zonetitle", num)); sizeof(mapheaderinfo[num]->zonttl), va("Level header %d: zonetitle", num));
} }
else if (fastcmp(word, "RELEVANTSKIN"))
{
deh_strlcpy(mapheaderinfo[num]->relevantskin, word2,
sizeof(mapheaderinfo[num]->relevantskin), va("Level header %d: relevantskin", num));
}
else if (fastcmp(word, "SCRIPTNAME")) else if (fastcmp(word, "SCRIPTNAME"))
{ {
deh_strlcpy(mapheaderinfo[num]->scriptname, word2, deh_strlcpy(mapheaderinfo[num]->scriptname, word2,
@ -3211,11 +3216,6 @@ void readmaincfg(MYFILE *f, boolean mainfile)
bootmap = Z_StrDup(word2); bootmap = Z_StrDup(word2);
//titlechanged = true; //titlechanged = true;
} }
else if (fastcmp(word, "TUTORIALMAP"))
{
Z_Free(tutorialmap);
tutorialmap = Z_StrDup(word2);
}
else if (fastcmp(word, "PODIUMMAP")) else if (fastcmp(word, "PODIUMMAP"))
{ {
Z_Free(podiummap); Z_Free(podiummap);

View file

@ -5854,6 +5854,7 @@ const char *const GAMETYPERULE_LIST[] = {
"NOMP", "NOMP",
"NOCUPSELECT", "NOCUPSELECT",
"NOPOSITION",
NULL NULL
}; };

View file

@ -222,9 +222,6 @@ extern char * titlemap;
extern boolean hidetitlepics; extern boolean hidetitlepics;
extern char * bootmap; //bootmap for loading a map on startup extern char * bootmap; //bootmap for loading a map on startup
extern char * tutorialmap; // map to load for tutorial
extern boolean tutorialmode; // are we in a tutorial right now?
extern char * podiummap; // map to load for podium extern char * podiummap; // map to load for podium
extern boolean looptitle; extern boolean looptitle;
@ -430,6 +427,7 @@ struct mapheader_t
UINT32 typeoflevel; ///< Combination of typeoflevel flags. UINT32 typeoflevel; ///< Combination of typeoflevel flags.
UINT8 numlaps; ///< Number of laps in circuit mode, unless overridden. UINT8 numlaps; ///< Number of laps in circuit mode, unless overridden.
fixed_t gravity; ///< Map-wide gravity. fixed_t gravity; ///< Map-wide gravity.
char relevantskin[SKINNAMESIZE+1]; ///< Skin to use for tutorial (if not provided, uses Eggman.)
// Music information // Music information
char musname[MAXMUSNAMES][7]; ///< Music tracks to play. First dimension is the track number, second is the music string. "" for no music. char musname[MAXMUSNAMES][7]; ///< Music tracks to play. First dimension is the track number, second is the music string. "" for no music.
@ -499,6 +497,7 @@ enum GameType
GT_BATTLE, GT_BATTLE,
GT_SPECIAL, GT_SPECIAL,
GT_VERSUS, GT_VERSUS,
GT_TUTORIAL,
GT_FIRSTFREESLOT, GT_FIRSTFREESLOT,
GT_LASTFREESLOT = 127, // Previously (GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1) - it would be necessary to rewrite VOTEMODIFIER_ENCORE to go higher than this. GT_LASTFREESLOT = 127, // Previously (GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1) - it would be necessary to rewrite VOTEMODIFIER_ENCORE to go higher than this.
@ -546,10 +545,10 @@ enum GameTypeRules
// Bonus gametype rules // Bonus gametype rules
GTR_PRISONS = 1<<10, // Can enter Prison Break mode GTR_PRISONS = 1<<10, // Can enter Prison Break mode
GTR_CATCHER = 1<<11, // UFO Catcher (only works with GTR_CIRCUIT) GTR_CATCHER = 1<<11, // UFO Catcher (only works with GTR_CIRCUIT)
GTR_ROLLINGSTART = 1<<12, // Rolling start (only works with GTR_CIRCUIT) GTR_ROLLINGSTART = 1<<12, // Rolling start (only works with GTR_CIRCUIT)
GTR_SPECIALSTART = 1<<13, // White fade instant start GTR_SPECIALSTART = 1<<13, // White fade instant start
GTR_BOSS = 1<<14, // Boss intro and spawning GTR_BOSS = 1<<14, // Boss intro and spawning
// General purpose rules // General purpose rules
GTR_POINTLIMIT = 1<<15, // Reaching point limit ends the round GTR_POINTLIMIT = 1<<15, // Reaching point limit ends the round
@ -557,12 +556,13 @@ enum GameTypeRules
GTR_OVERTIME = 1<<17, // Allow overtime behavior GTR_OVERTIME = 1<<17, // Allow overtime behavior
GTR_ENCORE = 1<<18, // Alternate Encore mirroring, scripting, and texture remapping GTR_ENCORE = 1<<18, // Alternate Encore mirroring, scripting, and texture remapping
GTR_TEAMS = 1<<19, // Teams are forced on GTR_TEAMS = 1<<19, // Teams are forced on
GTR_NOTEAMS = 1<<20, // Teams are forced off GTR_NOTEAMS = 1<<20, // Teams are forced off
GTR_TEAMSTARTS = 1<<21, // Use team-based start positions GTR_TEAMSTARTS = 1<<21, // Use team-based start positions
GTR_NOMP = 1<<22, // No multiplayer GTR_NOMP = 1<<22, // No multiplayer
GTR_NOCUPSELECT = 1<<23, // Your maps are not selected via cup. GTR_NOCUPSELECT = 1<<23, // Your maps are not selected via cup.
GTR_NOPOSITION = 1<<24, // No POSITION
// free: to and including 1<<31 // free: to and including 1<<31
}; };
@ -577,10 +577,11 @@ enum GameTypeRules
enum TypeOfLevel enum TypeOfLevel
{ {
// Gametypes // Gametypes
TOL_RACE = 0x0001, ///< Race TOL_RACE = 0x0001, ///< Race
TOL_BATTLE = 0x0002, ///< Battle TOL_BATTLE = 0x0002, ///< Battle
TOL_BOSS = 0x0004, ///< Boss (variant of battle, but forbidden) TOL_BOSS = 0x0004, ///< Boss (variant of battle, but forbidden)
TOL_SPECIAL = 0x0008, ///< Special Stage (variant of race, but forbidden) TOL_SPECIAL = 0x0008, ///< Special Stage (variant of race, but forbidden)
TOL_TUTORIAL = 0x0010, ///< Tutorial (variant of race, but forbidden)
// Modifiers // Modifiers
TOL_TV = 0x0100 ///< Midnight Channel specific: draw TV like overlay on HUD TOL_TV = 0x0100 ///< Midnight Channel specific: draw TV like overlay on HUD

View file

@ -2826,7 +2826,7 @@ static boolean F_GetTextPromptTutorialTag(char *tag, INT32 length)
INT32 gcs = 0; INT32 gcs = 0;
boolean suffixed = true; boolean suffixed = true;
if (!tag || !tag[0] || !tutorialmode) if (!tag || !tag[0] || gametype == GT_TUTORIAL)
return false; return false;
/* /*
@ -2859,7 +2859,7 @@ static boolean F_GetTextPromptTutorialTag(char *tag, INT32 length)
void F_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum) void F_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum)
{ {
INT32 nosuffixpromptnum = INT32_MAX, nosuffixpagenum = INT32_MAX; INT32 nosuffixpromptnum = INT32_MAX, nosuffixpagenum = INT32_MAX;
INT32 tutorialpromptnum = (tutorialmode) ? TUTORIAL_PROMPT-1 : 0; INT32 tutorialpromptnum = (gametype == GT_TUTORIAL) ? TUTORIAL_PROMPT-1 : 0;
boolean suffixed = false, found = false; boolean suffixed = false, found = false;
char suffixedtag[33]; char suffixedtag[33];
@ -2871,7 +2871,7 @@ void F_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum
strncpy(suffixedtag, tag, 33); strncpy(suffixedtag, tag, 33);
suffixedtag[32] = 0; suffixedtag[32] = 0;
if (tutorialmode) if (gametype == GT_TUTORIAL)
suffixed = F_GetTextPromptTutorialTag(suffixedtag, 33); suffixed = F_GetTextPromptTutorialTag(suffixedtag, 33);
for (*promptnum = 0 + tutorialpromptnum; *promptnum < MAX_PROMPTS; (*promptnum)++) for (*promptnum = 0 + tutorialpromptnum; *promptnum < MAX_PROMPTS; (*promptnum)++)

View file

@ -167,9 +167,6 @@ char * titlemap = NULL;
boolean hidetitlepics = false; boolean hidetitlepics = false;
char * bootmap = NULL; //bootmap for loading a map on startup char * bootmap = NULL; //bootmap for loading a map on startup
char * tutorialmap = NULL; // map to load for tutorial
boolean tutorialmode = false; // are we in a tutorial right now?
char * podiummap = NULL; // map to load for podium char * podiummap = NULL; // map to load for podium
boolean looptitle = true; boolean looptitle = true;
@ -3398,6 +3395,17 @@ static gametype_t defaultgametypes[] =
0, 0,
0, 0,
}, },
// GT_TUTORIAL
{
"Tutorial",
"GT_TUTORIAL",
GTR_NOMP|GTR_NOCUPSELECT|GTR_NOPOSITION,
TOL_TUTORIAL,
int_none,
0,
0,
},
}; };
gametype_t *gametypes[MAXGAMETYPES+1] = gametype_t *gametypes[MAXGAMETYPES+1] =
@ -3406,6 +3414,7 @@ gametype_t *gametypes[MAXGAMETYPES+1] =
&defaultgametypes[GT_BATTLE], &defaultgametypes[GT_BATTLE],
&defaultgametypes[GT_SPECIAL], &defaultgametypes[GT_SPECIAL],
&defaultgametypes[GT_VERSUS], &defaultgametypes[GT_VERSUS],
&defaultgametypes[GT_TUTORIAL],
}; };
// //
@ -3541,6 +3550,7 @@ tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = {
{"BATTLE",TOL_BATTLE}, {"BATTLE",TOL_BATTLE},
{"BOSS",TOL_BOSS}, {"BOSS",TOL_BOSS},
{"SPECIAL",TOL_SPECIAL}, {"SPECIAL",TOL_SPECIAL},
{"TUTORIAL",TOL_TUTORIAL},
{"TV",TOL_TV}, {"TV",TOL_TV},
{NULL, 0} {NULL, 0}
}; };
@ -3667,6 +3677,7 @@ INT16 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.checklocked = true; templevelsearch.checklocked = true;
if (templevelsearch.cupmode) if (templevelsearch.cupmode)
@ -4246,16 +4257,19 @@ static void G_DoCompleted(void)
if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified) if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified)
{ {
UINT8 roundtype = GDGT_CUSTOM; if (gametype != GT_TUTORIAL)
{
UINT8 roundtype = GDGT_CUSTOM;
if (gametype == GT_RACE) if (gametype == GT_RACE)
roundtype = GDGT_RACE; roundtype = GDGT_RACE;
else if (gametype == GT_BATTLE) else if (gametype == GT_BATTLE)
roundtype = (battleprisons ? GDGT_PRISONS : GDGT_BATTLE); roundtype = (battleprisons ? GDGT_PRISONS : GDGT_BATTLE);
else if (gametype == GT_SPECIAL || gametype == GT_VERSUS) else if (gametype == GT_SPECIAL || gametype == GT_VERSUS)
roundtype = GDGT_SPECIAL; roundtype = GDGT_SPECIAL;
gamedata->roundsplayed[roundtype]++; gamedata->roundsplayed[roundtype]++;
}
gamedata->pendingkeyrounds++; gamedata->pendingkeyrounds++;
// Done before forced addition of PF_NOCONTEST to make UCRP_NOCONTEST harder to achieve // Done before forced addition of PF_NOCONTEST to make UCRP_NOCONTEST harder to achieve

View file

@ -715,6 +715,12 @@ boolean K_CanChangeRules(boolean allowdemos)
return false; return false;
} }
if (gametype == GT_TUTORIAL)
{
// Tutorials are locked down.
return false;
}
if (!allowdemos && demo.playback) if (!allowdemos && demo.playback)
{ {
// We've already got our important settings! // We've already got our important settings!

View file

@ -2488,7 +2488,7 @@ static void K_DrawLivesDigits(INT32 x, INT32 y, INT32 width, INT32 flags, patch_
V_DrawScaledPatch(x, y, flags, font[stplyr->lives % 10]); V_DrawScaledPatch(x, y, flags, font[stplyr->lives % 10]);
} }
static void K_drawRingCounter(void) static void K_drawRingCounter(boolean gametypeinfoshown)
{ {
const boolean uselives = G_GametypeUsesLives(); const boolean uselives = G_GametypeUsesLives();
SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe]; SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe];
@ -2547,43 +2547,57 @@ static void K_drawRingCounter(void)
fr = fx; fr = fx;
if (gametypeinfoshown)
{
fy -= 10;
}
// Rings // Rings
if (!uselives) if (!uselives)
{ {
V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy-10, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[1]); V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[1]->width) - 3) : 0), fy, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[1]);
if (flipflag) if (flipflag)
fr += 15; fr += 15;
} }
else else
V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[0]->width) - 3) : 0), fy-10, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]); V_DrawScaledPatch(fx-2 + (flipflag ? (SHORT(kp_ringstickersplit[0]->width) - 3) : 0), fy, V_HUDTRANS|V_SLIDEIN|splitflags|flipflag, kp_ringstickersplit[0]);
V_DrawMappedPatch(fr+ringx, fy-13, V_HUDTRANS|V_SLIDEIN|splitflags|ringflip, kp_smallring[ringanim_realframe], (colorring ? ringmap : NULL)); V_DrawMappedPatch(fr+ringx, fy-3, V_HUDTRANS|V_SLIDEIN|splitflags|ringflip, kp_smallring[ringanim_realframe], (colorring ? ringmap : NULL));
if (stplyr->rings < 0) // Draw the minus for ring debt if (stplyr->rings < 0) // Draw the minus for ring debt
V_DrawMappedPatch(fr+7, fy-10, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringdebtminussmall, ringmap); V_DrawMappedPatch(fr+7, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringdebtminussmall, ringmap);
V_DrawMappedPatch(fr+11, fy-10, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[0]], ringmap); V_DrawMappedPatch(fr+11, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[0]], ringmap);
V_DrawMappedPatch(fr+15, fy-10, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[1]], ringmap); V_DrawMappedPatch(fr+15, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[1]], ringmap);
// SPB ring lock // SPB ring lock
if (stplyr->pflags & PF_RINGLOCK) if (stplyr->pflags & PF_RINGLOCK)
V_DrawScaledPatch(fr-12, fy-23, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblocksmall[stplyr->karthud[khud_ringspblock]]); V_DrawScaledPatch(fr-12, fy-13, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblocksmall[stplyr->karthud[khud_ringspblock]]);
// Lives // Lives
if (uselives) if (uselives)
{ {
UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE); UINT8 *colormap = R_GetTranslationColormap(stplyr->skin, stplyr->skincolor, GTC_CACHE);
V_DrawMappedPatch(fr+21, fy-13, V_HUDTRANS|V_SLIDEIN|splitflags, faceprefix[stplyr->skin][FACE_MINIMAP], colormap); V_DrawMappedPatch(fr+21, fy-3, V_HUDTRANS|V_SLIDEIN|splitflags, faceprefix[stplyr->skin][FACE_MINIMAP], colormap);
if (stplyr->lives >= 0) if (stplyr->lives >= 0)
K_DrawLivesDigits(fr+34, fy-10, 4, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font); K_DrawLivesDigits(fr+34, fy, 4, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font);
} }
} }
else else
{ {
fy = LAPS_Y-11; fy = LAPS_Y;
if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) if (gametypeinfoshown)
fy -= 4; {
fy -= 11;
if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS)
fy -= 4;
}
else
{
fy += 9;
}
// Rings // Rings
if (!uselives) if (!uselives)
@ -2622,9 +2636,9 @@ static void K_drawRingCounter(void)
#undef RINGANIM_FLIPFRAME #undef RINGANIM_FLIPFRAME
static void K_drawKartAccessibilityIcons(INT32 fx) static void K_drawKartAccessibilityIcons(boolean gametypeinfoshown, INT32 fx)
{ {
INT32 fy = LAPS_Y-25; INT32 fy = LAPS_Y-14;
INT32 splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN; INT32 splitflags = V_SNAPTOLEFT|V_SNAPTOBOTTOM|V_SPLITSCREEN;
//INT32 step = 1; -- if there's ever more than one accessibility icon //INT32 step = 1; -- if there's ever more than one accessibility icon
@ -2632,8 +2646,17 @@ static void K_drawKartAccessibilityIcons(INT32 fx)
if (r_splitscreen < 2) // adjust to speedometer height if (r_splitscreen < 2) // adjust to speedometer height
{ {
if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) if (gametypeinfoshown)
fy -= 4; {
fy -= 11;
if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS)
fy -= 4;
}
else
{
fy += 9;
}
} }
else else
{ {
@ -2681,13 +2704,13 @@ static void K_drawKartAccessibilityIcons(INT32 fx)
} }
} }
static void K_drawKartSpeedometer(void) static void K_drawKartSpeedometer(boolean gametypeinfoshown)
{ {
static fixed_t convSpeed; static fixed_t convSpeed;
UINT8 labeln = 0; UINT8 labeln = 0;
UINT8 numbers[3]; UINT8 numbers[3];
INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN;
INT32 battleoffset = 0; INT32 fy = LAPS_Y-14;
if (!stplyr->exiting) // Keep the same speed value as when you crossed the finish line! if (!stplyr->exiting) // Keep the same speed value as when you crossed the finish line!
{ {
@ -2722,19 +2745,28 @@ static void K_drawKartSpeedometer(void)
numbers[1] = ((convSpeed / 10) % 10); numbers[1] = ((convSpeed / 10) % 10);
numbers[2] = (convSpeed % 10); numbers[2] = (convSpeed % 10);
if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) if (gametypeinfoshown)
battleoffset = -4; {
fy -= 11;
V_DrawScaledPatch(LAPS_X, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometersticker); if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS)
V_DrawScaledPatch(LAPS_X+7, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[0]]); fy -= 4;
V_DrawScaledPatch(LAPS_X+13, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[1]]); }
V_DrawScaledPatch(LAPS_X+19, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[2]]); else
V_DrawScaledPatch(LAPS_X+29, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometerlabel[labeln]); {
fy += 9;
}
K_drawKartAccessibilityIcons(56); V_DrawScaledPatch(LAPS_X, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometersticker);
V_DrawScaledPatch(LAPS_X+7, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[0]]);
V_DrawScaledPatch(LAPS_X+13, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[1]]);
V_DrawScaledPatch(LAPS_X+19, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[2]]);
V_DrawScaledPatch(LAPS_X+29, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometerlabel[labeln]);
K_drawKartAccessibilityIcons(gametypeinfoshown, 56);
} }
static void K_drawBlueSphereMeter(void) static void K_drawBlueSphereMeter(boolean gametypeinfoshown)
{ {
const UINT8 maxBars = 4; const UINT8 maxBars = 4;
const UINT8 segColors[] = {73, 64, 52, 54, 55, 35, 34, 33, 202, 180, 181, 182, 164, 165, 166, 153, 152}; const UINT8 segColors[] = {73, 64, 52, 54, 55, 35, 34, 33, 202, 180, 181, 182, 164, 165, 166, 153, 152};
@ -2752,7 +2784,17 @@ static void K_drawBlueSphereMeter(void)
if (r_splitscreen < 2) // don't change shit for THIS splitscreen. if (r_splitscreen < 2) // don't change shit for THIS splitscreen.
{ {
fx = LAPS_X; fx = LAPS_X;
fy = LAPS_Y-22; fy = LAPS_Y-7;
if (gametypeinfoshown)
{
fy -= 11 + 4;
}
else
{
fy += 9;
}
V_DrawScaledPatch(fx, fy, splitflags|flipflag, kp_spheresticker); V_DrawScaledPatch(fx, fy, splitflags|flipflag, kp_spheresticker);
} }
else else
@ -2771,7 +2813,12 @@ static void K_drawBlueSphereMeter(void)
flipflag = V_FLIP; // make the string right aligned and other shit flipflag = V_FLIP; // make the string right aligned and other shit
xstep = -xstep; xstep = -xstep;
} }
fy -= 16;
if (gametypeinfoshown)
{
fy -= 16;
}
V_DrawScaledPatch(fx, fy, splitflags|flipflag, kp_splitspheresticker); V_DrawScaledPatch(fx, fy, splitflags|flipflag, kp_splitspheresticker);
} }
@ -4294,21 +4341,7 @@ static void K_drawBattleFullscreen(void)
} }
// FREE PLAY? // FREE PLAY?
{ K_drawKartFreePlay();
UINT8 i;
// check to see if there's anyone else at all
for (i = 0; i < MAXPLAYERS; i++)
{
if (i == displayplayers[0])
continue;
if (playeringame[i] && !players[i].spectator)
break;
}
if (i != MAXPLAYERS)
K_drawKartFreePlay();
}
} }
static void K_drawKartFirstPerson(void) static void K_drawKartFirstPerson(void)
@ -4700,12 +4733,13 @@ static void K_drawTrickCool(void)
void K_drawKartFreePlay(void) void K_drawKartFreePlay(void)
{ {
// Doesn't support splitscreens higher than 2 for real estate reasons.
if (!LUA_HudEnabled(hud_freeplay)) if (!LUA_HudEnabled(hud_freeplay))
return; return;
if (modeattacking || grandprixinfo.gp || bossinfo.valid || stplyr->spectator) if (stplyr->spectator == true)
return;
if (M_NotFreePlay(stplyr) == true)
return; return;
if (lt_exitticker < TICRATE/2) if (lt_exitticker < TICRATE/2)
@ -5081,6 +5115,8 @@ void K_drawKartHUD(void)
} }
else else
{ {
boolean gametypeinfoshown = false;
if (LUA_HudEnabled(hud_position)) if (LUA_HudEnabled(hud_position))
{ {
if (bossinfo.valid) if (bossinfo.valid)
@ -5102,31 +5138,36 @@ void K_drawKartHUD(void)
{ {
if (gametyperules & GTR_CIRCUIT) if (gametyperules & GTR_CIRCUIT)
{ {
K_drawKartLaps(); if (numlaps > 1)
{
K_drawKartLaps();
gametypeinfoshown = true;
}
} }
else if (gametyperules & GTR_BUMPERS) else if (gametyperules & GTR_BUMPERS)
{ {
K_drawKartBumpersOrKarma(); K_drawKartBumpersOrKarma();
gametypeinfoshown = true;
} }
} }
// Draw the speedometer and/or accessibility icons // Draw the speedometer and/or accessibility icons
if (cv_kartspeedometer.value && !r_splitscreen && (LUA_HudEnabled(hud_speedometer))) if (cv_kartspeedometer.value && !r_splitscreen && (LUA_HudEnabled(hud_speedometer)))
{ {
K_drawKartSpeedometer(); K_drawKartSpeedometer(gametypeinfoshown);
} }
else else
{ {
K_drawKartAccessibilityIcons(0); K_drawKartAccessibilityIcons(gametypeinfoshown, 0);
} }
if (gametyperules & GTR_SPHERES) if (gametyperules & GTR_SPHERES)
{ {
K_drawBlueSphereMeter(); K_drawBlueSphereMeter(gametypeinfoshown);
} }
else else
{ {
K_drawRingCounter(); K_drawRingCounter(gametypeinfoshown);
} }
if (modeattacking && !bossinfo.valid) if (modeattacking && !bossinfo.valid)
@ -5180,8 +5221,7 @@ void K_drawKartHUD(void)
V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem); V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem);
// Draw FREE PLAY. // Draw FREE PLAY.
if (islonesome) K_drawKartFreePlay();
K_drawKartFreePlay();
if (r_splitscreen == 0 && (stplyr->pflags & PF_WRONGWAY) && ((leveltime / 8) & 1)) if (r_splitscreen == 0 && (stplyr->pflags & PF_WRONGWAY) && ((leveltime / 8) & 1))
{ {

View file

@ -187,7 +187,12 @@ void K_TimerInit(void)
} }
} }
starttime = (introtime + (3*TICRATE)) + ((2*TICRATE) + (numbulbs * bulbtime)); // Start countdown time, + buffer time starttime = introtime;
if (!(gametyperules & GTR_NOPOSITION))
{
// Start countdown time + buffer time
starttime += ((3*TICRATE) + ((2*TICRATE) + (numbulbs * bulbtime)));
}
} }
K_BattleInit(domodeattack); K_BattleInit(domodeattack);
@ -11695,6 +11700,11 @@ boolean K_Cooperative(void)
return true; return true;
} }
if (specialstageinfo.valid)
{
return true;
}
return false; return false;
} }

View file

@ -733,6 +733,7 @@ typedef struct levelsearch_s {
UINT32 typeoflevel; UINT32 typeoflevel;
cupheader_t *cup; cupheader_t *cup;
boolean timeattack; boolean timeattack;
boolean tutorial;
boolean cupmode; boolean cupmode;
boolean checklocked; boolean checklocked;
} levelsearch_t; } levelsearch_t;
@ -1017,8 +1018,8 @@ extern struct extrasmenu_s {
typedef enum typedef enum
{ {
extras_addons = 0, extras_addons = 0,
extras_tutorial,
extras_challenges, extras_challenges,
//extras_tutorial,
extras_statistics, extras_statistics,
extras_eggtv, extras_eggtv,
extras_stereo, extras_stereo,

View file

@ -2411,6 +2411,12 @@ void M_DrawLevelSelect(void)
INT16 y = 80 - (12 * levellist.y); INT16 y = 80 - (12 * levellist.y);
boolean tatransition = ((menutransition.startmenu == &PLAY_TimeAttackDef || menutransition.endmenu == &PLAY_TimeAttackDef) && menutransition.tics); boolean tatransition = ((menutransition.startmenu == &PLAY_TimeAttackDef || menutransition.endmenu == &PLAY_TimeAttackDef) && menutransition.tics);
if (levellist.levelsearch.tutorial)
{
patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE);
V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL);
}
if (tatransition) if (tatransition)
{ {
t = -t; t = -t;
@ -5183,6 +5189,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
templevelsearch.typeoflevel = G_TOLFlag(GT_RACE)|G_TOLFlag(GT_BATTLE); templevelsearch.typeoflevel = G_TOLFlag(GT_RACE)|G_TOLFlag(GT_BATTLE);
templevelsearch.cupmode = true; templevelsearch.cupmode = true;
templevelsearch.timeattack = false; templevelsearch.timeattack = false;
templevelsearch.tutorial = false;
templevelsearch.checklocked = false; templevelsearch.checklocked = false;
M_DrawCupPreview(146, &templevelsearch); M_DrawCupPreview(146, &templevelsearch);

View file

@ -216,12 +216,6 @@ int LUA_PushGlobals(lua_State *L, const char *word)
} else if (fastcmp(word,"bootmap")) { } else if (fastcmp(word,"bootmap")) {
lua_pushstring(L, bootmap); lua_pushstring(L, bootmap);
return 1; return 1;
} else if (fastcmp(word,"tutorialmap")) {
lua_pushstring(L, tutorialmap);
return 1;
} else if (fastcmp(word,"tutorialmode")) {
lua_pushboolean(L, tutorialmode);
return 1;
} else if (fastcmp(word,"podiummap")) { } else if (fastcmp(word,"podiummap")) {
lua_pushstring(L, podiummap); lua_pushstring(L, podiummap);
return 1; return 1;

View file

@ -624,10 +624,22 @@ void M_UpdateConditionSetsPending(void)
} }
} }
static boolean M_NotFreePlay(player_t *player) boolean M_NotFreePlay(player_t *player)
{ {
UINT8 i; UINT8 i;
if (K_CanChangeRules(true) == false)
{
// Rounds with direction are never FREE PLAY.
return true;
}
if (battleprisons)
{
// Prison Break is battle's FREE PLAY.
return false;
}
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)

View file

@ -325,6 +325,8 @@ void M_ClearConditionSet(UINT8 set);
void M_ClearSecrets(void); void M_ClearSecrets(void);
void M_ClearStats(void); void M_ClearStats(void);
boolean M_NotFreePlay(player_t *player);
// Updating conditions and unlockables // Updating conditions and unlockables
boolean M_CheckCondition(condition_t *cn, player_t *player); boolean M_CheckCondition(condition_t *cn, player_t *player);
boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall); boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall);

View file

@ -14,6 +14,9 @@ menuitem_t EXTRAS_Main[] =
{IT_STRING | IT_CALL, NULL, NULL, {IT_STRING | IT_CALL, NULL, NULL,
NULL, {.routine = M_Addons}, 0, 0}, NULL, {.routine = M_Addons}, 0, 0},
{IT_STRING | IT_CALL, "Tutorial", "Help Dr. Eggman and Tails test out their new Ring Racers.",
NULL, {.routine = M_LevelSelectInit}, 0, GT_TUTORIAL},
{IT_STRING | IT_CALL, "Challenges", "View the requirements for some of the secret content you can unlock!", {IT_STRING | IT_CALL, "Challenges", "View the requirements for some of the secret content you can unlock!",
NULL, {.routine = M_Challenges}, 0, 0}, NULL, {.routine = M_Challenges}, 0, 0},
@ -79,6 +82,25 @@ void M_InitExtras(INT32 choice)
} }
} }
// Tutorial
{
levelsearch_t templevelsearch;
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 |
((map == NEXTMAP_INVALID) ? IT_TRANSTEXT : IT_CALL));
}
// Egg TV // Egg TV
if (M_SecretUnlocked(SECRET_EGGTV, true)) if (M_SecretUnlocked(SECRET_EGGTV, true))
{ {

View file

@ -100,6 +100,7 @@ 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

@ -61,7 +61,7 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch)
return false; return false;
// Check for TOL (permits TEST RUN outside of time attack) // Check for TOL (permits TEST RUN outside of time attack)
if ((levelsearch->timeattack || mapheaderinfo[mapnum]->typeoflevel) if ((levelsearch->timeattack || levelsearch->tutorial || mapheaderinfo[mapnum]->typeoflevel)
&& !(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel)) && !(mapheaderinfo[mapnum]->typeoflevel & levelsearch->typeoflevel))
return false; return false;
@ -214,34 +214,42 @@ boolean M_LevelListFromGametype(INT16 gt)
static boolean first = true; static boolean first = true;
UINT8 temp = 0; UINT8 temp = 0;
if (gt != -1 && (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES)) if (gt != -1)
{ {
if (first) if (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES)
{ {
cupgrid.cappages = 0; if (first)
cupgrid.builtgrid = NULL; {
dummy_lostandfound.cachedlevels[0] = NEXTMAP_INVALID; cupgrid.cappages = 0;
cupgrid.builtgrid = NULL;
dummy_lostandfound.cachedlevels[0] = NEXTMAP_INVALID;
first = false; first = false;
}
levellist.newgametype = gt;
levellist.levelsearch.typeoflevel = G_TOLFlag(gt);
if (levellist.levelsearch.timeattack == true && gt == GT_SPECIAL)
{
// Sneak in an extra.
levellist.levelsearch.typeoflevel |= G_TOLFlag(GT_VERSUS);
levellist.guessgt = gt;
}
else
{
levellist.guessgt = MAXGAMETYPES;
}
levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT));
CV_SetValue(&cv_dummyspbattack, 0);
} }
levellist.newgametype = gt; PLAY_CupSelectDef.music = \
PLAY_LevelSelectDef.music = \
levellist.levelsearch.typeoflevel = G_TOLFlag(gt); PLAY_TimeAttackDef.music = \
if (levellist.levelsearch.timeattack == true && gt == GT_SPECIAL) currentMenu->music;
{
// Sneak in an extra.
levellist.levelsearch.typeoflevel |= G_TOLFlag(GT_VERSUS);
levellist.guessgt = gt;
}
else
{
levellist.guessgt = MAXGAMETYPES;
}
levellist.levelsearch.cupmode = (!(gametypes[gt]->rules & GTR_NOCUPSELECT));
CV_SetValue(&cv_dummyspbattack, 0);
} }
// Obviously go to Cup Select in gametypes that have cups. // Obviously go to Cup Select in gametypes that have cups.
@ -427,6 +435,7 @@ 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)
{ {
@ -498,7 +507,7 @@ void M_LevelSelected(INT16 add)
{ {
if (gamestate == GS_MENU) if (gamestate == GS_MENU)
{ {
UINT8 ssplayers = cv_splitplayers.value-1; UINT8 ssplayers = levellist.levelsearch.tutorial ? 0 : cv_splitplayers.value-1;
netgame = false; netgame = false;
multiplayer = true; multiplayer = true;
@ -543,7 +552,11 @@ void M_LevelSelected(INT16 add)
D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false); D_MapChange(levellist.choosemap+1, levellist.newgametype, (cv_kartencore.value == 1), 1, 1, false, false);
if (levellist.netgame == true) if (levellist.levelsearch.tutorial)
{
restoreMenu = currentMenu;
}
else if (levellist.netgame == true)
{ {
restoreMenu = &PLAY_MP_OptSelectDef; restoreMenu = &PLAY_MP_OptSelectDef;
} }

View file

@ -389,6 +389,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 num)
mapheaderinfo[num]->typeoflevel = 0; mapheaderinfo[num]->typeoflevel = 0;
mapheaderinfo[num]->gravity = DEFAULT_GRAVITY; mapheaderinfo[num]->gravity = DEFAULT_GRAVITY;
mapheaderinfo[num]->keywords[0] = '\0'; mapheaderinfo[num]->keywords[0] = '\0';
mapheaderinfo[num]->relevantskin[0] = '\0';
mapheaderinfo[num]->musname[0][0] = 0; mapheaderinfo[num]->musname[0][0] = 0;
mapheaderinfo[num]->musname_size = 0; mapheaderinfo[num]->musname_size = 0;
mapheaderinfo[num]->positionmus[0] = '\0'; mapheaderinfo[num]->positionmus[0] = '\0';
@ -7392,6 +7393,27 @@ static void P_InitCamera(void)
static void P_InitPlayers(void) static void P_InitPlayers(void)
{ {
UINT8 i; UINT8 i;
INT32 skin = -1;
// Are we forcing a character?
if (gametype == GT_TUTORIAL)
{
// Get skin from name.
if (mapheaderinfo[gamemap-1] && mapheaderinfo[gamemap-1]->relevantskin[0])
{
skin = R_SkinAvailable(mapheaderinfo[gamemap-1]->relevantskin);
}
else
{
skin = R_SkinAvailable(DEFAULTSKIN);
}
// Handle invalid case.
if (skin == -1)
{
skin = 0;
}
}
for (i = 0; i < MAXPLAYERS; i++) for (i = 0; i < MAXPLAYERS; i++)
{ {
@ -7400,6 +7422,15 @@ static void P_InitPlayers(void)
players[i].mo = NULL; players[i].mo = NULL;
// If we're forcing a character, do it now.
if (skin != -1)
{
players[i].skin = skin;
players[i].skincolor = skins[skin].prefcolor;
players[i].followerskin = -1;
// followercolor can be left alone for hopefully obvious reasons
}
if (!(gametyperules & GTR_CIRCUIT) && K_PodiumSequence() == false) if (!(gametyperules & GTR_CIRCUIT) && K_PodiumSequence() == false)
{ {
G_DoReborn(i); G_DoReborn(i);

View file

@ -1407,7 +1407,7 @@ boolean R_ViewpointHasChasecam(player_t *player)
} }
} }
if (player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN || tutorialmode) if (player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN)
chasecam = true; // force chasecam on chasecam = true; // force chasecam on
else if (player->spectator) // no spectator chasecam else if (player->spectator) // no spectator chasecam
chasecam = false; // force chasecam off chasecam = false; // force chasecam off

View file

@ -236,6 +236,12 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins)
return true; return true;
} }
if (gametype == GT_TUTORIAL)
{
// Being forced to play as this character by the tutorial
return true;
}
// Determine if this character is supposed to be unlockable or not // Determine if this character is supposed to be unlockable or not
if (useplayerstruct && demo.playback) if (useplayerstruct && demo.playback)
{ {