diff --git a/content/sounds.xml b/content/sounds.xml index cf9f498..09965ae 100644 --- a/content/sounds.xml +++ b/content/sounds.xml @@ -17,4 +17,4 @@ - \ No newline at end of file + diff --git a/main.lua b/main.lua index 12f9529..17b93be 100644 --- a/main.lua +++ b/main.lua @@ -1,3 +1,7 @@ +--fix dev stupidity +if REPENTANCE_PLUS and not REPENTANCE then + REPENTANCE = true +end if REPENTANCE then require("scripts.mmc.repentance") -end \ No newline at end of file +end diff --git a/scripts/mmc/repentance.lua b/scripts/mmc/repentance.lua index 3f790dc..8c13be4 100644 --- a/scripts/mmc/repentance.lua +++ b/scripts/mmc/repentance.lua @@ -220,6 +220,36 @@ chapter_music_greed[LevelStage.STAGE6_GREED] = { chapter_music_greed[LevelStage.STAGE7_GREED] = chapter_music_greed[LevelStage.STAGE6_GREED] +local random_music = { --this is for the "DELETE THIS" challenge + [0] = Music.MUSIC_BASEMENT, + [1] = Music.MUSIC_CELLAR, + [2] = Music.MUSIC_BURNING_BASEMENT, + [3] = Music.MUSIC_DOWNPOUR, + [4] = Music.MUSIC_DROSS, + [5] = Music.MUSIC_CAVES, + [6] = Music.MUSIC_CATACOMBS, + [7] = Music.MUSIC_FLOODED_CAVES, + [8] = Music.MUSIC_MINES, + [9] = Music.MUSIC_ASHPIT, + [10] = Music.MUSIC_DEPTHS, + [11] = Music.MUSIC_NECROPOLIS, + [12] = Music.MUSIC_DANK_DEPTHS, + [13] = Music.MUSIC_MAUSOLEUM, + [14] = Music.MUSIC_GEHENNA, + [15] = Music.MUSIC_WOMB_UTERO, + [16] = Music.MUSIC_UTERO, + [17] = Music.MUSIC_SCARRED_WOMB, + [18] = Music.MUSIC_CORPSE, + [19] = Music.MUSIC_BLUE_WOMB, + [20] = Music.MUSIC_SHEOL, + [21] = Music.MUSIC_CATHEDRAL, + [22] = Music.MUSIC_DARK_ROOM, + [23] = Music.MUSIC_CHEST, + [24] = Music.MUSIC_VOID, + --Music.MUSIC_MORTIS +} +local random_music_size = 25 + local function correctedTrackNum(n) if redirectmusicenum[n] then return redirectmusicenum[n] @@ -242,10 +272,14 @@ local previousgreedwave = 0 local previousbosscount = 0 local waitingforgamestjingle = true local satanfightstage = 0 +local ultragreedfightstage = 0 local doorprevvariants = {} local inbadstage = false local strangedoorstatebefore = DoorState.STATE_INIT local foundknifepiecebefore = false +local devildoorspawnedbefore = false +local angeldoorspawnedbefore = false +local dogmadeathjingledelay = 0 --this table is only for the start jingles, and for unlooped tracks that, under specific circumstances, need to continue playing upon entering a new room and retain the queued track local musicJingles = {} @@ -304,6 +338,14 @@ soundJingles[Music.MUSIC_JINGLE_SECRETROOM_FIND] = { soundJingles[Music.MUSIC_STRANGE_DOOR_JINGLE] = { ["id"] = Isaac.GetSoundIdByName("Strange Door Jingle"), } +soundJingles[Music.MUSIC_JINGLE_DEVILROOM_FIND] = { + ["id"] = SoundEffect.SOUND_SATAN_ROOM_APPEAR, + ["noVolumeChange"] = true, +} +soundJingles[Music.MUSIC_JINGLE_HOLYROOM_FIND] = { + ["id"] = SoundEffect.SOUND_CHOIR_UNLOCK, + ["noVolumeChange"] = true, +} local stageapiexists = false @@ -347,10 +389,6 @@ setmetatable(weakmusicmgr, weakmusicmgrfuncs) setmetatable(weakmusicmgrfuncs, overridemusicmgrfuncs) function MMC.ResetSave() - modSaveData["inmirrorroom"] = false - modSaveData["inmirroredworld"] = false - modSaveData["inmineroom"] = false - modSaveData["inmineshaft"] = false modSaveData["railcomplete"] = false modSaveData["deathcertificateroom"] = false modSaveData["darkhome"] = 0 @@ -385,6 +423,37 @@ local function getChapterMusic(floor_type, floor_variant, greed) return chapter[floor_variant] or chapter[StageType.STAGETYPE_ORIGINAL] or Music.MUSIC_TITLE_REPENTANCE end +local function getRandomStageMusic(seed) + return random_music[(seed % random_music_size)] +end + +local musicByBackdrop = { + [BackdropType.BASEMENT] = Music.MUSIC_BASEMENT, + [BackdropType.CELLAR] = Music.MUSIC_CELLAR, + [BackdropType.BURNT_BASEMENT] = Music.MUSIC_BURNING_BASEMENT, + [BackdropType.DOWNPOUR] = Music.MUSIC_DOWNPOUR, + [BackdropType.DROSS] = Music.MUSIC_DROSS, + [BackdropType.CAVES] = Music.MUSIC_CAVES, + [BackdropType.CATACOMBS] = Music.MUSIC_CATACOMBS, + [BackdropType.FLOODED_CAVES] = Music.MUSIC_FLOODED_CAVES, + [BackdropType.MINES] = Music.MUSIC_MINES, + [BackdropType.ASHPIT] = Music.MUSIC_ASHPIT, + [BackdropType.DEPTHS] = Music.MUSIC_DEPTHS, + [BackdropType.NECROPOLIS] = Music.MUSIC_NECROPOLIS, + [BackdropType.DANK_DEPTHS] = Music.MUSIC_DANK_DEPTHS, + [BackdropType.MAUSOLEUM] = Music.MUSIC_MAUSOLEUM, + [BackdropType.MAUSOLEUM2] = Music.MUSIC_MAUSOLEUM, + [BackdropType.MAUSOLEUM3] = Music.MUSIC_MAUSOLEUM, + [BackdropType.MAUSOLEUM4] = Music.MUSIC_MAUSOLEUM, + [BackdropType.GEHENNA] = Music.MUSIC_GEHENNA, + [BackdropType.WOMB] = Music.MUSIC_WOMB_UTERO, + [BackdropType.UTERO] = Music.MUSIC_UTERO, + [BackdropType.SCARRED_WOMB] = Music.MUSIC_SCARRED_WOMB, + [BackdropType.CORPSE] = Music.MUSIC_CORPSE, + [BackdropType.CORPSE2] = Music.MUSIC_CORPSE, + [BackdropType.CORPSE3] = Music.MUSIC_CORPSE, +} + --check for death certificate MusicModCallback:AddCallback(ModCallbacks.MC_USE_ITEM, function() modSaveData["deathcertificateroom"] = true @@ -395,10 +464,26 @@ local function getStageMusic() local level = game:GetLevel() local stage = level:GetStage() local stage_type = level:GetStageType() + local backdrop = Game():GetRoom():GetBackdropType() + + --play random music for "DELETE THIS" challenge + if Isaac.GetChallenge() == Challenge.CHALLENGE_DELETE_THIS then + local seeds = game:GetSeeds() + local stageseed = seeds:GetStageSeed(stage) + return getRandomStageMusic(stageseed) + end + + --change music when the room backdrop changes in "Red Redemption" challenge + if Isaac.GetChallenge() == Challenge.CHALLENGE_RED_REDEMPTION and musicByBackdrop[backdrop] then + --NOTE: this is incomplete + --TODO: specifically, we need a way to tell which music is supposed to play in special rooms that play the main stage music + --such as RoomType.ROOM_CURSE, RoomType.ROOM_DICE, etc. + return musicByBackdrop[backdrop] + end + --death certificate check if modSaveData["deathcertificateroom"] then - local backdrop = Game():GetRoom():GetBackdropType() - if (backdrop > 48 and backdrop < 55) or backdrop == 60 then + if backdrop == BackdropType.DARK_CLOSET or backdrop == BackdropType.CLOSET_B then return Music.MUSIC_DARK_CLOSET else modSaveData["deathcertificateroom"] = false @@ -411,11 +496,12 @@ local function getGenericBossMusic() local game = Game() local level = game:GetLevel() local stage_type = level:GetStageType() - local room = game:GetRoom() if stage_type == StageType.STAGETYPE_REPENTANCE or stage_type == StageType.STAGETYPE_REPENTANCE_B then return Music.MUSIC_BOSS3 else - if room:GetDecorationSeed() % 2 == 0 then + local roomdesc = level:GetCurrentRoomDesc() + local roomdescflags = roomdesc.Flags + if (roomdescflags & RoomDescriptor.FLAG_ALT_BOSS_MUSIC) == 0 then return Music.MUSIC_BOSS else return Music.MUSIC_BOSS2 @@ -427,11 +513,12 @@ local function getGenericBossDeathJingle() local game = Game() local level = game:GetLevel() local stage_type = level:GetStageType() - local room = game:GetRoom() if stage_type == StageType.STAGETYPE_REPENTANCE or stage_type == StageType.STAGETYPE_REPENTANCE_B then return Music.MUSIC_JINGLE_BOSS_OVER3 else - if room:GetDecorationSeed() % 2 == 0 then + local roomdesc = level:GetCurrentRoomDesc() + local roomdescflags = roomdesc.Flags + if (roomdescflags & RoomDescriptor.FLAG_ALT_BOSS_MUSIC) == 0 then return Music.MUSIC_JINGLE_BOSS_OVER else return Music.MUSIC_JINGLE_BOSS_OVER2 @@ -485,19 +572,20 @@ local function getMusicTrack() local stage = level:GetStage() local stagetype = level:GetStageType() local roomidx = level:GetCurrentRoomIndex() - local ascent = game:GetStateFlag(GameStateFlag.STATE_BACKWARDS_PATH) and stage <= 6 + local ascent = level:IsAscent() local inrepstage = stagetype == StageType.STAGETYPE_REPENTANCE or stagetype == StageType.STAGETYPE_REPENTANCE_B + local curseoflabyrinth = (level:GetCurses() & LevelCurse.CURSE_OF_LABYRINTH) == LevelCurse.CURSE_OF_LABYRINTH - if modSaveData["inmirroredworld"] and stage == LevelStage.STAGE1_2 and inrepstage then + if room:IsMirrorWorld() and (stage == LevelStage.STAGE1_2 or (stage == LevelStage.STAGE1_1 and curseoflabyrinth)) and inrepstage then if roomtype ~= RoomType.ROOM_BOSS then local stage_type = level:GetStageType() if stage_type == StageType.STAGETYPE_REPENTANCE then - return Music.MUSIC_DOWNPOUR_REVERSE --in vanilla, the reversed track is slowly faded in on top of the normal track (this is a low priority stretch goal) + return Music.MUSIC_DOWNPOUR_REVERSE --in vanilla, the reversed track for Downpour is slowly faded in on top of the normal track elseif stage_type == StageType.STAGETYPE_REPENTANCE_B then - return Music.MUSIC_DROSS_REVERSE + return Music.MUSIC_DROSS_REVERSE --but strangely, reversed Dross plays immediately end end - elseif modSaveData["inmineshaft"] and stage == LevelStage.STAGE2_2 and inrepstage then + elseif room:HasCurseMist() and (stage == LevelStage.STAGE2_2 or (stage == LevelStage.STAGE2_1 and curseoflabyrinth)) and inrepstage then if level:GetStateFlag(LevelStateFlag.STATE_MINESHAFT_ESCAPE) then --this flag doesn't seem to be set until leaving the room after Mom's Shadow spawns return Music.MUSIC_MINESHAFT_ESCAPE else @@ -507,8 +595,14 @@ local function getMusicTrack() return Music.MUSIC_DOGMA_BOSS end + --TODO: play Boss Over Twisted instead of Mom Fight music when fighting Mausoleum Mom when STATE_MAUSOLEUM_HEART_KILLED is true + --TODO: compare vanilla to MMC: check what happens if you rerun or use Forget Me Not and replay the Mineshaft Escape + --TODO: need to play Mineshaft Escape music in Crawlspace! Also, compare vanilla to MMC of Mineshaft Escape in Black Market + if ascent then return Music.MUSIC_REVERSE_GENESIS + elseif game:GetStateFlag(GameStateFlag.STATE_MAUSOLEUM_HEART_KILLED) and (stage == LevelStage.STAGE3_2 or (stage == LevelStage.STAGE3_1 and curseoflabyrinth)) and inrepstage and not (roomtype == RoomType.ROOM_BOSS and room:GetBossID() == 8) then + return Music.MUSIC_BOSS_OVER_TWISTED elseif roomtype == RoomType.ROOM_MINIBOSS or roomdesc.SurpriseMiniboss then if room:IsClear() then return Music.MUSIC_BOSS_OVER @@ -527,7 +621,7 @@ local function getMusicTrack() return getStageMusic() elseif roomtype == RoomType.ROOM_BOSS then if room:IsClear() then - if inrepstage and stage == LevelStage.STAGE3_2 then + if inrepstage and (stage == LevelStage.STAGE3_2 or (stage == LevelStage.STAGE3_1 and curseoflabyrinth)) then if game:GetStateFlag(GameStateFlag.STATE_MAUSOLEUM_HEART_KILLED) then if room:GetBossID() == 8 then return Music.MUSIC_NULL --No music plays here @@ -576,13 +670,13 @@ local function getMusicTrack() return Music.MUSIC_PLANETARIUM elseif roomtype == RoomType.ROOM_ULTRASECRET then return Music.MUSIC_SECRET_ROOM_ALT_ALT - elseif roomtype == (RoomType.ROOM_SECRET_EXIT or 27) then --RoomType.ROOM_SECRET_EXIT is not currently defined in enums.lua + elseif roomtype == RoomType.ROOM_SECRET_EXIT then return Music.MUSIC_BOSS_OVER --the rooms with the exits to the Repentance alt floors - elseif roomtype == RoomType.ROOM_DUNGEON and stage == LevelStage.STAGE8 and roomidx == -10 then + elseif roomtype == RoomType.ROOM_DUNGEON and stage == LevelStage.STAGE8 and roomidx == GridRooms.ROOM_SECRET_EXIT_IDX then return Music.MUSIC_BEAST_BOSS - elseif roomidx == -14 then + elseif roomidx == GridRooms.ROOM_ROTGUT_DUNGEON1_IDX then return getGenericBossMusic() - elseif roomidx == -15 then + elseif roomidx == GridRooms.ROOM_ROTGUT_DUNGEON2_IDX then return getGenericBossMusic() else return getStageMusic() @@ -647,13 +741,13 @@ local function iterateThroughCallbacks(track, isQueued) -- returns correct track return track end -function musicCrossfade(track, track2) +function musicCrossfade(track, track2) --TODO: make use of faderate argument? local replacedtrack2 = false local id, id2, jinglelength = iterateThroughCallbacks(track, false) if id2 then replacedtrack2 = true end id2 = id2 or track2 local faderate = 0.08 --0.08 is default in MusicManager.Crossfade - if modSaveData["inmirrorroom"] and mirrorSoundIsPlaying() and Game():GetLevel():GetStageType() == StageType.STAGETYPE_REPENTANCE then faderate = 0.01 end + if mirrorSoundIsPlaying() and Game():GetLevel():GetStageType() == StageType.STAGETYPE_REPENTANCE then faderate = 0.01 end if not id then return elseif id > 0 then @@ -723,8 +817,10 @@ function musicPlay(track, track2) --Isaac.DebugString("sfxid found to be "..tostring(sfxid)) if sfxid and sfxid > 0 then SFXManager():Play(sfxid,1,0,false,1) - soundJingleTimer = 145 --roughly 3 seconds - soundJingleVolume = false + if not soundJingles[track]["noVolumeChange"] then + soundJingleTimer = 145 --roughly 3 seconds + soundJingleVolume = false + end end id = -1 --musicmgr skips track 1, plays track 2 end @@ -829,6 +925,24 @@ function MusicModCallback:UpdateSaveValuesForNewFloor() MMC.ResetSave() end +function MusicModCallback:InitializeMusicJingles(isContinued) --this function kicks off the Game Start Jingle countdown + local currentMusicId = musicmgr:GetCurrentMusicID() + if currentMusicId == Music.MUSIC_JINGLE_GAME_START or currentMusicId == Music.MUSIC_JINGLE_GAME_START_ALT then + musicJingles[currentMusicId]["timeleft"] = musicJingles[currentMusicId]["length"] + + local room = Game():GetRoom() + if Isaac.GetChallenge() == Challenge.CHALLENGE_DELETE_THIS and not isContinued then + musicJingles[currentMusicId]["nexttrack"] = -1 --fadeout + elseif room:GetType() == RoomType.ROOM_BOSS and not room:IsClear() then + musicJingles[currentMusicId]["nexttrack"] = nil + else + waitingforgamestjingle = false --trick getMusicTrack into giving us the track early + musicJingles[currentMusicId]["nexttrack"] = getMusicTrack() + waitingforgamestjingle = true --Cyber: "This may not be good code, but I don't want to interfere with the check that Nato added to the beginning of getMusicTrack because I figure it is important for the Soundtrack Menu." + end + end +end + MusicModCallback:AddCallback(ModCallbacks.MC_POST_NEW_LEVEL, MusicModCallback.StageAPIcheck) MusicModCallback:AddCallback(ModCallbacks.MC_POST_NEW_LEVEL, MusicModCallback.UpdateSaveValuesForNewFloor) @@ -846,22 +960,7 @@ end) MusicModCallback:AddCallback(ModCallbacks.MC_POST_GAME_STARTED, MusicModCallback.StageAPIcheck) MusicModCallback:AddCallback(ModCallbacks.MC_POST_GAME_STARTED, MusicModCallback.LoadSaveData) - -MusicModCallback:AddCallback(ModCallbacks.MC_POST_GAME_STARTED, function() --this function kicks off the Game Start Jingle countdown - local currentMusicId = musicmgr:GetCurrentMusicID() - if currentMusicId == Music.MUSIC_JINGLE_GAME_START or currentMusicId == Music.MUSIC_JINGLE_GAME_START_ALT then - musicJingles[currentMusicId]["timeleft"] = musicJingles[currentMusicId]["length"] - - local room = Game():GetRoom() - if room:GetType() == RoomType.ROOM_BOSS and not room:IsClear() then - musicJingles[currentMusicId]["nexttrack"] = getBossMusic() - else - waitingforgamestjingle = false --trick getMusicTrack into giving us the track early - musicJingles[currentMusicId]["nexttrack"] = getMusicTrack() - waitingforgamestjingle = true --Cyber: "This may not be good code, but I don't want to interfere with the check that Nato added to the beginning of getMusicTrack because I figure it is important for the Soundtrack Menu." - end - end -end) +MusicModCallback:AddCallback(ModCallbacks.MC_POST_GAME_STARTED, MusicModCallback.InitializeMusicJingles) function MusicModCallback:PlayGameOverMusic(isGameOver) for i,v in pairs(musicJingles) do @@ -878,72 +977,73 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_NEW_ROOM, function() local room = Game():GetRoom() local roomtype = room:GetType() local level = Game():GetLevel() - local ascent = Game():GetStateFlag(GameStateFlag.STATE_BACKWARDS_PATH) and level:GetStage() <= 6 - - local previousinmirrorroom = modSaveData["inmirrorroom"] - modSaveData["inmirrorroom"] = false - - local previousinmineroom = modSaveData["inmineroom"] - modSaveData["inmineroom"] = false + local ascent = level:IsAscent() + local curseoflabyrinth = (level:GetCurses() & LevelCurse.CURSE_OF_LABYRINTH) == LevelCurse.CURSE_OF_LABYRINTH previousgreedwave = 0 previousbosscount = 0 satanfightstage = 0 + ultragreedfightstage = 0 challengeactivebefore = room:IsAmbushActive() challengedonebefore = room:IsAmbushDone() roomclearbefore = room:IsClear() + devildoorspawnedbefore = false + angeldoorspawnedbefore = false for i=0,7 do local door = room:GetDoor(i) if door then doorprevvariants[i] = door:GetVariant() - if door.TargetRoomIndex == -100 then --the Mirror - modSaveData["inmirrorroom"] = true - elseif door.TargetRoomIndex == -101 then --the door to the Mineshaft - modSaveData["inmineroom"] = true + if door.TargetRoomType == RoomType.ROOM_DEVIL then + devildoorspawnedbefore = true + end + if door.TargetRoomType == RoomType.ROOM_ANGEL then + angeldoorspawnedbefore = true end end end --initialize MINES/ASHPIT II RAIL BUTTONS upon discovery - if not modSaveData["railcomplete"] and level:GetStage() == LevelStage.STAGE2_2 and level:GetStageType() >= StageType.STAGETYPE_REPENTANCE then - for i = 1, room:GetGridSize() do - local gridentity = room:GetGridEntity(i) - if gridentity and gridentity:GetType() == GridEntityType.GRID_PRESSURE_PLATE and gridentity:GetVariant() == 3 then - --found a rail button - local roomidx = level:GetCurrentRoomDesc().SafeGridIndex - if modSaveData["railbuttons"][tostring(roomidx)] == nil then - modSaveData["railbuttons"][tostring(roomidx)] = false + if not modSaveData["railcomplete"] and (level:GetStage() == LevelStage.STAGE2_2 or (level:GetStage() == LevelStage.STAGE2_1 and curseoflabyrinth)) and level:GetStageType() >= StageType.STAGETYPE_REPENTANCE then + local roomdesc = level:GetCurrentRoomDesc() + local roomdescflags = roomdesc.Flags + if (roomdescflags & RoomDescriptor.FLAG_RED_ROOM) == 0 then + for i = 1, room:GetGridSize() do + local gridentity = room:GetGridEntity(i) + if gridentity and gridentity:GetType() == GridEntityType.GRID_PRESSURE_PLATE and gridentity:GetVariant() == 3 then + --found a rail button + local roomidx = roomdesc.SafeGridIndex + if modSaveData["railbuttons"][tostring(roomidx)] == nil then + modSaveData["railbuttons"][tostring(roomidx)] = false + end + break end - break end end end - if previousinmirrorroom and modSaveData["inmirrorroom"] then - --this isn't ideal, but I can't find a GameStateFlag or something similar for being in the mirrored world - if mirrorSoundIsPlaying() then - modSaveData["inmirroredworld"] = (not modSaveData["inmirroredworld"]) - end - elseif previousinmineroom and modSaveData["inmineroom"] then - --I'm concerned that a teleporting item or the D7 (restart room) could trigger this - modSaveData["inmineshaft"] = (not modSaveData["inmineshaft"]) - end - if not waitingforgamestjingle then --if we need to, we can stop music from playing in a new room local skipCrossfade = false - if modSaveData["inmineshaft"] and musicJingles[Music.MUSIC_MOTHERS_SHADOW_INTRO]["timeleft"] > 0 then + if room:HasCurseMist() and musicJingles[Music.MUSIC_MOTHERS_SHADOW_INTRO]["timeleft"] > 0 then skipCrossfade = true else musicJingles[Music.MUSIC_MOTHERS_SHADOW_INTRO]["timeleft"] = 0 end + local prevRoomType = RoomType.ROOM_NULL + if level.EnterDoor >= 0 then + local prevRoomDoor = room:GetDoor(level.EnterDoor) + if prevRoomDoor then + prevRoomType = prevRoomDoor.TargetRoomType + end + end + --NOTE: the room:IsClear() check handles back-to-back Bosses (i.e. XL floors) - if (roomtype == (RoomType.ROOM_SECRET_EXIT or 27) or (roomtype == RoomType.ROOM_BOSS and room:IsClear())) and (musicJingles[Music.MUSIC_JINGLE_BOSS_OVER]["timeleft"] > 0 or musicJingles[Music.MUSIC_JINGLE_BOSS_OVER2]["timeleft"] > 0 or musicJingles[Music.MUSIC_JINGLE_BOSS_OVER3]["timeleft"] > 0) then + if (roomtype == RoomType.ROOM_SECRET_EXIT or (roomtype == RoomType.ROOM_BOSS and room:IsClear() and (prevRoomType == RoomType.ROOM_SECRET_EXIT or prevRoomType == RoomType.ROOM_BOSS))) and (musicJingles[Music.MUSIC_JINGLE_BOSS_OVER]["timeleft"] > 0 or musicJingles[Music.MUSIC_JINGLE_BOSS_OVER2]["timeleft"] > 0 or musicJingles[Music.MUSIC_JINGLE_BOSS_OVER3]["timeleft"] > 0) then --Isaac.DebugString("skipping crossfade for Boss Room or Secret Exit Room") skipCrossfade = true else @@ -953,7 +1053,12 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_NEW_ROOM, function() end if not skipCrossfade then - musicCrossfade(getMusicTrack()) + local musictrack = getMusicTrack() + if musictrack == Music.MUSIC_BOSS_OVER_TWISTED then + musicPlay(musictrack) + else + musicCrossfade(musictrack) + end end else --if we change rooms while waiting for game start jingle, update "nexttrack" @@ -972,7 +1077,7 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_NEW_ROOM, function() --NOTE: moved treasure/sound jingle initalization to musicPlay --moved from getMusicTrack to here so we can use musicPlay instead of musicCrossfade - if room:GetType() == RoomType.ROOM_TREASURE and room:IsFirstVisit() and (Game():IsGreedMode() or level:GetStage() ~= LevelStage.STAGE4_3) and not modSaveData["inmirroredworld"] and not ascent then + if room:GetType() == RoomType.ROOM_TREASURE and room:IsFirstVisit() and (Game():IsGreedMode() or level:GetStage() ~= LevelStage.STAGE4_3) and not room:IsMirrorWorld() and not ascent then local rng = math.random(0,3) local treasurejingle if rng == 0 then @@ -989,25 +1094,35 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_NEW_ROOM, function() end end) -MusicModCallback:AddCallback(ModCallbacks.MC_POST_NPC_DEATH, function(self, ent) - if ent.Variant == 2 then +MusicModCallback:AddCallback(ModCallbacks.MC_POST_NPC_INIT, function(self, ent) + local room = Game():GetRoom() + local roomtype = room:GetType() + + if roomtype == RoomType.ROOM_BOSS and room:GetBossID() == 63 then musicCrossfade(Music.MUSIC_HUSH_BOSS) end -end, EntityType.ENTITY_ISAAC) +end, EntityType.ENTITY_HUSH) -MusicModCallback:AddCallback(ModCallbacks.MC_POST_NPC_DEATH, function() - modSaveData["darkhome"] = 5 -end, EntityType.ENTITY_DOGMA) +--[[MusicModCallback:AddCallback(ModCallbacks.MC_POST_NPC_DEATH, function() + modSaveData["darkhome"] = 5 +end, EntityType.ENTITY_DOGMA)]] function MusicModCallback:PlayDogmaOutro(entity) - local sprite = entity:GetSprite() - local anim = sprite:GetAnimation() - if anim == "Death" then - local frame = sprite:GetFrame() - if frame == 80 then - musicPlay(Music.MUSIC_JINGLE_DOGMA_OVER) - end - end + if entity.Variant ~= 1 and entity:IsDead() then + modSaveData["darkhome"] = 5 + end + --for whatever reason the NPC death doesn't trigger for dogma and + --this function stops running the same frame dogma starts the death + --animation so I moved the death jingle to post_render + + --[[local sprite = entity:GetSprite() + local anim = sprite:GetAnimation() + if anim == "Death" then + local frame = sprite:GetFrame() + if frame == 80 then + musicPlay(Music.MUSIC_JINGLE_DOGMA_OVER) + end + end]] end MusicModCallback:AddCallback(ModCallbacks.MC_NPC_UPDATE, MusicModCallback.PlayDogmaOutro, EntityType.ENTITY_DOGMA) @@ -1045,8 +1160,11 @@ MusicModCallback:AddCallback(ModCallbacks.MC_PRE_GAME_EXIT, function() previousgreedwave = 0 previousbosscount = 0 satanfightstage = 0 + ultragreedfightstage = 0 strangedoorstatebefore = DoorState.STATE_INIT foundknifepiecebefore = false + devildoorspawnedbefore = false + angeldoorspawnedbefore = false for i,v in pairs(musicJingles) do v["timeleft"] = 0 @@ -1074,6 +1192,7 @@ MusicModCallback:AddCallback(ModCallbacks.MC_EXECUTE_CMD, function(self, cmd, pa end end) +local NightmarePlaying = false MusicModCallback:AddCallback(ModCallbacks.MC_POST_RENDER, function() if inbadstage then return end @@ -1085,6 +1204,7 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_RENDER, function() local roomdesc = Game():GetLevel():GetCurrentRoomDesc() local currentMusicId = musicmgr:GetCurrentMusicID() local ispaused = Game():IsPaused() + local curseoflabyrinth = (level:GetCurses() & LevelCurse.CURSE_OF_LABYRINTH) == LevelCurse.CURSE_OF_LABYRINTH for i,v in pairs(musicJingles) do if v["timeleft"] > 0 then @@ -1096,11 +1216,14 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_RENDER, function() end if v["nexttrack"] then --Isaac.DebugString("nexttrack found to be "..tostring(v["nexttrack"])) - musicCrossfade(v["nexttrack"]) - else --failsafe for game start jingles, but nexttrack should be set for those, too - if room:GetType() == RoomType.ROOM_BOSS and not room:IsClear() then - musicCrossfade(getBossMusic()) + if v["nexttrack"] == Music.MUSIC_BOSS_OVER_TWISTED then + musicPlay(v["nexttrack"]) else + musicCrossfade(v["nexttrack"]) + end + v["nexttrack"] = nil + else --failsafe for game start jingles, but nexttrack should be set for those, too + if room:GetType() ~= RoomType.ROOM_BOSS or room:IsClear() then musicCrossfade(getMusicTrack()) end end @@ -1112,6 +1235,13 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_RENDER, function() if room:GetFrameCount() < 10 and (currentMusicId == Music.MUSIC_JINGLE_GAME_START or currentMusicId == Music.MUSIC_JINGLE_GAME_START_ALT) then waitingforgamestjingle = true end + + --dont mess with epiphany menu music + if Epiphany and (Epiphany.character_menu_visible or currentMusicId == Epiphany.MENU_MUSIC) then + waitingforgamestjingle = false + musicJingles[Music.MUSIC_JINGLE_GAME_START]["timeleft"] = 0 + musicJingles[Music.MUSIC_JINGLE_GAME_START_ALT]["timeleft"] = 0 + end --upon reset, play new music immediately if waitingforgamestjingle and (currentMusicId ~= Music.MUSIC_JINGLE_GAME_START and currentMusicId ~= Music.MUSIC_JINGLE_GAME_START_ALT) then @@ -1142,7 +1272,10 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_RENDER, function() end --this is necessary for the music to play after Dream Catcher animations - if currentMusicId == Music.MUSIC_JINGLE_NIGHTMARE and Game():GetHUD():IsVisible() then + if (currentMusicId == Music.MUSIC_JINGLE_NIGHTMARE or (NightmarePlaying and NightmareScene.IsDogmaNightmare() == false)) and Game():GetHUD():IsVisible() then + for i,v in pairs(musicJingles) do + v["timeleft"] = 0 + end musicCrossfade(getStageMusic()) end @@ -1170,31 +1303,48 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_RENDER, function() local currentbosscount = Isaac.CountBosses() if currentbosscount > 0 and room:GetFrameCount() == 1 then + ultragreedfightstage = 0 satanfightstage = 0 musicCrossfade(getBossMusic()) end if room:GetFrameCount() > 1 then if room:GetBossID() == 62 then - if satanfightstage == 0 then + if ultragreedfightstage == 0 then if currentbosscount == 0 then - satanfightstage = 2 + ultragreedfightstage = 2 musicCrossfade(Music.MUSIC_BOSS_OVER) return end - satanfightstage = 1 + ultragreedfightstage = 1 musicCrossfade(getBossMusic()) - elseif satanfightstage == 1 then + elseif ultragreedfightstage == 1 then for i,v in ipairs(Isaac.GetRoomEntities()) do if v.Type == EntityType.ENTITY_ULTRA_GREED then if v:ToNPC().State == 9001 then - satanfightstage = 2 + ultragreedfightstage = 2 musicCrossfade(Music.MUSIC_BOSS_OVER) --don't play boss over jingle for Ultra Greed end break end end end + elseif room:GetBossID() == 55 then --for the "Mega Satan in Greed Mode" mod + if satanfightstage == 0 and room:GetFrameCount() > 10 and not room:IsClear() then + local playertable = Isaac.FindByType(EntityType.ENTITY_PLAYER,0) --variant 0 is true players, i.e. not co-op babies + for i,entity in pairs(playertable) do + --TODO: check Soul of the Forgotten? + local tempPlayer = entity:ToPlayer() + if tempPlayer and tempPlayer.Position.Y < 540 then + musicCrossfade(Music.MUSIC_SATAN_BOSS) + satanfightstage = 3 + break + end + end + end + if currentbosscount == 0 and previousbosscount > 0 then --this doesn't actually occur for the "Mega Satan in Greed Mode" mod without this mod's help + musicCrossfade(getGenericBossDeathJingle(), Music.MUSIC_BOSS_OVER) + end else --the room right before Ultra Greed's room if currentbosscount > 0 and previousbosscount == 0 then musicCrossfade(getGenericBossMusic()) @@ -1284,6 +1434,7 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_RENDER, function() if satanfightstage == 0 and room:GetFrameCount() > 10 and not room:IsClear() then local playertable = Isaac.FindByType(EntityType.ENTITY_PLAYER,0) --variant 0 is true players, i.e. not co-op babies for i,entity in pairs(playertable) do + --TODO: check Soul of the Forgotten? local tempPlayer = entity:ToPlayer() if tempPlayer and tempPlayer.Position.Y < 540 then musicCrossfade(Music.MUSIC_SATAN_BOSS) @@ -1296,19 +1447,31 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_RENDER, function() satanfightstage = 0 end end + + --check for ultra greed outside of greed mode + if currentbosscount == 0 and room:GetBossID() == 62 and Game().Difficulty == 1 then + local ultragreeds = Isaac.FindByType(406) + if #ultragreeds > 0 then + if ultragreeds[1]:ToNPC().State < 9001 then + currentbosscount = 1 + end + end + end if currentbosscount == 0 and previousbosscount > 0 then - if level:GetStage() == LevelStage.STAGE3_2 and room:GetBossID() == 8 and level:GetStageType() >= StageType.STAGETYPE_REPENTANCE then + if (level:GetStage() == LevelStage.STAGE3_2 or (level:GetStage() == LevelStage.STAGE3_1 and curseoflabyrinth)) and room:GetBossID() == 8 and level:GetStageType() >= StageType.STAGETYPE_REPENTANCE then musicCrossfade(Music.MUSIC_NULL) + elseif (level:GetStage() == LevelStage.STAGE4_2 or (level:GetStage() == LevelStage.STAGE4_1 and curseoflabyrinth)) and room:GetBossID() == 88 and level:GetStageType() >= StageType.STAGETYPE_REPENTANCE then + musicCrossfade(Music.MUSIC_JINGLE_MOTHER_OVER, Music.MUSIC_BOSS_OVER) else musicCrossfade(getGenericBossDeathJingle(), Music.MUSIC_BOSS_OVER) end end previousbosscount = currentbosscount - elseif (level:GetStage() == LevelStage.STAGE3_2 or (level:GetStage() == LevelStage.STAGE3_1 and (level:GetCurses() & LevelCurse.CURSE_OF_LABYRINTH) == LevelCurse.CURSE_OF_LABYRINTH)) and level:GetStageType() < StageType.STAGETYPE_REPENTANCE then + elseif (level:GetStage() == LevelStage.STAGE3_2 or (level:GetStage() == LevelStage.STAGE3_1 and curseoflabyrinth)) and level:GetStageType() < StageType.STAGETYPE_REPENTANCE then local topDoor = room:GetDoor(DoorSlot.UP0) - if topDoor and topDoor.TargetRoomType == (RoomType.ROOM_SECRET_EXIT or 27) then + if topDoor and topDoor.TargetRoomType == RoomType.ROOM_SECRET_EXIT then local strangedoorstatenow = topDoor.State if strangedoorstatebefore == DoorState.STATE_CLOSED and strangedoorstatenow == DoorState.STATE_OPEN then @@ -1317,7 +1480,7 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_RENDER, function() strangedoorstatebefore = strangedoorstatenow end - elseif modSaveData["inmineshaft"] then + elseif room:HasCurseMist() then local foundknifepiecenow local knifetable = Isaac.FindByType(EntityType.ENTITY_PICKUP,PickupVariant.PICKUP_COLLECTIBLE,CollectibleType.COLLECTIBLE_KNIFE_PIECE_2) @@ -1337,10 +1500,15 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_RENDER, function() --PLAY TWISTED HOME MUSIC AFTER SLEEPING IN MOM'S BED if level:GetStage() == LevelStage.STAGE8 then - if modSaveData["darkhome"] == 0 and currentMusicId == Music.MUSIC_JINGLE_NIGHTMARE_ALT then + local dogmaNightmare = false + --check for dogma nightmare even if the music has been replaced + if currentMusicId == Music.MUSIC_JINGLE_NIGHTMARE_ALT or (NightmarePlaying and NightmareScene.IsDogmaNightmare()) then + dogmaNightmare = true + end + if modSaveData["darkhome"] == 0 and dogmaNightmare then modSaveData["darkhome"] = 1 end - if modSaveData["darkhome"] == 1 and currentMusicId == Music.MUSIC_JINGLE_NIGHTMARE_ALT and Game():GetHUD():IsVisible() then + if modSaveData["darkhome"] == 1 and dogmaNightmare and Game():GetHUD():IsVisible() then musicPlay(Music.MUSIC_ISAACS_HOUSE) modSaveData["darkhome"] = 2 end @@ -1363,33 +1531,46 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_RENDER, function() modSaveData["darkhome"] = 4 end --darkhome is set to 5 after killing Dogma + --PLAY DOGMA DEATH JINGLE + if modSaveData["darkhome"] == 5 then + dogmadeathjingledelay = dogmadeathjingledelay - 1 + if dogmadeathjingledelay == 0 then + musicPlay(Music.MUSIC_JINGLE_DOGMA_OVER) + modSaveData["darkhome"] = 6 + end + else + dogmadeathjingledelay = 170 + end end --MINES/ASHPIT II RAIL BUTTONS - if not modSaveData["railcomplete"] and level:GetStage() == LevelStage.STAGE2_2 and level:GetStageType() >= StageType.STAGETYPE_REPENTANCE then - for i = 1, room:GetGridSize() do - local gridentity = room:GetGridEntity(i) - if gridentity and gridentity:GetType() == GridEntityType.GRID_PRESSURE_PLATE and gridentity:GetVariant() == 3 then - --found a rail button (variant 3) - local railbuttonpressednow = (gridentity.State == 3) --State 3 is pressed - local roomidx = level:GetCurrentRoomDesc().SafeGridIndex - if railbuttonpressednow and not modSaveData["railbuttons"][tostring(roomidx)] then - modSaveData["railbuttons"][tostring(roomidx)] = true - - --now check if this is the third button pressed - local numrailbuttonspressed = 0 - for j,v in pairs(modSaveData["railbuttons"]) do - if v then - numrailbuttonspressed = numrailbuttonspressed + 1 + if not modSaveData["railcomplete"] and (level:GetStage() == LevelStage.STAGE2_2 or (level:GetStage() == LevelStage.STAGE2_1 and curseoflabyrinth)) and level:GetStageType() >= StageType.STAGETYPE_REPENTANCE then + local roomdescflags = roomdesc.Flags + if (roomdescflags & RoomDescriptor.FLAG_RED_ROOM) == 0 then + for i = 1, room:GetGridSize() do + local gridentity = room:GetGridEntity(i) + if gridentity and gridentity:GetType() == GridEntityType.GRID_PRESSURE_PLATE and gridentity:GetVariant() == 3 then + --found a rail button (variant 3) + local railbuttonpressednow = (gridentity.State == 3) --State 3 is pressed + local roomidx = roomdesc.SafeGridIndex + if railbuttonpressednow and not modSaveData["railbuttons"][tostring(roomidx)] then + modSaveData["railbuttons"][tostring(roomidx)] = true + + --now check if this is the third button pressed + local numrailbuttonspressed = 0 + for j,v in pairs(modSaveData["railbuttons"]) do + if v then + numrailbuttonspressed = numrailbuttonspressed + 1 + end + end + + if numrailbuttonspressed == 3 then + musicPlay(Music.MUSIC_JINGLE_SECRETROOM_FIND, getMusicTrack()) + modSaveData["railcomplete"] = true end end - - if numrailbuttonspressed == 3 then - musicPlay(Music.MUSIC_JINGLE_SECRETROOM_FIND, getMusicTrack()) - modSaveData["railcomplete"] = true - end + break end - break end end end @@ -1418,6 +1599,26 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_RENDER, function() end end + for i=0,7 do + local door = room:GetDoor(i) + if door then + local devildoorspawnednow = (door.TargetRoomType == RoomType.ROOM_DEVIL) + local angeldoorspawnednow = (door.TargetRoomType == RoomType.ROOM_ANGEL) + + --TODO: prevent these sound effects from playing when generating with Red Key? + if devildoorspawnednow and not devildoorspawnedbefore then + SFXManager():Stop(soundJingles[Music.MUSIC_JINGLE_DEVILROOM_FIND]["id"]) + musicPlay(Music.MUSIC_JINGLE_DEVILROOM_FIND, Music.MUSIC_NULL) + devildoorspawnedbefore = devildoorspawnednow + end + if angeldoorspawnednow and not angeldoorspawnedbefore then + SFXManager():Stop(soundJingles[Music.MUSIC_JINGLE_HOLYROOM_FIND]["id"]) + musicPlay(Music.MUSIC_JINGLE_HOLYROOM_FIND, Music.MUSIC_NULL) + angeldoorspawnedbefore = angeldoorspawnednow + end + end + end + for i=0,7 do local door = room:GetDoor(i) if door then @@ -1427,8 +1628,14 @@ MusicModCallback:AddCallback(ModCallbacks.MC_POST_RENDER, function() challengedonebefore = challengedonenow challengeactivebefore = challengeactivenow roomclearbefore = roomclearnow + if NightmarePlaying then NightmarePlaying = false end end) - +if REPENTOGON then + MusicModCallback:AddCallback(ModCallbacks.MC_POST_NIGHTMARE_SCENE_RENDER, function() + NightmarePlaying = true + end) +end + MMC.GetMusicTrack = getMusicTrack MMC.GetBossTrack = getBossMusic MMC.GetStageTrack = getStageMusic