From ec65cb3d27fe8b8010dceea095dd664f365921d1 Mon Sep 17 00:00:00 2001 From: sector-a <82838084+sector-a@users.noreply.github.com> Date: Thu, 2 Oct 2025 02:13:45 +0300 Subject: [PATCH] Add a better playtest menu fix spelling mistake Bring back neccessary stuff Fix input a bit Bring back neccessary stuff AGAIN --- source/funkin/InitState.hx | 24 +-- .../funkin/external/android/CallbackUtil.hx | 2 +- .../java/funkin/extension/FNFCExtension.java | 2 +- source/funkin/mobile/util/FNFCUtil.hx | 8 +- source/funkin/ui/debug/ChartPlaytestState.hx | 150 ------------------ .../ui/debug/playtest/ChartPlaytestMenu.hx | 99 ++++++++++++ .../debug/playtest/ChartPlaytestMenuButton.hx | 106 +++++++++++++ source/funkin/util/file/FNFCUtil.hx | 15 ++ 8 files changed, 233 insertions(+), 173 deletions(-) delete mode 100644 source/funkin/ui/debug/ChartPlaytestState.hx create mode 100644 source/funkin/ui/debug/playtest/ChartPlaytestMenu.hx create mode 100644 source/funkin/ui/debug/playtest/ChartPlaytestMenuButton.hx diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 390240b67..cf99f8e46 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -37,7 +37,7 @@ import funkin.util.macro.MacroUtil; import funkin.util.TrackerUtil; import funkin.util.WindowUtil; import openfl.display.BitmapData; -import funkin.ui.debug.ChartPlaytestState; +import funkin.ui.debug.playtest.ChartPlaytestMenu; #if FEATURE_DISCORD_RPC import funkin.api.discord.DiscordClient; #end @@ -392,26 +392,23 @@ class InitState extends FlxState fnfsTargetPath: params.stage.stagePath, })); } - #if sys else if (params.song.shouldLoadSong && params.song.songPath != null) { - FlxG.switchState(() -> new ChartPlaytestState( - { - fnfcFilePath: params.song.songPath, - })); + FlxG.switchState(() -> new ChartPlaytestMenu(params.song.songPath)); } else { // FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu')); #if mobile + funkin.mobile.util.FNFCUtil.onFNFCOpen.add(function(filePath:String) { + openSubState(new ChartPlaytestMenu(filePath)); + }); + final fnfcFile = funkin.mobile.util.FNFCUtil.queryFNFC(); if (fnfcFile != null) { trace('launching FNFC from $fnfcFile'); - FlxG.switchState(() -> new ChartPlaytestState( - { - fnfcFilePath: fnfcFile, - })); + FlxG.switchState(() -> new ChartPlaytestMenu(fnfcFile)); } else { @@ -421,13 +418,6 @@ class InitState extends FlxState FlxG.switchState(() -> new TitleState()); #end } - #else - else - { - // FlxG.sound.cache(Paths.music('freakyMenu/freakyMenu')); - FlxG.switchState(() -> new TitleState()); - } - #end } /** diff --git a/source/funkin/external/android/CallbackUtil.hx b/source/funkin/external/android/CallbackUtil.hx index f3470240a..9b14243ee 100644 --- a/source/funkin/external/android/CallbackUtil.hx +++ b/source/funkin/external/android/CallbackUtil.hx @@ -27,7 +27,7 @@ class CallbackUtil #if (lime >= "8.0.0") implements JNISafety #end * * First argument is the FNFC file path. */ - public static var onFNCOpen:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + public static var onFNFCOpen:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); /** * Initializes the callback utility. diff --git a/source/funkin/external/android/java/funkin/extension/FNFCExtension.java b/source/funkin/external/android/java/funkin/extension/FNFCExtension.java index af7f67e80..c907f58b3 100644 --- a/source/funkin/external/android/java/funkin/extension/FNFCExtension.java +++ b/source/funkin/external/android/java/funkin/extension/FNFCExtension.java @@ -57,7 +57,7 @@ public class FNFCExtension extends Extension if (doCallback) { - CallbackUtil.callMethod("onFNCOpen", lastFNFC); + CallbackUtil.callMethod("onFNFCOpen", lastFNFC); } } catch (IOException e) diff --git a/source/funkin/mobile/util/FNFCUtil.hx b/source/funkin/mobile/util/FNFCUtil.hx index b2be6b20c..f4f24becc 100644 --- a/source/funkin/mobile/util/FNFCUtil.hx +++ b/source/funkin/mobile/util/FNFCUtil.hx @@ -15,19 +15,19 @@ import flixel.util.FlxSignal; */ class FNFCUtil { - public static var onFNCOpen:FlxTypedSignalVoid>; + public static var onFNFCOpen:FlxTypedSignalVoid>; public static function init():Void { - onFNCOpen = new FlxTypedSignalVoid>(); + onFNFCOpen = new FlxTypedSignalVoid>(); #if ios FlxG.stage.window.onDropFile.add(function(_) { final url = queryFNFC(); - if (url != null) onFNCOpen.dispatch(getFNFCFromURL(url)); + if (url != null) onFNFCOpen.dispatch(getFNFCFromURL(url)); }); #elseif android - CallbackUtil.onFNCOpen.add(onFNCOpen.dispatch); + CallbackUtil.onFNFCOpen.add(onFNFCOpen.dispatch); #end final fnfcFile:String = queryFNFC(); diff --git a/source/funkin/ui/debug/ChartPlaytestState.hx b/source/funkin/ui/debug/ChartPlaytestState.hx deleted file mode 100644 index 01ab2d582..000000000 --- a/source/funkin/ui/debug/ChartPlaytestState.hx +++ /dev/null @@ -1,150 +0,0 @@ -package funkin.ui.debug; - -#if sys -import flixel.FlxSubState; -import flixel.text.FlxText; -import flixel.tweens.FlxTween; -import flixel.util.FlxTimer; -import funkin.data.freeplay.player.PlayerRegistry; -import funkin.play.ResultState.ResultsStateParams; -import funkin.ui.MenuList.MenuTypedItem; -import funkin.ui.MenuList.MenuTypedList; -import funkin.ui.options.items.CheckboxPreferenceItem; -import funkin.util.file.FNFCUtil; - -/** - * When playtesting from an FNFC file, we display a debug UI to choose a difficulty first. - */ -class ChartPlaytestState extends MusicBeatState -{ - var fnfcFilePath:String; - - var currentDifficulty:String; - var currentVariation:String; - - var items:MenuTypedList>; - - var justClosedSubstate:Bool = false; - - public function new(params:ChartPlaytestStateParams) - { - super(); - fnfcFilePath = params.fnfcFilePath; - persistentUpdate = false; - persistentDraw = false; - - items = new MenuTypedList>(); - add(items); - - createItems(); - - // Try to force the camera not to move - this.camera.follow(null); - this.camera.zoom = 1; - this.camera.x = 0; - this.camera.y = 0; - this.camera.scroll.x = 0; - this.camera.scroll.y = 0; - } - - override function update(elapsed:Float):Void - { - super.update(elapsed); - - if (this.subState == null) {} - } - - function createItems():Void - { - createTextItem("Playtest Song", function() { - try - { - FNFCUtil.playSongFromFNFCPath(fnfcFilePath, currentDifficulty, currentVariation); - } - catch (e) - { - lime.app.Application.current.window.alert('$e', 'Could Not Playtest Chart'); - } - }); - - createToggleListItem("Variation", Constants.DEFAULT_VARIATION_LIST, function(value:String) { - currentVariation = value; - }); - - createToggleListItem("Difficulty", Constants.DEFAULT_DIFFICULTY_LIST_FULL, function(value:String) { - currentDifficulty = value; - }); - } - - function createTextItem(name:String, ?onChange:Void->Void):MenuTypedItem - { - var txt:FlxText = new FlxText(0, 0, name); - txt.antialiasing = false; - txt.setFormat(Paths.font('vcr.ttf'), 32); - txt.scrollFactor.set(0, 0); - - var menuItem:MenuTypedItem = new MenuTypedItem(10, 36 * items.length, txt, name, onChange); - menuItem.setEmptyBackground(); - menuItem.fireInstantly = true; - return items.addItem(name, menuItem); - } - - function createCheckboxItem(name:String, ?onChange:Bool->Void):Void - { - var toggle:Bool = false; - var menuItem:MenuTypedItem = createTextItem(name); - menuItem.callback = function() { - menuItem.label.text = name + ": " + (toggle ? "on" : "off"); - toggle = !toggle; - onChange(toggle); - }; - } - - /** - * Toggles between different options in a list - * @param name - * @param toggleList - * @param onChange - * @return MenuTypedItem - */ - function createToggleListItem(name:String, toggleList:Array, ?onChange:String->Void):MenuTypedItem - { - var toggleCounter:Int = 0; - var menuItem:MenuTypedItem = createTextItem(name); - - // We create and call the labelCallback here to initalize it - var labelCallback:Void->Void = function() { - menuItem.label.text = name + ":" + toggleList[toggleCounter]; - onChange(toggleList[toggleCounter]); - }; - labelCallback(); - - menuItem.callback = function() { - toggleCounter = (toggleCounter + 1) % toggleList.length; - labelCallback(); - }; - - return menuItem; - } - - override function onCloseSubStateComplete(targetState:FlxSubState):Void - { - super.onCloseSubStateComplete(targetState); - - justClosedSubstate = true; - - // Try to force the camera not to move - this.camera.follow(null); - this.camera.zoom = 1; - this.camera.x = 0; - this.camera.y = 0; - this.camera.scroll.x = 0; - this.camera.scroll.y = 0; - } -} - -typedef ChartPlaytestStateParams = -{ - fnfcFilePath:Null -}; -#end diff --git a/source/funkin/ui/debug/playtest/ChartPlaytestMenu.hx b/source/funkin/ui/debug/playtest/ChartPlaytestMenu.hx new file mode 100644 index 000000000..3a5d9611b --- /dev/null +++ b/source/funkin/ui/debug/playtest/ChartPlaytestMenu.hx @@ -0,0 +1,99 @@ +package funkin.ui.debug.playtest; + +import flixel.text.FlxText; +import flixel.util.FlxColor; +import flixel.tweens.FlxTween; +import funkin.util.file.FNFCUtil; +import funkin.play.song.Song; +import funkin.graphics.FunkinSprite; +import funkin.graphics.FunkinCamera; +import funkin.ui.debug.playtest.ChartPlaytestMenuButton; +import funkin.ui.debug.playtest.ChartPlaytestMenuButton.ChartPlaytestMenuButtonListToggle; +#if NO_FEATURE_TOUCH_CONTROLS +import funkin.input.Cursor; +#end + +/** + * When playtesting from an FNFC file, we display a debug UI to choose a difficulty first. + */ +class ChartPlaytestMenu extends MusicBeatSubState +{ + var filePath:String; + + var songName:FlxText; + var variationButton:ChartPlaytestMenuButtonListToggle; + var difficultyButton:ChartPlaytestMenuButtonListToggle; + var playtestButton:ChartPlaytestMenuButton; + + var currentVariation:String; + var currentDifficulty:String; + + var playtestCam:FunkinCamera; + + public function new(filePath:String) + { + final targetSong:Song = FNFCUtil.loadSongFromFNFCPath(filePath); + + super(); + + FlxG.state.persistentDraw = true; + FlxG.state.persistentUpdate = false; + + playtestCam = new FunkinCamera('playtestCam'); + playtestCam.bgColor = 0x0; + // playtestCam.alpha = 0; + FlxG.cameras.add(playtestCam, false); + + var blackBG:FunkinSprite = new FunkinSprite(0, 0); + blackBG.makeSolidColor(FlxG.width * 3, FlxG.height * 3, FlxColor.BLACK); + blackBG.scrollFactor.set(); + blackBG.alpha = 0.4; + blackBG.cameras = [playtestCam]; + add(blackBG); + + this.filePath = filePath; + + songName = new FlxText(0, FlxG.height * 0.1, 0, 'Loaded Song: ${targetSong.songName}', + 30).setFormat(Paths.font('vcr.ttf'), 50, FlxColor.WHITE, FlxTextAlign.CENTER); + songName.screenCenter(X); + songName.cameras = [playtestCam]; + add(songName); + + variationButton = new ChartPlaytestMenuButtonListToggle(0, FlxG.height * 0.55, "Variation", targetSong.variations, function(value:String) { + currentVariation = value; + variationButton.screenCenter(X); + }); + variationButton.screenCenter(X); + variationButton.cameras = [playtestCam]; + add(variationButton); + + difficultyButton = new ChartPlaytestMenuButtonListToggle(0, FlxG.height * 0.45, "Difficulty", + targetSong.listDifficulties(null, targetSong.variations, true, true), function(value:String) { + currentDifficulty = value; + difficultyButton.screenCenter(X); + }); + difficultyButton.screenCenter(X); + difficultyButton.cameras = [playtestCam]; + add(difficultyButton); + + playtestButton = new ChartPlaytestMenuButton(0, FlxG.height * 0.8, "Playtest Song", function() { + try + { + FNFCUtil.playSongFromFNFCPath(filePath, currentDifficulty, currentVariation); + } + catch (e) + { + lime.app.Application.current.window.alert('$e', 'Could Not Playtest Chart'); + } + }); + playtestButton.screenCenter(X); + playtestButton.cameras = [playtestCam]; + add(playtestButton); + + FlxTween.tween(playtestCam, {alpha: 1}, 0.5); + + #if NO_FEATURE_TOUCH_CONTROLS + Cursor.show(); + #end + } +} diff --git a/source/funkin/ui/debug/playtest/ChartPlaytestMenuButton.hx b/source/funkin/ui/debug/playtest/ChartPlaytestMenuButton.hx new file mode 100644 index 000000000..e75e3b7e3 --- /dev/null +++ b/source/funkin/ui/debug/playtest/ChartPlaytestMenuButton.hx @@ -0,0 +1,106 @@ +package funkin.ui.debug.playtest; + +import flixel.group.FlxSpriteGroup; +import flixel.text.FlxText; +import flixel.util.FlxColor; +import flixel.FlxG; +import funkin.util.MathUtil; +#if FEATURE_TOUCH_CONTROLS +import funkin.util.TouchUtil; +#end + +class ChartPlaytestMenuButtonBase extends FlxText +{ + var currentScale:Float = 1; + var targetScale:Float = 1; + + public function new(x:Float, y:Float, text:String) + { + super(x, y, 0, text, 30); + + setFormat(Paths.font('vcr.ttf'), 50, FlxColor.WHITE, FlxTextAlign.CENTER); + } + + override function update(elapsed:Float) + { + super.update(elapsed); + + currentScale = MathUtil.smoothLerpPrecision(currentScale, targetScale, elapsed, 0.2); + scale.x = currentScale; + scale.y = currentScale; + + if (#if NO_FEATURE_TOUCH_CONTROLS FlxG.mouse.overlaps(this) #else TouchUtil.overlapsComplex(this) #end) + { + targetScale = 0.9; + + if (#if NO_FEATURE_TOUCH_CONTROLS FlxG.mouse.justPressed #else TouchUtil.justPressed #end) + { + onButtonPressed(); + } + } + else + { + targetScale = 1; + } + } + + function onButtonPressed():Void {} +} + +class ChartPlaytestMenuButton extends ChartPlaytestMenuButtonBase +{ + var onPressed:Void->Void; + + public function new(x:Float, y:Float, text:String, onPressed:Void->Void) + { + super(x, y, text); + + this.onPressed = onPressed; + } + + override function onButtonPressed():Void + { + if (onPressed != null) onPressed(); + } +} + +class ChartPlaytestMenuButtonListToggle extends ChartPlaytestMenuButtonBase +{ + var title:String; + var list:Array; + var onPressed:String->Void; + + var curSelected:Int = 0; + + public function new(x:Float, y:Float, title:String, list:Array, onPressed:String->Void) + { + super(x, y, getCurrentText(title, list[curSelected])); + + this.title = title; + this.list = list; + this.onPressed = onPressed; + } + + override function onButtonPressed():Void + { + curSelected++; + + if (curSelected > list.length - 1) + { + curSelected = 0; + } + else if (curSelected < 0) + { + curSelected = list.length - 1; + } + + text = getCurrentText(title, list[curSelected]); + + if (onPressed != null) onPressed(list[curSelected]); + } + + function getCurrentText(title:String, selectedItem:String):String + { + return '$title: $selectedItem'; + } +} diff --git a/source/funkin/util/file/FNFCUtil.hx b/source/funkin/util/file/FNFCUtil.hx index 3ff470fdc..6583ccf94 100644 --- a/source/funkin/util/file/FNFCUtil.hx +++ b/source/funkin/util/file/FNFCUtil.hx @@ -18,6 +18,21 @@ import thx.semver.Version as SemverVersion; @:nullSafety class FNFCUtil { + /** + * Loads a song from + * @param fnfcPath The absolute file path to the .FNFC file to load. + */ + public static function loadSongFromFNFCPath(fnfcPath:String):Song + { + var fileBytes = sys.io.File.getBytes(fnfcPath); + var fileEntries:Array = FileUtil.readZIPFromBytes(fileBytes); + var mappedFileEntries:Map = FileUtil.mapZIPEntriesByName(fileEntries); + + var manifest:ChartManifestData = loadChartManifestFromFNFCZipEntries(mappedFileEntries); + + return loadSongFromFNFCZipEntries(mappedFileEntries, manifest); + } + /** * Open a song's chart from a .FNFC file and play it in the Play State. * @param fnfcPath The absolute file path to the .FNFC file to load.