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)
V_SetPaletteLump("PLAYPAL");
// The title screen is obviously not a tutorial! (Unless I'm mistaken)
tutorialmode = false;
cursongcredit.def = NULL;
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);
Z_Free(realmapname);

View file

@ -1122,6 +1122,11 @@ void readlevelheader(MYFILE *f, char * name)
deh_strlcpy(mapheaderinfo[num]->zonttl, word2,
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"))
{
deh_strlcpy(mapheaderinfo[num]->scriptname, word2,
@ -3211,11 +3216,6 @@ void readmaincfg(MYFILE *f, boolean mainfile)
bootmap = Z_StrDup(word2);
//titlechanged = true;
}
else if (fastcmp(word, "TUTORIALMAP"))
{
Z_Free(tutorialmap);
tutorialmap = Z_StrDup(word2);
}
else if (fastcmp(word, "PODIUMMAP"))
{
Z_Free(podiummap);

View file

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

View file

@ -222,9 +222,6 @@ extern char * titlemap;
extern boolean hidetitlepics;
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 boolean looptitle;
@ -430,6 +427,7 @@ struct mapheader_t
UINT32 typeoflevel; ///< Combination of typeoflevel flags.
UINT8 numlaps; ///< Number of laps in circuit mode, unless overridden.
fixed_t gravity; ///< Map-wide gravity.
char relevantskin[SKINNAMESIZE+1]; ///< Skin to use for tutorial (if not provided, uses Eggman.)
// Music information
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_SPECIAL,
GT_VERSUS,
GT_TUTORIAL,
GT_FIRSTFREESLOT,
GT_LASTFREESLOT = 127, // Previously (GT_FIRSTFREESLOT + NUMGAMETYPEFREESLOTS - 1) - it would be necessary to rewrite VOTEMODIFIER_ENCORE to go higher than this.
@ -563,6 +562,7 @@ enum GameTypeRules
GTR_NOMP = 1<<22, // No multiplayer
GTR_NOCUPSELECT = 1<<23, // Your maps are not selected via cup.
GTR_NOPOSITION = 1<<24, // No POSITION
// free: to and including 1<<31
};
@ -581,6 +581,7 @@ enum TypeOfLevel
TOL_BATTLE = 0x0002, ///< Battle
TOL_BOSS = 0x0004, ///< Boss (variant of battle, but forbidden)
TOL_SPECIAL = 0x0008, ///< Special Stage (variant of race, but forbidden)
TOL_TUTORIAL = 0x0010, ///< Tutorial (variant of race, but forbidden)
// Modifiers
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;
boolean suffixed = true;
if (!tag || !tag[0] || !tutorialmode)
if (!tag || !tag[0] || gametype == GT_TUTORIAL)
return false;
/*
@ -2859,7 +2859,7 @@ static boolean F_GetTextPromptTutorialTag(char *tag, INT32 length)
void F_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum)
{
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;
char suffixedtag[33];
@ -2871,7 +2871,7 @@ void F_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum
strncpy(suffixedtag, tag, 33);
suffixedtag[32] = 0;
if (tutorialmode)
if (gametype == GT_TUTORIAL)
suffixed = F_GetTextPromptTutorialTag(suffixedtag, 33);
for (*promptnum = 0 + tutorialpromptnum; *promptnum < MAX_PROMPTS; (*promptnum)++)

View file

@ -167,9 +167,6 @@ char * titlemap = NULL;
boolean hidetitlepics = false;
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
boolean looptitle = true;
@ -3398,6 +3395,17 @@ static gametype_t defaultgametypes[] =
0,
0,
},
// GT_TUTORIAL
{
"Tutorial",
"GT_TUTORIAL",
GTR_NOMP|GTR_NOCUPSELECT|GTR_NOPOSITION,
TOL_TUTORIAL,
int_none,
0,
0,
},
};
gametype_t *gametypes[MAXGAMETYPES+1] =
@ -3406,6 +3414,7 @@ gametype_t *gametypes[MAXGAMETYPES+1] =
&defaultgametypes[GT_BATTLE],
&defaultgametypes[GT_SPECIAL],
&defaultgametypes[GT_VERSUS],
&defaultgametypes[GT_TUTORIAL],
};
//
@ -3541,6 +3550,7 @@ tolinfo_t TYPEOFLEVEL[NUMTOLNAMES] = {
{"BATTLE",TOL_BATTLE},
{"BOSS",TOL_BOSS},
{"SPECIAL",TOL_SPECIAL},
{"TUTORIAL",TOL_TUTORIAL},
{"TV",TOL_TV},
{NULL, 0}
};
@ -3667,6 +3677,7 @@ INT16 G_GetFirstMapOfGametype(UINT8 pgametype)
templevelsearch.typeoflevel = G_TOLFlag(pgametype);
templevelsearch.cupmode = (!(gametypes[pgametype]->rules & GTR_NOCUPSELECT));
templevelsearch.timeattack = false;
templevelsearch.tutorial = false;
templevelsearch.checklocked = true;
if (templevelsearch.cupmode)
@ -4245,6 +4256,8 @@ static void G_DoCompleted(void)
S_StopSounds();
if (legitimateexit && !demo.playback && !mapreset) // (yes you're allowed to unlock stuff this way when the game is modified)
{
if (gametype != GT_TUTORIAL)
{
UINT8 roundtype = GDGT_CUSTOM;
@ -4256,6 +4269,7 @@ static void G_DoCompleted(void)
roundtype = GDGT_SPECIAL;
gamedata->roundsplayed[roundtype]++;
}
gamedata->pendingkeyrounds++;
// 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;
}
if (gametype == GT_TUTORIAL)
{
// Tutorials are locked down.
return false;
}
if (!allowdemos && demo.playback)
{
// 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]);
}
static void K_drawRingCounter(void)
static void K_drawRingCounter(boolean gametypeinfoshown)
{
const boolean uselives = G_GametypeUsesLives();
SINT8 ringanim_realframe = stplyr->karthud[khud_ringframe];
@ -2547,43 +2547,57 @@ static void K_drawRingCounter(void)
fr = fx;
if (gametypeinfoshown)
{
fy -= 10;
}
// Rings
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)
fr += 15;
}
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
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+15, fy-10, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[1]], ringmap);
V_DrawMappedPatch(fr+11, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[0]], ringmap);
V_DrawMappedPatch(fr+15, fy, V_HUDTRANS|V_SLIDEIN|splitflags, fontv[PINGNUM_FONT].font[rn[1]], ringmap);
// SPB ring lock
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
if (uselives)
{
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)
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
{
fy = LAPS_Y-11;
fy = LAPS_Y;
if (gametypeinfoshown)
{
fy -= 11;
if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS)
fy -= 4;
}
else
{
fy += 9;
}
// Rings
if (!uselives)
@ -2622,9 +2636,9 @@ static void K_drawRingCounter(void)
#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 step = 1; -- if there's ever more than one accessibility icon
@ -2632,10 +2646,19 @@ static void K_drawKartAccessibilityIcons(INT32 fx)
if (r_splitscreen < 2) // adjust to speedometer height
{
if (gametypeinfoshown)
{
fy -= 11;
if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS)
fy -= 4;
}
else
{
fy += 9;
}
}
else
{
fx = LAPS_X+43;
fy = LAPS_Y;
@ -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;
UINT8 labeln = 0;
UINT8 numbers[3];
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!
{
@ -2722,19 +2745,28 @@ static void K_drawKartSpeedometer(void)
numbers[1] = ((convSpeed / 10) % 10);
numbers[2] = (convSpeed % 10);
if (gametypeinfoshown)
{
fy -= 11;
if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS)
battleoffset = -4;
fy -= 4;
}
else
{
fy += 9;
}
V_DrawScaledPatch(LAPS_X, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometersticker);
V_DrawScaledPatch(LAPS_X+7, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_facenum[numbers[0]]);
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]]);
V_DrawScaledPatch(LAPS_X+29, LAPS_Y-25 + battleoffset, V_HUDTRANS|V_SLIDEIN|splitflags, kp_speedometerlabel[labeln]);
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(56);
K_drawKartAccessibilityIcons(gametypeinfoshown, 56);
}
static void K_drawBlueSphereMeter(void)
static void K_drawBlueSphereMeter(boolean gametypeinfoshown)
{
const UINT8 maxBars = 4;
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.
{
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);
}
else
@ -2771,7 +2813,12 @@ static void K_drawBlueSphereMeter(void)
flipflag = V_FLIP; // make the string right aligned and other shit
xstep = -xstep;
}
if (gametypeinfoshown)
{
fy -= 16;
}
V_DrawScaledPatch(fx, fy, splitflags|flipflag, kp_splitspheresticker);
}
@ -4294,21 +4341,7 @@ static void K_drawBattleFullscreen(void)
}
// FREE PLAY?
{
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)
@ -4700,12 +4733,13 @@ static void K_drawTrickCool(void)
void K_drawKartFreePlay(void)
{
// Doesn't support splitscreens higher than 2 for real estate reasons.
if (!LUA_HudEnabled(hud_freeplay))
return;
if (modeattacking || grandprixinfo.gp || bossinfo.valid || stplyr->spectator)
if (stplyr->spectator == true)
return;
if (M_NotFreePlay(stplyr) == true)
return;
if (lt_exitticker < TICRATE/2)
@ -5081,6 +5115,8 @@ void K_drawKartHUD(void)
}
else
{
boolean gametypeinfoshown = false;
if (LUA_HudEnabled(hud_position))
{
if (bossinfo.valid)
@ -5101,32 +5137,37 @@ void K_drawKartHUD(void)
if (LUA_HudEnabled(hud_gametypeinfo))
{
if (gametyperules & GTR_CIRCUIT)
{
if (numlaps > 1)
{
K_drawKartLaps();
gametypeinfoshown = true;
}
}
else if (gametyperules & GTR_BUMPERS)
{
K_drawKartBumpersOrKarma();
gametypeinfoshown = true;
}
}
// Draw the speedometer and/or accessibility icons
if (cv_kartspeedometer.value && !r_splitscreen && (LUA_HudEnabled(hud_speedometer)))
{
K_drawKartSpeedometer();
K_drawKartSpeedometer(gametypeinfoshown);
}
else
{
K_drawKartAccessibilityIcons(0);
K_drawKartAccessibilityIcons(gametypeinfoshown, 0);
}
if (gametyperules & GTR_SPHERES)
{
K_drawBlueSphereMeter();
K_drawBlueSphereMeter(gametypeinfoshown);
}
else
{
K_drawRingCounter();
K_drawRingCounter(gametypeinfoshown);
}
if (modeattacking && !bossinfo.valid)
@ -5180,7 +5221,6 @@ void K_drawKartHUD(void)
V_DrawScaledPatch(BASEVIDWIDTH/2 - (SHORT(kp_yougotem->width)/2), 32, V_HUDTRANS, kp_yougotem);
// Draw FREE PLAY.
if (islonesome)
K_drawKartFreePlay();
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);
@ -11695,6 +11700,11 @@ boolean K_Cooperative(void)
return true;
}
if (specialstageinfo.valid)
{
return true;
}
return false;
}

View file

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

View file

@ -2411,6 +2411,12 @@ void M_DrawLevelSelect(void)
INT16 y = 80 - (12 * levellist.y);
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)
{
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.cupmode = true;
templevelsearch.timeattack = false;
templevelsearch.tutorial = false;
templevelsearch.checklocked = false;
M_DrawCupPreview(146, &templevelsearch);

View file

@ -216,12 +216,6 @@ int LUA_PushGlobals(lua_State *L, const char *word)
} else if (fastcmp(word,"bootmap")) {
lua_pushstring(L, bootmap);
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")) {
lua_pushstring(L, podiummap);
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;
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++)
{
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_ClearStats(void);
boolean M_NotFreePlay(player_t *player);
// Updating conditions and unlockables
boolean M_CheckCondition(condition_t *cn, player_t *player);
boolean M_UpdateUnlockablesAndExtraEmblems(boolean loud, boolean doall);

View file

@ -14,6 +14,9 @@ menuitem_t EXTRAS_Main[] =
{IT_STRING | IT_CALL, NULL, NULL,
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!",
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
if (M_SecretUnlocked(SECRET_EGGTV, true))
{

View file

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

View file

@ -61,7 +61,7 @@ boolean M_CanShowLevelInList(INT16 mapnum, levelsearch_t *levelsearch)
return false;
// 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))
return false;
@ -214,7 +214,9 @@ boolean M_LevelListFromGametype(INT16 gt)
static boolean first = true;
UINT8 temp = 0;
if (gt != -1 && (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES))
if (gt != -1)
{
if (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES)
{
if (first)
{
@ -244,6 +246,12 @@ boolean M_LevelListFromGametype(INT16 gt)
CV_SetValue(&cv_dummyspbattack, 0);
}
PLAY_CupSelectDef.music = \
PLAY_LevelSelectDef.music = \
PLAY_TimeAttackDef.music = \
currentMenu->music;
}
// Obviously go to Cup Select in gametypes that have cups.
// Use a really long level select in gametypes that don't use 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!
levellist.netgame = false;
levellist.levelsearch.checklocked = true;
levellist.levelsearch.tutorial = (gt == GT_TUTORIAL);
switch (currentMenu->menuitems[itemOn].mvar1)
{
@ -498,7 +507,7 @@ void M_LevelSelected(INT16 add)
{
if (gamestate == GS_MENU)
{
UINT8 ssplayers = cv_splitplayers.value-1;
UINT8 ssplayers = levellist.levelsearch.tutorial ? 0 : cv_splitplayers.value-1;
netgame = false;
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);
if (levellist.netgame == true)
if (levellist.levelsearch.tutorial)
{
restoreMenu = currentMenu;
}
else if (levellist.netgame == true)
{
restoreMenu = &PLAY_MP_OptSelectDef;
}

View file

@ -389,6 +389,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 num)
mapheaderinfo[num]->typeoflevel = 0;
mapheaderinfo[num]->gravity = DEFAULT_GRAVITY;
mapheaderinfo[num]->keywords[0] = '\0';
mapheaderinfo[num]->relevantskin[0] = '\0';
mapheaderinfo[num]->musname[0][0] = 0;
mapheaderinfo[num]->musname_size = 0;
mapheaderinfo[num]->positionmus[0] = '\0';
@ -7392,6 +7393,27 @@ static void P_InitCamera(void)
static void P_InitPlayers(void)
{
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++)
{
@ -7400,6 +7422,15 @@ static void P_InitPlayers(void)
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)
{
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
else if (player->spectator) // no spectator chasecam
chasecam = false; // force chasecam off

View file

@ -236,6 +236,12 @@ boolean R_SkinUsable(INT32 playernum, INT32 skinnum, boolean demoskins)
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
if (useplayerstruct && demo.playback)
{