diff --git a/src/d_main.c b/src/d_main.c index 605c3a677761620c9193bd6649faf686bbd99357..22369d1a4541faf7aabef1197463c9fbedc26b22 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -71,6 +71,7 @@ int snprintf(char *str, size_t n, const char *fmt, ...); #include "fastcmp.h" #include "keys.h" #include "filesrch.h" // refreshdirmenu, mainwadstally +#include "g_input.h" // tutorial mode control scheming #ifdef CMAKECONFIG #include "config.h" @@ -733,6 +734,19 @@ void D_StartTitle(void) // Reset the palette if (rendermode != render_none) V_SetPaletteLump("PLAYPAL"); + + // The title screen is obviously not a tutorial! (Unless I'm mistaken) + if (tutorialmode && tutorialgcs) + { + G_CopyControls(gamecontrol, gamecontroldefault[gcs_custom], gcl_tutorial_full, num_gcl_tutorial_full); // using gcs_custom as temp storage + CV_SetValue(&cv_usemouse, tutorialusemouse); + CV_SetValue(&cv_alwaysfreelook, tutorialfreelook); + CV_SetValue(&cv_mousemove, tutorialmousemove); + CV_SetValue(&cv_analog, tutorialanalog); + M_StartMessage("Do you want to \x82save the recommended \x82movement controls?\x80\n\nPress 'Y' or 'Enter' to confirm\nPress 'N' or any key to keep \nyour current controls", + M_TutorialSaveControlResponse, MM_YESNO); + } + tutorialmode = false; } // diff --git a/src/d_netcmd.c b/src/d_netcmd.c index f38e097bd42d683b4682fd1e89c571d443c8250e..bb33eb133ed5df2f2cb16481253e78824f3f8a82 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -712,6 +712,7 @@ void D_RegisterClientCommands(void) CV_RegisterVar(&cv_crosshair2); CV_RegisterVar(&cv_alwaysfreelook); CV_RegisterVar(&cv_alwaysfreelook2); + CV_RegisterVar(&cv_tutorialprompt); // g_input.c CV_RegisterVar(&cv_sideaxis); @@ -1808,6 +1809,16 @@ static void Command_Map_f(void) else fromlevelselect = ((netgame || multiplayer) && ((gametype == newgametype) && (newgametype == GT_COOP))); + if (tutorialmode && tutorialgcs) + { + G_CopyControls(gamecontrol, gamecontroldefault[gcs_custom], gcl_tutorial_full, num_gcl_tutorial_full); // using gcs_custom as temp storage + CV_SetValue(&cv_usemouse, tutorialusemouse); + CV_SetValue(&cv_alwaysfreelook, tutorialfreelook); + CV_SetValue(&cv_mousemove, tutorialmousemove); + CV_SetValue(&cv_analog, tutorialanalog); + } + tutorialmode = false; // warping takes us out of tutorial mode + D_MapChange(newmapnum, newgametype, false, newresetplayers, 0, false, fromlevelselect); } diff --git a/src/dehacked.c b/src/dehacked.c index aa5d84954591ad63e9620e7327d2e7c237bb566e..ff7b603d9b151943937b7388a09c3862b327e01f 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -2892,6 +2892,19 @@ static void readmaincfg(MYFILE *f) startchar = (INT16)value; char_on = -1; } + else if (fastcmp(word, "TUTORIALMAP")) + { + // Support using the actual map name, + // i.e., Level AB, Level FZ, etc. + + // Convert to map number + if (word2[0] >= 'A' && word2[0] <= 'Z') + value = M_MapNumber(word2[0], word2[1]); + else + value = get_number(word2); + + tutorialmap = (INT16)value; + } else deh_warning("Maincfg: unknown word '%s'", word); } diff --git a/src/doomstat.h b/src/doomstat.h index c4fda93ae553c1ae161a963beb6aa1e78af28f18..7660547685426bd01cfb84ef5aa5f28b49b6fad2 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -132,6 +132,10 @@ extern INT16 bootmap; //bootmap for loading a map on startup extern INT16 tutorialmap; // map to load for tutorial extern boolean tutorialmode; // are we in a tutorial right now? extern INT32 tutorialgcs; // which control scheme is loaded? +extern INT32 tutorialusemouse; // store cv_usemouse user value +extern INT32 tutorialfreelook; // store cv_alwaysfreelook user value +extern INT32 tutorialmousemove; // store cv_mousemove user value +extern INT32 tutorialanalog; // store cv_analog user value extern boolean looptitle; diff --git a/src/g_game.c b/src/g_game.c index 28a8a8d0a9853d499140ea5abd9970536fe81191..4fb56abaf95e1a5e12d9eb748120e10e478dc04f 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -130,6 +130,10 @@ INT16 bootmap; //bootmap for loading a map on startup INT16 tutorialmap = 0; // map to load for tutorial boolean tutorialmode = false; // are we in a tutorial right now? INT32 tutorialgcs = gcs_custom; // which control scheme is loaded? +INT32 tutorialusemouse = 0; // store cv_usemouse user value +INT32 tutorialfreelook = 0; // store cv_alwaysfreelook user value +INT32 tutorialmousemove = 0; // store cv_mousemove user value +INT32 tutorialanalog = 0; // store cv_analog user value boolean looptitle = false; diff --git a/src/g_game.h b/src/g_game.h index d6b41830e8c674e52bf0cb3374e526709df3e93b..9e8580d139c8e94b0c31183746723b62bbf32f65 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -55,6 +55,7 @@ extern tic_t timeinmap; // Ticker for time spent in level (used for levelcard di extern INT16 rw_maximums[NUM_WEAPONS]; // used in game menu +extern consvar_t cv_tutorialprompt; extern consvar_t cv_crosshair, cv_crosshair2; extern consvar_t cv_invertmouse, cv_alwaysfreelook, cv_mousemove; extern consvar_t cv_invertmouse2, cv_alwaysfreelook2, cv_mousemove2; diff --git a/src/m_menu.c b/src/m_menu.c index 5e2cc48d51fe8ac688639284f2e14c95463d6ee6..e973cea551ab692d44f76a62ee44f9d9b4460c61 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -273,6 +273,7 @@ menu_t SP_MainDef, OP_MainDef; menu_t MISC_ScrambleTeamDef, MISC_ChangeTeamDef; // Single Player +static void M_StartTutorial(INT32 choice); static void M_LoadGame(INT32 choice); static void M_TimeAttackLevelSelect(INT32 choice); static void M_TimeAttack(INT32 choice); @@ -440,6 +441,9 @@ static CV_PossibleValue_t serversort_cons_t[] = { }; consvar_t cv_serversort = {"serversort", "Ping", CV_HIDEN | CV_CALL, serversort_cons_t, M_SortServerList, 0, NULL, NULL, 0, 0, NULL}; +// first time memory +consvar_t cv_tutorialprompt = {"tutorialprompt", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; + // autorecord demos for time attack static consvar_t cv_autorecord = {"autorecord", "Yes", 0, CV_YesNo, NULL, 0, NULL, NULL, 0, 0, NULL}; @@ -731,6 +735,7 @@ static menuitem_t SR_EmblemHintMenu[] = // Single Player Main static menuitem_t SP_MainMenu[] = { + {IT_CALL | IT_STRING, NULL, "Tutorial", M_StartTutorial, 84}, {IT_CALL | IT_STRING, NULL, "Start Game", M_LoadGame, 92}, {IT_SECRET, NULL, "Record Attack", M_TimeAttack, 100}, {IT_SECRET, NULL, "NiGHTS Mode", M_NightsAttack, 108}, @@ -739,6 +744,7 @@ static menuitem_t SP_MainMenu[] = enum { + sptutorial, sploadgame, sprecordattack, spnightsmode, @@ -1554,7 +1560,18 @@ menu_t SR_EmblemHintDef = }; // Single Player -menu_t SP_MainDef = CENTERMENUSTYLE(NULL, SP_MainMenu, &MainDef, 72); +menu_t SP_MainDef = //CENTERMENUSTYLE(NULL, SP_MainMenu, &MainDef, 72); +{ + NULL, + sizeof(SP_MainMenu)/sizeof(menuitem_t), + &MainDef, + SP_MainMenu, + M_DrawCenteredMenu, + BASEVIDWIDTH/2, 72, + 1, // start at "Start Game" on first entry + NULL +}; + menu_t SP_LoadDef = { "M_PICKG", @@ -6093,6 +6110,8 @@ static void M_CustomLevelSelect(INT32 choice) static void M_SinglePlayerMenu(INT32 choice) { (void)choice; + SP_MainMenu[sptutorial].status = + tutorialmap ? IT_CALL|IT_STRING : IT_NOTHING|IT_DISABLED; SP_MainMenu[sprecordattack].status = (M_SecretUnlocked(SECRET_RECORDATTACK)) ? IT_CALL|IT_STRING : IT_SECRET; SP_MainMenu[spnightsmode].status = @@ -6118,6 +6137,80 @@ static void M_LoadGameLevelSelect(INT32 choice) M_SetupNextMenu(&SP_LevelSelectDef); } +void M_TutorialSaveControlResponse(INT32 ch) +{ + if (ch == 'y' || ch == KEY_ENTER) + { + G_CopyControls(gamecontrol, gamecontroldefault[tutorialgcs], gcl_tutorial_full, num_gcl_tutorial_full); + CV_Set(&cv_usemouse, cv_usemouse.defaultvalue); + CV_Set(&cv_alwaysfreelook, cv_alwaysfreelook.defaultvalue); + CV_Set(&cv_mousemove, cv_mousemove.defaultvalue); + CV_Set(&cv_analog, cv_analog.defaultvalue); + S_StartSound(NULL, sfx_itemup); + } + else + S_StartSound(NULL, sfx_menu1); +} + +static void M_TutorialControlResponse(INT32 ch) +{ + if (ch != KEY_ESCAPE) + { + G_CopyControls(gamecontroldefault[gcs_custom], gamecontrol, NULL, 0); // using gcs_custom as temp storage for old controls + if (ch == 'y' || ch == KEY_ENTER) + { + tutorialgcs = gcs_fps; + tutorialusemouse = cv_usemouse.value; + tutorialfreelook = cv_alwaysfreelook.value; + tutorialmousemove = cv_mousemove.value; + tutorialanalog = cv_analog.value; + + G_CopyControls(gamecontrol, gamecontroldefault[tutorialgcs], gcl_tutorial_full, num_gcl_tutorial_full); + CV_Set(&cv_usemouse, cv_usemouse.defaultvalue); + CV_Set(&cv_alwaysfreelook, cv_alwaysfreelook.defaultvalue); + CV_Set(&cv_mousemove, cv_mousemove.defaultvalue); + CV_Set(&cv_analog, cv_analog.defaultvalue); + + //S_StartSound(NULL, sfx_itemup); + } + else + { + tutorialgcs = gcs_custom; + S_StartSound(NULL, sfx_menu1); + } + M_StartTutorial(INT32_MAX); + } + else + S_StartSound(NULL, sfx_menu1); + + MessageDef.prevMenu = &SP_MainDef; // if FirstPrompt -> ControlsPrompt -> ESC, we would go to the main menu unless we force this +} + +// Starts up the tutorial immediately (tbh I wasn't sure where else to put this) +static void M_StartTutorial(INT32 choice) +{ + if (!tutorialmap) + return; // no map to go to, don't bother + + if (choice != INT32_MAX && G_GetControlScheme(gamecontrol, gcl_tutorial_check, num_gcl_tutorial_check) != gcs_fps) + { + M_StartMessage("Do you want to try the \202recommended \202movement controls\x80?\n\nWe will set them just for this tutorial.\n\nPress 'Y' or 'Enter' to confirm\nPress 'N' or any key to keep \nyour current controls.\n",M_TutorialControlResponse,MM_YESNO); + return; + } + else if (choice != INT32_MAX) + tutorialgcs = gcs_custom; + + CV_SetValue(&cv_tutorialprompt, 0); // first-time prompt + + tutorialmode = true; // turn on tutorial mode + + emeralds = 0; + M_ClearMenus(true); + gamecomplete = false; + cursaveslot = 0; + G_DeferedInitNew(false, G_BuildMapName(tutorialmap), 0, false, false); +} + // ============== // LOAD GAME MENU // ============== @@ -6725,6 +6818,26 @@ static void M_HandleLoadSave(INT32 choice) } } +static void M_FirstTimeResponse(INT32 ch) +{ + S_StartSound(NULL, sfx_menu1); + + if (ch == KEY_ESCAPE) + return; + + if (ch != 'y' && ch != KEY_ENTER) + { + CV_SetValue(&cv_tutorialprompt, 0); + M_ReadSaveStrings(); + MessageDef.prevMenu = &SP_LoadDef; // calls M_SetupNextMenu + } + else + { + M_StartTutorial(0); + MessageDef.prevMenu = &MessageDef; // otherwise, the controls prompt won't fire + } +} + // // Selected from SRB2 menu // @@ -6732,6 +6845,13 @@ static void M_LoadGame(INT32 choice) { (void)choice; + if (tutorialmap && cv_tutorialprompt.value) + { + M_StartMessage("Do you want to \x82play a brief Tutorial\x80?\n\nWe highly recommend this because \nthe controls are slightly different \nfrom other games.\n\nPress 'Y' or 'Enter' to go\nPress 'N' or any key to skip\n", + M_FirstTimeResponse, MM_YESNO); + return; + } + M_ReadSaveStrings(); M_SetupNextMenu(&SP_LoadDef); } @@ -9198,9 +9318,17 @@ static void M_DrawControl(void) // draw title (or big pic) M_DrawMenuTitle(); - M_CentreText(30, - (setupcontrols_secondaryplayer ? "SET CONTROLS FOR SECONDARY PLAYER" : - "PRESS ENTER TO CHANGE, BACKSPACE TO CLEAR")); + if (tutorialmode && tutorialgcs) + { + if ((gametic / TICRATE) % 2) + M_CentreText(30, "\202EXIT THE TUTORIAL TO CHANGE THE CONTROLS"); + else + M_CentreText(30, "EXIT THE TUTORIAL TO CHANGE THE CONTROLS"); + } + else + M_CentreText(30, + (setupcontrols_secondaryplayer ? "SET CONTROLS FOR SECONDARY PLAYER" : + "PRESS ENTER TO CHANGE, BACKSPACE TO CLEAR")); if (i) V_DrawString(currentMenu->x - 16, y-(skullAnimCounter/5), V_YELLOWMAP, "\x1A"); // up arrow @@ -9335,6 +9463,9 @@ static void M_ChangeControl(INT32 choice) { static char tmp[55]; + if (tutorialmode && tutorialgcs) // don't allow control changes if temp control override is active + return; + controltochange = currentMenu->menuitems[choice].alphaKey; sprintf(tmp, M_GetText("Hit the new key for\n%s\nESC for Cancel"), currentMenu->menuitems[choice].text); diff --git a/src/m_menu.h b/src/m_menu.h index 9df56e897690f2044951fe85d16e96f4ee36f202..ad32de1b155120b2d82cacb74a17c28b7ee51207 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -240,6 +240,8 @@ extern INT16 char_on, startchar; #define BwehHehHe() S_StartSound(NULL, sfx_bewar1+M_RandomKey(4)) // Bweh heh he +void M_TutorialSaveControlResponse(INT32 ch); + void M_ForceSaveSlotSelected(INT32 sslot); void M_CheatActivationResponder(INT32 ch); diff --git a/src/m_misc.c b/src/m_misc.c index 7bc2d75cb3ba6cfb04668d6c4ac9f6726eef1fa6..92c2aeea9dd3458d66edc0f8495119542859998c 100644 --- a/src/m_misc.c +++ b/src/m_misc.c @@ -539,7 +539,21 @@ void M_SaveConfig(const char *filename) // FIXME: save key aliases if ever implemented.. - CV_SaveVariables(f); + if (tutorialmode && tutorialgcs) + { + CV_SetValue(&cv_usemouse, tutorialusemouse); + CV_SetValue(&cv_alwaysfreelook, tutorialfreelook); + CV_SetValue(&cv_mousemove, tutorialmousemove); + CV_SetValue(&cv_analog, tutorialanalog); + CV_SaveVariables(f); + CV_Set(&cv_usemouse, cv_usemouse.defaultvalue); + CV_Set(&cv_alwaysfreelook, cv_alwaysfreelook.defaultvalue); + CV_Set(&cv_mousemove, cv_mousemove.defaultvalue); + CV_Set(&cv_analog, cv_analog.defaultvalue); + } + else + CV_SaveVariables(f); + if (!dedicated) { if (tutorialmode && tutorialgcs) diff --git a/src/p_user.c b/src/p_user.c index 57ee9169769a782bfb751ca962284d0efc8cc32a..f4bfd4c610282aad30be8c7ca5e4f2bf7b86ec8d 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -8785,7 +8785,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall cameranoclip = (player->powers[pw_carry] == CR_NIGHTSMODE || player->pflags & PF_NOCLIP) || (mo->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT)); // Noclipping player camera noclips too!! - if (!(player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD)) + if (!(player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD || tutorialmode)) { if (player->spectator) // force cam off for spectators return true; @@ -8848,7 +8848,16 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall if (P_CameraThinker(player, thiscam, resetcalled)) return true; - if (thiscam == &camera) + if (tutorialmode) + { + // force defaults because we have a camera look section + camspeed = (INT32)(atof(cv_cam_speed.defaultvalue) * FRACUNIT); + camstill = (!stricmp(cv_cam_still.defaultvalue, "off")) ? false : true; + camrotate = atoi(cv_cam_rotate.defaultvalue); + camdist = FixedMul((INT32)(atof(cv_cam_dist.defaultvalue) * FRACUNIT), mo->scale); + camheight = FixedMul((INT32)(atof(cv_cam_height.defaultvalue) * FRACUNIT), FixedMul(player->camerascale, mo->scale)); + } + else if (thiscam == &camera) { camspeed = cv_cam_speed.value; camstill = cv_cam_still.value; diff --git a/src/r_main.c b/src/r_main.c index bfca180d0011ed9dc34d68bf9948b448b12827b8..98ce978a693b73acb894b46cfb9fe682990f7fe5 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -913,7 +913,7 @@ void R_SetupFrame(player_t *player, boolean skybox) chasecam = (cv_chasecam.value != 0); } - if (player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN) + if (player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD || gamestate == GS_TITLESCREEN || tutorialmode) chasecam = true; // force chasecam on else if (player->spectator) // no spectator chasecam chasecam = false; // force chasecam off