From 12a48b9b86df7cf7265ddd9d7834440cad1f351c Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 26 Feb 2024 23:18:56 -0500 Subject: [PATCH 01/15] Work in progress on revamped, more extensible pause menu. --- .../tweens/misc/BackgroundColorTween.hx | 66 ++ source/funkin/import.hx | 1 + source/funkin/play/PauseSubState.hx | 574 ++++++++++-------- source/funkin/util/tools/FlxTweenTools.hx | 25 + 4 files changed, 408 insertions(+), 258 deletions(-) create mode 100644 source/flixel/tweens/misc/BackgroundColorTween.hx create mode 100644 source/funkin/util/tools/FlxTweenTools.hx diff --git a/source/flixel/tweens/misc/BackgroundColorTween.hx b/source/flixel/tweens/misc/BackgroundColorTween.hx new file mode 100644 index 000000000..b8b2d993f --- /dev/null +++ b/source/flixel/tweens/misc/BackgroundColorTween.hx @@ -0,0 +1,66 @@ +package flixel.tweens.misc; + +/** + * Tweens the background color of a state. + * Tweens the red, green, blue, and/or alpha values of the color. + * + * @see `flixel.tweens.misc.ColorTween` for something that operates on sprites! + */ +class BackgroundColorTween extends FlxTween +{ + public var color(default, null):FlxColor; + + var startColor:FlxColor; + var endColor:FlxColor; + + /** + * State object whose color to tween + */ + public var targetState(default, null):FlxState; + + /** + * Clean up references + */ + override public function destroy() + { + super.destroy(); + targetState = null; + } + + /** + * Tweens the color to a new color and an alpha to a new alpha. + * + * @param duration Duration of the tween. + * @param fromColor Start color. + * @param toColor End color. + * @param targetState Optional sprite object whose color to tween. + * @return The tween for chaining. + */ + public function tween(duration:Float, fromColor:FlxColor, toColor:FlxColor, ?targetState:FlxSprite):ColorTween + { + this.color = startColor = fromColor; + this.endColor = toColor; + this.duration = duration; + this.targetState = targetState; + this.start(); + return this; + } + + override function update(elapsed:Float):Void + { + super.update(elapsed); + color = FlxColor.interpolate(startColor, endColor, scale); + + if (targetState != null) + { + targetState.bgColor = color; + // Alpha should apply inherently. + // targetState.alpha = color.alphaFloat; + } + } + + override function isTweenOf(object:Dynamic, ?field:String):Bool + { + return targetState == object && (field == null || field == "color"); + } +} diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 02055d4ed..23b588044 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -15,6 +15,7 @@ using funkin.util.tools.ArraySortTools; using funkin.util.tools.ArrayTools; using funkin.util.tools.DynamicTools; using funkin.util.tools.FloatTools; +using funkin.util.tools.FlxTweenTools; using funkin.util.tools.Int64Tools; using funkin.util.tools.IntTools; using funkin.util.tools.IteratorTools; diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 023b8d5be..8300ac936 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -1,312 +1,370 @@ package funkin.play; -import funkin.play.PlayStatePlaylist; -import flixel.FlxSprite; -import flixel.addons.transition.FlxTransitionableState; -import flixel.group.FlxGroup.FlxTypedGroup; -import funkin.ui.MusicBeatSubState; -import flixel.sound.FlxSound; -import flixel.text.FlxText; -import flixel.tweens.FlxEase; -import flixel.tweens.FlxTween; -import flixel.util.FlxColor; -import funkin.play.PlayState; -import funkin.data.song.SongRegistry; -import funkin.ui.Alphabet; +typedef PauseSubStateParams = +{ + ?mode:PauseMode, +}; +/** + * The menu displayed when the Play State is paused. + */ class PauseSubState extends MusicBeatSubState { - var grpMenuShit:FlxTypedGroup; - - final pauseOptionsBase:Array = [ - 'Resume', - 'Restart Song', - 'Change Difficulty', - 'Toggle Practice Mode', - 'Exit to Menu' + static final PAUSE_MENU_ENTRIES_STANDARD = [ + {text: 'Resume', callback: resume}, + {text: 'Restart Song', callback: restartPlayState}, + {text: 'Change Difficulty', callback: switchMode.bind(_, Difficulty)}, + {text: 'Enable Practice Mode', callback: enablePracticeMode, filter: () -> (PlayState.instance?.isPracticeMode ?? true)}, + {text: 'Exit to Menu', callback: quitToMenu}, ]; - final pauseOptionsCharting:Array = ['Resume', 'Restart Song', 'Exit to Chart Editor']; - final pauseOptionsDifficultyBase:Array = ['BACK']; + static final PAUSE_MENU_ENTRIES_CHARTING = [ + {text: 'Resume', callback: resume}, + {text: 'Restart Song', callback: restartPlayState}, + {text: 'Return to Chart Editor', callback: quitToChartEditor}, + ]; - var pauseOptionsDifficulty:Array = []; // AUTO-POPULATED + static final PAUSE_MENU_ENTRIES_DIFFICULTY = [ + {text: 'Back', callback: switchMode.bind(_, Standard)} + // Other entries are added dynamically. + ]; - var menuItems:Array = []; - var curSelected:Int = 0; + static final PAUSE_MENU_ENTRIES_CUTSCENE = [ + {text: 'Resume', callback: resume}, + {text: 'Restart Cutscene', callback: restartCutscene}, + {text: 'Skip Cutscene', callback: skipCutscene}, + {text: 'Exit to Menu', callback: quitToMenu}, + ]; - var pauseMusic:FlxSound; + static final MUSIC_FADE_IN_TIME:Float = 50; + static final MUSIC_FINAL_VOLUME:Float = 0.5; - var practiceText:FlxText; + public static var musicSuffix:String = ''; - public var exitingToMenu:Bool = false; + // Status + var menuEntries:Array; + var currentEntry:Int = 0; + var currentMode:PauseMode; + var allowInput:Bool = true; - var bg:FlxSprite; - var metaDataGrp:FlxTypedGroup; + // Graphics + var metadata:FlxTypedGroup; + var metadataPractice:FlxText; + var menuEntryText:FlxTypedGroup; - var isChartingMode:Bool; + // Audio + var pauseMusic:FunkinSound; - public function new(isChartingMode:Bool = false) + public function new(?params:PauseSubStateParams) { super(); + this.currentMode = params?.mode ?? PauseMode.Standard; - this.isChartingMode = isChartingMode; - - menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase; - var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation); - trace('DIFFICULTIES: ${difficultiesInVariation}'); - - pauseOptionsDifficulty = difficultiesInVariation.map(function(item:String):String { - return item.toUpperCase(); - }).concat(pauseOptionsDifficultyBase); - - if (PlayStatePlaylist.campaignId == 'week6') - { - pauseMusic = new FlxSound().loadEmbedded(Paths.music('breakfast-pixel'), true, true); - } - else - { - pauseMusic = new FlxSound().loadEmbedded(Paths.music('breakfast'), true, true); - } - pauseMusic.volume = 0; - pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2))); - - FlxG.sound.list.add(pauseMusic); - - bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); - bg.alpha = 0; - bg.scrollFactor.set(); - add(bg); - - metaDataGrp = new FlxTypedGroup(); - add(metaDataGrp); - - var levelInfo:FlxText = new FlxText(20, 15, 0, '', 32); - if (PlayState.instance.currentChart != null) - { - levelInfo.text += '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}'; - } - levelInfo.scrollFactor.set(); - levelInfo.setFormat(Paths.font('vcr.ttf'), 32); - levelInfo.updateHitbox(); - metaDataGrp.add(levelInfo); - - var levelDifficulty:FlxText = new FlxText(20, 15 + 32, 0, '', 32); - levelDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase(); - levelDifficulty.scrollFactor.set(); - levelDifficulty.setFormat(Paths.font('vcr.ttf'), 32); - levelDifficulty.updateHitbox(); - metaDataGrp.add(levelDifficulty); - - var deathCounter:FlxText = new FlxText(20, 15 + 64, 0, '', 32); - deathCounter.text = 'Blue balled: ${PlayState.instance.deathCounter}'; - FlxG.watch.addQuick('totalNotesHit', Highscore.tallies.totalNotesHit); - FlxG.watch.addQuick('totalNotes', Highscore.tallies.totalNotes); - deathCounter.scrollFactor.set(); - deathCounter.setFormat(Paths.font('vcr.ttf'), 32); - deathCounter.updateHitbox(); - metaDataGrp.add(deathCounter); - - practiceText = new FlxText(20, 15 + 64 + 32, 0, 'PRACTICE MODE', 32); - practiceText.scrollFactor.set(); - practiceText.setFormat(Paths.font('vcr.ttf'), 32); - practiceText.updateHitbox(); - practiceText.x = FlxG.width - (practiceText.width + 20); - practiceText.visible = PlayState.instance.isPracticeMode; - metaDataGrp.add(practiceText); - - levelDifficulty.alpha = 0; - levelInfo.alpha = 0; - deathCounter.alpha = 0; - - levelInfo.x = FlxG.width - (levelInfo.width + 20); - levelDifficulty.x = FlxG.width - (levelDifficulty.width + 20); - deathCounter.x = FlxG.width - (deathCounter.width + 20); - - FlxTween.tween(bg, {alpha: 0.6}, 0.4, {ease: FlxEase.quartInOut}); - FlxTween.tween(levelInfo, {alpha: 1, y: 20}, 0.4, {ease: FlxEase.quartInOut, startDelay: 0.3}); - FlxTween.tween(levelDifficulty, {alpha: 1, y: levelDifficulty.y + 5}, 0.4, {ease: FlxEase.quartInOut, startDelay: 0.5}); - FlxTween.tween(deathCounter, {alpha: 1, y: deathCounter.y + 5}, 0.4, {ease: FlxEase.quartInOut, startDelay: 0.7}); - - grpMenuShit = new FlxTypedGroup(); - add(grpMenuShit); - - regenMenu(); - - // cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; + this.bgColor = FlxColor.TRANSPARENT; // Transparent, fades into black later. } - function regenMenu():Void + public override function create():Void { - while (grpMenuShit.members.length > 0) - { - grpMenuShit.remove(grpMenuShit.members[0], true); - } + super.create(); - for (i in 0...menuItems.length) - { - var songText:Alphabet = new Alphabet(0, (70 * i) + 30, menuItems[i], true, false); - songText.isMenuItem = true; - songText.targetY = i; - grpMenuShit.add(songText); - } + startPauseMusic(); - curSelected = 0; - changeSelection(); + buildMetadata(); + + transitionIn(); } - override function update(elapsed:Float):Void + public override function update(elapsed:Float):Void { - if (pauseMusic.volume < 0.5) pauseMusic.volume += 0.01 * elapsed; - super.update(elapsed); handleInputs(); } + function startPauseMusic():Void + { + pauseMusic = FunkinSound.load(Paths.music('breakfast-pixel'), true, true); + + // Start playing at a random point in the song. + pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2))); + pauseMusic.fadeIn(MUSIC_FADE_IN_TIME, 0, MUSIC_FINAL_VOLUME); + } + + /** + * Render the metadata in the top right. + */ + function buildMetadata():Void + { + metadata = new FlxTypedGroup(); + add(metadata); + + var metadataSong:FlxText = new FlxText(20, 15, 0, 'Song Name - Artist'); + metadataSong.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); + if (PlayState.instance?.currentChart != null) + { + metadataSong.text += '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}'; + } + metadata.add(metadataSong); + + var metadataDifficulty:FlxText = new FlxText(20, 15 + 32, 0, 'Difficulty'); + metadataDifficulty.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); + if (PlayState.instance?.currentDifficulty != null) + { + metadataDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase(); + } + metadata.add(metadataDifficulty); + + var metadataDeaths:FlxText = new FlxText(20, 15 + 64, 0, '0 Blue Balls'); + metadataDeaths.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); + metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls'; + metadata.add(metadataDeaths); + + metadataPractice = new FlxText(20, 15 + 96, 0, 'PRACTICE MODE'); + metadataPractice.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); + metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; + metadata.add(metadataPractice); + } + + function regenerateMenu(?targetMode:PauseMode):Void + { + var previousMode:PauseMode = this.currentMode; + this.currentMode = targetMode ?? this.currentMode; + this.currentEntry = 0; + + menuEntryText.clear(); + + // Choose the correct menu entries. + switch (this.currentMode) + { + case PauseMode.Standard: + currentMenuEntries = PAUSE_MENU_ENTRIES_STANDARD.clone(); + case PauseMode.Charting: + currentMenuEntries = PAUSE_MENU_ENTRIES_CHARTING.clone(); + case PauseMode.Difficulty: + // Prepend the difficulties. + var entries:Array = []; + if (PlayState.instance.currentChart != null) + { + var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation); + trace('DIFFICULTIES: ${difficultiesInVariation}'); + for (difficulty in difficultiesInVariation) + { + difficulties.push({text: difficulty.toTitleCase(), callback: () -> changeDifficulty(this, difficulty)}); + } + } + + // Add the back button. + currentMenuEntries = entries.concat(PAUSE_MENU_ENTRIES_DIFFICULTY.clone()); + case PauseMode.Cutscene: + currentMenuEntries = PAUSE_MENU_ENTRIES_CUTSCENE.clone(); + } + + // Render out the entries depending on the mode. + for (entryIndex in 0...entries) + { + var entry:PauseMenuEntry = entries[entryIndex]; + + // Remove entries that should be hidden. + if (entry.filter != null && !entry.filter()) currentMenuEntries.remove(entry); + + var yPos:Float = 70 * entryIndex + 30; + var text:AtlasText = new AtlasText(0, yPos, entry.text, AtlasFont.BOLD); + text.alpha = 0; + menuEntryText.add(text); + + entry.sprite = text; + } + + metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; + + changeSelection(); + } + + function transitionIn():Void + { + FlxTween.globalManager.bgColor(this, 0.4, FlxColor.fromRGB(0, 0, 0, 0.0), FlxColor.fromRGB(0, 0, 0, 0.6), {ease: FlxEase.quartInOut}); + + // Animate each element a little bit downwards. + var delay:Float = 0.3; + for (child in metadata.members) + { + FlxTween.tween(child, {alpha: 1, y: child.y + 5}, 0.4, {ease: FlxEase.quartInOut, startDelay: delay}); + delay += 0.2; + } + } + function handleInputs():Void { - var upP = controls.UI_UP_P; - var downP = controls.UI_DOWN_P; - var accepted = controls.ACCEPT; + if (!allowInput) return; + + if (controls.UI_UP_P) + { + changeSelection(-1); + } + if (controls.UI_DOWN_P) + { + changeSelection(1); + } + if (controls.PAUSE) + { + resume(this); + } + if (controls.ACCEPT) + { + menuEntries[currentEntry].callback(this); + } #if (debug || FORCE_DEBUG_VERSION) // to pause the game and get screenshots easy, press H on pause menu! if (FlxG.keys.justPressed.H) { - bg.visible = !bg.visible; - grpMenuShit.visible = !grpMenuShit.visible; - metaDataGrp.visible = !metaDataGrp.visible; + var visible = !metaDataGrp.visible; + metadata = visible; + menuEntryText = visible; + this.bgColor = visible ? 0x99000000 : 0x00000000; // 60% or fully transparent black } #end - - if (!exitingToMenu) - { - if (upP) - { - changeSelection(-1); - } - if (downP) - { - changeSelection(1); - } - - var androidPause:Bool = false; - - #if android - androidPause = FlxG.android.justPressed.BACK; - #end - - if (androidPause) close(); - - if (accepted) - { - var daSelected:String = menuItems[curSelected]; - - switch (daSelected) - { - case 'Resume': - close(); - - case 'Change Difficulty': - menuItems = pauseOptionsDifficulty; - regenMenu(); - - case 'Toggle Practice Mode': - PlayState.instance.isPracticeMode = true; - practiceText.visible = PlayState.instance.isPracticeMode; - - case 'Restart Song': - PlayState.instance.needsReset = true; - close(); - - case 'Exit to Menu': - exitingToMenu = true; - PlayState.instance.deathCounter = 0; - - for (item in grpMenuShit.members) - { - item.targetY = -3; - item.alpha = 0.6; - } - - FlxTransitionableState.skipNextTransIn = true; - FlxTransitionableState.skipNextTransOut = true; - - if (PlayStatePlaylist.isStoryMode) - { - PlayStatePlaylist.reset(); - openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker))); - } - else - { - openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(null, sticker))); - } - - case 'Exit to Chart Editor': - this.close(); - if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position! - PlayState.instance.close(); // This only works because PlayState is a substate! - - case 'BACK': - menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase; - regenMenu(); - - default: - if (pauseOptionsDifficulty.contains(daSelected)) - { - PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase()); - - // Reset campaign score when changing difficulty - // So if you switch difficulty on the last song of a week you get a really low overall score. - PlayStatePlaylist.campaignScore = 0; - PlayStatePlaylist.campaignDifficulty = daSelected.toLowerCase(); - PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty; - - PlayState.instance.needsReset = true; - - close(); - } - else - { - trace('[WARN] Unhandled pause menu option: ${daSelected}'); - } - } - } - - if (FlxG.keys.justPressed.J) - { - // for reference later! - // PlayerSettings.player1.controls.replaceBinding(Control.LEFT, Keys, FlxKey.J, null); - } - } - } - - override function destroy():Void - { - pauseMusic.destroy(); - - super.destroy(); } function changeSelection(change:Int = 0):Void { FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); - curSelected += change; + currentEntry += change; - if (curSelected < 0) curSelected = menuItems.length - 1; - if (curSelected >= menuItems.length) curSelected = 0; + if (currentEntry < 0) currentEntry = menuEntries.length - 1; + if (currentEntry >= menuEntries.length) currentEntry = 0; - for (index => item in grpMenuShit.members) + for (entryIndex in 0...menuEntries.length) { - item.targetY = index - curSelected; + var isCurrent:Bool = entryIndex == currentEntry; - item.alpha = 0.6; + var entry:PauseMenuEntry = menuEntries[entryIndex]; + var text:AtlasText = entry.sprite; - if (item.targetY == 0) - { - item.alpha = 1; - } + // Set the transparency. + text.alpha = isCurrent ? 1.0 : 0.6; + + // Set the position. + var targetX = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 20 + 90; + var targetY = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 120 + (FlxG.height * 0.48); + FlxTween.tween(text, {x: targetX, y: targetY}, 0.16, {ease: FlxEase.linear}); } } + + // =============== + // Menu Callbacks + // =============== + static function resume(state:PauseSubState):Void + { + state.close(); + } + + static function switchMode(state:PauseSubState, targetMode:PauseMode):Void + { + state.regenerateMenu(targetMode); + } + + static function changeDifficulty(state:PauseSubState, difficulty:String):Void + { + PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase()); + + // Reset campaign score when changing difficulty + // So if you switch difficulty on the last song of a week you get a really low overall score. + PlayStatePlaylist.campaignScore = 0; + PlayStatePlaylist.campaignDifficulty = difficulty; + PlayState.instance.currentDifficulty = PlayStatePlaylist.campaignDifficulty; + + PlayState.instance.needsReset = true; + + state.close(); + } + + static function restartPlayState(state:PauseSubState):Void + { + PlayState.instance.needsReset = true; + state.close(); + } + + static function enablePracticeMode(state:PauseSubState):Void + { + if (PlayState.instance == null) return; + + PlayState.instance.isPracticeMode = true; + regenerateMenu(); + } + + static function quitToMenu(state:PauseSubState):Void + { + state.allowInput = false; + + PlayState.instance.deathCounter = 0; + + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; + + if (PlayStatePlaylist.isStoryMode) + { + PlayStatePlaylist.reset(); + openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker))); + } + else + { + openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(null, sticker))); + } + } + + static function quitToChartEditor(state:PauseSubState):Void + { + state.close(); + if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position! + PlayState.instance.close(); // This only works because PlayState is a substate! + } + + /** + * Reset the pause configuration to the default. + */ + public static function reset():Void + { + musicSuffix = ''; + } } + +/** + * Which set of options the pause menu should display. + */ +enum PauseMode +{ + /** + * The menu displayed when the player pauses the game during a song. + */ + Standard; + + /** + * The menu displayed when the player pauses the game during a song while in charting mode. + */ + Charting; + + /** + * The menu displayed when the player moves to change the game's difficulty. + */ + Difficulty; + + /** + * The menu displayed when the player pauses the game during a cutscene. + */ + Cutscene; +} + +typedef PauseMenuEntry = +{ + var text:String; + var callback:PauseSubState->Void; + + var ?sprite:AtlasText; + + /** + * If this returns true, the entry will be displayed. If it returns false, the entry will be hidden. + */ + var ?filter:Void->Bool; +}; diff --git a/source/funkin/util/tools/FlxTweenTools.hx b/source/funkin/util/tools/FlxTweenTools.hx new file mode 100644 index 000000000..0860af64b --- /dev/null +++ b/source/funkin/util/tools/FlxTweenTools.hx @@ -0,0 +1,25 @@ +package funkin.util.tools; + +import flixel.tweens.FlxTween; +import flixel.tweens.FlxTween.FlxTweenManager; + +class FlxTweenTools +{ + /** + * Tween the background color of a FlxState. + * @param globalManager `flixel.tweens.FlxTween.globalManager` + * @param targetState The FlxState to tween the background color of. + * @param duration The duration of the tween. + * @param fromColor The starting color. + * @param toColor The ending color. + * @param options The options for the tween. + * @return The tween. + */ + public static function bgColor(globalManager:FlxTweenManager, targetState:FlxState, duration:Float = 1.0, fromColor:FlxColor, toColor:FlxColor, + ?options:TweenOptions):BackgroundColorTween + { + var tween = new BackgroundColorTween(options, this); + tween.tween(duration, fromColor, toColor, targetState); + globalManager.add(tween); + } +} From 5db3e006d6635a7e83cb847630786bcf18604c60 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Tue, 27 Feb 2024 23:48:29 -0500 Subject: [PATCH 02/15] asset submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index f8c259584..742384647 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit f8c2595844eff9375b522f117bfdadbdc6728c49 +Subproject commit 742384647b778392f8688f17a3fc16bf4ae9f3ee From b9594ad812e11534c2d77e5fce08a20c8c8d748e Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 28 Feb 2024 00:07:52 -0500 Subject: [PATCH 03/15] asset submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 742384647..84b157429 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 742384647b778392f8688f17a3fc16bf4ae9f3ee +Subproject commit 84b1574294e7a7af21adf1a0e4894c88431ca43d From 13d6ba378e03c0f34470aeef5b38e2a32b4c143f Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 28 Feb 2024 00:19:08 -0500 Subject: [PATCH 04/15] Work in progress on fixing Week 6 --- .../tweens/misc/BackgroundColorTween.hx | 66 ------ source/funkin/InitState.hx | 3 + source/funkin/audio/FunkinSound.hx | 5 + source/funkin/audio/SoundGroup.hx | 5 +- source/funkin/graphics/video/FlxVideo.hx | 9 + source/funkin/import.hx | 1 - source/funkin/input/Controls.hx | 80 +++---- source/funkin/play/PauseSubState.hx | 219 +++++++++++++----- source/funkin/play/PlayState.hx | 95 +++----- source/funkin/play/cutscene/VideoCutscene.hx | 18 ++ .../play/cutscene/dialogue/Conversation.hx | 65 +++--- .../play/cutscene/dialogue/DialogueBox.hx | 41 +++- .../funkin/play/cutscene/dialogue/Speaker.hx | 17 ++ source/funkin/play/song/Song.hx | 4 +- source/funkin/save/Save.hx | 6 - .../save/migrator/RawSaveData_v1_0_0.hx | 1 - .../funkin/save/migrator/SaveDataMigrator.hx | 2 - source/funkin/ui/MusicBeatSubState.hx | 4 + .../debug/dialogue/ConversationDebugState.hx | 9 - source/funkin/ui/options/ControlsMenu.hx | 2 +- source/funkin/util/plugins/MemoryGCPlugin.hx | 37 +++ source/funkin/util/tools/FlxTweenTools.hx | 25 -- 22 files changed, 399 insertions(+), 315 deletions(-) delete mode 100644 source/flixel/tweens/misc/BackgroundColorTween.hx create mode 100644 source/funkin/util/plugins/MemoryGCPlugin.hx delete mode 100644 source/funkin/util/tools/FlxTweenTools.hx diff --git a/source/flixel/tweens/misc/BackgroundColorTween.hx b/source/flixel/tweens/misc/BackgroundColorTween.hx deleted file mode 100644 index b8b2d993f..000000000 --- a/source/flixel/tweens/misc/BackgroundColorTween.hx +++ /dev/null @@ -1,66 +0,0 @@ -package flixel.tweens.misc; - -/** - * Tweens the background color of a state. - * Tweens the red, green, blue, and/or alpha values of the color. - * - * @see `flixel.tweens.misc.ColorTween` for something that operates on sprites! - */ -class BackgroundColorTween extends FlxTween -{ - public var color(default, null):FlxColor; - - var startColor:FlxColor; - var endColor:FlxColor; - - /** - * State object whose color to tween - */ - public var targetState(default, null):FlxState; - - /** - * Clean up references - */ - override public function destroy() - { - super.destroy(); - targetState = null; - } - - /** - * Tweens the color to a new color and an alpha to a new alpha. - * - * @param duration Duration of the tween. - * @param fromColor Start color. - * @param toColor End color. - * @param targetState Optional sprite object whose color to tween. - * @return The tween for chaining. - */ - public function tween(duration:Float, fromColor:FlxColor, toColor:FlxColor, ?targetState:FlxSprite):ColorTween - { - this.color = startColor = fromColor; - this.endColor = toColor; - this.duration = duration; - this.targetState = targetState; - this.start(); - return this; - } - - override function update(elapsed:Float):Void - { - super.update(elapsed); - color = FlxColor.interpolate(startColor, endColor, scale); - - if (targetState != null) - { - targetState.bgColor = color; - // Alpha should apply inherently. - // targetState.alpha = color.alphaFloat; - } - } - - override function isTweenOf(object:Dynamic, ?field:String):Bool - { - return targetState == object && (field == null || field == "color"); - } -} diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 0a59fb70b..b08710c33 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -203,6 +203,9 @@ class InitState extends FlxState // Plugins provide a useful interface for globally active Flixel objects, // that receive update events regardless of the current state. // TODO: Move scripted Module behavior to a Flixel plugin. + #if debug + funkin.util.plugins.MemoryGCPlugin.initialize(); + #end funkin.util.plugins.EvacuateDebugPlugin.initialize(); funkin.util.plugins.ForceCrashPlugin.initialize(); funkin.util.plugins.ReloadAssetsDebugPlugin.initialize(); diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index e7ce68d08..704407c14 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -25,6 +25,9 @@ class FunkinSound extends FlxSound implements ICloneable { static final MAX_VOLUME:Float = 2.0; + /** + * Using `FunkinSound.load` will override a dead instance from here rather than creating a new one, if possible! + */ static var cache(default, null):FlxTypedGroup = new FlxTypedGroup(); public var muted(default, set):Bool = false; @@ -264,6 +267,8 @@ class FunkinSound extends FlxSound implements ICloneable { var sound:FunkinSound = cache.recycle(construct); + // Load the sound. + // Sets `exists = true` as a side effect. sound.loadEmbedded(embeddedSound, looped, autoDestroy, onComplete); if (embeddedSound is String) diff --git a/source/funkin/audio/SoundGroup.hx b/source/funkin/audio/SoundGroup.hx index 0e81a0901..a26537c2a 100644 --- a/source/funkin/audio/SoundGroup.hx +++ b/source/funkin/audio/SoundGroup.hx @@ -80,8 +80,9 @@ class SoundGroup extends FlxTypedGroup // We have to play, then pause the sound to set the time, // else the sound will restart immediately when played. - result.play(true, 0.0); - result.pause(); + // TODO: Past me experienced that issue but present me didn't? Investigate. + // result.play(true, 0.0); + // result.pause(); result.time = this.time; result.onComplete = function() { diff --git a/source/funkin/graphics/video/FlxVideo.hx b/source/funkin/graphics/video/FlxVideo.hx index e95d7caa6..66829af00 100644 --- a/source/funkin/graphics/video/FlxVideo.hx +++ b/source/funkin/graphics/video/FlxVideo.hx @@ -40,6 +40,15 @@ class FlxVideo extends FlxBasic netStream.play(videoPath); } + public function restartVideo():Void + { + // Seek to the beginning of the video. + if (netStream != null) + { + netStream.seek(0); + } + } + public function finishVideo():Void { netStream.dispose(); diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 23b588044..02055d4ed 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -15,7 +15,6 @@ using funkin.util.tools.ArraySortTools; using funkin.util.tools.ArrayTools; using funkin.util.tools.DynamicTools; using funkin.util.tools.FloatTools; -using funkin.util.tools.FlxTweenTools; using funkin.util.tools.Int64Tools; using funkin.util.tools.IntTools; using funkin.util.tools.IteratorTools; diff --git a/source/funkin/input/Controls.hx b/source/funkin/input/Controls.hx index c4760cf5f..0857678d0 100644 --- a/source/funkin/input/Controls.hx +++ b/source/funkin/input/Controls.hx @@ -58,12 +58,11 @@ class Controls extends FlxActionSet var _back = new FlxActionDigital(Action.BACK); var _pause = new FlxActionDigital(Action.PAUSE); var _reset = new FlxActionDigital(Action.RESET); + var _screenshot = new FlxActionDigital(Action.SCREENSHOT); var _cutscene_advance = new FlxActionDigital(Action.CUTSCENE_ADVANCE); - var _cutscene_skip = new FlxActionDigital(Action.CUTSCENE_SKIP); var _debug_menu = new FlxActionDigital(Action.DEBUG_MENU); var _debug_chart = new FlxActionDigital(Action.DEBUG_CHART); var _debug_stage = new FlxActionDigital(Action.DEBUG_STAGE); - var _screenshot = new FlxActionDigital(Action.SCREENSHOT); var _volume_up = new FlxActionDigital(Action.VOLUME_UP); var _volume_down = new FlxActionDigital(Action.VOLUME_DOWN); var _volume_mute = new FlxActionDigital(Action.VOLUME_MUTE); @@ -208,16 +207,21 @@ class Controls extends FlxActionSet inline function get_PAUSE() return _pause.check(); + public var RESET(get, never):Bool; + + inline function get_RESET() + return _reset.check(); + + public var SCREENSHOT(get, never):Bool; + + inline function get_SCREENSHOT() + return _screenshot.check(); + public var CUTSCENE_ADVANCE(get, never):Bool; inline function get_CUTSCENE_ADVANCE() return _cutscene_advance.check(); - public var CUTSCENE_SKIP(get, never):Bool; - - inline function get_CUTSCENE_SKIP() - return _cutscene_skip.check(); - public var DEBUG_MENU(get, never):Bool; inline function get_DEBUG_MENU() @@ -233,11 +237,6 @@ class Controls extends FlxActionSet inline function get_DEBUG_STAGE() return _debug_stage.check(); - public var SCREENSHOT(get, never):Bool; - - inline function get_SCREENSHOT() - return _screenshot.check(); - public var VOLUME_UP(get, never):Bool; inline function get_VOLUME_UP() @@ -253,11 +252,6 @@ class Controls extends FlxActionSet inline function get_VOLUME_MUTE() return _volume_mute.check(); - public var RESET(get, never):Bool; - - inline function get_RESET() - return _reset.check(); - public function new(name, scheme:KeyboardScheme = null) { super(name); @@ -289,16 +283,15 @@ class Controls extends FlxActionSet add(_accept); add(_back); add(_pause); + add(_reset); + add(_screenshot); add(_cutscene_advance); - add(_cutscene_skip); add(_debug_menu); add(_debug_chart); add(_debug_stage); - add(_screenshot); add(_volume_up); add(_volume_down); add(_volume_mute); - add(_reset); for (action in digitalActions) byName[action.name] = action; @@ -383,12 +376,11 @@ class Controls extends FlxActionSet case BACK: _back; case PAUSE: _pause; case RESET: _reset; + case SCREENSHOT: _screenshot; case CUTSCENE_ADVANCE: _cutscene_advance; - case CUTSCENE_SKIP: _cutscene_skip; case DEBUG_MENU: _debug_menu; case DEBUG_CHART: _debug_chart; case DEBUG_STAGE: _debug_stage; - case SCREENSHOT: _screenshot; case VOLUME_UP: _volume_up; case VOLUME_DOWN: _volume_down; case VOLUME_MUTE: _volume_mute; @@ -449,26 +441,24 @@ class Controls extends FlxActionSet func(_back, JUST_PRESSED); case PAUSE: func(_pause, JUST_PRESSED); + case RESET: + func(_reset, JUST_PRESSED); + case SCREENSHOT: + func(_screenshot, JUST_PRESSED); case CUTSCENE_ADVANCE: func(_cutscene_advance, JUST_PRESSED); - case CUTSCENE_SKIP: - func(_cutscene_skip, PRESSED); case DEBUG_MENU: func(_debug_menu, JUST_PRESSED); case DEBUG_CHART: func(_debug_chart, JUST_PRESSED); case DEBUG_STAGE: func(_debug_stage, JUST_PRESSED); - case SCREENSHOT: - func(_screenshot, JUST_PRESSED); case VOLUME_UP: func(_volume_up, JUST_PRESSED); case VOLUME_DOWN: func(_volume_down, JUST_PRESSED); case VOLUME_MUTE: func(_volume_mute, JUST_PRESSED); - case RESET: - func(_reset, JUST_PRESSED); } } @@ -654,13 +644,12 @@ class Controls extends FlxActionSet bindKeys(Control.ACCEPT, getDefaultKeybinds(scheme, Control.ACCEPT)); bindKeys(Control.BACK, getDefaultKeybinds(scheme, Control.BACK)); bindKeys(Control.PAUSE, getDefaultKeybinds(scheme, Control.PAUSE)); + bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET)); + bindKeys(Control.SCREENSHOT, getDefaultKeybinds(scheme, Control.SCREENSHOT)); bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE)); - bindKeys(Control.CUTSCENE_SKIP, getDefaultKeybinds(scheme, Control.CUTSCENE_SKIP)); bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU)); bindKeys(Control.DEBUG_CHART, getDefaultKeybinds(scheme, Control.DEBUG_CHART)); bindKeys(Control.DEBUG_STAGE, getDefaultKeybinds(scheme, Control.DEBUG_STAGE)); - bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET)); - bindKeys(Control.SCREENSHOT, getDefaultKeybinds(scheme, Control.SCREENSHOT)); bindKeys(Control.VOLUME_UP, getDefaultKeybinds(scheme, Control.VOLUME_UP)); bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN)); bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE)); @@ -683,16 +672,15 @@ class Controls extends FlxActionSet case Control.ACCEPT: return [Z, SPACE, ENTER]; case Control.BACK: return [X, BACKSPACE, ESCAPE]; case Control.PAUSE: return [P, ENTER, ESCAPE]; + case Control.RESET: return [R]; + case Control.SCREENSHOT: return [F3]; // TODO: Change this back to PrintScreen case Control.CUTSCENE_ADVANCE: return [Z, ENTER]; - case Control.CUTSCENE_SKIP: return [P, ESCAPE]; case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; case Control.DEBUG_STAGE: return []; - case Control.SCREENSHOT: return [F3]; // TODO: Change this back to PrintScreen case Control.VOLUME_UP: return [PLUS, NUMPADPLUS]; case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS]; case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO]; - case Control.RESET: return [R]; } case Duo(true): switch (control) { @@ -707,16 +695,15 @@ class Controls extends FlxActionSet case Control.ACCEPT: return [G, Z]; case Control.BACK: return [H, X]; case Control.PAUSE: return [ONE]; + case Control.RESET: return [R]; + case Control.SCREENSHOT: return [PRINTSCREEN]; case Control.CUTSCENE_ADVANCE: return [G, Z]; - case Control.CUTSCENE_SKIP: return [ONE]; case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; case Control.DEBUG_STAGE: return []; - case Control.SCREENSHOT: return [PRINTSCREEN]; case Control.VOLUME_UP: return [PLUS]; case Control.VOLUME_DOWN: return [MINUS]; case Control.VOLUME_MUTE: return [ZERO]; - case Control.RESET: return [R]; } case Duo(false): switch (control) { @@ -731,16 +718,15 @@ class Controls extends FlxActionSet case Control.ACCEPT: return [ENTER]; case Control.BACK: return [ESCAPE]; case Control.PAUSE: return [ONE]; + case Control.RESET: return [R]; + case Control.SCREENSHOT: return [PRINTSCREEN]; case Control.CUTSCENE_ADVANCE: return [ENTER]; - case Control.CUTSCENE_SKIP: return [ONE]; case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; case Control.DEBUG_STAGE: return []; - case Control.SCREENSHOT: return [PRINTSCREEN]; case Control.VOLUME_UP: return [NUMPADPLUS]; case Control.VOLUME_DOWN: return [NUMPADMINUS]; case Control.VOLUME_MUTE: return [NUMPADZERO]; - case Control.RESET: return [R]; } default: // Fallthrough. @@ -843,15 +829,14 @@ class Controls extends FlxActionSet Control.NOTE_LEFT => getDefaultGamepadBinds(Control.NOTE_LEFT), Control.NOTE_RIGHT => getDefaultGamepadBinds(Control.NOTE_RIGHT), Control.PAUSE => getDefaultGamepadBinds(Control.PAUSE), + Control.RESET => getDefaultGamepadBinds(Control.RESET), // Control.SCREENSHOT => [], // Control.VOLUME_UP => [RIGHT_SHOULDER], // Control.VOLUME_DOWN => [LEFT_SHOULDER], // Control.VOLUME_MUTE => [RIGHT_TRIGGER], Control.CUTSCENE_ADVANCE => getDefaultGamepadBinds(Control.CUTSCENE_ADVANCE), - Control.CUTSCENE_SKIP => getDefaultGamepadBinds(Control.CUTSCENE_SKIP), // Control.DEBUG_MENU // Control.DEBUG_CHART - Control.RESET => getDefaultGamepadBinds(Control.RESET) ]); } @@ -868,15 +853,14 @@ class Controls extends FlxActionSet case Control.NOTE_LEFT: return [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT]; case Control.NOTE_RIGHT: return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT]; case Control.PAUSE: return [START]; + case Control.RESET: return [RIGHT_SHOULDER]; case Control.SCREENSHOT: return []; case Control.VOLUME_UP: return []; case Control.VOLUME_DOWN: return []; case Control.VOLUME_MUTE: return []; case Control.CUTSCENE_ADVANCE: return [A]; - case Control.CUTSCENE_SKIP: return [START]; case Control.DEBUG_MENU: return []; case Control.DEBUG_CHART: return []; - case Control.RESET: return [RIGHT_SHOULDER]; default: // Fallthrough. } @@ -1228,14 +1212,13 @@ enum Control UI_RIGHT; UI_DOWN; RESET; + SCREENSHOT; ACCEPT; BACK; PAUSE; // CUTSCENE CUTSCENE_ADVANCE; - CUTSCENE_SKIP; // SCREENSHOT - SCREENSHOT; // VOLUME VOLUME_UP; VOLUME_DOWN; @@ -1279,15 +1262,14 @@ abstract Action(String) to String from String var BACK = "back"; var PAUSE = "pause"; var RESET = "reset"; + // SCREENSHOT + var SCREENSHOT = "screenshot"; // CUTSCENE var CUTSCENE_ADVANCE = "cutscene_advance"; - var CUTSCENE_SKIP = "cutscene_skip"; // VOLUME var VOLUME_UP = "volume_up"; var VOLUME_DOWN = "volume_down"; var VOLUME_MUTE = "volume_mute"; - // SCREENSHOT - var SCREENSHOT = "screenshot"; // DEBUG var DEBUG_MENU = "debug_menu"; var DEBUG_CHART = "debug_chart"; diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 8300ac936..4f2a76e19 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -1,5 +1,24 @@ package funkin.play; +import flixel.addons.transition.FlxTransitionableState; +import flixel.FlxG; +import flixel.util.FlxTimer; +import flixel.FlxSprite; +import flixel.group.FlxSpriteGroup; +import flixel.math.FlxMath; +import flixel.text.FlxText; +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; +import flixel.util.FlxColor; +import funkin.audio.FunkinSound; +import funkin.data.song.SongRegistry; +import funkin.graphics.FunkinSprite; +import funkin.play.cutscene.VideoCutscene; +import funkin.play.PlayState; +import funkin.ui.AtlasText; +import funkin.ui.MusicBeatSubState; +import funkin.ui.transition.StickerSubState; + typedef PauseSubStateParams = { ?mode:PauseMode, @@ -10,29 +29,36 @@ typedef PauseSubStateParams = */ class PauseSubState extends MusicBeatSubState { - static final PAUSE_MENU_ENTRIES_STANDARD = [ + static final PAUSE_MENU_ENTRIES_STANDARD:Array = [ {text: 'Resume', callback: resume}, {text: 'Restart Song', callback: restartPlayState}, {text: 'Change Difficulty', callback: switchMode.bind(_, Difficulty)}, - {text: 'Enable Practice Mode', callback: enablePracticeMode, filter: () -> (PlayState.instance?.isPracticeMode ?? true)}, + {text: 'Enable Practice Mode', callback: enablePracticeMode, filter: () -> !(PlayState.instance?.isPracticeMode ?? false)}, {text: 'Exit to Menu', callback: quitToMenu}, ]; - static final PAUSE_MENU_ENTRIES_CHARTING = [ + static final PAUSE_MENU_ENTRIES_CHARTING:Array = [ {text: 'Resume', callback: resume}, {text: 'Restart Song', callback: restartPlayState}, {text: 'Return to Chart Editor', callback: quitToChartEditor}, ]; - static final PAUSE_MENU_ENTRIES_DIFFICULTY = [ + static final PAUSE_MENU_ENTRIES_DIFFICULTY:Array = [ {text: 'Back', callback: switchMode.bind(_, Standard)} // Other entries are added dynamically. ]; - static final PAUSE_MENU_ENTRIES_CUTSCENE = [ + static final PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE:Array = [ {text: 'Resume', callback: resume}, - {text: 'Restart Cutscene', callback: restartCutscene}, - {text: 'Skip Cutscene', callback: skipCutscene}, + {text: 'Restart Cutscene', callback: restartVideoCutscene}, + {text: 'Skip Cutscene', callback: skipVideoCutscene}, + {text: 'Exit to Menu', callback: quitToMenu}, + ]; + + static final PAUSE_MENU_ENTRIES_CONVERSATION:Array = [ + {text: 'Resume', callback: resume}, + {text: 'Restart Dialogue', callback: restartConversation}, + {text: 'Skip Dialogue', callback: skipConversation}, {text: 'Exit to Menu', callback: quitToMenu}, ]; @@ -42,15 +68,21 @@ class PauseSubState extends MusicBeatSubState public static var musicSuffix:String = ''; // Status - var menuEntries:Array; + var currentMenuEntries:Array; var currentEntry:Int = 0; var currentMode:PauseMode; - var allowInput:Bool = true; + + /** + * Disallow input until the transition in is complete! + * This prevents the pause menu from immediately closing. + */ + public var allowInput:Bool = false; // Graphics - var metadata:FlxTypedGroup; + var background:FunkinSprite; + var metadata:FlxTypedSpriteGroup; var metadataPractice:FlxText; - var menuEntryText:FlxTypedGroup; + var menuEntryText:FlxTypedSpriteGroup; // Audio var pauseMusic:FunkinSound; @@ -58,9 +90,7 @@ class PauseSubState extends MusicBeatSubState public function new(?params:PauseSubStateParams) { super(); - this.currentMode = params?.mode ?? PauseMode.Standard; - - this.bgColor = FlxColor.TRANSPARENT; // Transparent, fades into black later. + this.currentMode = params?.mode ?? Standard; } public override function create():Void @@ -69,8 +99,16 @@ class PauseSubState extends MusicBeatSubState startPauseMusic(); + buildBackground(); + buildMetadata(); + menuEntryText = new FlxTypedSpriteGroup(); + menuEntryText.scrollFactor.set(0, 0); + add(menuEntryText); + + regenerateMenu(); + transitionIn(); } @@ -81,47 +119,69 @@ class PauseSubState extends MusicBeatSubState handleInputs(); } + public override function destroy():Void + { + super.destroy(); + pauseMusic.stop(); + } + function startPauseMusic():Void { - pauseMusic = FunkinSound.load(Paths.music('breakfast-pixel'), true, true); + pauseMusic = FunkinSound.load(Paths.music('breakfast$musicSuffix'), true, true); // Start playing at a random point in the song. pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2))); pauseMusic.fadeIn(MUSIC_FADE_IN_TIME, 0, MUSIC_FINAL_VOLUME); } + function buildBackground():Void + { + // Using state.bgColor causes bugs! + background = new FunkinSprite(0, 0); + background.makeSolidColor(FlxG.width, FlxG.height, FlxColor.BLACK); + background.alpha = 0.0; + background.scrollFactor.set(0, 0); + background.updateHitbox(); + add(background); + } + /** * Render the metadata in the top right. */ function buildMetadata():Void { - metadata = new FlxTypedGroup(); + metadata = new FlxTypedSpriteGroup(); + metadata.scrollFactor.set(0, 0); add(metadata); - var metadataSong:FlxText = new FlxText(20, 15, 0, 'Song Name - Artist'); + var metadataSong:FlxText = new FlxText(20, 15, FlxG.width - 40, 'Song Name - Artist'); metadataSong.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); if (PlayState.instance?.currentChart != null) { - metadataSong.text += '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}'; + metadataSong.text = '${PlayState.instance.currentChart.songName} - ${PlayState.instance.currentChart.songArtist}'; } + metadataSong.scrollFactor.set(0, 0); metadata.add(metadataSong); - var metadataDifficulty:FlxText = new FlxText(20, 15 + 32, 0, 'Difficulty'); + var metadataDifficulty:FlxText = new FlxText(20, 15 + 32, FlxG.width - 40, 'Difficulty: '); metadataDifficulty.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); if (PlayState.instance?.currentDifficulty != null) { metadataDifficulty.text += PlayState.instance.currentDifficulty.toTitleCase(); } + metadataDifficulty.scrollFactor.set(0, 0); metadata.add(metadataDifficulty); - var metadataDeaths:FlxText = new FlxText(20, 15 + 64, 0, '0 Blue Balls'); + var metadataDeaths:FlxText = new FlxText(20, 15 + 64, FlxG.width - 40, '0 Blue Balls'); metadataDeaths.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls'; + metadataDeaths.scrollFactor.set(0, 0); metadata.add(metadataDeaths); - metadataPractice = new FlxText(20, 15 + 96, 0, 'PRACTICE MODE'); + metadataPractice = new FlxText(20, 15 + 96, FlxG.width - 40, 'PRACTICE MODE'); metadataPractice.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; + metadataPractice.scrollFactor.set(0, 0); metadata.add(metadataPractice); } @@ -149,30 +209,45 @@ class PauseSubState extends MusicBeatSubState trace('DIFFICULTIES: ${difficultiesInVariation}'); for (difficulty in difficultiesInVariation) { - difficulties.push({text: difficulty.toTitleCase(), callback: () -> changeDifficulty(this, difficulty)}); + entries.push({text: difficulty.toTitleCase(), callback: (state) -> changeDifficulty(state, difficulty)}); } } // Add the back button. currentMenuEntries = entries.concat(PAUSE_MENU_ENTRIES_DIFFICULTY.clone()); + case PauseMode.Conversation: + currentMenuEntries = PAUSE_MENU_ENTRIES_CONVERSATION.clone(); case PauseMode.Cutscene: - currentMenuEntries = PAUSE_MENU_ENTRIES_CUTSCENE.clone(); + currentMenuEntries = PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE.clone(); } // Render out the entries depending on the mode. - for (entryIndex in 0...entries) + var entryIndex:Int = 0; + var toRemove = []; + for (entry in currentMenuEntries) { - var entry:PauseMenuEntry = entries[entryIndex]; + if (entry == null || (entry.filter != null && !entry.filter())) + { + // Remove entries that should be hidden. + toRemove.push(entry); + } + else + { + // Handle visible entries. + var yPos:Float = 70 * entryIndex + 30; + var text:AtlasText = new AtlasText(0, yPos, entry.text, AtlasFont.BOLD); + text.scrollFactor.set(0, 0); + text.alpha = 0; + menuEntryText.add(text); - // Remove entries that should be hidden. - if (entry.filter != null && !entry.filter()) currentMenuEntries.remove(entry); + entry.sprite = text; - var yPos:Float = 70 * entryIndex + 30; - var text:AtlasText = new AtlasText(0, yPos, entry.text, AtlasFont.BOLD); - text.alpha = 0; - menuEntryText.add(text); - - entry.sprite = text; + entryIndex++; + } + } + for (entry in toRemove) + { + currentMenuEntries.remove(entry); } metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; @@ -182,15 +257,19 @@ class PauseSubState extends MusicBeatSubState function transitionIn():Void { - FlxTween.globalManager.bgColor(this, 0.4, FlxColor.fromRGB(0, 0, 0, 0.0), FlxColor.fromRGB(0, 0, 0, 0.6), {ease: FlxEase.quartInOut}); + FlxTween.tween(background, {alpha: 0.6}, 0.4, {ease: FlxEase.quartInOut}); // Animate each element a little bit downwards. - var delay:Float = 0.3; + var delay:Float = 0.1; for (child in metadata.members) { FlxTween.tween(child, {alpha: 1, y: child.y + 5}, 0.4, {ease: FlxEase.quartInOut, startDelay: delay}); - delay += 0.2; + delay += 0.05; } + + new FlxTimer().start(0.2, (_) -> { + allowInput = true; + }); } function handleInputs():Void @@ -205,22 +284,24 @@ class PauseSubState extends MusicBeatSubState { changeSelection(1); } - if (controls.PAUSE) - { - resume(this); - } + if (controls.ACCEPT) { - menuEntries[currentEntry].callback(this); + currentMenuEntries[currentEntry].callback(this); + } + else if (controls.PAUSE) + { + resume(this); } #if (debug || FORCE_DEBUG_VERSION) // to pause the game and get screenshots easy, press H on pause menu! if (FlxG.keys.justPressed.H) { - var visible = !metaDataGrp.visible; - metadata = visible; - menuEntryText = visible; + var visible = !metadata.visible; + + metadata.visible = visible; + menuEntryText.visible = visible; this.bgColor = visible ? 0x99000000 : 0x00000000; // 60% or fully transparent black } #end @@ -232,14 +313,14 @@ class PauseSubState extends MusicBeatSubState currentEntry += change; - if (currentEntry < 0) currentEntry = menuEntries.length - 1; - if (currentEntry >= menuEntries.length) currentEntry = 0; + if (currentEntry < 0) currentEntry = currentMenuEntries.length - 1; + if (currentEntry >= currentMenuEntries.length) currentEntry = 0; - for (entryIndex in 0...menuEntries.length) + for (entryIndex in 0...currentMenuEntries.length) { var isCurrent:Bool = entryIndex == currentEntry; - var entry:PauseMenuEntry = menuEntries[entryIndex]; + var entry:PauseMenuEntry = currentMenuEntries[entryIndex]; var text:AtlasText = entry.sprite; // Set the transparency. @@ -248,6 +329,7 @@ class PauseSubState extends MusicBeatSubState // Set the position. var targetX = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 20 + 90; var targetY = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 120 + (FlxG.height * 0.48); + trace(targetY); FlxTween.tween(text, {x: targetX, y: targetY}, 0.16, {ease: FlxEase.linear}); } } @@ -291,7 +373,35 @@ class PauseSubState extends MusicBeatSubState if (PlayState.instance == null) return; PlayState.instance.isPracticeMode = true; - regenerateMenu(); + state.regenerateMenu(); + } + + static function restartVideoCutscene(state:PauseSubState):Void + { + VideoCutscene.restartVideo(); + state.close(); + } + + static function skipVideoCutscene(state:PauseSubState):Void + { + VideoCutscene.finishVideo(); + state.close(); + } + + static function restartConversation(state:PauseSubState):Void + { + if (PlayState.instance?.currentConversation == null) return; + + PlayState.instance.currentConversation.resetConversation(); + state.close(); + } + + static function skipConversation(state:PauseSubState):Void + { + if (PlayState.instance?.currentConversation == null) return; + + PlayState.instance.currentConversation.skipConversation(); + state.close(); } static function quitToMenu(state:PauseSubState):Void @@ -306,11 +416,11 @@ class PauseSubState extends MusicBeatSubState if (PlayStatePlaylist.isStoryMode) { PlayStatePlaylist.reset(); - openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker))); + state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.story.StoryMenuState(sticker))); } else { - openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(null, sticker))); + state.openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new funkin.ui.freeplay.FreeplayState(null, sticker))); } } @@ -351,7 +461,12 @@ enum PauseMode Difficulty; /** - * The menu displayed when the player pauses the game during a cutscene. + * The menu displayed when the player pauses the game during a conversation. + */ + Conversation; + + /** + * The menu displayed when the player pauses the game during a video cutscene. */ Cutscene; } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 1dbba5b54..ca2344b0a 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -314,6 +314,11 @@ class PlayState extends MusicBeatSubState **/ var inputReleaseQueue:Array = []; + /** + * If we just unpaused the game, we shouldn't be able to pause again for one frame. + */ + var justUnpaused:Bool = false; + /** * PRIVATE INSTANCE VARIABLES * Private instance variables should be used for information that must be reset or dereferenced @@ -442,11 +447,6 @@ class PlayState extends MusicBeatSubState */ var comboPopUps:PopUpStuff; - /** - * The circular sprite that appears while the user is holding down the Skip Cutscene button. - */ - var skipTimer:FlxPieDial; - /** * PROPERTIES */ @@ -474,7 +474,7 @@ class PlayState extends MusicBeatSubState if (!Std.isOfType(this.subState, PauseSubState)) return false; var pauseSubState:PauseSubState = cast this.subState; - return pauseSubState.exitingToMenu; + return !pauseSubState.allowInput; } /** @@ -629,14 +629,6 @@ class PlayState extends MusicBeatSubState comboPopUps.cameras = [camHUD]; add(comboPopUps); - // The little dial that shows up when you hold the Skip Cutscene key. - skipTimer = new FlxPieDial(16, 16, 32, FlxColor.WHITE, 36, CIRCLE, true, 24); - skipTimer.amount = 0; - skipTimer.zIndex = 1000; - add(skipTimer); - // Renders only in video cutscene mode. - skipTimer.cameras = [camCutscene]; - #if discord_rpc // Initialize Discord Rich Presence. initDiscord(); @@ -752,6 +744,8 @@ class PlayState extends MusicBeatSubState public override function update(elapsed:Float):Void { + // TOTAL: 9.42% CPU Time when profiled in VS 2019. + if (criticalFailure) return; super.update(elapsed); @@ -853,7 +847,7 @@ class PlayState extends MusicBeatSubState #end // Attempt to pause the game. - if ((controls.PAUSE || androidPause) && isInCountdown && mayPauseGame) + if ((controls.PAUSE || androidPause) && isInCountdown && mayPauseGame && !justUnpaused) { var event = new PauseScriptEvent(FlxG.random.bool(1 / 1000)); @@ -888,12 +882,12 @@ class PlayState extends MusicBeatSubState boyfriendPos = currentStage.getBoyfriend().getScreenPosition(); } - var pauseSubState:FlxSubState = new PauseSubState(isChartingMode); + var pauseSubState:FlxSubState = new PauseSubState({mode: isChartingMode ? Charting : Standard}); FlxTransitionableSubState.skipNextTransIn = true; FlxTransitionableSubState.skipNextTransOut = true; - openSubState(pauseSubState); pauseSubState.camera = camHUD; + openSubState(pauseSubState); // boyfriendPos.put(); // TODO: Why is this here? } @@ -1017,6 +1011,8 @@ class PlayState extends MusicBeatSubState // Moving notes into position is now done by Strumline.update(). processNotes(elapsed); + + justUnpaused = false; } public override function dispatchEvent(event:ScriptEvent):Void @@ -1105,6 +1101,8 @@ class PlayState extends MusicBeatSubState DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC); } #end + + justUnpaused = true; } else if (Std.isOfType(subState, Transition)) { @@ -2444,58 +2442,39 @@ class PlayState extends MusicBeatSubState */ function handleCutsceneKeys(elapsed:Float):Void { + if (isGamePaused) return; + if (currentConversation != null) { - if (controls.CUTSCENE_ADVANCE) currentConversation?.advanceConversation(); - - if (controls.CUTSCENE_SKIP) + // Pause/unpause may conflict with advancing the conversation! + if (controls.CUTSCENE_ADVANCE && !justUnpaused) { - currentConversation?.trySkipConversation(elapsed); + currentConversation?.advanceConversation(); } - else + else if (controls.PAUSE && !justUnpaused) { - currentConversation?.trySkipConversation(-1); + var pauseSubState:FlxSubState = new PauseSubState({mode: Conversation}); + + persistentUpdate = false; + FlxTransitionableSubState.skipNextTransIn = true; + FlxTransitionableSubState.skipNextTransOut = true; + pauseSubState.camera = camCutscene; + openSubState(pauseSubState); } } else if (VideoCutscene.isPlaying()) { // This is a video cutscene. - - if (controls.CUTSCENE_SKIP) + if (controls.PAUSE && !justUnpaused) { - trySkipVideoCutscene(elapsed); + var pauseSubState:FlxSubState = new PauseSubState({mode: Cutscene}); + + persistentUpdate = false; + FlxTransitionableSubState.skipNextTransIn = true; + FlxTransitionableSubState.skipNextTransOut = true; + pauseSubState.camera = camCutscene; + openSubState(pauseSubState); } - else - { - trySkipVideoCutscene(-1); - } - } - } - - /** - * Handle logic for the skip timer. - * If the skip button is being held, pass the amount of time elapsed since last game update. - * If the skip button has been released, pass a negative number. - */ - function trySkipVideoCutscene(elapsed:Float):Void - { - if (skipTimer == null || skipTimer.animation == null) return; - - if (elapsed < 0) - { - skipHeldTimer = 0.0; - } - else - { - skipHeldTimer += elapsed; - } - - skipTimer.visible = skipHeldTimer >= 0.05; - skipTimer.amount = Math.min(skipHeldTimer / 1.5, 1.0); - - if (skipHeldTimer >= 1.5) - { - VideoCutscene.finishVideo(); } } @@ -2694,7 +2673,7 @@ class PlayState extends MusicBeatSubState FlxG.sound.music.pause(); if (vocals != null) { - vocals.pause(); + vocals.destroy(); remove(vocals); } } diff --git a/source/funkin/play/cutscene/VideoCutscene.hx b/source/funkin/play/cutscene/VideoCutscene.hx index 934919b65..a883a528a 100644 --- a/source/funkin/play/cutscene/VideoCutscene.hx +++ b/source/funkin/play/cutscene/VideoCutscene.hx @@ -118,6 +118,24 @@ class VideoCutscene } #end + public static function restartVideo():Void + { + #if html5 + if (vid != null) + { + vid.restartVideo(); + } + #end + + #if hxCodec + if (vid != null) + { + // Seek to the start of the video. + vid.bitmap.time = 0; + } + #end + } + public static function finishVideo(?transitionTime:Float = 0.5):Void { trace('ALERT: Finish video cutscene called!'); diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx index dc3fd8c8a..150f6bf6f 100644 --- a/source/funkin/play/cutscene/dialogue/Conversation.hx +++ b/source/funkin/play/cutscene/dialogue/Conversation.hx @@ -31,10 +31,6 @@ import funkin.data.dialogue.DialogueBoxRegistry; */ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass implements IRegistryEntry { - static final CONVERSATION_SKIP_TIMER:Float = 1.5; - - var skipHeldTimer:Float = 0.0; - /** * The ID of the conversation. */ @@ -105,8 +101,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl var currentDialogueBox:DialogueBox; - var skipTimer:FlxPieDial; - public function new(id:String) { super(); @@ -124,8 +118,8 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl { // Reset the progress in the dialogue. currentDialogueEntry = 0; + currentDialogueLine = 0; this.state = ConversationState.Start; - this.alpha = 1.0; // Start the dialogue. dispatchEvent(new DialogueScriptEvent(DIALOGUE_START, this, false)); @@ -153,6 +147,12 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl function setupBackdrop():Void { + if (backdrop != null) + { + backdrop.destroy(); + backdrop = null; + } + backdrop = new FunkinSprite(0, 0); if (_data.backdrop == null) return; @@ -181,12 +181,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl refresh(); } - function setupSkipTimer():Void - { - add(skipTimer = new FlxPieDial(16, 16, 32, FlxColor.WHITE, 36, CIRCLE, true, 24)); - skipTimer.amount = 0; - } - public override function update(elapsed:Float):Void { super.update(elapsed); @@ -201,8 +195,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl // Skip the next steps if the current speaker is already displayed. if (currentSpeaker != null && nextSpeakerId == currentSpeaker.id) return; - var nextSpeaker:Speaker = SpeakerRegistry.instance.fetchEntry(nextSpeakerId); - if (currentSpeaker != null) { remove(currentSpeaker); @@ -210,6 +202,8 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl currentSpeaker = null; } + var nextSpeaker:Speaker = SpeakerRegistry.instance.fetchEntry(nextSpeakerId); + if (nextSpeaker == null) { if (nextSpeakerId == null) @@ -222,6 +216,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl } return; } + if (!nextSpeaker.alive) nextSpeaker.revive(); ScriptEventDispatcher.callEvent(nextSpeaker, new ScriptEvent(CREATE, true)); @@ -266,6 +261,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl trace('Dialogue box could not be retrieved.'); return; } + if (!nextDialogueBox.alive) nextDialogueBox.revive(); ScriptEventDispatcher.callEvent(nextDialogueBox, new ScriptEvent(CREATE, true)); @@ -347,29 +343,21 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl currentDialogueEntry = 0; this.state = ConversationState.Start; - advanceConversation(); - } + if (outroTween != null) outroTween.cancel(); // Canc + outroTween = null; - public function trySkipConversation(elapsed:Float):Void - { - if (skipTimer == null || skipTimer.animation == null) return; + this.alpha = 0.0; + if (this.music != null) this.music.stop(); + this.music = null; - if (elapsed < 0) - { - skipHeldTimer = 0.0; - } - else - { - skipHeldTimer += elapsed; - } + if (currentSpeaker != null) currentSpeaker.kill(); + currentSpeaker = null; + if (currentDialogueBox != null) currentDialogueBox.kill(); + currentDialogueBox = null; + if (backdrop != null) backdrop.kill(); + backdrop = null; - skipTimer.visible = skipHeldTimer >= 0.05; - skipTimer.amount = Math.min(skipHeldTimer / CONVERSATION_SKIP_TIMER, 1.0); - - if (skipHeldTimer >= CONVERSATION_SKIP_TIMER) - { - skipConversation(); - } + startConversation(); } /** @@ -425,7 +413,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl // Fade in the music and backdrop. setupMusic(); setupBackdrop(); - setupSkipTimer(); // Advance the conversation. state = ConversationState.Opening; @@ -554,12 +541,16 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl if (this.music != null) this.music.stop(); this.music = null; - this.skipTimer = null; if (currentSpeaker != null) currentSpeaker.kill(); + remove(currentSpeaker); currentSpeaker = null; + if (currentDialogueBox != null) currentDialogueBox.kill(); + remove(currentDialogueBox); currentDialogueBox = null; + if (backdrop != null) backdrop.kill(); + remove(backdrop); backdrop = null; this.clear(); diff --git a/source/funkin/play/cutscene/dialogue/DialogueBox.hx b/source/funkin/play/cutscene/dialogue/DialogueBox.hx index 6f8a0086a..eb603a352 100644 --- a/source/funkin/play/cutscene/dialogue/DialogueBox.hx +++ b/source/funkin/play/cutscene/dialogue/DialogueBox.hx @@ -8,6 +8,7 @@ import flixel.text.FlxText; import flixel.addons.text.FlxTypeText; import funkin.util.assets.FlxAnimationUtil; import funkin.modding.events.ScriptEvent; +import funkin.audio.FunkinSound; import funkin.modding.IScriptedClass.IDialogueScriptedClass; import flixel.util.FlxColor; import funkin.data.dialogue.DialogueBoxData; @@ -111,9 +112,6 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple this.y = 0; this.alpha = 1; - this.boxSprite = new FlxSprite(0, 0); - add(this.boxSprite); - loadSpritesheet(); loadAnimations(); @@ -122,6 +120,15 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple function loadSpritesheet():Void { + if (this.boxSprite != null) + { + remove(this.boxSprite); + this.boxSprite = null; + } + + this.boxSprite = new FlxSprite(0, 0); + add(this.boxSprite); + trace('[DIALOGUE BOX] Loading spritesheet ${_data.assetPath} for ${id}'); var tex:FlxFramesCollection = Paths.getSparrowAtlas(_data.assetPath); @@ -190,6 +197,31 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple this.boxSprite.updateHitbox(); } + /** + * Calls `kill()` on the group's members and then on the group itself. + * You can revive this group later via `revive()` after this. + */ + public override function kill():Void + { + super.kill(); + if (this.boxSprite != null) + { + this.boxSprite.kill(); + this.boxSprite = null; + } + if (this.textDisplay != null) + { + this.textDisplay.kill(); + this.textDisplay = null; + } + this.clear(); + } + + public override function revive():Void + { + super.revive(); + } + function loadAnimations():Void { trace('[DIALOGUE BOX] Loading ${_data.animations.length} animations for ${id}'); @@ -246,7 +278,8 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple textDisplay.setFormat(_data.text.fontFamily, _data.text.size, FlxColor.fromString(_data.text.color), LEFT, SHADOW, FlxColor.fromString(_data.text.shadowColor ?? '#00000000'), false); textDisplay.borderSize = _data.text.shadowWidth ?? 2; - textDisplay.sounds = [FlxG.sound.load(Paths.sound('pixelText'), 0.6)]; + // TODO: Add an option to configure this. + textDisplay.sounds = [FunkinSound.load(Paths.sound('pixelText'), 0.6)]; textDisplay.completeCallback = onTypingComplete; diff --git a/source/funkin/play/cutscene/dialogue/Speaker.hx b/source/funkin/play/cutscene/dialogue/Speaker.hx index d5bffd7b0..0d59854a7 100644 --- a/source/funkin/play/cutscene/dialogue/Speaker.hx +++ b/source/funkin/play/cutscene/dialogue/Speaker.hx @@ -106,6 +106,23 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe loadAnimations(); } + /** + * Calls `kill()` on the group's members and then on the group itself. + * You can revive this group later via `revive()` after this. + */ + public override function kill():Void + { + super.kill(); + } + + public override function revive():Void + { + super.revive(); + + loadSpritesheet(); + loadAnimations(); + } + function loadSpritesheet():Void { trace('[SPEAKER] Loading spritesheet ${_data.assetPath} for ${id}'); diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 970aebc57..105e6db43 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -614,9 +614,9 @@ class SongDifficulty } // Add player vocals. - if (voiceList[0] != null) result.addPlayerVoice(FunkinSound.load(Assets.getSound(voiceList[0]))); + if (voiceList[0] != null) result.addPlayerVoice(FunkinSound.load(voiceList[0])); // Add opponent vocals. - if (voiceList[1] != null) result.addOpponentVoice(FunkinSound.load(Assets.getSound(voiceList[1]))); + if (voiceList[1] != null) result.addOpponentVoice(FunkinSound.load(voiceList[1])); // Add additional vocals. if (voiceList.length > 2) diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index ff5e5bfb8..6246dcb58 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -893,12 +893,6 @@ typedef SaveControlsData = */ var ?CUTSCENE_ADVANCE:Array; - /** - * Keybind for skipping a cutscene. - * @default `Escape` - */ - var ?CUTSCENE_SKIP:Array; - /** * Keybind for increasing volume. * @default `Plus` diff --git a/source/funkin/save/migrator/RawSaveData_v1_0_0.hx b/source/funkin/save/migrator/RawSaveData_v1_0_0.hx index b71102cce..a516f944a 100644 --- a/source/funkin/save/migrator/RawSaveData_v1_0_0.hx +++ b/source/funkin/save/migrator/RawSaveData_v1_0_0.hx @@ -35,7 +35,6 @@ typedef SaveControlsData_v1_0_0 = var ?ACCEPT:Array; var ?BACK:Array; var ?CUTSCENE_ADVANCE:Array; - var ?CUTSCENE_SKIP:Array; var ?NOTE_DOWN:Array; var ?NOTE_LEFT:Array; var ?NOTE_RIGHT:Array; diff --git a/source/funkin/save/migrator/SaveDataMigrator.hx b/source/funkin/save/migrator/SaveDataMigrator.hx index 92bee4ceb..f995660f7 100644 --- a/source/funkin/save/migrator/SaveDataMigrator.hx +++ b/source/funkin/save/migrator/SaveDataMigrator.hx @@ -272,7 +272,6 @@ class SaveDataMigrator ACCEPT: controlsData?.keys?.ACCEPT ?? null, BACK: controlsData?.keys?.BACK ?? null, CUTSCENE_ADVANCE: controlsData?.keys?.CUTSCENE_ADVANCE ?? null, - CUTSCENE_SKIP: controlsData?.keys?.CUTSCENE_SKIP ?? null, NOTE_DOWN: controlsData?.keys?.NOTE_DOWN ?? null, NOTE_LEFT: controlsData?.keys?.NOTE_LEFT ?? null, NOTE_RIGHT: controlsData?.keys?.NOTE_RIGHT ?? null, @@ -293,7 +292,6 @@ class SaveDataMigrator ACCEPT: controlsData?.pad?.ACCEPT ?? null, BACK: controlsData?.pad?.BACK ?? null, CUTSCENE_ADVANCE: controlsData?.pad?.CUTSCENE_ADVANCE ?? null, - CUTSCENE_SKIP: controlsData?.pad?.CUTSCENE_SKIP ?? null, NOTE_DOWN: controlsData?.pad?.NOTE_DOWN ?? null, NOTE_LEFT: controlsData?.pad?.NOTE_LEFT ?? null, NOTE_RIGHT: controlsData?.pad?.NOTE_RIGHT ?? null, diff --git a/source/funkin/ui/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx index 2c8970357..dc742874f 100644 --- a/source/funkin/ui/MusicBeatSubState.hx +++ b/source/funkin/ui/MusicBeatSubState.hx @@ -51,6 +51,7 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler override function update(elapsed:Float):Void { + // 3.59% CPU Usage (100% is FlxTypedGroup#update() and most of that is updating each member.) super.update(elapsed); // Emergency exit button. @@ -61,8 +62,11 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler // Display Conductor info in the watch window. FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0); + + // 0.09% CPU Usage? Conductor.watchQuick(); + // 4.31% CPU Usage dispatchEvent(new UpdateScriptEvent(elapsed)); } diff --git a/source/funkin/ui/debug/dialogue/ConversationDebugState.hx b/source/funkin/ui/debug/dialogue/ConversationDebugState.hx index 33a6f365a..abda3d3b1 100644 --- a/source/funkin/ui/debug/dialogue/ConversationDebugState.hx +++ b/source/funkin/ui/debug/dialogue/ConversationDebugState.hx @@ -62,15 +62,6 @@ class ConversationDebugState extends MusicBeatState if (conversation != null) { if (controls.CUTSCENE_ADVANCE) conversation.advanceConversation(); - - if (controls.CUTSCENE_SKIP) - { - conversation.trySkipConversation(elapsed); - } - else - { - conversation.trySkipConversation(-1); - } } } } diff --git a/source/funkin/ui/options/ControlsMenu.hx b/source/funkin/ui/options/ControlsMenu.hx index bda071846..62ae4b1a9 100644 --- a/source/funkin/ui/options/ControlsMenu.hx +++ b/source/funkin/ui/options/ControlsMenu.hx @@ -27,7 +27,7 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page static var controlGroups:Array> = [ [NOTE_UP, NOTE_DOWN, NOTE_LEFT, NOTE_RIGHT], [UI_UP, UI_DOWN, UI_LEFT, UI_RIGHT, ACCEPT, BACK], - [CUTSCENE_ADVANCE, CUTSCENE_SKIP], + [CUTSCENE_ADVANCE], [VOLUME_UP, VOLUME_DOWN, VOLUME_MUTE], [DEBUG_MENU, DEBUG_CHART] ]; diff --git a/source/funkin/util/plugins/MemoryGCPlugin.hx b/source/funkin/util/plugins/MemoryGCPlugin.hx new file mode 100644 index 000000000..3df861eb5 --- /dev/null +++ b/source/funkin/util/plugins/MemoryGCPlugin.hx @@ -0,0 +1,37 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; + +/** + * A plugin which adds functionality to press `Ins` to immediately perform memory garbage collection. + */ +class MemoryGCPlugin extends FlxBasic +{ + public function new() + { + super(); + } + + public static function initialize():Void + { + FlxG.plugins.addPlugin(new MemoryGCPlugin()); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + if (FlxG.keys.justPressed.INSERT) + { + var perfStart:Float = Sys.time(); + funkin.util.MemoryUtil.collect(true); + var perfEnd:Float = Sys.time(); + trace("Memory GC took " + (perfEnd - perfStart) + " seconds"); + } + } + + public override function destroy():Void + { + super.destroy(); + } +} diff --git a/source/funkin/util/tools/FlxTweenTools.hx b/source/funkin/util/tools/FlxTweenTools.hx deleted file mode 100644 index 0860af64b..000000000 --- a/source/funkin/util/tools/FlxTweenTools.hx +++ /dev/null @@ -1,25 +0,0 @@ -package funkin.util.tools; - -import flixel.tweens.FlxTween; -import flixel.tweens.FlxTween.FlxTweenManager; - -class FlxTweenTools -{ - /** - * Tween the background color of a FlxState. - * @param globalManager `flixel.tweens.FlxTween.globalManager` - * @param targetState The FlxState to tween the background color of. - * @param duration The duration of the tween. - * @param fromColor The starting color. - * @param toColor The ending color. - * @param options The options for the tween. - * @return The tween. - */ - public static function bgColor(globalManager:FlxTweenManager, targetState:FlxState, duration:Float = 1.0, fromColor:FlxColor, toColor:FlxColor, - ?options:TweenOptions):BackgroundColorTween - { - var tween = new BackgroundColorTween(options, this); - tween.tween(duration, fromColor, toColor, targetState); - globalManager.add(tween); - } -} From 4da3729058720d183d63127f0e4e9c062452cfaf Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 28 Feb 2024 00:20:06 -0500 Subject: [PATCH 05/15] Update assets submodule --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index cb0fbb56b..e0b13c91c 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit cb0fbb56b9667f68a9776a216c16a4e2b29f7096 +Subproject commit e0b13c91c2c39c877d9cd4fdc6acc90ec982cfdf From cecddbc2ee12c31641cbc38c660f16a46ec70bcb Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 28 Feb 2024 03:01:20 -0500 Subject: [PATCH 06/15] Fixed issues with audio in Week 6 after skipping the cutscene. --- source/funkin/audio/FunkinSound.hx | 7 ++- source/funkin/play/PlayState.hx | 40 ++++++++------ .../play/cutscene/dialogue/Conversation.hx | 52 ++++++++++++++----- .../play/cutscene/dialogue/DialogueBox.hx | 10 ++-- .../funkin/play/cutscene/dialogue/Speaker.hx | 3 ++ source/funkin/play/song/Song.hx | 4 +- .../debug/dialogue/ConversationDebugState.hx | 34 ++++++++++-- source/funkin/util/plugins/WatchPlugin.hx | 3 ++ 8 files changed, 114 insertions(+), 39 deletions(-) diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index 704407c14..2630a806a 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -189,10 +189,14 @@ class FunkinSound extends FlxSound implements ICloneable */ override function onFocus():Void { - if (!_alreadyPaused && this._shouldPlay) + if (!_alreadyPaused) { resume(); } + else + { + trace('Not resuming audio on focus!'); + } } /** @@ -200,6 +204,7 @@ class FunkinSound extends FlxSound implements ICloneable */ override function onFocusLost():Void { + trace('Focus lost, pausing audio!'); _alreadyPaused = _paused; pause(); } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index ca2344b0a..bb9e05638 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -770,8 +770,7 @@ class PlayState extends MusicBeatSubState inputSpitter = []; // Reset music properly. - - FlxG.sound.music.time = Math.max(0, startTimestamp - Conductor.instance.instrumentalOffset); + FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset; FlxG.sound.music.pause(); if (!overrideMusic) @@ -1054,7 +1053,10 @@ class PlayState extends MusicBeatSubState if (FlxG.sound.music != null) { musicPausedBySubState = FlxG.sound.music.playing; - FlxG.sound.music.pause(); + if (musicPausedBySubState) + { + FlxG.sound.music.pause(); + } if (vocals != null) vocals.pause(); } @@ -1082,7 +1084,7 @@ class PlayState extends MusicBeatSubState // Resume if (musicPausedBySubState) { - FlxG.sound.music.play(FlxG.sound.music.time); + FlxG.sound.music.play(); } if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals(); @@ -1284,12 +1286,6 @@ class PlayState extends MusicBeatSubState public override function destroy():Void { - if (currentConversation != null) - { - remove(currentConversation); - currentConversation.kill(); - } - performCleanup(); super.destroy(); @@ -1708,6 +1704,7 @@ class PlayState extends MusicBeatSubState currentConversation = ConversationRegistry.instance.fetchEntry(conversationId); if (currentConversation == null) return; + if (!currentConversation.alive) currentConversation.revive(); currentConversation.completeCallback = onConversationComplete; currentConversation.cameras = [camCutscene]; @@ -1725,8 +1722,13 @@ class PlayState extends MusicBeatSubState function onConversationComplete():Void { isInCutscene = false; - remove(currentConversation); - currentConversation = null; + + if (currentConversation != null) + { + currentConversation.kill(); + remove(currentConversation); + currentConversation = null; + } if (startingSong && !isInCountdown) { @@ -1751,12 +1753,14 @@ class PlayState extends MusicBeatSubState FlxG.sound.music.onComplete = endSong; // A negative instrumental offset means the song skips the first few milliseconds of the track. // This just gets added into the startTimestamp behavior so we don't need to do anything extra. - FlxG.sound.music.time = startTimestamp - Conductor.instance.instrumentalOffset; + FlxG.sound.music.play(true, startTimestamp - Conductor.instance.instrumentalOffset); + + // I am going insane. + FlxG.sound.music.volume = 1.0; + FlxG.sound.music.fadeTween.cancel(); trace('Playing vocals...'); add(vocals); - - FlxG.sound.music.play(FlxG.sound.music.time); vocals.play(); resyncVocals(); @@ -2657,6 +2661,12 @@ class PlayState extends MusicBeatSubState */ function performCleanup():Void { + if (currentConversation != null) + { + remove(currentConversation); + currentConversation.kill(); + } + if (currentChart != null) { // TODO: Uncache the song. diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx index 150f6bf6f..a85a47c49 100644 --- a/source/funkin/play/cutscene/dialogue/Conversation.hx +++ b/source/funkin/play/cutscene/dialogue/Conversation.hx @@ -150,6 +150,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl if (backdrop != null) { backdrop.destroy(); + remove(backdrop); backdrop = null; } @@ -193,7 +194,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl var nextSpeakerId:String = currentDialogueEntryData.speaker; // Skip the next steps if the current speaker is already displayed. - if (currentSpeaker != null && nextSpeakerId == currentSpeaker.id) return; + if ((currentSpeaker != null && currentSpeaker.alive) && nextSpeakerId == currentSpeaker.id) return; if (currentSpeaker != null) { @@ -244,8 +245,8 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl { var nextDialogueBoxId:String = currentDialogueEntryData?.box; - // Skip the next steps if the current speaker is already displayed. - if (currentDialogueBox != null && nextDialogueBoxId == currentDialogueBox.id) return; + // Skip the next steps if the current dialogue box is already displayed. + if ((currentDialogueBox != null && currentDialogueBox.alive) && nextDialogueBoxId == currentDialogueBox.id) return; if (currentDialogueBox != null) { @@ -343,18 +344,25 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl currentDialogueEntry = 0; this.state = ConversationState.Start; - if (outroTween != null) outroTween.cancel(); // Canc + if (outroTween != null) + { + outroTween.cancel(); + } outroTween = null; - this.alpha = 0.0; if (this.music != null) this.music.stop(); this.music = null; if (currentSpeaker != null) currentSpeaker.kill(); + remove(currentSpeaker); currentSpeaker = null; + if (currentDialogueBox != null) currentDialogueBox.kill(); + remove(currentDialogueBox); currentDialogueBox = null; - if (backdrop != null) backdrop.kill(); + + if (backdrop != null) backdrop.destroy(); + remove(backdrop); backdrop = null; startConversation(); @@ -371,7 +379,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl dispatchEvent(new DialogueScriptEvent(DIALOGUE_SKIP, this, true)); } - static var outroTween:FlxTween; + var outroTween:FlxTween; public function startOutro():Void { @@ -399,7 +407,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl public function endOutro():Void { - outroTween = null; ScriptEventDispatcher.callEvent(this, new ScriptEvent(DESTROY, false)); } @@ -534,10 +541,12 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl { propagateEvent(event); - if (outroTween != null) outroTween.cancel(); // Canc + if (outroTween != null) + { + outroTween.cancel(); + } outroTween = null; - this.alpha = 0.0; if (this.music != null) this.music.stop(); this.music = null; @@ -549,7 +558,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl remove(currentDialogueBox); currentDialogueBox = null; - if (backdrop != null) backdrop.kill(); + if (backdrop != null) backdrop.destroy(); remove(backdrop); backdrop = null; @@ -569,16 +578,27 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl */ function propagateEvent(event:ScriptEvent):Void { - if (this.currentDialogueBox != null) + if (this.currentDialogueBox != null && this.currentDialogueBox.exists) { ScriptEventDispatcher.callEvent(this.currentDialogueBox, event); } - if (this.currentSpeaker != null) + if (this.currentSpeaker != null && this.currentSpeaker.exists) { ScriptEventDispatcher.callEvent(this.currentSpeaker, event); } } + /** + * Calls `kill()` on the group's members and then on the group itself. + * You can revive this group later via `revive()` after this. + */ + public override function revive():Void + { + super.revive(); + this.alpha = 1; + this.visible = true; + } + /** * Calls `kill()` on the group's members and then on the group itself. * You can revive this group later via `revive()` after this. @@ -590,6 +610,12 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl exists = false; _skipTransformChildren = false; if (group != null) group.kill(); + + if (outroTween != null) + { + outroTween.cancel(); + outroTween = null; + } } public override function toString():String diff --git a/source/funkin/play/cutscene/dialogue/DialogueBox.hx b/source/funkin/play/cutscene/dialogue/DialogueBox.hx index eb603a352..fe4f13be0 100644 --- a/source/funkin/play/cutscene/dialogue/DialogueBox.hx +++ b/source/funkin/play/cutscene/dialogue/DialogueBox.hx @@ -4,7 +4,7 @@ import flixel.FlxSprite; import funkin.data.IRegistryEntry; import flixel.group.FlxSpriteGroup; import flixel.graphics.frames.FlxFramesCollection; -import flixel.text.FlxText; +import funkin.graphics.FunkinSprite; import flixel.addons.text.FlxTypeText; import funkin.util.assets.FlxAnimationUtil; import funkin.modding.events.ScriptEvent; @@ -126,8 +126,7 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple this.boxSprite = null; } - this.boxSprite = new FlxSprite(0, 0); - add(this.boxSprite); + this.boxSprite = new FunkinSprite(0, 0); trace('[DIALOGUE BOX] Loading spritesheet ${_data.assetPath} for ${id}'); @@ -153,6 +152,8 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple this.flipY = _data.flipY; this.globalOffsets = _data.offsets; this.setScale(_data.scale); + + add(this.boxSprite); } public function setText(newText:String):Void @@ -220,6 +221,9 @@ class DialogueBox extends FlxSpriteGroup implements IDialogueScriptedClass imple public override function revive():Void { super.revive(); + + this.visible = true; + this.alpha = 1.0; } function loadAnimations():Void diff --git a/source/funkin/play/cutscene/dialogue/Speaker.hx b/source/funkin/play/cutscene/dialogue/Speaker.hx index 0d59854a7..f848d79c8 100644 --- a/source/funkin/play/cutscene/dialogue/Speaker.hx +++ b/source/funkin/play/cutscene/dialogue/Speaker.hx @@ -119,6 +119,9 @@ class Speaker extends FlxSprite implements IDialogueScriptedClass implements IRe { super.revive(); + this.visible = true; + this.alpha = 1.0; + loadSpritesheet(); loadAnimations(); } diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 105e6db43..61f83d1ed 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -502,11 +502,11 @@ class SongDifficulty } } - public inline function playInst(volume:Float = 1.0, looped:Bool = false):Void + public function playInst(volume:Float = 1.0, looped:Bool = false):Void { var suffix:String = (variation != null && variation != '' && variation != 'default') ? '-$variation' : ''; - FlxG.sound.music = FunkinSound.load(Paths.inst(this.song.id, suffix), volume, looped); + FlxG.sound.music = FunkinSound.load(Paths.inst(this.song.id, suffix), volume, looped, false, true); // Workaround for a bug where FlxG.sound.music.update() was being called twice. FlxG.sound.list.remove(FlxG.sound.music); diff --git a/source/funkin/ui/debug/dialogue/ConversationDebugState.hx b/source/funkin/ui/debug/dialogue/ConversationDebugState.hx index abda3d3b1..f165865c4 100644 --- a/source/funkin/ui/debug/dialogue/ConversationDebugState.hx +++ b/source/funkin/ui/debug/dialogue/ConversationDebugState.hx @@ -36,11 +36,24 @@ class ConversationDebugState extends MusicBeatState public override function create():Void { - conversation = ConversationRegistry.instance.fetchEntry(conversationId); - conversation.completeCallback = onConversationComplete; - add(conversation); + super.create(); + startConversation(); + } - ScriptEventDispatcher.callEvent(conversation, new ScriptEvent(CREATE, false)); + function startConversation():Void + { + if (conversation != null) return; + + conversation = ConversationRegistry.instance.fetchEntry(conversationId); + if (conversation == null) return; + if (!conversation.alive) conversation.revive(); + + conversation.zIndex = 1000; + add(conversation); + refresh(); + + var event:ScriptEvent = new ScriptEvent(CREATE, false); + ScriptEventDispatcher.callEvent(conversation, event); } function onConversationComplete():Void @@ -61,7 +74,18 @@ class ConversationDebugState extends MusicBeatState if (conversation != null) { - if (controls.CUTSCENE_ADVANCE) conversation.advanceConversation(); + if (controls.CUTSCENE_ADVANCE) + { + conversation.advanceConversation(); + } + else if (controls.PAUSE) + { + conversation.kill(); + remove(conversation); + conversation = null; + + FlxG.switchState(() -> new ConversationDebugState()); + } } } } diff --git a/source/funkin/util/plugins/WatchPlugin.hx b/source/funkin/util/plugins/WatchPlugin.hx index defd9797d..a03115820 100644 --- a/source/funkin/util/plugins/WatchPlugin.hx +++ b/source/funkin/util/plugins/WatchPlugin.hx @@ -35,7 +35,10 @@ class WatchPlugin extends FlxBasic FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition); FlxG.watch.addQuick("songPositionNoOffset", Conductor.instance.songPosition + Conductor.instance.instrumentalOffset); + + FlxG.watch.addQuick("musicLength", FlxG.sound?.music?.length ?? 0.0); FlxG.watch.addQuick("musicTime", FlxG.sound?.music?.time ?? 0.0); + FlxG.watch.addQuick("bpm", Conductor.instance.bpm); FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentMeasureTime); FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime); From 8683900922507d028e336e377482a7a812f04934 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 28 Feb 2024 03:53:36 -0500 Subject: [PATCH 07/15] Fixes to cutscene pausing. --- source/funkin/graphics/video/FlxVideo.hx | 17 ++++++++ source/funkin/play/PauseSubState.hx | 28 +++++++++++-- source/funkin/play/PlayState.hx | 2 + source/funkin/play/cutscene/VideoCutscene.hx | 42 +++++++++++++++++++- 4 files changed, 85 insertions(+), 4 deletions(-) diff --git a/source/funkin/graphics/video/FlxVideo.hx b/source/funkin/graphics/video/FlxVideo.hx index 66829af00..5e178efb3 100644 --- a/source/funkin/graphics/video/FlxVideo.hx +++ b/source/funkin/graphics/video/FlxVideo.hx @@ -40,6 +40,23 @@ class FlxVideo extends FlxBasic netStream.play(videoPath); } + public function pauseVideo():Void + { + if (netStream != null) + { + netStream.pause(); + } + } + + public function resumeVideo():Void + { + // Resume playing the video. + if (netStream != null) + { + netStream.resume(); + } + } + public function restartVideo():Void { // Seek to the beginning of the video. diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 4f2a76e19..afed43b7d 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -82,6 +82,7 @@ class PauseSubState extends MusicBeatSubState var background:FunkinSprite; var metadata:FlxTypedSpriteGroup; var metadataPractice:FlxText; + var metadataDeaths:FlxText; var menuEntryText:FlxTypedSpriteGroup; // Audio @@ -172,9 +173,8 @@ class PauseSubState extends MusicBeatSubState metadataDifficulty.scrollFactor.set(0, 0); metadata.add(metadataDifficulty); - var metadataDeaths:FlxText = new FlxText(20, 15 + 64, FlxG.width - 40, '0 Blue Balls'); + metadataDeaths = new FlxText(20, 15 + 64, FlxG.width - 40, '${PlayState.instance?.deathCounter} Blue Balls'); metadataDeaths.setFormat(Paths.font('vcr.ttf'), 32, FlxColor.WHITE, FlxTextAlign.RIGHT); - metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls'; metadataDeaths.scrollFactor.set(0, 0); metadata.add(metadataDeaths); @@ -183,6 +183,8 @@ class PauseSubState extends MusicBeatSubState metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; metadataPractice.scrollFactor.set(0, 0); metadata.add(metadataPractice); + + updateMetadataText(); } function regenerateMenu(?targetMode:PauseMode):Void @@ -250,11 +252,28 @@ class PauseSubState extends MusicBeatSubState currentMenuEntries.remove(entry); } - metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; + updateMetadataText(); changeSelection(); } + function updateMetadataText():Void + { + metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; + + switch (this.currentMode) + { + case Standard | Difficulty: + metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls'; + case Charting: + metadataDeaths.text = 'Chart Editor Preview'; + case Conversation: + metadataDeaths.text = 'Dialogue Paused'; + case Cutscene: + metadataDeaths.text = 'Video Paused'; + } + } + function transitionIn():Void { FlxTween.tween(background, {alpha: 0.6}, 0.4, {ease: FlxEase.quartInOut}); @@ -339,6 +358,9 @@ class PauseSubState extends MusicBeatSubState // =============== static function resume(state:PauseSubState):Void { + // Resume a paused video if it exists. + VideoCutscene.resumeVideo(); + state.close(); } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index d4c0a965a..efe9cd04a 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2480,6 +2480,8 @@ class PlayState extends MusicBeatSubState // This is a video cutscene. if (controls.PAUSE && !justUnpaused) { + VideoCutscene.pauseVideo(); + var pauseSubState:FlxSubState = new PauseSubState({mode: Cutscene}); persistentUpdate = false; diff --git a/source/funkin/play/cutscene/VideoCutscene.hx b/source/funkin/play/cutscene/VideoCutscene.hx index 8786a12b2..2d31b0a28 100644 --- a/source/funkin/play/cutscene/VideoCutscene.hx +++ b/source/funkin/play/cutscene/VideoCutscene.hx @@ -112,6 +112,7 @@ class VideoCutscene { vid.zIndex = 0; vid.bitmap.onEndReached.add(finishVideo.bind(0.5)); + vid.autoPause = false; vid.cameras = [PlayState.instance.camCutscene]; @@ -136,7 +137,7 @@ class VideoCutscene } #end - public static function restartVideo():Void + public static function restartVideo(resume:Bool = true):Void { #if html5 if (vid != null) @@ -150,6 +151,45 @@ class VideoCutscene { // Seek to the start of the video. vid.bitmap.time = 0; + if (resume) + { + // Resume the video if it was paused. + vid.resume(); + } + } + #end + } + + public static function pauseVideo():Void + { + #if html5 + if (vid != null) + { + vid.pauseVideo(); + } + #end + + #if hxCodec + if (vid != null) + { + vid.pause(); + } + #end + } + + public static function resumeVideo():Void + { + #if html5 + if (vid != null) + { + vid.resumeVideo(); + } + #end + + #if hxCodec + if (vid != null) + { + vid.resume(); } #end } From 2252040241a8db150c5e0b78f2dfece09c13a5ab Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 28 Feb 2024 14:51:39 -0500 Subject: [PATCH 08/15] Fix custom pause music in Week 6, improve documentation --- assets | 2 +- source/funkin/play/PauseSubState.hx | 458 +++++++++++++----- source/funkin/play/PlayState.hx | 10 +- .../play/cutscene/dialogue/Conversation.hx | 16 + 4 files changed, 369 insertions(+), 117 deletions(-) diff --git a/assets b/assets index 7b9959492..1abf0f921 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 7b9959492306af796204f88f7b6dcaf3e0d0c702 +Subproject commit 1abf0f92177529d542181e4f3e0b07c5eceed5e8 diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index afed43b7d..4165d5d2a 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -19,8 +19,14 @@ import funkin.ui.AtlasText; import funkin.ui.MusicBeatSubState; import funkin.ui.transition.StickerSubState; +/** + * Parameters for initializing the PauseSubState. + */ typedef PauseSubStateParams = { + /** + * Which mode to start in. Dictates what entries are displayed. + */ ?mode:PauseMode, }; @@ -29,6 +35,13 @@ typedef PauseSubStateParams = */ class PauseSubState extends MusicBeatSubState { + // =============== + // Constants + // =============== + + /** + * Pause menu entries for when the game is paused during a song. + */ static final PAUSE_MENU_ENTRIES_STANDARD:Array = [ {text: 'Resume', callback: resume}, {text: 'Restart Song', callback: restartPlayState}, @@ -37,17 +50,26 @@ class PauseSubState extends MusicBeatSubState {text: 'Exit to Menu', callback: quitToMenu}, ]; + /** + * Pause menu entries for when the game is paused in the Chart Editor preview. + */ static final PAUSE_MENU_ENTRIES_CHARTING:Array = [ {text: 'Resume', callback: resume}, {text: 'Restart Song', callback: restartPlayState}, {text: 'Return to Chart Editor', callback: quitToChartEditor}, ]; + /** + * Pause menu entries for when the user selects "Change Difficulty". + */ static final PAUSE_MENU_ENTRIES_DIFFICULTY:Array = [ {text: 'Back', callback: switchMode.bind(_, Standard)} // Other entries are added dynamically. ]; + /** + * Pause menu entries for when the game is paused during a video cutscene. + */ static final PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE:Array = [ {text: 'Resume', callback: resume}, {text: 'Restart Cutscene', callback: restartVideoCutscene}, @@ -55,6 +77,9 @@ class PauseSubState extends MusicBeatSubState {text: 'Exit to Menu', callback: quitToMenu}, ]; + /** + * Pause menu entries for when the game is paused during a conversation. + */ static final PAUSE_MENU_ENTRIES_CONVERSATION:Array = [ {text: 'Resume', callback: resume}, {text: 'Restart Dialogue', callback: restartConversation}, @@ -62,38 +87,105 @@ class PauseSubState extends MusicBeatSubState {text: 'Exit to Menu', callback: quitToMenu}, ]; - static final MUSIC_FADE_IN_TIME:Float = 50; - static final MUSIC_FINAL_VOLUME:Float = 0.5; - - public static var musicSuffix:String = ''; - - // Status - var currentMenuEntries:Array; - var currentEntry:Int = 0; - var currentMode:PauseMode; + /** + * Duration for the music to fade in when the pause menu is opened. + */ + static final MUSIC_FADE_IN_TIME:Float = 5; /** - * Disallow input until the transition in is complete! - * This prevents the pause menu from immediately closing. + * The final volume for the music when the pause menu is opened. + */ + static final MUSIC_FINAL_VOLUME:Float = 0.75; + + /** + * Defines which pause music to use. + */ + public static var musicSuffix:String = ''; + + /** + * Reset the pause configuration to the default. + */ + public static function reset():Void + { + musicSuffix = ''; + } + + // =============== + // Status Variables + // =============== + + /** + * Disallow input until transitions are complete! + * This prevents the pause menu from immediately closing when opened, among other things. */ public var allowInput:Bool = false; - // Graphics + /** + * The entries currently displayed in the pause menu. + */ + var currentMenuEntries:Array; + + /** + * The index of `currentMenuEntries` that is currently selected. + */ + var currentEntry:Int = 0; + + /** + * The mode that the pause menu is currently in. + */ + var currentMode:PauseMode; + + // =============== + // Graphics Variables + // =============== + + /** + * The semi-transparent black background that appears when the game is paused. + */ var background:FunkinSprite; + + /** + * The metadata displayed in the top right. + */ var metadata:FlxTypedSpriteGroup; + + /** + * A text object that displays the current practice mode status. + */ var metadataPractice:FlxText; + + /** + * A text object that displays the current death count. + */ var metadataDeaths:FlxText; + + /** + * The actual text objects for the menu entries. + */ var menuEntryText:FlxTypedSpriteGroup; - // Audio + // =============== + // Audio Variables + // =============== var pauseMusic:FunkinSound; + // =============== + // Constructor + // =============== + public function new(?params:PauseSubStateParams) { super(); this.currentMode = params?.mode ?? Standard; } + // =============== + // Lifecycle Functions + // =============== + + /** + * Called when the state is first loaded. + */ public override function create():Void { super.create(); @@ -104,15 +196,15 @@ class PauseSubState extends MusicBeatSubState buildMetadata(); - menuEntryText = new FlxTypedSpriteGroup(); - menuEntryText.scrollFactor.set(0, 0); - add(menuEntryText); - regenerateMenu(); transitionIn(); } + /** + * Called every frame. + * @param elapsed The time elapsed since the last frame, in seconds. + */ public override function update(elapsed:Float):Void { super.update(elapsed); @@ -120,21 +212,40 @@ class PauseSubState extends MusicBeatSubState handleInputs(); } + /** + * Called when the state is closed. + */ public override function destroy():Void { super.destroy(); pauseMusic.stop(); } + // =============== + // Initialization Functions + // =============== + + /** + * Play the pause music. + */ function startPauseMusic():Void { - pauseMusic = FunkinSound.load(Paths.music('breakfast$musicSuffix'), true, true); + var pauseMusicPath:String = Paths.music('breakfast$musicSuffix'); + pauseMusic = FunkinSound.load(pauseMusicPath, true, true); + + if (pauseMusic == null) + { + FlxG.log.warn('Could not play pause music: ${pauseMusicPath} does not exist!'); + } // Start playing at a random point in the song. pauseMusic.play(false, FlxG.random.int(0, Std.int(pauseMusic.length / 2))); pauseMusic.fadeIn(MUSIC_FADE_IN_TIME, 0, MUSIC_FINAL_VOLUME); } + /** + * Render the semi-transparent black background. + */ function buildBackground():Void { // Using state.bgColor causes bugs! @@ -187,93 +298,9 @@ class PauseSubState extends MusicBeatSubState updateMetadataText(); } - function regenerateMenu(?targetMode:PauseMode):Void - { - var previousMode:PauseMode = this.currentMode; - this.currentMode = targetMode ?? this.currentMode; - this.currentEntry = 0; - - menuEntryText.clear(); - - // Choose the correct menu entries. - switch (this.currentMode) - { - case PauseMode.Standard: - currentMenuEntries = PAUSE_MENU_ENTRIES_STANDARD.clone(); - case PauseMode.Charting: - currentMenuEntries = PAUSE_MENU_ENTRIES_CHARTING.clone(); - case PauseMode.Difficulty: - // Prepend the difficulties. - var entries:Array = []; - if (PlayState.instance.currentChart != null) - { - var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation); - trace('DIFFICULTIES: ${difficultiesInVariation}'); - for (difficulty in difficultiesInVariation) - { - entries.push({text: difficulty.toTitleCase(), callback: (state) -> changeDifficulty(state, difficulty)}); - } - } - - // Add the back button. - currentMenuEntries = entries.concat(PAUSE_MENU_ENTRIES_DIFFICULTY.clone()); - case PauseMode.Conversation: - currentMenuEntries = PAUSE_MENU_ENTRIES_CONVERSATION.clone(); - case PauseMode.Cutscene: - currentMenuEntries = PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE.clone(); - } - - // Render out the entries depending on the mode. - var entryIndex:Int = 0; - var toRemove = []; - for (entry in currentMenuEntries) - { - if (entry == null || (entry.filter != null && !entry.filter())) - { - // Remove entries that should be hidden. - toRemove.push(entry); - } - else - { - // Handle visible entries. - var yPos:Float = 70 * entryIndex + 30; - var text:AtlasText = new AtlasText(0, yPos, entry.text, AtlasFont.BOLD); - text.scrollFactor.set(0, 0); - text.alpha = 0; - menuEntryText.add(text); - - entry.sprite = text; - - entryIndex++; - } - } - for (entry in toRemove) - { - currentMenuEntries.remove(entry); - } - - updateMetadataText(); - - changeSelection(); - } - - function updateMetadataText():Void - { - metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; - - switch (this.currentMode) - { - case Standard | Difficulty: - metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls'; - case Charting: - metadataDeaths.text = 'Chart Editor Preview'; - case Conversation: - metadataDeaths.text = 'Dialogue Paused'; - case Cutscene: - metadataDeaths.text = 'Video Paused'; - } - } - + /** + * Perform additional animations to transition the pause menu in when it is first displayed. + */ function transitionIn():Void { FlxTween.tween(background, {alpha: 0.6}, 0.4, {ease: FlxEase.quartInOut}); @@ -291,6 +318,13 @@ class PauseSubState extends MusicBeatSubState }); } + // =============== + // Input Handling + // =============== + + /** + * Process user inputs every frame. + */ function handleInputs():Void { if (!allowInput) return; @@ -326,6 +360,10 @@ class PauseSubState extends MusicBeatSubState #end } + /** + * Move the current selection up or down. + * @param change The amount to change the selection by, with sign indicating direction. + */ function changeSelection(change:Int = 0):Void { FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); @@ -353,9 +391,148 @@ class PauseSubState extends MusicBeatSubState } } + // =============== + // Menu Functions + // =============== + + /** + * Clear the current menu entries and regenerate them based on the current mode. + * @param targetMode Optionally specify a mode to switch to before regenerating the menu. + */ + function regenerateMenu(?targetMode:PauseMode):Void + { + // If targetMode is null, keep the current mode. + if (targetMode == null) targetMode = this.currentMode; + + var previousMode:PauseMode = this.currentMode; + this.currentMode = targetMode; + + resetSelection(); + chooseMenuEntries(); + clearAndAddMenuEntries(); + updateMetadataText(); + changeSelection(); + } + + /** + * Reset the current selection to the first entry. + */ + function resetSelection():Void + { + this.currentEntry = 0; + } + + /** + * Select which menu entries to display based on the current mode. + */ + function chooseMenuEntries():Void + { + // Choose the correct menu entries. + // NOTE: We clone the arrays to prevent modifications to the arrays from affecting the original. + switch (this.currentMode) + { + case PauseMode.Standard: + currentMenuEntries = PAUSE_MENU_ENTRIES_STANDARD.clone(); + case PauseMode.Charting: + currentMenuEntries = PAUSE_MENU_ENTRIES_CHARTING.clone(); + case PauseMode.Difficulty: + // Prepend the difficulties. + var entries:Array = []; + if (PlayState.instance.currentChart != null) + { + var difficultiesInVariation = PlayState.instance.currentSong.listDifficulties(PlayState.instance.currentChart.variation); + trace('DIFFICULTIES: ${difficultiesInVariation}'); + for (difficulty in difficultiesInVariation) + { + entries.push({text: difficulty.toTitleCase(), callback: (state) -> changeDifficulty(state, difficulty)}); + } + } + + // Add the back button. + currentMenuEntries = entries.concat(PAUSE_MENU_ENTRIES_DIFFICULTY.clone()); + case PauseMode.Conversation: + currentMenuEntries = PAUSE_MENU_ENTRIES_CONVERSATION.clone(); + case PauseMode.Cutscene: + currentMenuEntries = PAUSE_MENU_ENTRIES_VIDEO_CUTSCENE.clone(); + } + } + + /** + * Clear the `menuEntryText` group and render the current menu entries to it. + * We first create the `menuEntryText` group if it doesn't already exist. + */ + function clearAndAddMenuEntries():Void + { + if (menuEntryText == null) + { + menuEntryText = new FlxTypedSpriteGroup(); + menuEntryText.scrollFactor.set(0, 0); + add(menuEntryText); + } + menuEntryText.clear(); + + // Render out the entries depending on the mode. + var entryIndex:Int = 0; + var toRemove = []; + for (entry in currentMenuEntries) + { + if (entry == null || (entry.filter != null && !entry.filter())) + { + // Remove entries that should be hidden. + toRemove.push(entry); + } + else + { + // Handle visible entries. + var yPos:Float = 70 * entryIndex + 30; + var text:AtlasText = new AtlasText(0, yPos, entry.text, AtlasFont.BOLD); + text.scrollFactor.set(0, 0); + text.alpha = 0; + menuEntryText.add(text); + + entry.sprite = text; + + entryIndex++; + } + } + for (entry in toRemove) + { + currentMenuEntries.remove(entry); + } + } + + // =============== + // Metadata Functions + // =============== + + /** + * Update the values for the metadata text in the top right. + */ + function updateMetadataText():Void + { + metadataPractice.visible = PlayState.instance?.isPracticeMode ?? false; + + switch (this.currentMode) + { + case Standard | Difficulty: + metadataDeaths.text = '${PlayState.instance?.deathCounter} Blue Balls'; + case Charting: + metadataDeaths.text = 'Chart Editor Preview'; + case Conversation: + metadataDeaths.text = 'Dialogue Paused'; + case Cutscene: + metadataDeaths.text = 'Video Paused'; + } + } + // =============== // Menu Callbacks // =============== + + /** + * Close the pause menu and resume the game. + * @param state The current PauseSubState. + */ static function resume(state:PauseSubState):Void { // Resume a paused video if it exists. @@ -364,11 +541,22 @@ class PauseSubState extends MusicBeatSubState state.close(); } + /** + * Switch the pause menu to the indicated mode. + * Create a callback from this using `.bind(_, targetMode)`. + * @param state The current PauseSubState. + * @param targetMode The mode to switch to. + */ static function switchMode(state:PauseSubState, targetMode:PauseMode):Void { state.regenerateMenu(targetMode); } + /** + * Switch the game's difficulty to the indicated difficulty, then resume the game. + * @param state The current PauseSubState. + * @param difficulty The difficulty to switch to. + */ static function changeDifficulty(state:PauseSubState, difficulty:String):Void { PlayState.instance.currentSong = SongRegistry.instance.fetchEntry(PlayState.instance.currentSong.id.toLowerCase()); @@ -384,12 +572,20 @@ class PauseSubState extends MusicBeatSubState state.close(); } + /** + * Restart the current level, then resume the game. + * @param state The current PauseSubState. + */ static function restartPlayState(state:PauseSubState):Void { PlayState.instance.needsReset = true; state.close(); } + /** + * Force the game into practice mode, then update the pause menu. + * @param state The current PauseSubState. + */ static function enablePracticeMode(state:PauseSubState):Void { if (PlayState.instance == null) return; @@ -398,18 +594,30 @@ class PauseSubState extends MusicBeatSubState state.regenerateMenu(); } + /** + * Restart the paused video cutscene, then resume the game. + * @param state The current PauseSubState. + */ static function restartVideoCutscene(state:PauseSubState):Void { VideoCutscene.restartVideo(); state.close(); } + /** + * Skip the paused video cutscene, then resume the game. + * @param state The current PauseSubState. + */ static function skipVideoCutscene(state:PauseSubState):Void { VideoCutscene.finishVideo(); state.close(); } + /** + * Restart the paused conversation, then resume the game. + * @param state The current PauseSubState. + */ static function restartConversation(state:PauseSubState):Void { if (PlayState.instance?.currentConversation == null) return; @@ -418,6 +626,10 @@ class PauseSubState extends MusicBeatSubState state.close(); } + /** + * Skip the paused conversation, then resume the game. + * @param state The current PauseSubState. + */ static function skipConversation(state:PauseSubState):Void { if (PlayState.instance?.currentConversation == null) return; @@ -426,6 +638,10 @@ class PauseSubState extends MusicBeatSubState state.close(); } + /** + * Quit the game and return to the main menu. + * @param state The current PauseSubState. + */ static function quitToMenu(state:PauseSubState):Void { state.allowInput = false; @@ -446,20 +662,16 @@ class PauseSubState extends MusicBeatSubState } } + /** + * Quit the game and return to the chart editor. + * @param state The current PauseSubState. + */ static function quitToChartEditor(state:PauseSubState):Void { state.close(); if (FlxG.sound.music != null) FlxG.sound.music.pause(); // Don't reset song position! PlayState.instance.close(); // This only works because PlayState is a substate! } - - /** - * Reset the pause configuration to the default. - */ - public static function reset():Void - { - musicSuffix = ''; - } } /** @@ -493,15 +705,31 @@ enum PauseMode Cutscene; } +/** + * Represents a single entry in the pause menu. + */ typedef PauseMenuEntry = { + /** + * The text to display for this entry. + * TODO: Implement localization. + */ var text:String; - var callback:PauseSubState->Void; - var ?sprite:AtlasText; + /** + * The callback to execute when the user selects this entry. + */ + var callback:PauseSubState->Void; /** * If this returns true, the entry will be displayed. If it returns false, the entry will be hidden. */ var ?filter:Void->Bool; + + // Instance-specific properties + + /** + * The text object currently displaying this entry. + */ + var ?sprite:AtlasText; }; diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index efe9cd04a..e00d4db48 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1099,6 +1099,11 @@ class PlayState extends MusicBeatSubState FlxG.sound.music.play(); } + if (currentConversation != null) + { + currentConversation.resumeMusic(); + } + if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals(); // Resume the countdown. @@ -2462,10 +2467,12 @@ class PlayState extends MusicBeatSubState // Pause/unpause may conflict with advancing the conversation! if (controls.CUTSCENE_ADVANCE && !justUnpaused) { - currentConversation?.advanceConversation(); + currentConversation.advanceConversation(); } else if (controls.PAUSE && !justUnpaused) { + currentConversation.pauseMusic(); + var pauseSubState:FlxSubState = new PauseSubState({mode: Conversation}); persistentUpdate = false; @@ -2740,6 +2747,7 @@ class PlayState extends MusicBeatSubState } GameOverSubState.reset(); + PauseSubState.reset(); // Clear the static reference to this state. instance = null; diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx index a85a47c49..f865f3b7b 100644 --- a/source/funkin/play/cutscene/dialogue/Conversation.hx +++ b/source/funkin/play/cutscene/dialogue/Conversation.hx @@ -145,6 +145,22 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl music.play(); } + public function pauseMusic():Void + { + if (music != null) + { + music.pause(); + } + } + + public function resumeMusic():Void + { + if (music != null) + { + music.resume(); + } + } + function setupBackdrop():Void { if (backdrop != null) From 1d70ff95f9d2073d8ab761a6bb26771ffc73baa7 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 28 Feb 2024 21:19:21 -0500 Subject: [PATCH 09/15] Several chart editor fixes (more precise playback speed, live input mode loads properly from save) --- assets | 2 +- source/funkin/play/PlayState.hx | 13 +++++++++--- source/funkin/play/components/PopUpStuff.hx | 18 +++-------------- .../ui/debug/charting/ChartEditorState.hx | 20 +++++++++++++------ 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/assets b/assets index 84b157429..a1c3d095a 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 84b1574294e7a7af21adf1a0e4894c88431ca43d +Subproject commit a1c3d095a3c16db88c010267d1a7f9695506c66c diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 5bbf83e17..07cf6d352 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -631,8 +631,9 @@ class PlayState extends MusicBeatSubState // Initialize the judgements and combo meter. comboPopUps = new PopUpStuff(); - comboPopUps.cameras = [camHUD]; + comboPopUps.zIndex = 900; add(comboPopUps); + comboPopUps.cameras = [camHUD]; // The little dial that shows up when you hold the Skip Cutscene key. skipTimer = new FlxPieDial(16, 16, 32, FlxColor.WHITE, 36, CIRCLE, true, 24); @@ -1277,6 +1278,7 @@ class PlayState extends MusicBeatSubState { var animShit:ComboMilestone = new ComboMilestone(-100, 300, Highscore.tallies.combo); animShit.scrollFactor.set(0.6, 0.6); + animShit.zIndex = 1100; animShit.cameras = [camHUD]; add(animShit); @@ -1363,18 +1365,21 @@ class PlayState extends MusicBeatSubState healthBarBG = FunkinSprite.create(0, healthBarYPos, Paths.image('healthBar')); healthBarBG.screenCenter(X); healthBarBG.scrollFactor.set(0, 0); + healthBarBG.zIndex = 800; add(healthBarBG); healthBar = new FlxBar(healthBarBG.x + 4, healthBarBG.y + 4, RIGHT_TO_LEFT, Std.int(healthBarBG.width - 8), Std.int(healthBarBG.height - 8), this, 'healthLerp', 0, 2); healthBar.scrollFactor.set(); healthBar.createFilledBar(Constants.COLOR_HEALTH_BAR_RED, Constants.COLOR_HEALTH_BAR_GREEN); + healthBar.zIndex = 801; add(healthBar); // The score text below the health bar. scoreText = new FlxText(healthBarBG.x + healthBarBG.width - 190, healthBarBG.y + 30, 0, '', 20); scoreText.setFormat(Paths.font('vcr.ttf'), 16, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); scoreText.scrollFactor.set(); + scoreText.zIndex = 802; add(scoreText); // Move the health bar to the HUD camera. @@ -1488,6 +1493,7 @@ class PlayState extends MusicBeatSubState iconP2 = new HealthIcon('dad', 1); iconP2.y = healthBar.y - (iconP2.height / 2); dad.initHealthIcon(true); // Apply the character ID here + iconP2.zIndex = 850; add(iconP2); iconP2.cameras = [camHUD]; @@ -1507,6 +1513,7 @@ class PlayState extends MusicBeatSubState iconP1 = new HealthIcon('bf', 0); iconP1.y = healthBar.y - (iconP1.height / 2); boyfriend.initHealthIcon(false); // Apply the character ID here + iconP1.zIndex = 850; add(iconP1); iconP1.cameras = [camHUD]; @@ -1574,13 +1581,13 @@ class PlayState extends MusicBeatSubState playerStrumline.x = FlxG.width / 2 + Constants.STRUMLINE_X_OFFSET; // Classic style // playerStrumline.x = FlxG.width - playerStrumline.width - Constants.STRUMLINE_X_OFFSET; // Centered style playerStrumline.y = Preferences.downscroll ? FlxG.height - playerStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET; - playerStrumline.zIndex = 200; + playerStrumline.zIndex = 1001; playerStrumline.cameras = [camHUD]; // Position the opponent strumline on the left half of the screen opponentStrumline.x = Constants.STRUMLINE_X_OFFSET; opponentStrumline.y = Preferences.downscroll ? FlxG.height - opponentStrumline.height - Constants.STRUMLINE_Y_OFFSET : Constants.STRUMLINE_Y_OFFSET; - opponentStrumline.zIndex = 100; + opponentStrumline.zIndex = 1000; opponentStrumline.cameras = [camHUD]; if (!PlayStatePlaylist.isStoryMode) diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx index 88ffa468c..faab5e4dc 100644 --- a/source/funkin/play/components/PopUpStuff.hx +++ b/source/funkin/play/components/PopUpStuff.hx @@ -5,6 +5,7 @@ import flixel.group.FlxGroup.FlxTypedGroup; import flixel.tweens.FlxTween; import funkin.graphics.FunkinSprite; import funkin.play.PlayState; +import flixel.util.FlxDirection; class PopUpStuff extends FlxTypedGroup { @@ -30,14 +31,7 @@ class PopUpStuff extends FlxTypedGroup rating.zIndex = 1000; rating.x = FlxG.width * 0.50; - rating.x -= FlxG.camera.scroll.x * 0.2; - // make sure rating is visible lol! - // if (rating.x < FlxG.camera.scroll.x) - // rating.x = FlxG.camera.scroll.x; - // else if (rating.x > FlxG.camera.scroll.x + FlxG.camera.width - rating.width) - // rating.x = FlxG.camera.scroll.x + FlxG.camera.width - rating.width; - - // FlxG.camera.scroll.y + + // rating.x -= FlxG.camera.scroll.x * 0.2; rating.y = FlxG.camera.height * 0.4 - 60; rating.acceleration.y = 550; rating.velocity.y -= FlxG.random.int(140, 175); @@ -91,13 +85,7 @@ class PopUpStuff extends FlxTypedGroup var comboSpr:FunkinSprite = FunkinSprite.create(Paths.image(pixelShitPart1 + 'combo' + pixelShitPart2)); comboSpr.y = FlxG.camera.height * 0.4 + 80; comboSpr.x = FlxG.width * 0.50; - comboSpr.x -= FlxG.camera.scroll.x * 0.2; - // make sure combo is visible lol! - // 194 fits 4 combo digits - // if (comboSpr.x < FlxG.camera.scroll.x + 194) - // comboSpr.x = FlxG.camera.scroll.x + 194; - // else if (comboSpr.x > FlxG.camera.scroll.x + FlxG.camera.width - comboSpr.width) - // comboSpr.x = FlxG.camera.scroll.x + FlxG.camera.width - comboSpr.width; + // comboSpr.x -= FlxG.camera.scroll.x * 0.2; comboSpr.acceleration.y = 600; comboSpr.velocity.y -= 150; diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 768ea9e43..e46779483 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -2838,12 +2838,15 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menuBarItemInputStyleNone.onClick = function(event:UIEvent) { currentLiveInputStyle = None; }; + menuBarItemInputStyleNone.selected = currentLiveInputStyle == None; menuBarItemInputStyleNumberKeys.onClick = function(event:UIEvent) { currentLiveInputStyle = NumberKeys; }; + menuBarItemInputStyleNumberKeys.selected = currentLiveInputStyle == NumberKeys; menuBarItemInputStyleWASD.onClick = function(event:UIEvent) { - currentLiveInputStyle = WASD; + currentLiveInputStyle = WASDKeys; }; + menuBarItemInputStyleWASD.selected = currentLiveInputStyle == WASDKeys; menubarItemAbout.onClick = _ -> this.openAboutDialog(); menubarItemWelcomeDialog.onClick = _ -> this.openWelcomeDialog(true); @@ -2942,7 +2945,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemPlaybackSpeed.onChange = event -> { var pitch:Float = (event.value.toFloat() * 2.0) / 100.0; - pitch = Math.floor(pitch / 0.25) * 0.25; // Round to nearest 0.25. + pitch = Math.floor(pitch / 0.05) * 0.05; // Round to nearest 5% + pitch = Math.max(0.05, Math.min(2.0, pitch)); // Clamp to 5% to 200% #if FLX_PITCH if (audioInstTrack != null) audioInstTrack.pitch = pitch; audioVocalTrackGroup.pitch = pitch; @@ -4933,7 +4937,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Place notes at the playhead. switch (currentLiveInputStyle) { - case ChartEditorLiveInputStyle.WASD: + case ChartEditorLiveInputStyle.WASDKeys: if (FlxG.keys.justPressed.A) placeNoteAtPlayhead(4); if (FlxG.keys.justPressed.S) placeNoteAtPlayhead(5); if (FlxG.keys.justPressed.W) placeNoteAtPlayhead(6); @@ -5236,6 +5240,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } // Would bind Ctrl+A and Ctrl+D here, but they are already bound to Select All and Select None. } + else + { + trace('Ignoring keybinds for View menu items because we are in live input mode (${currentLiveInputStyle}).'); + } } /** @@ -6125,7 +6133,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } /** - * Available input modes for the chart editor state. + * Available input modes for the chart editor state. Numbers/arrows/WASD available for other keybinds. */ enum ChartEditorLiveInputStyle { @@ -6140,9 +6148,9 @@ enum ChartEditorLiveInputStyle NumberKeys; /** - * WASD to place notes on opponent's side, arrow keys to place notes on player's side. + * WASD to place notes on opponent's side, Arrow keys to place notes on player's side. */ - WASD; + WASDKeys; } typedef ChartEditorParams = From 0139d5c4f6d941264622e7849c907d4e252cdeca Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 29 Feb 2024 01:19:32 -0500 Subject: [PATCH 10/15] Fix a crash on atlas characters. --- source/funkin/play/character/AnimateAtlasCharacter.hx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/funkin/play/character/AnimateAtlasCharacter.hx b/source/funkin/play/character/AnimateAtlasCharacter.hx index f9dc18119..418982bef 100644 --- a/source/funkin/play/character/AnimateAtlasCharacter.hx +++ b/source/funkin/play/character/AnimateAtlasCharacter.hx @@ -191,7 +191,11 @@ class AnimateAtlasCharacter extends BaseCharacter _skipTransformChildren = true; super.kill(); _skipTransformChildren = false; - this.mainSprite.kill(); + if (this.mainSprite != null) + { + this.mainSprite.kill(); + this.mainSprite = null; + } } /** From 1102c483fd265126be77c72ee46619990dc2aea9 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 29 Feb 2024 01:19:51 -0500 Subject: [PATCH 11/15] Update assets submodule. --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index a1c3d095a..8b914574f 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit a1c3d095a3c16db88c010267d1a7f9695506c66c +Subproject commit 8b914574fc4724c5fe483f4f9d81081bb1518c12 From 2e012d54a43872d8bdb40688fe19f960e35a8a24 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 29 Feb 2024 02:26:29 -0500 Subject: [PATCH 12/15] assets submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 1abf0f921..8b914574f 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 1abf0f92177529d542181e4f3e0b07c5eceed5e8 +Subproject commit 8b914574fc4724c5fe483f4f9d81081bb1518c12 From 0193c1ddeaf78e0c1018098a98ce34f9a4fb7f2a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 29 Feb 2024 02:50:04 -0500 Subject: [PATCH 13/15] Update hxCodec to add autoFocus --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index 700b42dfe..6ec068c52 100644 --- a/hmm.json +++ b/hmm.json @@ -73,7 +73,7 @@ "name": "hxCodec", "type": "git", "dir": null, - "ref": "c8c47e706ad82a423783006ed901b6d93c89a421", + "ref": "387e1665d6feb5762358134f168e6ebfe46acec8", "url": "https://github.com/polybiusproxy/hxCodec" }, { From aab050d757c2617e80a2649a07023087f24d7587 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 29 Feb 2024 13:44:53 -0500 Subject: [PATCH 14/15] trolled use FunkinCrew for hxcodec in hmm.json --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index 6ec068c52..836b01c9b 100644 --- a/hmm.json +++ b/hmm.json @@ -74,7 +74,7 @@ "type": "git", "dir": null, "ref": "387e1665d6feb5762358134f168e6ebfe46acec8", - "url": "https://github.com/polybiusproxy/hxCodec" + "url": "https://github.com/FunkinCrew/hxCodec" }, { "name": "hxcpp", From 88f57cf52078c3111288722a86e26ff9a7d1017f Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Thu, 29 Feb 2024 13:53:09 -0500 Subject: [PATCH 15/15] pause menu tween polish --- source/funkin/play/PauseSubState.hx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 4165d5d2a..2b705ea9e 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -303,14 +303,14 @@ class PauseSubState extends MusicBeatSubState */ function transitionIn():Void { - FlxTween.tween(background, {alpha: 0.6}, 0.4, {ease: FlxEase.quartInOut}); + FlxTween.tween(background, {alpha: 0.6}, 0.8, {ease: FlxEase.quartOut}); // Animate each element a little bit downwards. var delay:Float = 0.1; for (child in metadata.members) { - FlxTween.tween(child, {alpha: 1, y: child.y + 5}, 0.4, {ease: FlxEase.quartInOut, startDelay: delay}); - delay += 0.05; + FlxTween.tween(child, {alpha: 1, y: child.y + 5}, 1.8, {ease: FlxEase.quartOut, startDelay: delay}); + delay += 0.1; } new FlxTimer().start(0.2, (_) -> { @@ -387,7 +387,8 @@ class PauseSubState extends MusicBeatSubState var targetX = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 20 + 90; var targetY = FlxMath.remapToRange((entryIndex - currentEntry), 0, 1, 0, 1.3) * 120 + (FlxG.height * 0.48); trace(targetY); - FlxTween.tween(text, {x: targetX, y: targetY}, 0.16, {ease: FlxEase.linear}); + FlxTween.globalManager.cancelTweensOf(text); + FlxTween.tween(text, {x: targetX, y: targetY}, 0.33, {ease: FlxEase.quartOut}); } }