As promised, Encore (alt)music

- `EncoreMusic` on mapheader
    - Supports up to 3 alt musics, as with every other type
- Do not vape/nightcoreify if provided
- To avoid complicating the Alt Music logic too much, uses "side B" of the same Prison Egg CD
    - That is to say, if there are 2 encore tracks and 3 normal tracks, the second Encore track will only play if you've gotten the CD associated with the second normal track

I haven't actually tested it *with* any Encore music, but I've triplechecked literally everywhere to make sure it didn't break standard play
This commit is contained in:
toaster 2024-03-10 14:26:33 +00:00
parent 0f6ddefe30
commit 6e46f97847
8 changed files with 112 additions and 18 deletions

View file

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

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

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

@ -446,6 +446,8 @@ static void P_ClearSingleMapHeaderInfo(INT16 num)
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++)
{
@ -8194,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);
@ -8201,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]))
@ -8260,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)
@ -1435,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.