diff --git a/.vscode/settings.json b/.vscode/settings.json index af5986fbf..793f2997f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -111,6 +111,11 @@ "target": "hl", "args": ["-debug"] }, + { + "label": "Windows / Debug (Discord)", + "target": "windows", + "args": ["-debug", "-DFEATURE_DEBUG_FUNCTIONS", "-DFEATURE_DISCORD_RPC"] + }, { "label": "Windows / Debug (FlxAnimate Test)", "target": "windows", diff --git a/README.md b/README.md index b1e16f6de..34ec930f6 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Please check out our [Contributor's guide](./CONTRIBUTORS.md) on how you can act # Credits and Special Thanks -Full credits can be found in-game, or wherever the credits.json file is. +Full credits can be found in-game, or in the `credits.json` file which is located [here](https://github.com/FunkinCrew/funkin.assets/blob/main/exclude/data/credits.json). ## Programming - [ninjamuffin99](https://twitter.com/ninja_muffin99) - Lead Programmer diff --git a/art b/art index 1f64f3e74..fbd3e3df7 160000 --- a/art +++ b/art @@ -1 +1 @@ -Subproject commit 1f64f3e7403a090b164f4442d10152b2be5d3d0a +Subproject commit fbd3e3df77734606d88516770b71b56e6fa04bce diff --git a/assets b/assets index 8140f8255..b2404b6b1 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 8140f8255c0db78135dbfa7b6d329f52c363f51b +Subproject commit b2404b6b1cba47da8eef4910f49985d54318186b diff --git a/example_mods/testing123/_polymod_meta.json b/example_mods/testing123/_polymod_meta.json index 0a2ed042c..b8fc60b11 100644 --- a/example_mods/testing123/_polymod_meta.json +++ b/example_mods/testing123/_polymod_meta.json @@ -6,7 +6,7 @@ "name": "EliteMasterEric" } ], - "api_version": "0.1.0", + "api_version": "0.5.0", "mod_version": "1.0.0", "license": "Apache-2.0" } diff --git a/hmm.json b/hmm.json index 7c8959e91..0c0c5571b 100644 --- a/hmm.json +++ b/hmm.json @@ -7,13 +7,6 @@ "ref": "a1eab7b9bf507b87200a3341719054fe427f3b15", "url": "https://github.com/FunkinCrew/FlxPartialSound.git" }, - { - "name": "discord_rpc", - "type": "git", - "dir": null, - "ref": "2d83fa863ef0c1eace5f1cf67c3ac315d1a3a8a5", - "url": "https://github.com/FunkinCrew/linc_discord-rpc" - }, { "name": "flixel", "type": "git", @@ -108,6 +101,13 @@ "ref": "147294123f983e35f50a966741474438069a7a8f", "url": "https://github.com/FunkinCrew/hxcpp-debugger" }, + { + "name": "hxdiscord_rpc", + "type": "git", + "dir": null, + "ref": "82c47ecc1a454b7dd644e4fcac7e91155f176dec", + "url": "https://github.com/FunkinCrew/hxdiscord_rpc" + }, { "name": "hxjsonast", "type": "git", diff --git a/project.hxp b/project.hxp index 1a2198997..2f537935a 100644 --- a/project.hxp +++ b/project.hxp @@ -214,6 +214,12 @@ class Project extends HXProject { */ static final FEATURE_OPEN_URL:FeatureFlag = "FEATURE_OPEN_URL"; + /** + * `-DFEATURE_SCREENSHOTS` + * If this flag is enabled, the game will support the screenshots feature. + */ + static final FEATURE_SCREENSHOTS:FeatureFlag = "FEATURE_SCREENSHOTS"; + /** * `-DFEATURE_CHART_EDITOR` * If this flag is enabled, the Chart Editor will be accessible from the debug menu. @@ -473,10 +479,9 @@ class Project extends HXProject { // Should default to true on workspace builds and false on release builds. REDIRECT_ASSETS_FOLDER.apply(this, isDebug() && isDesktop()); - // Should be true on release, non-tester builds. + // Should be true on desktop, release, non-tester builds. // We don't want testers to accidentally leak songs to their Discord friends! - // TODO: Re-enable this. - FEATURE_DISCORD_RPC.apply(this, false && !FEATURE_DEBUG_FUNCTIONS.isEnabled(this)); + FEATURE_DISCORD_RPC.apply(this, isDesktop() && !FEATURE_DEBUG_FUNCTIONS.isEnabled(this)); // Should be true only on web builds. // Audio context issues only exist there. @@ -494,6 +499,10 @@ class Project extends HXProject { // Should be true except on web builds. // Chart editor doesn't work there. FEATURE_CHART_EDITOR.apply(this, !isWeb()); + + // Should be true except on web builds. + // Screenshots doesn't work there. + FEATURE_SCREENSHOTS.apply(this, !isWeb()); } /** @@ -644,7 +653,7 @@ class Project extends HXProject { } if (FEATURE_DISCORD_RPC.isEnabled(this)) { - addHaxelib('discord_rpc'); // Discord API + addHaxelib('hxdiscord_rpc'); // Discord API } if (FEATURE_NEWGROUNDS.isEnabled(this)) { diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index f71de00f4..a0ca74162 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -1,42 +1,42 @@ package funkin; -import funkin.data.freeplay.player.PlayerRegistry; -import funkin.ui.debug.charting.ChartEditorState; -import funkin.ui.transition.LoadingState; -import flixel.FlxState; import flixel.addons.transition.FlxTransitionableState; import flixel.addons.transition.FlxTransitionSprite.GraphicTransTileDiamond; import flixel.addons.transition.TransitionData; +import flixel.FlxSprite; +import flixel.FlxState; import flixel.graphics.FlxGraphic; import flixel.math.FlxPoint; import flixel.math.FlxRect; -import flixel.FlxSprite; import flixel.system.debug.log.LogStyle; import flixel.util.FlxColor; -import funkin.util.macro.MacroUtil; -import funkin.util.WindowUtil; -import funkin.play.PlayStatePlaylist; -import openfl.display.BitmapData; -import funkin.data.story.level.LevelRegistry; -import funkin.data.notestyle.NoteStyleRegistry; -import funkin.data.freeplay.style.FreeplayStyleRegistry; -import funkin.data.event.SongEventRegistry; -import funkin.data.stage.StageRegistry; import funkin.data.dialogue.conversation.ConversationRegistry; import funkin.data.dialogue.dialoguebox.DialogueBoxRegistry; import funkin.data.dialogue.speaker.SpeakerRegistry; import funkin.data.freeplay.album.AlbumRegistry; +import funkin.data.freeplay.player.PlayerRegistry; +import funkin.data.freeplay.style.FreeplayStyleRegistry; +import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.song.SongRegistry; +import funkin.data.event.SongEventRegistry; +import funkin.data.stage.StageRegistry; +import funkin.data.story.level.LevelRegistry; +import funkin.modding.module.ModuleHandler; import funkin.play.character.CharacterData.CharacterDataParser; import funkin.play.notes.notekind.NoteKindManager; -import funkin.modding.module.ModuleHandler; +import funkin.play.PlayStatePlaylist; +import funkin.ui.debug.charting.ChartEditorState; import funkin.ui.title.TitleState; +import funkin.ui.transition.LoadingState; import funkin.util.CLIUtil; import funkin.util.CLIUtil.CLIParams; +import funkin.util.macro.MacroUtil; import funkin.util.TimerUtil; import funkin.util.TrackerUtil; +import funkin.util.WindowUtil; +import openfl.display.BitmapData; #if FEATURE_DISCORD_RPC -import Discord.DiscordClient; +import funkin.api.discord.DiscordClient; #end /** @@ -125,10 +125,10 @@ class InitState extends FlxState // DISCORD API SETUP // #if FEATURE_DISCORD_RPC - DiscordClient.initialize(); + DiscordClient.instance.init(); - Application.current.onExit.add(function(exitCode) { - DiscordClient.shutdown(); + lime.app.Application.current.onExit.add(function(exitCode) { + DiscordClient.instance.shutdown(); }); #end @@ -148,10 +148,12 @@ class InitState extends FlxState #if FEATURE_DEBUG_FUNCTIONS funkin.util.plugins.MemoryGCPlugin.initialize(); #end + #if FEATURE_SCREENSHOTS + funkin.util.plugins.ScreenshotPlugin.initialize(); + #end funkin.util.plugins.EvacuateDebugPlugin.initialize(); funkin.util.plugins.ForceCrashPlugin.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/api/discord/Discord.hx b/source/funkin/api/discord/Discord.hx deleted file mode 100644 index 9dd513bf7..000000000 --- a/source/funkin/api/discord/Discord.hx +++ /dev/null @@ -1,91 +0,0 @@ -package funkin.api.discord; - -import Sys.sleep; -#if FEATURE_DISCORD_RPC -import discord_rpc.DiscordRpc; -#end - -class DiscordClient -{ - #if FEATURE_DISCORD_RPC - public function new() - { - trace("Discord Client starting..."); - DiscordRpc.start( - { - clientID: "814588678700924999", - onReady: onReady, - onError: onError, - onDisconnected: onDisconnected - }); - trace("Discord Client started."); - - while (true) - { - DiscordRpc.process(); - sleep(2); - // trace("Discord Client Update"); - } - - DiscordRpc.shutdown(); - } - - public static function shutdown() - { - DiscordRpc.shutdown(); - } - - static function onReady() - { - DiscordRpc.presence( - { - details: "In the Menus", - state: null, - largeImageKey: 'icon', - largeImageText: "Friday Night Funkin'" - }); - } - - static function onError(_code:Int, _message:String) - { - trace('Error! $_code : $_message'); - } - - static function onDisconnected(_code:Int, _message:String) - { - trace('Disconnected! $_code : $_message'); - } - - public static function initialize() - { - var DiscordDaemon = sys.thread.Thread.create(() -> { - new DiscordClient(); - }); - trace("Discord Client initialized"); - } - - public static function changePresence(details:String, ?state:String, ?smallImageKey:String, ?hasStartTimestamp:Bool, ?endTimestamp:Float) - { - var startTimestamp:Float = if (hasStartTimestamp) Date.now().getTime() else 0; - - if (endTimestamp > 0) - { - endTimestamp = startTimestamp + endTimestamp; - } - - DiscordRpc.presence( - { - details: details, - state: state, - largeImageKey: 'icon', - largeImageText: "Friday Night Funkin'", - smallImageKey: smallImageKey, - // Obtained times are in milliseconds so they are divided so Discord can use it - startTimestamp: Std.int(startTimestamp / 1000), - endTimestamp: Std.int(endTimestamp / 1000) - }); - - // trace('Discord RPC Updated. Arguments: $details, $state, $smallImageKey, $hasStartTimestamp, $endTimestamp'); - } - #end -} diff --git a/source/funkin/api/discord/DiscordClient.hx b/source/funkin/api/discord/DiscordClient.hx new file mode 100644 index 000000000..3be2d4c87 --- /dev/null +++ b/source/funkin/api/discord/DiscordClient.hx @@ -0,0 +1,204 @@ +package funkin.api.discord; + +#if FEATURE_DISCORD_RPC +import hxdiscord_rpc.Discord; +import hxdiscord_rpc.Types; +import sys.thread.Thread; + +class DiscordClient +{ + static final CLIENT_ID:String = "816168432860790794"; + + public static var instance(get, never):DiscordClient; + static var _instance:Null = null; + + static function get_instance():DiscordClient + { + if (DiscordClient._instance == null) _instance = new DiscordClient(); + if (DiscordClient._instance == null) throw "Could not initialize singleton DiscordClient!"; + return DiscordClient._instance; + } + + var handlers:DiscordEventHandlers; + + private function new() + { + trace('[DISCORD] Initializing event handlers...'); + + handlers = DiscordEventHandlers.create(); + + handlers.ready = cpp.Function.fromStaticFunction(onReady); + handlers.disconnected = cpp.Function.fromStaticFunction(onDisconnected); + handlers.errored = cpp.Function.fromStaticFunction(onError); + } + + public function init():Void + { + trace('[DISCORD] Initializing connection...'); + + // Discord.initialize(CLIENT_ID, handlers, true, null); + Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, null); + + createDaemon(); + } + + var daemon:Thread = null; + + function createDaemon():Void + { + daemon = Thread.create(doDaemonWork); + } + + function doDaemonWork():Void + { + while (true) + { + trace('[DISCORD] Performing client update...'); + + #if DISCORD_DISABLE_IO_THREAD + Discord.updateConnection(); + #end + + Discord.runCallbacks(); + Sys.sleep(2); + } + } + + public function shutdown():Void + { + trace('[DISCORD] Shutting down...'); + + Discord.shutdown(); + } + + public function setPresence(params:DiscordClientPresenceParams):Void + { + trace('[DISCORD] Updating presence... (${params})'); + + Discord.updatePresence(buildPresence(params)); + } + + function buildPresence(params:DiscordClientPresenceParams):DiscordRichPresence + { + var presence = DiscordRichPresence.create(); + + // Presence should always be playing the game. + presence.type = DiscordActivityType_Playing; + + // Text when hovering over the large image. We just leave this as the game name. + presence.largeImageText = "Friday Night Funkin'"; + + // State should be generally what the person is doing, like "In the Menus" or "Pico (Pico Mix) [Freeplay Hard]" + presence.state = cast(params.state, Null); + // Details should be what the person is specifically doing, including stuff like timestamps (maybe something like "03:24 elapsed"). + presence.details = cast(params.details, Null); + + // The large image displaying what the user is doing. + // This should probably be album art. + // IMPORTANT NOTE: This can be an asset key uploaded to Discord's developer panel OR any URL you like. + presence.largeImageKey = cast(params.largeImageKey, Null) ?? "album-volume1"; + + trace('[DISCORD] largeImageKey: ${presence.largeImageKey}'); + + // TODO: Make this use the song's album art. + // presence.largeImageKey = "icon"; + // presence.largeImageKey = "https://f4.bcbits.com/img/a0746694746_16.jpg"; + + // The small inset image for what the user is doing. + // This can be the opponent's health icon? + // NOTE: Like largeImageKey, this can be a URL, or an asset key. + presence.smallImageKey = cast(params.smallImageKey, Null); + + // NOTE: In previous versions, this showed as "Elapsed", but now shows as playtime and doesn't look good + // presence.startTimestamp = time - 10; + // presence.endTimestamp = time + 30; + + final button1:DiscordButton = DiscordButton.create(); + button1.label = "Play on Web"; + button1.url = Constants.URL_NEWGROUNDS; + presence.buttons[0] = button1; + + final button2:DiscordButton = DiscordButton.create(); + button2.label = "Download"; + button2.url = Constants.URL_ITCH; + presence.buttons[1] = button2; + + return presence; + } + + // TODO: WHAT THE FUCK get this pointer bullfuckery out of here + private static function onReady(request:cpp.RawConstPointer):Void + { + trace('[DISCORD] Client has connected!'); + + final username:String = request[0].username; + final globalName:String = request[0].username; + final discriminator:Int = Std.parseInt(request[0].discriminator); + + if (discriminator != 0) + { + trace('[DISCORD] User: ${username}#${discriminator} (${globalName})'); + } + else + { + trace('[DISCORD] User: @${username} (${globalName})'); + } + } + + private static function onDisconnected(errorCode:Int, message:cpp.ConstCharStar):Void + { + trace('[DISCORD] Client has disconnected! ($errorCode) "${cast (message, String)}"'); + } + + private static function onError(errorCode:Int, message:cpp.ConstCharStar):Void + { + trace('[DISCORD] Client has received an error! ($errorCode) "${cast (message, String)}"'); + } + + // public var type(get, set):DiscordActivityType; + // public var state(get, set):String; + // public var details(get, set):String; + // public var startTimestamp(get, set):Int; + // public var endTimestamp(get, set):Int; + // public var largeImageKey(get, set):String; + // public var largeImageText(get, set):String; + // public var smallImageKey(get, set):String; + // public var smallImageText(get, set):String; + // + // + // public var partyId(get, set) + // public var partySize(get, set) + // public var partyMax(get, set) + // public var partyPrivacy(get, set) + // + // public var buttons(get, set) + // + // public var matchSecret(get, set) + // public var joinSecret(get, set) + // public var spectateSecret(get, set) +} + +typedef DiscordClientPresenceParams = +{ + /** + * The first row of text below the game title. + */ + var state:String; + + /** + * The second row of text below the game title. + * Use `null` to display no text. + */ + var details:Null; + + /** + * A large, 4-row high image to the left of the content. + */ + var ?largeImageKey:String; + + /** + * A small, inset image to the bottom right of `largeImageKey`. + */ + var ?smallImageKey:String; +} +#end diff --git a/source/funkin/data/stage/CHANGELOG.md b/source/funkin/data/stage/CHANGELOG.md index 879139db5..bf9d750cc 100644 --- a/source/funkin/data/stage/CHANGELOG.md +++ b/source/funkin/data/stage/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.2] +### Added +- Added the ability to specify `flipX` and `flipY` on stage props to horizontally or vertically flip, respectively. + ## [1.0.1] ### Added - Added the ability to specify a hexadecimal color in the `assetPath` field instead of a texture key. diff --git a/source/funkin/data/stage/StageData.hx b/source/funkin/data/stage/StageData.hx index eda8e3148..1e9172b00 100644 --- a/source/funkin/data/stage/StageData.hx +++ b/source/funkin/data/stage/StageData.hx @@ -118,6 +118,22 @@ typedef StageDataProp = @:default(false) var isPixel:Bool; + /** + * If set to true, the prop will be flipped horizontally. + * @default false + */ + @:optional + @:default(false) + var flipX:Bool; + + /** + * If set to true, the prop will be flipped vertically. + * @default false + */ + @:optional + @:default(false) + var flipY:Bool; + /** * Either the scale of the prop as a float, or the [w, h] scale as an array of two floats. * Pro tip: On pixel-art levels, save the sprite small and set this value to 6 or so to save memory. diff --git a/source/funkin/data/stage/StageRegistry.hx b/source/funkin/data/stage/StageRegistry.hx index 87113ef05..e11166bdd 100644 --- a/source/funkin/data/stage/StageRegistry.hx +++ b/source/funkin/data/stage/StageRegistry.hx @@ -11,9 +11,9 @@ class StageRegistry extends BaseRegistry * Handle breaking changes by incrementing this value * and adding migration to the `migrateStageData()` function. */ - public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.0"; + public static final STAGE_DATA_VERSION:thx.semver.Version = "1.0.2"; - public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x"; + public static final STAGE_DATA_VERSION_RULE:thx.semver.VersionRule = ">=1.0.0 <=1.0.2"; public static var instance(get, never):StageRegistry; static var _instance:Null = null; diff --git a/source/funkin/input/Controls.hx b/source/funkin/input/Controls.hx index da5aaac58..ab1d0e0a7 100644 --- a/source/funkin/input/Controls.hx +++ b/source/funkin/input/Controls.hx @@ -59,7 +59,9 @@ class Controls extends FlxActionSet var _back = new FunkinAction(Action.BACK); var _pause = new FunkinAction(Action.PAUSE); var _reset = new FunkinAction(Action.RESET); + #if FEATURE_SCREENSHOTS var _window_screenshot = new FunkinAction(Action.WINDOW_SCREENSHOT); + #end var _window_fullscreen = new FunkinAction(Action.WINDOW_FULLSCREEN); var _freeplay_favorite = new FunkinAction(Action.FREEPLAY_FAVORITE); var _freeplay_left = new FunkinAction(Action.FREEPLAY_LEFT); @@ -67,8 +69,12 @@ class Controls extends FlxActionSet var _freeplay_char_select = new FunkinAction(Action.FREEPLAY_CHAR_SELECT); var _cutscene_advance = new FunkinAction(Action.CUTSCENE_ADVANCE); var _debug_menu = new FunkinAction(Action.DEBUG_MENU); + #if FEATURE_CHART_EDITOR var _debug_chart = new FunkinAction(Action.DEBUG_CHART); + #end + #if FEATURE_STAGE_EDITOR var _debug_stage = new FunkinAction(Action.DEBUG_STAGE); + #end var _volume_up = new FunkinAction(Action.VOLUME_UP); var _volume_down = new FunkinAction(Action.VOLUME_DOWN); var _volume_mute = new FunkinAction(Action.VOLUME_MUTE); @@ -243,10 +249,12 @@ class Controls extends FlxActionSet inline function get_WINDOW_FULLSCREEN() return _window_fullscreen.check(); + #if FEATURE_SCREENSHOTS public var WINDOW_SCREENSHOT(get, never):Bool; inline function get_WINDOW_SCREENSHOT() return _window_screenshot.check(); + #end public var FREEPLAY_FAVORITE(get, never):Bool; @@ -278,15 +286,19 @@ class Controls extends FlxActionSet inline function get_DEBUG_MENU() return _debug_menu.check(); + #if FEATURE_CHART_EDITOR public var DEBUG_CHART(get, never):Bool; inline function get_DEBUG_CHART() return _debug_chart.check(); + #end + #if FEATURE_STAGE_EDITOR public var DEBUG_STAGE(get, never):Bool; inline function get_DEBUG_STAGE() return _debug_stage.check(); + #end public var VOLUME_UP(get, never):Bool; @@ -319,7 +331,7 @@ class Controls extends FlxActionSet add(_back); add(_pause); add(_reset); - add(_window_screenshot); + #if FEATURE_SCREENSHOTS add(_window_screenshot); #end add(_window_fullscreen); add(_freeplay_favorite); add(_freeplay_left); @@ -327,8 +339,8 @@ class Controls extends FlxActionSet add(_freeplay_char_select); add(_cutscene_advance); add(_debug_menu); - add(_debug_chart); - add(_debug_stage); + #if FEATURE_CHART_EDITOR add(_debug_chart); #end + #if FEATURE_STAGE_EDITOR add(_debug_stage); #end add(_volume_up); add(_volume_down); add(_volume_mute); @@ -444,7 +456,7 @@ class Controls extends FlxActionSet case BACK: _back; case PAUSE: _pause; case RESET: _reset; - case WINDOW_SCREENSHOT: _window_screenshot; + #if FEATURE_SCREENSHOTS case WINDOW_SCREENSHOT: _window_screenshot; #end case WINDOW_FULLSCREEN: _window_fullscreen; case FREEPLAY_FAVORITE: _freeplay_favorite; case FREEPLAY_LEFT: _freeplay_left; @@ -452,8 +464,8 @@ class Controls extends FlxActionSet case FREEPLAY_CHAR_SELECT: _freeplay_char_select; case CUTSCENE_ADVANCE: _cutscene_advance; case DEBUG_MENU: _debug_menu; - case DEBUG_CHART: _debug_chart; - case DEBUG_STAGE: _debug_stage; + #if FEATURE_CHART_EDITOR case DEBUG_CHART: _debug_chart; #end + #if FEATURE_STAGE_EDITOR case DEBUG_STAGE: _debug_stage; #end case VOLUME_UP: _volume_up; case VOLUME_DOWN: _volume_down; case VOLUME_MUTE: _volume_mute; @@ -516,8 +528,10 @@ class Controls extends FlxActionSet func(_pause, JUST_PRESSED); case RESET: func(_reset, JUST_PRESSED); + #if FEATURE_SCREENSHOTS case WINDOW_SCREENSHOT: func(_window_screenshot, JUST_PRESSED); + #end case WINDOW_FULLSCREEN: func(_window_fullscreen, JUST_PRESSED); case FREEPLAY_FAVORITE: @@ -532,10 +546,14 @@ class Controls extends FlxActionSet func(_cutscene_advance, JUST_PRESSED); case DEBUG_MENU: func(_debug_menu, JUST_PRESSED); + #if FEATURE_CHART_EDITOR case DEBUG_CHART: func(_debug_chart, JUST_PRESSED); + #end + #if FEATURE_STAGE_EDITOR case DEBUG_STAGE: func(_debug_stage, JUST_PRESSED); + #end case VOLUME_UP: func(_volume_up, JUST_PRESSED); case VOLUME_DOWN: @@ -744,7 +762,9 @@ class Controls extends FlxActionSet bindKeys(Control.BACK, getDefaultKeybinds(scheme, Control.BACK)); bindKeys(Control.PAUSE, getDefaultKeybinds(scheme, Control.PAUSE)); bindKeys(Control.RESET, getDefaultKeybinds(scheme, Control.RESET)); + #if FEATURE_SCREENSHOTS bindKeys(Control.WINDOW_SCREENSHOT, getDefaultKeybinds(scheme, Control.WINDOW_SCREENSHOT)); + #end bindKeys(Control.WINDOW_FULLSCREEN, getDefaultKeybinds(scheme, Control.WINDOW_FULLSCREEN)); bindKeys(Control.FREEPLAY_FAVORITE, getDefaultKeybinds(scheme, Control.FREEPLAY_FAVORITE)); bindKeys(Control.FREEPLAY_LEFT, getDefaultKeybinds(scheme, Control.FREEPLAY_LEFT)); @@ -752,8 +772,12 @@ class Controls extends FlxActionSet bindKeys(Control.FREEPLAY_CHAR_SELECT, getDefaultKeybinds(scheme, Control.FREEPLAY_CHAR_SELECT)); bindKeys(Control.CUTSCENE_ADVANCE, getDefaultKeybinds(scheme, Control.CUTSCENE_ADVANCE)); bindKeys(Control.DEBUG_MENU, getDefaultKeybinds(scheme, Control.DEBUG_MENU)); + #if FEATURE_CHART_EDITOR bindKeys(Control.DEBUG_CHART, getDefaultKeybinds(scheme, Control.DEBUG_CHART)); + #end + #if FEATURE_STAGE_EDITOR bindKeys(Control.DEBUG_STAGE, getDefaultKeybinds(scheme, Control.DEBUG_STAGE)); + #end 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)); @@ -781,15 +805,15 @@ class Controls extends FlxActionSet case Control.PAUSE: return [P, ENTER, ESCAPE]; case Control.RESET: return [R]; case Control.WINDOW_FULLSCREEN: return [F11]; // We use F for other things LOL. - case Control.WINDOW_SCREENSHOT: return [F3]; + #if FEATURE_SCREENSHOTS case Control.WINDOW_SCREENSHOT: return [F3]; #end case Control.FREEPLAY_FAVORITE: return [F]; // Favorite a song on the menu case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu case Control.FREEPLAY_RIGHT: return [E]; // Switch tabs on the menu case Control.FREEPLAY_CHAR_SELECT: return [TAB]; case Control.CUTSCENE_ADVANCE: return [Z, ENTER]; case Control.DEBUG_MENU: return [GRAVEACCENT]; - case Control.DEBUG_CHART: return []; - case Control.DEBUG_STAGE: return []; + #if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end + #if FEATURE_STAGE_EDITOR case Control.DEBUG_STAGE: return []; #end case Control.VOLUME_UP: return [PLUS, NUMPADPLUS]; case Control.VOLUME_DOWN: return [MINUS, NUMPADMINUS]; case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO]; @@ -809,7 +833,7 @@ class Controls extends FlxActionSet case Control.BACK: return [H, X]; case Control.PAUSE: return [ONE]; case Control.RESET: return [R]; - case Control.WINDOW_SCREENSHOT: return [F3]; + #if FEATURE_SCREENSHOTS case Control.WINDOW_SCREENSHOT: return [F3]; #end case Control.WINDOW_FULLSCREEN: return [F11]; case Control.FREEPLAY_FAVORITE: return [F]; // Favorite a song on the menu case Control.FREEPLAY_LEFT: return [Q]; // Switch tabs on the menu @@ -817,8 +841,8 @@ class Controls extends FlxActionSet case Control.FREEPLAY_CHAR_SELECT: return [TAB]; case Control.CUTSCENE_ADVANCE: return [G, Z]; case Control.DEBUG_MENU: return [GRAVEACCENT]; - case Control.DEBUG_CHART: return []; - case Control.DEBUG_STAGE: return []; + #if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end + #if FEATURE_STAGE_EDITOR case Control.DEBUG_STAGE: return []; #end case Control.VOLUME_UP: return [PLUS]; case Control.VOLUME_DOWN: return [MINUS]; case Control.VOLUME_MUTE: return [ZERO]; @@ -838,7 +862,7 @@ class Controls extends FlxActionSet case Control.BACK: return [ESCAPE]; case Control.PAUSE: return [ONE]; case Control.RESET: return [R]; - case Control.WINDOW_SCREENSHOT: return []; + #if FEATURE_SCREENSHOTS case Control.WINDOW_SCREENSHOT: return []; #end case Control.WINDOW_FULLSCREEN: return []; case Control.FREEPLAY_FAVORITE: return []; case Control.FREEPLAY_LEFT: return []; @@ -846,8 +870,8 @@ class Controls extends FlxActionSet case Control.FREEPLAY_CHAR_SELECT: return []; case Control.CUTSCENE_ADVANCE: return [ENTER]; case Control.DEBUG_MENU: return []; - case Control.DEBUG_CHART: return []; - case Control.DEBUG_STAGE: return []; + #if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: return []; #end + #if FEATURE_STAGE_EDITOR case Control.DEBUG_STAGE: return []; #end case Control.VOLUME_UP: return [NUMPADPLUS]; case Control.VOLUME_DOWN: return [NUMPADMINUS]; case Control.VOLUME_MUTE: return [NUMPADZERO]; @@ -952,7 +976,9 @@ class Controls extends FlxActionSet Control.PAUSE => getDefaultGamepadBinds(Control.PAUSE), Control.RESET => getDefaultGamepadBinds(Control.RESET), Control.WINDOW_FULLSCREEN => getDefaultGamepadBinds(Control.WINDOW_FULLSCREEN), + #if FEATURE_SCREENSHOTS Control.WINDOW_SCREENSHOT => getDefaultGamepadBinds(Control.WINDOW_SCREENSHOT), + #end Control.CUTSCENE_ADVANCE => getDefaultGamepadBinds(Control.CUTSCENE_ADVANCE), Control.FREEPLAY_FAVORITE => getDefaultGamepadBinds(Control.FREEPLAY_FAVORITE), Control.FREEPLAY_LEFT => getDefaultGamepadBinds(Control.FREEPLAY_LEFT), @@ -961,8 +987,12 @@ class Controls extends FlxActionSet Control.VOLUME_DOWN => getDefaultGamepadBinds(Control.VOLUME_DOWN), Control.VOLUME_MUTE => getDefaultGamepadBinds(Control.VOLUME_MUTE), Control.DEBUG_MENU => getDefaultGamepadBinds(Control.DEBUG_MENU), + #if FEATURE_CHART_EDITOR Control.DEBUG_CHART => getDefaultGamepadBinds(Control.DEBUG_CHART), + #end + #if FEATURE_STAGE_EDITOR Control.DEBUG_STAGE => getDefaultGamepadBinds(Control.DEBUG_STAGE), + #end ]); } @@ -996,8 +1026,10 @@ class Controls extends FlxActionSet return [FlxGamepadInputID.BACK]; // Back (i.e. Select) case Control.WINDOW_FULLSCREEN: []; + #if FEATURE_SCREENSHOTS case Control.WINDOW_SCREENSHOT: []; + #end case Control.CUTSCENE_ADVANCE: return [A]; case Control.FREEPLAY_FAVORITE: @@ -1014,10 +1046,14 @@ class Controls extends FlxActionSet []; case Control.DEBUG_MENU: []; + #if FEATURE_CHART_EDITOR case Control.DEBUG_CHART: []; + #end + #if FEATURE_STAGE_EDITOR case Control.DEBUG_STAGE: []; + #end default: // Fallthrough. } @@ -1582,7 +1618,7 @@ enum Control FREEPLAY_RIGHT; FREEPLAY_CHAR_SELECT; // WINDOW - WINDOW_SCREENSHOT; + #if FEATURE_SCREENSHOTS WINDOW_SCREENSHOT; #end WINDOW_FULLSCREEN; // VOLUME VOLUME_UP; @@ -1590,8 +1626,8 @@ enum Control VOLUME_MUTE; // DEBUG DEBUG_MENU; - DEBUG_CHART; - DEBUG_STAGE; + #if FEATURE_CHART_EDITOR DEBUG_CHART; #end + #if FEATURE_STAGE_EDITOR DEBUG_STAGE; #end } enum abstract Action(String) to String from String @@ -1628,7 +1664,9 @@ enum abstract Action(String) to String from String var RESET = "reset"; // WINDOW var WINDOW_FULLSCREEN = "window_fullscreen"; + #if FEATURE_SCREENSHOTS var WINDOW_SCREENSHOT = "window_screenshot"; + #end // CUTSCENE var CUTSCENE_ADVANCE = "cutscene_advance"; // FREEPLAY @@ -1642,8 +1680,12 @@ enum abstract Action(String) to String from String var VOLUME_MUTE = "volume_mute"; // DEBUG var DEBUG_MENU = "debug_menu"; + #if FEATURE_CHART_EDITOR var DEBUG_CHART = "debug_chart"; + #end + #if FEATURE_STAGE_EDITOR var DEBUG_STAGE = "debug_stage"; + #end } enum Device diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx index ec3acf3b3..125882161 100644 --- a/source/funkin/modding/PolymodHandler.hx +++ b/source/funkin/modding/PolymodHandler.hx @@ -27,11 +27,18 @@ import polymod.Polymod; class PolymodHandler { /** - * The API version that mods should comply with. - * Indicates which mods are compatible with this version of the game. + * The API version for the current version of the game. Since 0.5.0, we've just made this the game version! * Minor updates rarely impact mods but major versions often do. */ - static final API_VERSION:String = "0.5.0"; // Constants.VERSION; + // static final API_VERSION:String = Constants.VERSION; + + /** + * The Semantic Versioning rule + * Indicates which mods are compatible with this version of the game. + * Using more complex rules allows mods from older compatible versions to stay functioning, + * while preventing mods made for future versions from being installed. + */ + static final API_VERSION_RULE:String = ">=0.5.0 <0.6.0"; /** * Where relative to the executable that mods are located. @@ -131,7 +138,7 @@ class PolymodHandler // Framework being used to load assets. framework: OPENFL, // The current version of our API. - apiVersionRule: API_VERSION, + apiVersionRule: API_VERSION_RULE, // Call this function any time an error occurs. errorCallback: PolymodErrorHandler.onPolymodError, // Enforce semantic version patterns for each mod. @@ -338,7 +345,7 @@ class PolymodHandler var modMetadata:Array = Polymod.scan( { modRoot: MOD_FOLDER, - apiVersionRule: API_VERSION, + apiVersionRule: API_VERSION_RULE, fileSystem: modFileSystem, errorCallback: PolymodErrorHandler.onPolymodError }); diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 6678cfc3c..fa2b28d54 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -15,8 +15,8 @@ import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.ui.FlxBar; import flixel.util.FlxColor; -import flixel.util.FlxTimer; import flixel.util.FlxStringUtil; +import flixel.util.FlxTimer; import funkin.api.newgrounds.NGio; import funkin.audio.FunkinSound; import funkin.audio.VoicesGroup; @@ -44,12 +44,12 @@ import funkin.play.cutscene.dialogue.Conversation; import funkin.play.cutscene.VanillaCutscenes; import funkin.play.cutscene.VideoCutscene; import funkin.play.notes.NoteDirection; +import funkin.play.notes.notekind.NoteKindManager; import funkin.play.notes.NoteSplash; import funkin.play.notes.NoteSprite; import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.Strumline; import funkin.play.notes.SustainTrail; -import funkin.play.notes.notekind.NoteKindManager; import funkin.play.scoring.Scoring; import funkin.play.song.Song; import funkin.play.stage.Stage; @@ -68,7 +68,7 @@ import openfl.display.BitmapData; import openfl.geom.Rectangle; import openfl.Lib; #if FEATURE_DISCORD_RPC -import Discord.DiscordClient; +import funkin.api.discord.DiscordClient; #end /** @@ -447,10 +447,8 @@ class PlayState extends MusicBeatSubState #if FEATURE_DISCORD_RPC // Discord RPC variables - var storyDifficultyText:String = ''; - var iconRPC:String = ''; - var detailsText:String = ''; - var detailsPausedText:String = ''; + var discordRPCAlbum:String = ''; + var discordRPCIcon:String = ''; #end /** @@ -817,6 +815,7 @@ class PlayState extends MusicBeatSubState } else { + this.remove(currentStage); FlxG.switchState(() -> new MainMenuState()); } return false; @@ -965,6 +964,7 @@ class PlayState extends MusicBeatSubState // It's a reference to Gitaroo Man, which doesn't let you pause the game. if (!isSubState && event.gitaroo) { + this.remove(currentStage); FlxG.switchState(() -> new GitarooPause( { targetSong: currentSong, @@ -992,7 +992,15 @@ class PlayState extends MusicBeatSubState } #if FEATURE_DISCORD_RPC - DiscordClient.changePresence(detailsPausedText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC); + DiscordClient.instance.setPresence( + { + details: 'Paused - ${buildDiscordRPCDetails()}', + + state: buildDiscordRPCState(), + + largeImageKey: discordRPCAlbum, + smallImageKey: discordRPCIcon + }); #end } } @@ -1081,8 +1089,14 @@ class PlayState extends MusicBeatSubState } #if FEATURE_DISCORD_RPC - // Game Over doesn't get his own variable because it's only used here - DiscordClient.changePresence('Game Over - ' + detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC); + DiscordClient.instance.setPresence( + { + details: 'Game Over - ${buildDiscordRPCDetails()}', + state: buildDiscordRPCState(), + + largeImageKey: discordRPCAlbum, + smallImageKey: discordRPCIcon + }); #end } else if (isPlayerDying) @@ -1293,14 +1307,29 @@ class PlayState extends MusicBeatSubState Countdown.resumeCountdown(); #if FEATURE_DISCORD_RPC - if (startTimer.finished) + if (Conductor.instance.songPosition > 0) { - DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, - currentSongLengthMs - Conductor.instance.songPosition); + // DiscordClient.changePresence(detailsText, '${currentChart.songName} ($discordRPCDifficulty)', discordRPCIcon, true, + // currentSongLengthMs - Conductor.instance.songPosition); + DiscordClient.instance.setPresence( + { + state: buildDiscordRPCState(), + details: buildDiscordRPCDetails(), + + largeImageKey: discordRPCAlbum, + smallImageKey: discordRPCIcon + }); } else { - DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC); + DiscordClient.instance.setPresence( + { + state: buildDiscordRPCState(), + details: buildDiscordRPCDetails(), + + largeImageKey: discordRPCAlbum, + smallImageKey: discordRPCIcon + }); } #end @@ -1326,16 +1355,32 @@ class PlayState extends MusicBeatSubState #end #if FEATURE_DISCORD_RPC - if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause) + if (health > Constants.HEALTH_MIN && !isGamePaused && FlxG.autoPause) { - if (Conductor.instance.songPosition > 0.0) DiscordClient.changePresence(detailsText, currentSong.song - + ' (' - + storyDifficultyText - + ')', iconRPC, true, - currentSongLengthMs - - Conductor.instance.songPosition); + if (Conductor.instance.songPosition > 0.0) + { + DiscordClient.instance.setPresence( + { + state: buildDiscordRPCState(), + details: buildDiscordRPCDetails(), + + largeImageKey: discordRPCAlbum, + smallImageKey: discordRPCIcon + }); + } else - DiscordClient.changePresence(detailsText, currentSong.song + ' (' + storyDifficultyText + ')', iconRPC); + { + DiscordClient.instance.setPresence( + { + state: buildDiscordRPCState(), + details: buildDiscordRPCDetails(), + + largeImageKey: discordRPCAlbum, + smallImageKey: discordRPCIcon + }); + // DiscordClient.changePresence(detailsText, '${currentChart.songName} ($discordRPCDifficulty)', discordRPCIcon, true, + // currentSongLengthMs - Conductor.instance.songPosition); + } } #end @@ -1352,8 +1397,17 @@ class PlayState extends MusicBeatSubState #end #if FEATURE_DISCORD_RPC - if (health > Constants.HEALTH_MIN && !paused && FlxG.autoPause) DiscordClient.changePresence(detailsPausedText, - currentSong.song + ' (' + storyDifficultyText + ')', iconRPC); + if (health > Constants.HEALTH_MIN && !isGamePaused && FlxG.autoPause) + { + DiscordClient.instance.setPresence( + { + state: buildDiscordRPCState(), + details: buildDiscordRPCDetails(), + + largeImageKey: discordRPCAlbum, + smallImageKey: discordRPCIcon + }); + } #end super.onFocusLost(); @@ -1366,6 +1420,7 @@ class PlayState extends MusicBeatSubState { funkin.modding.PolymodHandler.forceReloadAssets(); lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id); + this.remove(currentStage); LoadingState.loadPlayState(lastParams); } @@ -1650,6 +1705,11 @@ class PlayState extends MusicBeatSubState iconP2.zIndex = 850; add(iconP2); iconP2.cameras = [camHUD]; + + #if FEATURE_DISCORD_RPC + discordRPCAlbum = 'album-${currentChart.album}'; + discordRPCIcon = 'icon-${currentCharacterData.opponent}'; + #end } // @@ -1765,29 +1825,53 @@ class PlayState extends MusicBeatSubState function initDiscord():Void { #if FEATURE_DISCORD_RPC - storyDifficultyText = difficultyString(); - iconRPC = currentSong.player2; - - // To avoid having duplicate images in Discord assets - switch (iconRPC) - { - case 'senpai-angry': - iconRPC = 'senpai'; - case 'monster-christmas': - iconRPC = 'monster'; - case 'mom-car': - iconRPC = 'mom'; - } - - // String that contains the mode defined here so it isn't necessary to call changePresence for each mode - detailsText = isStoryMode ? 'Story Mode: Week $storyWeek' : 'Freeplay'; - detailsPausedText = 'Paused - $detailsText'; + // Determine the details strings once and reuse them. // Updating Discord Rich Presence. - DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC); + DiscordClient.instance.setPresence( + { + state: buildDiscordRPCState(), + details: buildDiscordRPCDetails(), + + largeImageKey: discordRPCAlbum, + smallImageKey: discordRPCIcon + }); #end } + function buildDiscordRPCDetails():String + { + if (PlayStatePlaylist.isStoryMode) + { + return 'Story Mode: ${PlayStatePlaylist.campaignTitle}'; + } + else + { + if (isChartingMode) + { + return 'Chart Editor [Playtest]'; + } + else if (isPracticeMode) + { + return 'Freeplay [Practice]'; + } + else if (isBotPlayMode) + { + return 'Freeplay [Bot Play]'; + } + else + { + return 'Freeplay'; + } + } + } + + function buildDiscordRPCState():String + { + var discordRPCDifficulty = PlayState.instance.currentDifficulty.replace('-', ' ').toTitleCase(); + return '${currentChart.songName} [${discordRPCDifficulty}]'; + } + function initPreciseInputs():Void { PreciseInputManager.instance.onInputPressed.add(onKeyPress); @@ -1976,13 +2060,21 @@ class PlayState extends MusicBeatSubState vocals.volume = 1.0; vocals.pitch = playbackRate; vocals.time = FlxG.sound.music.time; - trace('${FlxG.sound.music.time}'); - trace('${vocals.time}'); + // trace('${FlxG.sound.music.time}'); + // trace('${vocals.time}'); resyncVocals(); #if FEATURE_DISCORD_RPC // Updating Discord Rich Presence (with Time Left) - DiscordClient.changePresence(detailsText, '${currentChart.songName} ($storyDifficultyText)', iconRPC, true, currentSongLengthMs); + DiscordClient.instance.setPresence( + { + state: buildDiscordRPCState(), + details: buildDiscordRPCDetails(), + + largeImageKey: discordRPCAlbum, + smallImageKey: discordRPCIcon + }); + // DiscordClient.changePresence(detailsText, '${currentChart.songName} ($discordRPCDifficulty)', discordRPCIcon, true, currentSongLengthMs); #end if (startTimestamp > 0) @@ -2578,7 +2670,7 @@ class PlayState extends MusicBeatSubState */ function debugKeyShit():Void { - #if FEATURE_CHART_EDITOR + #if FEATURE_STAGE_EDITOR // Open the stage editor overlaying the current state. if (controls.DEBUG_STAGE) { @@ -2587,7 +2679,9 @@ class PlayState extends MusicBeatSubState persistentUpdate = false; openSubState(new StageOffsetSubState()); } + #end + #if FEATURE_CHART_EDITOR // Redirect to the chart editor playing the current song. if (controls.DEBUG_CHART) { @@ -2595,11 +2689,13 @@ class PlayState extends MusicBeatSubState persistentUpdate = false; if (isChartingMode) { + // Close the playtest substate. FlxG.sound.music?.pause(); this.close(); } else { + this.remove(currentStage); FlxG.switchState(() -> new ChartEditorState( { targetSongId: currentSong.id, @@ -2949,6 +3045,7 @@ class PlayState extends MusicBeatSubState { targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION; } + this.remove(currentStage); LoadingState.loadPlayState( { targetSong: targetSong, @@ -2966,6 +3063,7 @@ class PlayState extends MusicBeatSubState { targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION; } + this.remove(currentStage); LoadingState.loadPlayState( { targetSong: targetSong, diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index 33f3587ea..7a2a015b7 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -74,6 +74,8 @@ class ResultState extends MusicBeatSubState var playerCharacterId:Null; + var introMusicAudio:Null; + var rankBg:FunkinSprite; final cameraBG:FunkinCamera; final cameraScroll:FunkinCamera; @@ -413,7 +415,8 @@ class ResultState extends MusicBeatSubState if (Assets.exists(introMusic)) { // Play the intro music. - FunkinSound.load(introMusic, 1.0, false, true, true, () -> { + introMusicAudio = FunkinSound.load(introMusic, 1.0, false, true, true, () -> { + introMusicAudio = null; FunkinSound.playMusic(getMusicPath(playerCharacter, rank), { startingVolume: 1.0, @@ -727,9 +730,34 @@ class ResultState extends MusicBeatSubState if (controls.PAUSE) { - if (FlxG.sound.music != null) + if (introMusicAudio != null) { + @:nullSafety(Off) + introMusicAudio.onComplete = null; + + FlxTween.tween(introMusicAudio, {volume: 0}, 0.8, { + onComplete: _ -> { + if (introMusicAudio != null) { + introMusicAudio.stop(); + introMusicAudio.destroy(); + introMusicAudio = null; + } + } + }); + FlxTween.tween(introMusicAudio, {pitch: 3}, 0.1, + { + onComplete: _ -> { + FlxTween.tween(introMusicAudio, {pitch: 0.5}, 0.4); + } + }); + } + else if (FlxG.sound.music != null) { - FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8); + FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8, { + onComplete: _ -> { + FlxG.sound.music.stop(); + FlxG.sound.music.destroy(); + } + }); FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1, { onComplete: _ -> { diff --git a/source/funkin/play/character/AnimateAtlasCharacter.hx b/source/funkin/play/character/AnimateAtlasCharacter.hx index b78aed983..22af05b24 100644 --- a/source/funkin/play/character/AnimateAtlasCharacter.hx +++ b/source/funkin/play/character/AnimateAtlasCharacter.hx @@ -147,7 +147,7 @@ class AnimateAtlasCharacter extends BaseCharacter if (getAnimationData() != null && getAnimationData().looped) { - playAnimation(prefix, true, false); + playAnimation(currentAnimName, true, false); } else { diff --git a/source/funkin/play/character/BaseCharacter.hx b/source/funkin/play/character/BaseCharacter.hx index 1b5a68e96..a7fdee3fb 100644 --- a/source/funkin/play/character/BaseCharacter.hx +++ b/source/funkin/play/character/BaseCharacter.hx @@ -299,7 +299,7 @@ class BaseCharacter extends Bopper { super.onAnimationFinished(animationName); - trace('${characterId} has finished animation: ${animationName}'); + // trace('${characterId} has finished animation: ${animationName}'); if ((animationName.endsWith(Constants.ANIMATION_END_SUFFIX) && !animationName.startsWith('idle') && !animationName.startsWith('dance')) || animationName.startsWith('combo') || animationName.startsWith('drop')) @@ -317,6 +317,11 @@ class BaseCharacter extends Bopper this.cameraFocusPoint = new FlxPoint(charCenterX + _data.cameraOffsets[0], charCenterY + _data.cameraOffsets[1]); } + public function getHealthIconId():String + { + return _data?.healthIcon?.id ?? Constants.DEFAULT_HEALTH_ICON; + } + public function initHealthIcon(isOpponent:Bool):Void { if (!isOpponent) @@ -326,7 +331,7 @@ class BaseCharacter extends Bopper trace('[WARN] Player 1 health icon not found!'); return; } - PlayState.instance.iconP1.configure(_data.healthIcon); + PlayState.instance.iconP1.configure(_data?.healthIcon); PlayState.instance.iconP1.flipX = !PlayState.instance.iconP1.flipX; // BF is looking the other way. } else @@ -336,7 +341,7 @@ class BaseCharacter extends Bopper trace('[WARN] Player 2 health icon not found!'); return; } - PlayState.instance.iconP2.configure(_data.healthIcon); + PlayState.instance.iconP2.configure(_data?.healthIcon); } } @@ -396,7 +401,7 @@ class BaseCharacter extends Bopper FlxG.watch.addQuick('singTimeSec-${characterId}', singTimeSec); if (holdTimer > singTimeSec && shouldStopSinging) { - trace('holdTimer reached ${holdTimer}sec (> ${singTimeSec}), stopping sing animation'); + // trace('holdTimer reached ${holdTimer}sec (> ${singTimeSec}), stopping sing animation'); holdTimer = 0; var currentAnimation:String = getCurrentAnimation(); @@ -630,7 +635,7 @@ class BaseCharacter extends Bopper var anim:String = 'sing${dir.nameUpper}${miss ? 'miss' : ''}${suffix != '' ? '-${suffix}' : ''}'; // restart even if already playing, because the character might sing the same note twice. - trace('Playing ${anim}...'); + // trace('Playing ${anim}...'); playAnimation(anim, true); } diff --git a/source/funkin/play/notes/notestyle/NoteStyle.hx b/source/funkin/play/notes/notestyle/NoteStyle.hx index 3318e9086..dd0885751 100644 --- a/source/funkin/play/notes/notestyle/NoteStyle.hx +++ b/source/funkin/play/notes/notestyle/NoteStyle.hx @@ -34,7 +34,12 @@ class NoteStyle implements IRegistryEntry * The note style to use if this one doesn't have a certain asset. * This can be recursive, ehe. */ - final fallback:Null; + var fallback(get, never):Null; + + function get_fallback():Null { + if (_data == null || _data.fallback == null) return null; + return NoteStyleRegistry.instance.fetchEntry(_data.fallback); + } /** * @param id The ID of the JSON file to parse. @@ -43,9 +48,6 @@ class NoteStyle implements IRegistryEntry { this.id = id; _data = _fetchData(id); - - var fallbackID = _data.fallback; - if (fallbackID != null) this.fallback = NoteStyleRegistry.instance.fetchEntry(fallbackID); } /** @@ -140,7 +142,7 @@ class NoteStyle implements IRegistryEntry if (raw) { var rawPath:Null = _data?.assets?.note?.assetPath; - if (rawPath == null && fallback != null) return fallback.getNoteAssetPath(true); + if (rawPath == null) return fallback?.getNoteAssetPath(true); return rawPath; } @@ -178,12 +180,13 @@ class NoteStyle implements IRegistryEntry public function isNoteAnimated():Bool { - return _data.assets?.note?.animated ?? false; + // LOL is double ?? bad practice? + return _data.assets?.note?.animated ?? fallback?.isNoteAnimated() ?? false; } public function getNoteScale():Float { - return _data.assets?.note?.scale ?? 1.0; + return _data.assets?.note?.scale ?? fallback?.getNoteScale() ?? 1.0; } function fetchNoteAnimationData(dir:NoteDirection):Null @@ -196,16 +199,15 @@ class NoteStyle implements IRegistryEntry case RIGHT: _data.assets?.note?.data?.right?.toNamed(); }; - return (result == null && fallback != null) ? fallback.fetchNoteAnimationData(dir) : result; + return result ?? fallback?.fetchNoteAnimationData(dir); } public function getHoldNoteAssetPath(raw:Bool = false):Null { if (raw) { - // TODO: figure out why ?. didn't work here - var rawPath:Null = (_data?.assets?.holdNote == null) ? null : _data?.assets?.holdNote?.assetPath; - return (rawPath == null && fallback != null) ? fallback.getHoldNoteAssetPath(true) : rawPath; + var rawPath:Null = _data?.assets?.holdNote?.assetPath; + return rawPath ?? fallback?.getHoldNoteAssetPath(true); } // library:path @@ -217,23 +219,17 @@ class NoteStyle implements IRegistryEntry public function isHoldNotePixel():Bool { - var data = _data?.assets?.holdNote; - if (data == null && fallback != null) return fallback.isHoldNotePixel(); - return data?.isPixel ?? false; + return _data?.assets?.holdNote?.isPixel ?? fallback?.isHoldNotePixel() ?? false; } public function fetchHoldNoteScale():Float { - var data = _data?.assets?.holdNote; - if (data == null && fallback != null) return fallback.fetchHoldNoteScale(); - return data?.scale ?? 1.0; + return _data?.assets?.holdNote?.scale ?? fallback?.fetchHoldNoteScale() ?? 1.0; } public function getHoldNoteOffsets():Array { - var data = _data?.assets?.holdNote; - if (data == null && fallback != null) return fallback.getHoldNoteOffsets(); - return data?.offsets ?? [0.0, 0.0]; + return _data?.assets?.holdNote?.offsets ?? fallback?.getHoldNoteOffsets() ?? [0.0, 0.0]; } public function applyStrumlineFrames(target:StrumlineNote):Void @@ -258,9 +254,7 @@ class NoteStyle implements IRegistryEntry { if (raw) { - var rawPath:Null = _data?.assets?.noteStrumline?.assetPath; - if (rawPath == null && fallback != null) return fallback.getStrumlineAssetPath(true); - return rawPath; + return _data?.assets?.noteStrumline?.assetPath ?? fallback?.getStrumlineAssetPath(true); } // library:path @@ -282,11 +276,19 @@ class NoteStyle implements IRegistryEntry FlxAnimationUtil.addAtlasAnimations(target, getStrumlineAnimationData(dir)); } + /** + * Fetch the animation data for the strumline. + * NOTE: This function only queries the fallback note style if all the animations are missing for a given direction. + * + * @param dir The direction to fetch the animation data for. + * @return The animation data for the strumline in that direction. + */ function getStrumlineAnimationData(dir:NoteDirection):Array { var result:Array> = switch (dir) { - case NoteDirection.LEFT: [ + case NoteDirection.LEFT: + [ _data.assets.noteStrumline?.data?.leftStatic?.toNamed('static'), _data.assets.noteStrumline?.data?.leftPress?.toNamed('press'), _data.assets.noteStrumline?.data?.leftConfirm?.toNamed('confirm'), @@ -313,14 +315,17 @@ class NoteStyle implements IRegistryEntry default: []; }; - return thx.Arrays.filterNull(result); + // New variable so we can change the type. + var filteredResult:Array = thx.Arrays.filterNull(result); + + if (filteredResult.length == 0) return fallback?.getStrumlineAnimationData(dir) ?? []; + + return filteredResult; } public function getStrumlineOffsets():Array { - var data = _data?.assets?.noteStrumline; - if (data == null && fallback != null) return fallback.getStrumlineOffsets(); - return data?.offsets ?? [0.0, 0.0]; + return _data?.assets?.noteStrumline?.offsets ?? fallback?.getStrumlineOffsets() ?? [0.0, 0.0]; } public function applyStrumlineOffsets(target:StrumlineNote):Void @@ -332,21 +337,17 @@ class NoteStyle implements IRegistryEntry public function getStrumlineScale():Float { - return _data?.assets?.noteStrumline?.scale ?? 1.0; + return _data?.assets?.noteStrumline?.scale ?? fallback?.getStrumlineScale() ?? 1.0; } public function isNoteSplashEnabled():Bool { - var data = _data?.assets?.noteSplash?.data; - if (data == null) return fallback?.isNoteSplashEnabled() ?? false; - return data.enabled ?? false; + return _data?.assets?.noteSplash?.data?.enabled ?? fallback?.isNoteSplashEnabled() ?? false; } public function isHoldNoteCoverEnabled():Bool { - var data = _data?.assets?.holdNoteCover?.data; - if (data == null) return fallback?.isHoldNoteCoverEnabled() ?? false; - return data.enabled ?? false; + return _data?.assets?.holdNoteCover?.data?.enabled ?? fallback?.isHoldNoteCoverEnabled() ?? false; } /** @@ -457,20 +458,20 @@ class NoteStyle implements IRegistryEntry { case THREE: var result = _data.assets.countdownThree?.isPixel; - if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step); - return result ?? false; + if (result == null) result = fallback?.isCountdownSpritePixel(step) ?? false; + return result; case TWO: var result = _data.assets.countdownTwo?.isPixel; - if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step); - return result ?? false; + if (result == null) result = fallback?.isCountdownSpritePixel(step) ?? false; + return result; case ONE: var result = _data.assets.countdownOne?.isPixel; - if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step); - return result ?? false; + if (result == null) result = fallback?.isCountdownSpritePixel(step) ?? false; + return result; case GO: var result = _data.assets.countdownGo?.isPixel; - if (result == null && fallback != null) result = fallback.isCountdownSpritePixel(step); - return result ?? false; + if (result == null) result = fallback?.isCountdownSpritePixel(step) ?? false; + return result; default: return false; } @@ -482,20 +483,20 @@ class NoteStyle implements IRegistryEntry { case THREE: var result = _data.assets.countdownThree?.offsets; - if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step); - return result ?? [0, 0]; + if (result == null) result = fallback?.getCountdownSpriteOffsets(step) ?? [0, 0]; + return result; case TWO: var result = _data.assets.countdownTwo?.offsets; - if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step); - return result ?? [0, 0]; + if (result == null) result = fallback?.getCountdownSpriteOffsets(step) ?? [0, 0]; + return result; case ONE: var result = _data.assets.countdownOne?.offsets; - if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step); - return result ?? [0, 0]; + if (result == null) result = fallback?.getCountdownSpriteOffsets(step) ?? [0, 0]; + return result; case GO: var result = _data.assets.countdownGo?.offsets; - if (result == null && fallback != null) result = fallback.getCountdownSpriteOffsets(step); - return result ?? [0, 0]; + if (result == null) result = fallback?.getCountdownSpriteOffsets(step) ?? [0, 0]; + return result; default: return [0, 0]; } @@ -520,7 +521,7 @@ class NoteStyle implements IRegistryEntry null; } - return (rawPath == null && fallback != null) ? fallback.getCountdownSoundPath(step, true) : rawPath; + return (rawPath == null) ? fallback?.getCountdownSoundPath(step, true) : rawPath; } // library:path @@ -584,20 +585,20 @@ class NoteStyle implements IRegistryEntry { case "sick": var result = _data.assets.judgementSick?.isPixel; - if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating); - return result ?? false; + if (result == null) result = fallback?.isJudgementSpritePixel(rating) ?? false; + return result; case "good": var result = _data.assets.judgementGood?.isPixel; - if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating); - return result ?? false; + if (result == null) result = fallback?.isJudgementSpritePixel(rating) ?? false; + return result; case "bad": var result = _data.assets.judgementBad?.isPixel; - if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating); - return result ?? false; + if (result == null) result = fallback?.isJudgementSpritePixel(rating) ?? false; + return result; case "shit": var result = _data.assets.judgementShit?.isPixel; - if (result == null && fallback != null) result = fallback.isJudgementSpritePixel(rating); - return result ?? false; + if (result == null) result = fallback?.isJudgementSpritePixel(rating) ?? false; + return result; default: return false; } @@ -635,20 +636,20 @@ class NoteStyle implements IRegistryEntry { case "sick": var result = _data.assets.judgementSick?.offsets; - if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating); - return result ?? [0, 0]; + if (result == null) result = fallback?.getJudgementSpriteOffsets(rating) ?? [0, 0]; + return result; case "good": var result = _data.assets.judgementGood?.offsets; - if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating); - return result ?? [0, 0]; + if (result == null) result = fallback?.getJudgementSpriteOffsets(rating) ?? [0, 0]; + return result; case "bad": var result = _data.assets.judgementBad?.offsets; - if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating); - return result ?? [0, 0]; + if (result == null) result = fallback?.getJudgementSpriteOffsets(rating) ?? [0, 0]; + return result; case "shit": var result = _data.assets.judgementShit?.offsets; - if (result == null && fallback != null) result = fallback.getJudgementSpriteOffsets(rating); - return result ?? [0, 0]; + if (result == null) result = fallback?.getJudgementSpriteOffsets(rating) ?? [0, 0]; + return result; default: return [0, 0]; } @@ -749,44 +750,44 @@ class NoteStyle implements IRegistryEntry { case 0: var result = _data.assets.comboNumber0?.isPixel; - if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit); - return result ?? false; + if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false; + return result; case 1: var result = _data.assets.comboNumber1?.isPixel; - if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit); - return result ?? false; + if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false; + return result; case 2: var result = _data.assets.comboNumber2?.isPixel; - if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit); - return result ?? false; + if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false; + return result; case 3: var result = _data.assets.comboNumber3?.isPixel; - if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit); - return result ?? false; + if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false; + return result; case 4: var result = _data.assets.comboNumber4?.isPixel; - if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit); - return result ?? false; + if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false; + return result; case 5: var result = _data.assets.comboNumber5?.isPixel; - if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit); - return result ?? false; + if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false; + return result; case 6: var result = _data.assets.comboNumber6?.isPixel; - if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit); - return result ?? false; + if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false; + return result; case 7: var result = _data.assets.comboNumber7?.isPixel; - if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit); - return result ?? false; + if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false; + return result; case 8: var result = _data.assets.comboNumber8?.isPixel; - if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit); - return result ?? false; + if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false; + return result; case 9: var result = _data.assets.comboNumber9?.isPixel; - if (result == null && fallback != null) result = fallback.isComboNumSpritePixel(digit); - return result ?? false; + if (result == null) result = fallback?.isComboNumSpritePixel(digit) ?? false; + return result; default: return false; } @@ -836,44 +837,44 @@ class NoteStyle implements IRegistryEntry { case 0: var result = _data.assets.comboNumber0?.offsets; - if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit); - return result ?? [0, 0]; + if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0]; + return result; case 1: var result = _data.assets.comboNumber1?.offsets; - if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit); - return result ?? [0, 0]; + if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0]; + return result; case 2: var result = _data.assets.comboNumber2?.offsets; - if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit); - return result ?? [0, 0]; + if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0]; + return result; case 3: var result = _data.assets.comboNumber3?.offsets; - if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit); - return result ?? [0, 0]; + if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0]; + return result; case 4: var result = _data.assets.comboNumber4?.offsets; - if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit); - return result ?? [0, 0]; + if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0]; + return result; case 5: var result = _data.assets.comboNumber5?.offsets; - if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit); - return result ?? [0, 0]; + if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0]; + return result; case 6: var result = _data.assets.comboNumber6?.offsets; - if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit); - return result ?? [0, 0]; + if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0]; + return result; case 7: var result = _data.assets.comboNumber7?.offsets; - if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit); - return result ?? [0, 0]; + if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0]; + return result; case 8: var result = _data.assets.comboNumber8?.offsets; - if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit); - return result ?? [0, 0]; + if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0]; + return result; case 9: var result = _data.assets.comboNumber9?.offsets; - if (result == null && fallback != null) result = fallback.getComboNumSpriteOffsets(digit); - return result ?? [0, 0]; + if (result == null) result = fallback?.getComboNumSpriteOffsets(digit) ?? [0, 0]; + return result; default: return [0, 0]; } diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index 9023b872e..a6449bc10 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -156,6 +156,11 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry = fetchVariationMetadata(id, vari); if (variMeta != null) { @@ -407,7 +412,6 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry { - if (char == null) return variations; + if (char == null) + { + var result = variations; + result.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_VARIATION_LIST)); + return result; + } var result = []; trace('Evaluating variations for ${this.id} ${char.id}: ${this.variations}'); @@ -445,6 +454,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry = difficulties.keys() - .array() - .map(function(diffId:String):Null { - var difficulty:Null = difficulties.get(diffId); - if (difficulty == null) return null; - if (variationIds.length > 0 && !variationIds.contains(difficulty.variation)) return null; - return difficulty.difficulty; - }) + var diffFiltered:Array = variationIds.map(function(variationId:String):Array { + var metadata = _metadata.get(variationId); + return metadata?.playData?.difficulties ?? []; + }) + .flatten() .filterNull() .distinct(); @@ -489,11 +493,15 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry, ?showLocked:Bool, ?showHidden:Bool):Array { var result = []; @@ -509,6 +517,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry = SongRegistry.instance.parseEntryMetadataWithMigration(id, vari, version); return meta; } + + static final VARIATION_REGEX = ~/^[a-z][a-z0-9]+$/; + + /** + * Validate that the variation ID is valid. + * Auto-accept if it's one of the base game default variations. + * Reject if the ID starts with a number, or contains invalid characters. + */ + static function validateVariationId(variation:String):Bool { + if (Constants.DEFAULT_VARIATION_LIST.contains(variation)) return true; + + return VARIATION_REGEX.match(variation); + } } class SongDifficulty diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index 15648249c..62295a717 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -258,6 +258,9 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements propSprite.zIndex = dataProp.zIndex; + propSprite.flipX = dataProp.flipX; + propSprite.flipY = dataProp.flipY; + switch (dataProp.animType) { case 'packer': diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 0d86c628b..dc92f7f1a 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -603,11 +603,14 @@ class Save return; } + var newCompletion = (newScoreData.tallies.sick + newScoreData.tallies.good) / newScoreData.tallies.totalNotes; + var previousCompletion = (previousScoreData.tallies.sick + previousScoreData.tallies.good) / previousScoreData.tallies.totalNotes; + // Set the high score and the high rank separately. var newScore:SaveScoreData = { score: (previousScoreData.score > newScoreData.score) ? previousScoreData.score : newScoreData.score, - tallies: (previousRank > newRank) ? previousScoreData.tallies : newScoreData.tallies + tallies: (previousRank > newRank || previousCompletion > newCompletion) ? previousScoreData.tallies : newScoreData.tallies }; song.set(difficultyId, newScore); diff --git a/source/funkin/ui/PixelatedIcon.hx b/source/funkin/ui/PixelatedIcon.hx index 4252c9695..71d5d74ab 100644 --- a/source/funkin/ui/PixelatedIcon.hx +++ b/source/funkin/ui/PixelatedIcon.hx @@ -50,8 +50,13 @@ class PixelatedIcon extends FlxFilteredSprite if (!openfl.utils.Assets.exists(Paths.image(charPath))) { trace('[WARN] Character ${char} has no freeplay icon.'); + this.visible = false; return; } + else + { + this.visible = true; + } var isAnimated = openfl.utils.Assets.exists(Paths.file('images/$charPath.xml')); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 7f8bfdca7..ba2f24a73 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -2274,8 +2274,25 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState this.openBackupAvailableDialog(welcomeDialog); } } + + #if FEATURE_DISCORD_RPC + updateDiscordRPC(); + #end } + #if FEATURE_DISCORD_RPC + function updateDiscordRPC():Void + { + funkin.api.discord.DiscordClient.instance.setPresence( + { + // TODO: Make this display the song name and update when it changes. + // state: '${currentSongName} [${selectedDifficulty}]', + state: null, + details: 'Chart Editor [Charting]' + }); + } + #end + function setupWelcomeMusic() { this.welcomeMusic.loadEmbedded(Paths.music('chartEditorLoop/chartEditorLoop')); diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index aef40ef91..7d40e8516 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -54,6 +54,9 @@ import funkin.util.SortUtil; import openfl.display.BlendMode; import funkin.data.freeplay.style.FreeplayStyleRegistry; import funkin.data.song.SongData.SongMusicData; +#if FEATURE_DISCORD_RPC +import funkin.api.discord.DiscordClient; +#end /** * Parameters used to initialize the FreeplayState. @@ -144,11 +147,37 @@ class FreeplayState extends MusicBeatSubState var songs:Array> = []; + // List of available difficulties for the current song, without `-variation` at the end (no duplicates or nulls). var diffIdsCurrent:Array = []; + // List of available difficulties for the total song list, without `-variation` at the end (no duplicates or nulls). var diffIdsTotal:Array = []; + // List of available difficulties for the current song, with `-variation` at the end (no duplicates or nulls). + var suffixedDiffIdsCurrent:Array = []; + // List of available difficulties for the total song list, with `-variation` at the end (no duplicates or nulls). + var suffixedDiffIdsTotal:Array = []; var curSelected:Int = 0; - var currentDifficulty:String = Constants.DEFAULT_DIFFICULTY; + var currentSuffixedDifficulty:String = Constants.DEFAULT_DIFFICULTY; + var currentUnsuffixedDifficulty(get, never):String; + + function get_currentUnsuffixedDifficulty():String + { + if (Constants.DEFAULT_DIFFICULTY_LIST_FULL.contains(currentSuffixedDifficulty)) return currentSuffixedDifficulty; + + // Else, we need to strip the suffix. + return currentSuffixedDifficulty.substring(0, currentSuffixedDifficulty.lastIndexOf('-')); + } + + var currentVariation(get, never):String; + + function get_currentVariation():String + { + if (Constants.DEFAULT_DIFFICULTY_LIST.contains(currentSuffixedDifficulty)) return Constants.DEFAULT_VARIATION; + if (Constants.DEFAULT_DIFFICULTY_LIST_ERECT.contains(currentSuffixedDifficulty)) return 'erect'; + + // Else, we need to isolate the suffix. + return currentSuffixedDifficulty.substring(currentSuffixedDifficulty.lastIndexOf('-') + 1, currentSuffixedDifficulty.length); + } public var fp:FreeplayScore; @@ -312,7 +341,7 @@ class FreeplayState extends MusicBeatSubState #if FEATURE_DISCORD_RPC // Updating Discord Rich Presence - DiscordClient.changePresence('In the Menus', null); + DiscordClient.instance.setPresence({state: 'In the Menus', details: null}); #end var isDebug:Bool = false; @@ -356,11 +385,15 @@ class FreeplayState extends MusicBeatSubState trace('Available Difficulties: $availableDifficultiesForSong'); if (availableDifficultiesForSong.length == 0) continue; - songs.push(new FreeplaySongData(levelId, songId, song, displayedVariations)); + songs.push(new FreeplaySongData(levelId, songId, song, currentCharacter, displayedVariations)); for (difficulty in unsuffixedDifficulties) { diffIdsTotal.pushUnique(difficulty); } + for (difficulty in availableDifficultiesForSong) + { + suffixedDiffIdsTotal.pushUnique(difficulty); + } } } @@ -453,7 +486,7 @@ class FreeplayState extends MusicBeatSubState wait: 0.1 }); - for (diffId in diffIdsTotal) + for (diffId in suffixedDiffIdsTotal) { var diffSprite:DifficultySprite = new DifficultySprite(diffId); diffSprite.difficultyId = diffId; @@ -467,7 +500,7 @@ class FreeplayState extends MusicBeatSubState for (diffSprite in grpDifficulties.group.members) { if (diffSprite == null) continue; - if (diffSprite.difficultyId == currentDifficulty) diffSprite.visible = true; + if (diffSprite.difficultyId == currentSuffixedDifficulty) diffSprite.visible = true; } albumRoll.albumId = null; @@ -743,17 +776,14 @@ class FreeplayState extends MusicBeatSubState { var tempSongs:Array> = songs; - // Remember just the difficulty because it's important for song sorting. - currentDifficulty = rememberedDifficulty; - if (filterStuff != null) tempSongs = sortSongs(tempSongs, filterStuff); // Filter further by current selected difficulty. - if (currentDifficulty != null) + if (currentSuffixedDifficulty != null) { tempSongs = tempSongs.filter(song -> { if (song == null) return true; // Random - return song.songDifficulties.contains(currentDifficulty); + return song.suffixedSongDifficulties.contains(currentSuffixedDifficulty); }); } @@ -1344,7 +1374,6 @@ class FreeplayState extends MusicBeatSubState var dyTouch:Float = 0; var velTouch:Float = 0; - var veloctiyLoopShit:Float = 0; var touchTimer:Float = 0; var initTouchPos:FlxPoint = new FlxPoint(); @@ -1756,16 +1785,18 @@ class FreeplayState extends MusicBeatSubState { touchTimer = 0; - var currentDifficultyIndex:Int = diffIdsCurrent.indexOf(currentDifficulty); + var currentDifficultyIndex:Int = suffixedDiffIdsCurrent.indexOf(currentSuffixedDifficulty); - if (currentDifficultyIndex == -1) currentDifficultyIndex = diffIdsCurrent.indexOf(Constants.DEFAULT_DIFFICULTY); + if (currentDifficultyIndex == -1) currentDifficultyIndex = suffixedDiffIdsCurrent.indexOf(Constants.DEFAULT_DIFFICULTY); currentDifficultyIndex += change; - if (currentDifficultyIndex < 0) currentDifficultyIndex = diffIdsCurrent.length - 1; - if (currentDifficultyIndex >= diffIdsCurrent.length) currentDifficultyIndex = 0; + if (currentDifficultyIndex < 0) currentDifficultyIndex = suffixedDiffIdsCurrent.length - 1; + if (currentDifficultyIndex >= suffixedDiffIdsCurrent.length) currentDifficultyIndex = 0; - currentDifficulty = diffIdsCurrent[currentDifficultyIndex]; + currentSuffixedDifficulty = suffixedDiffIdsCurrent[currentDifficultyIndex]; + + trace('Switching to difficulty: ${currentSuffixedDifficulty}'); var daSong:Null = grpCapsules.members[curSelected].songData; if (daSong != null) @@ -1776,22 +1807,20 @@ class FreeplayState extends MusicBeatSubState FlxG.log.warn('WARN: could not find song with id (${daSong.songId})'); return; } - var targetVariation:String = targetSong.getFirstValidVariation(currentDifficulty, currentCharacter) ?? ''; - // TODO: This line of code makes me sad, but you can't really fix it without a breaking migration. - var suffixedDifficulty = (targetVariation != Constants.DEFAULT_VARIATION - && targetVariation != 'erect') ? '$currentDifficulty-${targetVariation}' : currentDifficulty; + var suffixedDifficulty = suffixedDiffIdsCurrent[currentDifficultyIndex]; var songScore:Null = Save.instance.getSongScore(daSong.songId, suffixedDifficulty); trace(songScore); intendedScore = songScore?.score ?? 0; intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes); rememberedDifficulty = suffixedDifficulty; + currentSuffixedDifficulty = suffixedDifficulty; } else { intendedScore = 0; intendedCompletion = 0.0; - rememberedDifficulty = currentDifficulty; + rememberedDifficulty = currentSuffixedDifficulty; } if (intendedCompletion == Math.POSITIVE_INFINITY || intendedCompletion == Math.NEGATIVE_INFINITY || Math.isNaN(intendedCompletion)) @@ -1806,7 +1835,7 @@ class FreeplayState extends MusicBeatSubState for (diffSprite in grpDifficulties.group.members) { if (diffSprite == null) continue; - if (diffSprite.difficultyId == currentDifficulty) + if (diffSprite.difficultyId == currentSuffixedDifficulty) { if (change != 0) { @@ -1833,7 +1862,9 @@ class FreeplayState extends MusicBeatSubState if (songCapsule == null) continue; if (songCapsule.songData != null) { - songCapsule.songData.currentDifficulty = currentDifficulty; + songCapsule.songData.currentVariation = currentVariation; + songCapsule.songData.currentUnsuffixedDifficulty = currentUnsuffixedDifficulty; + songCapsule.songData.currentSuffixedDifficulty = currentSuffixedDifficulty; songCapsule.init(null, null, songCapsule.songData); songCapsule.checkClip(); } @@ -1921,8 +1952,9 @@ class FreeplayState extends MusicBeatSubState return; } var targetSong:Song = targetSongNullable; - var targetDifficultyId:String = currentDifficulty; - var targetVariation:Null = targetSong.getFirstValidVariation(targetDifficultyId, currentCharacter); + var targetDifficultyId:String = currentUnsuffixedDifficulty; + var targetVariation:Null = currentVariation; + trace('target song: ${targetSongId} (${targetVariation})'); var targetLevelId:Null = cap?.songData?.levelId; PlayStatePlaylist.campaignId = targetLevelId ?? null; @@ -2002,8 +2034,8 @@ class FreeplayState extends MusicBeatSubState return; } var targetSong:Song = targetSongNullable; - var targetDifficultyId:String = currentDifficulty; - var targetVariation:Null = targetSong.getFirstValidVariation(targetDifficultyId, currentCharacter); + var targetDifficultyId:String = currentUnsuffixedDifficulty; + var targetVariation:Null = currentVariation; var targetLevelId:Null = cap?.songData?.levelId; PlayStatePlaylist.campaignId = targetLevelId ?? null; @@ -2069,7 +2101,7 @@ class FreeplayState extends MusicBeatSubState if (rememberedDifficulty != null) { - currentDifficulty = rememberedDifficulty; + currentSuffixedDifficulty = rememberedDifficulty; } } @@ -2087,10 +2119,11 @@ class FreeplayState extends MusicBeatSubState var daSongCapsule:SongMenuItem = grpCapsules.members[curSelected]; if (daSongCapsule.songData != null) { - var songScore:Null = Save.instance.getSongScore(daSongCapsule.songData.songId, currentDifficulty); + var songScore:Null = Save.instance.getSongScore(daSongCapsule.songData.songId, currentSuffixedDifficulty); intendedScore = songScore?.score ?? 0; intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes); diffIdsCurrent = daSongCapsule.songData.songDifficulties; + suffixedDiffIdsCurrent = daSongCapsule.songData.suffixedSongDifficulties; rememberedSongId = daSongCapsule.songData.songId; changeDiff(); } @@ -2099,6 +2132,7 @@ class FreeplayState extends MusicBeatSubState intendedScore = 0; intendedCompletion = 0.0; diffIdsCurrent = diffIdsTotal; + suffixedDiffIdsCurrent = suffixedDiffIdsTotal; rememberedSongId = null; rememberedDifficulty = Constants.DEFAULT_DIFFICULTY; albumRoll.albumId = null; @@ -2143,17 +2177,18 @@ class FreeplayState extends MusicBeatSubState if (previewSongId == null) return; var previewSong:Null = SongRegistry.instance.fetchEntry(previewSongId); - var currentVariation = previewSong?.getVariationsByCharacter(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST; - var songDifficulty:Null = previewSong?.getDifficulty(currentDifficulty, - previewSong?.getVariationsByCharacter(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST); + if (previewSong == null) return; + // var currentVariation = previewSong.getVariationsByCharacter(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST; + var targetDifficultyId:String = currentUnsuffixedDifficulty; + var targetVariation:Null = currentVariation; + var songDifficulty:Null = previewSong.getDifficulty(targetDifficultyId, targetVariation ?? Constants.DEFAULT_VARIATION); - var baseInstrumentalId:String = previewSong?.getBaseInstrumentalId(currentDifficulty, songDifficulty?.variation ?? Constants.DEFAULT_VARIATION) ?? ''; - var altInstrumentalIds:Array = previewSong?.listAltInstrumentalIds(currentDifficulty, + var baseInstrumentalId:String = previewSong.getBaseInstrumentalId(targetDifficultyId, songDifficulty?.variation ?? Constants.DEFAULT_VARIATION) ?? ''; + var altInstrumentalIds:Array = previewSong.listAltInstrumentalIds(targetDifficultyId, songDifficulty?.variation ?? Constants.DEFAULT_VARIATION) ?? []; var instSuffix:String = baseInstrumentalId; - // TODO: Make this a UI element. #if FEATURE_DEBUG_FUNCTIONS if (altInstrumentalIds.length > 0 && FlxG.keys.pressed.CONTROL) { @@ -2312,6 +2347,7 @@ class FreeplaySongData public var songId(default, null):String = ''; public var songDifficulties(default, null):Array = []; + public var suffixedSongDifficulties(default, null):Array = []; public var songName(default, null):String = ''; public var songCharacter(default, null):String = ''; @@ -2319,22 +2355,25 @@ class FreeplaySongData public var difficultyRating(default, null):Int = 0; public var albumId(default, null):Null = null; - public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY; + public var currentCharacter:PlayableCharacter; + public var currentVariation:String = Constants.DEFAULT_VARIATION; + public var currentSuffixedDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY; + public var currentUnsuffixedDifficulty:String = Constants.DEFAULT_DIFFICULTY; public var scoringRank:Null = null; var displayedVariations:Array = [Constants.DEFAULT_VARIATION]; - function set_currentDifficulty(value:String):String + function set_currentSuffixedDifficulty(value:String):String { - if (currentDifficulty == value) return value; + if (currentSuffixedDifficulty == value) return value; - currentDifficulty = value; + currentSuffixedDifficulty = value; updateValues(displayedVariations); return value; } - public function new(levelId:String, songId:String, song:Song, ?displayedVariations:Array) + public function new(levelId:String, songId:String, song:Song, currentCharacter:PlayableCharacter, ?displayedVariations:Array) { this.levelId = levelId; this.songId = songId; @@ -2342,6 +2381,7 @@ class FreeplaySongData this.isFav = Save.instance.isSongFavorited(songId); + this.currentCharacter = currentCharacter; if (displayedVariations != null) this.displayedVariations = displayedVariations; updateValues(displayedVariations); @@ -2368,15 +2408,18 @@ class FreeplaySongData function updateValues(variations:Array):Void { this.songDifficulties = song.listDifficulties(null, variations, false, false); - if (!this.songDifficulties.contains(currentDifficulty)) + this.suffixedSongDifficulties = song.listSuffixedDifficulties(variations, false, false); + if (!this.songDifficulties.contains(currentUnsuffixedDifficulty)) { - currentDifficulty = Constants.DEFAULT_DIFFICULTY; + currentSuffixedDifficulty = Constants.DEFAULT_DIFFICULTY; // This method gets called again by the setter-method // or the difficulty didn't change, so there's no need to continue. return; } - var songDifficulty:SongDifficulty = song.getDifficulty(currentDifficulty, null, variations); + var targetVariation:Null = currentVariation; + + var songDifficulty:SongDifficulty = song.getDifficulty(currentUnsuffixedDifficulty, targetVariation); if (songDifficulty == null) return; this.songStartingBpm = songDifficulty.getStartingBPM(); this.songName = songDifficulty.songName; @@ -2392,10 +2435,7 @@ class FreeplaySongData this.albumId = songDifficulty.album; } - // TODO: This line of code makes me sad, but you can't really fix it without a breaking migration. - // `easy`, `erect`, `normal-pico`, etc. - var suffixedDifficulty = (songDifficulty.variation != Constants.DEFAULT_VARIATION - && songDifficulty.variation != 'erect') ? '$currentDifficulty-${songDifficulty.variation}' : currentDifficulty; + var suffixedDifficulty = currentSuffixedDifficulty; this.scoringRank = Save.instance.getSongRank(songId, suffixedDifficulty); diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx index 11ca44d54..864fa2d1d 100644 --- a/source/funkin/ui/freeplay/SongMenuItem.hx +++ b/source/funkin/ui/freeplay/SongMenuItem.hx @@ -678,7 +678,7 @@ class SongMenuItem extends FlxSpriteGroup public function confirm():Void { if (songText != null) songText.flickerText(); - if (pixelIcon != null) + if (pixelIcon != null && pixelIcon.visible) { pixelIcon.animation.play('confirm'); } diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index 8ba85fbba..685626cba 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -28,7 +28,7 @@ import funkin.ui.story.StoryMenuState; import funkin.ui.Prompt; import funkin.util.WindowUtil; #if FEATURE_DISCORD_RPC -import Discord.DiscordClient; +import funkin.api.discord.DiscordClient; #end #if newgrounds import funkin.ui.NgPrompt; @@ -55,8 +55,7 @@ class MainMenuState extends MusicBeatState override function create():Void { #if FEATURE_DISCORD_RPC - // Updating Discord Rich Presence - DiscordClient.changePresence("In the Menus", null); + DiscordClient.instance.setPresence({state: "In the Menus", details: null}); #end FlxG.cameras.reset(new FunkinCamera('mainMenu')); @@ -362,6 +361,7 @@ class MainMenuState extends MusicBeatState // Ctrl+Alt+Shift+R = Score/Rank conflict test // Ctrl+Alt+Shift+N = Mark all characters as not seen // Ctrl+Alt+Shift+E = Dump save data + // Ctrl+Alt+Shift+L = Force crash and create a log dump if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.P) { diff --git a/source/funkin/ui/options/ControlsMenu.hx b/source/funkin/ui/options/ControlsMenu.hx index 1f40a8455..3d68485a5 100644 --- a/source/funkin/ui/options/ControlsMenu.hx +++ b/source/funkin/ui/options/ControlsMenu.hx @@ -28,10 +28,14 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page [NOTE_UP, NOTE_DOWN, NOTE_LEFT, NOTE_RIGHT], [UI_UP, UI_DOWN, UI_LEFT, UI_RIGHT, ACCEPT, BACK], [CUTSCENE_ADVANCE], - [FREEPLAY_FAVORITE, FREEPLAY_LEFT, FREEPLAY_RIGHT], - [WINDOW_FULLSCREEN, WINDOW_SCREENSHOT], + [FREEPLAY_FAVORITE, FREEPLAY_LEFT, FREEPLAY_RIGHT, FREEPLAY_CHAR_SELECT], + [WINDOW_FULLSCREEN, #if FEATURE_SCREENSHOTS WINDOW_SCREENSHOT, #end], [VOLUME_UP, VOLUME_DOWN, VOLUME_MUTE], - [DEBUG_MENU, DEBUG_CHART] + [ + DEBUG_MENU, + #if FEATURE_CHART_EDITOR DEBUG_CHART, #end + #if FEATURE_STAGE_EDITOR DEBUG_STAGE, #end + ] ]; var itemGroups:Array> = [for (i in 0...controlGroups.length) []]; @@ -137,7 +141,8 @@ class ControlsMenu extends funkin.ui.options.OptionsState.Page if (currentHeader != null && name.indexOf(currentHeader) == 0) name = name.substr(currentHeader.length); - var label = labels.add(new AtlasText(100, y, name, AtlasFont.BOLD)); + var formatName = name.replace('_', ' '); + var label = labels.add(new AtlasText(100, y, formatName, AtlasFont.BOLD)); label.alpha = 0.6; for (i in 0...COLUMNS) createItem(label.x + 550 + i * 400, y, control, i); diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index c0ed2712a..09af08b22 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -23,6 +23,10 @@ import funkin.ui.MusicBeatState; import funkin.ui.transition.LoadingState; import funkin.ui.transition.StickerSubState; import funkin.util.MathUtil; +import openfl.utils.Assets; +#if FEATURE_DISCORD_RPC +import funkin.api.discord.DiscordClient; +#end class StoryMenuState extends MusicBeatState { @@ -217,7 +221,7 @@ class StoryMenuState extends MusicBeatState #if FEATURE_DISCORD_RPC // Updating Discord Rich Presence - DiscordClient.changePresence('In the Menus', null); + DiscordClient.instance.setPresence({state: 'In the Menus', details: null}); #end } diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index 10e7dfaae..cdd1e3916 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -485,7 +485,13 @@ class TitleState extends MusicBeatState case 13: addMoreText('Friday'); case 14: - addMoreText('Night'); + // easter egg for when the game is trending with the wrong spelling + // the random intro text would be "trending--only on x" + + if (curWacky[0] == "trending") + addMoreText('Nigth'); + else + addMoreText('Night'); case 15: addMoreText('Funkin'); case 16: diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index cf58d191a..d5bc987e8 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -78,7 +78,12 @@ class Constants /** * Link to download the game on Itch.io. */ - public static final URL_ITCH:String = 'https://ninja-muffin24.itch.io/funkin/purchase'; + public static final URL_ITCH:String = 'https://ninja-muffin24.itch.io/funkin'; + + /** + * Link to play the game on Newgrounds. + */ + public static final URL_NEWGROUNDS:String = 'https://www.newgrounds.com/portal/view/770371'; /** * Link to the game's page on Kickstarter. @@ -186,6 +191,11 @@ class Constants */ public static final DEFAULT_DIFFICULTY_LIST:Array = ['easy', 'normal', 'hard']; + /** + * Default list of difficulties for Erect mode. + */ + public static final DEFAULT_DIFFICULTY_LIST_ERECT:Array = ['erect', 'nightmare']; + /** * List of all difficulties used by the base game. * Includes Erect and Nightmare. diff --git a/source/funkin/util/plugins/ForceCrashPlugin.hx b/source/funkin/util/plugins/ForceCrashPlugin.hx index e8094eb3c..6f402c4e0 100644 --- a/source/funkin/util/plugins/ForceCrashPlugin.hx +++ b/source/funkin/util/plugins/ForceCrashPlugin.hx @@ -22,8 +22,8 @@ class ForceCrashPlugin extends FlxBasic { super.update(elapsed); - // Ctrl + Shift + L = Crash the game for debugging purposes - if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.SHIFT && FlxG.keys.pressed.L) + // Ctrl + Alt + Shift + L = Crash the game for debugging purposes + if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.pressed.L) { // TODO: Make this message 87% funnier. throw "DEBUG: Crashing the game via debug keybind!"; diff --git a/source/funkin/util/plugins/ScreenshotPlugin.hx b/source/funkin/util/plugins/ScreenshotPlugin.hx index c859710de..dcd82ecc5 100644 --- a/source/funkin/util/plugins/ScreenshotPlugin.hx +++ b/source/funkin/util/plugins/ScreenshotPlugin.hx @@ -103,7 +103,11 @@ class ScreenshotPlugin extends FlxBasic public function hasPressedScreenshot():Bool { + #if FEATURE_SCREENSHOTS return PlayerSettings.player1.controls.WINDOW_SCREENSHOT; + #else + return false; + #end } public function updatePreferences():Void