diff --git a/art b/art index 67e550dbd..490e97f4c 160000 --- a/art +++ b/art @@ -1 +1 @@ -Subproject commit 67e550dbd22a8ea429eecc8ce078a36f74ea7723 +Subproject commit 490e97f4c6e673a52ee4f9af98325b1aa2d0c3fe diff --git a/assets b/assets index 69c02fa50..26d1cffe1 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 69c02fa5019f603324a7d2ae362327a1eef9d109 +Subproject commit 26d1cffe19793c67a89f299b7a60be95c5656282 diff --git a/example_mods/introMod/_polymod_meta.json b/example_mods/introMod/_polymod_meta.json index 74c4a2504..cdc43efbe 100644 --- a/example_mods/introMod/_polymod_meta.json +++ b/example_mods/introMod/_polymod_meta.json @@ -6,7 +6,7 @@ "name": "EliteMasterEric" } ], - "api_version": "0.5.0", + "api_version": "0.7.0", "mod_version": "1.0.0", "license": "Apache-2.0" } diff --git a/example_mods/testing123/_polymod_meta.json b/example_mods/testing123/_polymod_meta.json index b8fc60b11..54a6fd582 100644 --- a/example_mods/testing123/_polymod_meta.json +++ b/example_mods/testing123/_polymod_meta.json @@ -6,7 +6,7 @@ "name": "EliteMasterEric" } ], - "api_version": "0.5.0", + "api_version": "0.7.0", "mod_version": "1.0.0", "license": "Apache-2.0" } diff --git a/hmm.json b/hmm.json index 7c7a9c71d..849244b5c 100644 --- a/hmm.json +++ b/hmm.json @@ -18,28 +18,28 @@ "name": "extension-admob", "type": "git", "dir": null, - "ref": "a53d5916bdcb2e48913f94d9ae1d949b049dcdc1", + "ref": "02334589ff9603a5f483077a44395009644f6274", "url": "https://github.com/FunkinCrew/extension-admob" }, { "name": "extension-androidtools", "type": "haxelib", - "version": "2.2.1" + "version": "2.2.2" }, { "name": "extension-haptics", "type": "haxelib", - "version": "1.0.3" + "version": "1.0.4" }, { "name": "extension-iapcore", "type": "haxelib", - "version": "1.0.3" + "version": "1.0.4" }, { "name": "extension-iarcore", "type": "haxelib", - "version": "1.0.2" + "version": "1.0.3" }, { "name": "flixel", @@ -59,7 +59,7 @@ "name": "flxanimate", "type": "git", "dir": null, - "ref": "39c1572add28869c558b218fffed13df1b64f376", + "ref": "dc302f74c0365ac43480844197f4119a727513ea", "url": "https://github.com/FunkinCrew/flxanimate" }, { @@ -111,7 +111,7 @@ "name": "hxcpp", "type": "git", "dir": null, - "ref": "4e24283a047f11bded6affabbc9ec405156e026e", + "ref": "5a0dc3f644dc676a4a092b7e6c8edc8be941f024", "url": "https://github.com/FunkinCrew/hxcpp" }, { @@ -170,7 +170,7 @@ "name": "lime", "type": "git", "dir": null, - "ref": "c750ebf6b48c4bc018abe9855fbae5ffdbc4771a", + "ref": "e5f8c27124598505917a001588b560244731adfb", "url": "https://github.com/FunkinCrew/lime" }, { @@ -219,7 +219,7 @@ "name": "polymod", "type": "git", "dir": null, - "ref": "866f19edbcd872b3358f9a41f2f6a24c71c191d1", + "ref": "d4142dd15a3b57ed4eb149f9f6a2c3ad9935bf7b", "url": "https://github.com/larsiusprime/polymod" }, { diff --git a/project.hxp b/project.hxp index 16fb0160e..fc4830540 100644 --- a/project.hxp +++ b/project.hxp @@ -5,6 +5,7 @@ import hxp.*; import lime.tools.*; import sys.FileSystem; import sys.io.File; +import haxe.io.Bytes; import haxe.io.Path; import haxe.ds.Map; @@ -330,10 +331,10 @@ class Project extends HXProject static final FEATURE_HAPTICS:FeatureFlag = "FEATURE_HAPTICS"; /** - * `-DFEATURE_INPUT_OFFSETS` + * `-DFEATURE_LAG_ADJUSTMENT` * If this flag is enabled, the input offsets menu will be available to configure your audio and visual offsets. */ - static final FEATURE_INPUT_OFFSETS:FeatureFlag = "FEATURE_INPUT_OFFSETS"; + static final FEATURE_LAG_ADJUSTMENT:FeatureFlag = "FEATURE_LAG_ADJUSTMENT"; /** * `-DFEATURE_LOG_TRACE` @@ -500,13 +501,7 @@ class Project extends HXProject configureHaxelibs(); configureAssets(); configureIcons(); - - readASTCExclusion(); - - if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this)) - { - runASTCCompressor(); - } + configureASTCTextures(); if (FEATURE_MOBILE_ADVERTISEMENTS.isEnabled(this)) { @@ -829,7 +824,7 @@ class Project extends HXProject FEATURE_STAGE_EDITOR.apply(this, !(isWeb() || isMobile())); // Should be true except on web builds (some asset stuff breaks it) - FEATURE_INPUT_OFFSETS.apply(this, !isWeb()); + FEATURE_LAG_ADJUSTMENT.apply(this, !isWeb()); // Should be true except on web and mobile builds. // Screenshots doesn't work there, and mobile has its own screenshots anyway. @@ -890,7 +885,7 @@ class Project extends HXProject if (FEATURE_LOG_TRACE.isDisabled(this)) { - addHaxeFlag("--no-traces"); + setHaxedef("no-traces"); } // Disable the built in pause screen when unfocusing the game. @@ -983,7 +978,7 @@ class Project extends HXProject function configureAndroid() { - javaPaths.push(Path.join([SOURCE_DIR, 'funkin/mobile/external/android/java'])); + javaPaths.push(Path.join([SOURCE_DIR, 'funkin/external/android/java'])); if (isRelease()) { @@ -1251,27 +1246,7 @@ class Project extends HXProject if (isAndroid()) { // Adaptive icons - // TODO: Add Adapative Icons in Lime - templatePaths.push("templates"); - - var androidTheme:String = "@style/LimeAppMainTheme"; - if (window.fullscreen != null && window.fullscreen) - { - androidTheme += "Fullscreen"; - } - - final iconXmlChild:Dynamic = { - "android:label": meta.title, - "android:allowBackup": "true", - "android:theme": androidTheme, - "android:hardwareAccelerated": "true", - "android:allowNativeHeapPointerTagging": ANDROID_TARGET_SDK_VERSION >= 30 ? "false" : null, - "android:largeHeap": "true", - "android:icon": "@mipmap/ic_launcher", - "android:roundIcon": "@mipmap/ic_launcher_round" - }; - - config.set('android.application', iconXmlChild); + adaptiveIcon = new AdaptiveIcon('art/icons/android/', true); } else if (isIOS()) { @@ -1303,7 +1278,24 @@ class Project extends HXProject addIcon("art/icons/icon64.png", 64); addIcon("art/icons/iconOG.png"); - info('Done configuring icons.'); + } + + info('Done configuring icons.'); + } + + /** + * Configure the astc textures. + */ + function configureASTCTextures() + { + if (command != "display") + { + readASTCExclusion(); + + if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this)) + { + runASTCCompressor(); + } } } @@ -1703,7 +1695,8 @@ class Project extends HXProject */ public function error(message:String):Void { - Log.error('${message}'); + Sys.stderr().write(Bytes.ofString('[ERROR] ${message}')); + Sys.exit(1); } /** @@ -1713,7 +1706,7 @@ class Project extends HXProject { if (command != "display") { - Log.info('[INFO] ${message}'); + Sys.println('[INFO] ${message}'); } } @@ -1740,17 +1733,37 @@ class Project extends HXProject var env = new Map(); for (line in envFile.split('\n')) { - if (line == "" || line.startsWith("#")) continue; + if (line.length <= 0 || line.startsWith("#") || shouldExcludeEnvKey(line)) continue; - var parts = line.split('='); - if (parts.length != 2) continue; + var index:Int = line.indexOf('='); - env.set(parts[0], parts[1]); + if (index == -1) continue; + + var field:String = line.substr(0, index); + var value:String = line.substr(index + 1); + + env.set(field, value); } return env; } + private function shouldExcludeEnvKey(key:String):Bool + { + final android:Bool = key.startsWith('ANDROID_'); + final ios:Bool = key.startsWith('IOS_'); + final mobile:Bool = key.startsWith('MOBILE_') || ios || android; + final web:Bool = key.startsWith('WEB_'); + final desktop:Bool = key.startsWith('DESKTOP_'); + + if (isWeb() && (mobile || desktop)) return true; + if (isDesktop() && (mobile || web)) return true; + if (isAndroid() && (ios || web || desktop)) return true; + if (isIOS() && (android || web || desktop)) return true; + + return false; + } + public function readASTCExclusion():Void { astcExcludes = File.getContent('./compression-excludes.txt').trim().split('\n'); diff --git a/source/Postbuild.hx b/source/Postbuild.hx index 405d0400b..e778c1792 100644 --- a/source/Postbuild.hx +++ b/source/Postbuild.hx @@ -29,7 +29,7 @@ class Postbuild var buildTime:Float = roundToTwoDecimals(end - start); - trace('Build took: ${buildTime} seconds'); + Sys.println('[INFO] Build took: ${buildTime} seconds'); } } diff --git a/source/Prebuild.hx b/source/Prebuild.hx index 7102d826b..6c3a57b03 100644 --- a/source/Prebuild.hx +++ b/source/Prebuild.hx @@ -9,28 +9,16 @@ class Prebuild { static inline final BUILD_TIME_FILE:String = '.build_time'; - static final NG_CREDS_PATH:String = './source/funkin/api/newgrounds/NewgroundsCredentials.hx'; - - static final NG_CREDS_TEMPLATE:String = "package funkin.api.newgrounds; - -class NewgroundsCredentials -{ - public static final APP_ID:String = #if API_NG_APP_ID haxe.macro.Compiler.getDefine(\"API_NG_APP_ID\") #else 'INSERT APP ID HERE' #end; - public static final ENCRYPTION_KEY:String = #if API_NG_ENC_KEY haxe.macro.Compiler.getDefine(\"API_NG_ENC_KEY\") #else 'INSERT ENCRYPTION KEY HERE' #end; -}"; - static function main():Void { var start:Float = Sys.time(); - trace('[PREBUILD] Performing pre-build tasks...'); + Sys.println('[INFO] Performing pre-build tasks...'); saveBuildTime(); - buildCredsFile(); - var end:Float = Sys.time(); var duration:Float = end - start; - trace('[PREBUILD] Finished pre-build tasks in $duration seconds.'); + Sys.println('[INFO] Finished pre-build tasks in $duration seconds.'); } static function saveBuildTime():Void @@ -41,20 +29,4 @@ class NewgroundsCredentials fo.writeDouble(now); fo.close(); } - - static function buildCredsFile():Void - { - if (sys.FileSystem.exists(NG_CREDS_PATH)) - { - trace('[PREBUILD] NewgroundsCredentials.hx already exists, skipping.'); - } - else - { - trace('[PREBUILD] Creating NewgroundsCredentials.hx...'); - - var fileContents:String = NG_CREDS_TEMPLATE; - - sys.io.File.saveContent(NG_CREDS_PATH, fileContents); - } - } } diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 0cafefb91..3193f9b8d 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -114,7 +114,7 @@ class InitState extends FlxState #if ios // Setup Audio session - funkin.mobile.external.ios.AudioSession.initialize(); + funkin.external.ios.AudioSession.initialize(); #end // This ain't a pixel art game! (most of the time) @@ -202,7 +202,7 @@ class InitState extends FlxState // #if android FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK]; - funkin.mobile.external.android.CallbackUtil.init(); + funkin.external.android.CallbackUtil.init(); #end // diff --git a/source/funkin/api/discord/DiscordClient.hx b/source/funkin/api/discord/DiscordClient.hx index b439a8f1d..7df9d1b33 100644 --- a/source/funkin/api/discord/DiscordClient.hx +++ b/source/funkin/api/discord/DiscordClient.hx @@ -1,5 +1,6 @@ package funkin.api.discord; +import funkin.util.macro.EnvironmentConfigMacro; #if FEATURE_DISCORD_RPC import hxdiscord_rpc.Discord; import hxdiscord_rpc.Types.DiscordButton; @@ -11,7 +12,7 @@ import sys.thread.Thread; @:nullSafety class DiscordClient { - static final CLIENT_ID:String = "816168432860790794"; + static final CLIENT_ID:Null = EnvironmentConfigMacro.environmentConfig?.get("DESKTOP_DISCORD_CLIENT_ID"); public static var instance(get, never):DiscordClient; static var _instance:Null = null; @@ -40,12 +41,28 @@ class DiscordClient { trace('[DISCORD] Initializing connection...'); - // Discord.initialize(CLIENT_ID, handlers, true, null); - Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, ""); + if (!hasValidCredentials()) + { + FlxG.log.warn("Tried to initialize Discord connection, but credentials are invalid!"); + return; + } + + @:nullSafety(Off) + { + Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, ""); + } createDaemon(); } + /** + * @returns `false` if the client ID is invalid. + */ + static function hasValidCredentials():Bool + { + return !(CLIENT_ID == null || CLIENT_ID == "" || (CLIENT_ID != null && CLIENT_ID.contains(" "))); + } + var daemon:Null = null; function createDaemon():Void diff --git a/source/funkin/api/newgrounds/NGSaveSlot.hx b/source/funkin/api/newgrounds/NGSaveSlot.hx new file mode 100644 index 000000000..72d70ac0d --- /dev/null +++ b/source/funkin/api/newgrounds/NGSaveSlot.hx @@ -0,0 +1,156 @@ +package funkin.api.newgrounds; + +#if FEATURE_NEWGROUNDS +import io.newgrounds.utils.SaveSlotList; +import io.newgrounds.objects.SaveSlot; +import io.newgrounds.Call.CallError; +import io.newgrounds.objects.events.Outcome; +import funkin.save.Save; + +@:nullSafety +@:access(funkin.save.Save) +class NGSaveSlot +{ + public static var instance(get, never):NGSaveSlot; + static var _instance:Null = null; + + static function get_instance():NGSaveSlot + { + if (_instance == null) + { + return loadInstance(); + } + return _instance; + } + + public static function loadInstance():NGSaveSlot + { + var loadedSave:NGSaveSlot = loadSlot(Save.BASE_SAVE_SLOT); + if (_instance == null) _instance = loadedSave; + + return loadedSave; + } + + static function loadSlot(slot:Int):NGSaveSlot + { + trace('[NEWGROUNDS] Getting save slot from ID $slot'); + + var saveSlot:Null = NewgroundsClient.instance.saveSlots?.getById(slot); + + var saveSlotObj:NGSaveSlot = new NGSaveSlot(saveSlot); + return saveSlotObj; + } + + public var ngSaveSlot:Null = null; + + public function new(?ngSaveSlot:Null) + { + this.ngSaveSlot = ngSaveSlot; + + #if FLX_DEBUG + FlxG.console.registerClass(NGSaveSlot); + FlxG.console.registerClass(Save); + #end + } + + /** + * Saves `data` to the newgrounds save slot. + * @param data The raw save data. + */ + public function save(data:RawSaveData):Void + { + var encodedData:String = haxe.Serializer.run(data); + + try + { + ngSaveSlot?.save(encodedData, function(outcome:Outcome) { + switch (outcome) + { + case SUCCESS: + trace('[NEWGROUNDS] Successfully saved save data to save slot!'); + case FAIL(error): + trace('[NEWGROUNDS] Failed to save data to save slot!'); + trace(error); + } + }); + } + catch (error:String) + { + trace('[NEWGROUNDS] Failed to save data to save slot!'); + trace(error); + } + } + + public function load(?onComplete:NullVoid>, ?onError:NullVoid>):Void + { + try + { + ngSaveSlot?.load(function(outcome:SaveSlotOutcome):Void { + switch (outcome) + { + case SUCCESS(value): + trace('[NEWGROUNDS] Loaded save slot with the ID of ${ngSaveSlot?.id}!'); + #if FEATURE_DEBUG_FUNCTIONS + trace('Save Slot Data:'); + trace(value); + #end + + if (onComplete != null && value != null) + { + var decodedData:Dynamic = haxe.Unserializer.run(value); + onComplete(decodedData); + } + case FAIL(error): + trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!'); + trace(error); + + if (onError != null) + { + onError(error); + } + } + }); + } + catch (error:String) + { + trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!'); + trace(error); + + if (onError != null) + { + onError(RESPONSE({message: error, code: 500})); + } + } + } + + public function clear():Void + { + try + { + ngSaveSlot?.clear(function(outcome:Outcome) { + switch (outcome) + { + case SUCCESS: + trace('[NEWGROUNDS] Successfully cleared save slot!'); + case FAIL(error): + trace('[NEWGROUNDS] Failed to clear save slot!'); + trace(error); + } + }); + } + catch (error:String) + { + trace('[NEWGROUNDS] Failed to clear save slot!'); + trace(error); + } + } + + public function checkSlot():Void + { + trace('[NEWGROUNDS] Checking save slot with the ID of ${ngSaveSlot?.id}...'); + + trace(' Is null? ${ngSaveSlot == null}'); + trace(' Is empty? ${ngSaveSlot?.isEmpty() ?? false}'); + } +} +#end diff --git a/source/funkin/api/newgrounds/NewgroundsClient.hx b/source/funkin/api/newgrounds/NewgroundsClient.hx index 8bd993ff6..60491a652 100644 --- a/source/funkin/api/newgrounds/NewgroundsClient.hx +++ b/source/funkin/api/newgrounds/NewgroundsClient.hx @@ -1,5 +1,6 @@ package funkin.api.newgrounds; +import funkin.util.macro.EnvironmentConfigMacro; import funkin.save.Save; import funkin.api.newgrounds.Medals.Medal; #if FEATURE_NEWGROUNDS @@ -10,13 +11,18 @@ import io.newgrounds.NGLite.LoginOutcome; import io.newgrounds.NGLite.LoginFail; import io.newgrounds.objects.events.Outcome; import io.newgrounds.utils.MedalList; +import io.newgrounds.utils.SaveSlotList; import io.newgrounds.utils.ScoreBoardList; import io.newgrounds.objects.User; @:nullSafety class NewgroundsClient { + static final APP_ID:Null = EnvironmentConfigMacro.environmentConfig?.get("API_NG_APP_ID"); + static final ENCRYPTION_KEY:Null = EnvironmentConfigMacro.environmentConfig?.get("API_NG_ENC_KEY"); + public static var instance(get, never):NewgroundsClient; + static var _instance:Null = null; static function get_instance():NewgroundsClient @@ -29,14 +35,15 @@ class NewgroundsClient public var user(get, never):Null; public var medals(get, never):Null; public var leaderboards(get, never):Null; + public var saveSlots(get, never):Null; private function new() { trace('[NEWGROUNDS] Initializing client...'); #if FEATURE_NEWGROUNDS_DEBUG - trace('[NEWGROUNDS] App ID: ${NewgroundsCredentials.APP_ID}'); - trace('[NEWGROUNDS] Encryption Key: ${NewgroundsCredentials.ENCRYPTION_KEY}'); + trace('[NEWGROUNDS] App ID: ${APP_ID}'); + trace('[NEWGROUNDS] Encryption Key: ${ENCRYPTION_KEY}'); #end if (!hasValidCredentials()) @@ -45,9 +52,12 @@ class NewgroundsClient return; } - var debug = #if FEATURE_NEWGROUNDS_DEBUG true #else false #end; - NG.create(NewgroundsCredentials.APP_ID, getSessionId(), debug, onLoginResolved); - NG.core.setupEncryption(NewgroundsCredentials.ENCRYPTION_KEY); + @:nullSafety(Off) + { + NG.create(APP_ID, getSessionId(), #if FEATURE_NEWGROUNDS_DEBUG true #else false #end, onLoginResolved); + + NG.core.setupEncryption(ENCRYPTION_KEY); + } } public function init() @@ -166,12 +176,12 @@ class NewgroundsClient */ static function hasValidCredentials():Bool { - return !(NewgroundsCredentials.APP_ID == null - || NewgroundsCredentials.APP_ID == "" - || NewgroundsCredentials.APP_ID.contains(" ") - || NewgroundsCredentials.ENCRYPTION_KEY == null - || NewgroundsCredentials.ENCRYPTION_KEY == "" - || NewgroundsCredentials.ENCRYPTION_KEY.contains(" ")); + return !(APP_ID == null + || APP_ID == "" + || (APP_ID != null && APP_ID.contains(" ")) + || ENCRYPTION_KEY == null + || ENCRYPTION_KEY == "" + || (ENCRYPTION_KEY != null && ENCRYPTION_KEY.contains(" "))); } function onLoginResolved(outcome:LoginOutcome):Void @@ -236,6 +246,8 @@ class NewgroundsClient trace('[NEWGROUNDS] Submitting leaderboard request...'); NG.core.scoreBoards.loadList(onFetchedLeaderboards); + trace('[NEWGROUNDS] Submitting save slot request...'); + NG.core.saveSlots.loadList(onFetchedSaveSlots); } function onLoginFailed(result:LoginFail):Void @@ -301,6 +313,13 @@ class NewgroundsClient // trace(funkin.api.newgrounds.Leaderboards.listLeaderboardData()); } + function onFetchedSaveSlots(outcome:Outcome):Void + { + trace('[NEWGROUNDS] Fetched save slots!'); + + NGSaveSlot.instance.checkSlot(); + } + function get_user():Null { if (NG.core == null || !this.isLoggedIn()) return null; @@ -319,6 +338,12 @@ class NewgroundsClient return NG.core.scoreBoards; } + function get_saveSlots():Null + { + if (NG.core == null || !this.isLoggedIn()) return null; + return NG.core.saveSlots; + } + static function getSessionId():Null { #if js diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 136c728a9..565b362ea 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -1117,6 +1117,23 @@ class SongNoteDataRaw implements ICloneable return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}' + (this.kind != '' ? ' [kind: ${this.kind}])' : ')'); } + + public function buildTooltip():String + { + if ((this.kind?.length ?? 0) == 0) return ""; + + var result:String = 'Kind: ${this.kind}'; + if (this.params.length == 0) return result; + + result += "\nParams:"; + + for (param in params) + { + result += '\n- ${param.name}: ${param.value}'; + } + + return result; + } } /** diff --git a/source/funkin/mobile/external/android/CallbackUtil.hx b/source/funkin/external/android/CallbackUtil.hx similarity index 92% rename from source/funkin/mobile/external/android/CallbackUtil.hx rename to source/funkin/external/android/CallbackUtil.hx index fe04f27d7..4715dad3f 100644 --- a/source/funkin/mobile/external/android/CallbackUtil.hx +++ b/source/funkin/external/android/CallbackUtil.hx @@ -1,5 +1,6 @@ -package funkin.mobile.external.android; +package funkin.external.android; +#if android import lime.system.JNI; import flixel.util.FlxSignal; @@ -9,7 +10,6 @@ import flixel.util.FlxSignal; @:unreflective class CallbackUtil { - #if android /** * The result code for `DATA_FOLDER_CLOSED` activity. */ @@ -42,7 +42,6 @@ class CallbackUtil initCallBackJNI(new CallbackHandler()); } } - #end } /** @@ -50,8 +49,7 @@ class CallbackUtil */ class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end { - #if android - @:allow(funkin.mobile.external.android.CallbackUtil) + @:allow(funkin.external.android.CallbackUtil) function new():Void {} /** @@ -68,5 +66,5 @@ class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end { if (CallbackUtil.onActivityResult != null) CallbackUtil.onActivityResult.dispatch(requestCode, resultCode); } - #end } +#end diff --git a/source/funkin/mobile/external/android/DataFolderUtil.hx b/source/funkin/external/android/DataFolderUtil.hx similarity index 88% rename from source/funkin/mobile/external/android/DataFolderUtil.hx rename to source/funkin/external/android/DataFolderUtil.hx index e18c2de64..4d9a89a95 100644 --- a/source/funkin/mobile/external/android/DataFolderUtil.hx +++ b/source/funkin/external/android/DataFolderUtil.hx @@ -1,12 +1,12 @@ -package funkin.mobile.external.android; +package funkin.external.android; +#if android /** * A Utility class to manage the Application's Data folder on Android. */ @:unreflective class DataFolderUtil { - #if android /** * Opens the data folder on an Android device using JNI. */ @@ -19,5 +19,5 @@ class DataFolderUtil openDataFolderJNI(CallbackUtil.DATA_FOLDER_CLOSED); } } - #end } +#end diff --git a/source/funkin/mobile/external/android/JNIUtil.hx b/source/funkin/external/android/JNIUtil.hx similarity index 99% rename from source/funkin/mobile/external/android/JNIUtil.hx rename to source/funkin/external/android/JNIUtil.hx index 52d66e55e..cfebdfdad 100644 --- a/source/funkin/mobile/external/android/JNIUtil.hx +++ b/source/funkin/external/android/JNIUtil.hx @@ -1,4 +1,4 @@ -package funkin.mobile.external.android; +package funkin.external.android; #if android import lime.system.JNI; diff --git a/source/funkin/mobile/external/android/ScreenUtil.hx b/source/funkin/external/android/ScreenUtil.hx similarity index 96% rename from source/funkin/mobile/external/android/ScreenUtil.hx rename to source/funkin/external/android/ScreenUtil.hx index 54c6e8825..fc271a91c 100644 --- a/source/funkin/mobile/external/android/ScreenUtil.hx +++ b/source/funkin/external/android/ScreenUtil.hx @@ -1,5 +1,6 @@ -package funkin.mobile.external.android; +package funkin.external.android; +#if android import lime.math.Rectangle; import lime.system.JNI; @@ -9,7 +10,6 @@ import lime.system.JNI; @:unreflective class ScreenUtil { - #if android /** * Retrieves the dimensions of display cutouts (such as notches) on Android devices. * @@ -48,5 +48,5 @@ class ScreenUtil return []; } - #end } +#end diff --git a/source/funkin/mobile/external/android/java/funkin/extension/CallbackUtil.java b/source/funkin/external/android/java/funkin/extension/CallbackUtil.java similarity index 100% rename from source/funkin/mobile/external/android/java/funkin/extension/CallbackUtil.java rename to source/funkin/external/android/java/funkin/extension/CallbackUtil.java diff --git a/source/funkin/mobile/external/android/java/funkin/provider/DataFolderProvider.java b/source/funkin/external/android/java/funkin/provider/DataFolderProvider.java similarity index 100% rename from source/funkin/mobile/external/android/java/funkin/provider/DataFolderProvider.java rename to source/funkin/external/android/java/funkin/provider/DataFolderProvider.java diff --git a/source/funkin/mobile/external/android/java/funkin/util/DataFolderUtil.java b/source/funkin/external/android/java/funkin/util/DataFolderUtil.java similarity index 100% rename from source/funkin/mobile/external/android/java/funkin/util/DataFolderUtil.java rename to source/funkin/external/android/java/funkin/util/DataFolderUtil.java diff --git a/source/funkin/mobile/external/android/java/funkin/util/ScreenUtil.java b/source/funkin/external/android/java/funkin/util/ScreenUtil.java similarity index 100% rename from source/funkin/mobile/external/android/java/funkin/util/ScreenUtil.java rename to source/funkin/external/android/java/funkin/util/ScreenUtil.java diff --git a/source/funkin/mobile/external/ios/AudioSession.hx b/source/funkin/external/ios/AudioSession.hx similarity index 70% rename from source/funkin/mobile/external/ios/AudioSession.hx rename to source/funkin/external/ios/AudioSession.hx index deca7e3ce..bd31b4f65 100644 --- a/source/funkin/mobile/external/ios/AudioSession.hx +++ b/source/funkin/external/ios/AudioSession.hx @@ -1,9 +1,10 @@ -package funkin.mobile.external.ios; +package funkin.external.ios; +#if ios /** * A Utility class to manage iOS audio. */ -@:build(funkin.mobile.macros.LinkerMacro.xml('project/Build.xml')) +@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml')) @:include('AudioSession.hpp') @:unreflective extern class AudioSession @@ -13,3 +14,4 @@ extern class AudioSession @:native('setActive') static function setActive(active:Bool):Void; } +#end diff --git a/source/funkin/mobile/external/ios/ScreenUtil.hx b/source/funkin/external/ios/ScreenUtil.hx similarity index 81% rename from source/funkin/mobile/external/ios/ScreenUtil.hx rename to source/funkin/external/ios/ScreenUtil.hx index 76d93a704..8586e5f7a 100644 --- a/source/funkin/mobile/external/ios/ScreenUtil.hx +++ b/source/funkin/external/ios/ScreenUtil.hx @@ -1,9 +1,10 @@ -package funkin.mobile.external.ios; +package funkin.external.ios; +#if ios /** * A Utility class to get iOS screen related informations. */ -@:build(funkin.mobile.macros.LinkerMacro.xml('project/Build.xml')) +@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml')) @:include('ScreenUtil.hpp') @:unreflective extern class ScreenUtil @@ -14,3 +15,4 @@ extern class ScreenUtil @:native('getScreenSize') static function getScreenSize(width:cpp.RawPointer, height:cpp.RawPointer):Void; } +#end diff --git a/source/funkin/mobile/external/ios/project/Build.xml b/source/funkin/external/ios/project/Build.xml similarity index 100% rename from source/funkin/mobile/external/ios/project/Build.xml rename to source/funkin/external/ios/project/Build.xml diff --git a/source/funkin/mobile/external/ios/project/ios/include/AudioSession.hpp b/source/funkin/external/ios/project/ios/include/AudioSession.hpp similarity index 100% rename from source/funkin/mobile/external/ios/project/ios/include/AudioSession.hpp rename to source/funkin/external/ios/project/ios/include/AudioSession.hpp diff --git a/source/funkin/mobile/external/ios/project/ios/include/ScreenUtil.hpp b/source/funkin/external/ios/project/ios/include/ScreenUtil.hpp similarity index 100% rename from source/funkin/mobile/external/ios/project/ios/include/ScreenUtil.hpp rename to source/funkin/external/ios/project/ios/include/ScreenUtil.hpp diff --git a/source/funkin/mobile/external/ios/project/ios/src/AudioSession.mm b/source/funkin/external/ios/project/ios/src/AudioSession.mm similarity index 100% rename from source/funkin/mobile/external/ios/project/ios/src/AudioSession.mm rename to source/funkin/external/ios/project/ios/src/AudioSession.mm diff --git a/source/funkin/mobile/external/ios/project/ios/src/ScreenUtil.mm b/source/funkin/external/ios/project/ios/src/ScreenUtil.mm similarity index 100% rename from source/funkin/mobile/external/ios/project/ios/src/ScreenUtil.mm rename to source/funkin/external/ios/project/ios/src/ScreenUtil.mm diff --git a/source/funkin/mobile/macros/LinkerMacro.hx b/source/funkin/mobile/macros/LinkerMacro.hx deleted file mode 100644 index 009bc193c..000000000 --- a/source/funkin/mobile/macros/LinkerMacro.hx +++ /dev/null @@ -1,37 +0,0 @@ -package funkin.mobile.macros; - -import haxe.macro.Context; -import haxe.macro.Expr; -import haxe.xml.Printer; -import sys.FileSystem; - -using haxe.io.Path; - -/** - * This class provides a macro to include an XML build file in the metadata of a Haxe class. - * The file must be located relative to the directory of the Haxe class that uses this macro. - */ -@:nullSafety -class LinkerMacro -{ - /** - * Adds an XML `` element to the class's metadata, pointing to a specified build file. - * @param file_name The name of the XML file to include. Defaults to `Build.xml` if not provided. - * @return An array of fields that are processed during the build. - */ - public static macro function xml(?file_name:String = 'Build.xml'):Array - { - final pos:Position = Context.currentPos(); - final sourcePath:String = FileSystem.absolutePath(Context.getPosInfos(pos).file.directory()).removeTrailingSlashes(); - final fileToInclude:String = Path.join([sourcePath, file_name?.length > 0 ? file_name : 'Build.xml']); - - if (!FileSystem.exists(fileToInclude)) Context.error('The specified file "$fileToInclude" could not be found at "$sourcePath".', pos); - - final includeElement:Xml = Xml.createElement('include'); - includeElement.set('name', fileToInclude); - Context.getLocalClass().get().meta.add(':buildXml', [ - {expr: EConst(CString(Printer.print(includeElement, true))), pos: pos}], pos); - - return Context.getBuildFields(); - } -} diff --git a/source/funkin/mobile/util/AdMobUtil.hx b/source/funkin/mobile/util/AdMobUtil.hx index 6bce88c4b..47a4a8a32 100644 --- a/source/funkin/mobile/util/AdMobUtil.hx +++ b/source/funkin/mobile/util/AdMobUtil.hx @@ -29,7 +29,7 @@ class AdMobUtil /** * AdMob publisher ID used for the application. */ - static final ADMOB_PUBLISHER:String = EnvironmentConfigMacro.environmentConfig.get("GLOBAL_ADMOB_PUBLISHER"); + static final ADMOB_PUBLISHER:String = EnvironmentConfigMacro.environmentConfig.get("MOBILE_GLOBAL_ADMOB_PUBLISHER"); /** * Test ad unit IDs for development and testing purposes. diff --git a/source/funkin/mobile/util/ScreenUtil.hx b/source/funkin/mobile/util/ScreenUtil.hx index 27bf023f3..cb1a9c562 100644 --- a/source/funkin/mobile/util/ScreenUtil.hx +++ b/source/funkin/mobile/util/ScreenUtil.hx @@ -1,9 +1,9 @@ package funkin.mobile.util; #if ios -import funkin.mobile.external.ios.ScreenUtil as NativeScreenUtil; +import funkin.external.ios.ScreenUtil as NativeScreenUtil; #elseif android -import funkin.mobile.external.android.ScreenUtil as NativeScreenUtil; +import funkin.external.android.ScreenUtil as NativeScreenUtil; #end import lime.math.Rectangle; import lime.system.System; diff --git a/source/funkin/modding/ModStore.hx b/source/funkin/modding/ModStore.hx new file mode 100644 index 000000000..981a3498a --- /dev/null +++ b/source/funkin/modding/ModStore.hx @@ -0,0 +1,54 @@ +package funkin.modding; + +import haxe.ds.StringMap; + +/** + * Temporary persistent data storage for mods to use. + */ +@:nullSafety +class ModStore +{ + /** + * All registered stores for this session. + */ + public static final stores:StringMap = new StringMap(); + + /** + * Attempts to register a new store with the given ID and return it. + * If a store with the same ID already exists, that store will be returned instead (discards `data`). + * + * @id The unique ID for this store. + * @data Optional initial data, uses an empty object by default. + * @return The store data at the given ID. + */ + public static function register(id:String, ?data:Dynamic):Dynamic + { + if (stores.exists(id)) return stores.get(id); + stores.set(id, data ??= {}); + return data; + } + + /** + * Helper function to get a store by ID. + * + * @id The target ID of the store. + * @return The store data, or `null` if the store did not exist. + */ + public static function get(id:String):Null + { + return stores.get(id); + } + + /** + * Helper function to remove a store by ID and return it. + * + * @id The target ID of the store. + * @return The store data, or `null` if the store did not exist. + */ + public static function remove(id:String):Null + { + var data:Null = stores.get(id); + stores.remove(id); + return data; + } +} diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 875cb598e..f5fc04569 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -21,7 +21,6 @@ import funkin.util.MathUtil; import funkin.effects.RetroCameraFade; import flixel.math.FlxPoint; import funkin.util.TouchUtil; -import openfl.utils.Assets; #if FEATURE_MOBILE_ADVERTISEMENTS import funkin.mobile.util.AdMobUtil; #end @@ -102,18 +101,6 @@ class GameOverSubState extends MusicBeatSubState var canInput:Bool = false; - var justDied:Bool = true; - - var isSpecialAnimation:Bool = false; - - var gameOverVibrationPreset:VibrationPreset = - { - period: 0, - duration: Constants.DEFAULT_VIBRATION_DURATION, - amplitude: Constants.MIN_VIBRATION_AMPLITUDE, - sharpness: Constants.DEFAULT_VIBRATION_SHARPNESS - }; - public function new(params:GameOverParams) { super(); @@ -175,7 +162,7 @@ class GameOverSubState extends MusicBeatSubState if ((parentPlayState?.isMinimalMode ?? true)) {} else { - boyfriend = parentPlayState?.currentStage.getBoyfriend(true); + boyfriend = parentPlayState?.currentStage?.getBoyfriend(true); if (boyfriend != null) { boyfriend.canPlayOtherAnims = true; @@ -198,16 +185,17 @@ class GameOverSubState extends MusicBeatSubState addBackButton(FlxG.width - 230, FlxG.height - 200, FlxColor.WHITE, goBack); #end + HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION); + // Allow input a second later to prevent accidental gameover skips. new FlxTimer().start(1, function(tmr:FlxTimer) { canInput = true; }); } - @:nullSafety(Off) function setCameraTarget():Void { - if ((parentPlayState?.isMinimalMode ?? true) || boyfriend == null) return; + if (parentPlayState == null || parentPlayState.isMinimalMode || boyfriend == null) return; // Assign a camera follow point to the boyfriend's position. cameraFollowPoint = new FlxObject(parentPlayState.cameraFollowPoint.x, parentPlayState.cameraFollowPoint.y, 1, 1); @@ -218,6 +206,7 @@ class GameOverSubState extends MusicBeatSubState cameraFollowPoint.y += offsets[1]; add(cameraFollowPoint); + @:nullSafety(Off) FlxG.camera.target = null; FlxG.camera.follow(cameraFollowPoint, LOCKON, Constants.DEFAULT_CAMERA_FOLLOW_RATE / 2); targetCameraZoom = (parentPlayState?.currentStage?.camZoom ?? 1.0) * boyfriend.getDeathCameraZoom(); @@ -330,9 +319,6 @@ class GameOverSubState extends MusicBeatSubState } } - // Handle vibrations on update. - if (HapticUtil.hapticsAvailable) handleAnimationVibrations(); - // Start death music before firstDeath gets replaced super.update(elapsed); } @@ -408,7 +394,7 @@ class GameOverSubState extends MusicBeatSubState // Readd Boyfriend to the stage. boyfriend.isDead = false; remove(boyfriend); - parentPlayState?.currentStage.addCharacter(boyfriend, BF); + parentPlayState?.currentStage?.addCharacter(boyfriend, BF); } // Snap reset the camera which may have changed because of the player character data. @@ -578,11 +564,11 @@ class GameOverSubState extends MusicBeatSubState PlayStatePlaylist.reset(); } - var stickerPackId:Null = parentPlayState?.currentChart.stickerPack; + var stickerPackId:Null = parentPlayState?.currentChart?.stickerPack; if (stickerPackId == null) { - var playerCharacterId:Null = PlayerRegistry.instance.getCharacterOwnerId(parentPlayState?.currentChart.characters.player); + var playerCharacterId:Null = PlayerRegistry.instance.getCharacterOwnerId(parentPlayState?.currentChart?.characters.player); var playerCharacter:Null = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? Constants.DEFAULT_CHARACTER); if (playerCharacter != null) @@ -615,108 +601,6 @@ class GameOverSubState extends MusicBeatSubState var hasPlayedDeathQuote:Bool = false; - /** - * Used for death haptics. - */ - var startedTimerHaptics:Bool = false; - - /** - * Unique vibrations for each death animation. - */ - function handleAnimationVibrations():Void - { - if ((parentPlayState?.isMinimalMode ?? true) || boyfriend == null) return; - - if (justDied) - { - if (isSpecialAnimation) - { - HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION * 5); - trace("It's a special game over animation."); - } - else - { - HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION); - } - justDied = false; - } - - if (boyfriend.animation == null) return; - - final curFrame:Int = (boyfriend.animation.curAnim != null) ? boyfriend.animation.curAnim.curFrame : -1; - if (boyfriend.characterId.startsWith("bf")) - { - // BF's mic drops. - if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && curFrame == 27) - { - HapticUtil.vibrateByPreset(gameOverVibrationPreset); - } - - // BF's balls pulsating. - if (boyfriend.getCurrentAnimation().startsWith('deathLoop') && (curFrame == 0 || curFrame == 18)) - { - HapticUtil.vibrateByPreset(gameOverVibrationPreset); - } - - return; - } - - // Pico dies because of Darnell beating him up. - if (boyfriend.characterId == "pico-blazin") - { - if (!startedTimerHaptics) - { - startedTimerHaptics = true; - - new FlxTimer().start(0.5, function(tmr:FlxTimer) { - // Pico falls on his knees. - HapticUtil.vibrateByPreset(gameOverVibrationPreset); - - new FlxTimer().start(0.6, function(tmr:FlxTimer) { - // Pico falls "asleep". :) - HapticUtil.vibrateByPreset(gameOverVibrationPreset); - }); - }); - - return; - } - } - else if (boyfriend.characterId.startsWith("pico") && boyfriend.characterId != "pico-holding-nene") - { - if (isSpecialAnimation) - { - if (startedTimerHaptics) return; - - startedTimerHaptics = true; - - // Death by Darnell's can. - new FlxTimer().start(1.85, function(tmr:FlxTimer) { - // Pico falls on his knees. - HapticUtil.vibrateByPreset(gameOverVibrationPreset); - }); - } - else - { - // Pico falls on his back. - if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && curFrame == 20) - { - HapticUtil.vibrateByPreset(gameOverVibrationPreset); - } - - // Blood firework woohoo!!!! - if (boyfriend.getCurrentAnimation().startsWith('deathLoop') && curFrame % 2 == 0) - { - final randomAmplitude:Float = FlxG.random.float(Constants.MIN_VIBRATION_AMPLITUDE / 100, Constants.MIN_VIBRATION_AMPLITUDE); - final randomDuration:Float = FlxG.random.float(Constants.DEFAULT_VIBRATION_DURATION / 10, Constants.DEFAULT_VIBRATION_DURATION); - - HapticUtil.vibrate(0, randomDuration, randomAmplitude); - } - } - - return; - } - } - public override function destroy():Void { super.destroy(); diff --git a/source/funkin/play/GitarooPause.hx b/source/funkin/play/GitarooPause.hx index 8a2d7f3f9..9df525629 100644 --- a/source/funkin/play/GitarooPause.hx +++ b/source/funkin/play/GitarooPause.hx @@ -18,9 +18,9 @@ class GitarooPause extends MusicBeatState var replaySelect:Bool = false; - var previousParams:PlayStateParams; + var previousParams:Null; - public function new(previousParams:PlayStateParams):Void + public function new(?previousParams:PlayStateParams):Void { super(); diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index e681f42d2..e3766791c 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -213,6 +213,11 @@ class PauseSubState extends MusicBeatSubState */ var menuEntryText:FlxTypedSpriteGroup; + /** + * Callback that gets called once substate gets open. + */ + var onPause:Void->Void; + // =============== // Audio Variables // =============== @@ -222,10 +227,11 @@ class PauseSubState extends MusicBeatSubState // Constructor // =============== - public function new(?params:PauseSubStateParams) + public function new(?params:PauseSubStateParams, ?onPause:Void->Void) { super(); this.currentMode = params?.mode ?? Standard; + this.onPause = onPause; } // =============== @@ -244,6 +250,8 @@ class PauseSubState extends MusicBeatSubState AdMobUtil.addBanner(extension.admob.AdmobBannerSize.BANNER, extension.admob.AdmobBannerAlign.TOP_LEFT); #end + if (onPause != null) onPause(); + super.create(); startPauseMusic(); @@ -286,6 +294,7 @@ class PauseSubState extends MusicBeatSubState hapticTimer.cancel(); hapticTimer = null; pauseMusic.stop(); + onPause = null; } // =============== @@ -444,7 +453,7 @@ class PauseSubState extends MusicBeatSubState offsetText.y = FlxG.height - (offsetText.height + offsetText.height + 40); offsetTextInfo.y = offsetText.y + offsetText.height + 4; - #if !mobile + #if (!mobile && FEATURE_LAG_ADJUSTMENT) metadata.add(offsetText); metadata.add(offsetTextInfo); #end diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index d02e05412..d7679d354 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -43,6 +43,7 @@ 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.notekind.NoteKind; import funkin.play.notes.NoteSprite; import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.Strumline; @@ -145,6 +146,7 @@ typedef PlayStateParams = * The gameplay state, where all the rhythm gaming happens. * SubState so it can be loaded as a child of the chart editor. */ +@:nullSafety class PlayState extends MusicBeatSubState { /** @@ -156,13 +158,13 @@ class PlayState extends MusicBeatSubState * The currently active PlayState. * There should be only one PlayState in existance at a time, we can use a singleton. */ - public static var instance:PlayState = null; + public static var instance:Null; /** * This sucks. We need this because FlxG.resetState(); assumes the constructor has no arguments. * @see https://github.com/HaxeFlixel/flixel/issues/2541 */ - static var lastParams:PlayStateParams = null; + static var lastParams:Null; /** * PUBLIC INSTANCE VARIABLES @@ -172,7 +174,7 @@ class PlayState extends MusicBeatSubState /** * The currently selected stage. */ - public var currentSong:Song = null; + public var currentSong:Song; /** * The currently selected difficulty. @@ -193,7 +195,7 @@ class PlayState extends MusicBeatSubState /** * The currently active Stage. This is the object containing all the props. */ - public var currentStage:Stage = null; + public var currentStage:Null = null; /** * Gets set to true when the PlayState needs to reset (player opted to restart or died). @@ -249,12 +251,12 @@ class PlayState extends MusicBeatSubState * An FlxTween that tweens the camera to the follow point. * Only used when tweening the camera manually, rather than tweening via follow. */ - public var cameraFollowTween:FlxTween; + public var cameraFollowTween:Null; /** * An FlxTween that zooms the camera to the desired amount. */ - public var cameraZoomTween:FlxTween; + public var cameraZoomTween:Null; /** * An FlxTween that changes the additive speed to the desired amount. @@ -265,7 +267,7 @@ class PlayState extends MusicBeatSubState * The camera follow point from the last stage. * Used to persist the position of the `cameraFollowPosition` between levels. */ - public var previousCameraFollowPoint:FlxPoint = null; + public var previousCameraFollowPoint:Null; /** * The current camera zoom level without any modifiers applied. @@ -379,7 +381,7 @@ class PlayState extends MusicBeatSubState /** * The current dialogue. */ - public var currentConversation:Conversation; + public var currentConversation:Null; /** * Key press inputs which have been received but not yet processed. @@ -398,6 +400,11 @@ class PlayState extends MusicBeatSubState */ var justUnpaused:Bool = false; + /** + * The current note style used by the song. + */ + var noteStyle:NoteStyle; + /** * PRIVATE INSTANCE VARIABLES * Private instance variables should be used for information that must be reset or dereferenced @@ -407,7 +414,7 @@ class PlayState extends MusicBeatSubState * The Array containing the upcoming song events. * The `update()` function regularly shifts these out to trigger events. */ - var songEvents:Array; + var songEvents:Array = []; /** * If true, the player is allowed to pause the game. @@ -466,7 +473,7 @@ class PlayState extends MusicBeatSubState /** * A group of audio tracks, used to play the song's vocals. */ - public var vocals:VoicesGroup; + public var vocals:Null; #if FEATURE_DISCORD_RPC // Discord RPC variables @@ -497,12 +504,12 @@ class PlayState extends MusicBeatSubState /** * The health icon representing the player. */ - public var iconP1:HealthIcon; + public var iconP1:Null; /** * The health icon representing the opponent. */ - public var iconP2:HealthIcon; + public var iconP2:Null; /** * The sprite group containing active player's strumline notes. @@ -593,9 +600,9 @@ class PlayState extends MusicBeatSubState * Data for the current difficulty for the current song. * Includes chart data, scroll speed, and other information. */ - public var currentChart(get, never):SongDifficulty; + public var currentChart(get, never):Null; - function get_currentChart():SongDifficulty + function get_currentChart():Null { if (currentSong == null || currentDifficulty == null) return null; return currentSong.getDifficulty(currentDifficulty, currentVariation); @@ -609,8 +616,8 @@ class PlayState extends MusicBeatSubState function get_currentStageId():String { - if (currentChart == null || currentChart.stage == null || currentChart.stage == '') return Constants.DEFAULT_STAGE; - return currentChart.stage; + var stage:String = currentChart?.stage ?? ''; + return stage == '' ? Constants.DEFAULT_STAGE : stage; } /** @@ -620,7 +627,7 @@ class PlayState extends MusicBeatSubState function get_currentSongLengthMs():Float { - return FlxG?.sound?.music?.length; + return FlxG.sound.music?.length ?? 0; } /** @@ -641,27 +648,20 @@ class PlayState extends MusicBeatSubState * @param params The parameters used to initialize the PlayState. * Includes information about what song to play and more. */ - public function new(params:PlayStateParams) + public function new(?params:PlayStateParams) { super(); // Validate parameters. - if (params == null && lastParams == null) - { - throw 'PlayState constructor called with no available parameters.'; - } - else if (params == null) - { - trace('WARNING: PlayState constructor called with no parameters. Reusing previous parameters.'); - params = lastParams; - } - else - { - lastParams = params; - } + var params:PlayStateParams = params ?? + { + trace('WARNING: PlayState constructor called with no parameters. Reusing previous parameters.'); + lastParams ?? throw 'PlayState constructor called with no available parameters.'; + } + lastParams = params; // Apply parameters. - currentSong = params.targetSong; + currentSong = params.targetSong ?? throw "targetSong should not be null"; if (params.targetDifficulty != null) currentDifficulty = params.targetDifficulty; previousDifficulty = currentDifficulty; if (params.targetVariation != null) currentVariation = params.targetVariation; @@ -674,26 +674,7 @@ class PlayState extends MusicBeatSubState overrideMusic = params.overrideMusic ?? false; previousCameraFollowPoint = params.cameraFollowPoint; - // Don't do anything else here! Wait until create() when we attach to the camera. - } - - /** - * Called when the PlayState is switched to. - */ - public override function create():Void - { - if (instance != null) - { - // TODO: Do something in this case? IDK. - trace('WARNING: PlayState instance already exists. This should not happen.'); - } - instance = this; - #if !mobile - // TODO: Figure out how to do the flair for charting mode!! I can't figure it out for the love of god. -Zack - if (!isChartingMode) FlxG.autoPause = false; - #end - - if (!assertChartExists()) return; + // Basic object initialization // TODO: Add something to toggle this on! if (false) @@ -711,11 +692,61 @@ class PlayState extends MusicBeatSubState cameraFollowPoint = new FlxObject(0, 0); } + // Cameras + camGame = new FunkinCamera('playStateCamGame'); + camHUD = new FlxCamera(); + camCutscene = new FlxCamera(); + camCutouts = new FlxCamera(); + + var currentChart = currentSong.getDifficulty(currentDifficulty, currentVariation); + var noteStyleId:Null = currentChart?.noteStyle; + var nulNoteStyle:Null = NoteStyleRegistry.instance.fetchEntry(noteStyleId ?? Constants.DEFAULT_NOTE_STYLE); + if (nulNoteStyle == null) throw "Failed to retrieve both note style and default note style. This shouldn't happen!"; + noteStyle = nulNoteStyle; + + // Strumlines + playerStrumline = new Strumline(noteStyle, !isBotPlayMode, currentChart?.scrollSpeed); + opponentStrumline = new Strumline(noteStyle, false, currentChart?.scrollSpeed); + + // Healthbar + healthBarBG = FunkinSprite.create(0, 0, 'healthBar'); + healthBar = new FlxBar(0, 0, RIGHT_TO_LEFT, Std.int(healthBarBG.width - 8), Std.int(healthBarBG.height - 8), null, 0, 2); + scoreText = new FlxText(0, 0, 0, '', 20); + + // Combo & Pop Up + comboPopUps = new PopUpStuff(noteStyle); + + // Pause sprites + #if mobile + pauseButton = FunkinSprite.createSparrow(0, 0, "pauseButton"); + pauseCircle = FunkinSprite.create(0, 0, 'pauseCircle'); + #end + + // Don't do anything else here! Wait until create() when we attach to the camera. + } + + /** + * Called when the PlayState is switched to. + */ + @:nullSafety(Off) + public override function create():Void + { + if (instance != null) + { + // TODO: Do something in this case? IDK. + trace('WARNING: PlayState instance already exists. This should not happen.'); + } + instance = this; + #if !mobile + // TODO: Figure out how to do the flair for charting mode!! I can't figure it out for the love of god. -Zack + if (!isChartingMode) FlxG.autoPause = false; + #end + + if (!assertChartExists()) return; + #if mobile // Force allowScreenTimeout to be disabled lime.system.System.allowScreenTimeout = false; - // TODO: For some reason the touch pointer's positioning gets weird in playstate, find a way to fix it! -Zack - funkin.util.plugins.TouchPointerPlugin.enabled = false; #end // This state receives update() even when a substate is active. @@ -724,11 +755,11 @@ class PlayState extends MusicBeatSubState this.persistentDraw = true; // Stop any pre-existing music. - if (!overrideMusic && FlxG.sound.music != null) FlxG.sound.music.stop(); - - // Prepare the current song's instrumental and vocals to be played. - if (!overrideMusic && currentChart != null) + if (!overrideMusic) { + if (FlxG.sound.music != null) FlxG.sound.music.stop(); + + // Prepare the current song's instrumental and vocals to be played. currentChart.cacheInst(currentInstrumental); currentChart.cacheVocals(); } @@ -768,13 +799,16 @@ class PlayState extends MusicBeatSubState { // Initialize the hitbox for mobile controls addHitbox(false); - hitbox.isPixel = currentChart.noteStyle == "pixel"; - - if (Preferences.controlsScheme == FunkinHitboxControlSchemes.Arrows) + if (hitbox != null) { - for (direction in Strumline.DIRECTIONS) + hitbox.isPixel = currentChart.noteStyle == "pixel"; + + if (Preferences.controlsScheme == FunkinHitboxControlSchemes.Arrows) { - hitbox.getFirstHintByDirection(direction).follow(playerStrumline.getByDirection(direction)); + for (direction in Strumline.DIRECTIONS) + { + hitbox.getFirstHintByDirection(direction).follow(playerStrumline.getByDirection(direction)); + } } } } @@ -807,7 +841,7 @@ class PlayState extends MusicBeatSubState startingSong = true; // TODO: We hardcoded the transition into Winter Horrorland. Do this with a ScriptedSong instead. - if ((currentSong?.id ?? '').toLowerCase() == 'winter-horrorland') + if ((currentSong.id ?? '').toLowerCase() == 'winter-horrorland') { // VanillaCutscenes will call startCountdown later. VanillaCutscenes.playHorrorStartCutscene(); @@ -822,31 +856,7 @@ class PlayState extends MusicBeatSubState // Create the pause button. #if mobile - pauseButton = FunkinSprite.createSparrow(0, 0, "pauseButton"); - pauseButton.animation.addByIndices('idle', 'back', [0], "", 24, false); - pauseButton.animation.addByIndices('hold', 'back', [5], "", 24, false); - pauseButton.animation.addByIndices('confirm', 'back', [ - 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 - ], "", 24, false); - pauseButton.scale.set(0.8, 0.8); - pauseButton.updateHitbox(); - pauseButton.animation.play("idle"); - pauseButton.setPosition((FlxG.width - pauseButton.width) - 35, 35); - pauseButton.cameras = [camControls]; - - pauseCircle = FunkinSprite.create(0, 0, 'pauseCircle'); - pauseCircle.scale.set(0.84, 0.8); - pauseCircle.updateHitbox(); - pauseCircle.cameras = [camControls]; - pauseCircle.x = ((pauseButton.x + (pauseButton.width / 2)) - (pauseCircle.width / 2)); - pauseCircle.y = ((pauseButton.y + (pauseButton.height / 2)) - (pauseCircle.height / 2)); - pauseCircle.alpha = 0.1; - - add(pauseCircle); - add(pauseButton); - hitbox?.forEachAlive(function(hint:FunkinHint) { - hint.deadZones.push(pauseButton); - }); + initPauseSprites(); #end // Do this last to prevent beatHit from being called before create() is done. @@ -881,7 +891,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 || currentChart.notes == 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; @@ -900,7 +910,7 @@ class PlayState extends MusicBeatSubState { message = 'There 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) + else if (currentChart?.notes == null) { message = 'There was a critical error retrieving note data for this song on "$currentDifficulty" difficulty with variation "$currentVariation". Click OK to return to the main menu.'; } @@ -915,7 +925,7 @@ class PlayState extends MusicBeatSubState } else { - this.remove(currentStage); + if (currentStage != null) this.remove(currentStage); FlxG.switchState(() -> new MainMenuState()); } return false; @@ -964,26 +974,31 @@ class PlayState extends MusicBeatSubState FlxG.sound.music.pitch = playbackRate; } - if (!overrideMusic) + if (!overrideMusic && vocals != null) { // Stop the vocals if they already exist. - if (vocals != null) vocals.stop(); - vocals = currentChart.buildVocals(currentInstrumental); + vocals.stop(); + vocals = currentChart?.buildVocals(currentInstrumental); - if (vocals.members.length == 0) + if (vocals?.members?.length == 0) { trace('WARNING: No vocals found for this song.'); } } - vocals.pause(); - vocals.time = startTimestamp - Conductor.instance.instrumentalOffset; if (FlxG.sound.music != null) FlxG.sound.music.volume = 1; - vocals.volume = 1; - vocals.playerVolume = 1; - vocals.opponentVolume = 1; - if (currentStage != null) currentStage.resetStage(); + if (vocals != null) + { + vocals.pause(); + vocals.time = startTimestamp - Conductor.instance.instrumentalOffset; + + vocals.volume = 1; + vocals.playerVolume = 1; + vocals.opponentVolume = 1; + } + + currentStage?.resetStage(); if (!fromDeathState) { @@ -1063,10 +1078,10 @@ class PlayState extends MusicBeatSubState // If, after updating the conductor, the instrumental has finished, end the song immediately. // This helps prevent a major bug where the level suddenly loops back to the start or middle. - if (Conductor.instance.songPosition >= (FlxG.sound.music.endTime ?? FlxG.sound.music.length)) - { - if (mayPauseGame && !isSongEnd) endSong(skipEndingTransition); - } + // if (Conductor.instance.songPosition >= (FlxG.sound.music.endTime ?? FlxG.sound.music.length)) + // { + // if (mayPauseGame && !isSongEnd) endSong(skipEndingTransition); + // } } var pauseButtonCheck:Bool = false; @@ -1142,7 +1157,7 @@ class PlayState extends MusicBeatSubState if (health <= Constants.HEALTH_MIN && !isPracticeMode && !isPlayerDying) { - vocals.pause(); + vocals?.pause(); if (FlxG.sound.music != null) FlxG.sound.music.pause(); @@ -1227,21 +1242,19 @@ class PlayState extends MusicBeatSubState #end } - function pause(?mode:PauseMode = Standard):Void + function pause(mode:PauseMode = Standard):Void { - if (!mayPauseGame || justUnpaused || isGamePaused) return; + if (!mayPauseGame || justUnpaused || isGamePaused || isPlayerDying) return; switch (mode) { case Conversation: - currentConversation.pauseMusic(); preparePauseUI(); - openPauseSubState(Conversation, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene); + openPauseSubState(Conversation, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene, () -> currentConversation?.pauseMusic()); case Cutscene: - VideoCutscene.pauseVideo(); preparePauseUI(); - openPauseSubState(Cutscene, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene); + openPauseSubState(Cutscene, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene, () -> VideoCutscene.pauseVideo()); default: // also known as standard if (!isInCountdown || isInCutscene) return; @@ -1259,7 +1272,7 @@ class PlayState extends MusicBeatSubState if (!isSubState && event.gitaroo) { - this.remove(currentStage); + if (currentStage != null) this.remove(currentStage); FlxG.switchState(() -> new GitarooPause(lastParams)); } else @@ -1299,9 +1312,9 @@ class PlayState extends MusicBeatSubState #end } - function openPauseSubState(mode:PauseMode, cam:FlxCamera):Void + function openPauseSubState(mode:PauseMode, cam:FlxCamera, ?onPause:Void->Void):Void { - final pauseSubState = new PauseSubState({mode: mode}); + final pauseSubState = new PauseSubState({mode: mode}, onPause); FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransOut = true; pauseSubState.camera = cam; @@ -1327,8 +1340,8 @@ class PlayState extends MusicBeatSubState if (!isMinimalMode) { - if (iconP1 != null) iconP1.updatePosition(); - if (iconP2 != null) iconP2.updatePosition(); + iconP1?.updatePosition(); + iconP2?.updatePosition(); } // Transition to the game over substate. @@ -1346,7 +1359,7 @@ class PlayState extends MusicBeatSubState { // Query and activate song events. // TODO: Check that these work appropriately even when songPosition is less than 0, to play events during countdown. - if (songEvents != null && songEvents.length > 0) + if (songEvents.length > 0) { var songEventsToActivate:Array = SongEventRegistry.queryEvents(songEvents, Conductor.instance.songPosition); @@ -1502,7 +1515,9 @@ class PlayState extends MusicBeatSubState musicPausedBySubState = false; } - forEachPausedSound((s) -> needsReset ? s.destroy() : s.resume()); + // The logic here is that if this sound doesn't auto-destroy + // then it's gonna be reused somewhere, so we just stop it instead. + forEachPausedSound(s -> needsReset ? (s.autoDestroy ? s.destroy() : s.stop()) : s.resume()); // Resume camera tweens if we paused any. for (camTween in cameraTweensPausedBySubState) @@ -1659,7 +1674,11 @@ class PlayState extends MusicBeatSubState instance = this; funkin.modding.PolymodHandler.forceReloadAssets(); - lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id); + if (lastParams == null) + { + throw "No lastParams to refer to"; + } + lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id) ?? throw "Could not load current song from ID. This shouldn't happen!"; LoadingState.loadPlayState(lastParams); } @@ -1672,8 +1691,8 @@ class PlayState extends MusicBeatSubState if (isGamePaused) return false; - if (iconP1 != null) iconP1.onStepHit(Std.int(Conductor.instance.currentStep)); - if (iconP2 != null) iconP2.onStepHit(Std.int(Conductor.instance.currentStep)); + iconP1?.onStepHit(Std.int(Conductor.instance.currentStep)); + iconP2?.onStepHit(Std.int(Conductor.instance.currentStep)); // Try to call hold note haptics each step hit. Works if atleast one note status is NoteStatus.isHoldNotePressed. playerStrumline.noteVibrations.tryHoldNoteVibration(); @@ -1701,17 +1720,17 @@ class PlayState extends MusicBeatSubState var correctSync:Float = Math.min(FlxG.sound.music.length, Math.max(0, Conductor.instance.songPosition - Conductor.instance.combinedOffset)); var playerVoicesError:Float = 0; var opponentVoicesError:Float = 0; - if (vocals != null && vocals.playing) { + @:nullSafety(Off) @:privateAccess // todo: maybe make the groups public :thinking: { - vocals.playerVoices.forEachAlive(function(voice:FunkinSound) { + vocals.playerVoices?.forEachAlive(function(voice:FunkinSound) { var currentRawVoiceTime:Float = voice.time + vocals.playerVoicesOffset; if (Math.abs(currentRawVoiceTime - correctSync) > Math.abs(playerVoicesError)) playerVoicesError = currentRawVoiceTime - correctSync; }); - vocals.opponentVoices.forEachAlive(function(voice:FunkinSound) { + vocals.opponentVoices?.forEachAlive(function(voice:FunkinSound) { var currentRawVoiceTime:Float = voice.time + vocals.opponentVoicesOffset; if (Math.abs(currentRawVoiceTime - correctSync) > Math.abs(opponentVoicesError)) opponentVoicesError = currentRawVoiceTime - correctSync; }); @@ -1761,7 +1780,6 @@ class PlayState extends MusicBeatSubState #if mobile // Syncing allowScreenTimeout with Preferences option. lime.system.System.allowScreenTimeout = Preferences.screenTimeout; - funkin.util.plugins.TouchPointerPlugin.enabled = true; #end #if !mobile @@ -1783,13 +1801,11 @@ class PlayState extends MusicBeatSubState */ function initCameras():Void { - camGame = new FunkinCamera('playStateCamGame'); camGame.bgColor = BACKGROUND_COLOR; // Show a pink background behind the stage. - camHUD = new FlxCamera(); camHUD.bgColor.alpha = 0; // Show the game scene behind the camera. - camCutscene = new FlxCamera(); camCutscene.bgColor.alpha = 0; // Show the game scene behind the camera. - camCutouts = new FlxCamera((FlxG.width - FlxG.initialWidth) / 2, (FlxG.height - FlxG.initialHeight) / 2, FlxG.initialWidth, FlxG.initialHeight); + camCutouts.setPosition((FlxG.width - FlxG.initialWidth) / 2, (FlxG.height - FlxG.initialHeight) / 2); + camCutouts.setSize(FlxG.initialWidth, FlxG.initialHeight); camCutouts.bgColor.alpha = 0; // Show the game scene behind the camera. FlxG.cameras.reset(camGame); @@ -1817,21 +1833,24 @@ class PlayState extends MusicBeatSubState && !ControlsHandler.usingExternalInputDevice) healthBarYPos = FlxG.height * 0.1; #end - healthBarBG = FunkinSprite.create(0, healthBarYPos, 'healthBar'); + healthBarBG.y = healthBarYPos; healthBarBG.screenCenter(X); healthBarBG.scrollFactor.set(0, 0); healthBarBG.zIndex = 800; add(healthBarBG); - healthBar = new FlxBar(healthBarBG.x + 4, healthBarBG.y + 4, RIGHT_TO_LEFT, Std.int(healthBarBG.width - 8), Std.int(healthBarBG.height - 8), this, - 'healthLerp', 0, 2); + healthBar.x = healthBarBG.x + 4; + healthBar.y = healthBarBG.y + 4; + healthBar.parent = this; + healthBar.parentVariable = 'healthLerp'; healthBar.scrollFactor.set(); healthBar.createFilledBar(Constants.COLOR_HEALTH_BAR_RED, Constants.COLOR_HEALTH_BAR_GREEN); healthBar.zIndex = 801; add(healthBar); // The score text below the health bar. - scoreText = new FlxText(healthBarBG.x + healthBarBG.width - 190, healthBarBG.y + 30, 0, '', 20); + scoreText.x = healthBarBG.x + healthBarBG.width - 190; + scoreText.y = healthBarBG.y + 30; scoreText.setFormat(Paths.font('vcr.ttf'), 16, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); scoreText.scrollFactor.set(); scoreText.zIndex = 802; @@ -1899,7 +1918,7 @@ class PlayState extends MusicBeatSubState public function resetCameraZoom():Void { - if (PlayState.instance.isMinimalMode) return; + if (isMinimalMode) return; // Apply camera zoom level from stage data. currentCameraZoom = stageZoom; FlxG.camera.zoom = currentCameraZoom; @@ -1915,15 +1934,21 @@ class PlayState extends MusicBeatSubState { if (currentSong == null || currentChart == null) { - trace('Song difficulty could not be loaded.'); + throw 'Song difficulty could not be loaded.'; } - var currentCharacterData:SongCharacterData = currentChart.characters; // Switch the variation we are playing on by manipulating targetVariation. + var currentCharacterData:Null = currentChart?.characters; + if (currentCharacterData == null) + { + trace('Cannot retrieve character data'); + return; + } + // Switch the variation we are playing on by manipulating targetVariation. // // GIRLFRIEND // - var girlfriend:BaseCharacter = CharacterDataParser.fetchCharacter(currentCharacterData.girlfriend); + var girlfriend:Null = CharacterDataParser.fetchCharacter(currentCharacterData.girlfriend); if (girlfriend != null) { @@ -1941,7 +1966,7 @@ class PlayState extends MusicBeatSubState // // DAD // - var dad:BaseCharacter = CharacterDataParser.fetchCharacter(currentCharacterData.opponent); + var dad:Null = CharacterDataParser.fetchCharacter(currentCharacterData.opponent); if (dad != null) { @@ -1956,7 +1981,7 @@ class PlayState extends MusicBeatSubState iconP2.cameras = [camHUD]; #if FEATURE_DISCORD_RPC - discordRPCAlbum = 'album-${currentChart.album}'; + discordRPCAlbum = 'album-${currentChart?.album}'; discordRPCIcon = 'icon-${currentCharacterData.opponent}'; #end } @@ -1964,7 +1989,7 @@ class PlayState extends MusicBeatSubState // // BOYFRIEND // - var boyfriend:BaseCharacter = CharacterDataParser.fetchCharacter(currentCharacterData.player); + var boyfriend:Null = CharacterDataParser.fetchCharacter(currentCharacterData.player); if (boyfriend != null) { @@ -2025,13 +2050,7 @@ class PlayState extends MusicBeatSubState */ function initStrumlines():Void { - var noteStyleId:String = currentChart.noteStyle; - var noteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId); - if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault(); - - playerStrumline = new Strumline(noteStyle, !isBotPlayMode); playerStrumline.onNoteIncoming.add(onStrumlineNoteIncoming); - opponentStrumline = new Strumline(noteStyle, false); opponentStrumline.onNoteIncoming.add(onStrumlineNoteIncoming); add(playerStrumline); add(opponentStrumline); @@ -2076,6 +2095,7 @@ class PlayState extends MusicBeatSubState playerStrumline.strumlineScale.set(playerStrumlineScale, playerStrumlineScale); playerStrumline.setNoteSpacing(playerNoteSpacing); + @:nullSafety(Off) // Who thought it'd be a good idea to make the iterator nullable? for (strum in playerStrumline) { strum.width *= 2; @@ -2084,7 +2104,7 @@ class PlayState extends MusicBeatSubState playerStrumline.x = (FlxG.width - playerStrumline.width) / 2 + Constants.STRUMLINE_X_OFFSET; playerStrumline.y = (FlxG.height - playerStrumline.height) * 0.95 - Constants.STRUMLINE_Y_OFFSET; - if (currentChart.noteStyle != "pixel") + if (currentChart?.noteStyle != "pixel") { #if android playerStrumline.y += 10; #end } @@ -2095,6 +2115,35 @@ class PlayState extends MusicBeatSubState opponentStrumline.y = Constants.STRUMLINE_Y_OFFSET * 0.3; opponentStrumline.x -= 30; } + + function initPauseSprites() + { + pauseButton.animation.addByIndices('idle', 'back', [0], "", 24, false); + pauseButton.animation.addByIndices('hold', 'back', [5], "", 24, false); + pauseButton.animation.addByIndices('confirm', 'back', [ + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 + ], "", 24, false); + pauseButton.scale.set(0.8, 0.8); + pauseButton.updateHitbox(); + pauseButton.animation.play("idle"); + pauseButton.setPosition((FlxG.width - pauseButton.width) - 35, 35); + @:nullSafety(Off) // AAAAAAA why did camControls have to be nullable AAAAAAAAAA + pauseButton.cameras = [camControls]; + + pauseCircle.scale.set(0.84, 0.8); + pauseCircle.updateHitbox(); + @:nullSafety(Off) + pauseCircle.cameras = [camControls]; + pauseCircle.x = ((pauseButton.x + (pauseButton.width / 2)) - (pauseCircle.width / 2)); + pauseCircle.y = ((pauseButton.y + (pauseButton.height / 2)) - (pauseCircle.height / 2)); + pauseCircle.alpha = 0.1; + + add(pauseCircle); + add(pauseButton); + hitbox?.forEachAlive(function(hint:FunkinHint) { + hint.deadZones.push(pauseButton); + }); + } #end /** @@ -2102,11 +2151,7 @@ class PlayState extends MusicBeatSubState */ function initPopups():Void { - var noteStyleId:String = currentChart.noteStyle; - var noteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId); - if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault(); // Initialize the judgements and combo meter. - comboPopUps = new PopUpStuff(noteStyle); comboPopUps.zIndex = 900; add(comboPopUps); comboPopUps.cameras = [camHUD]; @@ -2172,8 +2217,12 @@ class PlayState extends MusicBeatSubState function buildDiscordRPCState():String { - var discordRPCDifficulty = PlayState.instance.currentDifficulty.replace('-', ' ').toTitleCase(); - return '${currentChart.songName} [${discordRPCDifficulty}]'; + if (currentChart == null) + { + trace("WARNING: Difficulty data for RPC is null."); + } + var discordRPCDifficulty = PlayState.instance?.currentDifficulty?.replace('-', ' ')?.toTitleCase() ?? '???'; + return '${currentChart?.songName ?? '???'} [${discordRPCDifficulty}]'; } function initPreciseInputs():Void @@ -2190,7 +2239,7 @@ class PlayState extends MusicBeatSubState { if (currentChart == null) { - trace('Song difficulty could not be loaded.'); + throw 'Song difficulty could not be loaded.'; } // Conductor.instance.forceBPM(currentChart.getStartingBPM()); @@ -2198,10 +2247,10 @@ class PlayState extends MusicBeatSubState if (!overrideMusic) { // Stop the vocals if they already exist. - if (vocals != null) vocals.stop(); - vocals = currentChart.buildVocals(currentInstrumental); + vocals?.stop(); + vocals = currentChart?.buildVocals(currentInstrumental); - if (vocals.members.length == 0) + if (vocals?.members?.length == 0) { trace('WARNING: No vocals found for this song.'); } @@ -2220,9 +2269,16 @@ class PlayState extends MusicBeatSubState */ function regenNoteData(startTime:Float = 0):Void { + if (currentChart == null) + { + trace('Cannot regenerate note data for null chart'); + return; + } + Highscore.tallies.combo = 0; Highscore.tallies = new Tallies(); + @:nullSafety(Off) var event:SongLoadScriptEvent = new SongLoadScriptEvent(currentChart.song.id, currentChart.difficulty, currentChart.notes.copy(), currentChart.getEvents()); dispatchEvent(event); @@ -2242,6 +2298,13 @@ class PlayState extends MusicBeatSubState var strumTime:Float = songNote.time; if (strumTime < startTime) continue; // Skip notes that are before the start time. + var scoreable = true; + if (songNote.kind != null) + { + var noteKind:Null = NoteKindManager.getNoteKind(songNote.kind ?? ''); + if (noteKind != null) scoreable = noteKind.scoreable; + } + var noteData:Int = songNote.getDirection(); var playerNote:Bool = true; @@ -2252,7 +2315,7 @@ class PlayState extends MusicBeatSubState case 0: playerNoteData.push(songNote); // increment totalNotes for total possible notes able to be hit by the player - Highscore.tallies.totalNotes++; + if (scoreable) Highscore.tallies.totalNotes++; case 1: opponentNoteData.push(songNote); } @@ -2341,7 +2404,7 @@ class PlayState extends MusicBeatSubState if (!overrideMusic && !isGamePaused && currentChart != null) { - currentChart.playInst(1.0, currentInstrumental, false); + currentChart?.playInst(1.0, currentInstrumental, false); } if (FlxG.sound.music == null) @@ -2362,19 +2425,23 @@ class PlayState extends MusicBeatSubState FlxG.sound.music.volume = 1.0; if (FlxG.sound.music.fadeTween != null) FlxG.sound.music.fadeTween.cancel(); - trace('Playing vocals...'); - add(vocals); + if (vocals != null) + { + trace('Playing vocals...'); + add(vocals); - vocals.time = startTimestamp - Conductor.instance.instrumentalOffset; - vocals.pitch = playbackRate; - vocals.volume = 1.0; + vocals.time = startTimestamp - Conductor.instance.instrumentalOffset; + vocals.pitch = playbackRate; + vocals.volume = 1.0; - // trace('STARTING SONG AT:'); - // trace('${FlxG.sound.music.time}'); - // trace('${vocals.time}'); + // trace('STARTING SONG AT:'); + // trace('${FlxG.sound.music.time}'); + // trace('${vocals.time}'); + + vocals.play(); + } FlxG.sound.music.play(); - vocals.play(); #if FEATURE_DISCORD_RPC // Updating Discord Rich Presence (with Time Left) @@ -2488,7 +2555,7 @@ class PlayState extends MusicBeatSubState */ function processNotes(elapsed:Float):Void { - if (playerStrumline?.notes?.members == null || opponentStrumline?.notes?.members == null) return; + if (playerStrumline.notes?.members == null || opponentStrumline.notes?.members == null) return; // Process notes on the opponent's side. for (note in opponentStrumline.notes.members) @@ -2517,7 +2584,7 @@ class PlayState extends MusicBeatSubState // Process hold notes on the opponent's side. for (holdNote in opponentStrumline.holdNotes.members) { - if (holdNote == null || !holdNote.alive) continue; + if (holdNote == null || !holdNote.alive || holdNote.noteData == null) continue; // While the hold note is being hit, and there is length on the hold note... if (holdNote.hitNote && !holdNote.missedNote && holdNote.sustainLength > 0) @@ -2536,7 +2603,7 @@ class PlayState extends MusicBeatSubState // We dropped a hold note. // Play miss animation, but don't penalize. - currentStage.getOpponent().playSingAnimation(holdNote.noteData.getDirection(), true); + if (currentStage != null) currentStage.getOpponent().playSingAnimation(holdNote.noteData.getDirection(), true); } } @@ -2649,7 +2716,7 @@ class PlayState extends MusicBeatSubState applyScore(event.score, '', event.healthChange, event.isComboBreak); // Play the miss sound. - vocals.playerVolume = 0; + if (vocals != null) vocals.playerVolume = 0; FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.5, 0.6)); } else @@ -2707,7 +2774,8 @@ class PlayState extends MusicBeatSubState while (inputPressQueue.length > 0) { - var input:PreciseInputEvent = inputPressQueue.shift(); + var input:Null = inputPressQueue.shift(); + if (input == null) continue; playerStrumline.pressKey(input.noteDirection); @@ -2759,7 +2827,8 @@ class PlayState extends MusicBeatSubState while (inputReleaseQueue.length > 0) { - var input:PreciseInputEvent = inputReleaseQueue.shift(); + var input:Null = inputReleaseQueue.shift(); + if (input == null) continue; // Play the strumline animation. playerStrumline.playStatic(input.noteDirection); @@ -2811,23 +2880,25 @@ class PlayState extends MusicBeatSubState } // Send the note hit event. - var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, isComboBreak, Highscore.tallies.combo + 1, noteDiff, - daRating == 'sick'); + var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, isComboBreak, + note.scoreable ? Highscore.tallies.combo + 1 : Highscore.tallies.combo, noteDiff, daRating == 'sick'); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! if (event.eventCanceled) return; - - Highscore.tallies.totalNotesHit++; // Display the hit on the strums playerStrumline.hitNote(note, !event.isComboBreak); if (event.doesNotesplash) playerStrumline.playNoteSplash(note.noteData.getDirection()); if (note.isHoldNote && note.holdNoteSprite != null) playerStrumline.playNoteHoldCover(note.holdNoteSprite); - vocals.playerVolume = 1; + if (vocals != null) vocals.playerVolume = 1; // Display the combo meter and add the calculation to the score. - applyScore(event.score, event.judgement, event.healthChange, event.isComboBreak); - popUpScore(event.judgement); + if (note.scoreable) + { + Highscore.tallies.totalNotesHit++; + applyScore(event.score, event.judgement, event.healthChange, event.isComboBreak); + popUpScore(event.judgement); + } } /** @@ -2854,13 +2925,13 @@ class PlayState extends MusicBeatSubState if (pressArray[i]) indices.push(i); } } - vocals.playerVolume = 0; + if (vocals != null) vocals.playerVolume = 0; applyScore(Scoring.getMissScore(), 'miss', healthChange, true); if (playSound) { - vocals.playerVolume = 0; + if (vocals != null) vocals.playerVolume = 0; FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.5, 0.6)); } } @@ -2906,7 +2977,7 @@ class PlayState extends MusicBeatSubState if (event.playSound) { - vocals.playerVolume = 0; + if (vocals != null) vocals.playerVolume = 0; FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2)); } } @@ -2941,7 +3012,7 @@ class PlayState extends MusicBeatSubState } else { - this.remove(currentStage); + if (currentStage != null) this.remove(currentStage); FlxG.switchState(() -> new ChartEditorState( { targetSongId: currentSong.id, @@ -3049,7 +3120,7 @@ class PlayState extends MusicBeatSubState comboPopUps.displayRating(daRating); if (combo >= 10) comboPopUps.displayCombo(combo); - vocals.playerVolume = 1; + if (vocals != null) vocals.playerVolume = 1; } /** @@ -3112,7 +3183,7 @@ class PlayState extends MusicBeatSubState public function endSong(rightGoddamnNow:Bool = false):Void { if (FlxG.sound.music != null) FlxG.sound.music.volume = 0; - vocals.volume = 0; + if (vocals != null) vocals.volume = 0; mayPauseGame = false; isSongEnd = true; @@ -3193,7 +3264,7 @@ class PlayState extends MusicBeatSubState if (Date.now().getDay() == 5) Medals.award(FridayNight); // Determine the score rank for this song we just finished. - var scoreRank:ScoringRank = Scoring.calculateRank( + var scoreRank:Null = Scoring.calculateRank( { score: songScore, tallies: @@ -3219,7 +3290,7 @@ class PlayState extends MusicBeatSubState if (currentVariation == 'pico' && !PlayStatePlaylist.isStoryMode) Medals.award(FreeplayPicoMix); if (currentVariation == 'pico' && currentSong.id == 'stress') Medals.award(FreeplayStressPico); - Events.logEarnRank(scoreRank.toString()); + if (scoreRank != null) Events.logEarnRank(scoreRank.toString()); } #end @@ -3235,7 +3306,7 @@ class PlayState extends MusicBeatSubState // Pop the next song ID from the list. // Returns null if the list is empty. - var targetSongId:String = PlayStatePlaylist.playlistSongIds.shift(); + var targetSongId:Null = PlayStatePlaylist.playlistSongIds.shift(); if (targetSongId == null) { @@ -3259,20 +3330,23 @@ class PlayState extends MusicBeatSubState }, }; - #if FEATURE_NEWGROUNDS - // Award a medal for beating a Story level. - Medals.awardStoryLevel(PlayStatePlaylist.campaignId); - - // Submit the score for the Story level to Newgrounds. - Leaderboards.submitLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, PlayStatePlaylist.campaignScore); - - Events.logCompleteLevel(PlayStatePlaylist.campaignId); - #end - - if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data)) + if (PlayStatePlaylist.campaignId != null) { - Save.instance.setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data); - isNewHighscore = true; + #if FEATURE_NEWGROUNDS + // Award a medal for beating a Story level. + Medals.awardStoryLevel(PlayStatePlaylist.campaignId); + + // Submit the score for the Story level to Newgrounds. + Leaderboards.submitLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, PlayStatePlaylist.campaignScore); + + Events.logCompleteLevel(PlayStatePlaylist.campaignId); + #end + + if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data)) + { + Save.instance.setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data); + isNewHighscore = true; + } } } @@ -3301,8 +3375,8 @@ class PlayState extends MusicBeatSubState FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransOut = true; - if (FlxG.sound.music != null) FlxG.sound.music.stop(); - vocals.stop(); + FlxG.sound.music?.stop(); + vocals?.stop(); // TODO: Softcode this cutscene. if (currentSong.id == 'eggnog') @@ -3316,13 +3390,13 @@ class PlayState extends MusicBeatSubState FunkinSound.playOnce(Paths.sound('Lights_Shut_off'), function() { // no camFollow so it centers on horror tree - var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId); + var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId) ?? throw 'Could not find a song with the ID $targetSongId'; var targetVariation:String = currentVariation; if (!targetSong.hasDifficulty(PlayStatePlaylist.campaignDifficulty, currentVariation)) { targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION; } - this.remove(currentStage); + if (currentStage != null) this.remove(currentStage); LoadingState.loadPlayState( { targetSong: targetSong, @@ -3334,13 +3408,13 @@ class PlayState extends MusicBeatSubState } else { - var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId); + var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId) ?? throw 'Could not find a song with ID $targetSongId'; var targetVariation:String = currentVariation; if (!targetSong.hasDifficulty(PlayStatePlaylist.campaignDifficulty, currentVariation)) { targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION; } - this.remove(currentStage); + if (currentStage != null) this.remove(currentStage); LoadingState.loadPlayState( { targetSong: targetSong, @@ -3462,20 +3536,24 @@ class PlayState extends MusicBeatSubState // If the opponent is GF, zoom in on the opponent. // Else, if there is no GF, zoom in on BF. // Else, zoom in on GF. - var targetDad:Bool = currentStage.getDad() != null && currentStage.getDad().characterId == 'gf'; - var targetBF:Bool = currentStage.getGirlfriend() == null && !targetDad; + var boyfriend:Null = currentStage?.getBoyfriend(); + var girlfriend:Null = currentStage?.getGirlfriend(); + var dad:Null = currentStage?.getDad(); - if (targetBF) + var targetDad:Bool = dad?.characterId == 'gf'; + var targetBF:Bool = girlfriend == null && !targetDad; + + if (targetBF && boyfriend != null) { - FlxG.camera.follow(currentStage.getBoyfriend(), null, 0.05); + FlxG.camera.follow(boyfriend, null, 0.05); } - else if (targetDad) + else if (targetDad && dad != null) { - FlxG.camera.follow(currentStage.getDad(), null, 0.05); + FlxG.camera.follow(dad, null, 0.05); } - else + else if (girlfriend != null) { - FlxG.camera.follow(currentStage.getGirlfriend(), null, 0.05); + FlxG.camera.follow(girlfriend, null, 0.05); } // TODO: Make target offset configurable. @@ -3497,15 +3575,15 @@ class PlayState extends MusicBeatSubState new FlxTimer().start(0.8, function(_) { if (targetBF) { - currentStage.getBoyfriend().animation.play('hey'); + boyfriend?.animation.play('hey'); } else if (targetDad) { - currentStage.getDad().animation.play('cheer'); + dad?.animation.play('cheer'); } else { - currentStage.getGirlfriend().animation.play('cheer'); + girlfriend?.animation.play('cheer'); } // Zoom over to the Results screen. @@ -3524,12 +3602,17 @@ class PlayState extends MusicBeatSubState */ function moveToResultsScreen(isNewHighscore:Bool, ?prevScoreData:SaveScoreData):Void { + var currentChart:SongDifficulty = currentChart ?? + { + trace('ERROR: Cannot move to results screen with a null chart.'); + return; + } + persistentUpdate = false; - vocals.stop(); + vocals?.stop(); camHUD.alpha = 1; var talliesToUse:Tallies = PlayStatePlaylist.isStoryMode ? Highscore.talliesLevel : Highscore.tallies; - var res:ResultState = new ResultState( { storyMode: PlayStatePlaylist.isStoryMode, @@ -3575,7 +3658,7 @@ class PlayState extends MusicBeatSubState /** * Resets the camera's zoom level and focus point. */ - public function resetCamera(?resetZoom:Bool = true, ?cancelTweens:Bool = true, ?snap:Bool = true):Void + public function resetCamera(resetZoom:Bool = true, cancelTweens:Bool = true, snap:Bool = true):Void { // Cancel camera tweens if any are active. if (cancelTweens) @@ -3598,7 +3681,7 @@ class PlayState extends MusicBeatSubState /** * Sets the camera follow point's position and tweens the camera there. */ - public function tweenCameraToPosition(?x:Float, ?y:Float, ?duration:Float, ?ease:NullFloat>):Void + public function tweenCameraToPosition(x:Float = 0, y:Float = 0, duration:Float = 0, ?ease:NullFloat>):Void { cameraFollowPoint.setPosition(x, y); tweenCameraToFollowPoint(duration, ease); @@ -3607,7 +3690,7 @@ class PlayState extends MusicBeatSubState /** * Disables camera following and tweens the camera to the follow point manually. */ - public function tweenCameraToFollowPoint(?duration:Float, ?ease:NullFloat>):Void + public function tweenCameraToFollowPoint(duration:Float = 0, ?ease:NullFloat>):Void { // Cancel the current tween if it's active. cancelCameraFollowTween(); @@ -3620,6 +3703,7 @@ class PlayState extends MusicBeatSubState else { // Disable camera following for the duration of the tween. + @:nullSafety(Off) FlxG.camera.target = null; // Follow tween! Caching it so we can cancel/pause it later if needed. @@ -3645,7 +3729,7 @@ class PlayState extends MusicBeatSubState /** * Tweens the camera zoom to the desired amount. */ - public function tweenCameraZoom(?zoom:Float, ?duration:Float, ?direct:Bool, ?ease:NullFloat>):Void + public function tweenCameraZoom(zoom:Float = 1, duration:Float = 0, direct:Bool = false, ?ease:NullFloat>):Void { // Cancel the current tween if it's active. cancelCameraZoomTween(); @@ -3666,7 +3750,7 @@ class PlayState extends MusicBeatSubState } } - public function cancelCameraZoomTween() + public function cancelCameraZoomTween():Void { if (cameraZoomTween != null) { @@ -3706,7 +3790,7 @@ class PlayState extends MusicBeatSubState for (i in strumlines) { - var value:Float = speed; + var value:Float = speed ?? 0; var strum:Strumline = Reflect.getProperty(this, i); if (duration == 0) diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index 9db60589c..e104f5eee 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -527,8 +527,7 @@ class ResultState extends MusicBeatSubState bgFlash.visible = true; FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24); // NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors. - var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : (params.scoreData.tallies.sick + params.scoreData.tallies.good - - params.scoreData.tallies.missed) / params.scoreData.tallies.totalNotes * 100; + var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : Scoring.tallyCompletion(params.scoreData.tallies) * 100; clearPercentTarget = Math.floor(clearPercentFloat); // Prevent off-by-one errors. @@ -744,6 +743,7 @@ class ResultState extends MusicBeatSubState super.draw(); songName.clipRect = FlxRect.get(Math.max(0, 520 - songName.x), 0, FlxG.width, songName.height); + clearPercentSmall.forEachAlive(spr -> spr.clipRect = FlxRect.get(Math.max(0, 520 - spr.x), 0, FlxG.width, spr.height)); // PROBABLY SHOULD FIX MEMORY FREE OR WHATEVER THE PUT() FUNCTION DOES !!!! FEELS LIKE IT STUTTERS!!! @@ -751,105 +751,6 @@ class ResultState extends MusicBeatSubState // maskShaderSongName.frameUV = songName.frame.uv; } - private function handleAnimationVibrations() - { - for (atlas in characterAtlasAnimations) - { - if (atlas == null || atlas.sprite == null) continue; - - switch (rank) - { - case ScoringRank.PERFECT | ScoringRank.PERFECT_GOLD: - switch (playerCharacterId) - { - // Feel the bed fun :freaky: - case "bf": - if (atlas.sprite.anim.curFrame > 87 && atlas.sprite.anim.curFrame % 5 == 0) - { - HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE); - break; - } - - // GF slams into the wall. - if (atlas.sprite.anim.curFrame == 51) - { - HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5); - break; - } - - // Pico drop-kicking Nene. - case "pico": - if (atlas.sprite.anim.curFrame == 52) - { - HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION * 5, Constants.MAX_VIBRATION_AMPLITUDE); - break; - } - - default: - break; - } - - case ScoringRank.GREAT | ScoringRank.EXCELLENT: - switch (playerCharacterId) - { - // Pico explodes the targets with a rocket launcher. - case "pico": - // Pico shoots. - if (atlas.sprite.anim.curFrame == 45) - { - HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5); - break; - } - - // The targets explode. - if (atlas.sprite.anim.curFrame == 50) - { - HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION, Constants.MAX_VIBRATION_AMPLITUDE); - break; - } - - default: - break; - } - - case ScoringRank.GOOD: - switch (playerCharacterId) - { - // Pico shooting the targets. - case "pico": - if (atlas.sprite.anim.curFrame % 2 != 0) continue; - - final frames:Array> = [[40, 50], [80, 90], [140, 157]]; - for (i in 0...frames.length) - { - if (atlas.sprite.anim.curFrame < frames[i][0] || atlas.sprite.anim.curFrame > frames[i][1]) continue; - - HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE); - break; - } - - default: - break; - } - - case ScoringRank.SHIT: - switch (playerCharacterId) - { - // BF falling and GF slams on BF with her ass. - case "bf": - if (atlas.sprite.anim.curFrame == 5 || atlas.sprite.anim.curFrame == 90) - { - HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD * 2, Constants.DEFAULT_VIBRATION_DURATION * 2, Constants.MAX_VIBRATION_AMPLITUDE); - break; - } - - default: - break; - } - } - } - } - override function update(elapsed:Float):Void { maskShaderDifficulty.swagSprX = difficulty.x; @@ -1035,8 +936,6 @@ class ResultState extends MusicBeatSubState #end } - if (HapticUtil.hapticsAvailable) handleAnimationVibrations(); - super.update(elapsed); } diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index d4448dcda..5b533c86a 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -288,33 +288,22 @@ class CharacterDataParser { var charPath:String = "freeplay/icons/"; - // FunkinCrew please dont skin me alive for copying pixelated icon and changing it a tiny bit - switch (char) + final charIDParts:Array = char.split("-"); + var iconName:String = ""; + for (i in 0...charIDParts.length) { - case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf" | "bf-dark": - charPath += "bfpixel"; - case "monster-christmas": - charPath += "monsterpixel"; - case "mom" | "mom-car": - charPath += "mommypixel"; - case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene": - charPath += "picopixel"; - case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen" | "gf-dark": - charPath += "gfpixel"; - case "dad": - charPath += "dadpixel"; - case "darnell-blazin": - charPath += "darnellpixel"; - case "senpai-angry": - charPath += "senpaipixel"; - case "spooky-dark": - charPath += "spookypixel"; - case "tankman-atlas" | "tankman-bloody": - charPath += "tankmanpixel"; - case "pico-christmas" | "pico-dark": - charPath += "picopixel"; - default: - charPath += '${char}pixel'; + iconName += charIDParts[i]; + + if (Assets.exists(Paths.image(charPath + '${iconName}pixel'))) + { + charPath += '${iconName}pixel'; + break; + } + else + { + if (i < charIDParts.length - 1) iconName += '-'; + continue; + } } if (!Assets.exists(Paths.image(charPath))) diff --git a/source/funkin/play/components/HealthIcon.hx b/source/funkin/play/components/HealthIcon.hx index 459869bd9..2d0b4cc78 100644 --- a/source/funkin/play/components/HealthIcon.hx +++ b/source/funkin/play/components/HealthIcon.hx @@ -151,10 +151,12 @@ class HealthIcon extends FunkinSprite */ public function toggleOldIcon():Void { + final playState:Null = PlayState.instance; + if (playState == null || playState.currentStage == null) return; if (characterId == 'bf-old') { - isPixel = PlayState.instance.currentStage.getBoyfriend().isPixel; - PlayState.instance.currentStage.getBoyfriend().initHealthIcon(false); + isPixel = playState.currentStage.getBoyfriend()?.isPixel ?? false; + playState.currentStage.getBoyfriend()?.initHealthIcon(false); } else { diff --git a/source/funkin/play/notes/NoteSprite.hx b/source/funkin/play/notes/NoteSprite.hx index 0962521fa..c6d07d718 100644 --- a/source/funkin/play/notes/NoteSprite.hx +++ b/source/funkin/play/notes/NoteSprite.hx @@ -10,6 +10,9 @@ class NoteSprite extends FunkinSprite { static final DIRECTION_COLORS:Array = ['purple', 'blue', 'green', 'red']; + /** + * The hold note sprite for this note. + */ public var holdNoteSprite:SustainTrail; var hsvShader:HSVShader; @@ -95,8 +98,23 @@ class NoteSprite extends FunkinSprite return this.direction; } + /** + * The note data associated with this note sprite. + * This is used to store the strum time, length, and other properties. + */ public var noteData:SongNoteData; + /** + * If this note kind is scoreable (i.e., counted towards score and accuracy) + * Only accessible in scripts + * Defaults to true + */ + public var scoreable:Bool = true; + + /** + * Whether this note is a hold note. + * This is true if the length is greater than 0. + */ public var isHoldNote(get, never):Bool; function get_isHoldNote():Bool diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 9fcb9594c..23ff49582 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -17,6 +17,7 @@ import funkin.play.notes.NoteVibrationsHandler; import funkin.data.song.SongData.SongNoteData; import funkin.util.SortUtil; import funkin.util.GRhythmUtil; +import funkin.play.notes.notekind.NoteKind; import funkin.play.notes.notekind.NoteKindManager; import flixel.math.FlxPoint; #if mobile @@ -96,9 +97,9 @@ class Strumline extends FlxSpriteGroup /** * Reset the scroll speed to the current chart's scroll speed. */ - public function resetScrollSpeed():Void + public function resetScrollSpeed(?newScrollSpeed:Float):Void { - scrollSpeed = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0; + scrollSpeed = newScrollSpeed ?? PlayState.instance?.currentChart?.scrollSpeed ?? 1.0; } var _conductorInUse:Null; @@ -183,7 +184,7 @@ class Strumline extends FlxSpriteGroup static final BACKGROUND_PAD:Int = 16; - public function new(noteStyle:NoteStyle, isPlayer:Bool) + public function new(noteStyle:NoteStyle, isPlayer:Bool, ?scrollSpeed:Float) { super(); @@ -235,14 +236,13 @@ class Strumline extends FlxSpriteGroup if (inArrowContorlSchemeMode && isPlayer) this.background.x -= 100; #end this.add(this.background); - strumlineScale = new FlxCallbackPoint(strumlineScaleCallback); strumlineScale = new FlxCallbackPoint(strumlineScaleCallback); this.refresh(); this.onNoteIncoming = new FlxTypedSignalVoid>(); - resetScrollSpeed(); + resetScrollSpeed(scrollSpeed); for (i in 0...KEY_COUNT) { @@ -1103,6 +1103,7 @@ class Strumline extends FlxSpriteGroup if (noteSprite != null) { + var noteKind:NoteKind = NoteKindManager.getNoteKind(note.kind); var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle; noteSprite.setupNoteGraphic(noteKindStyle); @@ -1127,6 +1128,7 @@ class Strumline extends FlxSpriteGroup noteSprite.x -= (noteSprite.width - Strumline.STRUMLINE_SIZE) / 2; // Center it noteSprite.x -= NUDGE; noteSprite.y = -9999; + if (noteKind != null) noteSprite.scoreable = noteKind.scoreable; } return noteSprite; diff --git a/source/funkin/play/notes/notekind/NoteKind.hx b/source/funkin/play/notes/notekind/NoteKind.hx index 9cc125a31..700d2f9b3 100644 --- a/source/funkin/play/notes/notekind/NoteKind.hx +++ b/source/funkin/play/notes/notekind/NoteKind.hx @@ -28,6 +28,13 @@ class NoteKind implements INoteScriptedClass */ public var params:Array; + /** + * If this note kind is scoreable (ie, counted towards score and accuracy) + * Only accessible in scripts + * Defaults to true + */ + public var scoreable:Bool = true; + public function new(noteKind:String, description:String = "", ?noteStyleId:String, ?params:Array) { this.noteKind = noteKind; diff --git a/source/funkin/play/notes/notekind/NoteKindManager.hx b/source/funkin/play/notes/notekind/NoteKindManager.hx index f1f0c11fe..3ae55133e 100644 --- a/source/funkin/play/notes/notekind/NoteKindManager.hx +++ b/source/funkin/play/notes/notekind/NoteKindManager.hx @@ -11,7 +11,21 @@ import funkin.play.notes.notekind.NoteKind.NoteKindParam; class NoteKindManager { - static var noteKinds:Map = []; + /** + * A map of all note kinds, keyed by their name. + * This is used to retrieve note kinds by their name. + */ + public static var noteKinds:Map = []; + + /** + * Retrieve a note kind by its name. + * @param noteKind The name of the note kind. + * @return The note kind, or null if it doesn't exist. + */ + public static function getNoteKind(noteKind:String):Null + { + return noteKinds.get(noteKind); + } public static function loadScripts():Void { diff --git a/source/funkin/play/scoring/Scoring.hx b/source/funkin/play/scoring/Scoring.hx index e2dab3b2f..eaa8efbd2 100644 --- a/source/funkin/play/scoring/Scoring.hx +++ b/source/funkin/play/scoring/Scoring.hx @@ -1,6 +1,7 @@ package funkin.play.scoring; import funkin.save.Save.SaveScoreData; +import funkin.save.Save.SaveScoreTallyData; /** * Which system to use when scoring and judging notes. @@ -374,8 +375,7 @@ class Scoring if (scoreData.tallies.totalNotes == 0) return null; // Perfect (Platinum) is a Sick Full Clear - var isPerfectGold = scoreData.tallies.sick == scoreData.tallies.totalNotes; - if (isPerfectGold) + if (scoreData.tallies.sick == scoreData.tallies.totalNotes) { return ScoringRank.PERFECT_GOLD; } @@ -384,21 +384,21 @@ class Scoring // Final Grade = (Sick + Good - Miss) / (Total Notes) - var grade = (scoreData.tallies.sick + scoreData.tallies.good - scoreData.tallies.missed) / scoreData.tallies.totalNotes; + var completionAmount:Float = Scoring.tallyCompletion(scoreData.tallies); - if (grade == Constants.RANK_PERFECT_THRESHOLD) + if (completionAmount == Constants.RANK_PERFECT_THRESHOLD) { return ScoringRank.PERFECT; } - else if (grade >= Constants.RANK_EXCELLENT_THRESHOLD) + else if (completionAmount >= Constants.RANK_EXCELLENT_THRESHOLD) { return ScoringRank.EXCELLENT; } - else if (grade >= Constants.RANK_GREAT_THRESHOLD) + else if (completionAmount >= Constants.RANK_GREAT_THRESHOLD) { return ScoringRank.GREAT; } - else if (grade >= Constants.RANK_GOOD_THRESHOLD) + else if (completionAmount >= Constants.RANK_GOOD_THRESHOLD) { return ScoringRank.GOOD; } @@ -407,6 +407,21 @@ class Scoring return ScoringRank.SHIT; } } + + /** + * Calculates the "completion" of a song, based on how many GOOD and SICK notes were hit, minus how many were missed + * Top secret funkin crew patented algorithm + * TODO: Could possibly move more of the "tallying" related handling here. + * In FreeplayState we make sure it's clamped between 0 and 1, and we probably always want to assume that? + * + * @param tallies + * @return Float Completion, as a float value between 0 and 1. If `tallies` is `null`, we return 0; + */ + public static function tallyCompletion(?tallies:SaveScoreTallyData):Float + { + if (tallies == null) return 0.0; + return (tallies.sick + tallies.good - tallies.missed) / tallies.totalNotes; + } } enum abstract ScoringRank(String) diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 1f6c66f3a..e77daa9b5 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -187,6 +187,7 @@ class Save theme: ChartEditorTheme.Light, playtestStartTime: false, downscroll: false, + showNoteKinds: true, metronomeVolume: 1.0, hitsoundVolumePlayer: 1.0, hitsoundVolumeOpponent: 1.0, @@ -358,6 +359,23 @@ class Save return data.optionsChartEditor.downscroll; } + public var chartEditorShowNoteKinds(get, set):Bool; + + function get_chartEditorShowNoteKinds():Bool + { + if (data.optionsChartEditor.showNoteKinds == null) data.optionsChartEditor.showNoteKinds = true; + + return data.optionsChartEditor.showNoteKinds; + } + + function set_chartEditorShowNoteKinds(value:Bool):Bool + { + // Set and apply. + data.optionsChartEditor.showNoteKinds = value; + flush(); + return data.optionsChartEditor.showNoteKinds; + } + public var chartEditorPlaytestStartTime(get, set):Bool; function get_chartEditorPlaytestStartTime():Bool @@ -882,14 +900,12 @@ 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 || previousCompletion > newCompletion) ? previousScoreData.tallies : newScoreData.tallies + tallies: (previousRank > newRank + || Scoring.tallyCompletion(previousScoreData.tallies) > Scoring.tallyCompletion(newScoreData.tallies)) ? previousScoreData.tallies : newScoreData.tallies }; song.set(difficultyId, newScore); @@ -1356,6 +1372,45 @@ class Save { FileUtil.saveFile(haxe.io.Bytes.ofString(this.serialize()), [FileUtil.FILE_FILTER_JSON], null, null, './save.json', 'Write save data as JSON...'); } + + #if FEATURE_NEWGROUNDS + public static function saveToNewgrounds():Void + { + if (_instance == null) return; + trace('[SAVE] Saving Save Data to Newgrounds...'); + funkin.api.newgrounds.NGSaveSlot.instance.save(_instance.data); + } + + public static function loadFromNewgrounds(onFinish:Void->Void):Void + { + trace('[SAVE] Loading Save Data from Newgrounds...'); + funkin.api.newgrounds.NGSaveSlot.instance.load(function(data:Dynamic) { + FlxG.save.bind('$SAVE_NAME${BASE_SAVE_SLOT}', SAVE_PATH); + + if (FlxG.save.status != EMPTY) + { + // best i can do in case the NG file is corrupted or something along those lines + var backupSlot:Int = Save.archiveBadSaveData(FlxG.save.data); + trace('[SAVE] Backed up current save data in case of emergency to $backupSlot!'); + } + + FlxG.save.erase(); + FlxG.save.bind('$SAVE_NAME${BASE_SAVE_SLOT}', SAVE_PATH); // forces regeneration of the file as erase deletes it + + var gameSave = SaveDataMigrator.migrate(data); + FlxG.save.mergeData(gameSave.data, true); + _instance = gameSave; + onFinish(); + }, function(error:io.newgrounds.Call.CallError) { + var errorMsg:String = io.newgrounds.Call.CallErrorTools.toString(error); + + var msg = 'There was an error loading your save data from Newgrounds.'; + msg += '\n${errorMsg}'; + msg += '\nAre you sure you are connected to the internet?'; + lime.app.Application.current.window.alert(msg, "Newgrounds Save Slot Failure"); + }); + } + #end } /** @@ -1805,6 +1860,12 @@ typedef SaveDataChartEditorOptions = */ var ?downscroll:Bool; + /** + * Show Note Kind Indicator in the Chart Editor. + * @default `true` + */ + var ?showNoteKinds:Bool; + /** * Metronome volume in the Chart Editor. * @default `1.0` diff --git a/source/funkin/ui/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx index da70ec012..af71b0f6c 100644 --- a/source/funkin/ui/MusicBeatState.hx +++ b/source/funkin/ui/MusicBeatState.hx @@ -12,6 +12,7 @@ import funkin.modding.PolymodHandler; import funkin.modding.events.ScriptEvent; import funkin.modding.module.ModuleHandler; import funkin.util.SortUtil; +import funkin.util.WindowUtil; import funkin.input.Controls; #if mobile import funkin.graphics.FunkinCamera; @@ -156,7 +157,11 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler function handleFunctionControls():Void { // Emergency exit button. - if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState()); + if (FlxG.keys.justPressed.F4) + { + FlxG.switchState(() -> new MainMenuState()); + WindowUtil.setWindowTitle('Friday Night Funkin\''); + } } override function update(elapsed:Float) diff --git a/source/funkin/ui/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx index 42bf8d249..0298c1097 100644 --- a/source/funkin/ui/MusicBeatSubState.hx +++ b/source/funkin/ui/MusicBeatSubState.hx @@ -10,6 +10,7 @@ import funkin.modding.IScriptedClass.IEventHandler; import funkin.modding.module.ModuleHandler; import funkin.modding.PolymodHandler; import funkin.util.SortUtil; +import funkin.util.WindowUtil; import flixel.util.FlxSort; import funkin.input.Controls; #if mobile @@ -140,7 +141,11 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler super.update(elapsed); // Emergency exit button. - if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState()); + if (FlxG.keys.justPressed.F4) + { + FlxG.switchState(() -> new MainMenuState()); + WindowUtil.setWindowTitle('Friday Night Funkin\''); + } // Display Conductor info in the watch window. FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0); diff --git a/source/funkin/ui/PixelatedIcon.hx b/source/funkin/ui/PixelatedIcon.hx index 880ed7897..7a01a023a 100644 --- a/source/funkin/ui/PixelatedIcon.hx +++ b/source/funkin/ui/PixelatedIcon.hx @@ -21,33 +21,25 @@ class PixelatedIcon extends FlxFilteredSprite { var charPath:String = "freeplay/icons/"; - switch (char) + final charIDParts:Array = char.split("-"); + var iconName:String = ""; + for (i in 0...charIDParts.length) { - case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf": - charPath += "bfpixel"; - case "monster-christmas": - charPath += "monsterpixel"; - case "mom" | "mom-car": - charPath += "mommypixel"; - case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene": - charPath += "picopixel"; - case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen": - charPath += "gfpixel"; - case "dad": - charPath += "dadpixel"; - case "darnell-blazin": - charPath += "darnellpixel"; - case "senpai-angry": - charPath += "senpaipixel"; - case "spooky-dark": - charPath += "spookypixel"; - case "tankman-atlas" | "tankman-bloody": - charPath += "tankmanpixel"; - default: - charPath += '${char}pixel'; + iconName += charIDParts[i]; + + if (Assets.exists(Paths.image(charPath + '${iconName}pixel'))) + { + charPath += '${iconName}pixel'; + break; + } + else + { + if (i < charIDParts.length - 1) iconName += '-'; + continue; + } } - if (!openfl.utils.Assets.exists(Paths.image(charPath))) + if (!Assets.exists(Paths.image(charPath))) { trace('[WARN] Character ${char} has no freeplay icon.'); this.visible = false; @@ -58,7 +50,7 @@ class PixelatedIcon extends FlxFilteredSprite this.visible = true; } - var isAnimated = openfl.utils.Assets.exists(Paths.file('images/$charPath.xml')); + var isAnimated = Assets.exists(Paths.file('images/$charPath.xml')); if (isAnimated) { diff --git a/source/funkin/ui/charSelect/CharSelectSubState.hx b/source/funkin/ui/charSelect/CharSelectSubState.hx index b1d113e14..692274774 100644 --- a/source/funkin/ui/charSelect/CharSelectSubState.hx +++ b/source/funkin/ui/charSelect/CharSelectSubState.hx @@ -271,6 +271,12 @@ class CharSelectSubState extends MusicBeatSubState nametag.midpointX += cutoutSize; add(nametag); + @:privateAccess + { + nametag.midpointY += 200; + FlxTween.tween(nametag, {midpointY: nametag.midpointY - 200}, 1, {ease: FlxEase.expoOut}); + } + nametag.scrollFactor.set(); FlxG.debugger.addTrackerProfile(new TrackerProfile(FlxSprite, ["x", "y", "alpha", "scale", "blend"])); @@ -739,6 +745,7 @@ class CharSelectSubState extends MusicBeatSubState FlxTween.tween(cursorConfirmed, {alpha: 0}, 0.8, {ease: FlxEase.expoOut}); FlxTween.tween(barthing, {y: barthing.y + 80}, 0.8, {ease: FlxEase.backIn}); + FlxTween.tween(nametag, {y: nametag.y + 80}, 0.8, {ease: FlxEase.backIn}); FlxTween.tween(dipshitBacking, {y: dipshitBacking.y + 210}, 0.8, {ease: FlxEase.backIn}); FlxTween.tween(chooseDipshit, {y: chooseDipshit.y + 200}, 0.8, {ease: FlxEase.backIn}); FlxTween.tween(dipshitBlur, {y: dipshitBlur.y + 220}, 0.8, {ease: FlxEase.backIn}); diff --git a/source/funkin/ui/credits/CreditsDataHandler.hx b/source/funkin/ui/credits/CreditsDataHandler.hx index 844d0f4db..79ac5fcb3 100644 --- a/source/funkin/ui/credits/CreditsDataHandler.hx +++ b/source/funkin/ui/credits/CreditsDataHandler.hx @@ -15,17 +15,18 @@ class CreditsDataHandler static final CREDITS_DATA_PATH:String = "assets/data/credits.json"; #end + #if macro public static function debugPrint(data:Null):Void { if (data == null) { - trace('CreditsData(NULL)'); + Sys.println('[INFO] CreditsData(NULL)'); return; } if (data.entries == null || data.entries.length == 0) { - trace('CreditsData(EMPTY)'); + Sys.println('[INFO] CreditsData(EMPTY)'); return; } @@ -36,8 +37,9 @@ class CreditsDataHandler lineCount += entry?.body?.length ?? 0; } - trace('CreditsData($entryCount entries containing $lineCount lines)'); + Sys.println('[INFO] CreditsData($entryCount entries containing $lineCount lines)'); } + #end /** * If for some reason the full credits won't load, diff --git a/source/funkin/ui/credits/CreditsDataMacro.hx b/source/funkin/ui/credits/CreditsDataMacro.hx index bb1233b36..c49e34d2f 100644 --- a/source/funkin/ui/credits/CreditsDataMacro.hx +++ b/source/funkin/ui/credits/CreditsDataMacro.hx @@ -10,7 +10,7 @@ class CreditsDataMacro public static macro function loadCreditsData():haxe.macro.Expr.ExprOf { #if !display - trace('Hardcoding credits data...'); + Sys.println('[INFO] Hardcoding credits data...'); var json = CreditsDataMacro.fetchJSON(); if (json == null) diff --git a/source/funkin/ui/debug/DebugMenuSubState.hx b/source/funkin/ui/debug/DebugMenuSubState.hx index 3f5183819..3b06a4c58 100644 --- a/source/funkin/ui/debug/DebugMenuSubState.hx +++ b/source/funkin/ui/debug/DebugMenuSubState.hx @@ -4,6 +4,7 @@ import flixel.math.FlxPoint; import flixel.FlxObject; import flixel.FlxSprite; import funkin.ui.MusicBeatSubState; +import funkin.ui.FullScreenScaleMode; import funkin.audio.FunkinSound; import funkin.ui.TextMenuList; import funkin.ui.debug.charting.ChartEditorState; @@ -37,7 +38,7 @@ class DebugMenuSubState extends MusicBeatSubState // Create the green background. var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat')); menuBG.color = 0xFF4CAF50; - menuBG.setGraphicSize(Std.int(menuBG.width * 1.1)); + menuBG.setGraphicSize(Std.int(menuBG.width * 1.1 * FullScreenScaleMode.wideScale.x)); menuBG.updateHitbox(); menuBG.screenCenter(); menuBG.scrollFactor.set(0, 0); diff --git a/source/funkin/ui/debug/anim/DebugBoundingState.hx b/source/funkin/ui/debug/anim/DebugBoundingState.hx index 73be8b208..ccef6eb4a 100644 --- a/source/funkin/ui/debug/anim/DebugBoundingState.hx +++ b/source/funkin/ui/debug/anim/DebugBoundingState.hx @@ -100,6 +100,7 @@ class DebugBoundingState extends FlxState offsetAnimationDropdown = offsetEditorDialog.findComponent("animationDropdown", DropDown); offsetEditorDialog.cameras = [hudCam]; + offsetEditorDialog.closable = false; add(offsetEditorDialog); offsetEditorDialog.showDialog(false); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 10ec9a84a..0f2b6b946 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -94,6 +94,9 @@ import haxe.ui.components.Button; import haxe.ui.components.DropDown; import haxe.ui.components.Label; import haxe.ui.components.Slider; +import haxe.ui.containers.dialogs.Dialogs; +import haxe.ui.containers.dialogs.Dialog.DialogButton; +import haxe.ui.containers.dialogs.MessageBox.MessageBoxType; import haxe.ui.containers.dialogs.CollapsibleDialog; import haxe.ui.containers.menus.Menu; import haxe.ui.containers.menus.MenuBar; @@ -631,6 +634,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState return isViewDownscroll; } + /** + * Whether to show an indicator if a note is of a non-default kind. + */ + var showNoteKindIndicators:Bool = false; + /** * The current theme used by the editor. * Dictates the appearance of many UI elements. @@ -1855,6 +1863,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var menubarItemDownscroll:MenuCheckBox; + /** + * The `View -> Note Kind Indicator` menu item. + */ + var menubarItemViewIndicators:MenuCheckBox; + /** * The `View -> Increase Difficulty` menu item. */ @@ -2358,6 +2371,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState noteSnapQuantIndex = save.chartEditorNoteQuant; currentLiveInputStyle = save.chartEditorLiveInputStyle; isViewDownscroll = save.chartEditorDownscroll; + showNoteKindIndicators = save.chartEditorShowNoteKinds; playtestStartTime = save.chartEditorPlaytestStartTime; currentTheme = save.chartEditorTheme; metronomeVolume = save.chartEditorMetronomeVolume; @@ -2387,6 +2401,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState save.chartEditorNoteQuant = noteSnapQuantIndex; save.chartEditorLiveInputStyle = currentLiveInputStyle; save.chartEditorDownscroll = isViewDownscroll; + save.chartEditorShowNoteKinds = showNoteKindIndicators; save.chartEditorPlaytestStartTime = playtestStartTime; save.chartEditorTheme = currentTheme; save.chartEditorMetronomeVolume = metronomeVolume; @@ -2519,7 +2534,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState add(gridTiledSprite); gridTiledSprite.zIndex = 10; - gridGhostNote = new ChartEditorNoteSprite(this); + gridGhostNote = new ChartEditorNoteSprite(this, true); gridGhostNote.alpha = 0.6; gridGhostNote.noteData = new SongNoteData(0, 0, 0, "", []); gridGhostNote.visible = false; @@ -3089,6 +3104,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemDownscroll.onClick = event -> isViewDownscroll = event.value; menubarItemDownscroll.selected = isViewDownscroll; + menubarItemViewIndicators.onClick = event -> showNoteKindIndicators = menubarItemViewIndicators.selected; + menubarItemViewIndicators.selected = showNoteKindIndicators; + menubarItemDifficultyUp.onClick = _ -> incrementDifficulty(1); menubarItemDifficultyDown.onClick = _ -> incrementDifficulty(-1); @@ -3921,6 +3939,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState selectionSquare.width = selectionSquare.height = GRID_SIZE; selectionSquare.color = FlxColor.RED; } + + // Additional cleanup on notes. + if (noteTooltipsDirty) noteSprite.updateTooltipText(); } for (eventSprite in renderedEvents.members) @@ -5163,7 +5184,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var variationMetadata:Null = songMetadata.get(selectedVariation); if (variationMetadata != null) - variationMetadata.playData.difficulties.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL)); + variationMetadata.playData.difficulties.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL)); var difficultyToolbox:ChartEditorDifficultyToolbox = cast this.getToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT); if (difficultyToolbox == null) return; @@ -5599,7 +5620,20 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState @:nullSafety(Off) function quitChartEditor():Void { - autoSave(); + if (saveDataDirty) + { + Dialogs.messageBox("You are about to leave the editor without saving.\n\nAre you sure?", "Leave Editor", MessageBoxType.TYPE_YESNO, true, + function(button:DialogButton) { + if (button == DialogButton.YES) + { + autoSave(); + quitChartEditor(); + } + }); + + return; + } + stopWelcomeMusic(); // TODO: PR Flixel to make onComplete nullable. if (audioInstTrack != null) audioInstTrack.onComplete = null; @@ -5859,9 +5893,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var startTimestamp:Float = 0; if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs; - var playbackRate:Float = ((menubarItemPlaybackSpeed.value ?? 1.0) * 2.0) / 100.0; - playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% - playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% + var playbackRate:Float = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0; + playbackRate = Math.round(playbackRate / 0.05) * 0.05; // Round to nearest 5% + playbackRate = FlxMath.clamp(playbackRate, 0.05, 2.0); // Clamp to 5% to 200% var targetSong:Song; try @@ -6296,7 +6330,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { currentScrollEase = Math.max(0, targetScrollPosition); currentScrollEase = Math.min(currentScrollEase, songLengthInPixels); - scrollPositionInPixels = MathUtil.snap(MathUtil.smoothLerpPrecision(scrollPositionInPixels, currentScrollEase, FlxG.elapsed, SCROLL_EASE_DURATION, 1 / 1000), currentScrollEase, 1 / 1000); + scrollPositionInPixels = MathUtil.snap(MathUtil.smoothLerpPrecision(scrollPositionInPixels, currentScrollEase, FlxG.elapsed, SCROLL_EASE_DURATION, + 1 / 1000), currentScrollEase, 1 / 1000); moveSongToScrollPosition(); } @@ -6331,20 +6366,30 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState fadeInWelcomeMusic(WELCOME_MUSIC_FADE_IN_DELAY, WELCOME_MUSIC_FADE_IN_DURATION); - // Reapply the volume. - var instTargetVolume:Float = menubarItemVolumeInstrumental.value / 100.0 ?? 1.0; - var vocalPlayerTargetVolume:Float = menubarItemVolumeVocalsPlayer.value / 100.0 ?? 1.0; - var vocalOpponentTargetVolume:Float = menubarItemVolumeVocalsOpponent.value / 100.0 ?? 1.0; + // Reapply the volume and playback rate. + var instTargetVolume:Float = (menubarItemVolumeInstrumental.value / 100.0) ?? 1.0; + var vocalPlayerTargetVolume:Float = (menubarItemVolumeVocalsPlayer.value / 100.0) ?? 1.0; + var vocalOpponentTargetVolume:Float = (menubarItemVolumeVocalsOpponent.value / 100.0) ?? 1.0; + + var playbackRate = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0; + playbackRate = Math.round(playbackRate / 0.05) * 0.05; // Round to nearest 5% + playbackRate = FlxMath.clamp(playbackRate, 0.05, 2.0); // Clamp to 5% to 200% if (audioInstTrack != null) { audioInstTrack.volume = instTargetVolume; + #if FLX_PITCH + audioInstTrack.pitch = playbackRate; + #end audioInstTrack.onComplete = null; } if (audioVocalTrackGroup != null) { audioVocalTrackGroup.playerVolume = vocalPlayerTargetVolume; audioVocalTrackGroup.opponentVolume = vocalOpponentTargetVolume; + #if FLX_PITCH + audioVocalTrackGroup.pitch = playbackRate; + #end } } @@ -6499,6 +6544,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState public function postLoadInstrumental():Void { + // Reapply the volume and playback rate. + var instTargetVolume:Float = ((menubarItemVolumeInstrumental.value / 100) ?? 1.0); + var playbackRate:Float = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0; + playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% + playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% if (audioInstTrack != null) { // Prevent the time from skipping back to 0 when the song ends. @@ -6511,6 +6561,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } audioVocalTrackGroup.pause(); }; + audioInstTrack.volume = instTargetVolume; + #if FLX_PITCH + audioInstTrack.pitch = playbackRate; + #end } else { @@ -6527,6 +6581,25 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState healthIconsDirty = true; } + public function postLoadVocals():Void + { + // Reapply the volume and playback rate. + var vocalPlayerTargetVolume:Float = (menubarItemVolumeVocalsPlayer.value / 100.0) ?? 1.0; + var vocalOpponentTargetVolume:Float = (menubarItemVolumeVocalsOpponent.value / 100.0) ?? 1.0; + var playbackRate:Float = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0; + playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% + playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% + + if (audioVocalTrackGroup != null) + { + audioVocalTrackGroup.playerVolume = vocalPlayerTargetVolume; + audioVocalTrackGroup.opponentVolume = vocalOpponentTargetVolume; + #if FLX_PITCH + audioVocalTrackGroup.pitch = playbackRate; + #end + } + } + function hardRefreshOffsetsToolbox():Void { var offsetsToolbox:ChartEditorOffsetsToolbox = cast this.getToolbox(CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT); diff --git a/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx index 17cd2bc64..de03dde59 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx @@ -1,5 +1,7 @@ package funkin.ui.debug.charting.components; +import flixel.text.FlxText; +import flixel.util.FlxColor; import flixel.FlxObject; import flixel.FlxSprite; import flixel.graphics.frames.FlxFramesCollection; @@ -10,6 +12,9 @@ import funkin.data.song.SongData.SongNoteData; import funkin.data.notestyle.NoteStyleRegistry; import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.NoteDirection; +import haxe.ui.tooltips.ToolTipRegionOptions; +import funkin.util.HaxeUIUtil; +import haxe.ui.tooltips.ToolTipManager; /** * A sprite that can be used to display a note in a chart. @@ -63,11 +68,21 @@ class ChartEditorNoteSprite extends FlxSprite return overrideData; } - public function new(parent:ChartEditorState) + public var isGhost:Bool = false; + public var tooltip:ToolTipRegionOptions; + + /** + * An indicator if the note is a note kind different than Default (""). + */ + public var kindIndicator:FlxText = new FlxText(5, 5, 100, '*', 16); + + public function new(parent:ChartEditorState, isGhost:Bool = false) { super(); this.parentState = parent; + this.isGhost = isGhost; + this.tooltip = HaxeUIUtil.buildTooltip('N/A'); var entries:Array = NoteStyleRegistry.instance.listEntryIds(); @@ -89,6 +104,8 @@ class ChartEditorNoteSprite extends FlxSprite { addNoteStyleAnimations(fetchNoteStyle(entry)); } + + kindIndicator.setFormat("VCR OSD Mono", 24, FlxColor.YELLOW, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); } static var noteFrameCollection:Null = null; @@ -156,6 +173,7 @@ class ChartEditorNoteSprite extends FlxSprite if (this.noteData == null) { this.kill(); + updateTooltipPosition(); return this.noteData; } @@ -167,7 +185,7 @@ class ChartEditorNoteSprite extends FlxSprite // Update the position to match the note data. updateNotePosition(); - + updateTooltipText(); return this.noteData; } @@ -194,6 +212,50 @@ class ChartEditorNoteSprite extends FlxSprite this.x += origin.x; this.y += origin.y; } + + this.updateTooltipPosition(); + } + + public function updateTooltipText():Void + { + if (this.noteData == null) return; + if (this.isGhost) return; + this.tooltip.tipData = {text: this.noteData.buildTooltip()}; + } + + public function updateTooltipPosition():Void + { + // No tooltip for ghost sprites. + if (this.isGhost) return; + + if (this.noteData == null || (this.tooltip.tipData?.text ?? "").length == 0) + { + // Disable the tooltip. + ToolTipManager.instance.unregisterTooltipRegion(this.tooltip); + } + else + { + // Update the position. + this.tooltip.left = this.x; + this.tooltip.top = this.y; + this.tooltip.width = this.width; + this.tooltip.height = this.height; + + // Enable the tooltip. + ToolTipManager.instance.registerTooltipRegion(this.tooltip); + } + } + + override public function draw() + { + super.draw(); + + if (!parentState.showNoteKindIndicators) return; + if ((this.noteData?.kind ?? "").length == 0) return; // Do not render the note kind indicator if the note kind is default. + + kindIndicator.x = this.x; + kindIndicator.y = this.y; + kindIndicator.draw(); } function get_noteStyle():Null diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 61a831b45..f01464893 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -137,6 +137,8 @@ class ChartEditorAudioHandler result = playVocals(state, DAD, opponentId, instId); // if (!result) return false; + state.postLoadVocals(); + state.hardRefreshOffsetsToolbox(); state.hardRefreshFreeplayToolbox(); diff --git a/source/funkin/ui/debug/stageeditor/handlers/AssetDataHandler.hx b/source/funkin/ui/debug/stageeditor/handlers/AssetDataHandler.hx index 283ff12db..0aca1dfdb 100644 --- a/source/funkin/ui/debug/stageeditor/handlers/AssetDataHandler.hx +++ b/source/funkin/ui/debug/stageeditor/handlers/AssetDataHandler.hx @@ -192,7 +192,7 @@ class AssetDataHandler for (daFrame in obj.frames.frames) { - xml += ' \n'; + xml += ' \n'; } xml += ""; diff --git a/source/funkin/ui/freeplay/FreeplayDJ.hx b/source/funkin/ui/freeplay/FreeplayDJ.hx index c91bf991b..bd424f1ba 100644 --- a/source/funkin/ui/freeplay/FreeplayDJ.hx +++ b/source/funkin/ui/freeplay/FreeplayDJ.hx @@ -81,8 +81,6 @@ class FreeplayDJ extends FlxAtlasSprite public override function update(elapsed:Float):Void { - super.update(elapsed); - switch (currentState) { case Intro: @@ -185,6 +183,8 @@ class FreeplayDJ extends FlxAtlasSprite default: // I shit myself. } + + super.update(elapsed); } function onFinishAnim(name:String):Void diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index e67394e3b..f0a4d4391 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -31,6 +31,7 @@ import funkin.input.Controls; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEventDispatcher; import funkin.play.PlayStatePlaylist; +import funkin.play.scoring.Scoring; import funkin.play.scoring.Scoring.ScoringRank; import funkin.play.song.Song; import funkin.save.Save; @@ -119,6 +120,11 @@ class FreeplayState extends MusicBeatSubState */ public static final SONGS_POS_MULTI:Float = 0.75; + /** + * For positioning the difficulty dots. + */ + public static final DEFAULT_DOTS_GROUP_POS:Array = [260, 170]; + var songs:Array> = []; var curSelected:Int = 0; @@ -169,17 +175,6 @@ class FreeplayState extends MusicBeatSubState return grpCapsules.members[curSelected]; } - var coolColors:Array = [ - 0xFF9271FD, - 0xFF9271FD, - 0xFF223344, - 0xFF941653, - 0xFFFC96D7, - 0xFFA0D1FF, - 0xFFFF78BF, - 0xFFF6B604 - ]; - var grpCapsules:FlxTypedGroup; var dj:Null = null; @@ -315,7 +310,7 @@ class FreeplayState extends MusicBeatSubState grpCapsules = new FlxTypedGroup(); grpDifficulties = new FlxTypedSpriteGroup(-300, 80); - difficultyDots = new FlxTypedSpriteGroup(203, 170); + difficultyDots = new FlxTypedSpriteGroup(DEFAULT_DOTS_GROUP_POS[0], DEFAULT_DOTS_GROUP_POS[1]); letterSort = new LetterSort((CUTOUT_WIDTH * SONGS_POS_MULTI) + 400, 75); rankBg = new FunkinSprite(0, 0); rankVignette = new FlxSprite(0, 0).loadGraphic(Paths.image('freeplay/rankVignette')); @@ -1270,7 +1265,6 @@ class FreeplayState extends MusicBeatSubState }); new FlxTimer().start(2, _ -> { - // dj.fistPump(); prepForNewRank = false; }); } @@ -1295,9 +1289,22 @@ class FreeplayState extends MusicBeatSubState function refreshDots(amount:Int, index:Int, prevIndex:Int):Void { var distance:Int = 30; + var groupOffset:Float = 14.7; var shiftAmt:Float = (distance * amount) / 2; var daSong:Null = currentCapsule.freeplayData; + final maxDotsPerRow:Int = 8; + if (difficultyDots.group.members.length > maxDotsPerRow) + { + difficultyDots.x = DEFAULT_DOTS_GROUP_POS[0] - groupOffset * (maxDotsPerRow - 1); + } + else + { + difficultyDots.x = DEFAULT_DOTS_GROUP_POS[0] - groupOffset * (difficultyDots.group.members.length - 1); + } + + var curRow:Int = 0; + var curDot:Int = 0; for (i in 0...difficultyDots.group.members.length) { // if (difficultyDots.group.members[i] == null) continue; @@ -1329,7 +1336,16 @@ class FreeplayState extends MusicBeatSubState } difficultyDots.group.members[i].visible = true; - difficultyDots.group.members[i].x = (CUTOUT_WIDTH * DJ_POS_MULTI) + ((difficultyDots.x + (distance * i)) - shiftAmt); + difficultyDots.group.members[i].x = (CUTOUT_WIDTH * DJ_POS_MULTI) + ((difficultyDots.x + (distance * curDot)) - shiftAmt); + difficultyDots.group.members[i].y = DEFAULT_DOTS_GROUP_POS[1] + distance * curRow; + + curDot++; + + if (curDot >= maxDotsPerRow) + { + curDot = 0; + curRow++; + } if (daSong?.data.hasDifficulty(diffId, daSong?.data.getFirstValidVariation(diffId, currentCharacter)) == false) { @@ -1582,12 +1598,10 @@ class FreeplayState extends MusicBeatSubState } if (controls.FREEPLAY_FAVORITE && controls.active) favoriteSong(); - if (controls.FREEPLAY_JUMP_TO_TOP && controls.active) changeSelection(-curSelected); - if (controls.FREEPLAY_JUMP_TO_BOTTOM && controls.active) changeSelection(grpCapsules.countLiving() - curSelected - 1); - calculateCompletion(); + lerpScoreDisplays(); handleInputs(elapsed); @@ -1597,7 +1611,7 @@ class FreeplayState extends MusicBeatSubState if (allowPicoBulletsVibration) HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5); } - function calculateCompletion():Void + function lerpScoreDisplays():Void { lerpScore = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpScore, intendedScore, FlxG.elapsed, 0.2), intendedScore, 1); lerpCompletion = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpCompletion, intendedCompletion, FlxG.elapsed, 0.5), intendedCompletion, 1 / 100); @@ -2179,8 +2193,6 @@ class FreeplayState extends MusicBeatSubState */ function changeDiff(change:Int = 0, force:Bool = false, capsuleAnim:Bool = false):Void { - if (!controls.active) return; - if (capsuleAnim) { if (currentCapsule != null) @@ -2275,11 +2287,10 @@ class FreeplayState extends MusicBeatSubState var songScore:Null = Save.instance.getSongScore(daSong.data.id, currentDifficulty, currentVariation); intendedScore = songScore?.score ?? 0; - intendedCompletion = songScore == null ? 0.0 : Math.max(0, - ((songScore.tallies.sick + songScore.tallies.good - songScore.tallies.missed) / songScore.tallies.totalNotes)); + intendedCompletion = Math.max(0, Scoring.tallyCompletion(songScore?.tallies)); rememberedDifficulty = currentDifficulty; if (!capsuleAnim) generateSongList(currentFilter, false, true, true); - currentCapsule.refreshDisplay((prepForNewRank == true) ? false : true); + currentCapsule.refreshDisplay(!prepForNewRank); } else { @@ -2390,11 +2401,6 @@ class FreeplayState extends MusicBeatSubState { trace('RANDOM SELECTED'); - controls.active = false; - #if NO_FEATURE_TOUCH_CONTROLS - letterSort.inputEnabled = false; - #end - var availableSongCapsules:Array = grpCapsules.members.filter(function(cap:SongMenuItem) { // Dead capsules are ones which were removed from the list when changing filters. return cap.alive && cap.freeplayData != null; @@ -2420,6 +2426,10 @@ class FreeplayState extends MusicBeatSubState // Seeing if I can do an animation... curSelected = grpCapsules.members.indexOf(targetSong); changeSelection(0); // Trigger an update. + controls.active = false; + #if NO_FEATURE_TOUCH_CONTROLS + letterSort.inputEnabled = false; + #end // Act like we hit Confirm on that song. capsuleOnConfirmDefault(targetSong); @@ -2587,26 +2597,28 @@ class FreeplayState extends MusicBeatSubState new FlxTimer().start(styleData?.getStartDelay(), function(tmr:FlxTimer) { FunkinSound.emptyPartialQueue(); - Paths.setCurrentLevel(cap?.freeplayData?.levelId); - LoadingState.loadPlayState( - { - targetSong: targetSong, - targetDifficulty: currentDifficulty, - targetVariation: currentVariation, - targetInstrumental: targetInstId, - practiceMode: false, - minimalMode: false, + funnyCam.fade(FlxColor.BLACK, 0.2, false, function() { + Paths.setCurrentLevel(cap?.freeplayData?.levelId); + LoadingState.loadPlayState( + { + targetSong: targetSong, + targetDifficulty: currentDifficulty, + targetVariation: currentVariation, + targetInstrumental: targetInstId, + practiceMode: false, + minimalMode: false, - #if FEATURE_DEBUG_FUNCTIONS - botPlayMode: FlxG.keys.pressed.SHIFT, - #else - botPlayMode: false, - #end - // TODO: Make these an option! It's currently only accessible via chart editor. - // startTimestamp: 0.0, - // playbackRate: 0.5, - // botPlayMode: true, - }, true); + #if FEATURE_DEBUG_FUNCTIONS + botPlayMode: FlxG.keys.pressed.SHIFT, + #else + botPlayMode: false, + #end + // TODO: Make these an option! It's currently only accessible via chart editor. + // startTimestamp: 0.0, + // playbackRate: 0.5, + // botPlayMode: true, + }, true); + }); }); } @@ -2654,6 +2666,7 @@ class FreeplayState extends MusicBeatSubState capsule.targetPos.y = capsule.intendedY(index - curSelectedFloat); capsule.targetPos.x = capsule.intendedX(index - curSelectedFloat) + (CUTOUT_WIDTH * SONGS_POS_MULTI); + if (index + 0.5 < curSelectedFloat) capsule.targetPos.y -= 100; } if (curSelected != prevSelected) @@ -2693,26 +2706,18 @@ class FreeplayState extends MusicBeatSubState if (!prepForNewRank && curSelected != prevSelected) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); - var daSongCapsule:SongMenuItem = currentCapsule; - if (daSongCapsule.freeplayData != null) - { - var songScore:Null = Save.instance.getSongScore(daSongCapsule.freeplayData.data.id, currentDifficulty, currentVariation); - intendedScore = songScore?.score ?? 0; - intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + - songScore.tallies.good - songScore.tallies.missed) / songScore.tallies.totalNotes); - rememberedSongId = daSongCapsule.freeplayData.data.id; - changeDiff(); - daSongCapsule.refreshDisplay((prepForNewRank == true) ? false : true); - } + var songScore:Null = Save.instance.getSongScore(currentCapsule.freeplayData?.data.id ?? "", currentDifficulty, currentVariation); + intendedScore = songScore?.score ?? 0; + + intendedCompletion = Scoring.tallyCompletion(songScore?.tallies); + rememberedSongId = currentCapsule.freeplayData?.data.id; + + if (currentCapsule.freeplayData == null) albumRoll.albumId = null; + + changeDiff(); + if (currentCapsule.freeplayData == null) currentCapsule.refreshDisplay(); else - { - intendedScore = 0; - intendedCompletion = 0.0; - rememberedSongId = null; - albumRoll.albumId = null; - changeDiff(); - daSongCapsule.refreshDisplay(); - } + currentCapsule.refreshDisplay(!prepForNewRank); for (index => capsule in grpCapsules.members) { @@ -2725,16 +2730,15 @@ class FreeplayState extends MusicBeatSubState capsule.targetPos.y = capsule.intendedY(index - curSelected); capsule.targetPos.x = capsule.intendedX(index - curSelected) + (CUTOUT_WIDTH * SONGS_POS_MULTI); - if (index < curSelected #if FEATURE_TOUCH_CONTROLS - && ControlsHandler.usingExternalInputDevice #end) capsule.targetPos.y -= 100; // another 100 for good measure + if (index < curSelected) capsule.targetPos.y -= 100; // another 100 for good measure } if (grpCapsules.countLiving() > 0 && !prepForNewRank && controls.active) { - playCurSongPreview(daSongCapsule); + playCurSongPreview(currentCapsule); currentCapsule.selected = true; - // switchBackingImage(daSongCapsule.freeplayData); + // switchBackingImage(currentCapsule.freeplayData); } // Small vibrations every selection change. @@ -2752,7 +2756,7 @@ class FreeplayState extends MusicBeatSubState overrideExisting: true, restartTrack: false }); - FlxG.sound.music.fadeIn(2, 0, 0.8); + FlxG.sound.music.fadeIn(2, 0, 0.7); } else { @@ -2789,7 +2793,7 @@ class FreeplayState extends MusicBeatSubState end: 0.2 }, onLoad: function() { - FlxG.sound.music.fadeIn(2, 0, 0.4); + FlxG.sound.music.fadeIn(2, 0, 0.7); } }); if (songDifficulty != null) diff --git a/source/funkin/ui/freeplay/LetterSort.hx b/source/funkin/ui/freeplay/LetterSort.hx index d1a654c56..6251a74c9 100644 --- a/source/funkin/ui/freeplay/LetterSort.hx +++ b/source/funkin/ui/freeplay/LetterSort.hx @@ -148,18 +148,22 @@ class LetterSort extends FlxSpriteGroup public function changeSelection(diff:Int = 0, playSound:Bool = true):Void { - doLetterChangeAnims(diff); + @:privateAccess + if (instance.controls.active) + { + doLetterChangeAnims(diff); - var multiPosOrNeg:Float = diff > 0 ? 1 : -1; + var multiPosOrNeg:Float = diff > 0 ? 1 : -1; - // if we're moving left (diff < 0), we want control of the right arrow, and vice versa - var arrowToMove:FlxSprite = diff < 0 ? leftArrow : rightArrow; - arrowToMove.offset.x = 3 * multiPosOrNeg; + // if we're moving left (diff < 0), we want control of the right arrow, and vice versa + var arrowToMove:FlxSprite = diff < 0 ? leftArrow : rightArrow; + arrowToMove.offset.x = 3 * multiPosOrNeg; - new FlxTimer().start(2 / 24, function(_) { - arrowToMove.offset.x = 0; - }); - if (playSound && diff != 0) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); + new FlxTimer().start(2 / 24, function(_) { + arrowToMove.offset.x = 0; + }); + if (playSound && diff != 0) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); + } } /** diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx index a91394aec..61f966f42 100644 --- a/source/funkin/ui/freeplay/SongMenuItem.hx +++ b/source/funkin/ui/freeplay/SongMenuItem.hx @@ -49,8 +49,6 @@ class SongMenuItem extends FlxSpriteGroup public var fakeRanking:FreeplayRank; - var ranks:Array = ["fail", "average", "great", "excellent", "perfect", "perfectsick"]; - public var targetPos:FlxPoint = new FlxPoint(); public var doLerp:Bool = false; public var doJumpIn:Bool = false; @@ -69,12 +67,9 @@ class SongMenuItem extends FlxSpriteGroup public var newText:FlxSprite; - // public var weekType:FlxSprite; - public var bigNumbers:Array = []; - - public var smallNumbers:Array = []; - - public var weekNumbers:Array = []; + var difficultyNumbers:Array = []; // referred to as "bignumbers" in the .fla file! + var bpmNumbers:Array = []; // referred to as "smallnumbers" in the .fla file! + var weekNumbers:Array = []; var impactThing:FunkinSprite; @@ -131,18 +126,18 @@ class SongMenuItem extends FlxSpriteGroup for (i in 0...2) { - var bigNumber:CapsuleNumber = new CapsuleNumber(466 + (i * 30), 32, true, 0); - add(bigNumber); + var num:CapsuleNumber = new CapsuleNumber(466 + (i * 30), 32, true, 0); + add(num); - bigNumbers.push(bigNumber); + difficultyNumbers.push(num); } for (i in 0...3) { - var smallNumber:CapsuleNumber = new CapsuleNumber(185 + (i * 11), 88.5, false, 0); - add(smallNumber); + var num:CapsuleNumber = new CapsuleNumber(185 + (i * 11), 88.5, false, 0); + add(num); - smallNumbers.push(smallNumber); + bpmNumbers.push(num); } // doesn't get added, simply is here to help with visibility of things for the pop in! @@ -334,38 +329,38 @@ class SongMenuItem extends FlxSpriteGroup shiftX = 186; } - for (i in 0...smallNumbers.length) + for (i in 0...bpmNumbers.length) { - smallNumbers[i].x = this.x + (shiftX + (i * 11)); + bpmNumbers[i].x = this.x + (shiftX + (i * 11)); switch (i) { case 0: if (newBPM < 100) { - smallNumbers[i].digit = 0; + bpmNumbers[i].digit = 0; } else { - smallNumbers[i].digit = Math.floor(newBPM / 100) % 10; + bpmNumbers[i].digit = Math.floor(newBPM / 100) % 10; } case 1: if (newBPM < 10) { - smallNumbers[i].digit = 0; + bpmNumbers[i].digit = 0; } else { - smallNumbers[i].digit = Math.floor(newBPM / 10) % 10; + bpmNumbers[i].digit = Math.floor(newBPM / 10) % 10; if (Math.floor(newBPM / 10) % 10 == 1) tempShift = -4; } case 2: - smallNumbers[i].digit = newBPM % 10; + bpmNumbers[i].digit = newBPM % 10; default: trace('why the fuck is this being called'); } - smallNumbers[i].x += tempShift; + bpmNumbers[i].x += tempShift; } // diffRatingSprite.loadGraphic(Paths.image('freeplay/diffRatings/diff${ratingPadded}')); // diffRatingSprite.visible = false; @@ -439,21 +434,21 @@ class SongMenuItem extends FlxSpriteGroup { var ratingPadded:String = newRating < 10 ? '0$newRating' : '$newRating'; - for (i in 0...bigNumbers.length) + for (i in 0...difficultyNumbers.length) { switch (i) { case 0: if (newRating < 10) { - bigNumbers[i].digit = 0; + difficultyNumbers[i].digit = 0; } else { - bigNumbers[i].digit = Math.floor(newRating / 10); + difficultyNumbers[i].digit = Math.floor(newRating / 10); } case 1: - bigNumbers[i].digit = newRating % 10; + difficultyNumbers[i].digit = newRating % 10; default: trace('why the fuck is this being called'); } diff --git a/source/funkin/ui/freeplay/backcards/BackingCard.hx b/source/funkin/ui/freeplay/backcards/BackingCard.hx index 34e5c5398..a005e315e 100644 --- a/source/funkin/ui/freeplay/backcards/BackingCard.hx +++ b/source/funkin/ui/freeplay/backcards/BackingCard.hx @@ -16,7 +16,6 @@ import funkin.modding.IScriptedClass.IStateChangingScriptedClass; import funkin.modding.events.ScriptEvent; import funkin.ui.FullScreenScaleMode; import funkin.util.BitmapUtil; -import openfl.utils.Assets; /** * A class for the backing cards so they dont have to be part of freeplayState...... diff --git a/source/funkin/ui/freeplay/backcards/NewCharacterCard.hx b/source/funkin/ui/freeplay/backcards/NewCharacterCard.hx index b703b0561..bb05cfe22 100644 --- a/source/funkin/ui/freeplay/backcards/NewCharacterCard.hx +++ b/source/funkin/ui/freeplay/backcards/NewCharacterCard.hx @@ -10,7 +10,6 @@ import funkin.graphics.adobeanimate.FlxAtlasSprite; import funkin.modding.events.ScriptEvent; import openfl.display.BlendMode; import funkin.util.BitmapUtil; -import openfl.utils.Assets; class NewCharacterCard extends BackingCard { diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index 72bfbb950..1bc3afbec 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -268,10 +268,8 @@ class MainMenuState extends MusicBeatState // reset camera when debug menu is closed subStateClosed.add(_ -> resetCamStuff(false)); - // TODO: Why does this specific function break with null safety? - @:nullSafety(Off) subStateOpened.add((sub:FlxSubState) -> { - if (Type.getClass(sub) == FreeplayState) + if (Std.isOfType(sub, FreeplayState)) { new FlxTimer().start(0.5, _ -> { magenta.visible = false; diff --git a/source/funkin/ui/options/OffsetMenu.hx b/source/funkin/ui/options/OffsetMenu.hx index 7e6a1af8b..78419ddc4 100644 --- a/source/funkin/ui/options/OffsetMenu.hx +++ b/source/funkin/ui/options/OffsetMenu.hx @@ -545,6 +545,7 @@ class OffsetMenu extends Page if (FlxG.sound.music.time < _lastTime) { localConductor.update(FlxG.sound.music.time, !calibrating); + b = localConductor.currentBeatTime; // Update arrows to be the correct distance away from the receptor. var lastArrowBeat:Float = 0; @@ -558,7 +559,7 @@ class OffsetMenu extends Page } if (calibrating) { - arrowBeat = lastArrowBeat + 2; + arrowBeat = lastArrowBeat; } else arrowBeat = 4; @@ -566,6 +567,10 @@ class OffsetMenu extends Page testStrumline.clean(); testStrumline.noteData = []; testStrumline.nextNoteIndex = 0; + trace('Restarting conductor'); + + _lastTime = FlxG.sound.music.time; + return; } _lastBeat = b; @@ -608,7 +613,7 @@ class OffsetMenu extends Page countText.text = 'Current Offset: ' + Std.int(appliedOffsetLerp) + 'ms'; var toRemove:Array = []; - + var _lastArrowBeat:Float = 0; // Update arrows for (i in 0...arrows.length) { @@ -629,12 +634,13 @@ class OffsetMenu extends Page arrow.sprite.alpha -= elapsed * 5; } - if (arrow.sprite.alpha <= 0) + if (arrow.beat == _lastArrowBeat || arrow.sprite.alpha <= 0) { toRemove.push(arrow); arrow.sprite.kill(); - // arrow.debugText.kill(); + continue; } + _lastArrowBeat = arrow.beat; } // Remove arrows that are marked for removal. diff --git a/source/funkin/ui/options/OptionsState.hx b/source/funkin/ui/options/OptionsState.hx index ed456034d..ebf538dff 100644 --- a/source/funkin/ui/options/OptionsState.hx +++ b/source/funkin/ui/options/OptionsState.hx @@ -69,10 +69,11 @@ class OptionsState extends MusicBeatState optionsCodex = new Codex(Options); add(optionsCodex); - var options:OptionsMenu = optionsCodex.addPage(Options, new OptionsMenu()); + var saveData:SaveDataMenu = optionsCodex.addPage(SaveData, new SaveDataMenu()); + var options:OptionsMenu = optionsCodex.addPage(Options, new OptionsMenu(saveData)); var preferences:PreferencesMenu = optionsCodex.addPage(Preferences, new PreferencesMenu()); var controls:ControlsMenu = optionsCodex.addPage(Controls, new ControlsMenu()); - #if FEATURE_INPUT_OFFSETS + #if FEATURE_LAG_ADJUSTMENT var offsets:OffsetMenu = optionsCodex.addPage(Offsets, new OffsetMenu()); #end @@ -81,9 +82,10 @@ class OptionsState extends MusicBeatState options.onExit.add(exitToMainMenu); controls.onExit.add(exitControls); preferences.onExit.add(optionsCodex.switchPage.bind(Options)); - #if FEATURE_INPUT_OFFSETS + #if FEATURE_LAG_ADJUSTMENT offsets.onExit.add(exitOffsets); #end + saveData.onExit.add(optionsCodex.switchPage.bind(Options)); } else { @@ -159,7 +161,7 @@ class OptionsMenu extends Page final CAMERA_MARGIN:Int = 150; - public function new() + public function new(saveDataMenu:SaveDataMenu) { super(); add(items = new TextMenuList()); @@ -172,8 +174,8 @@ class OptionsMenu extends Page // createItem("CONTROL SCHEMES", function() { // FlxG.state.openSubState(new ControlsSchemeMenu()); // }); - #if FEATURE_INPUT_OFFSETS - createItem("INPUT OFFSETS", function() { + #if FEATURE_LAG_ADJUSTMENT + createItem("LAG ADJUSTMENT", function() { FlxG.sound.music.fadeOut(0.5, 0, function(tw) { FunkinSound.playMusic('offsetsLoop', { @@ -196,7 +198,7 @@ class OptionsMenu extends Page #end #if android createItem("OPEN DATA FOLDER", function() { - funkin.mobile.external.android.DataFolderUtil.openDataFolder(); + funkin.external.android.DataFolderUtil.openDataFolder(); }); #end #if FEATURE_NEWGROUNDS @@ -227,9 +229,19 @@ class OptionsMenu extends Page }); } #end - createItem("CLEAR SAVE DATA", function() { - promptClearSaveData(); - }); + + // no need to show an entire new menu for just one option + if (saveDataMenu.hasMultipleOptions()) + { + createItem("SAVE DATA OPTIONS", function() { + codex.switchPage(SaveData); + }); + } + else + { + createItem("CLEAR SAVE DATA", saveDataMenu.openSaveDataPrompt); + } + #if NO_FEATURE_TOUCH_CONTROLS createItem("EXIT", exit); #else @@ -277,7 +289,6 @@ class OptionsMenu extends Page override function update(elapsed:Float):Void { - enabled = (prompt == null); #if FEATURE_TOUCH_CONTROLS backButton.active = (!goingBack) ? !items.busy : true; #end @@ -298,31 +309,6 @@ class OptionsMenu extends Page { return items.length > 2; } - - var prompt:Prompt; - - function promptClearSaveData():Void - { - if (prompt != null) return; - prompt = new Prompt("This will delete - \nALL your save data. - \nAre you sure? - ", Custom("Delete", "Cancel")); - prompt.create(); - prompt.createBgFromMargin(100, 0xFFFAFD6D); - prompt.back.scrollFactor.set(0, 0); - add(prompt); - prompt.onYes = function() { - // Clear the save data. - funkin.save.Save.clearData(); - FlxG.switchState(() -> new funkin.InitState()); - }; - prompt.onNo = function() { - prompt.close(); - prompt.destroy(); - prompt = null; - }; - } } enum abstract OptionsMenuPageName(String) to PageName @@ -333,4 +319,5 @@ enum abstract OptionsMenuPageName(String) to PageName var Mods = "mods"; var Preferences = "preferences"; var Offsets = "offsets"; + var SaveData = "saveData"; } diff --git a/source/funkin/ui/options/PreferencesMenu.hx b/source/funkin/ui/options/PreferencesMenu.hx index e149ac17b..6433c1dba 100644 --- a/source/funkin/ui/options/PreferencesMenu.hx +++ b/source/funkin/ui/options/PreferencesMenu.hx @@ -65,7 +65,6 @@ class PreferencesMenu extends Page createPrefDescription(); camFollow = new FlxObject(FlxG.width / 2, 0, 140, 70); - if (items != null) camFollow.y = items.selectedItem.y; menuCamera.follow(camFollow, null, 0.085); var margin = 160; @@ -73,7 +72,6 @@ class PreferencesMenu extends Page menuCamera.minScrollY = 0; items.onChange.add(function(selected) { - camFollow.y = selected.y; itemDesc.text = preferenceDesc[items.selectedIndex]; }); @@ -204,6 +202,9 @@ class PreferencesMenu extends Page { super.update(elapsed); + // Positions the camera to the selected item. + if (items != null) camFollow.y = items.selectedItem.y; + // Indent the selected item. items.forEach(function(daItem:TextMenuItem) { var thyOffset:Int = 0; diff --git a/source/funkin/ui/options/SaveDataMenu.hx b/source/funkin/ui/options/SaveDataMenu.hx new file mode 100644 index 000000000..e5f3d34ca --- /dev/null +++ b/source/funkin/ui/options/SaveDataMenu.hx @@ -0,0 +1,125 @@ +package funkin.ui.options; + +#if FEATURE_NEWGROUNDS +import funkin.api.newgrounds.NewgroundsClient; +#end +import funkin.save.Save; + +class SaveDataMenu extends Page +{ + var items:TextMenuList; + + public function new() + { + super(); + + add(items = new TextMenuList()); + + createItem("CLEAR SAVE DATA", openSaveDataPrompt); + + #if FEATURE_NEWGROUNDS + if (NewgroundsClient.instance.isLoggedIn()) + { + createItem("LOAD FROM NG", function() { + openConfirmPrompt("This will overwrite + \nALL your save data. + \nAre you sure? + ", "Overwrite", function() { + Save.loadFromNewgrounds(function() { + FlxG.switchState(() -> new funkin.InitState()); + }); + }); + }); + + createItem("SAVE TO NG", function() { + openConfirmPrompt("This will overwrite + \nALL save data saved + \non NG. Are you sure?", "Overwrite", function() { + Save.saveToNewgrounds(); + }); + }); + + createItem("CLEAR NG SAVE DATA", function() { + openConfirmPrompt("This will delete + \nALL save data saved + \non NG. Are you sure?", "Delete", function() { + funkin.api.newgrounds.NGSaveSlot.instance.clear(); + }); + }); + } + #end + + createItem("EXIT", exit); + } + + function createItem(name:String, callback:Void->Void, fireInstantly = false) + { + var item = items.createItem(0, 100 + items.length * 100, name, BOLD, callback); + item.fireInstantly = fireInstantly; + item.screenCenter(X); + return item; + } + + override function update(elapsed:Float) + { + enabled = (prompt == null); + super.update(elapsed); + } + + override function set_enabled(value:Bool) + { + items.enabled = value; + return super.set_enabled(value); + } + + var prompt:Prompt; + + function openConfirmPrompt(text:String, yesText:String, onYes:Void->Void, ?groupToOpenOn:Null):Void + { + if (prompt != null) return; + + prompt = new Prompt(text, Custom(yesText, "Cancel")); + prompt.create(); + prompt.createBgFromMargin(100, 0xFFFAFD6D); + prompt.back.scrollFactor.set(0, 0); + FlxG.state.add(prompt); + + prompt.onYes = function() { + onYes(); + + if (prompt != null) + { + prompt.close(); + prompt.destroy(); + prompt = null; + } + }; + + prompt.onNo = function() { + prompt.close(); + prompt.destroy(); + prompt = null; + } + } + public function openSaveDataPrompt() + { + openConfirmPrompt("This will delete + \nALL your save data. + \nAre you sure? + ", "Delete", function() { + // Clear the save data. + Save.clearData(); + + FlxG.switchState(() -> new funkin.InitState()); + }); + } + + /** + * True if this page has multiple options, excluding the exit option. + * If false, there's no reason to ever show this page. + */ + public function hasMultipleOptions():Bool + { + return items.length > 2; + } +} diff --git a/source/funkin/ui/options/items/NumberPreferenceItem.hx b/source/funkin/ui/options/items/NumberPreferenceItem.hx index 2f292f909..2972c9d44 100644 --- a/source/funkin/ui/options/items/NumberPreferenceItem.hx +++ b/source/funkin/ui/options/items/NumberPreferenceItem.hx @@ -148,6 +148,6 @@ class NumberPreferenceItem extends TextMenuItem function toFixed(value:Float):Float { var multiplier:Float = Math.pow(10, precision); - return Math.floor(value * multiplier) / multiplier; + return Math.round(value * multiplier) / multiplier; } } diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index e160173e8..b83e2c89f 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -25,7 +25,6 @@ import funkin.ui.transition.stickers.StickerSubState; import funkin.util.MathUtil; import funkin.util.SwipeUtil; import funkin.util.TouchUtil; -import openfl.utils.Assets; import funkin.ui.FullScreenScaleMode; #if FEATURE_DISCORD_RPC import funkin.api.discord.DiscordClient; @@ -605,12 +604,14 @@ class StoryMenuState extends MusicBeatState var targetVariation:String = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty); - LoadingState.loadPlayState( - { - targetSong: targetSong, - targetDifficulty: PlayStatePlaylist.campaignDifficulty, - targetVariation: targetVariation - }, true); + FlxG.camera.fade(FlxColor.BLACK, 0.2, false, function() { + LoadingState.loadPlayState( + { + targetSong: targetSong, + targetDifficulty: PlayStatePlaylist.campaignDifficulty, + targetVariation: targetVariation + }, true); + }); }); } diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index b5aef5b44..3ae94b5c6 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -228,11 +228,11 @@ class TitleState extends MusicBeatState { FlxG.bitmapLog.add(FlxG.camera.buffer); - #if desktop + #if (desktop || android) // Pressing BACK on the title screen should close the game. // This lets you exit without leaving fullscreen mode. - // Only applicable on desktop. - if (controls.BACK) + // Only applicable on desktop and Android. + if (#if android FlxG.android.justReleased.BACK || #end controls.BACK) { openfl.Lib.application.window.close(); } @@ -282,7 +282,7 @@ class TitleState extends MusicBeatState if (gamepad != null) { - if (gamepad.justPressed.START) pressedEnter = true; + if (gamepad.justPressed.START || gamepad.justPressed.ACCEPT) pressedEnter = true; } // If you spam Enter, we should skip the transition. diff --git a/source/funkin/ui/transition/LoadingState.hx b/source/funkin/ui/transition/LoadingState.hx index fd8579a42..be0d17240 100644 --- a/source/funkin/ui/transition/LoadingState.hx +++ b/source/funkin/ui/transition/LoadingState.hx @@ -12,6 +12,7 @@ import funkin.graphics.shaders.ScreenWipeShader; import funkin.play.PlayState; import funkin.play.PlayStatePlaylist; import funkin.play.song.Song.SongDifficulty; +import funkin.play.stage.Stage; import haxe.io.Path; import lime.app.Future; import lime.app.Promise; @@ -21,6 +22,7 @@ import lime.utils.Assets as LimeAssets; import openfl.filters.ShaderFilter; import openfl.utils.Assets as OpenFLAssets; +@:nullSafety class LoadingState extends MusicBeatSubState { inline static var MIN_TIME = 1.0; @@ -30,18 +32,21 @@ class LoadingState extends MusicBeatSubState var target:NextState; var playParams:Null; var stopMusic:Bool = false; - var callbacks:MultiCallback; + var callbacks:Null; var danceLeft:Bool = false; var loadBar:FlxSprite; var funkay:FlxSprite; - function new(target:NextState, stopMusic:Bool, playParams:Null = null) + function new(target:NextState, stopMusic:Bool, ?playParams:PlayStateParams) { super(); this.target = target; this.playParams = playParams; this.stopMusic = stopMusic; + + this.loadBar = new FunkinSprite(0, FlxG.height - 20).makeSolidColor(0, 10, 0xFFff16d2); + this.funkay = FunkinSprite.create('funkay'); } override function create():Void @@ -49,14 +54,12 @@ class LoadingState extends MusicBeatSubState var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, 0xFFcaff4d); add(bg); - funkay = FunkinSprite.create('funkay'); funkay.setGraphicSize(0, FlxG.height); funkay.updateHitbox(); add(funkay); funkay.scrollFactor.set(); funkay.screenCenter(); - loadBar = new FunkinSprite(0, FlxG.height - 20).makeSolidColor(0, 10, 0xFFff16d2); add(loadBar); initSongsManifest().onComplete(function(lib) { @@ -66,15 +69,21 @@ class LoadingState extends MusicBeatSubState if (playParams != null) { // Load and cache the song's charts. - if (playParams.targetSong != null) + if (playParams.targetSong == null) { - playParams.targetSong.cacheCharts(true); + throw 'Invalid parameter: Target song should not be null'; } + playParams.targetSong.cacheCharts(true); + // Preload the song for the play state. var difficulty:String = playParams.targetDifficulty ?? Constants.DEFAULT_DIFFICULTY; var variation:String = playParams.targetVariation ?? Constants.DEFAULT_VARIATION; - var targetChart:SongDifficulty = playParams.targetSong?.getDifficulty(difficulty, variation); + var targetChart:Null = playParams.targetSong.getDifficulty(difficulty, variation); + if (targetChart == null) + { + throw 'Couldn\'t retrieve chart data for song "${playParams.targetSong.songName}" on difficulty "$difficulty" and variation "$variation"'; + } var instPath:String = targetChart.getInstPath(playParams.targetInstrumental); var voicesPaths:Array = targetChart.buildVoiceList(); @@ -105,9 +114,9 @@ class LoadingState extends MusicBeatSubState // library.types.set(symbolPath, SOUND); // @:privateAccess // library.pathGroups.set(symbolPath, [library.__cacheBreak(symbolPath)]); - var callback = callbacks.add('song:' + path); + var callback = callbacks?.add('song:' + path); Assets.loadSound(path).onComplete(function(_) { - callback(); + if (callback != null) callback(); }); } } @@ -120,9 +129,9 @@ class LoadingState extends MusicBeatSubState @:privateAccess if (!LimeAssets.libraryPaths.exists(library)) throw 'Missing library: ' + library; - var callback = callbacks.add('library:' + library); + var callback = callbacks?.add('library:' + library); Assets.loadLibrary(library).onComplete(function(_) { - callback(); + if (callback != null) callback(); }); } } @@ -177,6 +186,7 @@ class LoadingState extends MusicBeatSubState function onLoad():Void { // Stop the instrumental. + @:nullSafety(Off) if (stopMusic && FlxG.sound.music != null) { FlxG.sound.music.destroy(); @@ -197,7 +207,7 @@ class LoadingState extends MusicBeatSubState static function getSongPath():String { - return Paths.inst(PlayState.instance.currentSong.id); + return Paths.inst(PlayState.instance?.currentSong.id ?? throw 'Cannot retrieve song path'); } static var stageDirectory:String = "shared"; @@ -211,10 +221,10 @@ class LoadingState extends MusicBeatSubState */ public static function loadPlayState(params:PlayStateParams, shouldStopMusic = false, asSubState = false, ?onConstruct:PlayState->Void):Void { - var daChart:Null = params.targetSong.getDifficulty(params.targetDifficulty ?? Constants.DEFAULT_DIFFICULTY, + var daChart:Null = params.targetSong?.getDifficulty(params.targetDifficulty ?? Constants.DEFAULT_DIFFICULTY, params.targetVariation ?? Constants.DEFAULT_VARIATION); - var daStage = funkin.data.stage.StageRegistry.instance.fetchEntry(daChart?.stage ?? Constants.DEFAULT_STAGE); + var daStage:Null = funkin.data.stage.StageRegistry.instance.fetchEntry(daChart?.stage ?? Constants.DEFAULT_STAGE); stageDirectory = daStage?._data?.directory ?? "shared"; Paths.setCurrentLevel(stageDirectory); @@ -249,6 +259,7 @@ class LoadingState extends MusicBeatSubState } #else // All assets preloaded, switch directly to play state (defualt on other targets). + @:nullSafety(Off) if (shouldStopMusic && FlxG.sound.music != null) { FlxG.sound.music.destroy(); @@ -257,7 +268,7 @@ class LoadingState extends MusicBeatSubState // Load and cache the song's charts. // Don't do this if we already provided the music and charts. - if (params?.targetSong != null && !params.overrideMusic) + if (!(params.overrideMusic ?? false)) { params.targetSong.cacheCharts(true); } @@ -272,7 +283,8 @@ class LoadingState extends MusicBeatSubState var songDifficulty = params.targetSong.getDifficulty(params.targetDifficulty, params.targetVariation); if (songDifficulty != null) { - var noteStyle = NoteStyleRegistry.instance.fetchEntry(songDifficulty.noteStyle); + var noteStyle = NoteStyleRegistry.instance.fetchEntry(songDifficulty.noteStyle ?? ''); + if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault(); FunkinMemory.cacheNoteStyle(noteStyle); } @@ -439,7 +451,7 @@ class LoadingState extends MusicBeatSubState var libraryPaths = LimeAssets.libraryPaths; if (libraryPaths.exists(id)) { - path = libraryPaths[id]; + path = libraryPaths[id] ?? path; rootPath = Path.directory(path); } else @@ -490,17 +502,18 @@ class LoadingState extends MusicBeatSubState } } +@:nullSafety class MultiCallback { public var callback:Void->Void; - public var logId:String = null; + public var logId:Null; public var length(default, null) = 0; public var numRemaining(default, null) = 0; var unfired = new MapVoid>(); var fired = new Array(); - public function new(callback:Void->Void, logId:String = null) + public function new(callback:Void->Void, ?logId:String) { this.callback = callback; this.logId = logId; @@ -511,8 +524,7 @@ class MultiCallback id = '$length:$id'; length++; numRemaining++; - var func:Void->Void = null; - func = function() { + var func:Void->Void = function() { if (unfired.exists(id)) { unfired.remove(id); diff --git a/source/funkin/util/WindowUtil.hx b/source/funkin/util/WindowUtil.hx index 85734c6fc..2ee34ee80 100644 --- a/source/funkin/util/WindowUtil.hx +++ b/source/funkin/util/WindowUtil.hx @@ -102,12 +102,19 @@ class WindowUtil */ public static final windowExit:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + /** + * Has `initWindowEvents()` been called already? + * This is to prevent multiple instances of the same function. + */ + private static var _initializedWindowEvents:Bool = false; + /** * Wires up FlxSignals that happen based on window activity. * For example, we can run a callback when the window is closed. */ public static function initWindowEvents():Void { + if (_initializedWindowEvents) return; // Fix that annoying // onUpdate is called every frame just before rendering. // onExit is called when the game window is closed. @@ -137,6 +144,7 @@ class WindowUtil } }); #end + _initializedWindowEvents = true; } /** diff --git a/source/funkin/util/macro/EnvironmentConfigMacro.hx b/source/funkin/util/macro/EnvironmentConfigMacro.hx index 247f3df45..9a4e70540 100644 --- a/source/funkin/util/macro/EnvironmentConfigMacro.hx +++ b/source/funkin/util/macro/EnvironmentConfigMacro.hx @@ -59,13 +59,21 @@ class EnvironmentConfigMacro for (line in envFile.split('\n')) { - if (line == "" || line.startsWith("#")) continue; + if (line.length <= 0 || line.startsWith("#") || shouldExcludeKey(line)) continue; - var parts = line.split('='); - if (parts.length != 2) continue; + var index:Int = line.indexOf('='); - envFields.push(parts[0]); - envValues.push(parts[1]); + if (index == -1) continue; + + var field:String = line.substr(0, index); + var value:String = line.substr(index + 1); + + if (value == "") continue; + + Sys.println('[INFO] Found a key for environment value $field!'); + + envFields.push(field); + envValues.push(value); } var newFields = fields.copy(); @@ -100,6 +108,27 @@ class EnvironmentConfigMacro return newFields; } + + private static function shouldExcludeKey(key:String):Bool + { + final android:Bool = key.startsWith('ANDROID_'); + final ios:Bool = key.startsWith('IOS_'); + final mobile:Bool = key.startsWith('MOBILE_') || ios || android; + final web:Bool = key.startsWith('WEB_'); + final desktop:Bool = key.startsWith('DESKTOP_'); + + #if html5 + if (mobile || desktop) return true; + #elseif desktop + if (mobile || web) return true; + #elseif android + if (ios || web || desktop) return true; + #elseif ios + if (android || web || desktop) return true; + #end + + return false; + } #end } diff --git a/source/funkin/util/macro/GitCommit.hx b/source/funkin/util/macro/GitCommit.hx index 04f173c56..8acef82d4 100644 --- a/source/funkin/util/macro/GitCommit.hx +++ b/source/funkin/util/macro/GitCommit.hx @@ -26,7 +26,7 @@ class GitCommit process.close(); - trace('Git Commit ID: ${commitHashSplice}'); + Sys.println('[INFO] Git Commit ID: ${commitHashSplice}'); // Generates a string expression return macro $v{commitHashSplice}; @@ -56,7 +56,7 @@ class GitCommit var branchName:String = branchProcess.stdout.readLine(); branchProcess.close(); - trace('Git Branch Name: ${branchName}'); + Sys.println('[INFO] Git Branch Name: ${branchName}'); // Generates a string expression return macro $v{branchName}; @@ -103,7 +103,7 @@ class GitCommit throw e; } } - trace('Git Status Output: ${output}'); + Sys.println('[INFO] Git Status Output: ${output}'); // Generates a string expression return macro $v{output.length > 0}; diff --git a/source/funkin/util/macro/LinkerMacro.hx b/source/funkin/util/macro/LinkerMacro.hx new file mode 100644 index 000000000..b0df1cee8 --- /dev/null +++ b/source/funkin/util/macro/LinkerMacro.hx @@ -0,0 +1,44 @@ +package funkin.util.macro; + +/** + * This class provides a macro to include an XML build file in the metadata of a Haxe class. + * + * The file must be located relative to the directory of the Haxe class that uses this macro. + */ +@:nullSafety +class LinkerMacro +{ + /** + * Adds an XML `` element to the class's metadata, pointing to a specified build file. + * @param fileName The name of the XML file to include. Defaults to `Build.xml` if not provided. + * @return An array of fields that are processed during the build. + */ + public static macro function xml(?fileName:String = 'Build.xml'):Array + { + final fields:Array = haxe.macro.Context.getBuildFields(); + final cls:haxe.macro.Type.ClassType = haxe.macro.Context.getLocalClass().get(); + final pos:haxe.macro.Expr.Position = haxe.macro.Context.currentPos(); + + final sourcePath:String = haxe.io.Path.directory(haxe.macro.Context.getPosInfos(pos).file); + final absSourcePath:String = haxe.io.Path.removeTrailingSlashes(sys.FileSystem.absolutePath(sourcePath)); + final fileToInclude:String = haxe.io.Path.join([absSourcePath, fileName?.length > 0 ? fileName : 'Build.xml']); + + if (!sys.FileSystem.exists(fileToInclude)) + { + haxe.macro.Context.error('The specified file "$fileToInclude" could not be found at "$absSourcePath".', pos); + } + + final includeElement:Xml = Xml.createElement('include'); + + includeElement.set('name', fileToInclude); + + cls.meta.add(':buildXml', [ + { + expr: EConst(CString(haxe.xml.Printer.print(includeElement, true))), + pos: pos + } + ], pos); + + return fields; + } +} diff --git a/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx b/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx index 51424c1f3..077ac6672 100644 --- a/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx +++ b/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx @@ -5,7 +5,7 @@ import flixel.FlxBasic; import funkin.ui.MusicBeatState; import funkin.ui.MusicBeatSubState; #if android -import funkin.mobile.external.android.CallbackUtil; +import funkin.external.android.CallbackUtil; #end /** diff --git a/source/funkin/util/plugins/TouchPointerPlugin.hx b/source/funkin/util/plugins/TouchPointerPlugin.hx index baa09a1f6..eec962796 100644 --- a/source/funkin/util/plugins/TouchPointerPlugin.hx +++ b/source/funkin/util/plugins/TouchPointerPlugin.hx @@ -1,8 +1,8 @@ package funkin.util.plugins; -import flixel.FlxCamera; import flixel.FlxG; import flixel.FlxSprite; +import flixel.FlxCamera; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import flixel.input.touch.FlxTouch; import flixel.math.FlxAngle; @@ -33,6 +33,11 @@ class TouchPointerPlugin extends FlxTypedSpriteGroup */ private static var instance:TouchPointerPlugin = null; + /** + * A camera dedicated to displaying the pointers. + */ + private static var pointerCamera:FlxCamera; + public function new() { super(); @@ -43,7 +48,7 @@ class TouchPointerPlugin extends FlxTypedSpriteGroup */ public static function initialize():Void { - var pointerCamera:FlxCamera = new FlxCamera(); + pointerCamera = new FlxCamera(); pointerCamera.bgColor.alpha = 0; instance = new TouchPointerPlugin(); instance.cameras = [pointerCamera]; @@ -125,7 +130,7 @@ class TouchPointerPlugin extends FlxTypedSpriteGroup add(pointer); } - pointer.updateFromTouch(touch); + pointer.updateFromTouch(touch, pointerCamera); } for (pointer in members) @@ -226,6 +231,12 @@ class TouchPointer extends FlxSprite */ public var touchId:Int = -1; + /** + * An internal point for grabbing the view position of the camera. + * Useful for reducing point allocation. + */ + private var viewPoint:FlxPoint; + /** * Stores the last position of the touch pointer. */ @@ -240,6 +251,7 @@ class TouchPointer extends FlxSprite super(); makeGraphic(16, 16, FlxColor.RED); scrollFactor.set(0, 0); + viewPoint = FlxPoint.get(); lastPosition = FlxPoint.get(); } @@ -260,12 +272,16 @@ class TouchPointer extends FlxSprite * Used in TouchPointerPlugin's update method. * * @param touch The FlxTouch object containing the current touch input data. + * @param camera The FlxCamera to grab the touch's view position from. */ - public function updateFromTouch(touch:FlxTouch):Void + public function updateFromTouch(touch:FlxTouch, camera:FlxCamera):Void { + // Grab the view coordinates + touch.getViewPosition(camera, viewPoint); + // Update position - x = touch.viewX - width / 2; - y = touch.viewY - height / 2; + x = viewPoint.x - width / 2; + y = viewPoint.y - height / 2; if (camera.target != null) { @@ -274,7 +290,7 @@ class TouchPointer extends FlxSprite } // Calculate angle if moving - if (lastPosition.distanceTo(FlxPoint.weak(touch.viewX, touch.viewY)) > 3) + if (lastPosition.distanceTo(FlxPoint.weak(viewPoint.x, viewPoint.y)) > 3) { var angle = FlxAngle.angleBetweenPoint(this, lastPosition, true); this.angle = angle; @@ -286,11 +302,12 @@ class TouchPointer extends FlxSprite loadGraphic("assets/images/cursor/michael.png"); } - lastPosition.set(touch.viewX, touch.viewY); + lastPosition.copyFrom(viewPoint); } override public function destroy():Void { + viewPoint.put(); lastPosition.put(); super.destroy(); } diff --git a/templates/android/template/app/src/main/res/drawable/ic_launcher_background.xml b/templates/android/template/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9cb..000000000 --- a/templates/android/template/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/templates/android/template/app/src/main/res/drawable/ic_launcher_foreground.xml b/templates/android/template/app/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d114..000000000 --- a/templates/android/template/app/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 036d09bc5..000000000 --- a/templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 036d09bc5..000000000 --- a/templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher.png b/templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 548e1f239..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 46d9cfb36..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 2486946b3..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher.png b/templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c9a4c6bc0..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 5ac1d12f6..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 610e28840..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index ee4956ace..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index d5ddb2643..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 1e13a4061..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index c38b4a3e4..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 69a39479a..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index b2f1a7e4c..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a5f4b953c..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index fa8c7e19b..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 7ec4a289e..000000000 Binary files a/templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/templates/android/template/app/src/main/res/values/colors.xml b/templates/android/template/app/src/main/res/values/colors.xml deleted file mode 100644 index f8c6127d3..000000000 --- a/templates/android/template/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git a/templates/android/template/app/src/main/res/values/ic_launcher_background.xml b/templates/android/template/app/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index 8f19d93a8..000000000 --- a/templates/android/template/app/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #FDF150 - \ No newline at end of file diff --git a/tests/unit/source/FunkinTest.hx b/tests/unit/source/FunkinTest.hx index 8f47a7d36..046beef40 100644 --- a/tests/unit/source/FunkinTest.hx +++ b/tests/unit/source/FunkinTest.hx @@ -1,6 +1,5 @@ package; -import openfl.utils.Assets; import openfl.errors.Error; import flixel.FlxG; import flixel.FlxState; diff --git a/tests/unit/source/funkin/play/notes/notestyle/NoteStyleTest.hx b/tests/unit/source/funkin/play/notes/notestyle/NoteStyleTest.hx index f2d3e570c..33f2a4c43 100644 --- a/tests/unit/source/funkin/play/notes/notestyle/NoteStyleTest.hx +++ b/tests/unit/source/funkin/play/notes/notestyle/NoteStyleTest.hx @@ -11,7 +11,6 @@ import massive.munit.async.AsyncFactory; import funkin.data.notestyle.NoteStyleRegistry; import funkin.play.notes.notestyle.NoteStyle; import flixel.animation.FlxAnimationController; -import openfl.utils.Assets; import flixel.math.FlxPoint; @:access(funkin.play.notes.notestyle.NoteStyle) diff --git a/tests/unit/source/funkin/util/assets/DataAssetsTest.hx b/tests/unit/source/funkin/util/assets/DataAssetsTest.hx index b3df036e5..579ae9903 100644 --- a/tests/unit/source/funkin/util/assets/DataAssetsTest.hx +++ b/tests/unit/source/funkin/util/assets/DataAssetsTest.hx @@ -1,6 +1,5 @@ package funkin.util.assets; -import openfl.utils.Assets; import massive.munit.util.Timer; import massive.munit.Assert; import massive.munit.async.AsyncFactory;