From 5ff546baccf803127087faea055537dd0a1e5b05 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 26 Jul 2023 16:52:58 -0400 Subject: [PATCH 01/29] First iteration of song playtesting from editor! --- .../transition/FlxTransitionableSubState.hx | 234 ++++++++++++++++++ source/funkin/Controls.hx | 1 - source/funkin/FreeplayState.hx | 7 + source/funkin/MainMenuState.hx | 1 - source/funkin/MusicBeatState.hx | 4 +- source/funkin/MusicBeatSubState.hx | 21 +- source/funkin/PauseSubState.hx | 25 +- source/funkin/play/PlayState.hx | 110 ++++++-- source/funkin/play/song/Song.hx | 67 +++-- source/funkin/play/song/SongData.hx | 4 +- source/funkin/ui/debug/DebugMenuSubState.hx | 4 + .../charting/ChartEditorDialogHandler.hx | 2 + .../ui/debug/charting/ChartEditorState.hx | 78 +++++- source/funkin/ui/story/StoryMenuState.hx | 7 + source/funkin/util/tools/ArrayTools.hx | 11 + 15 files changed, 520 insertions(+), 56 deletions(-) create mode 100644 source/flixel/addons/transition/FlxTransitionableSubState.hx diff --git a/source/flixel/addons/transition/FlxTransitionableSubState.hx b/source/flixel/addons/transition/FlxTransitionableSubState.hx new file mode 100644 index 000000000..7bb536bb2 --- /dev/null +++ b/source/flixel/addons/transition/FlxTransitionableSubState.hx @@ -0,0 +1,234 @@ +package flixel.addons.transition; + +import flixel.FlxSubState; +import flixel.addons.transition.FlxTransitionableState; + +/** + * A `FlxSubState` which can perform visual transitions + * + * Usage: + * + * First, extend `FlxTransitionableSubState` as ie, `FooState`. + * + * Method 1: + * + * ```haxe + * var in:TransitionData = new TransitionData(...); // add your data where "..." is + * var out:TransitionData = new TransitionData(...); + * + * FlxG.switchState(new FooState(in,out)); + * ``` + * + * Method 2: + * + * ```haxe + * FlxTransitionableSubState.defaultTransIn = new TransitionData(...); + * FlxTransitionableSubState.defaultTransOut = new TransitionData(...); + * + * FlxG.switchState(new FooState()); + * ``` + */ +class FlxTransitionableSubState extends FlxSubState +{ + // global default transitions for ALL states, used if transIn/transOut are null + public static var defaultTransIn(get, set):TransitionData; + + static function get_defaultTransIn():TransitionData + { + return FlxTransitionableState.defaultTransIn; + } + + static function set_defaultTransIn(value:TransitionData):TransitionData + { + return FlxTransitionableState.defaultTransIn = value; + } + + public static var defaultTransOut(get, set):TransitionData; + + static function get_defaultTransOut():TransitionData + { + return FlxTransitionableState.defaultTransOut; + } + + static function set_defaultTransOut(value:TransitionData):TransitionData + { + return FlxTransitionableState.defaultTransOut = value; + } + + public static var skipNextTransIn(get, set):Bool; + + static function get_skipNextTransIn():Bool + { + return FlxTransitionableState.skipNextTransIn; + } + + static function set_skipNextTransIn(value:Bool):Bool + { + return FlxTransitionableState.skipNextTransIn = value; + } + + public static var skipNextTransOut(get, set):Bool; + + static function get_skipNextTransOut():Bool + { + return FlxTransitionableState.skipNextTransOut; + } + + static function set_skipNextTransOut(value:Bool):Bool + { + return FlxTransitionableState.skipNextTransOut = value; + } + + // beginning & ending transitions for THIS state: + public var transIn:TransitionData; + public var transOut:TransitionData; + + public var hasTransIn(get, never):Bool; + public var hasTransOut(get, never):Bool; + + /** + * Create a state with the ability to do visual transitions + * @param TransIn Plays when the state begins + * @param TransOut Plays when the state ends + */ + public function new(?TransIn:TransitionData, ?TransOut:TransitionData) + { + transIn = TransIn; + transOut = TransOut; + + if (transIn == null && defaultTransIn != null) + { + transIn = defaultTransIn; + } + if (transOut == null && defaultTransOut != null) + { + transOut = defaultTransOut; + } + super(); + } + + override function destroy():Void + { + super.destroy(); + transIn = null; + transOut = null; + _onExit = null; + } + + override function create():Void + { + super.create(); + transitionIn(); + } + + override function startOutro(onOutroComplete:() -> Void) + { + if (!hasTransOut) onOutroComplete(); + else if (!_exiting) + { + // play the exit transition, and when it's done call FlxG.switchState + _exiting = true; + transitionOut(onOutroComplete); + + if (skipNextTransOut) + { + skipNextTransOut = false; + finishTransOut(); + } + } + } + + /** + * Starts the in-transition. Can be called manually at any time. + */ + public function transitionIn():Void + { + if (transIn != null && transIn.type != NONE) + { + if (skipNextTransIn) + { + skipNextTransIn = false; + if (finishTransIn != null) + { + finishTransIn(); + } + return; + } + + var _trans = createTransition(transIn); + + _trans.setStatus(FULL); + openSubState(_trans); + + _trans.finishCallback = finishTransIn; + _trans.start(OUT); + } + } + + /** + * Starts the out-transition. Can be called manually at any time. + */ + public function transitionOut(?OnExit:Void->Void):Void + { + _onExit = OnExit; + if (hasTransOut) + { + var _trans = createTransition(transOut); + + _trans.setStatus(EMPTY); + openSubState(_trans); + + _trans.finishCallback = finishTransOut; + _trans.start(IN); + } + else + { + _onExit(); + } + } + + var transOutFinished:Bool = false; + + var _exiting:Bool = false; + var _onExit:Void->Void; + + function get_hasTransIn():Bool + { + return transIn != null && transIn.type != NONE; + } + + function get_hasTransOut():Bool + { + return transOut != null && transOut.type != NONE; + } + + function createTransition(data:TransitionData):Transition + { + return switch (data.type) + { + case TILES: new Transition(data); + case FADE: new Transition(data); + default: null; + } + } + + function finishTransIn() + { + closeSubState(); + } + + function finishTransOut() + { + transOutFinished = true; + + if (!_exiting) + { + closeSubState(); + } + + if (_onExit != null) + { + _onExit(); + } + } +} diff --git a/source/funkin/Controls.hx b/source/funkin/Controls.hx index 88b637e72..e8a66cb14 100644 --- a/source/funkin/Controls.hx +++ b/source/funkin/Controls.hx @@ -16,7 +16,6 @@ import flixel.input.keyboard.FlxKey; import flixel.input.mouse.FlxMouseButton.FlxMouseButtonID; import flixel.math.FlxAngle; import flixel.math.FlxPoint; -import flixel.ui.FlxVirtualPad; import flixel.util.FlxColor; import flixel.util.FlxTimer; import lime.ui.Haptic; diff --git a/source/funkin/FreeplayState.hx b/source/funkin/FreeplayState.hx index 608898a5f..a86bfe8cc 100644 --- a/source/funkin/FreeplayState.hx +++ b/source/funkin/FreeplayState.hx @@ -890,6 +890,13 @@ class FreeplayState extends MusicBeatSubState FlxG.sound.play(Paths.sound('confirmMenu')); dj.confirm(); + if (targetSong != null) + { + // Load and cache the song's charts. + // TODO: Do this in the loading state. + targetSong.cacheCharts(true); + } + new FlxTimer().start(1, function(tmr:FlxTimer) { LoadingState.loadAndSwitchState(new PlayState( { diff --git a/source/funkin/MainMenuState.hx b/source/funkin/MainMenuState.hx index 2c251635c..bca20980c 100644 --- a/source/funkin/MainMenuState.hx +++ b/source/funkin/MainMenuState.hx @@ -12,7 +12,6 @@ import flixel.input.touch.FlxTouch; import flixel.text.FlxText; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; -import flixel.ui.FlxButton; import flixel.util.FlxColor; import flixel.util.FlxTimer; import funkin.NGio; diff --git a/source/funkin/MusicBeatState.hx b/source/funkin/MusicBeatState.hx index 20330f257..9a986a8b5 100644 --- a/source/funkin/MusicBeatState.hx +++ b/source/funkin/MusicBeatState.hx @@ -3,7 +3,7 @@ package funkin; import funkin.modding.IScriptedClass.IEventHandler; import flixel.FlxState; import flixel.FlxSubState; -import flixel.addons.ui.FlxUIState; +import flixel.addons.transition.FlxTransitionableState; import flixel.text.FlxText; import flixel.util.FlxColor; import flixel.util.FlxSort; @@ -16,7 +16,7 @@ import funkin.util.SortUtil; * MusicBeatState actually represents the core utility FlxState of the game. * It includes functionality for event handling, as well as maintaining BPM-based update events. */ -class MusicBeatState extends FlxUIState implements IEventHandler +class MusicBeatState extends FlxTransitionableState implements IEventHandler { var controls(get, never):Controls; diff --git a/source/funkin/MusicBeatSubState.hx b/source/funkin/MusicBeatSubState.hx index 244d2ceea..1958c6074 100644 --- a/source/funkin/MusicBeatSubState.hx +++ b/source/funkin/MusicBeatSubState.hx @@ -1,24 +1,28 @@ package funkin; +import flixel.addons.transition.FlxTransitionableSubState; import flixel.FlxSubState; -import funkin.modding.IScriptedClass.IEventHandler; +import flixel.text.FlxText; import flixel.util.FlxColor; import funkin.modding.events.ScriptEvent; +import funkin.modding.IScriptedClass.IEventHandler; import funkin.modding.module.ModuleHandler; -import flixel.text.FlxText; import funkin.modding.PolymodHandler; +import funkin.util.SortUtil; +import flixel.util.FlxSort; /** * MusicBeatSubState reincorporates the functionality of MusicBeatState into an FlxSubState. */ -class MusicBeatSubState extends FlxSubState implements IEventHandler +class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandler { public var leftWatermarkText:FlxText = null; public var rightWatermarkText:FlxText = null; public function new(bgColor:FlxColor = FlxColor.TRANSPARENT) { - super(bgColor); + super(); + this.bgColor = bgColor; } var controls(get, never):Controls; @@ -67,6 +71,15 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler FlxG.resetState(); } + /** + * Refreshes the state, by redoing the render order of all sprites. + * It does this based on the `zIndex` of each prop. + */ + public function refresh() + { + sort(SortUtil.byZIndex, FlxSort.ASCENDING); + } + /** * Called when a step is hit in the current song. * Continues outside of PlayState, for things like animations in menus. diff --git a/source/funkin/PauseSubState.hx b/source/funkin/PauseSubState.hx index d5584fbc7..9133a8fab 100644 --- a/source/funkin/PauseSubState.hx +++ b/source/funkin/PauseSubState.hx @@ -16,14 +16,17 @@ class PauseSubState extends MusicBeatSubState { var grpMenuShit:FlxTypedGroup; - var pauseOG:Array = [ + var pauseOptionsBase:Array = [ 'Resume', 'Restart Song', 'Change Difficulty', 'Toggle Practice Mode', 'Exit to Menu' ]; - var difficultyChoices:Array = ['EASY', 'NORMAL', 'HARD', 'ERECT', 'BACK']; + + var pauseOptionsDifficulty:Array = ['EASY', 'NORMAL', 'HARD', 'ERECT', 'BACK']; + + var pauseOptionsCharting:Array = ['Resume', 'Restart Song', 'Exit to Chart Editor']; var menuItems:Array = []; var curSelected:Int = 0; @@ -36,11 +39,15 @@ class PauseSubState extends MusicBeatSubState var bg:FlxSprite; var metaDataGrp:FlxTypedGroup; - public function new() + var isChartingMode:Bool; + + public function new(?isChartingMode:Bool = false) { super(); - menuItems = pauseOG; + this.isChartingMode = isChartingMode; + + menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase; if (PlayStatePlaylist.campaignId == 'week6') { @@ -180,14 +187,13 @@ class PauseSubState extends MusicBeatSubState { var daSelected:String = menuItems[curSelected]; - // TODO: Why is this based on the menu item's name? Make this an enum or something. switch (daSelected) { case 'Resume': close(); case 'Change Difficulty': - menuItems = difficultyChoices; + menuItems = pauseOptionsDifficulty; regenMenu(); case 'EASY' | 'NORMAL' | 'HARD' | 'ERECT': @@ -199,7 +205,7 @@ class PauseSubState extends MusicBeatSubState close(); case 'BACK': - menuItems = pauseOG; + menuItems = this.isChartingMode ? pauseOptionsCharting : pauseOptionsBase; regenMenu(); case 'Toggle Practice Mode': @@ -226,6 +232,11 @@ class PauseSubState extends MusicBeatSubState if (PlayStatePlaylist.isStoryMode) openSubState(new funkin.ui.StickerSubState(null, STORY)); else openSubState(new funkin.ui.StickerSubState(null, FREEPLAY)); + + case 'Exit to Chart Editor': + this.close(); + if (FlxG.sound.music != null) FlxG.sound.music.stop(); + PlayState.instance.close(); // This only works because PlayState is a substate! } } diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index c0705bd96..cf12db06b 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1,5 +1,6 @@ package funkin.play; +import funkin.ui.debug.charting.ChartEditorState; import haxe.Int64; import funkin.play.notes.notestyle.NoteStyle; import funkin.data.notestyle.NoteStyleData; @@ -77,12 +78,23 @@ typedef PlayStateParams = * @default `bf`, or the first character in the song's character list. */ ?targetCharacter:String, + /** + * Whether the song should start in Practice Mode. + * @default `false` + */ + ?practiceMode:Bool, + /** + * Whether the song should be in minimal mode. + * @default `false` + */ + ?minimalMode:Bool, } /** * The gameplay state, where all the rhythm gaming happens. + * SubState so it can be loaded as a child of the chart editor. */ -class PlayState extends MusicBeatState +class PlayState extends MusicBeatSubState { /** * STATIC VARIABLES @@ -209,6 +221,11 @@ class PlayState extends MusicBeatState */ public var isPracticeMode:Bool = false; + /** + * In Minimal Mode, the stage and characters are not loaded and a standard background is used. + */ + public var isMinimalMode:Bool = false; + /** * Whether the game is currently in an animated cutscene, and gameplay should be stopped. */ @@ -219,6 +236,20 @@ class PlayState extends MusicBeatState */ public var disableKeys:Bool = false; + public var isSubState(get, null):Bool; + + function get_isSubState():Bool + { + return this._parentState != null; + } + + public var isChartingMode(get, null):Bool; + + function get_isChartingMode():Bool + { + return this._parentState != null && Std.isOfType(this._parentState, ChartEditorState); + } + /** * The current dialogue. */ @@ -438,6 +469,8 @@ class PlayState extends MusicBeatState currentSong = params.targetSong; if (params.targetDifficulty != null) currentDifficulty = params.targetDifficulty; if (params.targetCharacter != null) currentPlayerId = params.targetCharacter; + isPracticeMode = params.practiceMode ?? false; + isMinimalMode = params.minimalMode ?? false; // Don't do anything else here! Wait until create() when we attach to the camera. } @@ -458,13 +491,6 @@ class PlayState extends MusicBeatState NoteSplash.buildSplashFrames(); - if (currentSong != null) - { - // Load and cache the song's charts. - // TODO: Do this in the loading state. - currentSong.cacheCharts(true); - } - // Returns null if the song failed to load or doesn't have the selected difficulty. if (currentSong == null || currentChart == null) { @@ -490,7 +516,14 @@ class PlayState extends MusicBeatState lime.app.Application.current.window.alert(message, 'Error loading PlayState'); // Force the user back to the main menu. - FlxG.switchState(new MainMenuState()); + if (isSubState) + { + this.close(); + } + else + { + FlxG.switchState(new MainMenuState()); + } return; } @@ -532,8 +565,15 @@ class PlayState extends MusicBeatState // The song is now loaded. We can continue to initialize the play state. initCameras(); initHealthBar(); - initStage(); - initCharacters(); + if (!isMinimalMode) + { + initStage(); + initCharacters(); + } + else + { + initMinimalMode(); + } initStrumlines(); // Initialize the judgements and combo meter. @@ -706,7 +746,7 @@ class PlayState extends MusicBeatState // There is a 1/1000 change to use a special pause menu. // This prevents the player from resuming, but that's the point. // It's a reference to Gitaroo Man, which doesn't let you pause the game. - if (event.gitaroo) + if (!isSubState && event.gitaroo) { FlxG.switchState(new GitarooPause( { @@ -725,7 +765,7 @@ class PlayState extends MusicBeatState boyfriendPos = currentStage.getBoyfriend().getScreenPosition(); } - var pauseSubState:FlxSubState = new PauseSubState(); + var pauseSubState:FlxSubState = new PauseSubState(isChartingMode); openSubState(pauseSubState); pauseSubState.camera = camHUD; @@ -1202,6 +1242,19 @@ class PlayState extends MusicBeatState loadStage(currentStageId); } + function initMinimalMode():Void + { + // Create the green background. + var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat')); + menuBG.color = 0xFF4CAF50; + menuBG.setGraphicSize(Std.int(menuBG.width * 1.1)); + menuBG.updateHitbox(); + menuBG.screenCenter(); + menuBG.scrollFactor.set(0, 0); + menuBG.zIndex = -1000; + add(menuBG); + } + /** * Loads stage data from cache, assembles the props, * and adds it to the state. @@ -2132,6 +2185,7 @@ class PlayState extends MusicBeatState if (FlxG.keys.justPressed.H) camHUD.visible = !camHUD.visible; #end + // Eject button if (FlxG.keys.justPressed.F4) FlxG.switchState(new MainMenuState()); if (FlxG.keys.justPressed.F5) debug_refreshModules(); @@ -2163,7 +2217,10 @@ class PlayState extends MusicBeatState } // 8: Move to the offset editor. - if (FlxG.keys.justPressed.EIGHT) FlxG.switchState(new funkin.ui.animDebugShit.DebugBoundingState()); + if (FlxG.keys.justPressed.EIGHT) + { + lime.app.Application.current.window.alert("Press ~ on the main menu to get to the editor", 'LOL'); + } // 9: Toggle the old icon. if (FlxG.keys.justPressed.NINE) iconP1.toggleOldIcon(); @@ -2384,7 +2441,14 @@ class PlayState extends MusicBeatState // FlxG.save.data.weekUnlocked = StoryMenuState.weekUnlocked; FlxG.save.flush(); - moveToResultsScreen(); + if (isSubState) + { + this.close(); + } + else + { + moveToResultsScreen(); + } } else { @@ -2438,10 +2502,23 @@ class PlayState extends MusicBeatState } else { - moveToResultsScreen(); + if (isSubState) + { + this.close(); + } + else + { + moveToResultsScreen(); + } } } + public override function close():Void + { + performCleanup(); + super.close(); + } + /** * Perform necessary cleanup before leaving the PlayState. */ @@ -2552,6 +2629,7 @@ class PlayState extends MusicBeatState FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.04); FlxG.camera.targetOffset.set(); FlxG.camera.zoom = defaultCameraZoom; + // Snap the camera to the follow point immediately. FlxG.camera.focusOn(cameraFollowPoint.getPosition()); } diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 4cbf1ade3..a8f004520 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -56,6 +56,32 @@ class Song implements IPlayStateScriptedClass populateFromMetadata(); } + @:allow(funkin.play.song.Song) + public static function buildRaw(songId:String, metadata:Array, variations:Array, charts:Map, + ?validScore:Bool = false):Song + { + var result:Song = new Song(songId); + + result._metadata.clear(); + for (meta in metadata) + result._metadata.push(meta); + + result.variations.clear(); + for (vari in variations) + result.variations.push(vari); + + result.difficultyIds.clear(); + + result.populateFromMetadata(); + + for (variation => chartData in charts) + result.applyChartData(chartData, variation); + + result.validScore = validScore; + + return result; + } + public function getRawMetadata():Array { return _metadata; @@ -119,28 +145,33 @@ class Song implements IPlayStateScriptedClass for (variation in variations) { var chartData:SongChartData = SongDataParser.parseSongChartData(songId, variation); - var chartNotes = chartData.notes; - - for (diffId in chartNotes.keys()) - { - // Retrieve the cached difficulty data. - var difficulty:Null = difficulties.get(diffId); - if (difficulty == null) - { - trace('Fabricated new difficulty for $diffId.'); - difficulty = new SongDifficulty(this, diffId, variation); - difficulties.set(diffId, difficulty); - } - // Add the chart data to the difficulty. - difficulty.notes = chartData.notes.get(diffId); - difficulty.scrollSpeed = chartData.getScrollSpeed(diffId); - - difficulty.events = chartData.events; - } + applyChartData(chartData, variation); } trace('Done caching charts.'); } + function applyChartData(chartData:SongChartData, variation:String):Void + { + var chartNotes = chartData.notes; + + for (diffId in chartNotes.keys()) + { + // Retrieve the cached difficulty data. + var difficulty:Null = difficulties.get(diffId); + if (difficulty == null) + { + trace('Fabricated new difficulty for $diffId.'); + difficulty = new SongDifficulty(this, diffId, variation); + difficulties.set(diffId, difficulty); + } + // Add the chart data to the difficulty. + difficulty.notes = chartData.notes.get(diffId); + difficulty.scrollSpeed = chartData.getScrollSpeed(diffId); + + difficulty.events = chartData.events; + } + } + /** * Retrieve the metadata for a specific difficulty, including the chart if it is loaded. * @param diffId The difficulty ID, such as `easy` or `hard`. diff --git a/source/funkin/play/song/SongData.hx b/source/funkin/play/song/SongData.hx index c2a701ce9..740fec9d1 100644 --- a/source/funkin/play/song/SongData.hx +++ b/source/funkin/play/song/SongData.hx @@ -202,7 +202,7 @@ class SongDataParser static function loadMusicMetadataFile(musicPath:String, variation:String = ''):String { - var musicMetadataFilePath:String = (variation != '') ? Paths.file('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata-$variation.json') : Paths.file('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata.json'); + var musicMetadataFilePath:String = (variation != '' || variation == "default") ? Paths.file('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata-$variation.json') : Paths.file('$MUSIC_DATA_PATH$musicPath/$musicPath-metadata.json'); var rawJson:String = Assets.getText(musicMetadataFilePath).trim(); @@ -238,7 +238,7 @@ class SongDataParser static function loadSongChartDataFile(songPath:String, variation:String = ''):String { - var songChartDataFilePath:String = (variation != '') ? Paths.json('$SONG_DATA_PATH$songPath/$songPath-chart-$variation') : Paths.json('$SONG_DATA_PATH$songPath/$songPath-chart'); + var songChartDataFilePath:String = (variation != '' || variation == 'default') ? Paths.json('$SONG_DATA_PATH$songPath/$songPath-chart-$variation') : Paths.json('$SONG_DATA_PATH$songPath/$songPath-chart'); var rawJson:String = Assets.getText(songChartDataFilePath).trim(); diff --git a/source/funkin/ui/debug/DebugMenuSubState.hx b/source/funkin/ui/debug/DebugMenuSubState.hx index 9f18acd35..7ef4cb238 100644 --- a/source/funkin/ui/debug/DebugMenuSubState.hx +++ b/source/funkin/ui/debug/DebugMenuSubState.hx @@ -1,5 +1,6 @@ package funkin.ui.debug; +import flixel.math.FlxPoint; import flixel.FlxObject; import flixel.FlxSprite; import funkin.MusicBeatSubState; @@ -48,6 +49,9 @@ class DebugMenuSubState extends MusicBeatSubState createItem("ANIMATION EDITOR", openAnimationEditor); createItem("STAGE EDITOR", openStageEditor); createItem("TEST STICKERS", testStickers); + + FlxG.camera.focusOn(new FlxPoint(camFocusPoint.x, camFocusPoint.y)); + FlxG.camera.focusOn(new FlxPoint(camFocusPoint.x, camFocusPoint.y + 500)); } function onMenuChange(selected:TextMenuItem) diff --git a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx index 9453c8c94..2e8cc78d4 100644 --- a/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx +++ b/source/funkin/ui/debug/charting/ChartEditorDialogHandler.hx @@ -580,6 +580,8 @@ class ChartEditorDialogHandler state.isHaxeUIDialogOpen = false; }; + dialog.zIndex = 1000; + return dialog; } } diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 2238fff3f..a3fcd0f22 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -2,7 +2,9 @@ package funkin.ui.debug.charting; import flixel.addons.display.FlxSliceSprite; import flixel.addons.display.FlxTiledSprite; +import flixel.FlxCamera; import flixel.FlxSprite; +import flixel.FlxSubState; import flixel.group.FlxSpriteGroup; import flixel.input.keyboard.FlxKey; import flixel.math.FlxPoint; @@ -20,6 +22,7 @@ import funkin.modding.events.ScriptEvent; import funkin.play.HealthIcon; import funkin.play.notes.NoteSprite; import funkin.play.notes.Strumline; +import funkin.play.PlayState; import funkin.play.song.Song; import funkin.play.song.SongData.SongChartData; import funkin.play.song.SongData.SongDataParser; @@ -977,6 +980,13 @@ class ChartEditorState extends HaxeUIState override function create():Void { + // super.create() must be called first, the HaxeUI components get created here. + super.create(); + // Set the z-index of the HaxeUI. + this.component.zIndex = 100; + + fixCamera(); + // Get rid of any music from the previous state. FlxG.sound.music.stop(); @@ -989,8 +999,6 @@ class ChartEditorState extends HaxeUIState buildGrid(); buildSelectionBox(); - // Add the HaxeUI components after the grid so they're on top. - super.create(); buildAdditionalUI(); // Setup the onClick listeners for the UI after it's been created. @@ -999,6 +1007,8 @@ class ChartEditorState extends HaxeUIState setupAutoSave(); + refresh(); + ChartEditorDialogHandler.openWelcomeDialog(this, false); } @@ -1028,6 +1038,7 @@ class ChartEditorState extends HaxeUIState menuBG.updateHitbox(); menuBG.screenCenter(); menuBG.scrollFactor.set(0, 0); + menuBG.zIndex = -100; } /** @@ -1039,28 +1050,33 @@ class ChartEditorState extends HaxeUIState gridTiledSprite.x = FlxG.width / 2 - GRID_SIZE * STRUMLINE_SIZE; // Center the grid. gridTiledSprite.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; // Push down to account for the menu bar. add(gridTiledSprite); + gridTiledSprite.zIndex = 10; gridGhostNote = new ChartEditorNoteSprite(this); gridGhostNote.alpha = 0.6; gridGhostNote.noteData = new SongNoteData(0, 0, 0, ""); gridGhostNote.visible = false; add(gridGhostNote); + gridGhostNote.zIndex = 11; gridGhostEvent = new ChartEditorEventSprite(this); gridGhostEvent.alpha = 0.6; gridGhostEvent.eventData = new SongEventData(-1, "", {}); gridGhostEvent.visible = false; add(gridGhostEvent); + gridGhostEvent.zIndex = 12; buildNoteGroup(); gridPlayheadScrollArea = new FlxSprite(gridTiledSprite.x - PLAYHEAD_SCROLL_AREA_WIDTH, MENU_BAR_HEIGHT).makeGraphic(PLAYHEAD_SCROLL_AREA_WIDTH, FlxG.height - MENU_BAR_HEIGHT, PLAYHEAD_SCROLL_AREA_COLOR); add(gridPlayheadScrollArea); + gridPlayheadScrollArea.zIndex = 25; // The playhead that show the current position in the song. gridPlayhead = new FlxSpriteGroup(); add(gridPlayhead); + gridPlayhead.zIndex = 30; var playheadWidth = GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + (PLAYHEAD_SCROLL_AREA_WIDTH * 2); var playheadBaseYPos = MENU_BAR_HEIGHT + GRID_TOP_PAD; @@ -1082,6 +1098,7 @@ class ChartEditorState extends HaxeUIState healthIconDad.x = gridTiledSprite.x - 15 - (HealthIcon.HEALTH_ICON_SIZE * 0.5); healthIconDad.y = gridTiledSprite.y + 5; add(healthIconDad); + healthIconDad.zIndex = 30; healthIconBF = new HealthIcon('bf'); healthIconBF.autoUpdate = false; @@ -1090,12 +1107,14 @@ class ChartEditorState extends HaxeUIState healthIconBF.y = gridTiledSprite.y + 5; healthIconBF.flipX = true; add(healthIconBF); + healthIconBF.zIndex = 30; } function buildSelectionBox():Void { selectionBoxSprite.scrollFactor.set(0, 0); add(selectionBoxSprite); + selectionBoxSprite.zIndex = 30; setSelectionBoxBounds(); } @@ -1140,18 +1159,22 @@ class ChartEditorState extends HaxeUIState renderedHoldNotes = new FlxTypedSpriteGroup(); renderedHoldNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y); add(renderedHoldNotes); + renderedHoldNotes.zIndex = 24; renderedNotes = new FlxTypedSpriteGroup(); renderedNotes.setPosition(gridTiledSprite.x, gridTiledSprite.y); add(renderedNotes); + renderedNotes.zIndex = 25; renderedEvents = new FlxTypedSpriteGroup(); renderedEvents.setPosition(gridTiledSprite.x, gridTiledSprite.y); add(renderedEvents); + renderedNotes.zIndex = 25; renderedSelectionSquares = new FlxTypedSpriteGroup(); renderedSelectionSquares.setPosition(gridTiledSprite.x, gridTiledSprite.y); add(renderedSelectionSquares); + renderedNotes.zIndex = 26; } var playbarHeadLayout:Component; @@ -1159,6 +1182,7 @@ class ChartEditorState extends HaxeUIState function buildAdditionalUI():Void { playbarHeadLayout = buildComponent(CHART_EDITOR_PLAYBARHEAD_LAYOUT); + playbarHeadLayout.zIndex = 110; playbarHeadLayout.width = FlxG.width - 8; playbarHeadLayout.height = 10; @@ -1271,6 +1295,9 @@ class ChartEditorState extends HaxeUIState // addUIClickListener('menubarItemSelectBeforeCursor', _ -> doSomething()); // addUIClickListener('menubarItemSelectAfterCursor', _ -> doSomething()); + addUIClickListener('menubarItemPlaytestFull', _ -> testSongInPlayState(false)); + addUIClickListener('menubarItemPlaytestMinimal', _ -> testSongInPlayState(true)); + addUIClickListener('menubarItemAbout', _ -> ChartEditorDialogHandler.openAboutDialog(this)); addUIClickListener('menubarItemUserGuide', _ -> ChartEditorDialogHandler.openUserGuideDialog(this)); @@ -1423,6 +1450,7 @@ class ChartEditorState extends HaxeUIState handleFileKeybinds(); handleEditKeybinds(); handleViewKeybinds(); + handleTestKeybinds(); handleHelpKeybinds(); // DEBUG @@ -2536,6 +2564,18 @@ class ChartEditorState extends HaxeUIState */ function handleViewKeybinds():Void {} + /** + * Handle keybinds for the Test menu items. + */ + function handleTestKeybinds():Void + { + if (FlxG.keys.justPressed.ENTER) + { + var minimal = FlxG.keys.pressed.SHIFT; + testSongInPlayState(minimal); + } + } + /** * Handle keybinds for Help menu items. */ @@ -2907,9 +2947,9 @@ class ChartEditorState extends HaxeUIState function startAudioPlayback():Void { - if (audioInstTrack != null) audioInstTrack.play(); - if (audioVocalTrackGroup != null) audioVocalTrackGroup.play(); - if (audioVocalTrackGroup != null) audioVocalTrackGroup.play(); + if (audioInstTrack != null) audioInstTrack.play(false, audioInstTrack.time); + if (audioVocalTrackGroup != null) audioVocalTrackGroup.play(false, audioInstTrack.time); + if (audioVocalTrackGroup != null) audioVocalTrackGroup.play(false, audioInstTrack.time); setComponentText('playbarPlay', '||'); } @@ -3004,6 +3044,34 @@ class ChartEditorState extends HaxeUIState return this.scrollPositionInPixels; } + /** + * Transitions to the Play State to test the song + */ + public function testSongInPlayState(?minimal:Bool = false):Void + { + var targetSong:Song = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, false); + + subStateClosed.add(fixCamera); + + openSubState(new PlayState( + { + targetSong: targetSong, + targetDifficulty: selectedDifficulty, + // TODO: Add this. + // targetCharacter: targetCharacter, + practiceMode: true, + minimalMode: minimal, + })); + } + + function fixCamera(_:FlxSubState = null):Void + { + FlxG.cameras.reset(new FlxCamera()); + FlxG.camera.focusOn(new FlxPoint(FlxG.width / 2, FlxG.height / 2)); + + add(this.component); + } + /** * Loads an instrumental from an absolute file path, replacing the current instrumental. * diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 12b0f58c5..29bc4beca 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -513,6 +513,13 @@ class StoryMenuState extends MusicBeatState PlayStatePlaylist.campaignId = currentLevel.id; PlayStatePlaylist.campaignTitle = currentLevel.getTitle(); + if (targetSong != null) + { + // Load and cache the song's charts. + // TODO: Do this in the loading state. + targetSong.cacheCharts(true); + } + new FlxTimer().start(1, function(tmr:FlxTimer) { LoadingState.loadAndSwitchState(new PlayState( { diff --git a/source/funkin/util/tools/ArrayTools.hx b/source/funkin/util/tools/ArrayTools.hx index c27f1bf43..67cc1c041 100644 --- a/source/funkin/util/tools/ArrayTools.hx +++ b/source/funkin/util/tools/ArrayTools.hx @@ -37,4 +37,15 @@ class ArrayTools } return null; } + + /** + * Remove all elements from the array, without creating a new array. + * @param array The array to clear. + */ + public static function clear(array:Array):Void + { + // This method is faster than array.splice(0, array.length) + while (array.length > 0) + array.pop(); + } } From 2048e65bf28a01a2a582f0c5a1ae4e7749eb1c62 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 26 Jul 2023 20:03:31 -0400 Subject: [PATCH 02/29] Added ability to start song at a specific timestamp --- source/funkin/LatencyState.hx | 2 +- source/funkin/MusicBeatSubState.hx | 9 ++ source/funkin/TitleState.hx | 2 +- source/funkin/play/Countdown.hx | 10 ++- source/funkin/play/GameOverSubState.hx | 4 +- source/funkin/play/PlayState.hx | 88 +++++++++++-------- source/funkin/play/notes/Strumline.hx | 2 + .../ui/debug/charting/ChartEditorState.hx | 12 +++ 8 files changed, 88 insertions(+), 41 deletions(-) diff --git a/source/funkin/LatencyState.hx b/source/funkin/LatencyState.hx index 4a8ed2d2e..7385ca640 100644 --- a/source/funkin/LatencyState.hx +++ b/source/funkin/LatencyState.hx @@ -191,7 +191,7 @@ class LatencyState extends MusicBeatSubState if (FlxG.keys.pressed.D) FlxG.sound.music.time += 1000 * FlxG.elapsed; - Conductor.songPosition = swagSong.getTimeWithDiff() - Conductor.offset; + Conductor.update(swagSong.getTimeWithDiff() - Conductor.offset); // Conductor.songPosition += (Timer.stamp() * 1000) - FlxG.sound.music.prevTimestamp; songPosVis.x = songPosToX(Conductor.songPosition); diff --git a/source/funkin/MusicBeatSubState.hx b/source/funkin/MusicBeatSubState.hx index 1958c6074..31d1bd14c 100644 --- a/source/funkin/MusicBeatSubState.hx +++ b/source/funkin/MusicBeatSubState.hx @@ -61,6 +61,15 @@ class MusicBeatSubState extends FlxTransitionableSubState implements IEventHandl // This can now be used in EVERY STATE YAY! if (FlxG.keys.justPressed.F5) debug_refreshModules(); + + // Display Conductor info in the watch window. + FlxG.watch.addQuick("songPosition", Conductor.songPosition); + FlxG.watch.addQuick("bpm", Conductor.bpm); + FlxG.watch.addQuick("currentMeasureTime", Conductor.currentBeatTime); + FlxG.watch.addQuick("currentBeatTime", Conductor.currentBeatTime); + FlxG.watch.addQuick("currentStepTime", Conductor.currentStepTime); + + dispatchEvent(new UpdateScriptEvent(elapsed)); } function debug_refreshModules() diff --git a/source/funkin/TitleState.hx b/source/funkin/TitleState.hx index 8ba5121fa..47cc33a38 100644 --- a/source/funkin/TitleState.hx +++ b/source/funkin/TitleState.hx @@ -256,7 +256,7 @@ class TitleState extends MusicBeatState FlxTween.tween(FlxG.stage.window, {y: FlxG.stage.window.y + 100}, 0.7, {ease: FlxEase.quadInOut, type: PINGPONG}); } - if (FlxG.sound.music != null) Conductor.songPosition = FlxG.sound.music.time; + if (FlxG.sound.music != null) Conductor.update(FlxG.sound.music.time); if (FlxG.keys.justPressed.F) FlxG.fullscreen = !FlxG.fullscreen; // do controls.PAUSE | controls.ACCEPT instead? diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index 51d72693e..48e98e84f 100644 --- a/source/funkin/play/Countdown.hx +++ b/source/funkin/play/Countdown.hx @@ -37,7 +37,7 @@ class Countdown stopCountdown(); PlayState.instance.isInCountdown = true; - Conductor.songPosition = Conductor.beatLengthMs * -5; + Conductor.update(PlayState.instance.startTimestamp + Conductor.beatLengthMs * -5); // Handle onBeatHit events manually @:privateAccess PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0)); @@ -46,6 +46,12 @@ class Countdown countdownTimer = new FlxTimer(); countdownTimer.start(Conductor.beatLengthMs / 1000, function(tmr:FlxTimer) { + if (PlayState.instance == null) + { + tmr.cancel(); + return; + } + countdownStep = decrement(countdownStep); // Handle onBeatHit events manually @@ -146,7 +152,7 @@ class Countdown { stopCountdown(); // This will trigger PlayState.startSong() - Conductor.songPosition = 0; + Conductor.update(0); // PlayState.isInCountdown = false; } diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index f38dabea4..161da5191 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -117,7 +117,7 @@ class GameOverSubState extends MusicBeatSubState gameOverMusic.stop(); // The conductor now represents the BPM of the game over music. - Conductor.songPosition = 0; + Conductor.update(0); } var hasStartedAnimation:Bool = false; @@ -183,7 +183,7 @@ class GameOverSubState extends MusicBeatSubState { // Match the conductor to the music. // This enables the stepHit and beatHit events. - Conductor.songPosition = gameOverMusic.time; + Conductor.update(gameOverMusic.time); } else { diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index cf12db06b..ae57f3cd5 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -88,6 +88,10 @@ typedef PlayStateParams = * @default `false` */ ?minimalMode:Bool, + /** + * If specified, the game will jump to the specified timestamp after the countdown ends. + */ + ?startTimestamp:Float, } /** @@ -236,6 +240,8 @@ class PlayState extends MusicBeatSubState */ public var disableKeys:Bool = false; + public var startTimestamp:Float = 0.0; + public var isSubState(get, null):Bool; function get_isSubState():Bool @@ -471,6 +477,7 @@ class PlayState extends MusicBeatSubState if (params.targetCharacter != null) currentPlayerId = params.targetCharacter; isPracticeMode = params.practiceMode ?? false; isMinimalMode = params.minimalMode ?? false; + startTimestamp = params.startTimestamp ?? 0.0; // Don't do anything else here! Wait until create() when we attach to the camera. } @@ -560,7 +567,7 @@ class PlayState extends MusicBeatSubState // Prepare the Conductor. Conductor.mapTimeChanges(currentChart.timeChanges); - Conductor.update(-5000); + Conductor.update((Conductor.beatLengthMs * -5) + startTimestamp); // The song is now loaded. We can continue to initialize the play state. initCameras(); @@ -669,7 +676,7 @@ class PlayState extends MusicBeatSubState FlxG.sound.music.pause(); vocals.pause(); - FlxG.sound.music.time = 0; + FlxG.sound.music.time = (startTimestamp); vocals.time = 0; FlxG.sound.music.volume = 1; @@ -700,8 +707,8 @@ class PlayState extends MusicBeatSubState { if (isInCountdown) { - Conductor.songPosition += elapsed * 1000; - if (Conductor.songPosition >= 0) startSong(); + Conductor.update(Conductor.songPosition + elapsed * 1000); + if (Conductor.songPosition >= startTimestamp) startSong(); } } else @@ -1067,7 +1074,8 @@ class PlayState extends MusicBeatSubState // super.stepHit() returns false if a module cancelled the event. if (!super.stepHit()) return false; - if (FlxG.sound.music != null + if (!startingSong + && FlxG.sound.music != null && (Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 200 || Math.abs(vocals.checkSyncError(Conductor.songPosition - Conductor.offset)) > 200)) { @@ -1515,7 +1523,7 @@ class PlayState extends MusicBeatSubState /** * Read note data from the chart and generate the notes. */ - function regenNoteData():Void + function regenNoteData(startTime:Float = 0):Void { Highscore.tallies.combo = 0; Highscore.tallies = new Tallies(); @@ -1531,6 +1539,8 @@ class PlayState extends MusicBeatSubState for (songNote in currentChart.notes) { var strumTime:Float = songNote.time; + if (strumTime < startTime) continue; // Skip notes that are before the start time. + var noteData:Int = songNote.getDirection(); var playerNote:Bool = true; @@ -1617,14 +1627,22 @@ class PlayState extends MusicBeatSubState } FlxG.sound.music.onComplete = endSong; + FlxG.sound.music.play(false, startTimestamp); trace('Playing vocals...'); add(vocals); vocals.play(); + resyncVocals(); #if discord_rpc // Updating Discord Rich Presence (with Time Left) DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, currentSongLengthMs); #end + + if (startTimestamp > 0) + { + FlxG.sound.music.time = startTimestamp; + handleSkippedNotes(); + } } /** @@ -1640,7 +1658,7 @@ class PlayState extends MusicBeatSubState Conductor.update(); vocals.time = FlxG.sound.music.time; - vocals.play(); + vocals.play(false, FlxG.sound.music.time); } /** @@ -1836,6 +1854,23 @@ class PlayState extends MusicBeatSubState */ var inputSpitter:Array = []; + function handleSkippedNotes():Void + { + for (note in playerStrumline.notes.members) + { + var hitWindowStart = note.strumTime - Constants.HIT_WINDOW_MS; + var hitWindowCenter = note.strumTime; + var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS; + + if (Conductor.songPosition > hitWindowEnd) + { + // We have passed this note. + // Flag the note for deletion without actually penalizing the player. + note.handledMiss = true; + } + } + } + /** * PreciseInputEvents are put into a queue between update() calls, * and then processed here. @@ -2064,11 +2099,10 @@ class PlayState extends MusicBeatSubState if (event.eventCanceled) return; health -= Constants.HEALTH_MISS_PENALTY; + songScore -= 10; if (!isPracticeMode) { - songScore -= 10; - // messy copy paste rn lol var pressArray:Array = [ controls.NOTE_LEFT_P, @@ -2139,11 +2173,10 @@ class PlayState extends MusicBeatSubState if (event.eventCanceled) return; health += event.healthChange; + songScore += event.scoreChange; if (!isPracticeMode) { - songScore += event.scoreChange; - var pressArray:Array = [ controls.NOTE_LEFT_P, controls.NOTE_DOWN_P, @@ -2282,11 +2315,10 @@ class PlayState extends MusicBeatSubState playerStrumline.playNoteSplash(daNote.noteData.getDirection()); } - // Only add the score if you're not on practice mode + songScore += score; + if (!isPracticeMode) { - songScore += score; - // TODO: Input splitter uses old input system, make it pull from the precise input queue directly. var pressArray:Array = [ controls.NOTE_LEFT_P, @@ -2643,30 +2675,16 @@ class PlayState extends MusicBeatSubState { FlxG.sound.music.pause(); - FlxG.sound.music.time += sections * Conductor.measureLengthMs; + var targetTimeSteps:Float = Conductor.currentStepTime + (Conductor.timeSignatureNumerator * Constants.STEPS_PER_BEAT * sections); + var targetTimeMs:Float = Conductor.getStepTimeInMs(targetTimeSteps); + + FlxG.sound.music.time = targetTimeMs; + + handleSkippedNotes(); + // regenNoteData(FlxG.sound.music.time); Conductor.update(FlxG.sound.music.time); - /** - * - // TODO: Redo this for the new conductor. - var daBPM:Float = Conductor.bpm; - var daPos:Float = 0; - for (i in 0...(Std.int(Conductor.currentStep / 16 + sec))) - { - var section = .getSong()[i]; - if (section == null) continue; - if (section.changeBPM) - { - daBPM = .getSong()[i].bpm; - } - daPos += 4 * (1000 * 60 / daBPM); - } - Conductor.songPosition = FlxG.sound.music.time = daPos; - Conductor.songPosition += Conductor.offset; - - */ - resyncVocals(); } #end diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 454ec13e1..40bd8656a 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -220,6 +220,7 @@ class Strumline extends FlxSpriteGroup { if (noteData.length == 0) return; + var songStart:Float = PlayState.instance.startTimestamp ?? 0.0; var renderWindowStart:Float = Conductor.songPosition + RENDER_DISTANCE_MS; for (noteIndex in nextNoteIndex...noteData.length) @@ -227,6 +228,7 @@ class Strumline extends FlxSpriteGroup var note:Null = noteData[noteIndex]; if (note == null) continue; + if (note.time < songStart) continue; if (note.time > renderWindowStart) break; var noteSprite = buildNoteSprite(note); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index a3fcd0f22..8cf496637 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -400,6 +400,11 @@ class ChartEditorState extends HaxeUIState return isViewDownscroll; } + /** + * If true, playtesting a chart will skip to the current playhead position. + */ + var playtestStartTime:Bool = false; + /** * Whether hitsounds are enabled for at least one character. */ @@ -1305,6 +1310,9 @@ class ChartEditorState extends HaxeUIState addUIChangeListener('menubarItemDownscroll', event -> isViewDownscroll = event.value); setUICheckboxSelected('menubarItemDownscroll', isViewDownscroll); + addUIChangeListener('menubarItemPlaytestStartTime', event -> playtestStartTime = event.value); + setUICheckboxSelected('menubarItemPlaytestStartTime', playtestStartTime); + addUIChangeListener('menuBarItemThemeLight', function(event:UIEvent) { if (event.target.value) currentTheme = ChartEditorTheme.Light; }); @@ -3049,6 +3057,9 @@ class ChartEditorState extends HaxeUIState */ public function testSongInPlayState(?minimal:Bool = false):Void { + var startTimestamp:Float = 0; + if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs; + var targetSong:Song = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, false); subStateClosed.add(fixCamera); @@ -3061,6 +3072,7 @@ class ChartEditorState extends HaxeUIState // targetCharacter: targetCharacter, practiceMode: true, minimalMode: minimal, + startTimestamp: startTimestamp, })); } From b974e6d6d7ef8aa846b96db611f783680f9f7b3a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 26 Jul 2023 21:34:38 -0400 Subject: [PATCH 03/29] Fix issues with playtest and backwards time travel --- source/funkin/play/PlayState.hx | 34 ++++++++++++++----- source/funkin/play/notes/Strumline.hx | 30 +++++++++++++--- .../ui/debug/charting/ChartEditorState.hx | 8 +++++ 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 78c953e99..479bc7424 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -684,7 +684,7 @@ class PlayState extends MusicBeatSubState vocals.playerVolume = 1; vocals.opponentVolume = 1; - currentStage.resetStage(); + if (currentStage != null) currentStage.resetStage(); playerStrumline.clean(); opponentStrumline.clean(); @@ -871,6 +871,13 @@ class PlayState extends MusicBeatSubState trace('Found ${songEventsToActivate.length} event(s) to activate.'); for (event in songEventsToActivate) { + // If an event is trying to play, but it's over 5 seconds old, skip it. + if (event.time - Conductor.songPosition < -5000) + { + event.activated = true; + continue; + }; + var eventEvent:SongEventScriptEvent = new SongEventScriptEvent(event); dispatchEvent(eventEvent); // Calling event.cancelEvent() skips the event. Neat! @@ -1828,6 +1835,7 @@ class PlayState extends MusicBeatSubState // Judge the miss. // NOTE: This is what handles the scoring. + trace('Missed note! ${note.noteData}'); onNoteMiss(note); note.handledMiss = true; @@ -1861,8 +1869,7 @@ class PlayState extends MusicBeatSubState { for (note in playerStrumline.notes.members) { - var hitWindowStart = note.strumTime - Constants.HIT_WINDOW_MS; - var hitWindowCenter = note.strumTime; + if (note == null || note.hasBeenHit) continue; var hitWindowEnd = note.strumTime + Constants.HIT_WINDOW_MS; if (Conductor.songPosition > hitWindowEnd) @@ -1872,6 +1879,9 @@ class PlayState extends MusicBeatSubState note.handledMiss = true; } } + + playerStrumline.handleSkippedNotes(); + opponentStrumline.handleSkippedNotes(); } /** @@ -1935,6 +1945,7 @@ class PlayState extends MusicBeatSubState if (targetNote == null) continue; // Judge and hit the note. + trace('Hit note! ${targetNote.noteData}'); goodNoteHit(targetNote, input); targetNote.visible = false; @@ -2262,12 +2273,12 @@ class PlayState extends MusicBeatSubState if (FlxG.keys.justPressed.NINE) iconP1.toggleOldIcon(); #if debug - // PAGEUP: Skip forward one section. - // SHIFT+PAGEUP: Skip forward ten sections. - if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 10 : 1); - // PAGEDOWN: Skip backward one section. Doesn't replace notes. - // SHIFT+PAGEDOWN: Skip backward ten sections. - if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -10 : -1); + // PAGEUP: Skip forward two sections. + // SHIFT+PAGEUP: Skip forward twenty sections. + if (FlxG.keys.justPressed.PAGEUP) changeSection(FlxG.keys.pressed.SHIFT ? 20 : 2); + // PAGEDOWN: Skip backward two section. Doesn't replace notes. + // SHIFT+PAGEDOWN: Skip backward twenty sections. + if (FlxG.keys.justPressed.PAGEDOWN) changeSection(FlxG.keys.pressed.SHIFT ? -20 : -2); #end if (FlxG.keys.justPressed.B) trace(inputSpitter.join('\n')); @@ -2550,6 +2561,7 @@ class PlayState extends MusicBeatSubState public override function close():Void { + criticalFailure = true; // Stop game updates. performCleanup(); super.close(); } @@ -2564,6 +2576,10 @@ class PlayState extends MusicBeatSubState // TODO: Uncache the song. } + // Stop the music. + FlxG.sound.music.pause(); + vocals.stop(); + // Remove reference to stage and remove sprites from it to save memory. if (currentStage != null) { diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 0407d8ffc..15069f9b7 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -55,7 +55,12 @@ class Strumline extends FlxSpriteGroup final noteStyle:NoteStyle; + /** + * The note data for the song. Should NOT be altered after the song starts, + * so we can easily rewind. + */ var noteData:Array = []; + var nextNoteIndex:Int = -1; var heldKeys:Array = []; @@ -221,15 +226,21 @@ class Strumline extends FlxSpriteGroup if (noteData.length == 0) return; var songStart:Float = PlayState.instance.startTimestamp ?? 0.0; + var hitWindowStart:Float = Conductor.songPosition - Constants.HIT_WINDOW_MS; var renderWindowStart:Float = Conductor.songPosition + RENDER_DISTANCE_MS; for (noteIndex in nextNoteIndex...noteData.length) { var note:Null = noteData[noteIndex]; - if (note == null) continue; - if (note.time < songStart) continue; - if (note.time > renderWindowStart) break; + if (note == null) continue; // Note is blank + if (note.time < songStart || note.time < hitWindowStart) + { + // Note is in the past, skip it. + nextNoteIndex = noteIndex + 1; + continue; + } + if (note.time > renderWindowStart) break; // Note is too far ahead to render var noteSprite = buildNoteSprite(note); @@ -238,7 +249,7 @@ class Strumline extends FlxSpriteGroup noteSprite.holdNoteSprite = buildHoldNoteSprite(note); } - nextNoteIndex++; // Increment the nextNoteIndex rather than splicing the array, because splicing is slow. + nextNoteIndex = noteIndex + 1; // Increment the nextNoteIndex rather than splicing the array, because splicing is slow. } // Update rendering of notes. @@ -376,6 +387,17 @@ class Strumline extends FlxSpriteGroup } } + /** + * Called when the PlayState skips a large amount of time forward or backward. + */ + public function handleSkippedNotes():Void + { + // By calling clean(), we remove all existing notes so they can be re-added. + clean(); + // By setting noteIndex to 0, the next update will skip past all the notes that are in the past. + nextNoteIndex = 0; + } + public function onBeatHit():Void { if (notes.members.length > 1) notes.members.insertionSort(compareNoteSprites.bind(FlxSort.ASCENDING)); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 8cf496637..c57835ca7 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -3063,6 +3063,7 @@ class ChartEditorState extends HaxeUIState var targetSong:Song = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, false); subStateClosed.add(fixCamera); + subStateClosed.add(updateConductor); openSubState(new PlayState( { @@ -3080,10 +3081,17 @@ class ChartEditorState extends HaxeUIState { FlxG.cameras.reset(new FlxCamera()); FlxG.camera.focusOn(new FlxPoint(FlxG.width / 2, FlxG.height / 2)); + FlxG.camera.zoom = 1.0; add(this.component); } + function updateConductor(_:FlxSubState = null):Void + { + var targetPos = scrollPositionInMs; + Conductor.update(targetPos); + } + /** * Loads an instrumental from an absolute file path, replacing the current instrumental. * From 26112aa6ed1326ea8e7a1013427e517f977839b5 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 26 Jul 2023 21:55:47 -0400 Subject: [PATCH 04/29] Update HaxeUI --- hmm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hmm.json b/hmm.json index be6b7fea0..a5e7e0f05 100644 --- a/hmm.json +++ b/hmm.json @@ -42,14 +42,14 @@ "name": "haxeui-core", "type": "git", "dir": null, - "ref": "1ac2f76", + "ref": "6136bf6", "url": "https://github.com/haxeui/haxeui-core/" }, { "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "999fadd", + "ref": "be0b185", "url": "https://github.com/haxeui/haxeui-flixel" }, { From 0136202c36e728b9fc2bd3439aa448f9769bacfd Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 2 Aug 2023 10:15:54 -0400 Subject: [PATCH 05/29] Janky fix --- .../ui/debug/charting/ChartEditorState.hx | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 5aaf3f787..ddd50073a 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -3310,6 +3310,29 @@ class ChartEditorState extends HaxeUIState var targetSong:Song = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, false); + // TODO: Rework asset system so we can remove this. + switch (currentSongId) + { + case 'tutorial': + Paths.setCurrentLevel('tutorial'); + case 'bopeebo' | 'fresh' | 'dadbattle': + Paths.setCurrentLevel('week1'); + case 'spookeez' | 'south' | 'monster': + Paths.setCurrentLevel('week2'); + case 'pico' | 'blammed' | 'philly-nice': + Paths.setCurrentLevel('week3'); + case 'satin-panties' | 'high' | 'milf': + Paths.setCurrentLevel('week4'); + case 'cocoa' | 'eggnog' | 'winter-horrorland': + Paths.setCurrentLevel('week5'); + case 'senpai' | 'roses' | 'thorns': + Paths.setCurrentLevel('week6'); + case 'ugh' | 'guns' | 'stress': + Paths.setCurrentLevel('week7'); + case 'darnell' | 'lit-up' | '2hot' | 'blazin': + Paths.setCurrentLevel('weekend1'); + } + subStateClosed.add(fixCamera); subStateClosed.add(updateConductor); From b54c335886823e9e3c08ecdfab10452b5e9f1ede Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 2 Aug 2023 11:00:23 -0400 Subject: [PATCH 06/29] Fix for Menu->Freeplay and Play->Pause transitions --- source/funkin/MainMenuState.hx | 4 ++++ source/funkin/play/PlayState.hx | 3 +++ 2 files changed, 7 insertions(+) diff --git a/source/funkin/MainMenuState.hx b/source/funkin/MainMenuState.hx index bca20980c..fc493ef4b 100644 --- a/source/funkin/MainMenuState.hx +++ b/source/funkin/MainMenuState.hx @@ -1,5 +1,6 @@ package funkin; +import flixel.addons.transition.FlxTransitionableSubState; import funkin.ui.debug.DebugMenuSubState; import flixel.FlxObject; import flixel.FlxSprite; @@ -102,6 +103,9 @@ class MainMenuState extends MusicBeatState createMenuItem('freeplay', 'mainmenu/freeplay', function() { persistentDraw = true; persistentUpdate = false; + // Freeplay has its own custom transition + FlxTransitionableSubState.skipNextTransIn = true; + FlxTransitionableSubState.skipNextTransOut = true; openSubState(new FreeplayState()); }); diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 1674eec25..4d4cf3d40 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1,5 +1,6 @@ package funkin.play; +import flixel.addons.transition.FlxTransitionableSubState; import funkin.ui.debug.charting.ChartEditorState; import haxe.Int64; import funkin.play.notes.notestyle.NoteStyle; @@ -780,6 +781,8 @@ class PlayState extends MusicBeatSubState var pauseSubState:FlxSubState = new PauseSubState(isChartingMode); + FlxTransitionableSubState.skipNextTransIn = true; + FlxTransitionableSubState.skipNextTransOut = true; openSubState(pauseSubState); pauseSubState.camera = camHUD; // boyfriendPos.put(); // TODO: Why is this here? From 3a31c9731c048155f5e77301737fb327f47b6a35 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 2 Aug 2023 18:08:49 -0400 Subject: [PATCH 07/29] Fixed several bugs with Play State (mostly restarting the song) --- source/funkin/GitarooPause.hx | 3 ++ source/funkin/play/GameOverSubState.hx | 7 ++--- source/funkin/play/PlayState.hx | 27 ++++++++++++++++-- source/funkin/play/character/BaseCharacter.hx | 23 ++++----------- source/funkin/play/stage/Bopper.hx | 7 +++-- source/funkin/play/stage/Stage.hx | 28 ++++++++++++++++--- .../ui/debug/charting/ChartEditorState.hx | 4 +++ source/funkin/ui/story/StoryMenuState.hx | 4 +++ 8 files changed, 74 insertions(+), 29 deletions(-) diff --git a/source/funkin/GitarooPause.hx b/source/funkin/GitarooPause.hx index 5747de5e5..a4dc766be 100644 --- a/source/funkin/GitarooPause.hx +++ b/source/funkin/GitarooPause.hx @@ -3,6 +3,7 @@ package funkin; import flixel.FlxSprite; import flixel.graphics.frames.FlxAtlasFrames; import funkin.play.PlayState; +import flixel.addons.transition.FlxTransitionableState; class GitarooPause extends MusicBeatState { @@ -61,6 +62,8 @@ class GitarooPause extends MusicBeatState { if (replaySelect) { + FlxTransitionableState.skipNextTransIn = false; + FlxTransitionableState.skipNextTransOut = false; FlxG.switchState(new PlayState(previousParams)); } else diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 161da5191..6483ea9e5 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -124,8 +124,6 @@ class GameOverSubState extends MusicBeatSubState override function update(elapsed:Float) { - super.update(elapsed); - if (!hasStartedAnimation) { hasStartedAnimation = true; @@ -205,12 +203,13 @@ class GameOverSubState extends MusicBeatSubState if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished()) { startDeathMusic(1.0, false); + boyfriend.playAnimation('deathLoop' + animationSuffix); } } } - // Dispatch the onUpdate event. - dispatchEvent(new UpdateScriptEvent(elapsed)); + // Start death music before firstDeath gets replaced + super.update(elapsed); } /** diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 4d4cf3d40..5e8f964d1 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -488,8 +488,6 @@ class PlayState extends MusicBeatSubState */ public override function create():Void { - super.create(); - if (instance != null) { // TODO: Do something in this case? IDK. @@ -630,6 +628,9 @@ class PlayState extends MusicBeatSubState startCountdown(); } + // Do this last to prevent beatHit from being called before create() is done. + super.create(); + leftWatermarkText.cameras = [camHUD]; rightWatermarkText.cameras = [camHUD]; @@ -810,6 +811,16 @@ class PlayState extends MusicBeatSubState FlxG.watch.addQuick('bfAnim', currentStage.getBoyfriend().getCurrentAnimation()); } + if (currentStage.getBoyfriend() != null) + { + FlxG.watch.addQuick('bfCameraFocus', currentStage.getBoyfriend().cameraFocusPoint); + } + + if (currentStage.getDad() != null) + { + FlxG.watch.addQuick('dadCameraFocus', currentStage.getDad().cameraFocusPoint); + } + // TODO: Add a song event for Handle GF dance speed. // Handle player death. @@ -857,6 +868,8 @@ class PlayState extends MusicBeatSubState #end var gameOverSubState = new GameOverSubState(); + FlxTransitionableSubState.skipNextTransIn = true; + FlxTransitionableSubState.skipNextTransOut = true; openSubState(gameOverSubState); #if discord_rpc @@ -971,6 +984,9 @@ class PlayState extends MusicBeatSubState if (event.eventCanceled) return; + // Resume + FlxG.sound.music.play(); + if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals(); // Resume the countdown. @@ -1669,6 +1685,9 @@ class PlayState extends MusicBeatSubState { if (_exiting || vocals == null) return; + // Skip this if the music is paused (GameOver, Pause menu, etc.) + if (!FlxG.sound.music.playing) return; + vocals.pause(); FlxG.sound.music.play(); @@ -1700,6 +1719,8 @@ class PlayState extends MusicBeatSubState */ function onKeyPress(event:PreciseInputEvent):Void { + if (isGamePaused) return; + // Do the minimal possible work here. inputPressQueue.push(event); } @@ -1709,6 +1730,8 @@ class PlayState extends MusicBeatSubState */ function onKeyRelease(event:PreciseInputEvent):Void { + if (isGamePaused) return; + // Do the minimal possible work here. inputReleaseQueue.push(event); } diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index bcb73d543..42dfd2da4 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -227,12 +227,15 @@ class BaseCharacter extends Bopper public function resetCharacter(resetCamera:Bool = true):Void { // Reset the animation offsets. This will modify x and y to be the absolute position of the character. - this.animOffsets = [0, 0]; + // this.animOffsets = [0, 0]; // Now we can set the x and y to be their original values without having to account for animOffsets. this.resetPosition(); - // Make sure we are playing the idle animation (to reapply animOffsets)... + // Then reapply animOffsets... + // applyAnimationOffsets(getCurrentAnimation()); + + // Make sure we are playing the idle animation this.dance(true); // Force to avoid the old animation playing with the wrong offset at the start of the song. // ...then update the hitbox so that this.width and this.height are correct. this.updateHitbox(); @@ -344,7 +347,7 @@ class BaseCharacter extends Bopper if (isDead) { - playDeathAnimation(); + // playDeathAnimation(); return; } @@ -392,20 +395,6 @@ class BaseCharacter extends Bopper FlxG.watch.addQuick('holdTimer-${characterId}', holdTimer); } - /** - * Since no `onBeatHit` or `dance` calls happen in GameOverSubState, - * this regularly gets called instead. - * - * @param force Force the deathLoop animation to play, even if `firstDeath` is still playing. - */ - public function playDeathAnimation(force:Bool = false):Void - { - if (force || (getCurrentAnimation().startsWith('firstDeath') && isAnimationFinished())) - { - playAnimation('deathLoop' + GameOverSubState.animationSuffix); - } - } - public function isSinging():Bool { return getCurrentAnimation().startsWith('sing'); diff --git a/source/funkin/play/stage/Bopper.hx b/source/funkin/play/stage/Bopper.hx index 5fb1022fe..d88618f8a 100644 --- a/source/funkin/play/stage/Bopper.hx +++ b/source/funkin/play/stage/Bopper.hx @@ -158,8 +158,11 @@ class Bopper extends StageProp implements IPlayStateScriptedClass */ public function resetPosition() { - this.x = originalPosition.x + animOffsets[0]; - this.y = originalPosition.y + animOffsets[1]; + var oldAnimOffsets = [animOffsets[0], animOffsets[1]]; + animOffsets = [0, 0]; + this.x = originalPosition.x; + this.y = originalPosition.y; + animOffsets = oldAnimOffsets; } function update_shouldAlternate():Void diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index ef2d28430..dafba1c06 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -78,6 +78,10 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass if (getBoyfriend() != null) { getBoyfriend().resetCharacter(true); + // Reapply the camera offsets. + var charData = _data.characters.bf; + getBoyfriend().cameraFocusPoint.x += charData.cameraOffsets[0]; + getBoyfriend().cameraFocusPoint.y += charData.cameraOffsets[1]; } else { @@ -86,10 +90,18 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass if (getGirlfriend() != null) { getGirlfriend().resetCharacter(true); + // Reapply the camera offsets. + var charData = _data.characters.gf; + getGirlfriend().cameraFocusPoint.x += charData.cameraOffsets[0]; + getGirlfriend().cameraFocusPoint.y += charData.cameraOffsets[1]; } if (getDad() != null) { getDad().resetCharacter(true); + // Reapply the camera offsets. + var charData = _data.characters.dad; + getDad().cameraFocusPoint.x += charData.cameraOffsets[0]; + getDad().cameraFocusPoint.y += charData.cameraOffsets[1]; } // Reset positions of named props. @@ -216,8 +228,12 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass { cast(propSprite, Bopper).setAnimationOffsets(propAnim.name, propAnim.offsets[0], propAnim.offsets[1]); } - cast(propSprite, Bopper).originalPosition.x = dataProp.position[0]; - cast(propSprite, Bopper).originalPosition.y = dataProp.position[1]; + + if (!Std.isOfType(propSprite, BaseCharacter)) + { + cast(propSprite, Bopper).originalPosition.x = dataProp.position[0]; + cast(propSprite, Bopper).originalPosition.y = dataProp.position[1]; + } } if (dataProp.startingAnimation != null) @@ -357,8 +373,12 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass character.x = charData.position[0] - character.characterOrigin.x + character.globalOffsets[0]; character.y = charData.position[1] - character.characterOrigin.y + character.globalOffsets[1]; - character.originalPosition.x = character.x; - character.originalPosition.y = character.y; + @:privateAccess(funkin.play.stage.Bopper) + { + // Undo animOffsets before saving original position. + character.originalPosition.x = character.x + character.animOffsets[0]; + character.originalPosition.y = character.y + character.animOffsets[1]; + } character.cameraFocusPoint.x += charData.cameraOffsets[0]; character.cameraFocusPoint.y += charData.cameraOffsets[1]; diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index ddd50073a..8e5a65c80 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -6,6 +6,7 @@ import flixel.FlxCamera; import flixel.FlxSprite; import flixel.FlxSubState; import flixel.group.FlxSpriteGroup; +import flixel.addons.transition.FlxTransitionableState; import flixel.input.keyboard.FlxKey; import flixel.math.FlxPoint; import flixel.math.FlxRect; @@ -3336,6 +3337,9 @@ class ChartEditorState extends HaxeUIState subStateClosed.add(fixCamera); subStateClosed.add(updateConductor); + FlxTransitionableState.skipNextTransIn = false; + FlxTransitionableState.skipNextTransOut = false; + openSubState(new PlayState( { targetSong: targetSong, diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 29bc4beca..8d2db0543 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -5,6 +5,7 @@ import flixel.addons.transition.FlxTransitionableState; import flixel.FlxSprite; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.text.FlxText; +import flixel.addons.transition.FlxTransitionableState; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.util.FlxColor; @@ -521,6 +522,9 @@ class StoryMenuState extends MusicBeatState } new FlxTimer().start(1, function(tmr:FlxTimer) { + FlxTransitionableState.skipNextTransIn = false; + FlxTransitionableState.skipNextTransOut = false; + LoadingState.loadAndSwitchState(new PlayState( { targetSong: targetSong, From 113b4c45a41b0685960ced8fb2cab03cfe3874e9 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 3 Aug 2023 09:23:53 -0400 Subject: [PATCH 08/29] Attempt at fixing Github Actions on WIN and HTML5 --- .github/workflows/build-shit.yml | 2 +- hmm.json | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index 32c2a0ede..45d04b92f 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -39,7 +39,7 @@ jobs: haxelib run lime rebuild linux --clean - name: Build game run: | - sudo apt-get install -y libx11-dev libxinerama-dev libxrandr-dev libgl1-mesa-dev libgl-dev libxi-dev libxext-dev libasound2-dev + sudo apt-get install -y libx11-dev xorg-dev libxinerama-dev libxrandr-dev libgl1-mesa-dev libgl-dev libxi-dev libxext-dev libasound2-dev haxelib run lime build html5 -debug --times ls - uses: ./.github/actions/upload-itch diff --git a/hmm.json b/hmm.json index f9ac62363..d0dde499d 100644 --- a/hmm.json +++ b/hmm.json @@ -91,6 +91,13 @@ "ref": "acb0334c59bd4618f3c0277584d524ed0b288b5f", "url": "https://github.com/EliteMasterEric/lime" }, + { + "name": "mockatoo", + "type": "git", + "dir": null, + "ref": "master", + "url": "https://github.com/EliteMasterEric/mockatoo" + }, { "name": "openfl", "type": "git", @@ -116,4 +123,4 @@ "version": "0.11.0" } ] -} +} \ No newline at end of file From adc5043da2e6d4fa476d2a2ce39e60e27d1a968e Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 3 Aug 2023 09:31:54 -0400 Subject: [PATCH 09/29] Improvements to build workflows --- .github/workflows/build-shit.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index 45d04b92f..7c986c15d 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -10,7 +10,7 @@ jobs: outputs: should_run: ${{ steps.should_run.outputs.should_run }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: print latest_commit run: echo ${{ github.sha }} - id: should_run @@ -24,6 +24,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + submodules: 'recursive' - uses: ./.github/actions/setup-haxeshit - name: Build Lime # TODO: Remove the step that builds Lime later. @@ -35,11 +37,11 @@ jobs: git submodule sync --recursive git submodule update --recursive git status - sudo apt-get install -y libxinerama-dev + sudo apt-get install -y libxinerama-dev libxrandr-dev haxelib run lime rebuild linux --clean - name: Build game run: | - sudo apt-get install -y libx11-dev xorg-dev libxinerama-dev libxrandr-dev libgl1-mesa-dev libgl-dev libxi-dev libxext-dev libasound2-dev + sudo apt-get install -y libx11-dev xorg-dev libgl1-mesa-dev libgl-dev libxi-dev libxext-dev libasound2-dev haxelib run lime build html5 -debug --times ls - uses: ./.github/actions/upload-itch @@ -56,6 +58,8 @@ jobs: actions: write steps: - uses: actions/checkout@v3 + with: + submodules: 'recursive' - uses: ./.github/actions/setup-haxeshit - name: Build Lime # TODO: Remove the step that builds Lime later. From 70d8a3638b3aae7c8f0fc0de2fce7e28b09eea24 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 3 Aug 2023 09:39:06 -0400 Subject: [PATCH 10/29] . --- .github/workflows/build-shit.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index 7c986c15d..c52a1554f 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -37,11 +37,11 @@ jobs: git submodule sync --recursive git submodule update --recursive git status - sudo apt-get install -y libxinerama-dev libxrandr-dev + sudo apt-get install -y libxinerama-dev libxrandr-dev libgl1-mesa-dev haxelib run lime rebuild linux --clean - name: Build game run: | - sudo apt-get install -y libx11-dev xorg-dev libgl1-mesa-dev libgl-dev libxi-dev libxext-dev libasound2-dev + sudo apt-get install -y libx11-dev xorg-dev libgl-dev libxi-dev libxext-dev libasound2-dev haxelib run lime build html5 -debug --times ls - uses: ./.github/actions/upload-itch From 050086fb36278726ea89bee64dea269386bcaf29 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 3 Aug 2023 11:40:19 -0400 Subject: [PATCH 11/29] Fixed camera focus on game over. --- .github/actions/setup-haxeshit/action.yml | 2 +- source/funkin/play/PlayState.hx | 4 ++++ source/funkin/play/stage/Stage.hx | 21 +++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/.github/actions/setup-haxeshit/action.yml b/.github/actions/setup-haxeshit/action.yml index 6b565bfa2..ec8ed52d8 100644 --- a/.github/actions/setup-haxeshit/action.yml +++ b/.github/actions/setup-haxeshit/action.yml @@ -21,7 +21,7 @@ runs: - name: Installing Haxe lol run: | haxe -version - haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git + haxelib git haxelib https://github.com/HaxeFoundation/haxelib.git development haxelib version haxelib --global install hmm haxelib --global run hmm install --quiet diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 5e8f964d1..297f14d69 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1107,6 +1107,8 @@ class PlayState extends MusicBeatSubState // super.stepHit() returns false if a module cancelled the event. if (!super.stepHit()) return false; + if (isGamePaused) return false; + if (!startingSong && FlxG.sound.music != null && (Math.abs(FlxG.sound.music.time - (Conductor.songPosition - Conductor.offset)) > 200 @@ -1131,6 +1133,8 @@ class PlayState extends MusicBeatSubState // super.beatHit() returns false if a module cancelled the event. if (!super.beatHit()) return false; + if (isGamePaused) return false; + if (generatedMusic) { // TODO: Sort more efficiently, or less often, to improve performance. diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index dafba1c06..d2b157acd 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -694,6 +694,27 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass } } + public override function kill() + { + _skipTransformChildren = true; + alive = false; + exists = false; + _skipTransformChildren = false; + if (group != null) group.kill(); + } + + public override function remove(Sprite:FlxSprite, Splice:Bool = false):FlxSprite + { + var sprite:FlxSprite = cast Sprite; + sprite.x -= x; + sprite.y -= y; + // alpha + sprite.cameras = null; + + if (group != null) group.remove(Sprite, Splice); + return Sprite; + } + public function onScriptEvent(event:ScriptEvent) {} public function onPause(event:PauseScriptEvent) {} From 4e1a5d6d65e98108e4e40db3ae5ec64cbbad415e Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 3 Aug 2023 11:47:40 -0400 Subject: [PATCH 12/29] Use cached builds for Lime --- .github/workflows/build-shit.yml | 25 +------------------------ hmm.json | 4 ++-- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index c52a1554f..ddd6e8be0 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -27,21 +27,9 @@ jobs: with: submodules: 'recursive' - uses: ./.github/actions/setup-haxeshit - - name: Build Lime - # TODO: Remove the step that builds Lime later. - # Bash method - run: | - LIME_PATH=`haxelib libpath lime` - echo "Moving to $LIME_PATH" - cd $LIME_PATH - git submodule sync --recursive - git submodule update --recursive - git status - sudo apt-get install -y libxinerama-dev libxrandr-dev libgl1-mesa-dev - haxelib run lime rebuild linux --clean - name: Build game run: | - sudo apt-get install -y libx11-dev xorg-dev libgl-dev libxi-dev libxext-dev libasound2-dev + sudo apt-get install -y libx11-dev xorg-dev libgl-dev libxi-dev libxext-dev libasound2-dev libxinerama-dev libxrandr-dev libgl1-mesa-dev haxelib run lime build html5 -debug --times ls - uses: ./.github/actions/upload-itch @@ -61,17 +49,6 @@ jobs: with: submodules: 'recursive' - uses: ./.github/actions/setup-haxeshit - - name: Build Lime - # TODO: Remove the step that builds Lime later. - # Powershell method - run: | - $LIME_PATH = haxelib libpath lime - echo "Moving to $LIME_PATH" - cd $LIME_PATH - git submodule sync --recursive - git submodule update --recursive - git status - haxelib run lime rebuild windows --clean - name: Build game run: | haxelib run lime build windows -debug diff --git a/hmm.json b/hmm.json index d0dde499d..7c23f2880 100644 --- a/hmm.json +++ b/hmm.json @@ -88,7 +88,7 @@ "name": "lime", "type": "git", "dir": null, - "ref": "acb0334c59bd4618f3c0277584d524ed0b288b5f", + "ref": "58800b7810123cbe5c1f5b88f23e4ebf6fae6f3a", "url": "https://github.com/EliteMasterEric/lime" }, { @@ -123,4 +123,4 @@ "version": "0.11.0" } ] -} \ No newline at end of file +} From 29466af4b7b9c5993d9a803ed6c5e69ed425e5c7 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 3 Aug 2023 12:19:36 -0400 Subject: [PATCH 13/29] Fix issue where strumline would stay pressed when resetting. --- source/funkin/play/notes/Strumline.hx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index e7c5fc2a7..b343bee86 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -509,6 +509,13 @@ class Strumline extends FlxSpriteGroup if (cover == null) continue; cover.kill(); } + + heldKeys = [false, false, false, false]; + + for (dir in DIRECTIONS) + { + playStatic(dir); + } } public function applyNoteData(data:Array):Void From 5590331e10a47190f2c36e7faa4899c6454f6435 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 3 Aug 2023 12:41:54 -0400 Subject: [PATCH 14/29] Fix HTML typing issue --- source/funkin/input/PreciseInputManager.hx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/source/funkin/input/PreciseInputManager.hx b/source/funkin/input/PreciseInputManager.hx index 11a3c2007..7e6a6569b 100644 --- a/source/funkin/input/PreciseInputManager.hx +++ b/source/funkin/input/PreciseInputManager.hx @@ -28,10 +28,6 @@ class PreciseInputManager extends FlxKeyManager return instance ?? (instance = new PreciseInputManager()); } - static final MS_TO_US:Int64 = 1000; - static final US_TO_NS:Int64 = 1000; - static final MS_TO_NS:Int64 = MS_TO_US * US_TO_NS; - static final DIRECTIONS:Array = [NoteDirection.LEFT, NoteDirection.DOWN, NoteDirection.UP, NoteDirection.RIGHT]; public var onInputPressed:FlxTypedSignalVoid>; @@ -101,11 +97,11 @@ class PreciseInputManager extends FlxKeyManager // NOTE: This timestamp isn't that precise on standard HTML5 builds. // This is because of browser safeguards against timing attacks. // See https://web.dev/coop-coep to enable headers which allow for more precise timestamps. - return js.Browser.window.performance.now() * MS_TO_NS; + return haxe.Int64.fromFloat(js.Browser.window.performance.now() * Constants.NS_PER_MS); #elseif cpp // NOTE: If the game hard crashes on this line, rebuild Lime! // `lime rebuild windows -clean` - return lime._internal.backend.native.NativeCFFI.lime_sdl_get_ticks() * MS_TO_NS; + return lime._internal.backend.native.NativeCFFI.lime_sdl_get_ticks() * Constants.NS_PER_MS; #else throw "Eric didn't implement precise timestamps on this platform!"; #end @@ -176,7 +172,7 @@ class PreciseInputManager extends FlxKeyManager // TODO: Remove this line with SDL3 when timestamps change meaning. // This is because SDL3's timestamps are measured in nanoseconds, not milliseconds. - timestamp *= MS_TO_NS; + timestamp *= Constants.NS_PER_MS; updateKeyStates(key, true); @@ -198,7 +194,7 @@ class PreciseInputManager extends FlxKeyManager // TODO: Remove this line with SDL3 when timestamps change meaning. // This is because SDL3's timestamps are measured in nanoseconds, not milliseconds. - timestamp *= MS_TO_NS; + timestamp *= Constants.NS_PER_MS; updateKeyStates(key, false); From 2ba2aab2e3e6318f22e6a3227fd97e5a5c997449 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 3 Aug 2023 12:55:29 -0400 Subject: [PATCH 15/29] Update Polymod --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index 7c23f2880..c9dc959c7 100644 --- a/hmm.json +++ b/hmm.json @@ -109,7 +109,7 @@ "name": "polymod", "type": "git", "dir": null, - "ref": "631a3637f30997e47cd37bbab3cb6a75636a4b2a", + "ref": "bb82bfe040965dd55c3b48cb8a3008afdc101ce7", "url": "https://github.com/larsiusprime/polymod" }, { From ba0ed22c1b4df71cf41567cd8d42a01cc3fda60d Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 3 Aug 2023 13:07:30 -0400 Subject: [PATCH 16/29] Update Polymod --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index c9dc959c7..a52055d03 100644 --- a/hmm.json +++ b/hmm.json @@ -109,7 +109,7 @@ "name": "polymod", "type": "git", "dir": null, - "ref": "bb82bfe040965dd55c3b48cb8a3008afdc101ce7", + "ref": "4bcd614103469af79a320898b823d1df8a55c3de", "url": "https://github.com/larsiusprime/polymod" }, { From 1caf905ef85ccc501d24911b76dda20f1b25c5eb Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 3 Aug 2023 17:44:17 -0400 Subject: [PATCH 17/29] Update Lime --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index a52055d03..52d5ffad8 100644 --- a/hmm.json +++ b/hmm.json @@ -88,7 +88,7 @@ "name": "lime", "type": "git", "dir": null, - "ref": "58800b7810123cbe5c1f5b88f23e4ebf6fae6f3a", + "ref": "558798adc5bf0e82d70fef589a59ce88892e0b5b", "url": "https://github.com/EliteMasterEric/lime" }, { From 49622f2441aa77214840e923cc0565c27192a1e2 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 3 Aug 2023 22:22:29 -0400 Subject: [PATCH 18/29] Fix build issues caused by int64 handling --- source/funkin/import.hx | 1 + source/funkin/play/PlayState.hx | 3 ++- source/funkin/util/tools/Int64Tools.hx | 32 ++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 source/funkin/util/tools/Int64Tools.hx diff --git a/source/funkin/import.hx b/source/funkin/import.hx index 4ba062b8f..cd0af4b55 100644 --- a/source/funkin/import.hx +++ b/source/funkin/import.hx @@ -11,6 +11,7 @@ using Lambda; using StringTools; using funkin.util.tools.ArrayTools; using funkin.util.tools.ArraySortTools; +using funkin.util.tools.Int64Tools; using funkin.util.tools.IteratorTools; using funkin.util.tools.MapTools; using funkin.util.tools.StringTools; diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 297f14d69..4e8b1ce9d 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2326,7 +2326,8 @@ class PlayState extends MusicBeatSubState vocals.playerVolume = 1; // Calculate the input latency (do this as late as possible). - var inputLatencyMs:Float = haxe.Int64.toInt(PreciseInputManager.getCurrentTimestamp() - input.timestamp) / 1000.0 / 1000.0; + var currentTimestampNs:Int64 = PreciseInputManager.getCurrentTimestamp(); + var inputLatencyMs:Float = haxe.Int64.toInt(currentTimestampNs - input.timestamp) / Constants.NS_PER_MS; trace('Input: ${daNote.noteData.getDirectionName()} pressed ${inputLatencyMs}ms ago!'); // Get the offset and compensate for input latency. diff --git a/source/funkin/util/tools/Int64Tools.hx b/source/funkin/util/tools/Int64Tools.hx new file mode 100644 index 000000000..75448b36f --- /dev/null +++ b/source/funkin/util/tools/Int64Tools.hx @@ -0,0 +1,32 @@ +package funkin.util.tools; + +/** + * @see https://github.com/fponticelli/thx.core/blob/master/src/thx/Int64s.hx + */ +class Int64Tools +{ + static var min = haxe.Int64.make(0x80000000, 0); + static var one = haxe.Int64.make(0, 1); + static var two = haxe.Int64.ofInt(2); + static var zero = haxe.Int64.make(0, 0); + static var ten = haxe.Int64.ofInt(10); + + public static function toFloat(i:haxe.Int64):Float + { + var isNegative = false; + if (i < 0) + { + if (i < min) return -9223372036854775808.0; // most -ve value can't be made +ve + isNegative = true; + i = -i; + } + var multiplier = 1.0, ret = 0.0; + for (_ in 0...64) + { + if (haxe.Int64.and(i, one) != zero) ret += multiplier; + multiplier *= 2.0; + i = haxe.Int64.shr(i, 1); + } + return (isNegative ? -1 : 1) * ret; + } +} From 13671cf29053cb68e12ed18ddbb432ceb75a4ed2 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 4 Aug 2023 11:13:41 -0400 Subject: [PATCH 19/29] Make sure timestamps are consistent with use of Int64. --- source/funkin/input/PreciseInputManager.hx | 9 +++++++-- source/funkin/play/PlayState.hx | 3 +-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/source/funkin/input/PreciseInputManager.hx b/source/funkin/input/PreciseInputManager.hx index 7e6a6569b..6217b2fe7 100644 --- a/source/funkin/input/PreciseInputManager.hx +++ b/source/funkin/input/PreciseInputManager.hx @@ -84,6 +84,11 @@ class PreciseInputManager extends FlxKeyManager }; } + /** + * Convert from int to Int64. + */ + static final NS_PER_MS:Int64 = Constants.NS_PER_MS; + /** * Returns a precise timestamp, measured in nanoseconds. * Timestamp is only useful for comparing against other timestamps. @@ -97,11 +102,11 @@ class PreciseInputManager extends FlxKeyManager // NOTE: This timestamp isn't that precise on standard HTML5 builds. // This is because of browser safeguards against timing attacks. // See https://web.dev/coop-coep to enable headers which allow for more precise timestamps. - return haxe.Int64.fromFloat(js.Browser.window.performance.now() * Constants.NS_PER_MS); + return haxe.Int64.fromFloat(js.Browser.window.performance.now()) * NS_PER_MS; #elseif cpp // NOTE: If the game hard crashes on this line, rebuild Lime! // `lime rebuild windows -clean` - return lime._internal.backend.native.NativeCFFI.lime_sdl_get_ticks() * Constants.NS_PER_MS; + return lime._internal.backend.native.NativeCFFI.lime_sdl_get_ticks() * NS_PER_MS; #else throw "Eric didn't implement precise timestamps on this platform!"; #end diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 4e8b1ce9d..297f14d69 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -2326,8 +2326,7 @@ class PlayState extends MusicBeatSubState vocals.playerVolume = 1; // Calculate the input latency (do this as late as possible). - var currentTimestampNs:Int64 = PreciseInputManager.getCurrentTimestamp(); - var inputLatencyMs:Float = haxe.Int64.toInt(currentTimestampNs - input.timestamp) / Constants.NS_PER_MS; + var inputLatencyMs:Float = haxe.Int64.toInt(PreciseInputManager.getCurrentTimestamp() - input.timestamp) / 1000.0 / 1000.0; trace('Input: ${daNote.noteData.getDirectionName()} pressed ${inputLatencyMs}ms ago!'); // Get the offset and compensate for input latency. From 4852515c4295a3b87bb9e435c0f8e38a67257323 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 4 Aug 2023 11:18:00 -0400 Subject: [PATCH 20/29] Fix bug where pressing ENTER in UI would cause song to try to preview. Fix bug where trying to preview a newly created song would crash. --- source/funkin/play/song/Song.hx | 10 +++++++--- source/funkin/ui/debug/charting/ChartEditorState.hx | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 189adb840..ab22ad9e9 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -39,7 +39,11 @@ class Song implements IPlayStateScriptedClass var difficultyIds:Array; - public function new(id:String) + /** + * @param id The ID of the song to load. + * @param ignoreErrors If false, an exception will be thrown if the song data could not be loaded. + */ + public function new(id:String, ignoreErrors:Bool = false) { this.songId = id; @@ -48,7 +52,7 @@ class Song implements IPlayStateScriptedClass difficulties = new Map(); _metadata = SongDataParser.loadSongMetadata(songId); - if (_metadata == null || _metadata.length == 0) + if (_metadata == null || _metadata.length == 0 && !ignoreErrors) { throw 'Could not find song data for songId: $songId'; } @@ -60,7 +64,7 @@ class Song implements IPlayStateScriptedClass public static function buildRaw(songId:String, metadata:Array, variations:Array, charts:Map, ?validScore:Bool = false):Song { - var result:Song = new Song(songId); + var result:Song = new Song(songId, true); result._metadata.clear(); for (meta in metadata) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 8e5a65c80..4f487f70c 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -2761,7 +2761,7 @@ class ChartEditorState extends HaxeUIState */ function handleTestKeybinds():Void { - if (FlxG.keys.justPressed.ENTER) + if (!isHaxeUIDialogOpen && FlxG.keys.justPressed.ENTER) { var minimal = FlxG.keys.pressed.SHIFT; testSongInPlayState(minimal); From 3e093510af31fa3261bf4dcb10ae9008a766046b Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 4 Aug 2023 12:35:01 -0400 Subject: [PATCH 21/29] Fixed a bug where beat hit events were called multiple times during the conductor --- source/funkin/play/Countdown.hx | 6 +++--- source/funkin/play/PlayState.hx | 10 ---------- source/funkin/play/song/Song.hx | 20 ++++++++++++++++---- source/funkin/play/stage/Bopper.hx | 2 ++ source/funkin/play/stage/Stage.hx | 6 +++++- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index 48e98e84f..33c0f852f 100644 --- a/source/funkin/play/Countdown.hx +++ b/source/funkin/play/Countdown.hx @@ -54,9 +54,9 @@ class Countdown countdownStep = decrement(countdownStep); - // Handle onBeatHit events manually - @:privateAccess - PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0)); + // onBeatHit events are now properly dispatched by the Conductor even at negative timestamps, + // so calling this is no longer necessary. + // PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0)); // Countdown graphic. showCountdownGraphic(countdownStep, isPixelStyle); diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 297f14d69..e3d14289e 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -811,16 +811,6 @@ class PlayState extends MusicBeatSubState FlxG.watch.addQuick('bfAnim', currentStage.getBoyfriend().getCurrentAnimation()); } - if (currentStage.getBoyfriend() != null) - { - FlxG.watch.addQuick('bfCameraFocus', currentStage.getBoyfriend().cameraFocusPoint); - } - - if (currentStage.getDad() != null) - { - FlxG.watch.addQuick('dadCameraFocus', currentStage.getDad().cameraFocusPoint); - } - // TODO: Add a song event for Handle GF dance speed. // Handle player death. diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index ab22ad9e9..398c28753 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -51,13 +51,23 @@ class Song implements IPlayStateScriptedClass difficultyIds = []; difficulties = new Map(); - _metadata = SongDataParser.loadSongMetadata(songId); - if (_metadata == null || _metadata.length == 0 && !ignoreErrors) + try + { + _metadata = SongDataParser.loadSongMetadata(songId); + } + catch (e) + { + _metadata = []; + } + + if (_metadata.length == 0 && !ignoreErrors) { throw 'Could not find song data for songId: $songId'; } - - populateFromMetadata(); + else + { + populateFromMetadata(); + } } @:allow(funkin.play.song.Song) @@ -97,6 +107,8 @@ class Song implements IPlayStateScriptedClass */ function populateFromMetadata():Void { + if (_metadata == null || _metadata.length == 0) return; + // Variations may have different artist, time format, generatedBy, etc. for (metadata in _metadata) { diff --git a/source/funkin/play/stage/Bopper.hx b/source/funkin/play/stage/Bopper.hx index d88618f8a..a144026f5 100644 --- a/source/funkin/play/stage/Bopper.hx +++ b/source/funkin/play/stage/Bopper.hx @@ -203,10 +203,12 @@ class Bopper extends StageProp implements IPlayStateScriptedClass { if (hasDanced) { + trace('DanceRight (alternate)'); playAnimation('danceRight$idleSuffix', forceRestart); } else { + trace('DanceLeft (alternate)'); playAnimation('danceLeft$idleSuffix', forceRestart); } hasDanced = !hasDanced; diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index d2b157acd..f4f380a0b 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -241,7 +241,11 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass propSprite.animation.play(dataProp.startingAnimation); } - if (Std.isOfType(propSprite, Bopper)) + if (Std.isOfType(propSprite, BaseCharacter)) + { + // Character stuff. + } + else if (Std.isOfType(propSprite, Bopper)) { addBopper(cast propSprite, dataProp.name); } From a0a8d472165fcf3a8b1954bb5f8a89995480712c Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 4 Aug 2023 16:15:07 -0400 Subject: [PATCH 22/29] Imported music now plays properly in chart editor state --- source/funkin/play/Countdown.hx | 4 +- source/funkin/play/PlayState.hx | 50 ++++++++++++++----- .../ui/debug/charting/ChartEditorState.hx | 11 +++- 3 files changed, 49 insertions(+), 16 deletions(-) diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index 33c0f852f..f521f9ffc 100644 --- a/source/funkin/play/Countdown.hx +++ b/source/funkin/play/Countdown.hx @@ -39,8 +39,8 @@ class Countdown PlayState.instance.isInCountdown = true; Conductor.update(PlayState.instance.startTimestamp + Conductor.beatLengthMs * -5); // Handle onBeatHit events manually - @:privateAccess - PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0)); + // @:privateAccess + // PlayState.instance.dispatchEvent(new SongTimeScriptEvent(ScriptEvent.SONG_BEAT_HIT, 0, 0)); // The timer function gets called based on the beat of the song. countdownTimer = new FlxTimer(); diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index e3d14289e..d85cc6a03 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -7,6 +7,7 @@ import funkin.play.notes.notestyle.NoteStyle; import funkin.data.notestyle.NoteStyleData; import funkin.data.notestyle.NoteStyleRegistry; import flixel.addons.display.FlxPieDial; +import flixel.addons.transition.Transition; import flixel.addons.transition.FlxTransitionableState; import flixel.FlxCamera; import flixel.FlxObject; @@ -93,6 +94,11 @@ typedef PlayStateParams = * If specified, the game will jump to the specified timestamp after the countdown ends. */ ?startTimestamp:Float, + /** + * If specified, the game will not load the instrumental or vocal tracks, + * and must be loaded externally. + */ + ?overrideMusic:Bool, } /** @@ -243,6 +249,8 @@ class PlayState extends MusicBeatSubState public var startTimestamp:Float = 0.0; + var overrideMusic:Bool = false; + public var isSubState(get, null):Bool; function get_isSubState():Bool @@ -316,7 +324,7 @@ class PlayState extends MusicBeatSubState /** * A group of audio tracks, used to play the song's vocals. */ - var vocals:VoicesGroup; + public var vocals:VoicesGroup; #if discord_rpc // Discord RPC variables @@ -479,6 +487,7 @@ class PlayState extends MusicBeatSubState isPracticeMode = params.practiceMode ?? false; isMinimalMode = params.minimalMode ?? false; startTimestamp = params.startTimestamp ?? 0.0; + overrideMusic = params.overrideMusic ?? false; // Don't do anything else here! Wait until create() when we attach to the camera. } @@ -555,16 +564,17 @@ class PlayState extends MusicBeatSubState this.persistentDraw = true; // Stop any pre-existing music. - if (FlxG.sound.music != null) FlxG.sound.music.stop(); + if (!overrideMusic && FlxG.sound.music != null) FlxG.sound.music.stop(); // Prepare the current song's instrumental and vocals to be played. - if (currentChart != null) + if (!overrideMusic && currentChart != null) { currentChart.cacheInst(currentPlayerId); currentChart.cacheVocals(currentPlayerId); } // Prepare the Conductor. + Conductor.forceBPM(null); Conductor.mapTimeChanges(currentChart.timeChanges); Conductor.update((Conductor.beatLengthMs * -5) + startTimestamp); @@ -966,7 +976,7 @@ class PlayState extends MusicBeatSubState */ public override function closeSubState():Void { - if (isGamePaused) + if (Std.isOfType(subState, PauseSubState)) { var event:ScriptEvent = new ScriptEvent(ScriptEvent.RESUME, true); @@ -994,6 +1004,10 @@ class PlayState extends MusicBeatSubState } #end } + else if (Std.isOfType(subState, Transition)) + { + // Do nothing. + } super.closeSubState(); } @@ -1534,12 +1548,16 @@ class PlayState extends MusicBeatSubState trace('Song difficulty could not be loaded.'); } - Conductor.forceBPM(currentChart.getStartingBPM()); + // Conductor.forceBPM(currentChart.getStartingBPM()); - vocals = currentChart.buildVocals(currentPlayerId); - if (vocals.members.length == 0) + if (!overrideMusic) { - trace('WARNING: No vocals found for this song.'); + vocals = currentChart.buildVocals(currentPlayerId); + + if (vocals.members.length == 0) + { + trace('WARNING: No vocals found for this song.'); + } } regenNoteData(); @@ -1648,7 +1666,7 @@ class PlayState extends MusicBeatSubState startingSong = false; - if (!isGamePaused && currentChart != null) + if (!overrideMusic && !isGamePaused && currentChart != null) { currentChart.playInst(1.0, false); } @@ -2600,9 +2618,17 @@ class PlayState extends MusicBeatSubState // TODO: Uncache the song. } - // Stop the music. - FlxG.sound.music.pause(); - vocals.stop(); + if (!overrideMusic) + { + // Stop the music. + FlxG.sound.music.pause(); + vocals.stop(); + } + else + { + FlxG.sound.music.pause(); + remove(vocals); + } // Remove reference to stage and remove sprites from it to save memory. if (currentStage != null) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 4f487f70c..b89748d87 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -3340,7 +3340,7 @@ class ChartEditorState extends HaxeUIState FlxTransitionableState.skipNextTransIn = false; FlxTransitionableState.skipNextTransOut = false; - openSubState(new PlayState( + var targetState = new PlayState( { targetSong: targetSong, targetDifficulty: selectedDifficulty, @@ -3349,7 +3349,14 @@ class ChartEditorState extends HaxeUIState practiceMode: true, minimalMode: minimal, startTimestamp: startTimestamp, - })); + overrideMusic: true, + }); + + // Override music. + FlxG.sound.music = audioInstTrack; + targetState.vocals = audioVocalTrackGroup; + + openSubState(targetState); } function fixCamera(_:FlxSubState = null):Void From b9c25d6ed9afed2d1b51b39b1c9f563365e0de65 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 4 Aug 2023 17:25:13 -0400 Subject: [PATCH 23/29] Fix crashing, broken countdown, broken miss muting in song preview --- source/funkin/play/Countdown.hx | 10 ++++-- source/funkin/play/PlayState.hx | 7 ++-- source/funkin/play/character/BaseCharacter.hx | 2 +- .../debug/charting/ChartEditorNotePreview.hx | 2 +- .../ui/debug/charting/ChartEditorState.hx | 32 +++++++++++++++---- 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index f521f9ffc..5ccb6e24c 100644 --- a/source/funkin/play/Countdown.hx +++ b/source/funkin/play/Countdown.hx @@ -31,7 +31,10 @@ class Countdown { countdownStep = BEFORE; var cancelled:Bool = propagateCountdownEvent(countdownStep); - if (cancelled) return false; + if (cancelled) + { + return false; + } // Stop any existing countdown. stopCountdown(); @@ -67,7 +70,10 @@ class Countdown // Event handling bullshit. var cancelled:Bool = propagateCountdownEvent(countdownStep); - if (cancelled) pauseCountdown(); + if (cancelled) + { + pauseCountdown(); + } if (countdownStep == AFTER) { diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index d85cc6a03..5409040ec 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -952,7 +952,7 @@ class PlayState extends MusicBeatSubState { // If there is a substate which requires the game to continue, // then make this a condition. - var shouldPause = true; + var shouldPause = (Std.isOfType(subState, PauseSubState) || Std.isOfType(subState, GameOverSubState)); if (shouldPause) { @@ -966,6 +966,7 @@ class PlayState extends MusicBeatSubState // Pause the countdown. Countdown.pauseCountdown(); } + else {} super.openSubState(subState); } @@ -1672,7 +1673,8 @@ class PlayState extends MusicBeatSubState } FlxG.sound.music.onComplete = endSong; - FlxG.sound.music.play(false, startTimestamp); + FlxG.sound.music.play(); + FlxG.sound.music.time = startTimestamp; trace('Playing vocals...'); add(vocals); vocals.play(); @@ -2627,6 +2629,7 @@ class PlayState extends MusicBeatSubState else { FlxG.sound.music.pause(); + vocals.pause(); remove(vocals); } diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index 42dfd2da4..72f968538 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -235,8 +235,8 @@ class BaseCharacter extends Bopper // Then reapply animOffsets... // applyAnimationOffsets(getCurrentAnimation()); - // Make sure we are playing the idle animation this.dance(true); // Force to avoid the old animation playing with the wrong offset at the start of the song. + // Make sure we are playing the idle animation // ...then update the hitbox so that this.width and this.height are correct. this.updateHitbox(); diff --git a/source/funkin/ui/debug/charting/ChartEditorNotePreview.hx b/source/funkin/ui/debug/charting/ChartEditorNotePreview.hx index 27951f079..d3296c400 100644 --- a/source/funkin/ui/debug/charting/ChartEditorNotePreview.hx +++ b/source/funkin/ui/debug/charting/ChartEditorNotePreview.hx @@ -40,7 +40,7 @@ class ChartEditorNotePreview extends FlxSprite */ function buildBackground():Void { - makeGraphic(WIDTH, 0, BG_COLOR); + makeGraphic(WIDTH, previewHeight, BG_COLOR); } /** diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index b89748d87..94cf05fc5 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -1247,7 +1247,8 @@ class ChartEditorState extends HaxeUIState var height:Int = FlxG.height - MENU_BAR_HEIGHT - GRID_TOP_PAD - 200; notePreview = new ChartEditorNotePreview(height); notePreview.y = MENU_BAR_HEIGHT + GRID_TOP_PAD; - add(notePreview); + // TODO: Re-enable. + // add(notePreview); } function buildSpectrogram(target:FlxSound):Void @@ -3496,14 +3497,23 @@ class ChartEditorState extends HaxeUIState * @param charKey Character to load the vocal track for. * @return Success or failure. */ - public function loadVocalsFromAsset(path:String, charKey:String = 'default'):Bool + public function loadVocalsFromAsset(path:String, charType:CharacterType = OTHER):Bool { var vocalTrack:FlxSound = FlxG.sound.load(path, 1.0, false); if (vocalTrack != null) { - audioVocalTrackGroup.add(vocalTrack); - - audioVocalTrackData.set(charKey, Assets.getBytes(path)); + switch (charType) + { + case CharacterType.BF: + audioVocalTrackGroup.addPlayerVoice(vocalTrack); + audioVocalTrackData.set(currentSongCharacterPlayer, Assets.getBytes(path)); + case CharacterType.DAD: + audioVocalTrackGroup.addOpponentVoice(vocalTrack); + audioVocalTrackData.set(currentSongCharacterOpponent, Assets.getBytes(path)); + default: + audioVocalTrackGroup.add(vocalTrack); + audioVocalTrackData.set('default', Assets.getBytes(path)); + } return true; } @@ -3565,9 +3575,17 @@ class ChartEditorState extends HaxeUIState loadInstrumentalFromAsset(Paths.inst(songId)); var voiceList:Array = song.getDifficulty(selectedDifficulty).buildVoiceList(); - for (voicePath in voiceList) + if (voiceList.length == 2) { - loadVocalsFromAsset(voicePath); + loadVocalsFromAsset(voiceList[0], BF); + loadVocalsFromAsset(voiceList[1], DAD); + } + else + { + for (voicePath in voiceList) + { + loadVocalsFromAsset(voicePath); + } } NotificationManager.instance.addNotification( From 3e02f7fca931f7d73cb9bc06171525c28f9e8113 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 9 Aug 2023 00:28:09 -0400 Subject: [PATCH 24/29] Fix a bug where `beatState` sometimes gets called before `create` --- source/funkin/play/PlayState.hx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 3e7325ae4..de096e2a0 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -275,6 +275,11 @@ class PlayState extends MusicBeatState */ var startingSong:Bool = false; + /** + * False until `create()` has completed. + */ + var initialized:Bool = false; + /** * A group of audio tracks, used to play the song's vocals. */ @@ -592,6 +597,8 @@ class PlayState extends MusicBeatState FlxG.console.registerObject('playState', this); #end + + initialized = true; } public override function update(elapsed:Float):Void @@ -1029,7 +1036,7 @@ class PlayState extends MusicBeatState override function stepHit():Bool { - if (criticalFailure) return false; + if (criticalFailure || !initialized) return false; // super.stepHit() returns false if a module cancelled the event. if (!super.stepHit()) return false; @@ -1052,7 +1059,7 @@ class PlayState extends MusicBeatState override function beatHit():Bool { - if (criticalFailure) return false; + if (criticalFailure || !initialized) return false; // super.beatHit() returns false if a module cancelled the event. if (!super.beatHit()) return false; From 34abee594db015d0ab90595fbaa3004dd0857da7 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 9 Aug 2023 18:11:19 -0400 Subject: [PATCH 25/29] Fix bug where onUpdate was called twice per frame --- source/funkin/play/PlayState.hx | 3 --- 1 file changed, 3 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index a4f29fda9..a28c35c67 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -923,9 +923,6 @@ class PlayState extends MusicBeatSubState // Moving notes into position is now done by Strumline.update(). processNotes(elapsed); - - // Dispatch the onUpdate event to scripted elements. - dispatchEvent(new UpdateScriptEvent(elapsed)); } public override function dispatchEvent(event:ScriptEvent):Void From de6972cb90049f89604c1baa7da9a5d49d29a6ea Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Wed, 9 Aug 2023 18:11:50 -0400 Subject: [PATCH 26/29] Fix issue where stage wasn't loaded properly, and metronome would play in song preview. --- .../funkin/ui/debug/charting/ChartEditorState.hx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 94cf05fc5..8d6743eca 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -378,7 +378,7 @@ class ChartEditorState extends HaxeUIState /** * Whether to play a metronome sound while the playhead is moving. */ - var shouldPlayMetronome:Bool = true; + var isMetronomeEnabled:Bool = true; /** * Use the tool window to affect how the user interacts with the program. @@ -903,7 +903,7 @@ class ChartEditorState extends HaxeUIState function get_currentSongId():String { - return currentSongName.toLowerKebabCase(); + return currentSongName.toLowerKebabCase().replace('.', '').replace(' ', '-'); } var currentSongArtist(get, set):String; @@ -1197,7 +1197,7 @@ class ChartEditorState extends HaxeUIState gridPlayhead.add(playheadBlock); // Character icons. - healthIconDad = new HealthIcon('dad'); + healthIconDad = new HealthIcon(currentSongCharacterOpponent); healthIconDad.autoUpdate = false; healthIconDad.size.set(0.5, 0.5); healthIconDad.x = gridTiledSprite.x - 15 - (HealthIcon.HEALTH_ICON_SIZE * 0.5); @@ -1205,7 +1205,7 @@ class ChartEditorState extends HaxeUIState add(healthIconDad); healthIconDad.zIndex = 30; - healthIconBF = new HealthIcon('bf'); + healthIconBF = new HealthIcon(currentSongCharacterPlayer); healthIconBF.autoUpdate = false; healthIconBF.size.set(0.5, 0.5); healthIconBF.x = gridTiledSprite.x + GRID_SIZE * (STRUMLINE_SIZE * 2 + 1) + 15; @@ -1451,8 +1451,8 @@ class ChartEditorState extends HaxeUIState }); setUICheckboxSelected('menuBarItemThemeDark', currentTheme == ChartEditorTheme.Dark); - addUIChangeListener('menubarItemMetronomeEnabled', event -> shouldPlayMetronome = event.value); - setUICheckboxSelected('menubarItemMetronomeEnabled', shouldPlayMetronome); + addUIChangeListener('menubarItemMetronomeEnabled', event -> isMetronomeEnabled = event.value); + setUICheckboxSelected('menubarItemMetronomeEnabled', isMetronomeEnabled); addUIChangeListener('menubarItemPlayerHitsounds', event -> hitsoundsEnabledPlayer = event.value); setUICheckboxSelected('menubarItemPlayerHitsounds', hitsoundsEnabledPlayer); @@ -1616,7 +1616,7 @@ class ChartEditorState extends HaxeUIState // dispatchEvent gets called here. if (!super.beatHit()) return false; - if (shouldPlayMetronome && (audioInstTrack != null && audioInstTrack.playing)) + if (isMetronomeEnabled && this.subState == null && (audioInstTrack != null && audioInstTrack.playing)) { playMetronomeTick(Conductor.currentBeat % 4 == 0); } @@ -3574,7 +3574,7 @@ class ChartEditorState extends HaxeUIState loadInstrumentalFromAsset(Paths.inst(songId)); - var voiceList:Array = song.getDifficulty(selectedDifficulty).buildVoiceList(); + var voiceList:Array = song.getDifficulty(selectedDifficulty).buildVoiceList(currentSongCharacterPlayer); if (voiceList.length == 2) { loadVocalsFromAsset(voiceList[0], BF); From 214c706cac07d02b26247d9cf78447c87cd8bf1c Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 10 Aug 2023 14:03:57 -0400 Subject: [PATCH 27/29] Fix a story menu crash --- source/funkin/ui/story/StoryMenuState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 8d2db0543..8276777ab 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -470,7 +470,7 @@ class StoryMenuState extends MusicBeatState // super.dispatchEvent(event) dispatches event to module scripts. super.dispatchEvent(event); - if ((levelProps?.length ?? 0) > 0) + if (levelProps != null && levelProps.length > 0) { // Dispatch event to props. for (prop in levelProps) From 2226f1f05e5687c9853ccf96d8409e7b9f418ed5 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 10 Aug 2023 14:04:09 -0400 Subject: [PATCH 28/29] Fix an issue where tracknames weren't loading properly --- source/funkin/ui/story/Level.hx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/funkin/ui/story/Level.hx b/source/funkin/ui/story/Level.hx index 83682fec9..7913ac8ca 100644 --- a/source/funkin/ui/story/Level.hx +++ b/source/funkin/ui/story/Level.hx @@ -71,7 +71,12 @@ class Level implements IRegistryEntry { var songList:Array = getSongs() ?? []; var songNameList:Array = songList.map(function(songId) { - return funkin.play.song.SongData.SongDataParser.fetchSong(songId) ?.getDifficulty(difficulty) ?.songName ?? 'Unknown'; + var song:Song = funkin.play.song.SongData.SongDataParser.fetchSong(songId); + if (song == null) return 'Unknown'; + var songDifficulty:SongDifficulty = song.getDifficulty(difficulty); + if (songDifficulty == null) songDifficulty = song.getDifficulty(); + var songName:String = songDifficulty?.songName; + return songName ?? 'Unknown'; }); return songNameList; } From 6096f12307f76d41670e00455f170d20ccc0a14a Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 10 Aug 2023 14:21:10 -0400 Subject: [PATCH 29/29] Fix an issue where new songs on specific stages would crash --- .../ui/debug/charting/ChartEditorState.hx | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 8d6743eca..26b001d7e 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -3313,25 +3313,23 @@ class ChartEditorState extends HaxeUIState var targetSong:Song = Song.buildRaw(currentSongId, songMetadata.values(), availableVariations, songChartData, false); // TODO: Rework asset system so we can remove this. - switch (currentSongId) + switch (currentSongStage) { - case 'tutorial': - Paths.setCurrentLevel('tutorial'); - case 'bopeebo' | 'fresh' | 'dadbattle': + case 'mainStage': Paths.setCurrentLevel('week1'); - case 'spookeez' | 'south' | 'monster': + case 'spookyMansion': Paths.setCurrentLevel('week2'); - case 'pico' | 'blammed' | 'philly-nice': + case 'phillyTrain': Paths.setCurrentLevel('week3'); - case 'satin-panties' | 'high' | 'milf': + case 'limoRide': Paths.setCurrentLevel('week4'); - case 'cocoa' | 'eggnog' | 'winter-horrorland': + case 'mallXmas' | 'mallEvil': Paths.setCurrentLevel('week5'); - case 'senpai' | 'roses' | 'thorns': + case 'school' | 'schoolEvil': Paths.setCurrentLevel('week6'); - case 'ugh' | 'guns' | 'stress': + case 'tankmanBattlefield': Paths.setCurrentLevel('week7'); - case 'darnell' | 'lit-up' | '2hot' | 'blazin': + case 'phillyStreets' | 'phillyBlazin': Paths.setCurrentLevel('weekend1'); }