Stereo mode

- Minimum viable implementation
- New horizontal menu
    - FUTURE WORK: visuals are extremely basic
- Accessible from both Extras and in-game Pause if SECRET_SOUNDTEST is unlocked
- Rather than the Shadow-the-Hedgehog style free select of SRB2's Sound Test, it's a Best Of The Hacks And Fan Music Sega CD player.
    - Back
        - Exit menu
    - Stop
       - Stops Stereo music entirely
    - Pause
      - Pauses Stereo music without losing place in sequence
          - FUTURE WORK: This should probably just pause the actual player ala minimised viewport
    - Play
      - Begins Stereo music on non-NULL musicdef entry
    - Track
      - For NULL soundtest entry:
          - Switches between sfx
      - For musicdefs with multiple tracks:
          - Switches between them
    - Prev and Next
        - Changes musicdef entry
            - FUTURE WORK: This is extremely naive and doesn't respect the following
                - Cup order
                - Unlocks
- Overrides all game-requested music changes when in Play or Pause mode
    - This makes it an actual fun in-game feature as a menuification of the `tunes` command, not just a pure novelty.
This commit is contained in:
toaster 2023-03-25 23:37:07 +00:00
parent afff038e34
commit 8f592c196f
9 changed files with 351 additions and 3 deletions

View file

@ -426,10 +426,14 @@ extern menuitem_t MISC_ChallengesStatsDummyMenu[];
extern menu_t MISC_ChallengesDef;
extern menu_t MISC_StatisticsDef;
extern menuitem_t MISC_SoundTest[];
extern menu_t MISC_SoundTestDef;
// We'll need this since we're gonna have to dynamically enable and disable options depending on which state we're in.
typedef enum
{
mpause_addons = 0,
mpause_stereo,
mpause_changegametype,
mpause_switchmap,
mpause_restartmap,
@ -1203,6 +1207,9 @@ void M_Statistics(INT32 choice);
void M_DrawStatistics(void);
boolean M_StatisticsInputs(INT32 ch);
void M_SoundTest(INT32 choice);
void M_DrawSoundTest(void);
// These defines make it a little easier to make menus
#define DEFAULTMENUSTYLE(source, prev, x, y)\
{\

View file

@ -5925,3 +5925,54 @@ void M_DrawStatistics(void)
}
#undef STATSSTEP
void M_DrawSoundTest(void)
{
INT32 x = currentMenu->x - menutransition.tics*48, y, i, w, cursorx = 0;
if (gamestate == GS_MENU)
{
patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE);
V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL);
}
y = 50;
if (soundtest.current != NULL)
{
V_DrawThinString(x, y, (soundtest.playing ? highlightflags : 0)|V_ALLOWLOWERCASE|V_6WIDTHSPACE, soundtest.current->title);
V_DrawThinString(x, (y += 10), V_ALLOWLOWERCASE|V_6WIDTHSPACE, va("%d", soundtest.currenttrack));
if (soundtest.current->author)
V_DrawThinString(x, (y += 10), V_ALLOWLOWERCASE|V_6WIDTHSPACE, soundtest.current->author);
if (soundtest.current->source)
V_DrawThinString(x, (y += 10), V_ALLOWLOWERCASE|V_6WIDTHSPACE, soundtest.current->source);
if (soundtest.current->composers)
V_DrawThinString(x, (y += 10), V_ALLOWLOWERCASE|V_6WIDTHSPACE, soundtest.current->composers);
}
else
{
const char *sfxstr = (cv_soundtest.value) ? S_sfx[cv_soundtest.value].name : "N/A";
V_DrawThinString(x, y, V_ALLOWLOWERCASE|V_6WIDTHSPACE, sfxstr);
V_DrawThinString(x, (y += 10), V_ALLOWLOWERCASE|V_6WIDTHSPACE, va("%d", cv_soundtest.value));
}
y = currentMenu->y;
for (i = 0; i < currentMenu->numitems; i++)
{
w = V_ThinStringWidth(currentMenu->menuitems[i].text, V_6WIDTHSPACE);
if (i == itemOn)
{
cursorx = x + w/2;
V_DrawThinString(x, y, V_6WIDTHSPACE|highlightflags, currentMenu->menuitems[i].text);
}
else
{
V_DrawThinString(x, y, V_6WIDTHSPACE, currentMenu->menuitems[i].text);
}
x += w + 8;
}
V_DrawCharacter(cursorx - 4, currentMenu->y - 8 - (skullAnimCounter/5),
'\x1B' | V_SNAPTOTOP|highlightflags, false); // up arrow
}

View file

@ -382,7 +382,7 @@ void M_PlayMenuJam(void)
return;
}
if (Playing())
if (Playing() || soundtest.playing)
return;
if (refMenu != NULL && refMenu->music != NULL)

View file

@ -22,6 +22,9 @@ menuitem_t EXTRAS_Main[] =
{IT_STRING | IT_CALL, NULL, NULL,
NULL, {.routine = M_ReplayHut}, 0, 0},
{IT_STRING | IT_CALL, NULL, NULL,
NULL, {.routine = M_SoundTest}, 0, 0},
};
// the extras menu essentially reuses the options menu stuff
@ -49,6 +52,8 @@ struct extrasmenu_s extrasmenu;
void M_InitExtras(INT32 choice)
{
UINT32 maxvalid = 2;
(void)choice;
extrasmenu.ticker = 0;
@ -82,6 +87,7 @@ void M_InitExtras(INT32 choice)
EXTRAS_Main[3].status = IT_STRING | IT_CALL;
EXTRAS_Main[3].text = "Egg TV";
EXTRAS_Main[3].tooltip = "Watch the replays you've saved throughout your many races & battles!";
maxvalid = 3;
}
else
{
@ -89,7 +95,25 @@ void M_InitExtras(INT32 choice)
EXTRAS_Main[3].text = EXTRAS_Main[3].tooltip = "???";
if (EXTRAS_MainDef.lastOn == 3)
{
EXTRAS_MainDef.lastOn = 2;
EXTRAS_MainDef.lastOn = maxvalid;
}
}
// Stereo Mode
if (M_SecretUnlocked(SECRET_SOUNDTEST, true))
{
EXTRAS_Main[4].status = IT_STRING | IT_CALL;
EXTRAS_Main[4].text = "Stereo Mode";
EXTRAS_Main[4].tooltip = "You can listen to your favourite tunes here!";
maxvalid = 4;
}
else
{
EXTRAS_Main[4].status = IT_STRING | IT_TRANSTEXT;
EXTRAS_Main[4].text = EXTRAS_Main[4].tooltip = "???";
if (EXTRAS_MainDef.lastOn == 4)
{
EXTRAS_MainDef.lastOn = maxvalid;
}
}

View file

@ -4,6 +4,7 @@ target_sources(SRB2SDL2 PRIVATE
level-select.c
gametype.c
manual.c
sound-test.c
message-box.c
pause-game.c
pause-replay.c

View file

@ -15,6 +15,9 @@ menuitem_t PAUSE_Main[] =
{IT_STRING | IT_CALL, "ADDONS", "M_ICOADD",
NULL, {.routine = M_Addons}, 0, 0},
{IT_STRING | IT_CALL, "STEREO MODE", "M_ICOSTM",
NULL, {.routine = M_SoundTest}, 0, 0},
{IT_STRING | IT_KEYHANDLER, "GAMETYPE", "M_ICOGAM",
NULL, {.routine = M_HandlePauseMenuGametype}, 0, 0},
@ -111,6 +114,7 @@ void M_OpenPauseMenu(void)
// By default, disable anything sensitive:
PAUSE_Main[mpause_addons].status = IT_DISABLED;
PAUSE_Main[mpause_stereo].status = IT_DISABLED;
PAUSE_Main[mpause_changegametype].status = IT_DISABLED;
PAUSE_Main[mpause_switchmap].status = IT_DISABLED;
PAUSE_Main[mpause_restartmap].status = IT_DISABLED;
@ -127,6 +131,11 @@ void M_OpenPauseMenu(void)
Dummymenuplayer_OnChange(); // Make sure the consvar is within bounds of the amount of splitscreen players we have.
if (M_SecretUnlocked(SECRET_SOUNDTEST, true))
{
PAUSE_Main[mpause_stereo].status = IT_STRING | IT_CALL;
}
if (K_CanChangeRules(false))
{
PAUSE_Main[mpause_psetup].status = IT_STRING | IT_CALL;

View file

@ -0,0 +1,145 @@
/// \file menus/transient/sound-test.c
/// \brief Stereo Mode menu
#include "../../k_menu.h"
#include "../../s_sound.h"
static void M_SoundTestMainControl(INT32 choice)
{
(void)choice;
// Sound test exception
if (soundtest.current == NULL || soundtest.current->numtracks == 0)
{
if (currentMenu->menuitems[itemOn].mvar1 == 1) // Play
{
soundtest.playing = true;
//soundtest.sequence = true;
S_UpdateSoundTestDef(false, false);
}
else if (cv_soundtest.value != 0)
{
S_StopSounds();
if (currentMenu->menuitems[itemOn].mvar1 == 0) // Stop
{
CV_SetValue(&cv_soundtest, 0);
}
}
return;
}
if (currentMenu->menuitems[itemOn].mvar1 == 1) // Play
{
S_SoundTestPlay();
}
else if (soundtest.playing == true)
{
S_SoundTestStop((currentMenu->menuitems[itemOn].mvar1 == 2));
}
}
static void M_SoundTestNextPrev(INT32 choice)
{
(void)choice;
S_UpdateSoundTestDef((currentMenu->menuitems[itemOn].mvar1 < 0), false);
}
static void M_SoundTestTrack(INT32 choice)
{
const UINT8 numtracks = (soundtest.current != NULL) ? soundtest.current->numtracks : 0;
if (numtracks == 1)
{
return;
}
// Soundtest exception
if (numtracks == 0)
{
S_StopSounds();
if (choice == -1) // Extra
{
if (cv_soundtest.value != 0)
CV_SetValue(&cv_soundtest, 0);
}
else if (choice == 2) // Confirm
{
if (cv_soundtest.value != 0)
S_StartSound(NULL, cv_soundtest.value);
}
else // Up or Down
{
CV_AddValue(&cv_soundtest, ((choice == 0) ? -1 : 1));
}
return;
}
if (choice == -1) // Extra
{
soundtest.currenttrack = 0;
}
else
{
// Confirm resets the current instance
if (choice == 0) // Down
{
soundtest.currenttrack--;
if (soundtest.currenttrack < 0)
soundtest.currenttrack = numtracks-1;
}
else if (choice == 1) // Up
{
soundtest.currenttrack++;
if (soundtest.currenttrack >= numtracks)
soundtest.currenttrack = 0;
}
}
if (soundtest.playing)
{
S_SoundTestPlay();
}
}
menuitem_t MISC_SoundTest[] =
{
{IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0},
{IT_STRING | IT_CALL, "Stop", NULL, NULL, {.routine = M_SoundTestMainControl}, 0, 0},
{IT_STRING | IT_CALL, "Pause", NULL, NULL, {.routine = M_SoundTestMainControl}, 2, 0},
{IT_STRING | IT_CALL, "Play", NULL, NULL, {.routine = M_SoundTestMainControl}, 1, 0},
{IT_STRING | IT_ARROWS, "Track", NULL, NULL, {.routine = M_SoundTestTrack}, 0, 0},
{IT_STRING | IT_CALL, "Prev", NULL, NULL, {.routine = M_SoundTestNextPrev}, -1, 0},
{IT_STRING | IT_CALL, "Next", NULL, NULL, {.routine = M_SoundTestNextPrev}, 1, 0},
};
menu_t MISC_SoundTestDef = {
sizeof (MISC_SoundTest)/sizeof (menuitem_t),
&MainDef,
0,
MISC_SoundTest,
42, BASEVIDHEIGHT/2,
0, 0,
MBF_UD_LR_FLIPPED|MBF_SOUNDLESS,
".",
98, 0,
M_DrawSoundTest,
NULL,
NULL,
NULL,
NULL,
};
void M_SoundTest(INT32 choice)
{
(void)choice;
// I reserve the right to add some sort of setup here -- toast 250323
MISC_SoundTestDef.prevMenu = currentMenu;
M_SetupNextMenu(&MISC_SoundTestDef, false);
}

View file

@ -1360,9 +1360,108 @@ static tic_t pause_starttic;
musicdef_t *musicdefstart = NULL;
struct cursongcredit cursongcredit; // Currently displayed song credit info
struct soundtest soundtest; // Sound Test (sound test)
void S_UpdateSoundTestDef(boolean reverse, boolean skipnull)
{
musicdef_t *newdef;
// Naive implementation for now.
newdef = (soundtest.current != NULL
&& (skipnull == false || soundtest.current->next != NULL))
? soundtest.current->next
: musicdefstart;
if (reverse == false)
{
// Due to how musicdefs are populated, we have to traverse backwards when attempting forwards.
musicdef_t *def;
if (soundtest.current == musicdefstart)
{
newdef = NULL;
if (skipnull == false)
{
goto conclusion;
}
}
for (def = musicdefstart; def; def = def->next)
{
if (def->next != soundtest.current)
continue;
newdef = def;
break;
}
}
conclusion:
soundtest.current = newdef;
soundtest.currenttrack = 0;
if (soundtest.playing == true)
{
if (newdef == NULL)
{
S_SoundTestStop(false);
}
else
{
S_SoundTestPlay();
}
}
}
void S_SoundTestPlay(void)
{
soundtest.privilegedrequest = true;
S_StopMusic();
soundtest.playing = true;
S_ChangeMusicInternal(soundtest.current->name[soundtest.currenttrack], true);
S_ShowMusicCredit();
soundtest.privilegedrequest = false;
}
void S_SoundTestStop(boolean pause)
{
if (soundtest.playing == false)
{
return;
}
soundtest.privilegedrequest = true;
S_StopMusic();
cursongcredit.def = NULL;
if (pause == false)
{
soundtest.playing = false;
soundtest.current = NULL;
soundtest.currenttrack = 0;
if (gamestate == GS_LEVEL)
{
P_RestoreMusic(&players[consoleplayer]);
}
}
soundtest.privilegedrequest = false;
}
boolean S_PlaysimMusicDisabled(void)
{
return (demo.rewinding // Don't mess with music while rewinding!
if (soundtest.privilegedrequest)
return false;
return (soundtest.playing // Ring Racers: Stereo Mode
|| demo.rewinding // Don't mess with music while rewinding!
|| demo.title); // SRB2Kart: Demos don't interrupt title screen music
}

View file

@ -200,6 +200,18 @@ extern struct cursongcredit
fixed_t old_x;
} cursongcredit;
extern struct soundtest
{
boolean playing; // Music is playing?
boolean privilegedrequest; // Overrides S_PlaysimMusicDisabled w/o changing every function signature
musicdef_t *current; // Current selected music definition
SINT8 currenttrack; // Current selected music track for definition
} soundtest;
void S_UpdateSoundTestDef(boolean reverse, boolean skipnull);
void S_SoundTestPlay(void);
void S_SoundTestStop(boolean pause);
boolean S_PlaysimMusicDisabled(void);
extern musicdef_t *musicdefstart;