From 5b20e00a7ba49cda1eb9bad57232095901696959 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 13 Feb 2024 21:43:54 -0500 Subject: [PATCH 01/14] Upgrade Polymod to no longer spam popups when the same function fails repeatedly. --- .vscode/settings.json | 45 +++++++++++++++++++++++++++++++++++++++++++ hmm.json | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3d1f488f71..2468e883a7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -95,46 +95,91 @@ "target": "windows", "args": ["-debug"] }, + { + "label": "HashLink / Debug", + "target": "hl", + "args": ["-debug"] + }, { "label": "Windows / Debug (FlxAnimate Test)", "target": "windows", "args": ["-debug", "-DANIMATE"] }, + { + "label": "HashLink / Debug (FlxAnimate Test)", + "target": "hl", + "args": ["-debug", "-DANIMATE"] + }, { "label": "Windows / Debug (Straight to Freeplay)", "target": "windows", "args": ["-debug", "-DFREEPLAY"] }, + { + "label": "HashLink / Debug (Straight to Freeplay)", + "target": "hl", + "args": ["-debug", "-DFREEPLAY"] + }, { "label": "Windows / Debug (Straight to Play - Bopeebo Normal)", "target": "windows", "args": ["-debug", "-DSONG=bopeebo -DDIFFICULTY=normal"] }, + { + "label": "HashLink / Debug (Straight to Play - Bopeebo Normal)", + "target": "hl", + "args": ["-debug", "-DSONG=bopeebo -DDIFFICULTY=normal"] + }, { "label": "Windows / Debug (Conversation Test)", "target": "windows", "args": ["-debug", "-DDIALOGUE"] }, + { + "label": "HashLink / Debug (Conversation Test)", + "target": "hl", + "args": ["-debug", "-DDIALOGUE"] + }, { "label": "Windows / Debug (Straight to Chart Editor)", "target": "windows", "args": ["-debug", "-DCHARTING"] }, + { + "label": "HashLink / Debug (Straight to Chart Editor)", + "target": "hl", + "args": ["-debug", "-DCHARTING"] + }, { "label": "Windows / Debug (Straight to Animation Editor)", "target": "windows", "args": ["-debug", "-DANIMDEBUG"] }, + { + "label": "HashLink / Debug (Straight to Animation Editor)", + "target": "hl", + "args": ["-debug", "-DANIMDEBUG"] + }, { "label": "Windows / Debug (Latency Test)", "target": "windows", "args": ["-debug", "-DLATENCY"] }, + { + "label": "HashLink / Debug (Latency Test)", + "target": "hl", + "args": ["-debug", "-DLATENCY"] + }, { "label": "Windows / Debug (Waveform Test)", "target": "windows", "args": ["-debug", "-DWAVEFORM"] }, + { + "label": "HashLink / Debug (Waveform Test)", + "target": "hl", + "args": ["-debug", "-DWAVEFORM"] + }, { "label": "HTML5 / Debug", "target": "html5", diff --git a/hmm.json b/hmm.json index e1acf6c876..ca9654291b 100644 --- a/hmm.json +++ b/hmm.json @@ -151,7 +151,7 @@ "name": "polymod", "type": "git", "dir": null, - "ref": "0b53e478bc375ec51b760b650201ac7a965d2ef4", + "ref": "d5a3b8995f64d20b95f844454e8c3b38c3d3a9fa", "url": "https://github.com/larsiusprime/polymod" }, { From 432e0de2b910e14c634e2f1c4c2350ccc6ad9b25 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 14 Feb 2024 06:57:11 -0500 Subject: [PATCH 02/14] assets submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 599169f478..1f00d24134 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 599169f4786d1feb62a089d25112035977647805 +Subproject commit 1f00d24134231433180affecc67a617d54169ffa From ed2be8becf443fb1fd9b4bd2896deb55b3539d27 Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Wed, 14 Feb 2024 16:32:41 -0800 Subject: [PATCH 03/14] Add `haxelib run lime setup` to COMPILING.md --- docs/COMPILING.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/COMPILING.md b/docs/COMPILING.md index 7f9c0cdb85..6d5b5b3651 100644 --- a/docs/COMPILING.md +++ b/docs/COMPILING.md @@ -7,7 +7,8 @@ - If you accidentally cloned without the `assets` submodule (aka didn't follow the step above), you can run `git submodule update --init --recursive` to get the assets in a foolproof way. 2. Install `hmm` (run `haxelib --global install hmm` and then `haxelib --global run hmm setup`) 3. Install all haxelibs of the current branch by running `hmm install` -4. Platform setup +4. Setup lime: `haxelib run lime setup` +5. Platform setup - For Windows, download the [Visual Studio Build Tools](https://aka.ms/vs/17/release/vs_BuildTools.exe) - When prompted, select "Individual Components" and make sure to download the following: - MSVC v143 VS 2022 C++ x64/x86 build tools @@ -15,5 +16,5 @@ - Mac: [`lime setup mac` Documentation](https://lime.openfl.org/docs/advanced-setup/macos/) - Linux: [`lime setup linux` Documentation](https://lime.openfl.org/docs/advanced-setup/linux/) - HTML5: Compiles without any extra setup -5. If you are targeting for native, you likely need to run `lime rebuild PLATFORM` and `lime rebuild PLATFORM -debug` -6. `lime test PLATFORM` ! +6. If you are targeting for native, you likely need to run `lime rebuild PLATFORM` and `lime rebuild PLATFORM -debug` +7. `lime test PLATFORM` ! From 91ab1cb52078ea60cfc0713fc48f9954d76b123f Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Thu, 15 Feb 2024 00:15:21 -0800 Subject: [PATCH 04/14] Remove `hmm` dependency to fix HTML5 target Remove the `hmm` haxelib dependency from the build. Linking to `hmm` caused `utest` to be transitively linked, which eventually caused OpenFL to act strangely and instatiate the application twice on the HTML5 target. `hmm` was only used for `HaxelibVersions.getLibraryVersions` macro call. Instead, manually parse the `hmm.json` ourselves to avoid the dependency. This fixes the HTML5 target. `hmm` is still used for package management, but no longer linked in to the build itself. --- Project.xml | 1 - hmm.json | 5 --- source/funkin/util/macro/HaxelibVersions.hx | 45 ++++++++------------- 3 files changed, 17 insertions(+), 34 deletions(-) diff --git a/Project.xml b/Project.xml index 0a8d31b866..c58153575a 100644 --- a/Project.xml +++ b/Project.xml @@ -113,7 +113,6 @@ - diff --git a/hmm.json b/hmm.json index ca9654291b..26cb0d0b44 100644 --- a/hmm.json +++ b/hmm.json @@ -64,11 +64,6 @@ "ref": "e9f880522e27134b29df4067f82df7d7e5237b70", "url": "https://github.com/haxeui/haxeui-flixel" }, - { - "name": "hmm", - "type": "haxelib", - "version": "3.1.0" - }, { "name": "hscript", "type": "haxelib", diff --git a/source/funkin/util/macro/HaxelibVersions.hx b/source/funkin/util/macro/HaxelibVersions.hx index 1a4699bba7..08ec3a7c4b 100644 --- a/source/funkin/util/macro/HaxelibVersions.hx +++ b/source/funkin/util/macro/HaxelibVersions.hx @@ -7,7 +7,7 @@ class HaxelibVersions public static macro function getLibraryVersions():haxe.macro.Expr.ExprOf> { #if !display - return macro $v{formatHmmData(readHmmData())}; + return macro $v{formatHmmData()}; #else // `#if display` is used for code completion. In this case returning an // empty string is good enough; We don't want to call functions on every hint. @@ -17,44 +17,33 @@ class HaxelibVersions } #if (macro) - static function readHmmData():hmm.HmmConfig - { - return hmm.HmmConfig.HmmConfigs.readHmmJsonOrThrow(); - } - - static function formatHmmData(hmmData:hmm.HmmConfig):Array + @SuppressWarnings('checkstyle:Dynamic') + static function formatHmmData():Array { var result:Array = []; - for (library in hmmData.dependencies) + var hmmData:Dynamic = haxe.Json.parse(sys.io.File.getContent('hmm.json')); + var dependencies:Array = cast hmmData.dependencies; + for (library in dependencies) { - switch (library) + switch (library.type) { - case Haxelib(name, version): - result.push('${name} haxelib(${o(version)})'); - case Git(name, url, ref, dir): - result.push('${name} git(${url}/${o(dir, '')}:${o(ref)})'); - case Mercurial(name, url, ref, dir): - result.push('${name} mercurial(${url}/${o(dir, '')}:${o(ref)})'); - case Dev(name, path): - result.push('${name} dev(${path})'); + case 'haxelib': + result.push('${library.name} haxelib(${library.version ?? 'None'})'); + case 'git': + result.push('${library.name} git(${library.url}/${library.dir ?? ''}:${library.ref ?? 'None'}'); + case 'mercurial': + result.push('${library.name} mercurial(${library.url}/${library.dir ?? ''}:${library.ref ?? 'None'})'); + case 'dev': + result.push('${library.name} dev(${library.path})'); + case ty: + throw 'Unhandled hmm library type ${ty}'; } } return result; } - static function o(option:haxe.ds.Option, defaultValue:String = 'None'):String - { - switch (option) - { - case Some(value): - return value; - case None: - return defaultValue; - } - } - static function readLibraryCurrentVersion(libraryName:String):String { var path = Path.join([Path.addTrailingSlash(Sys.getCwd()), '.haxelib', libraryName, '.current']); From 10f4ad704ee0f87d3c7933ed969465545793ab4f Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 15 Feb 2024 14:17:44 -0500 Subject: [PATCH 05/14] Additional mathematical utilities. --- source/funkin/util/MathUtil.hx | 46 +++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/source/funkin/util/MathUtil.hx b/source/funkin/util/MathUtil.hx index 3cb6621a76..c8c4f51091 100644 --- a/source/funkin/util/MathUtil.hx +++ b/source/funkin/util/MathUtil.hx @@ -5,6 +5,14 @@ package funkin.util; */ class MathUtil { + public static var E(get, never):Float; + + static function get_E():Float + { + // Math.E is not a constant in Haxe, so we'll just define it ourselves. + return 2.71828182845904523536; // Approximation. + } + /** * Perform linear interpolation between the base and the target, based on the current framerate. */ @@ -24,8 +32,44 @@ class MathUtil * @param value The value to get the logarithm of. * @return `log_base(value)` */ - public static function logBase(base:Float, value:Float):Float + public static function logBase(base:Float, value:Float) { return Math.log(value) / Math.log(base); } + + /** + * @returns `2^x` + */ + public static function exp2(x:Float) + { + return Math.pow(2, x); + } + + /** + * Linearly interpolate between two values. + * @param base The starting value, when `progress <= 0`. + * @param target The ending value, when `progress >= 1`. + * @param progress Value used to interpolate between `base` and `target`. + */ + public static function lerp(base:Float, target:Float, progress:Float) + { + return base + progress * (target - base); + } + + /** + * Perform a framerate-independent linear interpolation between the base value and the target. + * @param current The current value. + * @param target The target value. + * @param elapsed The time elapsed since the last frame. + * @param duration The total duration of the interpolation. Nominal duration until remaining distance is less than `precision`. + * @param precision The target precision of the interpolation. Defaults to 1% of distance remaining. + * @see https://twitter.com/FreyaHolmer/status/1757918211679650262 + */ + public static function smoothLerp(current:Float, target:Float, elapsed:Float, duration:Float, precision:Float = 1 / 100):Float + { + // var halfLife:Float = -duration / logBase(2, precision); + // lerp(current, target, 1 - exp2(-elapsed / halfLife)); + + return lerp(current, target, 1 - Math.pow(p, elapsed / duration)); + } } From e24c78ae160dbdd968ae5753f70ab5b4058b1e66 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 15 Feb 2024 21:34:24 -0500 Subject: [PATCH 06/14] Implemented a screenshot button. FancyPreview is broken. --- source/funkin/InitState.hx | 8 +- source/funkin/Preferences.hx | 30 +- source/funkin/save/Save.hx | 6 + source/funkin/util/FileUtil.hx | 6 + .../funkin/util/plugins/ScreenshotPlugin.hx | 269 ++++++++++++++++++ 5 files changed, 312 insertions(+), 7 deletions(-) create mode 100644 source/funkin/util/plugins/ScreenshotPlugin.hx diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 399f524985..ec6a4e6f0c 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -50,11 +50,13 @@ class InitState extends FlxState */ public override function create():Void { + // Setup a bunch of important Flixel stuff. setupShit(); - // loadSaveData(); // Moved to Main.hx // Load player options from save data. + // Flixel has already loaded the save data, so we can just use it. Preferences.init(); + // Load controls from save data. PlayerSettings.init(); @@ -198,6 +200,10 @@ class InitState extends FlxState // // FLIXEL PLUGINS // + // Plugins provide a useful interface for globally active Flixel objects, + // that receive update events regardless of the current state. + // TODO: Move Module behavior to a Flixel plugin. + funkin.util.plugins.ScreenshotPlugin.initialize(); funkin.util.plugins.EvacuateDebugPlugin.initialize(); funkin.util.plugins.ReloadAssetsDebugPlugin.initialize(); funkin.util.plugins.WatchPlugin.initialize(); diff --git a/source/funkin/Preferences.hx b/source/funkin/Preferences.hx index 6b0911eded..039a4c2852 100644 --- a/source/funkin/Preferences.hx +++ b/source/funkin/Preferences.hx @@ -20,7 +20,10 @@ class Preferences static function set_naughtyness(value:Bool):Bool { - return Save.get().options.naughtyness = value; + var save = Save.get(); + save.options.naughtyness = value; + save.flush(); + return value; } /** @@ -36,7 +39,10 @@ class Preferences static function set_downscroll(value:Bool):Bool { - return Save.get().options.downscroll = value; + var save = Save.get(); + save.options.downscroll = value; + save.flush(); + return value; } /** @@ -52,7 +58,10 @@ class Preferences static function set_flashingLights(value:Bool):Bool { - return Save.get().options.flashingLights = value; + var save = Save.get(); + save.options.flashingLights = value; + save.flush(); + return value; } /** @@ -68,7 +77,10 @@ class Preferences static function set_zoomCamera(value:Bool):Bool { - return Save.get().options.zoomCamera = value; + var save = Save.get(); + save.options.zoomCamera = value; + save.flush(); + return value; } /** @@ -89,7 +101,10 @@ class Preferences toggleDebugDisplay(value); } - return Save.get().options.debugDisplay = value; + var save = Save.get(); + save.options.debugDisplay = value; + save.flush(); + return value; } /** @@ -107,7 +122,10 @@ class Preferences { if (value != Save.get().options.autoPause) FlxG.autoPause = value; - return Save.get().options.autoPause = value; + var save = Save.get(); + save.options.autoPause = value; + save.flush(); + return value; } public static function init():Void diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 6a4dd048ce..5bbded231b 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -113,6 +113,9 @@ abstract Save(RawSaveData) }; } + /** + * NOTE: Modifications will not be saved without calling `Save.flush()`! + */ public var options(get, never):SaveDataOptions; function get_options():SaveDataOptions @@ -120,6 +123,9 @@ abstract Save(RawSaveData) return this.options; } + /** + * NOTE: Modifications will not be saved without calling `Save.flush()`! + */ public var modOptions(get, never):Map; function get_modOptions():Map diff --git a/source/funkin/util/FileUtil.hx b/source/funkin/util/FileUtil.hx index 6127376808..5d189c0e94 100644 --- a/source/funkin/util/FileUtil.hx +++ b/source/funkin/util/FileUtil.hx @@ -20,6 +20,7 @@ class FileUtil { public static final FILE_FILTER_FNFC:FileFilter = new FileFilter("Friday Night Funkin' Chart (.fnfc)", "*.fnfc"); public static final FILE_FILTER_ZIP:FileFilter = new FileFilter("ZIP Archive (.zip)", "*.zip"); + public static final FILE_FILTER_PNG:FileFilter = new FileFilter("PNG Image (.png)", "*.png"); public static final FILE_EXTENSION_INFO_FNFC:FileDialogExtensionInfo = { @@ -31,6 +32,11 @@ class FileUtil extension: 'zip', label: 'ZIP Archive', }; + public static final FILE_EXTENSION_INFO_PNG:FileDialogExtensionInfo = + { + extension: 'png', + label: 'PNG Image', + }; /** * Browses for a single file, then calls `onSelect(fileInfo)` when a file is selected. diff --git a/source/funkin/util/plugins/ScreenshotPlugin.hx b/source/funkin/util/plugins/ScreenshotPlugin.hx new file mode 100644 index 0000000000..a8b494feea --- /dev/null +++ b/source/funkin/util/plugins/ScreenshotPlugin.hx @@ -0,0 +1,269 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; +import flixel.FlxCamera; +import flixel.FlxG; +import flixel.FlxState; +import flixel.graphics.FlxGraphic; +import flixel.input.keyboard.FlxKey; +import flixel.tweens.FlxEase; +import flixel.tweens.FlxTween; +import flixel.util.FlxColor; +import flixel.util.FlxSignal; +import flixel.util.FlxTimer; +import funkin.graphics.FunkinSprite; +import funkin.input.Cursor; +import openfl.display.Bitmap; +import openfl.display.BitmapData; +import openfl.display.PNGEncoderOptions; +import openfl.geom.Matrix; +import openfl.geom.Rectangle; +import openfl.utils.ByteArray; + +typedef ScreenshotPluginParams = +{ + hotkeys:Array, + ?region:Rectangle, + shouldHideMouse:Bool, + flashColor:Null, + fancyPreview:Bool, +}; + +/** + * What if `flixel.addons.plugin.screengrab.FlxScreenGrab` but it's better? + * TODO: Contribute this upstream. + */ +class ScreenshotPlugin extends FlxBasic +{ + public static final SCREENSHOT_FOLDER = 'screenshots'; + + var _hotkeys:Array; + + var _region:Null; + + var _shouldHideMouse:Bool; + + var _flashColor:Null; + + var _fancyPreview:Bool; + + /** + * A signal fired before the screenshot is taken. + */ + public var onPreScreenshot(default, null):FlxTypedSignalVoid>; + + /** + * A signal fired after the screenshot is taken. + * @param bitmap The bitmap that was captured. + */ + public var onPostScreenshot(default, null):FlxTypedSignalVoid>; + + public function new(params:ScreenshotPluginParams) + { + super(); + + _hotkeys = params.hotkeys; + _region = params.region ?? null; + _shouldHideMouse = params.shouldHideMouse; + _flashColor = params.flashColor; + _fancyPreview = params.fancyPreview; + + onPreScreenshot = new FlxTypedSignalVoid>(); + onPostScreenshot = new FlxTypedSignalVoid>(); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + if (FlxG.keys.anyJustReleased(_hotkeys)) + { + capture(); + } + } + + /** + * Initialize the screenshot plugin. + */ + public static function initialize():Void + { + FlxG.plugins.addPlugin(new ScreenshotPlugin( + { + flashColor: Preferences.flashingLights ? FlxColor.WHITE : null, // Was originally a black flash. + + // TODO: Add a way to configure screenshots from the options menu. + hotkeys: [FlxKey.PRINTSCREEN], + shouldHideMouse: false, + fancyPreview: true, // TODO: Fancy preview is broken on substates. + })); + } + + public function updatePreferences():Void + { + _flashColor = Preferences.flashingLights ? FlxColor.WHITE : null; + } + + /** + * Defines the region of the screen that should be captured. + * You don't need to call this method if you want to capture the entire screen, that's the default behavior. + */ + public function defineCaptureRegion(x:Int, y:Int, width:Int, height:Int):Void + { + _region = new Rectangle(x, y, width, height); + } + + /** + * Capture the game screen as a bitmap. + */ + public function capture():Void + { + onPreScreenshot.dispatch(); + + var captureRegion = _region != null ? _region : new Rectangle(0, 0, FlxG.stage.stageWidth, FlxG.stage.stageHeight); + + var wasMouseHidden = false; + if (_shouldHideMouse && FlxG.mouse.visible) + { + wasMouseHidden = true; + Cursor.hide(); + } + + // The actual work. + // var bitmap = new Bitmap(new BitmapData(Math.floor(captureRegion.width), Math.floor(captureRegion.height), true, 0x00000000)); // Create a transparent empty bitmap. + // var drawMatrix = new Matrix(1, 0, 0, 1, -captureRegion.x, -captureRegion.y); // Modifying this will scale or skew the bitmap. + // bitmap.bitmapData.draw(FlxG.stage, drawMatrix); + var bitmap = new Bitmap(BitmapData.fromImage(FlxG.stage.window.readPixels())); + + if (wasMouseHidden) + { + Cursor.show(); + } + + // Save the bitmap to a file. + saveScreenshot(bitmap); + + // Show some feedback. + showCaptureFeedback(); + if (_fancyPreview) + { + showFancyPreview(bitmap); + } + + onPostScreenshot.dispatch(bitmap); + } + + final CAMERA_FLASH_DURATION = 0.25; + + /** + * Visual (and audio?) feedback when a screenshot is taken. + */ + function showCaptureFeedback():Void + { + if (_flashColor != null) + { + for (camera in FlxG.cameras.list) + { + camera.flash(_flashColor, CAMERA_FLASH_DURATION); + } + } + } + + static final PREVIEW_INITIAL_DELAY = 0.25; // How long before the preview starts fading in. + static final PREVIEW_FADE_IN_DURATION = 0.3; // How long the preview takes to fade in. + static final PREVIEW_FADE_OUT_DELAY = 0.25; // How long the preview stays on screen. + static final PREVIEW_FADE_OUT_DURATION = 0.3; // How long the preview takes to fade out. + + function showFancyPreview(bitmap:Bitmap):Void + { + // TODO: This function looks really nice but breaks substates. + var targetCamera = new FlxCamera(); + targetCamera.bgColor.alpha = 0; // Show the scene behind the camera. + FlxG.cameras.add(targetCamera); + + var flxGraphic = FlxGraphic.fromBitmapData(bitmap.bitmapData, false, "screenshot", false); + + var preview = new FunkinSprite(0, 0); + preview.frames = flxGraphic.imageFrame; + preview.setGraphicSize(bitmap.width / 4, bitmap.height / 4); + preview.x = FlxG.width - preview.width - 10; + preview.y = FlxG.height - preview.height - 10; + + preview.alpha = 0.0; + preview.cameras = [targetCamera]; + getCurrentState().add(preview); + + // Wait to fade in. + new FlxTimer().start(PREVIEW_INITIAL_DELAY, function(_) { + // Fade in. + FlxTween.tween(preview, {alpha: 1.0}, PREVIEW_FADE_IN_DURATION, + { + ease: FlxEase.elasticOut, + onComplete: function(_) { + // Wait to fade out. + new FlxTimer().start(PREVIEW_FADE_OUT_DELAY, function(_) { + // Fade out. + FlxTween.tween(preview, {alpha: 0.0}, PREVIEW_FADE_OUT_DURATION, + { + ease: FlxEase.elasticIn, + onComplete: function(_) { + preview.kill(); + } + }); + }); + } + }); + }); + } + + static function getCurrentState():FlxState + { + var state = FlxG.state; + while (state.subState != null) + { + state = state.subState; + } + return state; + } + + static function getScreenshotPath():String + { + return '$SCREENSHOT_FOLDER/screenshot-${DateUtil.generateTimestamp()}.png'; + } + + static function makeScreenshotPath():Void + { + FileUtil.createDirIfNotExists(SCREENSHOT_FOLDER); + } + + /** + * Convert a Bitmap to a PNG ByteArray to save to a file. + */ + static function encodePNG(bitmap:Bitmap):ByteArray + { + return bitmap.bitmapData.encode(bitmap.bitmapData.rect, new PNGEncoderOptions()); + } + + /** + * Save the generated bitmap to a file. + * @param bitmap The bitmap to save. + */ + static function saveScreenshot(bitmap:Bitmap) + { + makeScreenshotPath(); + var targetPath:String = getScreenshotPath(); + + var pngData = encodePNG(bitmap); + + if (pngData == null) + { + trace('[WARN] Failed to encode PNG data.'); + return; + } + else + { + trace('Saving screenshot to: ' + targetPath); + // TODO: Make this work on browser. + FileUtil.writeBytesToPath(targetPath, pngData); + } + } +} From 0036a334bc37b3e4bd8034c53f7de2dedfd0dcda Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 16 Feb 2024 04:48:54 -0500 Subject: [PATCH 07/14] display fancy preview as a sprite on top of FlxG.stage, to properly render over substates --- .../funkin/util/plugins/ScreenshotPlugin.hx | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/source/funkin/util/plugins/ScreenshotPlugin.hx b/source/funkin/util/plugins/ScreenshotPlugin.hx index a8b494feea..40454854c9 100644 --- a/source/funkin/util/plugins/ScreenshotPlugin.hx +++ b/source/funkin/util/plugins/ScreenshotPlugin.hx @@ -92,9 +92,9 @@ class ScreenshotPlugin extends FlxBasic flashColor: Preferences.flashingLights ? FlxColor.WHITE : null, // Was originally a black flash. // TODO: Add a way to configure screenshots from the options menu. - hotkeys: [FlxKey.PRINTSCREEN], + hotkeys: [FlxKey.F3], shouldHideMouse: false, - fancyPreview: true, // TODO: Fancy preview is broken on substates. + fancyPreview: true, })); } @@ -170,43 +170,42 @@ class ScreenshotPlugin extends FlxBasic static final PREVIEW_INITIAL_DELAY = 0.25; // How long before the preview starts fading in. static final PREVIEW_FADE_IN_DURATION = 0.3; // How long the preview takes to fade in. - static final PREVIEW_FADE_OUT_DELAY = 0.25; // How long the preview stays on screen. + static final PREVIEW_FADE_OUT_DELAY = 1.25; // How long the preview stays on screen. static final PREVIEW_FADE_OUT_DURATION = 0.3; // How long the preview takes to fade out. function showFancyPreview(bitmap:Bitmap):Void { - // TODO: This function looks really nice but breaks substates. - var targetCamera = new FlxCamera(); - targetCamera.bgColor.alpha = 0; // Show the scene behind the camera. - FlxG.cameras.add(targetCamera); + var scale:Float = 0.25; + var w:Int = Std.int(bitmap.bitmapData.width * scale); + var h:Int = Std.int(bitmap.bitmapData.height * scale); - var flxGraphic = FlxGraphic.fromBitmapData(bitmap.bitmapData, false, "screenshot", false); + var preview:BitmapData = new BitmapData(w, h, true); + var matrix:openfl.geom.Matrix = new openfl.geom.Matrix(); + matrix.scale(scale, scale); + preview.draw(bitmap.bitmapData, matrix); - var preview = new FunkinSprite(0, 0); - preview.frames = flxGraphic.imageFrame; - preview.setGraphicSize(bitmap.width / 4, bitmap.height / 4); - preview.x = FlxG.width - preview.width - 10; - preview.y = FlxG.height - preview.height - 10; + var previewBitmap = new Bitmap(preview); - preview.alpha = 0.0; - preview.cameras = [targetCamera]; - getCurrentState().add(preview); + FlxG.stage.addChild(previewBitmap); + + previewBitmap.alpha = 0.0; + previewBitmap.y -= 10; // Wait to fade in. new FlxTimer().start(PREVIEW_INITIAL_DELAY, function(_) { // Fade in. - FlxTween.tween(preview, {alpha: 1.0}, PREVIEW_FADE_IN_DURATION, + FlxTween.tween(previewBitmap, {alpha: 1.0, y: 0}, PREVIEW_FADE_IN_DURATION, { - ease: FlxEase.elasticOut, + ease: FlxEase.quartOut, onComplete: function(_) { // Wait to fade out. new FlxTimer().start(PREVIEW_FADE_OUT_DELAY, function(_) { // Fade out. - FlxTween.tween(preview, {alpha: 0.0}, PREVIEW_FADE_OUT_DURATION, + FlxTween.tween(previewBitmap, {alpha: 0.0, y: 10}, PREVIEW_FADE_OUT_DURATION, { - ease: FlxEase.elasticIn, + ease: FlxEase.quartInOut, onComplete: function(_) { - preview.kill(); + FlxG.stage.removeChild(previewBitmap); } }); }); From 94938313dd1e449b15f08c4b7798fc4e24f6b929 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 16 Feb 2024 05:24:43 -0500 Subject: [PATCH 08/14] click preview to open screenshots folder (and moved `openFolder` to FileUtil --- source/funkin/ui/debug/DebugMenuSubState.hx | 12 +--- source/funkin/util/FileUtil.hx | 19 ++++++ .../funkin/util/plugins/ScreenshotPlugin.hx | 63 ++++++++++++++++--- 3 files changed, 77 insertions(+), 17 deletions(-) diff --git a/source/funkin/ui/debug/DebugMenuSubState.hx b/source/funkin/ui/debug/DebugMenuSubState.hx index 861e99f6b0..375fb8f5c5 100644 --- a/source/funkin/ui/debug/DebugMenuSubState.hx +++ b/source/funkin/ui/debug/DebugMenuSubState.hx @@ -9,6 +9,7 @@ import funkin.ui.debug.charting.ChartEditorState; import funkin.ui.MusicBeatSubState; import funkin.util.logging.CrashHandler; import flixel.addons.transition.FlxTransitionableState; +import funkin.util.FileUtil; class DebugMenuSubState extends MusicBeatSubState { @@ -110,16 +111,7 @@ class DebugMenuSubState extends MusicBeatSubState #if sys function openLogFolder() { - #if windows - Sys.command('explorer', [CrashHandler.LOG_FOLDER]); - #elseif mac - // mac could be fuckie with where the log folder is relative to the game file... - // if this comment is still here... it means it has NOT been verified on mac yet! - Sys.command('open', [CrashHandler.LOG_FOLDER]); - #end - - // TODO: implement linux - // some shit with xdg-open :thinking: emoji... + FileUtil.openFolder(CrashHandler.LOG_FOLDER); } #end diff --git a/source/funkin/util/FileUtil.hx b/source/funkin/util/FileUtil.hx index 5d189c0e94..268aed57ea 100644 --- a/source/funkin/util/FileUtil.hx +++ b/source/funkin/util/FileUtil.hx @@ -645,6 +645,25 @@ class FileUtil }; } + #if sys + public static function openFolder(pathFolder:String) + { + #if windows + Sys.command('explorer', [pathFolder]); + #elseif mac + // mac could be fuckie with where the log folder is relative to the game file... + // if this comment is still here... it means it has NOT been verified on mac yet! + // + // FileUtil.hx note: this was originally used to open the logs specifically! + // thats why the above comment is there! + Sys.command('open', [pathFolder]); + #end + + // TODO: implement linux + // some shit with xdg-open :thinking: emoji... + } + #end + static function convertTypeFilter(typeFilter:Array):String { var filter:String = null; diff --git a/source/funkin/util/plugins/ScreenshotPlugin.hx b/source/funkin/util/plugins/ScreenshotPlugin.hx index 40454854c9..8d6f62a280 100644 --- a/source/funkin/util/plugins/ScreenshotPlugin.hx +++ b/source/funkin/util/plugins/ScreenshotPlugin.hx @@ -14,11 +14,13 @@ import flixel.util.FlxTimer; import funkin.graphics.FunkinSprite; import funkin.input.Cursor; import openfl.display.Bitmap; +import openfl.display.Sprite; import openfl.display.BitmapData; import openfl.display.PNGEncoderOptions; import openfl.geom.Matrix; import openfl.geom.Rectangle; import openfl.utils.ByteArray; +import openfl.events.MouseEvent; typedef ScreenshotPluginParams = { @@ -175,6 +177,27 @@ class ScreenshotPlugin extends FlxBasic function showFancyPreview(bitmap:Bitmap):Void { + // ermmm stealing this?? + var wasMouseHidden = false; + if (!FlxG.mouse.visible) + { + wasMouseHidden = true; + Cursor.show(); + } + + // so that it doesnt change the alpha when tweening in/out + var changingAlpha:Bool = false; + + // fuck it, cursed locally scoped functions, purely because im lazy + // (and so we can check changingAlpha, which is locally scoped.... because I'm lazy...) + var onHover = function(e:MouseEvent) { + if (!changingAlpha) e.target.alpha = 0.6; + }; + + var onHoverOut = function(e:MouseEvent) { + if (!changingAlpha) e.target.alpha = 1; + } + var scale:Float = 0.25; var w:Int = Std.int(bitmap.bitmapData.width * scale); var h:Int = Std.int(bitmap.bitmapData.height * scale); @@ -184,28 +207,49 @@ class ScreenshotPlugin extends FlxBasic matrix.scale(scale, scale); preview.draw(bitmap.bitmapData, matrix); - var previewBitmap = new Bitmap(preview); + // used for movement + button stuff + var previewSprite = new Sprite(); + + previewSprite.buttonMode = true; + previewSprite.addEventListener(MouseEvent.MOUSE_DOWN, openScreenshotsFolder); + previewSprite.addEventListener(MouseEvent.MOUSE_OVER, onHover); + previewSprite.addEventListener(MouseEvent.MOUSE_OUT, onHoverOut); - FlxG.stage.addChild(previewBitmap); + FlxG.stage.addChild(previewSprite); - previewBitmap.alpha = 0.0; - previewBitmap.y -= 10; + previewSprite.alpha = 0.0; + previewSprite.y -= 10; + + var previewBitmap = new Bitmap(preview); + previewSprite.addChild(previewBitmap); // Wait to fade in. new FlxTimer().start(PREVIEW_INITIAL_DELAY, function(_) { // Fade in. - FlxTween.tween(previewBitmap, {alpha: 1.0, y: 0}, PREVIEW_FADE_IN_DURATION, + changingAlpha = true; + FlxTween.tween(previewSprite, {alpha: 1.0, y: 0}, PREVIEW_FADE_IN_DURATION, { ease: FlxEase.quartOut, onComplete: function(_) { + changingAlpha = false; // Wait to fade out. new FlxTimer().start(PREVIEW_FADE_OUT_DELAY, function(_) { + changingAlpha = true; // Fade out. - FlxTween.tween(previewBitmap, {alpha: 0.0, y: 10}, PREVIEW_FADE_OUT_DURATION, + FlxTween.tween(previewSprite, {alpha: 0.0, y: 10}, PREVIEW_FADE_OUT_DURATION, { ease: FlxEase.quartInOut, onComplete: function(_) { - FlxG.stage.removeChild(previewBitmap); + if (wasMouseHidden) + { + Cursor.hide(); + } + + previewSprite.removeEventListener(MouseEvent.MOUSE_DOWN, openScreenshotsFolder); + previewSprite.removeEventListener(MouseEvent.MOUSE_OVER, onHover); + previewSprite.removeEventListener(MouseEvent.MOUSE_OUT, onHoverOut); + + FlxG.stage.removeChild(previewSprite); } }); }); @@ -214,6 +258,11 @@ class ScreenshotPlugin extends FlxBasic }); } + function openScreenshotsFolder(e:MouseEvent):Void + { + FileUtil.openFolder(SCREENSHOT_FOLDER); + } + static function getCurrentState():FlxState { var state = FlxG.state; From e90c37ad7562248953a92c642793bcb8f02c1649 Mon Sep 17 00:00:00 2001 From: Mike Welsh Date: Fri, 16 Feb 2024 04:54:27 -0800 Subject: [PATCH 09/14] Fix issue with incorrect music after pausing during countdown Whenever you pause gameplay, the `PlayState` pauses the music. But if you start a song from the main menu, pause during the 3,2,1 countdown, and then unpause, the main menu music will incorrectly start playing again due to `FlxG.sound.music` still referencing it before the gameplay song starts playing. Only restart the music if it was actually playing to begin with by storing the play state in `PlayState.openSubState` when we pause. This hopefully avoids any other cases where the pause state gets pushed while the music is stopped. --- source/funkin/play/PlayState.hx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index be4fab2549..66e8e0dcc9 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -354,6 +354,11 @@ class PlayState extends MusicBeatSubState */ var startingSong:Bool = false; + /** + * False if `FlxG.sound.music` + */ + var musicPausedBySubState:Bool = false; + /** * False until `create()` has completed. */ @@ -1042,6 +1047,7 @@ class PlayState extends MusicBeatSubState // Pause the music. if (FlxG.sound.music != null) { + musicPausedBySubState = FlxG.sound.music.playing; FlxG.sound.music.pause(); if (vocals != null) vocals.pause(); } @@ -1049,7 +1055,6 @@ class PlayState extends MusicBeatSubState // Pause the countdown. Countdown.pauseCountdown(); } - else {} super.openSubState(subState); } @@ -1069,7 +1074,10 @@ class PlayState extends MusicBeatSubState if (event.eventCanceled) return; // Resume - FlxG.sound.music.play(FlxG.sound.music.time); + if (musicPausedBySubState) + { + FlxG.sound.music.play(FlxG.sound.music.time); + } if (FlxG.sound.music != null && !startingSong && !isInCutscene) resyncVocals(); From e4cd694c15b681a3473c95ae30dc51e2b9878054 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 16 Feb 2024 14:42:28 -0500 Subject: [PATCH 10/14] ScreenshotPlugin now uses Player.controls. VolumePlugin is now outside MusicBeatState --- source/funkin/InitState.hx | 3 +- source/funkin/input/Controls.hx | 54 +++++++++---------- source/funkin/ui/MusicBeatState.hx | 15 ------ source/funkin/ui/MusicBeatSubState.hx | 5 -- .../funkin/util/plugins/ScreenshotPlugin.hx | 7 ++- source/funkin/util/plugins/VolumePlugin.hx | 34 ++++++++++++ 6 files changed, 67 insertions(+), 51 deletions(-) create mode 100644 source/funkin/util/plugins/VolumePlugin.hx diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index ec6a4e6f0c..ac1b3903b7 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -203,9 +203,10 @@ class InitState extends FlxState // Plugins provide a useful interface for globally active Flixel objects, // that receive update events regardless of the current state. // TODO: Move Module behavior to a Flixel plugin. - funkin.util.plugins.ScreenshotPlugin.initialize(); funkin.util.plugins.EvacuateDebugPlugin.initialize(); funkin.util.plugins.ReloadAssetsDebugPlugin.initialize(); + funkin.util.plugins.ScreenshotPlugin.initialize(); + funkin.util.plugins.VolumePlugin.initialize(); funkin.util.plugins.WatchPlugin.initialize(); // diff --git a/source/funkin/input/Controls.hx b/source/funkin/input/Controls.hx index 201c222a3d..c4760cf5fb 100644 --- a/source/funkin/input/Controls.hx +++ b/source/funkin/input/Controls.hx @@ -63,12 +63,10 @@ class Controls extends FlxActionSet var _debug_menu = new FlxActionDigital(Action.DEBUG_MENU); var _debug_chart = new FlxActionDigital(Action.DEBUG_CHART); var _debug_stage = new FlxActionDigital(Action.DEBUG_STAGE); + var _screenshot = new FlxActionDigital(Action.SCREENSHOT); var _volume_up = new FlxActionDigital(Action.VOLUME_UP); var _volume_down = new FlxActionDigital(Action.VOLUME_DOWN); var _volume_mute = new FlxActionDigital(Action.VOLUME_MUTE); - #if CAN_CHEAT - var _cheat = new FlxActionDigital(Action.CHEAT); - #end var byName:Map = new Map(); @@ -235,6 +233,11 @@ class Controls extends FlxActionSet inline function get_DEBUG_STAGE() return _debug_stage.check(); + public var SCREENSHOT(get, never):Bool; + + inline function get_SCREENSHOT() + return _screenshot.check(); + public var VOLUME_UP(get, never):Bool; inline function get_VOLUME_UP() @@ -255,13 +258,6 @@ class Controls extends FlxActionSet inline function get_RESET() return _reset.check(); - #if CAN_CHEAT - public var CHEAT(get, never):Bool; - - inline function get_CHEAT() - return _cheat.check(); - #end - public function new(name, scheme:KeyboardScheme = null) { super(name); @@ -295,13 +291,14 @@ class Controls extends FlxActionSet add(_pause); add(_cutscene_advance); add(_cutscene_skip); + add(_debug_menu); + add(_debug_chart); + add(_debug_stage); + add(_screenshot); add(_volume_up); add(_volume_down); add(_volume_mute); add(_reset); - #if CAN_CHEAT - add(_cheat); - #end for (action in digitalActions) byName[action.name] = action; @@ -391,12 +388,10 @@ class Controls extends FlxActionSet case DEBUG_MENU: _debug_menu; case DEBUG_CHART: _debug_chart; case DEBUG_STAGE: _debug_stage; + case SCREENSHOT: _screenshot; case VOLUME_UP: _volume_up; case VOLUME_DOWN: _volume_down; case VOLUME_MUTE: _volume_mute; - #if CAN_CHEAT - case CHEAT: _cheat; - #end } } @@ -464,6 +459,8 @@ class Controls extends FlxActionSet func(_debug_chart, JUST_PRESSED); case DEBUG_STAGE: func(_debug_stage, JUST_PRESSED); + case SCREENSHOT: + func(_screenshot, JUST_PRESSED); case VOLUME_UP: func(_volume_up, JUST_PRESSED); case VOLUME_DOWN: @@ -472,10 +469,6 @@ class Controls extends FlxActionSet func(_volume_mute, JUST_PRESSED); case RESET: func(_reset, JUST_PRESSED); - #if CAN_CHEAT - case CHEAT: - func(_cheat, JUST_PRESSED); - #end } } @@ -666,6 +659,8 @@ class Controls extends FlxActionSet bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU)); bindKeys(Control.DEBUG_CHART, getDefaultKeybinds(scheme, Control.DEBUG_CHART)); bindKeys(Control.DEBUG_STAGE, getDefaultKeybinds(scheme, Control.DEBUG_STAGE)); + bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET)); + bindKeys(Control.SCREENSHOT, getDefaultKeybinds(scheme, Control.SCREENSHOT)); bindKeys(Control.VOLUME_UP, getDefaultKeybinds(scheme, Control.VOLUME_UP)); bindKeys(Control.VOLUME_DOWN, getDefaultKeybinds(scheme, Control.VOLUME_DOWN)); bindKeys(Control.VOLUME_MUTE, getDefaultKeybinds(scheme, Control.VOLUME_MUTE)); @@ -693,6 +688,7 @@ class Controls extends FlxActionSet case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; case Control.DEBUG_STAGE: return []; + case Control.SCREENSHOT: return [F3]; // TODO: Change this back to PrintScreen case Control.VOLUME_UP: return [PLUS, NUMPADPLUS]; case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS]; case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO]; @@ -716,6 +712,7 @@ class Controls extends FlxActionSet case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; case Control.DEBUG_STAGE: return []; + case Control.SCREENSHOT: return [PRINTSCREEN]; case Control.VOLUME_UP: return [PLUS]; case Control.VOLUME_DOWN: return [MINUS]; case Control.VOLUME_MUTE: return [ZERO]; @@ -739,6 +736,7 @@ class Controls extends FlxActionSet case Control.DEBUG_MENU: return [GRAVEACCENT]; case Control.DEBUG_CHART: return []; case Control.DEBUG_STAGE: return []; + case Control.SCREENSHOT: return [PRINTSCREEN]; case Control.VOLUME_UP: return [NUMPADPLUS]; case Control.VOLUME_DOWN: return [NUMPADMINUS]; case Control.VOLUME_MUTE: return [NUMPADZERO]; @@ -845,6 +843,7 @@ class Controls extends FlxActionSet Control.NOTE_LEFT => getDefaultGamepadBinds(Control.NOTE_LEFT), Control.NOTE_RIGHT => getDefaultGamepadBinds(Control.NOTE_RIGHT), Control.PAUSE => getDefaultGamepadBinds(Control.PAUSE), + // Control.SCREENSHOT => [], // Control.VOLUME_UP => [RIGHT_SHOULDER], // Control.VOLUME_DOWN => [LEFT_SHOULDER], // Control.VOLUME_MUTE => [RIGHT_TRIGGER], @@ -852,8 +851,7 @@ class Controls extends FlxActionSet Control.CUTSCENE_SKIP => getDefaultGamepadBinds(Control.CUTSCENE_SKIP), // Control.DEBUG_MENU // Control.DEBUG_CHART - Control.RESET => getDefaultGamepadBinds(Control.RESET), - #if CAN_CHEAT, Control.CHEAT => getDefaultGamepadBinds(Control.CHEAT) #end + Control.RESET => getDefaultGamepadBinds(Control.RESET) ]); } @@ -870,6 +868,7 @@ class Controls extends FlxActionSet case Control.NOTE_LEFT: return [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT]; case Control.NOTE_RIGHT: return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT]; case Control.PAUSE: return [START]; + case Control.SCREENSHOT: return []; case Control.VOLUME_UP: return []; case Control.VOLUME_DOWN: return []; case Control.VOLUME_MUTE: return []; @@ -878,7 +877,6 @@ class Controls extends FlxActionSet case Control.DEBUG_MENU: return []; case Control.DEBUG_CHART: return []; case Control.RESET: return [RIGHT_SHOULDER]; - #if CAN_CHEAT, Control.CHEAT: return [X]; #end default: // Fallthrough. } @@ -1236,6 +1234,8 @@ enum Control // CUTSCENE CUTSCENE_ADVANCE; CUTSCENE_SKIP; + // SCREENSHOT + SCREENSHOT; // VOLUME VOLUME_UP; VOLUME_DOWN; @@ -1244,9 +1244,6 @@ enum Control DEBUG_MENU; DEBUG_CHART; DEBUG_STAGE; - #if CAN_CHEAT - CHEAT; - #end } enum @@ -1289,13 +1286,12 @@ abstract Action(String) to String from String var VOLUME_UP = "volume_up"; var VOLUME_DOWN = "volume_down"; var VOLUME_MUTE = "volume_mute"; + // SCREENSHOT + var SCREENSHOT = "screenshot"; // DEBUG var DEBUG_MENU = "debug_menu"; var DEBUG_CHART = "debug_chart"; var DEBUG_STAGE = "debug_stage"; - #if CAN_CHEAT - var CHEAT = "cheat"; - #end } enum Device diff --git a/source/funkin/ui/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx index 884fc50610..98197a0e78 100644 --- a/source/funkin/ui/MusicBeatState.hx +++ b/source/funkin/ui/MusicBeatState.hx @@ -58,19 +58,6 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler Conductor.stepHit.remove(this.stepHit); } - function handleControls():Void - { - var isHaxeUIFocused:Bool = haxe.ui.focus.FocusManager.instance?.focus != null; - - if (!isHaxeUIFocused) - { - // Rebindable volume keys. - if (controls.VOLUME_MUTE) FlxG.sound.toggleMuted(); - else if (controls.VOLUME_UP) FlxG.sound.changeVolume(0.1); - else if (controls.VOLUME_DOWN) FlxG.sound.changeVolume(-0.1); - } - } - function handleFunctionControls():Void { // Emergency exit button. @@ -84,8 +71,6 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler { super.update(elapsed); - handleControls(); - dispatchEvent(new UpdateScriptEvent(elapsed)); } diff --git a/source/funkin/ui/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx index 9a6f1a3235..2c89703575 100644 --- a/source/funkin/ui/MusicBeatSubState.hx +++ b/source/funkin/ui/MusicBeatSubState.hx @@ -53,11 +53,6 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler { super.update(elapsed); - // Rebindable volume keys. - if (controls.VOLUME_MUTE) FlxG.sound.toggleMuted(); - else if (controls.VOLUME_UP) FlxG.sound.changeVolume(0.1); - else if (controls.VOLUME_DOWN) FlxG.sound.changeVolume(-0.1); - // Emergency exit button. if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState()); diff --git a/source/funkin/util/plugins/ScreenshotPlugin.hx b/source/funkin/util/plugins/ScreenshotPlugin.hx index 8d6f62a280..afb9d34958 100644 --- a/source/funkin/util/plugins/ScreenshotPlugin.hx +++ b/source/funkin/util/plugins/ScreenshotPlugin.hx @@ -78,7 +78,7 @@ class ScreenshotPlugin extends FlxBasic { super.update(elapsed); - if (FlxG.keys.anyJustReleased(_hotkeys)) + if (hasPressedScreenshot()) { capture(); } @@ -100,6 +100,11 @@ class ScreenshotPlugin extends FlxBasic })); } + public function hasPressedScreenshot():Bool + { + return PlayerSettings.player1.controls.SCREENSHOT; + } + public function updatePreferences():Void { _flashColor = Preferences.flashingLights ? FlxColor.WHITE : null; diff --git a/source/funkin/util/plugins/VolumePlugin.hx b/source/funkin/util/plugins/VolumePlugin.hx new file mode 100644 index 0000000000..5dbe60abf7 --- /dev/null +++ b/source/funkin/util/plugins/VolumePlugin.hx @@ -0,0 +1,34 @@ +package funkin.util.plugins; + +import flixel.FlxBasic; + +/** + * Handles volume control in a way that is compatible with alternate control schemes. + */ +class VolumePlugin extends FlxBasic +{ + public function new() + { + super(); + } + + public static function initialize() + { + FlxG.plugins.addPlugin(new VolumePlugin()); + } + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + var isHaxeUIFocused:Bool = haxe.ui.focus.FocusManager.instance?.focus != null; + + if (!isHaxeUIFocused) + { + // Rebindable volume keys. + if (PlayerSettings.player1.controls.VOLUME_MUTE) FlxG.sound.toggleMuted(); + else if (PlayerSettings.player1.controls.VOLUME_UP) FlxG.sound.changeVolume(0.1); + else if (PlayerSettings.player1.controls.VOLUME_DOWN) FlxG.sound.changeVolume(-0.1); + } + } +} From 29e1c480fdffd1a7db39d727a710efa44c5637bd Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 16 Feb 2024 16:35:57 -0500 Subject: [PATCH 11/14] unwrap openFolder from #if sys --- source/funkin/util/FileUtil.hx | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/funkin/util/FileUtil.hx b/source/funkin/util/FileUtil.hx index 268aed57ea..7a7b1422cf 100644 --- a/source/funkin/util/FileUtil.hx +++ b/source/funkin/util/FileUtil.hx @@ -645,7 +645,6 @@ class FileUtil }; } - #if sys public static function openFolder(pathFolder:String) { #if windows @@ -662,7 +661,6 @@ class FileUtil // TODO: implement linux // some shit with xdg-open :thinking: emoji... } - #end static function convertTypeFilter(typeFilter:Array):String { From 0b58a4f66431a0979e518ae18e3af15ac7b3d44c Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Fri, 16 Feb 2024 18:58:27 -0500 Subject: [PATCH 12/14] camera flash fix --- source/funkin/util/plugins/ScreenshotPlugin.hx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/source/funkin/util/plugins/ScreenshotPlugin.hx b/source/funkin/util/plugins/ScreenshotPlugin.hx index afb9d34958..16d0c72441 100644 --- a/source/funkin/util/plugins/ScreenshotPlugin.hx +++ b/source/funkin/util/plugins/ScreenshotPlugin.hx @@ -166,13 +166,11 @@ class ScreenshotPlugin extends FlxBasic */ function showCaptureFeedback():Void { - if (_flashColor != null) - { - for (camera in FlxG.cameras.list) - { - camera.flash(_flashColor, CAMERA_FLASH_DURATION); - } - } + var flashBitmap = new Bitmap(new BitmapData(Std.int(FlxG.stage.width), Std.int(FlxG.stage.height), false, 0xFFFFFFFF)); + var flashSpr = new Sprite(); + flashSpr.addChild(flashBitmap); + FlxG.stage.addChild(flashSpr); + FlxTween.tween(flashSpr, {alpha: 0}, 0.15, {ease: FlxEase.quadOut, onComplete: _ -> FlxG.stage.removeChild(flashSpr)}); } static final PREVIEW_INITIAL_DELAY = 0.25; // How long before the preview starts fading in. From 98e6d4091ee3a89c579803132c5bd8de23512f31 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 16 Feb 2024 22:01:47 -0500 Subject: [PATCH 13/14] Revamp note kind handling in the chart editor. --- source/funkin/data/song/SongData.hx | 26 ++-- source/funkin/data/song/SongDataUtils.hx | 5 +- source/funkin/play/PlayState.hx | 6 +- .../ui/debug/charting/ChartEditorState.hx | 18 ++- .../charting/commands/SelectItemsCommand.hx | 11 ++ .../commands/SetItemSelectionCommand.hx | 10 ++ .../handlers/ChartEditorToolboxHandler.hx | 61 +--------- .../toolboxes/ChartEditorNoteDataToolbox.hx | 115 ++++++++++++++++++ .../charting/util/ChartEditorDropdowns.hx | 49 ++++++++ 9 files changed, 222 insertions(+), 79 deletions(-) create mode 100644 source/funkin/ui/debug/charting/toolboxes/ChartEditorNoteDataToolbox.hx diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 73ecbce148..bba5f899f6 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -898,20 +898,26 @@ class SongNoteDataRaw implements ICloneable /** * The kind of the note. * This can allow the note to include information used for custom behavior. - * Defaults to blank or `Constants.DEFAULT_DIFFICULTY`. + * Defaults to `null` for no kind. */ @:alias("k") - @:default("normal") @:optional - public var kind(get, default):String = ''; + @:isVar + public var kind(get, set):Null = null; - function get_kind():String + function get_kind():Null { - if (this.kind == null || this.kind == '') return 'normal'; + if (this.kind == null || this.kind == '') return null; return this.kind; } + function set_kind(value:Null):Null + { + if (value == '') value = null; + return this.kind = value; + } + public function new(time:Float, data:Int, length:Float = 0, kind:String = '') { this.time = time; @@ -1061,13 +1067,13 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw if (this == null) return other == null; if (other == null) return false; - if (this.kind == '') + if (this.kind == null || this.kind == '') { - if (other.kind != '' && other.kind != 'normal') return false; + if (other.kind != '' && this.kind != null) return false; } else { - if (other.kind == '' || other.kind != this.kind) return false; + if (other.kind == '' || this.kind == null) return false; } return this.time == other.time && this.data == other.data && this.length == other.length; @@ -1082,11 +1088,11 @@ abstract SongNoteData(SongNoteDataRaw) from SongNoteDataRaw to SongNoteDataRaw if (this.kind == '') { - if (other.kind != '' && other.kind != 'normal') return true; + if (other.kind != '') return true; } else { - if (other.kind == '' || other.kind != this.kind) return true; + if (other.kind == '') return true; } return this.time != other.time || this.data != other.data || this.length != other.length; diff --git a/source/funkin/data/song/SongDataUtils.hx b/source/funkin/data/song/SongDataUtils.hx index 01ea2da32f..7f3b01eb41 100644 --- a/source/funkin/data/song/SongDataUtils.hx +++ b/source/funkin/data/song/SongDataUtils.hx @@ -210,14 +210,13 @@ class SongDataUtils */ public static function writeItemsToClipboard(data:SongClipboardItems):Void { - var writer = new json2object.JsonWriter(); + var ignoreNullOptionals = true; + var writer = new json2object.JsonWriter(ignoreNullOptionals); var dataString:String = writer.write(data, ' '); ClipboardUtil.setClipboard(dataString); trace('Wrote ' + data.notes.length + ' notes and ' + data.events.length + ' events to clipboard.'); - - trace(dataString); } /** diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 66e8e0dcc9..b1d075de4c 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -702,7 +702,7 @@ class PlayState extends MusicBeatSubState function assertChartExists():Bool { // Returns null if the song failed to load or doesn't have the selected difficulty. - if (currentSong == null || currentChart == null) + if (currentSong == null || currentChart == null || currentChart.notes == null) { // We have encountered a critical error. Prevent Flixel from trying to run any gameplay logic. criticalFailure = true; @@ -721,6 +721,10 @@ class PlayState extends MusicBeatSubState { message = 'The was a critical error retrieving data for this song on "$currentDifficulty" difficulty with variation "$currentVariation". Click OK to return to the main menu.'; } + else if (currentChart.notes == null) + { + message = 'The was a critical error retrieving note data for this song on "$currentDifficulty" difficulty with variation "$currentVariation". Click OK to return to the main menu.'; + } // Display a popup. This blocks the application until the user clicks OK. lime.app.Application.current.window.alert(message, 'Error loading PlayState'); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 6c64f952b2..48a6e70c9d 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -162,8 +162,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState public static final CHART_EDITOR_TOOLBOX_OPPONENT_PREVIEW_LAYOUT:String = Paths.ui('chart-editor/toolbox/opponent-preview'); public static final CHART_EDITOR_TOOLBOX_METADATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/metadata'); public static final CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT:String = Paths.ui('chart-editor/toolbox/offsets'); - public static final CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/notedata'); - public static final CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/eventdata'); + public static final CHART_EDITOR_TOOLBOX_NOTE_DATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/note-data'); + public static final CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT:String = Paths.ui('chart-editor/toolbox/event-data'); public static final CHART_EDITOR_TOOLBOX_FREEPLAY_LAYOUT:String = Paths.ui('chart-editor/toolbox/freeplay'); public static final CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT:String = Paths.ui('chart-editor/toolbox/playtest-properties'); @@ -538,9 +538,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState // Tools Status /** - * The note kind to use for notes being placed in the chart. Defaults to `''`. + * The note kind to use for notes being placed in the chart. Defaults to `null`. */ - var noteKindToPlace:String = ''; + var noteKindToPlace:Null = null; /** * The event type to use for events being placed in the chart. Defaults to `''`. @@ -2458,11 +2458,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ override public function draw():Void { - if (selectionBoxStartPos != null) - { - trace('selectionBoxSprite: ${selectionBoxSprite.visible} ${selectionBoxSprite.exists} ${this.members.contains(selectionBoxSprite)}'); - } - super.draw(); } @@ -2968,7 +2963,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemToggleToolboxDifficulty.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT, event.value); menubarItemToggleToolboxMetadata.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_METADATA_LAYOUT, event.value); menubarItemToggleToolboxOffsets.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT, event.value); - menubarItemToggleToolboxNotes.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT, event.value); + menubarItemToggleToolboxNoteData.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_NOTE_DATA_LAYOUT, event.value); menubarItemToggleToolboxEventData.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT, event.value); menubarItemToggleToolboxFreeplay.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_FREEPLAY_LAYOUT, event.value); menubarItemToggleToolboxPlaytestProperties.onChange = event -> this.setToolboxState(CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT, event.value); @@ -5286,6 +5281,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { FlxG.watch.addQuick('musicTime', audioInstTrack?.time ?? 0.0); + FlxG.watch.addQuick('noteKindToPlace', noteKindToPlace); + FlxG.watch.addQuick('eventKindToPlace', eventKindToPlace); + FlxG.watch.addQuick('scrollPosInPixels', scrollPositionInPixels); FlxG.watch.addQuick('playheadPosInPixels', playheadPositionInPixels); diff --git a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx index d6c5beeac6..88f73cfed3 100644 --- a/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SelectItemsCommand.hx @@ -59,6 +59,17 @@ class SelectItemsCommand implements ChartEditorCommand state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT); } + // If we just selected one or more notes (and no events), then we should make the note data toolbox display the note data for the selected note. + if (this.events.length == 0 && this.notes.length >= 1) + { + var noteSelected = this.notes[0]; + + state.noteKindToPlace = noteSelected.kind; + + // This code is here to parse note data that's not built as a struct for some reason. + state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTE_DATA_LAYOUT); + } + state.noteDisplayDirty = true; state.notePreviewDirty = true; } diff --git a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx index 35a00e5623..5cc89e1370 100644 --- a/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx +++ b/source/funkin/ui/debug/charting/commands/SetItemSelectionCommand.hx @@ -56,6 +56,16 @@ class SetItemSelectionCommand implements ChartEditorCommand state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT); } + // IF we just selected one or more notes (and no events), then we should make the note data toolbox display the note data for the selected note. + if (this.events.length == 0 && this.notes.length >= 1) + { + var noteSelected = this.notes[0]; + + state.noteKindToPlace = noteSelected.kind; + + state.refreshToolbox(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTE_DATA_LAYOUT); + } + state.noteDisplayDirty = true; } diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx index 9e22ba833e..f32cc2bfbf 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorToolboxHandler.hx @@ -38,6 +38,7 @@ import funkin.ui.debug.charting.toolboxes.ChartEditorMetadataToolbox; import funkin.ui.debug.charting.toolboxes.ChartEditorOffsetsToolbox; import funkin.ui.debug.charting.toolboxes.ChartEditorFreeplayToolbox; import funkin.ui.debug.charting.toolboxes.ChartEditorEventDataToolbox; +import funkin.ui.debug.charting.toolboxes.ChartEditorNoteDataToolbox; import funkin.ui.debug.charting.toolboxes.ChartEditorDifficultyToolbox; import haxe.ui.containers.Frame; import haxe.ui.containers.Grid; @@ -79,17 +80,16 @@ class ChartEditorToolboxHandler switch (id) { - case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT: - onShowToolboxNoteData(state, toolbox); + case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTE_DATA_LAYOUT: + cast(toolbox, ChartEditorBaseToolbox).refresh(); case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT: - // TODO: Fix this. + // TODO: Make these better. cast(toolbox, ChartEditorBaseToolbox).refresh(); case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT: onShowToolboxPlaytestProperties(state, toolbox); case ChartEditorState.CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT: cast(toolbox, ChartEditorBaseToolbox).refresh(); case ChartEditorState.CHART_EDITOR_TOOLBOX_METADATA_LAYOUT: - // TODO: Fix this. cast(toolbox, ChartEditorBaseToolbox).refresh(); case ChartEditorState.CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT: cast(toolbox, ChartEditorBaseToolbox).refresh(); @@ -124,10 +124,6 @@ class ChartEditorToolboxHandler switch (id) { - case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT: - onHideToolboxNoteData(state, toolbox); - case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT: - onHideToolboxEventData(state, toolbox); case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYTEST_PROPERTIES_LAYOUT: onHideToolboxPlaytestProperties(state, toolbox); case ChartEditorState.CHART_EDITOR_TOOLBOX_PLAYER_PREVIEW_LAYOUT: @@ -196,7 +192,7 @@ class ChartEditorToolboxHandler var toolbox:Null = null; switch (id) { - case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT: + case ChartEditorState.CHART_EDITOR_TOOLBOX_NOTE_DATA_LAYOUT: toolbox = buildToolboxNoteDataLayout(state); case ChartEditorState.CHART_EDITOR_TOOLBOX_EVENT_DATA_LAYOUT: toolbox = buildToolboxEventDataLayout(state); @@ -262,58 +258,13 @@ class ChartEditorToolboxHandler static function buildToolboxNoteDataLayout(state:ChartEditorState):Null { - var toolbox:CollapsibleDialog = cast RuntimeComponentBuilder.fromAsset(ChartEditorState.CHART_EDITOR_TOOLBOX_NOTEDATA_LAYOUT); + var toolbox:ChartEditorBaseToolbox = ChartEditorNoteDataToolbox.build(state); if (toolbox == null) return null; - // Starting position. - toolbox.x = 75; - toolbox.y = 100; - - toolbox.onDialogClosed = function(event:DialogEvent) { - state.menubarItemToggleToolboxNotes.selected = false; - } - - var toolboxNotesNoteKind:Null = toolbox.findComponent('toolboxNotesNoteKind', DropDown); - if (toolboxNotesNoteKind == null) throw 'ChartEditorToolboxHandler.buildToolboxNoteDataLayout() - Could not find toolboxNotesNoteKind component.'; - var toolboxNotesCustomKindLabel:Null