diff --git a/src/d_main.c b/src/d_main.c index e41674e2d..03df9b60d 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -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(); diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 6e58e20ac..bf9416ce9 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -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); diff --git a/src/deh_soc.c b/src/deh_soc.c index c96bbbaac..190001f80 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -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); diff --git a/src/deh_tables.c b/src/deh_tables.c index 7d4f8d990..cfe834370 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -5854,6 +5854,7 @@ const char *const GAMETYPERULE_LIST[] = { "NOMP", "NOCUPSELECT", + "NOPOSITION", NULL }; diff --git a/src/doomstat.h b/src/doomstat.h index d72ef266b..01f584dd4 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -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. @@ -546,10 +545,10 @@ enum GameTypeRules // Bonus gametype rules GTR_PRISONS = 1<<10, // Can enter Prison Break mode - GTR_CATCHER = 1<<11, // UFO Catcher (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_BOSS = 1<<14, // Boss intro and spawning + GTR_CATCHER = 1<<11, // UFO Catcher (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_BOSS = 1<<14, // Boss intro and spawning // General purpose rules GTR_POINTLIMIT = 1<<15, // Reaching point limit ends the round @@ -557,12 +556,13 @@ enum GameTypeRules GTR_OVERTIME = 1<<17, // Allow overtime behavior GTR_ENCORE = 1<<18, // Alternate Encore mirroring, scripting, and texture remapping - GTR_TEAMS = 1<<19, // Teams are forced on - GTR_NOTEAMS = 1<<20, // Teams are forced off - GTR_TEAMSTARTS = 1<<21, // Use team-based start positions + GTR_TEAMS = 1<<19, // Teams are forced on + GTR_NOTEAMS = 1<<20, // Teams are forced off + GTR_TEAMSTARTS = 1<<21, // Use team-based start positions - GTR_NOMP = 1<<22, // No multiplayer - GTR_NOCUPSELECT = 1<<23, // Your maps are not selected via cup. + 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 }; @@ -577,10 +577,11 @@ enum GameTypeRules enum TypeOfLevel { // Gametypes - TOL_RACE = 0x0001, ///< Race - TOL_BATTLE = 0x0002, ///< Battle - TOL_BOSS = 0x0004, ///< Boss (variant of battle, but forbidden) - TOL_SPECIAL = 0x0008, ///< Special Stage (variant of race, but forbidden) + TOL_RACE = 0x0001, ///< Race + 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 diff --git a/src/f_finale.c b/src/f_finale.c index a39d020c0..160ddc27b 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -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)++) diff --git a/src/g_game.c b/src/g_game.c index 48168a3e7..72d4663f6 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -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) @@ -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) { - UINT8 roundtype = GDGT_CUSTOM; + if (gametype != GT_TUTORIAL) + { + UINT8 roundtype = GDGT_CUSTOM; - if (gametype == GT_RACE) - roundtype = GDGT_RACE; - else if (gametype == GT_BATTLE) - roundtype = (battleprisons ? GDGT_PRISONS : GDGT_BATTLE); - else if (gametype == GT_SPECIAL || gametype == GT_VERSUS) - roundtype = GDGT_SPECIAL; + if (gametype == GT_RACE) + roundtype = GDGT_RACE; + else if (gametype == GT_BATTLE) + roundtype = (battleprisons ? GDGT_PRISONS : GDGT_BATTLE); + else if (gametype == GT_SPECIAL || gametype == GT_VERSUS) + roundtype = GDGT_SPECIAL; - gamedata->roundsplayed[roundtype]++; + gamedata->roundsplayed[roundtype]++; + } gamedata->pendingkeyrounds++; // Done before forced addition of PF_NOCONTEST to make UCRP_NOCONTEST harder to achieve diff --git a/src/k_grandprix.c b/src/k_grandprix.c index 213e108b5..73cc0d27b 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.c @@ -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! diff --git a/src/k_hud.c b/src/k_hud.c index ddce61c2b..cd8f77af6 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -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 ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) - fy -= 4; + 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,8 +2646,17 @@ static void K_drawKartAccessibilityIcons(INT32 fx) if (r_splitscreen < 2) // adjust to speedometer height { - if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) - fy -= 4; + if (gametypeinfoshown) + { + fy -= 11; + + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) + fy -= 4; + } + else + { + fy += 9; + } } 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; 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 ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) - battleoffset = -4; + if (gametypeinfoshown) + { + fy -= 11; - 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]); + if ((gametyperules & (GTR_BUMPERS|GTR_CIRCUIT)) == GTR_BUMPERS) + fy -= 4; + } + else + { + 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 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; } - fy -= 16; + + 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(); - } + 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) @@ -5102,31 +5138,36 @@ void K_drawKartHUD(void) { if (gametyperules & GTR_CIRCUIT) { - K_drawKartLaps(); + 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,8 +5221,7 @@ 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(); + K_drawKartFreePlay(); if (r_splitscreen == 0 && (stplyr->pflags & PF_WRONGWAY) && ((leveltime / 8) & 1)) { diff --git a/src/k_kart.c b/src/k_kart.c index 065c4e1b2..48f1afaa8 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -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; } diff --git a/src/k_menu.h b/src/k_menu.h index e1e4af6ce..fa4b1e8ed 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -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, diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 326d143ca..3fce2fafa 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -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); diff --git a/src/lua_script.c b/src/lua_script.c index 6e34c9fbf..3a12c177b 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -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; diff --git a/src/m_cond.c b/src/m_cond.c index 5c7016c7a..45db53caf 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -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) diff --git a/src/m_cond.h b/src/m_cond.h index f65bba15b..c7712d5f7 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -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); diff --git a/src/menus/extras-1.c b/src/menus/extras-1.c index 771f70036..21cd2337e 100644 --- a/src/menus/extras-1.c +++ b/src/menus/extras-1.c @@ -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)) { diff --git a/src/menus/play-online-host.c b/src/menus/play-online-host.c index 95ce0660e..9610154ef 100644 --- a/src/menus/play-online-host.c +++ b/src/menus/play-online-host.c @@ -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; diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 234d6d539..776789dc1 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -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,34 +214,42 @@ 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) + if (first || gt != levellist.newgametype || levellist.guessgt != MAXGAMETYPES) { - cupgrid.cappages = 0; - cupgrid.builtgrid = NULL; - dummy_lostandfound.cachedlevels[0] = NEXTMAP_INVALID; + if (first) + { + 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; - - 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); + PLAY_CupSelectDef.music = \ + PLAY_LevelSelectDef.music = \ + PLAY_TimeAttackDef.music = \ + currentMenu->music; } // 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! 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; } diff --git a/src/p_setup.c b/src/p_setup.c index 9082c2796..0ca14594b 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -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); diff --git a/src/r_main.c b/src/r_main.c index 5731f7695..c8cfe4fb5 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -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 diff --git a/src/r_skins.c b/src/r_skins.c index 212256c21..9a9df4f88 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -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) {