Merge branch 'last-music' into 'master'

Last Music

See merge request KartKrew/Kart!2076
This commit is contained in:
AJ Martinez 2024-03-12 00:07:25 +00:00
commit c019dfa517
13 changed files with 353 additions and 40 deletions

View file

@ -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"))

View file

@ -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.

View file

@ -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

View file

@ -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)

View file

@ -1247,7 +1247,14 @@ void K_ResetCeremony(void)
mapmusrng = 0;
}
while (mapmusrng >= std::max<UINT8>(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--;
}

View file

@ -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"))

View file

@ -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

View file

@ -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:

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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];
}

View file

@ -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