diff --git a/src/deh_soc.c b/src/deh_soc.c index 8817ad40b..047a396af 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -1234,8 +1234,6 @@ void readlevelheader(MYFILE *f, char * name) break; deh_strlcpy(mapheaderinfo[num]->musname[j], tmp, sizeof(mapheaderinfo[num]->musname[j]), va("Level header %d: music", num)); - if (j) - mapheaderinfo[num]->cache_muslock[j - 1] = MAXUNLOCKABLES; j++; } while ((tmp = strtok(NULL,",")) != NULL); @@ -1244,6 +1242,30 @@ void readlevelheader(MYFILE *f, char * name) mapheaderinfo[num]->musname_size = j; } } + else if (fastcmp(word, "ENCOREMUSIC")) + { + if (fastcmp(word2, "NONE")) + { + mapheaderinfo[num]->encoremusname[0][0] = 0; // becomes empty string + mapheaderinfo[num]->encoremusname_size = 0; + } + else + { + UINT8 j = 0; // i was declared elsewhere + tmp = strtok(word2, ","); + do { + if (j >= MAXMUSNAMES) + break; + deh_strlcpy(mapheaderinfo[num]->encoremusname[j], tmp, + sizeof(mapheaderinfo[num]->encoremusname[j]), va("Level header %d: encore music", num)); + j++; + } while ((tmp = strtok(NULL,",")) != NULL); + + if (tmp != NULL) + deh_warning("Level header %d: additional music slots past %d discarded", num, MAXMUSNAMES); + mapheaderinfo[num]->encoremusname_size = j; + } + } else if (fastcmp(word, "ASSOCIATEDMUSIC")) { if (fastcmp(word2, "NONE")) diff --git a/src/doomstat.h b/src/doomstat.h index 761a3410b..255600a47 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -515,10 +515,12 @@ struct mapheader_t // 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 encoremusname[MAXMUSNAMES][7]; ///< Music tracks to play in Encore. First dimension is the track number, second is the music string. "" for no music. UINT16 cache_muslock[MAXMUSNAMES-1]; ///< Cached Alt Music IDs char associatedmus[MAXMUSNAMES][7]; ///< Associated music tracks for sound test unlock. char positionmus[7]; ///< Custom Position track. Doesn't play in Encore or other fun game-controlled contexts UINT8 musname_size; ///< Number of music tracks defined + UINT8 encoremusname_size; ///< Number of Encore music tracks defined UINT8 associatedmus_size; ///< Number of associated music tracks defined UINT16 mustrack; ///< Subsong to play. Only really relevant for music modules and specific formats supported by GME. 0 to ignore. UINT32 muspos; ///< Music position to jump to. diff --git a/src/hu_stuff.h b/src/hu_stuff.h index 487b06e1a..3be1bdcac 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -50,7 +50,7 @@ extern "C" { // Level title font #define LT_FONTSTART '!' // the first font characters -#define LT_FONTEND 'z' // the last font characters +#define LT_FONTEND '~' // the last font characters #define LT_FONTSIZE (LT_FONTEND - LT_FONTSTART + 1) #define CRED_FONTSTART '!' // the first font character diff --git a/src/k_menudraw.c b/src/k_menudraw.c index d1a44c962..b7a193879 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -7089,6 +7089,90 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) break; } + case SECRET_ALTMUSIC: + { + UINT16 map = M_UnlockableMapNum(ref); + if (map >= nummapheaders + || !mapheaderinfo[map]) + { + return; + } + + UINT8 musicid; + for (musicid = 1; musicid < MAXMUSNAMES; musicid++) + { + if (mapheaderinfo[map]->cache_muslock[musicid - 1] == challengesmenu.currentunlock) + break; + } + + if (musicid == MAXMUSNAMES) + { + return; + } + + spritedef_t *sprdef = &sprites[SPR_ALTM]; + spriteframe_t *sprframe; + patch_t *patch; + UINT32 addflags = 0; + + x -= 10; + y += 15; + + if (sprdef->numframes) + { + sprframe = &sprdef->spriteframes[0]; + patch = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE); + + if (sprframe->flip & 1) // Only for first sprite + { + addflags ^= V_FLIP; // This sprite is left/right flipped! + } + + V_DrawFixedPatch(x*FRACUNIT, (y+2)*FRACUNIT, FRACUNIT/2, addflags, patch, NULL); + } + + x = 8; + y = BASEVIDHEIGHT-16; + + const boolean thismusplaying = Music_Playing("challenge_altmusic"); + boolean pushed = false; + const char *song = NULL; + + if (M_SecretUnlocked(SECRET_ENCORE, true) + && musicid < mapheaderinfo[map]->encoremusname_size) + { + if (thismusplaying) + { + song = Music_Song("challenge_altmusic"); + pushed = strcmp(song, mapheaderinfo[map]->encoremusname[musicid]) == 0; + } + + K_drawButton(x&FRACUNIT, y*FRACUNIT, 0, kp_button_l, pushed); + x += SHORT(kp_button_l[0]->width); + V_DrawThinString(x, y + 1, (pushed ? V_GRAYMAP : highlightflags), "E Side"); + + x = 8; + y -= 10; + } + + if (musicid < mapheaderinfo[map]->musname_size) + { + if (pushed || !thismusplaying) + { + pushed = false; + } + else + { + if (!song) + song = Music_Song("challenge_altmusic"); + pushed = strcmp(song, mapheaderinfo[map]->musname[musicid]) == 0; + } + + K_drawButton(x*FRACUNIT, y*FRACUNIT, 0, kp_button_a[1], pushed); + x += SHORT(kp_button_a[1][0]->width); + V_DrawThinString(x, y + 1, (pushed ? V_GRAYMAP : highlightflags), "Play CD"); + } + } default: { break; @@ -8359,7 +8443,7 @@ void M_DrawSoundTest(void) { UINT32 currenttime = min(Music_Elapsed(tune), Music_TotalDuration(tune)); - V_DrawRightAlignedMenuString(x + 272-1, 18+32, 0, + V_DrawRightAlignedThinString(x + 272-1, 18+32, 0, va("%02u:%02u", G_TicsToMinutes(currenttime, true), G_TicsToSeconds(currenttime) @@ -8373,7 +8457,7 @@ void M_DrawSoundTest(void) { UINT32 exittime = Music_TotalDuration(tune); - V_DrawRightAlignedMenuString(x + 272-1, 18+32+10, 0, + V_DrawRightAlignedThinString(x + 272-1, 18+32+10, 0, va("%02u:%02u", G_TicsToMinutes(exittime, true), G_TicsToSeconds(exittime) diff --git a/src/k_podium.cpp b/src/k_podium.cpp index 272704a33..8e5ef73eb 100644 --- a/src/k_podium.cpp +++ b/src/k_podium.cpp @@ -1247,7 +1247,14 @@ void K_ResetCeremony(void) mapmusrng = 0; } - while (mapmusrng >= std::max(1, mapheaderinfo[gamemap-1]->musname_size)) + UINT8 limit = (encoremode && mapheaderinfo[gamemap-1]->encoremusname_size) + ? mapheaderinfo[gamemap-1]->encoremusname_size + : mapheaderinfo[gamemap-1]->musname_size; + + if (limit < 1) + limit = 1; + + while (mapmusrng >= limit) { mapmusrng--; } diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 9117858d1..adb65325f 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -2507,12 +2507,36 @@ static int mapheaderinfo_get(lua_State *L) lua_rawseti(L, -2, 1 + i); } } + else if (fastcmp(field,"encoremusname")) // we create a table here because it saves us from a userdata nightmare + { + UINT8 i; + lua_createtable(L, header->encoremusname_size, 0); + for (i = 0; i < header->encoremusname_size; i++) + { + lua_pushstring(L, header->encoremusname[i]); + lua_rawseti(L, -2, 1 + i); + } + } + else if (fastcmp(field,"associatedmus")) // we create a table here because it saves us from a userdata nightmare + { + UINT8 i; + lua_createtable(L, header->associatedmus_size, 0); + for (i = 0; i < header->associatedmus_size; i++) + { + lua_pushstring(L, header->associatedmus[i]); + lua_rawseti(L, -2, 1 + i); + } + } else if (fastcmp(field,"mustrack")) lua_pushinteger(L, header->mustrack); else if (fastcmp(field,"muspos")) lua_pushinteger(L, header->muspos); else if (fastcmp(field,"musname_size")) lua_pushinteger(L, header->musname_size); + else if (fastcmp(field,"encoremusname_size")) + lua_pushinteger(L, header->encoremusname_size); + else if (fastcmp(field,"associatedmus_size")) + lua_pushinteger(L, header->associatedmus_size); else if (fastcmp(field,"weather")) lua_pushinteger(L, header->weather); else if (fastcmp(field,"skytexture")) diff --git a/src/m_cond.c b/src/m_cond.c index 72a9f7792..b2d675894 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1077,7 +1077,8 @@ static void M_PrecacheLevelLocks(void) if (map < nummapheaders && mapheaderinfo[map]) { - for (j = 1; j < mapheaderinfo[map]->musname_size; j++) + UINT8 greatersize = max(mapheaderinfo[map]->musname_size, mapheaderinfo[map]->encoremusname_size); + for (j = 1; j < greatersize; j++) { if (mapheaderinfo[map]->cache_muslock[j - 1] != MAXUNLOCKABLES) { @@ -1143,7 +1144,7 @@ static void M_PrecacheLevelLocks(void) break; } - if (j == mapheaderinfo[map]->musname_size) + if (j == greatersize) CONS_Alert(CONS_ERROR, "Unlockable %u: Too many SECRET_ALTMUSICs associated with Level %s\n", i+1, mapheaderinfo[map]->lumpname); } else diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index b6b71957e..1d6b9704e 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -834,6 +834,8 @@ boolean M_ChallengesInputs(INT32 ch) { if (M_MenuBackPressed(pid) || start) { + Music_Stop("challenge_altmusic"); + currentMenu->prevMenu = M_SpecificMenuRestore(currentMenu->prevMenu); M_GoBack(0); @@ -1021,17 +1023,83 @@ boolean M_ChallengesInputs(INT32 ch) return true; } - if (M_MenuConfirmPressed(pid) - && challengesmenu.currentunlock < MAXUNLOCKABLES + if (challengesmenu.currentunlock < MAXUNLOCKABLES && gamedata->unlocked[challengesmenu.currentunlock]) { switch (unlockables[challengesmenu.currentunlock].type) { - case SECRET_ALTTITLE: { - extern consvar_t cv_alttitle; - CV_AddValue(&cv_alttitle, 1); - S_StartSound(NULL, sfx_s3kc3s); - M_SetMenuDelay(pid); + case SECRET_ALTTITLE: + { + if (M_MenuConfirmPressed(pid)) + { + extern consvar_t cv_alttitle; + CV_AddValue(&cv_alttitle, 1); + S_StartSound(NULL, sfx_s3kc3s); + M_SetMenuDelay(pid); + } + break; + } + case SECRET_ALTMUSIC: + { + UINT8 trymus = 0, musicid = MAXMUSNAMES; + + if (M_MenuConfirmPressed(pid)) + { + trymus = 1; + } + else if (M_MenuButtonPressed(pid, MBT_L)) + { + trymus = 2; + } + + if (trymus) + { + const char *trymusname = NULL; + + UINT16 map = M_UnlockableMapNum(&unlockables[challengesmenu.currentunlock]); + if (map >= nummapheaders + || !mapheaderinfo[map]) + { + ; + } + else for (musicid = 1; musicid < MAXMUSNAMES; musicid++) + { + if (mapheaderinfo[map]->cache_muslock[musicid - 1] == challengesmenu.currentunlock) + break; + } + + if (trymus == 1) + { + if (musicid < mapheaderinfo[map]->musname_size) + { + trymusname = mapheaderinfo[map]->musname[musicid]; + } + } + else + { + if (musicid < mapheaderinfo[map]->encoremusname_size) + { + trymusname = mapheaderinfo[map]->encoremusname[musicid]; + } + } + + if (trymusname) + { + if (!Music_Playing("challenge_altmusic") + || strcmp(Music_Song("challenge_altmusic"), trymusname)) + { + Music_Remap("challenge_altmusic", trymusname); + Music_Play("challenge_altmusic"); + } + else + { + Music_Stop("challenge_altmusic"); + } + + M_SetMenuDelay(pid); + } + } + break; } default: diff --git a/src/music.cpp b/src/music.cpp index caa77e51c..0f5c31e71 100644 --- a/src/music.cpp +++ b/src/music.cpp @@ -195,6 +195,14 @@ void Music_Init(void) tune.fade_out = 5000; tune.fade_out_inclusive = false; } + + { + Tune& tune = g_tunes.insert("challenge_altmusic"); + + tune.priority = 100; + tune.resist = true; + tune.credit = true; + } } void Music_Tick(void) @@ -218,6 +226,23 @@ void Music_Play(const char* id) } } +void Music_SetFadeOut(const char* id, int fade_out) +{ + Tune* tune = g_tunes.find(id); + + if (tune) + { + tune->fade_out = fade_out; + + if (tune->time_remaining() <= detail::msec_to_tics(tune->fade_out)) + { + // If this action would cause a fade out, start + // fading immediately. + g_tunes.tick(); + } + } +} + void Music_DelayEnd(const char* id, tic_t duration) { Tune* tune = g_tunes.find(id); diff --git a/src/music.h b/src/music.h index 2265639ea..61f6c4976 100644 --- a/src/music.h +++ b/src/music.h @@ -59,6 +59,10 @@ const char *Music_CurrentId(void); // back to the start.) void Music_Play(const char *id); +// Set fade out duration. Mostly to fix a last minute bug +// with Stereo Mode. +void Music_SetFadeOut(const char* id, int fade_out); + // Postpone the end of this tune until N tics from now. The // tune should already be playing before calling this. void Music_DelayEnd(const char *id, tic_t duration); diff --git a/src/music_tune.hpp b/src/music_tune.hpp index d8b859b5d..405832abe 100644 --- a/src/music_tune.hpp +++ b/src/music_tune.hpp @@ -103,7 +103,12 @@ public: return (1.f/encoremul); } - return encoremul; + if (!nightcoreable + || mapheaderinfo[gamemap-1]->encoremusname_size == 0) + { + // We only vape if the level doesn't have alternate tracks. + return encoremul; + } } return 1.f; diff --git a/src/p_setup.cpp b/src/p_setup.cpp index bb9639f82..6820def0d 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -433,6 +433,8 @@ static void P_ClearMapHeaderLighting(mapheader_lighting_t *lighting) */ static void P_ClearSingleMapHeaderInfo(INT16 num) { + int i; + mapheaderinfo[num]->lvlttl[0] = '\0'; mapheaderinfo[num]->menuttl[0] = '\0'; mapheaderinfo[num]->zonttl[0] = '\0'; @@ -441,13 +443,24 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) 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]->encoremusname[0][0] = 0; + mapheaderinfo[num]->encoremusname_size = 0; + + for (i = 0; i < MAXMUSNAMES-1; i++) + { + mapheaderinfo[num]->cache_muslock[i] = MAXUNLOCKABLES; + } + mapheaderinfo[num]->positionmus[0] = '\0'; mapheaderinfo[num]->associatedmus[0][0] = 0; mapheaderinfo[num]->associatedmus_size = 0; + mapheaderinfo[num]->mustrack = 0; mapheaderinfo[num]->muspos = 0; + mapheaderinfo[num]->weather = PRECIP_NONE; snprintf(mapheaderinfo[num]->skytexture, 5, "SKY1"); mapheaderinfo[num]->skytexture[4] = 0; @@ -494,7 +507,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) if (mapheaderinfo[num]->ghostBrief != NULL) { - for (int i = 0; i < mapheaderinfo[num]->ghostCount; i++) + for (i = 0; i < mapheaderinfo[num]->ghostCount; i++) { Z_Free(mapheaderinfo[num]->ghostBrief[i]); } @@ -8183,6 +8196,9 @@ void P_ResetLevelMusic(void) UINT8 idx = 0; mapheader_t* mapheader = mapheaderinfo[gamemap - 1]; + UINT8 truesize = (encoremode && mapheader->encoremusname_size) + ? mapheader->encoremusname_size + : mapheader->musname_size; // To keep RNG in sync, we will always pull from RNG, even if unused UINT32 random = P_Random(PR_MUSICSELECT); @@ -8190,20 +8206,20 @@ void P_ResetLevelMusic(void) if (demo.playback) { // mapmusrng has already been set by the demo; just make sure it's valid - if (mapmusrng >= mapheader->musname_size) + if (mapmusrng >= truesize) { mapmusrng = 0; } return; } - if (mapheader->musname_size > 1) + if (truesize > 1) { UINT8 tempmapmus[MAXMUSNAMES], tempmapmus_size = 1, i; tempmapmus[0] = 0; - for (i = 1; i < mapheader->musname_size; i++) + for (i = 1; i < truesize; i++) { if (mapheader->cache_muslock[i-1] < MAXUNLOCKABLES && !M_CheckNetUnlockByID(mapheader->cache_muslock[i-1])) @@ -8249,7 +8265,12 @@ void P_LoadLevelMusic(void) mapheader_t* mapheader = mapheaderinfo[gamemap-1]; const char *music = mapheader->musname[0]; - if (mapmusrng < mapheader->musname_size) + if (encoremode && mapheader->encoremusname_size + && mapmusrng < mapheader->encoremusname_size) + { + music = mapheader->encoremusname[mapmusrng]; + } + else if (mapmusrng < mapheader->musname_size) { music = mapheader->musname[mapmusrng]; } diff --git a/src/s_sound.c b/src/s_sound.c index d5cae81cf..7a10b4926 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -1292,6 +1292,11 @@ static void S_InsertMapIntoSoundTestSequence(UINT16 map, musicdef_t ***tail) { S_InsertMusicAtSoundTestSequenceTail(mapheaderinfo[map]->associatedmus[i], map, ALTREF_REQUIRESBEATEN, tail); } + + for (i = 0; i < mapheaderinfo[map]->encoremusname_size; i++) + { + S_InsertMusicAtSoundTestSequenceTail(mapheaderinfo[map]->encoremusname[i], map, i+MAXMUSNAMES, tail); + } } void S_PopulateSoundTestSequence(void) @@ -1317,7 +1322,22 @@ void S_PopulateSoundTestSequence(void) tail = &soundtest.sequence.next; - // We iterate over all cups. + // We iterate over all tutorial maps. + for (i = 0; i < nummapheaders; i++) + { + if (!mapheaderinfo[i]) + continue; + + if (mapheaderinfo[i]->cup != NULL) + continue; + + if ((mapheaderinfo[i]->typeoflevel & TOL_TUTORIAL) == 0) + continue; + + S_InsertMapIntoSoundTestSequence(i, &tail); + } + + // Next, we iterate over all cups. { cupheader_t *cup; for (cup = kartcupheaders; cup; cup = cup->next) @@ -1338,7 +1358,7 @@ void S_PopulateSoundTestSequence(void) } } - // Then, we iterate over all non-cupped maps. + // Then, we iterate over all remaining non-cupped maps. for (i = 0; i < nummapheaders; i++) { if (!mapheaderinfo[i]) @@ -1347,6 +1367,9 @@ void S_PopulateSoundTestSequence(void) if (mapheaderinfo[i]->cup != NULL) continue; + if (mapheaderinfo[i]->typeoflevel & TOL_TUTORIAL) + continue; + S_InsertMapIntoSoundTestSequence(i, &tail); } @@ -1417,17 +1440,33 @@ static boolean S_SoundTestDefLocked(musicdef_t *def) && !(header->records.mapvisited & MV_VISITED)) return true; - // Associated music only when completed - if ((def->sequence.altref == ALTREF_REQUIRESBEATEN) - && !(header->records.mapvisited & MV_BEATEN)) - return true; - - if (def->sequence.altref != 0 && def->sequence.altref < header->musname_size) + if (def->sequence.altref != 0) { - // Alt music requires unlocking the alt - if ((header->cache_muslock[def->sequence.altref - 1] < MAXUNLOCKABLES) - && gamedata->unlocked[header->cache_muslock[def->sequence.altref - 1]] == false) - return true; + if ((def->sequence.altref == ALTREF_REQUIRESBEATEN)) + { + // Associated music only when completed + if (!(header->records.mapvisited & MV_BEATEN)) + return true; + } + else if (def->sequence.altref < MAXMUSNAMES) + { + // Alt music requires unlocking the alt + if ((header->cache_muslock[def->sequence.altref - 1] < MAXUNLOCKABLES) + && gamedata->unlocked[header->cache_muslock[def->sequence.altref - 1]] == false) + return true; + } + else if (def->sequence.altref < MAXMUSNAMES*2) + { + // Encore! + if (M_SecretUnlocked(SECRET_ENCORE, true) == false) + return true; + + // Side B of the same CD + if (def->sequence.altref > MAXMUSNAMES + && (header->cache_muslock[def->sequence.altref - (1 + MAXMUSNAMES)] < MAXUNLOCKABLES) + && gamedata->unlocked[header->cache_muslock[def->sequence.altref - (1 + MAXMUSNAMES)]] == false) + return true; + } } // Finally, do a full-fat map check. @@ -1672,10 +1711,7 @@ const char *S_SoundTestTune(UINT8 invert) boolean S_SoundTestCanSequenceFade(void) { - return - soundtest.current->basenoloop[soundtest.currenttrack] == false && - // Only fade out if we're the last track for this song. - soundtest.currenttrack == soundtest.current->numtracks-1; + return soundtest.current->basenoloop[soundtest.currenttrack] == false; } static void S_SoundTestReconfigure(const char *tune) @@ -1710,12 +1746,16 @@ void S_SoundTestPlay(void) } // Does song have default loop? - if (soundtest.current->basenoloop[soundtest.currenttrack] == false) + if (S_SoundTestCanSequenceFade() == true) { + // I'd personally like songs in sequence to last between 3 and 6 minutes. if (sequencemaxtime < 3*60*1000) { - // I'd personally like songs in sequence to last between 3 and 6 minutes. - const UINT32 loopduration = (sequencemaxtime - I_GetSongLoopPoint()); + const UINT32 looppoint = I_GetSongLoopPoint(); + const UINT32 loopduration = + (looppoint < sequencemaxtime) + ? sequencemaxtime - looppoint + : 0; if (!loopduration) ; @@ -1727,6 +1767,16 @@ void S_SoundTestPlay(void) } } + // Only the last track fades out... but we still use stereo_fade to handle stopping. + if (soundtest.currenttrack == soundtest.current->numtracks-1) + { + Music_SetFadeOut("stereo_fade", 5000); + } + else + { + Music_SetFadeOut("stereo_fade", 0); + } + Music_DelayEnd( S_SoundTestCanSequenceFade() ? "stereo_fade" : "stereo", (TICRATE*sequencemaxtime)/1000 // ms to TICRATE conversion