diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9f8b9e91c80..987d4c4708b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -40,8 +40,8 @@ jobs: with: path: | .haxelib/ - export/release/linux/haxe/ - export/release/linux/obj/ + build/release/linux/haxe/ + build/release/linux/obj/ key: cache-linux-build # Runs a set of commands using the runners shell - name: Install Haxelib @@ -96,14 +96,14 @@ jobs: with: path: | .haxelib/ - export/release/linux/haxe/ - export/release/linux/obj/ + build/release/linux/haxe/ + build/release/linux/obj/ key: cache-linux-build - name: Publish Artifact uses: actions/upload-artifact@main with: name: linuxBuild - path: export/release/linux/bin + path: build/release/linux/bin buildWindows: runs-on: windows-latest @@ -121,8 +121,8 @@ jobs: with: path: | .haxelib/ - export/release/windows/haxe/ - export/release/windows/obj/ + build/release/windows/haxe/ + build/release/windows/obj/ key: cache-windows-build # Runs a set of commands using the runners shell - name: Install Haxelib @@ -175,14 +175,14 @@ jobs: with: path: | .haxelib/ - export/release/windows/haxe/ - export/release/windows/obj/ + build/release/windows/haxe/ + build/release/windows/obj/ key: cache-windows-build - name: Publish Artifact uses: actions/upload-artifact@main with: name: windowsBuild - path: export/release/windows/bin + path: build/release/windows/bin buildMacIntel: runs-on: macos-15 @@ -199,8 +199,8 @@ jobs: with: path: | .haxelib/ - export/release/macos/haxe/ - export/release/macos/obj/ + build/release/macos/haxe/ + build/release/macos/obj/ key: cache-macos-build # Runs a set of commands using the runners shell - name: Install Haxelib @@ -253,14 +253,14 @@ jobs: with: path: | .haxelib/ - export/release/macos/haxe/ - export/release/macos/obj/ + build/release/macos/haxe/ + build/release/macos/obj/ key: cache-macosintel-build - name: Publish Artifact uses: actions/upload-artifact@main with: name: macBuild-intel - path: export/release/macos/bin + path: build/release/macos/bin buildMacArm: runs-on: macos-15 @@ -278,8 +278,8 @@ jobs: with: path: | .haxelib/ - export/release/macos/haxe/ - export/release/macos/obj/ + build/release/macos/haxe/ + build/release/macos/obj/ key: cache-macos-build # Runs a set of commands using the runners shell - name: Install Haxelib @@ -332,11 +332,11 @@ jobs: with: path: | .haxelib/ - export/release/macos/haxe/ - export/release/macos/obj/ + build/release/macos/haxe/ + build/release/macos/obj/ key: cache-macosarm-build - name: Publish Artifact uses: actions/upload-artifact@main with: name: macBuild-arm - path: export/release/macos/bin + path: build/release/macos/bin diff --git a/.gitignore b/.gitignore index 2b96b31f279..241372cd2dd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ art/build_x64-officialrelease.bat art/test_x64-debug-officialrelease.bat ### VS Code +build/* export/* .vscode/* .haxelib/ @@ -20,4 +21,4 @@ export/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json -!.vscode/extensions.json \ No newline at end of file +!.vscode/extensions.json diff --git a/art/preloaderArt.png b/art/preloaderArt.png new file mode 100644 index 00000000000..eb43555c00e Binary files /dev/null and b/art/preloaderArt.png differ diff --git a/art/touchHereToPlay.png b/art/touchHereToPlay.png new file mode 100644 index 00000000000..9b15986e6fb Binary files /dev/null and b/art/touchHereToPlay.png differ diff --git a/Project.xml b/assets/exclude/Project.xml similarity index 100% rename from Project.xml rename to assets/exclude/Project.xml diff --git a/hmm.json b/hmm.json index 5d92add16c9..7b470339caa 100644 --- a/hmm.json +++ b/hmm.json @@ -12,7 +12,7 @@ "type": "git", "dir": null, "ref": null, - "url": "https://github.com/th2l-devs/openfl" + "url": "https://github.com/JS-Engine-things/openfl" }, { "name": "flixel", @@ -36,6 +36,11 @@ "type": "haxelib", "version": "2.6.0" }, + { + "name": "hxp", + "type": "haxelib", + "version": null + }, { "name": "hxcpp", "type": "git", diff --git a/hxformat.json b/hxformat.json index 4d31f145799..fb33f215255 100644 --- a/hxformat.json +++ b/hxformat.json @@ -1,16 +1,30 @@ { - "lineEnds": { - "leftCurly": "both", - "rightCurly": "both", - "emptyCurly": "break", - "objectLiteralCurly": { - "leftCurly": "after" - } - }, - "sameLine": { - "ifElse": "next", - "doWhile": "next", - "tryBody": "next", - "tryCatch": "next" - } + "disableFormatting": false, + + "indentation": { + "character": " ", + "tabWidth": 2, + "indentCaseLabels": true + }, + "lineEnds": { + "anonFunctionCurly": { + "emptyCurly": "break", + "leftCurly": "after", + "rightCurly": "both" + }, + "leftCurly": "both", + "rightCurly": "both" + }, + + "sameLine": { + "ifBody": "same", + "ifElse": "same", + "doWhile": "next", + "tryBody": "next", + "tryCatch": "next" + }, + + "whitespace": { + "switchPolicy": "around" + } } diff --git a/project.hxp b/project.hxp new file mode 100644 index 00000000000..b79d5ce1d72 --- /dev/null +++ b/project.hxp @@ -0,0 +1,1024 @@ +package; + +import hxp.*; +import lime.tools.*; +import sys.FileSystem; + +using StringTools; + +/** + * This HXP performs the functions of a Lime `project.xml` file, + * but it's written in Haxe rather than XML! + * + * This makes it far easier to organize, reuse, and refactor, + * and improves management of feature flag logic. + */ +@:nullSafety +class Project extends HXProject +{ + // + // METADATA + // + + /** + * The game's version number, as a Semantic Versioning string with no prefix. + * REMEMBER TO CHANGE THIS WHEN THE GAME UPDATES! + * You only have to change it here, the rest of the game will query this value. + */ + static final VERSION:String = "0.3.2"; + + /** + * The engine's version number, as a Semantic Versioning string with no prefix. + * Change this when a version is in development or release. (on function Flair) + * This is just used for the function of FLAIR and is not used anywhere else. + */ + public static var ENGINE_VERSION:String = ''; + + /** + * The game's name. Used as the default window title. + */ + static final TITLE:String = "Friday Night Funkin': JS Engine"; + + /** + * The name of the generated executable file. + * For example, `"Funkin"` will create a file called `Funkin.exe`. + */ + static final EXECUTABLE_NAME:String = "JSEngine"; + + /** + * The codename of the engine. + */ + static final CODE_NAME:String = ""; // put your codename here idfk + + /** + * The relative location of the source code. + */ + static final SOURCE_DIR:String = "source"; + + /** + * The fully qualified class path for the game's preloader. + * Particularly important on HTML5 but we use it on all platforms. + */ + static final PRELOADER:String = "flixel.system.FlxPreloader"; + + /** + * A package name used for identifying the app on various app stores. + */ + static final PACKAGE_NAME:String = "com.JordanSantiago.JSEngine"; + + /** + * The fully qualified class path for the entry point class to execute when launching the game. + * It's where `public static function main():Void` goes. + */ + static final MAIN_CLASS:String = "Main"; + + /** + * The company name for the game. + * This appears in metadata in places I think. + */ + static final COMPANY:String = "JordanSantiago"; + + /** + * Path to the Haxe script run before building the game. + */ + static final PREBUILD_HX:String = "source/Prebuild.hx"; + + /** + * Path to the Haxe script run after building the game. + */ + static final POSTBUILD_HX:String = "source/Postbuild.hx"; + + /** + * Asset path globs to always exclude from asset libraries. + */ + static final EXCLUDE_ASSETS:Array = [".*", "cvs", "thumbs.db", "desktop.ini", "*.hash", "*.md"]; + + /** + * Asset path globs to exclude on web platforms. + */ + static final EXCLUDE_ASSETS_WEB:Array = ["*.ogg"]; + + /** + * Asset path globs to exclude on native platforms. + */ + static final EXCLUDE_ASSETS_NATIVE:Array = ["*.mp3"]; + + // + // FEATURES + // + static final officialBuild:FeatureFlag = "officialBuild"; + static final MODS_ALLOWED:FeatureFlag = "MODS_ALLOWED"; + static final LUA_ALLOWED:FeatureFlag = "LUA_ALLOWED"; + static final VIDEOS_ALLOWED:FeatureFlag = "VIDEOS_ALLOWED"; + static final FEATURE_DEBUG_FUNCTIONS:FeatureFlag = "FEATURE_DEBUG_FUNCTIONS"; + static final FUNNY_ALLOWED:FeatureFlag = "FUNNY_ALLOWED"; + static final HSCRIPT_ALLOWED:FeatureFlag = "HSCRIPT_ALLOWED"; + static final SHADERS_ALLOWED:FeatureFlag = "SHADERS_ALLOWED"; + static final DISCORD_ALLOWED:FeatureFlag = "DISCORD_ALLOWED"; + static final PRELOAD_ALL:FeatureFlag = "PRELOAD_ALL"; + static final EMBED_ASSETS:FeatureFlag = "EMBED_ASSETS"; + static final FEATURE_FILE_DROP:FeatureFlag = "FEATURE_FILE_DROP"; + static final ACHIEVEMENTS_ALLOWED:FeatureFlag = "ACHIEVEMENTS_ALLOWED"; + static final CRASH_HANDLER:FeatureFlag = "CRASH_HANDLER"; + static final TITLE_SCREEN_EASTER_EGG:FeatureFlag = "TITLE_SCREEN_EASTER_EGG"; + static final CHECK_FOR_UPDATES:FeatureFlag = "CHECK_FOR_UPDATES"; + + /** + * `-DFEATURE_DEBUG_TRACY` + * If this flag is enabled, the game will have the necessary hooks for the Tracy profiler. + * Only enable this if you're using the correct fork of Haxe to support this. + * @see https://github.com/HaxeFoundation/hxcpp/pull/1153 + */ + static final FEATURE_DEBUG_TRACY:FeatureFlag = "FEATURE_DEBUG_TRACY"; + + /** + * `-DFEATURE_LOG_TRACE` + * If this flag is enabled, the game will print debug traces to the console. + * Disable to improve performance a bunch. + */ + static final FEATURE_LOG_TRACE:FeatureFlag = "FEATURE_LOG_TRACE"; + + public function new() + { + super(); + + setenv('HAXEPATH', './'); // Fixes an issue for haxelibs that use native C++ files until Haxe 5 releases. + + haxe.Log.trace = (v:Dynamic, ?info:haxe.PosInfos) -> + { + if (!isDisplay()) + Sys.println(v); + }; + + flair(); + configureApp(); + + displayTarget(); + configureCompileDefines(); + configureFeatureFlags(); + configureOutputDir(); + configureHaxelibs(); + configureAssets(); + configureIcons(); + } + + function flair() + { + info("Friday Night Funkin': JS Engine"); + info("Initializing build..."); + + // info("Target Version: " + ENGINE_VERSION); + info("Git Branch: " + getGitBranch()); + info("Git Commit: " + getGitCommit()); + info("Git Modified? " + getGitModified()); + info("Display? " + isDisplay()); + } + + /** + * Apply basic project metadata, such as the game title and version number, + * as well as info like the package name and company (used by various app stores). + */ + function configureApp() + { + this.meta.title = TITLE; + this.meta.version = VERSION; + this.meta.packageName = PACKAGE_NAME; + this.meta.company = COMPANY; + + this.app.main = MAIN_CLASS; + this.app.file = EXECUTABLE_NAME; + this.app.preloader = PRELOADER; + + // Tell Lime where to look for the game's source code. + // If for some reason we have multiple source directories, we can add more entries here. + this.sources.push(SOURCE_DIR); + + // Tell Lime to run some prebuild and postbuild scripts. + // this.preBuildCallbacks.push(buildHaxeCLICommand(PREBUILD_HX)); + // this.postBuildCallbacks.push(buildHaxeCLICommand(POSTBUILD_HX)); + + // Configure the window. + // Automatically configure FPS. + this.window.fps = 60; + // Set the window size. + this.window.width = 1280; + this.window.height = 720; + + // Sets the window background. + // Probably fixes white borders. + this.window.background = 0x000000; + + this.window.hardware = true; + this.window.vsync = false; + + if (isWeb()) + { + this.window.resizable = true; + } + + if (isDesktop()) + { + this.window.orientation = Orientation.LANDSCAPE; + this.window.fullscreen = false; + this.window.resizable = true; + this.window.vsync = false; + } + + if (isMobile()) + { + this.window.orientation = Orientation.LANDSCAPE; + this.window.fullscreen = false; + this.window.resizable = false; + this.window.width = 0; + this.window.height = 0; + } + } + + /** + * Log information about the configured target platform. + */ + function displayTarget() + { + // Display the target operating system. + switch (this.target) + { + case Platform.WINDOWS: + info('Target Platform: Windows'); + case Platform.MAC: + info('Target Platform: MacOS'); + case Platform.LINUX: + info('Target Platform: Linux'); + case Platform.ANDROID: + info('Target Platform: Android'); + case Platform.IOS: + info('Target Platform: IOS'); + case Platform.HTML5: + info('Target Platform: HTML5'); + default: + error('Unsupported platform! (got ${target})'); + } + + switch (this.platformType) + { + case PlatformType.DESKTOP: + info('Platform Type: Desktop'); + case PlatformType.MOBILE: + info('Platform Type: Mobile'); + case PlatformType.WEB: + info('Platform Type: Web'); + case PlatformType.CONSOLE: + info('Platform Type: Console'); + default: + error('Unknown platform type! (got ${platformType})'); + } + + // Print whether we are using HXCPP, HashLink, or something else. + if (isWeb()) + info('Target Language: JavaScript (HTML5)'); + else if (isHashLink()) + info('Target Language: HashLink'); + else if (isNeko()) + info('Target Language: Neko'); + else if (isJava()) + info('Target Language: Java'); + else if (isNodeJS()) + info('Target Language: JavaScript (NodeJS)'); + else if (isCSharp()) + info('Target Language: C#'); + else + info('Target Language: C++'); + + for (arch in this.architectures) + { + // Display the list of target architectures. + switch (arch) + { + case Architecture.X86: + info('Architecture: x86'); + case Architecture.X64: + info('Architecture: x64'); + case Architecture.ARMV5: + info('Architecture: ARMv5'); + case Architecture.ARMV6: + info('Architecture: ARMv6'); + case Architecture.ARMV7: + info('Architecture: ARMv7'); + case Architecture.ARMV7S: + info('Architecture: ARMv7S'); + case Architecture.ARM64: + info('Architecture: ARMx64'); + case Architecture.MIPS: + info('Architecture: MIPS'); + case Architecture.MIPSEL: + info('Architecture: MIPSEL'); + case null: + if (!isWeb()) + { + error('Unsupported architecture (got null on non-web platform)'); + } + else + { + info('Architecture: Web'); + } + default: + error('Unsupported architecture (got ${arch})'); + } + } + } + + /** + * Apply various feature flags based on the target platform and the user-provided build flags. + */ + function configureFeatureFlags() + { + // You can explicitly override any of these. + // For example, `-DMODS_ALLOWED` or `-DVIDEOS_ALLOWED` + + // Should be true unless explicitly requested. + MODS_ALLOWED.apply(this, true); + LUA_ALLOWED.apply(this, true); + VIDEOS_ALLOWED.apply(this, true); + CRASH_HANDLER.apply(this, true); + ACHIEVEMENTS_ALLOWED.apply(this, true); + CHECK_FOR_UPDATES.apply(this, true); + FEATURE_LOG_TRACE.apply(this, isDebug()); + HSCRIPT_ALLOWED.apply(this, isDesktop()); + DISCORD_ALLOWED.apply(this, isDesktop()); + SHADERS_ALLOWED.apply(this, true); + FUNNY_ALLOWED.apply(this, true); + // TITLE_SCREEN_EASTER_EGG.apply(this, true); + + // Should be true only on web builds. + // Enabling embedding and preloading is required to preload assets properly. + EMBED_ASSETS.apply(this, isWeb()); + PRELOAD_ALL.apply(this, true); + + // Should be true except on MacOS. + // File drop doesn't work there. + FEATURE_FILE_DROP.apply(this, !isMac()); + } + + /** + * Set compilation flags which are not feature flags. + */ + function configureCompileDefines() + { + // Enable OpenFL's error handler. Required for the crash logger. + setHaxedef("openfl-enable-handle-error"); + + // Enable stack trace tracking. Good for debugging but has a (minor) performance impact. (and also the crash handler) + setHaxedef("HXCPP_CHECK_POINTER"); + setHaxedef("HXCPP_STACK_LINE"); + setHaxedef("HXCPP_STACK_TRACE"); + setHaxedef("hscriptPos"); + if (!isDebug()) + setHaxedef("no-deprecation-warnings"); + setHaxedef("LINC_LUA_RELATIVE_DYNAMIC_LIB"); // DISABLE THIS IF ISSUES ARE FOUND! + setHaxedef("FLX_NO_FOCUS_LOST_SCREEN"); // I FORGOT LMAFOAOO + + // setHaxedef("NO_PRECOMPILED_HEADERS"); + + // Allow memory to go above 1GB. + setHaxedef("HXCPP_GC_BIG_BLOCKS"); + + // setHaxedef("safeMode"); // what is this + + // If we aren't using the Flixel debugger, strip it out. + if (!isDebug()) + { + setHaxedef("FLX_NO_DEBUG"); + } + + // Disable the built in pause screen when unfocusing the game. + // setHaxedef("FLX_NO_FOCUS_LOST_SCREEN"); + + // Cleaner looking compiler errors. + // setHaxedef("message.reporting", "pretty"); + + if (FEATURE_DEBUG_TRACY.isEnabled(this)) + { + setHaxedef("HXCPP_TELEMETRY"); // Enable telemetry + setHaxedef("HXCPP_TRACY"); // Enable Tracy telemetry + setHaxedef("HXCPP_TRACY_MEMORY"); // Track memory allocations + setHaxedef("HXCPP_TRACY_ON_DEMAND"); // Only collect telemetry when Tracy is open and reachable + // setHaxedef("HXCPP_TRACY_INCLUDE_CALLSTACKS"); // Inspect callstacks per zone, inflating telemetry data + + setHaxedef("absolute-paths"); // Fix source locations so Tracy can see them + } + } + + function configureOutputDir() + { + // Set the output directory. Depends on the target platform and build type. + + var buildDir = 'build/${isDebug() ? 'debug' : 'release'}'; + + // we use a dedicated 'tracy' folder, since it generally needs a recompile when in use + if (FEATURE_DEBUG_TRACY.isEnabled(this)) + buildDir += "-tracy"; + + // trailing slash might not be needed, works fine on macOS without it, but I haven't tested on Windows! + buildDir += "/"; + + info('Output directory: $buildDir'); + // setenv('BUILD_DIR', buildDir); + app.path = buildDir; + } + + function configureHaxelibs() + { + addHaxelib('lime'); // Game engine backend + addHaxelib('openfl'); // Game engine backend + + addHaxelib('flixel'); // Game engine + + addHaxelib('flixel-addons'); // Additional utilities for Flixel + addHaxelib('hscript'); // Scripting + addHaxelib('flixel-ui'); // UI framework + addHaxelib('grig.audio'); + addHaxelib('funkin.vis'); + addHaxelib('flxanimate'); // Texture atlas rendering + addHaxelib('hxnativefiledialog'); + addHaxelib('tjson'); + + if (isDesktop() && DISCORD_ALLOWED.isEnabled(this)) + addHaxelib('hxdiscord_rpc'); // Discord API + + addHaxelib('away3d'); // 3D API + + if (isDesktop() && LUA_ALLOWED.isEnabled(this)) + addHaxelib('linc_luajit'); // Lua API + + if (isDebug()) + addHaxelib('hxcpp-debug-server'); // VSCode debug support + + if (isDesktop() && VIDEOS_ALLOWED.isEnabled(this)) + addHaxelib('hxvlc'); + } + + function configureAssets() + { + var exclude = EXCLUDE_ASSETS.concat(isWeb() ? EXCLUDE_ASSETS_WEB : EXCLUDE_ASSETS_NATIVE); + var shouldPreload = PRELOAD_ALL.isEnabled(this); + var shouldEmbed = EMBED_ASSETS.isEnabled(this); + + if (shouldEmbed) + { + info('Embedding assets into executable...'); + } + else + { + info('Including assets alongside executable...'); + } + + // Default asset library + var shouldPreloadDefault = true; + addAssetLibrary("default", shouldEmbed, shouldPreloadDefault); + addAssetPath("assets/preload", "assets", "default", ["*"], exclude, shouldEmbed); + + // Font assets + var shouldEmbedFonts = true; + addAssetPath("assets/fonts", null, "default", ["*"], exclude, shouldEmbedFonts); + + // Volume stuff + var shouldEmbedVolume = true; + addAssetPath("assets/soundtray", null, "default", ["*"], exclude, shouldEmbedVolume); + + // splash stuff + var shouldEmbedSplash = true; + addAssetPath("assets/splash", null, "default", ["*"], exclude, shouldEmbedSplash); + + // 3D assets + addAssetPath("assets/preload/models", "assets/models", "default", ["*"], exclude, shouldEmbedFonts); + + // Shared asset libraries + addAssetLibrary("songs", shouldEmbed, shouldPreload); + addAssetPath("assets/songs", "assets/songs", "songs", ["*"], exclude, shouldEmbed); + addAssetLibrary("shared", shouldEmbed, shouldPreload); + addAssetPath("assets/shared", "assets/shared", "shared", ["*"], exclude, shouldEmbed); + if (VIDEOS_ALLOWED.isEnabled(this)) + { + var shouldEmbedVideos = false; + addAssetLibrary("videos", shouldEmbedVideos, shouldPreload); + addAssetPath("assets/videos", "assets/videos", "videos", ["*"], exclude, shouldEmbedVideos); + } + + // Level asset libraries + addAssetLibrary("week2", shouldEmbed, shouldPreload); + addAssetPath("assets/week2", "assets/week2", "week2", ["*"], exclude, shouldEmbed); + addAssetLibrary("week3", shouldEmbed, shouldPreload); + addAssetPath("assets/week3", "assets/week3", "week3", ["*"], exclude, shouldEmbed); + addAssetLibrary("week4", shouldEmbed, shouldPreload); + addAssetPath("assets/week4", "assets/week4", "week4", ["*"], exclude, shouldEmbed); + addAssetLibrary("week5", shouldEmbed, shouldPreload); + addAssetPath("assets/week5", "assets/week5", "week5", ["*"], exclude, shouldEmbed); + addAssetLibrary("week6", shouldEmbed, shouldPreload); + addAssetPath("assets/week6", "assets/week6", "week6", ["*"], exclude, shouldEmbed); + addAssetLibrary("week7", shouldEmbed, shouldPreload); + addAssetPath("assets/week7", "assets/week7", "week7", ["*"], exclude, shouldEmbed); + addAssetLibrary("weekend1", shouldEmbed, shouldPreload); + addAssetPath("assets/weekend1", "assets/weekend1", "weekend1", ["*"], exclude, shouldEmbed); + + // Art asset library (where README and CHANGELOG pull from) + var shouldEmbedArt = false; + var shouldPreloadArt = false; + // addAsset("art/readme.txt", "do NOT readme.txt", "art", shouldEmbedArt); + // addAsset("CHANGELOG.md", "changelog.txt", "/", shouldEmbedArt); + addAssetPath("example_mods", "mods", "default", ["*"], exclude, shouldEmbed); // lololololoolollo + } + + /** + * Configure the application's favicon and executable icon. + */ + function configureIcons() + { + for (size in [16, 32, 64]) + addIcon('icons/icon${size}.png', size); + addIcon("icons/iconOG.png"); + } + + // + // HELPER FUNCTIONS + // Easy functions to make the code more readable. + // + + public function isWeb():Bool + { + return this.platformType == PlatformType.WEB; + } + + public function isMobile():Bool + { + return this.platformType == PlatformType.MOBILE; + } + + public function isDesktop():Bool + { + return this.platformType == PlatformType.DESKTOP; + } + + public function isConsole():Bool + { + return this.platformType == PlatformType.CONSOLE; + } + + public function is32Bit():Bool + { + return this.architectures.contains(Architecture.X86); + } + + public function is64Bit():Bool + { + return this.architectures.contains(Architecture.X64); + } + + public function isWindows():Bool + { + return this.target == Platform.WINDOWS; + } + + public function isMac():Bool + { + return this.target == Platform.MAC; + } + + public function isLinux():Bool + { + return this.target == Platform.LINUX; + } + + public function isAndroid():Bool + { + return this.target == Platform.ANDROID; + } + + public function isIOS():Bool + { + return this.target == Platform.IOS; + } + + public function isHashLink():Bool + { + return this.targetFlags.exists("hl"); + } + + public function isNeko():Bool + { + return this.targetFlags.exists("neko"); + } + + public function isJava():Bool + { + return this.targetFlags.exists("java"); + } + + public function isNodeJS():Bool + { + return this.targetFlags.exists("nodejs"); + } + + public function isCSharp():Bool + { + return this.targetFlags.exists("cs"); + } + + public function isDisplay():Bool + { + return this.command == "display"; + } + + public function isDebug():Bool + { + return this.debug; + } + + public function isRelease():Bool + { + return !isDebug(); + } + + public function getHaxedef(name:String):Null + { + return this.haxedefs.get(name); + } + + public function setHaxedef(name:String, ?value:String):Void + { + if (value == null) + value = ""; + this.haxedefs.set(name, value); + } + + public function unsetHaxedef(name:String):Void + { + this.haxedefs.remove(name); + } + + public function getDefine(name:String):Null + { + return this.defines.get(name); + } + + public function hasDefine(name:String):Bool + { + return this.defines.exists(name); + } + + /** + * Add a library to the list of dependencies for the project. + * @param name The name of the library to add. + * @param version The version of the library to add. Optional. + */ + public function addHaxelib(name:String, version:String = ""):Void + { + this.haxelibs.push(new Haxelib(name, version)); + } + + /** + * Add a `haxeflag` to the project. + */ + public function addHaxeFlag(value:String):Void + { + this.haxeflags.push(value); + } + + /** + * Call a Haxe build macro. + */ + public function addHaxeMacro(value:String):Void + { + addHaxeFlag('--macro ${value}'); + } + + /** + * Add an icon to the project. + * @param icon The path to the icon. + * @param size The size of the icon. Optional. + */ + public function addIcon(icon:String, ?size:Int):Void + { + this.icons.push(new Icon(icon, size)); + } + + /** + * Add an asset to the game build. + * @param path The path the asset is located at. + * @param rename The path the asset should be placed. + * @param library The asset library to add the asset to. `null` = "default" + * @param embed Whether to embed the asset in the executable. + */ + public function addAsset(path:String, ?rename:String, ?library:String, embed:Bool = false):Void + { + // path, rename, type, embed, setDefaults + var asset = new Asset(path, rename, null, embed, true); + @:nullSafety(Off) + { + asset.library = library ?? "default"; + } + this.assets.push(asset); + } + + /** + * Add an entire path of assets to the game build. + * @param path The path the assets are located at. + * @param rename The path the assets should be placed. + * @param library The asset library to add the assets to. `null` = "default" + * @param include An optional array to include specific asset names. + * @param exclude An optional array to exclude specific asset names. + * @param embed Whether to embed the assets in the executable. + */ + public function addAssetPath(path:String, ?rename:String, library:String, ?include:Array, ?exclude:Array, embed:Bool = false):Void + { + // Argument parsing. + if (path == "") + return; + + if (include == null) + include = []; + + if (exclude == null) + exclude = []; + + var targetPath = rename ?? path; + if (targetPath != "") + targetPath += "/"; + + // Validate path. + if (!sys.FileSystem.exists(path)) + { + error('Could not find asset path "${path}".'); + } + else if (!sys.FileSystem.isDirectory(path)) + { + error('Could not parse asset path "${path}", expected a directory.'); + } + else + { + // info(' Found asset path "${path}".'); + } + + for (file in sys.FileSystem.readDirectory(path)) + { + if (sys.FileSystem.isDirectory('${path}/${file}')) + { + // Attempt to recursively add all assets in the directory. + if (this.filter(file, ["*"], exclude)) + { + addAssetPath('${path}/${file}', '${targetPath}${file}', library, include, exclude, embed); + } + } + else + { + if (this.filter(file, include, exclude)) + { + addAsset('${path}/${file}', '${targetPath}${file}', library, embed); + } + } + } + } + + /** + * Add an asset library to the game build. + * @param name The name of the library. + * @param embed + * @param preload + */ + public function addAssetLibrary(name:String, embed:Bool = false, preload:Bool = false):Void + { + // sourcePath, name, type, embed, preload, generate, prefix + var sourcePath = ''; + this.libraries.push(new Library(sourcePath, name, null, embed, preload, false, "")); + } + + /** + * A CLI command to run a command in the shell. + */ + public function buildCLICommand(cmd:String):CLICommand + { + return CommandHelper.fromSingleString(cmd); + } + + /** + * A CLI command to run a Haxe script via `--interp`. + */ + public function buildHaxeCLICommand(path:String):CLICommand + { + return CommandHelper.interpretHaxe(path); + } + + public function getGitCommit():String + { + // Cannibalized from GitCommit.hx + var process = new sys.io.Process('git', ['rev-parse', 'HEAD']); + if (process.exitCode() != 0) + { + var message = process.stderr.readAll().toString(); + error('[ERROR] Could not determine current git commit; is this a proper Git repository?'); + } + + var commitHash:String = process.stdout.readLine(); + var commitHashSplice:String = commitHash.substr(0, 7); + + process.close(); + + return commitHashSplice; + } + + public function getGitBranch():String + { + // Cannibalized from GitCommit.hx + var branchProcess = new sys.io.Process('git', ['rev-parse', '--abbrev-ref', 'HEAD']); + + if (branchProcess.exitCode() != 0) + { + var message = branchProcess.stderr.readAll().toString(); + error('Could not determine current git branch; is this a proper Git repository?'); + } + + var branchName:String = branchProcess.stdout.readLine(); + + branchProcess.close(); + + return branchName; + } + + public function getGitModified():Bool + { + var branchProcess = new sys.io.Process('git', ['status', '--porcelain']); + + if (branchProcess.exitCode() != 0) + { + var message = branchProcess.stderr.readAll().toString(); + error('Could not determine current git status; is this a proper Git repository?'); + } + + var output:String = ''; + try + { + output = branchProcess.stdout.readLine(); + } + catch (e) + { + if (e.message == 'Eof') + { + // Do nothing. + // Eof = No output. + } + else + { + // Rethrow other exceptions. + throw e; + } + } + + branchProcess.close(); + + return output.length > 0; + } + + // + // LOGGING FUNCTIONS + // + + /** + * Display an error message. This should stop the build process. + */ + public function error(message:String):Void + { + Log.error('${message}'); + } + + /** + * Display an info message. This should not interfere with the build process. + */ + public function info(message:String):Void + { + // CURSED: We have to disable info() log calls because of a bug. + // Hi EliteMasterEric can you push this fix onto the Funkin repo? + // https://github.com/haxelime/lime-vscode-extension/issues/88 + + if (command != "display") + { + Log.info('[INFO] ${message}'); + } + } +} + +/** + * An object representing a feature flag, which can be enabled or disabled. + * Includes features such as automatic generation of compile defines and inversion. + */ +abstract FeatureFlag(String) +{ + static final INVERSE_PREFIX:String = "NO_"; + + public function new(input:String) + { + this = input; + } + + @:from + public static function fromString(input:String):FeatureFlag + { + return new FeatureFlag(input); + } + + /** + * Enable/disable a feature flag if it is unset, and handle the inverse flag. + * Doesn't override a feature flag that was set explicitly. + * @param enableByDefault Whether to enable this feature flag if it is unset. + */ + public function apply(project:Project, enableByDefault:Bool = false):Void + { + // TODO: Name this function better? + + if (isEnabled(project)) + { + // If this flag was already enabled, disable the inverse. + // project.info('Enabling feature flag ${this}'); + getInverse().disable(project, false); + } + else if (getInverse().isEnabled(project)) + { + // If the inverse flag was already enabled, disable this flag. + // project.info('Disabling feature flag ${this}'); + disable(project, false); + } + else + { + if (enableByDefault) + { + // Enable this flag if it was unset, and disable the inverse. + // project.info('Enabling feature flag ${this}'); + enable(project, true); + } + else + { + // Disable this flag if it was unset, and enable the inverse. + // project.info('Disabling feature flag ${this}'); + disable(project, true); + } + } + } + + /** + * Enable this feature flag by setting the appropriate compile define. + * + * @param project The project to modify. + * @param andInverse Also disable the feature flag's inverse. + */ + public function enable(project:Project, andInverse:Bool = true) + { + project.setHaxedef(this, ""); + if (andInverse) + { + getInverse().disable(project, false); + } + } + + /** + * Disable this feature flag by removing the appropriate compile define. + * + * @param project The project to modify. + * @param andInverse Also enable the feature flag's inverse. + */ + public function disable(project:Project, andInverse:Bool = true) + { + project.unsetHaxedef(this); + if (andInverse) + { + getInverse().enable(project, false); + } + } + + /** + * Query if this feature flag is enabled. + * @param project The project to query. + */ + public function isEnabled(project:Project):Bool + { + // Check both Haxedefs and Defines for this flag. + return project.haxedefs.exists(this) || project.defines.exists(this); + } + + /** + * Query if this feature flag's inverse is enabled. + */ + public function isDisabled(project:Project):Bool + { + return getInverse().isEnabled(project); + } + + /** + * Return the inverse of this feature flag. + * @return A new feature flag that is the inverse of this one. + */ + public function getInverse():FeatureFlag + { + if (this.startsWith(INVERSE_PREFIX)) + { + return this.substring(INVERSE_PREFIX.length); + } + return INVERSE_PREFIX + this; + } +} diff --git a/setup/unix.sh b/setup/unix.sh index 8bc2e0cbd8a..76940a12f95 100644 --- a/setup/unix.sh +++ b/setup/unix.sh @@ -7,7 +7,7 @@ echo This might take a few moments depending on your internet speed. sudo apt update sudo apt install libgtk-3-dev libgl-dev libx11-dev libxi-dev libxpm-dev libxrandr-dev libncurses-dev haxelib git lime https://github.com/th2l-devs/lime --quiet -haxelib git openfl https://github.com/th2l-devs/openfl --quiet +haxelib git openfl https://github.com/JS-Engine-things/openfl --quiet haxelib git flixel https://github.com/JS-Engine-things/flixel-JS-Engine --quiet haxelib install flixel-addons 3.2.3 --quiet haxelib install flixel-tools 1.5.1 --quiet @@ -24,4 +24,5 @@ haxelib git grig.audio https://github.com/JS-Engine-things/grig.audio --quiet haxelib git hxdiscord_rpc https://github.com/MAJigsaw77/hxdiscord_rpc --quiet --skip-dependencies haxelib git hxvlc https://github.com/JS-Engine-things/hxvlc --quiet --skip-dependencies haxelib git hxnativefiledialog https://github.com/MAJigsaw77/hxnativefiledialog --quiet --skip-dependencies +haxelib install hxp echo Finished! diff --git a/setup/windows.bat b/setup/windows.bat index c8f5ba4b92a..d581d5e9ffd 100644 --- a/setup/windows.bat +++ b/setup/windows.bat @@ -5,7 +5,7 @@ echo Installing dependencies... echo This might take a few moments depending on your internet speed. @echo on haxelib git lime https://github.com/th2l-devs/lime --quiet -haxelib git openfl https://github.com/th2l-devs/openfl --quiet +haxelib git openfl https://github.com/JS-Engine-things/openfl --quiet haxelib git flixel https://github.com/JS-Engine-things/flixel-JS-Engine --quiet haxelib install flixel-addons 3.2.3 --quiet haxelib install flixel-tools 1.5.1 --quiet @@ -22,6 +22,7 @@ haxelib git grig.audio https://github.com/JS-Engine-things/grig.audio --quiet haxelib git hxdiscord_rpc https://github.com/MAJigsaw77/hxdiscord_rpc --quiet --skip-dependencies haxelib git hxvlc https://github.com/JS-Engine-things/hxvlc --quiet --skip-dependencies haxelib git hxnativefiledialog https://github.com/MAJigsaw77/hxnativefiledialog --quiet --skip-dependencies +haxelib install hxp @echo off echo Finished! pause diff --git a/source/Constants.hx b/source/Constants.hx new file mode 100644 index 00000000000..28ec7b932ae --- /dev/null +++ b/source/Constants.hx @@ -0,0 +1,78 @@ +package; + +import flixel.system.FlxBasePreloader; +import flixel.util.FlxColor; + +/** + * A store of unchanging, globally relevant values. + */ +@:nullSafety +class Constants +{ + /** + * Preloader sitelock. + * Matching is done by `FlxStringUtil.getDomain`, so any URL on the domain will work. + * The first link in this list is the one users will be redirected to if they try to access the game from a different URL. + */ + public static final SITE_LOCK:Array = [ + "https://github.com/JordanSantiagoYT/FNF-JS-Engine/releases/latest", // GitHub, baybee! + FlxBasePreloader.LOCAL // localhost for dev stuff + ]; + + /** + * LANGUAGE + */ + // ============================== + public static final SITE_LOCK_TITLE:String = "You Loser!"; + + public static final SITE_LOCK_DESC:String = "Go play JS Engine on here:"; + + /** + * The base colors used by notes. + */ + public static var COLOR_NOTES:Array = [ + 0xFFFF22AA, // left (0) + 0xFF00EEFF, // down (1) + 0xFF00CC00, // up (2) + 0xFFCC1111 // right (3) + ]; + + /** + * Color for the preloader background + */ + public static final COLOR_PRELOADER_BG:FlxColor = 0xFF000000; + + /** + * Color for the preloader progress bar + */ + public static final COLOR_PRELOADER_BAR:FlxColor = 0xFFA4FF11; + + /** + * Color for the preloader site lock background + */ + public static final COLOR_PRELOADER_LOCK_BG:FlxColor = 0xFF1B1717; + + /** + * Color for the preloader site lock foreground + */ + public static final COLOR_PRELOADER_LOCK_FG:FlxColor = 0xB96F10; + + /** + * Color for the preloader site lock text + */ + public static final COLOR_PRELOADER_LOCK_FONT:FlxColor = 0xCCCCCC; + + /** + * Color for the preloader site lock link + */ + public static final COLOR_PRELOADER_LOCK_LINK:FlxColor = 0xEEB211; + + /** + * Each step of the preloader has to be on screen at least this long. + * + * 0 = The preloader immediately moves to the next step when it's ready. + * 1 = The preloader waits for 1 second before moving to the next step. + * The progress bare is automatically rescaled to match. + */ + public static final PRELOADER_MIN_STAGE_TIME:Float = 0.1; +} diff --git a/source/MathUtil.hx b/source/MathUtil.hx new file mode 100644 index 00000000000..ee60e7f1150 --- /dev/null +++ b/source/MathUtil.hx @@ -0,0 +1,257 @@ +package; + +/** + * Utilities for performing mathematical operations. + */ +@:nullSafety +class MathUtil +{ + /** + * Euler's constant and the base of the natural logarithm. + * Math.E is not a constant in Haxe, so we'll just define it ourselves. + */ + public static final E:Float = 2.71828182845904523536; + + /** + * Get the logarithm of a value with a given base. + * @param base The base of the logarithm. + * @param value The value to get the logarithm of. + * @return `log_base(value)` + */ + public static function logBase(base:Float, value:Float):Float + { + return Math.log(value) / Math.log(base); + } + + public static function easeInOutCirc(x:Float):Float + { + if (x <= 0.0) return 0.0; + if (x >= 1.0) return 1.0; + var result:Float = (x < 0.5) ? (1 - Math.sqrt(1 - 4 * x * x)) / 2 : (Math.sqrt(1 - 4 * (1 - x) * (1 - x)) + 1) / 2; + return (result == Math.NaN) ? 1.0 : result; + } + + public static function easeInOutBack(x:Float, c:Float = 1.70158):Float + { + if (x <= 0.0) return 0.0; + if (x >= 1.0) return 1.0; + var result:Float = (x < 0.5) ? (2 * x * x * ((c + 1) * 2 * x - c)) / 2 : (1 - 2 * (1 - x) * (1 - x) * ((c + 1) * 2 * (1 - x) - c)) / 2; + return (result == Math.NaN) ? 1.0 : result; + } + + public static function easeInBack(x:Float, c:Float = 1.70158):Float + { + if (x <= 0.0) return 0.0; + if (x >= 1.0) return 1.0; + return (1 + c) * x * x * x - c * x * x; + } + + public static function easeOutBack(x:Float, c:Float = 1.70158):Float + { + if (x <= 0.0) return 0.0; + if (x >= 1.0) return 1.0; + return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); + } + + /** + * Get the base-2 exponent of a value. + * @param x value + * @return `2^x` + */ + public static function exp2(x:Float):Float + { + return Math.pow(2, x); + } + + /** + * Performs a modulo operation to calculate the remainder of `a` divided by `b`. + * + * The definition of "remainder" varies by implementation; + * this one is similar to GLSL or Python in that it uses Euclidean division, which always returns positive, + * while Haxe's `%` operator uses signed truncated division. + * + * For example, `-5 % 3` returns `-2` while `FlxMath.mod(-5, 3)` returns `1`. + * + * @param a The dividend. + * @param b The divisor. + * @return `a mod b`. + */ + public static function mod(a:Float, b:Float):Float + { + b = Math.abs(b); + return a - b * Math.floor(a / b); + } + + /** + * Helper function to get the fractional part of a value. + * @param x value + * @return `x mod 1`. + */ + public static function fract(x:Float):Float + { + return x - Math.floor(x); + } + + /** + * Linear interpolation. + * + * @param base The starting value, when `alpha = 0`. + * @param target The ending value, when `alpha = 1`. + * @param alpha The percentage of the interpolation from `base` to `target`. Forms a "line" intersecting the two. + * + * @return The interpolated value. + */ + public static function lerp(base:Float, target:Float, alpha:Float):Float + { + if (alpha == 0) return base; + if (alpha == 1) return target; + return base + alpha * (target - base); + } + + /** + * Exponential decay interpolation. + * + * Framerate-independent because the rate-of-change is proportional to the difference, so you can + * use the time elapsed since the last frame as `deltaTime` and the function will be consistent. + * + * Equivalent to `smoothLerpPrecision(base, target, deltaTime, halfLife, 0.5)`. + * + * @param base The starting or current value. + * @param target The value this function approaches. + * @param deltaTime The change in time along the function in seconds. + * @param halfLife Time in seconds to reach halfway to `target`. + * + * @see https://twitter.com/FreyaHolmer/status/1757918211679650262 + * + * @return The interpolated value. + */ + public static function smoothLerpDecay(base:Float, target:Float, deltaTime:Float, halfLife:Float):Float + { + if (deltaTime == 0) return base; + if (base == target) return target; + return lerp(target, base, exp2(-deltaTime / halfLife)); + } + + /** + * Exponential decay interpolation. + * + * Framerate-independent because the rate-of-change is proportional to the difference, so you can + * use the time elapsed since the last frame as `deltaTime` and the function will be consistent. + * + * Equivalent to `smoothLerpDecay(base, target, deltaTime, -duration / logBase(2, precision))`. + * + * @param base The starting or current value. + * @param target The value this function approaches. + * @param deltaTime The change in time along the function in seconds. + * @param duration Time in seconds to reach `target` within `precision`, relative to the original distance. + * @param precision Relative target precision of the interpolation. Defaults to 1% distance remaining. + * + * @see https://twitter.com/FreyaHolmer/status/1757918211679650262 + * + * @return The interpolated value. + */ + public static function smoothLerpPrecision(base:Float, target:Float, deltaTime:Float, duration:Float, precision:Float = 1 / 100):Float + { + if (deltaTime == 0) return base; + if (base == target) return target; + return lerp(target, base, Math.pow(precision, deltaTime / duration)); + } + + /** + * Snap a value to another if it's within a certain distance (inclusive). + * + * Helpful when using functions like `smoothLerpPrecision` to ensure the value actually reaches the target. + * + * @param base The base value to conditionally snap. + * @param target The target value to snap to. + * @param threshold Maximum distance between the two for snapping to occur. + * + * @return `target` if `base` is within `threshold` of it, otherwise `base`. + */ + public static function snap(base:Float, target:Float, threshold:Float):Float + { + return Math.abs(base - target) <= threshold ? target : base; + } + + /** + * Perform linear interpolation between the base and the target, based on the current framerate. + * @param base The starting value, when `progress <= 0`. + * @param target The ending value, when `progress >= 1`. + * @param ratio Value used to interpolate between `base` and `target`. + * + * @return The interpolated value. + */ + @:deprecated('Use smoothLerpPrecision instead') + public static function coolLerp(base:Float, target:Float, ratio:Float):Float + { + return base + cameraLerp(ratio) * (target - base); + } + + /** + * Perform linear interpolation based on the current framerate. + * @param lerp Value used to interpolate between `base` and `target`. + * + * @return The interpolated value. + */ + @:deprecated('Use smoothLerpPrecision instead') + public static function cameraLerp(lerp:Float):Float + { + return lerp * (FlxG.elapsed / (1 / 60)); + } + + /** + * Backwards compatibility for `smoothLerpPrecision`. + * + * Perform a framerate-independent linear interpolation between the base value and the target. + * @param current The current value. + * @param target The target value. + * @param elapsed The time elapsed since the last frame. + * @param duration The total duration of the interpolation. Nominal duration until remaining distance is less than `precision`. + * @param precision The target precision of the interpolation. Defaults to 1% of distance remaining. + * @see https://twitter.com/FreyaHolmer/status/1757918211679650262 + * + * @return A value between the current value and the target value. + */ + @:deprecated('Use smoothLerpPrecision instead') + public static function smoothLerp(current:Float, target:Float, elapsed:Float, duration:Float, precision:Float = 1 / 100):Float + { + // An alternative algorithm which uses a separate half-life value: + // var halfLife:Float = -duration / logBase(2, precision); + // lerp(current, target, 1 - exp2(-elapsed / halfLife)); + + if (current == target) return target; + + var result:Float = lerp(current, target, 1 - Math.pow(precision, elapsed / duration)); + + // TODO: Is there a better way to ensure a lerp which actually reaches the target? + // Research a framerate-independent PID lerp. + if (Math.abs(result - target) < (precision * target)) result = target; + + return result; + } + + /** + * GCD stands for Greatest Common Divisor + * It's used in FullScreenScaleMode to prevent weird window resolutions from being counted as wide screen since those were causing issues positioning the game + * It returns the greatest common divisor between m and n + * + * think it's from hxp..? + * @param m + * @param n + * @return Int the common divisor between m and n + */ + public static function gcd(m:Int, n:Int):Int + { + m = Math.floor(Math.abs(m)); + n = Math.floor(Math.abs(n)); + var t; + do + { + if (n == 0) return m; + t = m; + m = n; + n = t % m; + } + while (true); + } +} diff --git a/source/PlayState.hx b/source/PlayState.hx index 1e1fda87dfe..475bc2d70b7 100644 --- a/source/PlayState.hx +++ b/source/PlayState.hx @@ -1657,7 +1657,7 @@ class PlayState extends MusicBeatState polyphonyBF = value; // just in case, as an anti-crash prevention maybe? default: - polyphonyOppo = value; + polyphonyOppo = value; polyphonyBF = value; } return value; @@ -2824,7 +2824,7 @@ class PlayState extends MusicBeatState { for (group in [notes, sustainNotes]) for (note in group){ - if (note == null) + if (note == null || !note.alive) continue; if (ClientPrefs.enableColorShader) note.updateRGBColors(); } @@ -4273,7 +4273,7 @@ class PlayState extends MusicBeatState { for (group in [notes, sustainNotes]) for (note in group){ - if (note == null) + if (note == null || !note.alive) continue; if (ClientPrefs.enableColorShader) note.updateRGBColors(); } @@ -4783,7 +4783,7 @@ class PlayState extends MusicBeatState function judgeNote(note:Note = null, ?miss:Bool = false) { - if (note == null) return; + if (note == null || !note.alive) return; if (daRating == null) daRating = ratingsData[0]; //because it likes being stupid if (!cpuControlled) { @@ -5815,7 +5815,7 @@ class PlayState extends MusicBeatState } public function spawnHoldSplashOnNote(note:Note, ?isDad:Bool = false) { - if (!ClientPrefs.noteSplashes || note == null) + if (!ClientPrefs.noteSplashes || note == null || !note.alive) return; splashesPerFrame[(isDad ? 2 : 3)] += 1; diff --git a/source/ResetScoreSubState.hx b/source/ResetScoreSubState.hx index 431d6cc011c..a781ae7231c 100644 --- a/source/ResetScoreSubState.hx +++ b/source/ResetScoreSubState.hx @@ -2,112 +2,124 @@ import flixel.FlxG; class ResetScoreSubState extends MusicBeatSubstate { - var bg:FlxSprite; - var alphabetArray:Array = []; - var icon:HealthIcon; - var onYes:Bool = false; - var yesText:Alphabet; - var noText:Alphabet; + var bg:FlxSprite; + var alphabetArray:Array = []; + var icon:HealthIcon; + var onYes:Bool = false; + var yesText:Alphabet; + var noText:Alphabet; - var song:String; - var difficulty:Int; - var week:Int; + var song:String; + var difficulty:Int; + var week:Int; - // Week -1 = Freeplay - public function new(song:String, difficulty:Int, character:String, week:Int = -1) - { - this.song = song; - this.difficulty = difficulty; - this.week = week; + // Week -1 = Freeplay + public function new(song:String, difficulty:Int, character:String, week:Int = -1) + { + this.song = song; + this.difficulty = difficulty; + this.week = week; - super(); + super(); - var name:String = song; - if(week > -1) { - name = WeekData.weeksLoaded.get(WeekData.weeksList[week]).weekName; - } - name += ' (' + CoolUtil.difficulties[difficulty] + ')?'; + var name:String = song; + if (week > -1) + { + name = WeekData.weeksLoaded.get(WeekData.weeksList[week]).weekName; + } + name += ' (' + CoolUtil.difficulties[difficulty] + ')?'; - bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); - bg.alpha = 0; - bg.scrollFactor.set(); - add(bg); + bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); + bg.alpha = 0; + bg.scrollFactor.set(); + add(bg); - var tooLong:Float = (name.length > 18) ? 0.8 : 1; //Fucking Winter Horrorland - var text:Alphabet = new Alphabet(0, 180, "Reset the score of", true); - text.screenCenter(X); - alphabetArray.push(text); - text.alpha = 0; - add(text); - var text:Alphabet = new Alphabet(0, text.y + 90, name, true); - text.scaleX = tooLong; - text.screenCenter(X); - if(week == -1) text.x += 60 * tooLong; - alphabetArray.push(text); - text.alpha = 0; - add(text); - if(week == -1) { - icon = new HealthIcon(character); - icon.setGraphicSize(Std.int(icon.width * tooLong)); - icon.updateHitbox(); - icon.setPosition(text.x - icon.width + (10 * tooLong), text.y - 30); - icon.alpha = 0; - add(icon); - } + var tooLong:Float = (name.length > 18) ? 0.8 : 1; // Fucking Winter Horrorland + var text:Alphabet = new Alphabet(0, 180, "Reset the score of", true); + text.screenCenter(X); + alphabetArray.push(text); + text.alpha = 0; + add(text); + var text:Alphabet = new Alphabet(0, text.y + 90, name, true); + text.scaleX = tooLong; + text.screenCenter(X); + if (week == -1) text.x += 60 * tooLong; + alphabetArray.push(text); + text.alpha = 0; + add(text); + if (week == -1) + { + icon = new HealthIcon(character); + icon.setGraphicSize(Std.int(icon.width * tooLong)); + icon.updateHitbox(); + icon.setPosition(text.x - icon.width + (10 * tooLong), text.y - 30); + icon.alpha = 0; + add(icon); + } - yesText = new Alphabet(0, text.y + 150, 'Yes', true); - yesText.screenCenter(X); - yesText.x -= 200; - add(yesText); - noText = new Alphabet(0, text.y + 150, 'No', true); - noText.screenCenter(X); - noText.x += 200; - add(noText); - updateOptions(); - } + yesText = new Alphabet(0, text.y + 150, 'Yes', true); + yesText.screenCenter(X); + yesText.x -= 200; + add(yesText); + noText = new Alphabet(0, text.y + 150, 'No', true); + noText.screenCenter(X); + noText.x += 200; + add(noText); + updateOptions(); + } - override function update(elapsed:Float) - { - bg.alpha += elapsed * 1.5; - if(bg.alpha > 0.6) bg.alpha = 0.6; + override function update(elapsed:Float) + { + bg.alpha += elapsed * 1.5; + if (bg.alpha > 0.6) bg.alpha = 0.6; - for (i in 0...alphabetArray.length) { - var spr = alphabetArray[i]; - spr.alpha += elapsed * 2.5; - } - if(week == -1) icon.alpha += elapsed * 2.5; + for (i in 0...alphabetArray.length) + { + var spr = alphabetArray[i]; + spr.alpha += elapsed * 2.5; + } + if (week == -1) icon.alpha += elapsed * 2.5; - if(controls.UI_LEFT_P || controls.UI_RIGHT_P) { - FlxG.sound.play(Paths.sound('scrollMenu'), 1); - onYes = !onYes; - updateOptions(); - } - if(controls.BACK) { - FlxG.sound.play(Paths.sound('cancelMenu'), 1); - close(); - } else if(controls.ACCEPT) { - if(onYes) { - if(week == -1) { - Highscore.resetSong(song, difficulty); - } else { - Highscore.resetWeek(WeekData.weeksList[week], difficulty); - } - } - FlxG.sound.play(Paths.sound('cancelMenu'), 1); - close(); - } - super.update(elapsed); - } + if (controls.UI_LEFT_P || controls.UI_RIGHT_P) + { + FlxG.sound.play(Paths.sound('scrollMenu'), 1); + onYes = !onYes; + updateOptions(); + } + if (controls.BACK) + { + FlxG.sound.play(Paths.sound('cancelMenu'), 1); + close(); + } + else if (controls.ACCEPT) + { + if (onYes) + { + if (week == -1) + { + Highscore.resetSong(song, difficulty); + } + else + { + Highscore.resetWeek(WeekData.weeksList[week], difficulty); + } + } + FlxG.sound.play(Paths.sound('cancelMenu'), 1); + close(); + } + super.update(elapsed); + } - function updateOptions() { - var scales:Array = [0.75, 1]; - var alphas:Array = [0.6, 1.25]; - var confirmInt:Int = onYes ? 1 : 0; + function updateOptions() + { + var scales:Array = [0.75, 1]; + var alphas:Array = [0.6, 1.25]; + var confirmInt:Int = onYes ? 1 : 0; - yesText.alpha = alphas[confirmInt]; - yesText.scale.set(scales[confirmInt], scales[confirmInt]); - noText.alpha = alphas[1 - confirmInt]; - noText.scale.set(scales[1 - confirmInt], scales[1 - confirmInt]); - if(week == -1) icon.animation.curAnim.curFrame = confirmInt; - } + yesText.alpha = alphas[confirmInt]; + yesText.scale.set(scales[confirmInt], scales[confirmInt]); + noText.alpha = alphas[1 - confirmInt]; + noText.scale.set(scales[1 - confirmInt], scales[1 - confirmInt]); + if (week == -1) icon.animation.curAnim.curFrame = confirmInt; + } } diff --git a/source/StageData.hx b/source/StageData.hx index 2c22ff6b724..89d910a5048 100644 --- a/source/StageData.hx +++ b/source/StageData.hx @@ -79,10 +79,7 @@ class StageData { rawJson = Assets.getText(path); } #end - else - { - return null; - } + else { return null; } return cast Json.parse(rawJson); } } diff --git a/source/StrumNote.hx b/source/StrumNote.hx index d7447c6977c..6c28d653f8f 100644 --- a/source/StrumNote.hx +++ b/source/StrumNote.hx @@ -3,209 +3,263 @@ package; import shaders.RGBPalette.RGBShaderReference; import shaders.RGBPalette; +// wow this code is a mess -avie class StrumNote extends FlxSprite { - public var rgbShader:RGBShaderReference; - public var notes_angle:Null = null; - public var resetAnim:Float = 0; - public var noteData:Int = 0; - public var direction:Float = 90;//plan on doing scroll directions soon -bb - public var downScroll:Bool = false;//plan on doing scroll directions soon -bb - public var sustainReduce:Bool = true; - - public var player:Int; - public var ogNoteskin:String = null; - - public var texture(default, set):String = null; - private function set_texture(value:String):String { - if(texture != value) { - texture = (value != null ? value : "noteskins/NOTE_assets" + Note.getNoteSkinPostfix()); - reloadNote(); - } - return value; - } - public var useRGBShader:Bool = true; - - public function getAngle() { - return (notes_angle == null ? angle : notes_angle); - } - - public function new(x:Float, y:Float, leData:Int, player:Int, ?inEditor:Bool = false) { - rgbShader = new RGBShaderReference(this, Note.initializeGlobalRGBShader(leData)); - rgbShader.enabled = false; - if(PlayState.SONG != null && PlayState.SONG.disableNoteRGB || !ClientPrefs.enableColorShader) useRGBShader = false; - - var arr:Array = ClientPrefs.arrowRGB[leData]; - if(PlayState.isPixelStage) arr = ClientPrefs.arrowRGBPixel[leData]; - if(arr != null && leData <= arr.length && useRGBShader) - { - @:bypassAccessor - { - rgbShader.r = arr[0]; - rgbShader.g = arr[1]; - rgbShader.b = arr[2]; - } - } - noteData = leData; - this.player = player; - this.noteData = leData; - super(x, y); - - var skin:String = null; - if(PlayState.SONG != null && PlayState.SONG.arrowSkin != null && PlayState.SONG.arrowSkin.length > 1) skin = PlayState.SONG.arrowSkin; - else skin = Note.defaultNoteSkin; - - var customSkin:String = skin + Note.getNoteSkinPostfix(); - if(Paths.fileExists('images/$customSkin.png', IMAGE)) skin = customSkin; - - texture = skin; //Load texture and anims - ogNoteskin = skin; - - scrollFactor.set(); - } - - public function reloadNote() - { - var lastAnim:String = null; - if(animation.curAnim != null) lastAnim = animation.curAnim.name; - - if(PlayState.isPixelStage) - { - loadGraphic(Paths.image('pixelUI/' + texture)); - width = width / 4; - height = height / 5; - loadGraphic(Paths.image('pixelUI/' + texture), true, Math.floor(width), Math.floor(height)); - - antialiasing = false; - setGraphicSize(Std.int(width * PlayState.daPixelZoom)); - - animation.add('green', [6]); - animation.add('red', [7]); - animation.add('blue', [5]); - animation.add('purple', [4]); - switch (Math.abs(noteData) % 4) - { - case 0: - animation.add('static', [0]); - animation.add('pressed', [4, 8], 12, false); - animation.add('confirm', [12, 16], 24, false); - case 1: - animation.add('static', [1]); - animation.add('pressed', [5, 9], 12, false); - animation.add('confirm', [13, 17], 24, false); - case 2: - animation.add('static', [2]); - animation.add('pressed', [6, 10], 12, false); - animation.add('confirm', [14, 18], 12, false); - case 3: - animation.add('static', [3]); - animation.add('pressed', [7, 11], 12, false); - animation.add('confirm', [15, 19], 24, false); - } - } - else - { - frames = Paths.getSparrowAtlas(texture); - animation.addByPrefix('green', 'arrowUP'); - animation.addByPrefix('blue', 'arrowDOWN'); - animation.addByPrefix('purple', 'arrowLEFT'); - animation.addByPrefix('red', 'arrowRIGHT'); - - antialiasing = ClientPrefs.globalAntialiasing; - setGraphicSize(Std.int(width * 0.7)); - - switch (Math.abs(noteData) % 4) - { - case 0: - animation.addByPrefix('static', 'arrowLEFT'); - animation.addByPrefix('pressed', 'left press', 24, false); - animation.addByPrefix('confirm', 'left confirm', 24, false); - case 1: - animation.addByPrefix('static', 'arrowDOWN'); - animation.addByPrefix('pressed', 'down press', 24, false); - animation.addByPrefix('confirm', 'down confirm', 24, false); - case 2: - animation.addByPrefix('static', 'arrowUP'); - animation.addByPrefix('pressed', 'up press', 24, false); - animation.addByPrefix('confirm', 'up confirm', 24, false); - case 3: - animation.addByPrefix('static', 'arrowRIGHT'); - animation.addByPrefix('pressed', 'right press', 24, false); - animation.addByPrefix('confirm', 'right confirm', 24, false); - } - } - updateHitbox(); - - if(lastAnim != null) - { - playAnim(lastAnim, true); - } - } - - public function postAddedToGroup() { - playAnim('static'); - x += Note.swagWidth * noteData; - x += 50; - x += ((FlxG.width / 2) * player); - ID = noteData; - } - - override function update(elapsed:Float) { - if (ClientPrefs.ffmpegMode) elapsed = 1 / ClientPrefs.targetFPS; - if(resetAnim > 0) { - resetAnim -= elapsed; - if(resetAnim <= 0) { - playAnim('static'); - resetAnim = 0; - } - } - super.update(elapsed); - } - - public function playAnim(anim:String, ?force:Bool = false, ?r:FlxColor, ?g:FlxColor, ?b:FlxColor) { - animation.play(anim, force); - if(animation.curAnim != null) - { - centerOffsets(); - centerOrigin(); - } - if(useRGBShader) - { - rgbShader.enabled = (animation.curAnim != null && animation.curAnim.name != 'static'); - if (r != null && g != null && b != null) updateRGBColors(r, g, b); - } else if (!useRGBShader && rgbShader != null) rgbShader.enabled = false; - } - public function updateNoteSkin(noteskin:String) { - if (texture == "noteskins/" + noteskin || noteskin == ogNoteskin || texture == noteskin) return; //if the noteskin to change to is the same as before then don't update it - if (noteskin != null && noteskin.length > 0) texture = "noteskins/" + noteskin; - else texture = "noteskins/NOTE_assets" + Note.getNoteSkinPostfix(); - } - public function updateRGBColors(?r:FlxColor, ?g:FlxColor, ?b:FlxColor) { - if (rgbShader != null) - { - rgbShader.r = r; - rgbShader.g = g; - rgbShader.b = b; - } - } - public function resetRGB() - { - if (rgbShader != null && animation.curAnim != null && animation.curAnim.name == 'static') - { - switch (ClientPrefs.noteColorStyle) - { - case 'Quant-Based', 'Rainbow', 'Char-Based': - rgbShader.r = 0xFFF9393F; - rgbShader.g = 0xFFFFFFFF; - rgbShader.b = 0xFF651038; - case 'Grayscale': - rgbShader.r = 0xFFA0A0A0; - rgbShader.g = FlxColor.WHITE; - rgbShader.b = 0xFF424242; - default: - - } - rgbShader.enabled = false; - } - } + public var rgbShader:RGBShaderReference; + public var notes_angle:Null = null; + public var resetAnim:Float = 0; + public var noteData:Int = 0; + public var direction:Float = 90; // plan on doing scroll directions soon -bb + public var downScroll:Bool = false; // plan on doing scroll directions soon -bb + public var sustainReduce:Bool = true; + + static final HOLD_TIME:Float = 0.1; + + public var HoldTimer:Float = -1; + + public var player:Int; + public var ogNoteskin:String = null; + + public var texture(default, set):String = null; + + private function set_texture(value:String):String + { + if (texture != value) + { + texture = (value != null ? value : "noteskins/NOTE_assets" + Note.getNoteSkinPostfix()); + reloadNote(); + } + return value; + } + + public var useRGBShader:Bool = true; + + public function getAngle() + { + return (notes_angle == null ? angle : notes_angle); + } + + public function new(x:Float, y:Float, leData:Int, player:Int, ?inEditor:Bool = false) + { + rgbShader = new RGBShaderReference(this, Note.initializeGlobalRGBShader(leData)); + rgbShader.enabled = false; + if (PlayState.SONG != null && PlayState.SONG.disableNoteRGB || !ClientPrefs.enableColorShader) useRGBShader = false; + + var arr:Array = ClientPrefs.arrowRGB[leData]; + if (PlayState.isPixelStage) arr = ClientPrefs.arrowRGBPixel[leData]; + if (arr != null && leData <= arr.length && useRGBShader) + { + @:bypassAccessor + { + rgbShader.r = arr[0]; + rgbShader.g = arr[1]; + rgbShader.b = arr[2]; + } + } + noteData = leData; + this.player = player; + this.noteData = leData; + super(x, y); + + var skin:String = null; + + if (PlayState.SONG != null && PlayState.SONG.arrowSkin != null && PlayState.SONG.arrowSkin.length > 1) skin = PlayState.SONG.arrowSkin; + else + skin = Note.defaultNoteSkin; + + var customSkin:String = skin + Note.getNoteSkinPostfix(); + if (Paths.fileExists('images/$customSkin.png', IMAGE)) skin = customSkin; + + texture = skin; // Load texture and anims + ogNoteskin = skin; + + this.animation.onFrameChange.add(onAnimationFrame); + this.animation.onFinish.add(onAnimationFinished); + + scrollFactor.set(); + } + + function onAnimationFrame(name:String, frameNumber:Int, frameIndex:Int):Void {} // not that this func is unfinished but it's made to do nothing + + function onAnimationFinished(name:String):Void + { + // better code for the confirm shit + if (name == 'confirm') + { + HoldTimer = 0; + } + } + + public function reloadNote() + { + /* + * WHAT THE FUCK IS THIS?? + * YOU SHOULD MAKE THIS FUNCTION ON A SEPARATE SCRIPT FILE + * ALSO WHY THE FUCK IS THE GAME HARDCODED + * HXS, HXC AND OTHER HSCRIPT SHIT EXISTS + * + * @see https://files.catbox.moe/kfcmrb.PNG + * + * sorry i had to rant about everything on the jse source + * -avie + */ + var lastAnim:String = null; + if (animation.curAnim != null) lastAnim = animation.curAnim.name; + + if (PlayState.isPixelStage) + { + loadGraphic(Paths.image('pixelUI/' + texture)); + width = width / 4; + height = height / 5; + loadGraphic(Paths.image('pixelUI/' + texture), true, Math.floor(width), Math.floor(height)); + + antialiasing = false; + setGraphicSize(Std.int(width * PlayState.daPixelZoom)); + + animation.add('green', [6]); + animation.add('red', [7]); + animation.add('blue', [5]); + animation.add('purple', [4]); + switch (Math.abs(noteData) % 4) + { + case 0: + animation.add('static', [0]); + animation.add('pressed', [4, 8], 12, false); + animation.add('confirm', [12, 16], 24, false); + case 1: + animation.add('static', [1]); + animation.add('pressed', [5, 9], 12, false); + animation.add('confirm', [13, 17], 24, false); + case 2: + animation.add('static', [2]); + animation.add('pressed', [6, 10], 12, false); + animation.add('confirm', [14, 18], 12, false); + case 3: + animation.add('static', [3]); + animation.add('pressed', [7, 11], 12, false); + animation.add('confirm', [15, 19], 24, false); + } + } + else + { + frames = Paths.getSparrowAtlas(texture); + animation.addByPrefix('green', 'arrowUP'); + animation.addByPrefix('blue', 'arrowDOWN'); + animation.addByPrefix('purple', 'arrowLEFT'); + animation.addByPrefix('red', 'arrowRIGHT'); + + antialiasing = ClientPrefs.globalAntialiasing; + setGraphicSize(Std.int(width * 0.7)); + + switch (Math.abs(noteData) % 4) + { + case 0: + animation.addByPrefix('static', 'arrowLEFT'); + animation.addByPrefix('pressed', 'left press', 24, false); + animation.addByPrefix('confirm', 'left confirm', 24, false); + case 1: + animation.addByPrefix('static', 'arrowDOWN'); + animation.addByPrefix('pressed', 'down press', 24, false); + animation.addByPrefix('confirm', 'down confirm', 24, false); + case 2: + animation.addByPrefix('static', 'arrowUP'); + animation.addByPrefix('pressed', 'up press', 24, false); + animation.addByPrefix('confirm', 'up confirm', 24, false); + case 3: + animation.addByPrefix('static', 'arrowRIGHT'); + animation.addByPrefix('pressed', 'right press', 24, false); + animation.addByPrefix('confirm', 'right confirm', 24, false); + } + } + updateHitbox(); + + if (lastAnim != null) + { + playAnim(lastAnim, true); + } + } + + public function postAddedToGroup() + { + playAnim('static'); + x += Note.swagWidth * noteData; + x += 50; + x += ((FlxG.width / 2) * player); + ID = noteData; + } + + override function update(elapsed:Float) + { + if (ClientPrefs.ffmpegMode) elapsed = 1 / ClientPrefs.targetFPS; + + super.update(elapsed); + + centerOrigin(); + + if (HoldTimer >= 0) + { + HoldTimer += elapsed; + if (HoldTimer >= HOLD_TIME) + { + HoldTimer = -1; + playAnim('static'); + } + } + } + + public function playAnim(anim:String, ?force:Bool = false, ?r:FlxColor, ?g:FlxColor, ?b:FlxColor) + { + animation.play(anim, force); + if (animation.curAnim != null) + { + centerOffsets(); + centerOrigin(); + } + if (useRGBShader) + { + rgbShader.enabled = (animation.curAnim != null && animation.curAnim.name != 'static'); + if (r != null && g != null && b != null) updateRGBColors(r, g, b); + } + else if (!useRGBShader && rgbShader != null) rgbShader.enabled = false; + } + + public function updateNoteSkin(noteskin:String) + { + if (texture == "noteskins/" + noteskin || noteskin == ogNoteskin || texture == noteskin) return + ; // if the noteskin to change to is the same as before then don't update it + if (noteskin != null && noteskin.length > 0) texture = "noteskins/" + noteskin; + else + texture = "noteskins/NOTE_assets" + Note.getNoteSkinPostfix(); + } + + public function updateRGBColors(?r:FlxColor, ?g:FlxColor, ?b:FlxColor) + { + if (rgbShader != null) + { + rgbShader.r = r; + rgbShader.g = g; + rgbShader.b = b; + } + } + + public function resetRGB() + { + if (rgbShader != null && animation.curAnim != null && animation.curAnim.name == 'static') + { + switch (ClientPrefs.noteColorStyle) + { + case 'Quant-Based', 'Rainbow', 'Char-Based': + rgbShader.r = 0xFFF9393F; + rgbShader.g = 0xFFFFFFFF; + rgbShader.b = 0xFF651038; + case 'Grayscale': + rgbShader.r = 0xFFA0A0A0; + rgbShader.g = FlxColor.WHITE; + rgbShader.b = 0xFF424242; + default: + } + rgbShader.enabled = false; + } + } } diff --git a/source/TitleState.hx b/source/TitleState.hx index 16b1e27c46d..0fde83465dc 100644 --- a/source/TitleState.hx +++ b/source/TitleState.hx @@ -12,503 +12,513 @@ import openfl.Assets; import openfl.display.Bitmap; import openfl.display.BitmapData; import options.GraphicsSettingsSubState; - #if VIDEOS_ALLOWED import VideoSprite; #end typedef TitleData = { - - titlex:Float, - titley:Float, - startx:Float, - starty:Float, - gfx:Float, - gfy:Float, - backgroundSprite:String, - bpm:Int, - endY:Float, + titlex:Float, + titley:Float, + startx:Float, + starty:Float, + gfx:Float, + gfy:Float, + backgroundSprite:String, + bpm:Int, + endY:Float, } + class TitleState extends MusicBeatState { - public static var muteKeys:Array = [FlxKey.ZERO]; - public static var volumeDownKeys:Array = [FlxKey.NUMPADMINUS, FlxKey.MINUS]; - public static var volumeUpKeys:Array = [FlxKey.NUMPADPLUS, FlxKey.PLUS]; - - public static var initialized:Bool = false; - - public var inCutscene:Bool = false; - var canPause:Bool = true; - - var blackScreen:FlxSprite; - var credGroup:FlxGroup; - var credTextShit:Alphabet; - var textGroup:FlxGroup; - var ngSpr:FlxSprite; - - var titleTextColors:Array = [0xFF33FFFF, 0xFF3333CC]; - var titleTextAlphas:Array = [1, .64]; - - var curWacky:Array = []; - - var mustUpdate:Bool = false; - - public static var titleJSON:TitleData; - - public static var updateVersion:String = ''; - - override public function create():Void - { - Paths.clearStoredMemory(); - Paths.clearUnusedMemory(); - - MusicBeatState.windowNameSuffix = " - Title Screen"; - - MusicBeatState.windowNamePrefix = Assets.getText(Paths.txt("windowTitleBase", "preload")); - - curWacky = FlxG.random.getObject(getIntroTextShit()); - - // DEBUG BULLSHIT - - swagShader = new ColorSwap(); - super.create(); - - #if (CHECK_FOR_UPDATES) - if(ClientPrefs.checkForUpdates && !closedState && !Main.askedToUpdate) { - trace('checking for update'); - var http = new haxe.Http("https://raw.githubusercontent.com/JordanSantiagoYT/FNF-JS-Engine/main/THECHANGELOG.md"); - var returnedData:Array = []; - - http.onData = function (data:String) - { - var versionEndIndex:Int = data.indexOf(';'); - returnedData[0] = data.substring(0, versionEndIndex); - - // Extract the changelog after the version number - returnedData[1] = data.substring(versionEndIndex + 1, data.length); - updateVersion = returnedData[0]; - final curVersion:String = MainMenuState.psychEngineJSVersion.trim(); - final cleanVersion:String = curVersion.split(" (")[0]; // Removes everything after " (" - trace(cleanVersion); - trace('version online: ' + updateVersion + ', your version: ' + cleanVersion); - if(updateVersion != cleanVersion && CoolUtil.isVersionNewer(updateVersion, cleanVersion)) { - trace('versions arent matching!'); - OutdatedState.currChanges = returnedData[1]; - mustUpdate = true; - Main.askedToUpdate = true; - } - if(updateVersion == curVersion) { - trace('the versions match!'); - } - else - trace('$updateVersion is less than $cleanVersion. Skipping update as it\'s likely an dev version'); - } - - http.onError = function (error) { - trace('error: $error'); - } - - http.request(); - } - #end - - Highscore.load(); - - // IGNORE THIS!!! - titleJSON = Json.parse(Paths.getTextFromFile('images/gfDanceTitle.json')); - - if(!initialized) - { - if(FlxG.save.data != null && FlxG.save.data.fullscreen) - { - FlxG.fullscreen = FlxG.save.data.fullscreen; - } - persistentUpdate = true; - persistentDraw = true; - } - - if (FlxG.save.data.weekCompleted != null) - { - StoryMenuState.weekCompleted = FlxG.save.data.weekCompleted; - } - - FlxG.mouse.visible = false; - #if FREEPLAY - FlxG.switchState(FreeplayState.new); - #elseif CHARTING - FlxG.switchState(ChartingState.new); - #else - if(FlxG.save.data.flashing == null && !FlashingState.leftState) { - FlxTransitionableState.skipNextTransIn = true; - FlxTransitionableState.skipNextTransOut = true; - FlxG.switchState(FlashingState.new); - } else { - if (initialized) - startIntro(); - else - { - new FlxTimer().start(1, function(tmr:FlxTimer) - { - startIntro(); - }); - } - } - #end - } - - var logoBl:FlxSprite; - var gfDance:FlxSprite; - var danceLeft:Bool = false; - var titleText:FlxSprite; - var swagShader:ColorSwap = null; - - function startIntro() - { - if (!initialized) - { - Paths.playMenuMusic(true, 0); - } - - switch(ClientPrefs.daMenuMusic) // change this if you're making a source mod, like add your own or something - { - case 'Mashup' | 'VS Impostor' | 'VS Nonsense V2': - Conductor.changeBPM(102); - case 'Dave & Bambi': - Conductor.changeBPM(148); - case 'Dave & Bambi (Old)': - Conductor.changeBPM(150); - case 'DDTO+': - Conductor.changeBPM(120); - case 'Anniversary': - Conductor.changeBPM(115); - case 'Base Game' | 'Default' | 'None' | 'Christmas': // just in case you're not making a source mod & wanna change this - Conductor.changeBPM(titleJSON.bpm); - default: // fallback - Conductor.changeBPM(titleJSON.bpm); - } - persistentUpdate = true; - - var bg:FlxSprite = new FlxSprite(); - - if (titleJSON.backgroundSprite != null && titleJSON.backgroundSprite.length > 0 && titleJSON.backgroundSprite != "none"){ - bg.loadGraphic(Paths.image(titleJSON.backgroundSprite)); - }else{ - bg.makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); - } - add(bg); - - logoBl = new FlxSprite(titleJSON.titlex, titleJSON.titley); - logoBl.frames = Paths.getSparrowAtlas('logoBumpin'); - - logoBl.antialiasing = ClientPrefs.globalAntialiasing; - logoBl.animation.addByPrefix('bump', 'logo bumpin', 24, false); - logoBl.animation.play('bump'); - logoBl.updateHitbox(); - - swagShader = new ColorSwap(); - gfDance = new FlxSprite(titleJSON.gfx, titleJSON.gfy); - - gfDance.frames = Paths.getSparrowAtlas('gfDanceTitle'); - gfDance.animation.addByIndices('danceLeft', 'gfDance', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false); - gfDance.animation.addByIndices('danceRight', 'gfDance', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false); - gfDance.antialiasing = ClientPrefs.globalAntialiasing; - - add(gfDance); - gfDance.shader = swagShader.shader; - add(logoBl); - logoBl.shader = swagShader.shader; - - titleText = new FlxSprite(titleJSON.startx, titleJSON.starty); - #if (desktop && MODS_ALLOWED) - var path = "mods/" + Paths.currentModDirectory + "/images/titleEnter.png"; - //trace(path, FileSystem.exists(path)); - if (!FileSystem.exists(path)){ - path = "mods/images/titleEnter.png"; - } - //trace(path, FileSystem.exists(path)); - if (!FileSystem.exists(path)){ - path = "assets/images/titleEnter.png"; - } - //trace(path, FileSystem.exists(path)); - titleText.frames = FlxAtlasFrames.fromSparrow(BitmapData.fromFile(path),File.getContent(StringTools.replace(path,".png",".xml"))); - #else - - titleText.frames = Paths.getSparrowAtlas('titleEnter'); - #end - var animFrames:Array = []; - @:privateAccess { - titleText.animation.findByPrefix(animFrames, "ENTER IDLE"); - titleText.animation.findByPrefix(animFrames, "ENTER FREEZE"); - } - - if (animFrames.length > 0) { - newTitle = true; - - titleText.animation.addByPrefix('idle', "ENTER IDLE", 24); - titleText.animation.addByPrefix('press', ClientPrefs.flashing ? "ENTER PRESSED" : "ENTER FREEZE", 24); - } - else { - newTitle = false; - - titleText.animation.addByPrefix('idle', "Press Enter to Begin", 24); - titleText.animation.addByPrefix('press', "ENTER PRESSED", 24); - } - - titleText.antialiasing = ClientPrefs.globalAntialiasing; - titleText.animation.play('idle'); - titleText.updateHitbox(); - add(titleText); - - credGroup = new FlxGroup(); - add(credGroup); - textGroup = new FlxGroup(); - - blackScreen = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); - credGroup.add(blackScreen); - - credTextShit = new Alphabet(0, 0, "", true); - credTextShit.screenCenter(); - - credTextShit.visible = false; - - ngSpr = new FlxSprite(0, FlxG.height * 0.52).loadGraphic(Paths.image('newgrounds_logo')); - add(ngSpr); - ngSpr.visible = false; - ngSpr.setGraphicSize(Std.int(ngSpr.width * 0.8)); - ngSpr.updateHitbox(); - ngSpr.screenCenter(X); - ngSpr.antialiasing = ClientPrefs.globalAntialiasing; - - FlxTween.tween(credTextShit, {y: credTextShit.y + 20}, 2.9, {ease: FlxEase.quadInOut, type: PINGPONG}); - - if (initialized) - skipIntro(); - else - initialized = true; - } - - function getIntroTextShit():Array> - { - var fullText:String = Assets.getText(Paths.txt('introText')); - - var firstArray:Array = fullText.split('\n'); - var swagGoodArray:Array> = []; - - for (i in firstArray) - { - swagGoodArray.push(i.split('--')); - } - - return swagGoodArray; - } - - var transitioning:Bool = false; - - var newTitle:Bool = false; - var titleTimer:Float = 0; - - override function update(elapsed:Float) - { - if (FlxG.sound.music != null) - Conductor.songPosition = FlxG.sound.music.time; - - var pressedEnter:Bool = FlxG.keys.justPressed.ENTER || controls.ACCEPT; - - var gamepad:FlxGamepad = FlxG.gamepads.lastActive; - - if (gamepad != null) - { - if (gamepad.justPressed.START) - pressedEnter = true; - } - - if (newTitle) { - titleTimer += CoolUtil.boundTo(elapsed, 0, 1); - if (titleTimer > 2) titleTimer -= 2; - } - - if (initialized && !transitioning && skippedIntro) - { - if (newTitle && !pressedEnter) - { - var timer:Float = titleTimer; - if (timer >= 1) - timer = (-timer) + 2; - - timer = FlxEase.quadInOut(timer); - - titleText.color = FlxColor.interpolate(titleTextColors[0], titleTextColors[1], timer); - titleText.alpha = FlxMath.lerp(titleTextAlphas[0], titleTextAlphas[1], timer); - } - - if(pressedEnter) - { - titleText.color = FlxColor.WHITE; - titleText.alpha = 1; - - if(titleText != null) titleText.animation.play('press'); - - FlxG.camera.flash(ClientPrefs.flashing ? FlxColor.WHITE : 0x4CFFFFFF, 1); - FlxG.sound.play(Paths.sound('confirmMenu'), 0.7); - - transitioning = true; - - new FlxTimer().start(1, function(tmr:FlxTimer) - { - if (mustUpdate) { - FlxG.switchState(OutdatedState.new); - } else { - FlxG.switchState(MainMenuState.new); - } - closedState = true; - }); - } - } - - if (initialized && pressedEnter && !skippedIntro) - { - skipIntro(); - } - - if(swagShader != null) - { - if(controls.UI_LEFT) swagShader.hue -= elapsed * 0.1; - if(controls.UI_RIGHT) swagShader.hue += elapsed * 0.1; - } - - super.update(elapsed); - } - - function createCoolText(textArray:Array, ?offset:Float = 0) - { - for (i in 0...textArray.length) - { - var money:Alphabet = new Alphabet(0, 0, textArray[i], true); - money.screenCenter(X); - money.y += (i * 60) + 200 + offset; - if(credGroup != null && textGroup != null) { - credGroup.add(money); - textGroup.add(money); - } - } - } - - function addMoreText(text:String, ?offset:Float = 0) - { - if(textGroup != null && credGroup != null) { - var coolText:Alphabet = new Alphabet(0, 0, text, true); - coolText.screenCenter(X); - coolText.y += (textGroup.length * 60) + 200 + offset; - credGroup.add(coolText); - textGroup.add(coolText); - } - } - - function deleteCoolText() - { - while (textGroup.members.length > 0) - { - credGroup.remove(textGroup.members[0], true); - textGroup.remove(textGroup.members[0], true); - } - } - - private var sickBeats:Int = 0; //Basically curBeat but won't be skipped if you hold the tab or resize the screen - public static var closedState:Bool = false; - override function beatHit() - { - super.beatHit(); - - FlxG.camera.zoom += 0.015; - - FlxTween.tween(FlxG.camera, {zoom: 1}, Conductor.crochet / 1200, {ease: FlxEase.quadOut}); - - if(logoBl != null) - logoBl.animation.play('bump', true); - - if(gfDance != null) { - danceLeft = !danceLeft; - if (danceLeft) - gfDance.animation.play('danceRight'); - else - gfDance.animation.play('danceLeft'); - } - - if(!closedState) { - sickBeats++; - switch (sickBeats) - { - case 1: - FlxG.sound.music.fadeIn(4, 0, 0.7); - case 2: - #if PSYCH_WATERMARKS - createCoolText(['JS Engine by'], 15); - #else - createCoolText(['ninjamuffin99', 'phantomArcade', 'kawaisprite', 'evilsk8er']); - #end - case 4: - #if PSYCH_WATERMARKS - addMoreText('Jordan Santiago', 15); - addMoreText('Moxie', 15); - #else - addMoreText('present'); - #end - case 5: - deleteCoolText(); - case 6: - #if PSYCH_WATERMARKS - createCoolText(['Not associated', 'with'], -40); - #else - createCoolText(['In association', 'with'], -40); - #end - case 8: - addMoreText('newgrounds', -40); - ngSpr.visible = true; - case 9: - deleteCoolText(); - ngSpr.visible = false; - case 10: - createCoolText([curWacky[0]]); - case 12: - addMoreText(curWacky[1]); - case 13: - deleteCoolText(); - case 14: - addMoreText('Friday'); - case 15: - addMoreText('Night'); - case 16: - addMoreText('Funkin'); - - case 17: - skipIntro(); - } - } - } - - var skippedIntro:Bool = false; - var increaseVolume:Bool = false; - function skipIntro():Void - { - if (!skippedIntro) - { - FlxTween.tween(logoBl, {y: titleJSON.endY}, 1.4, {ease: FlxEase.expoInOut}); - - logoBl.angle = -4; - - new FlxTimer().start(0.01, function(tmr:FlxTimer) - { - if (logoBl.angle == -4) - FlxTween.angle(logoBl, logoBl.angle, 4, 4, {ease: FlxEase.quartInOut}); - if (logoBl.angle == 4) - FlxTween.angle(logoBl, logoBl.angle, -4, 4, {ease: FlxEase.quartInOut}); - }, 0); - - remove(ngSpr); - remove(credGroup); - FlxG.camera.flash(FlxColor.WHITE, 4); - skippedIntro = true; - } - } + public static var muteKeys:Array = [FlxKey.ZERO]; + public static var volumeDownKeys:Array = [FlxKey.NUMPADMINUS, FlxKey.MINUS]; + public static var volumeUpKeys:Array = [FlxKey.NUMPADPLUS, FlxKey.PLUS]; + + public static var initialized:Bool = false; + + public var inCutscene:Bool = false; + + var canPause:Bool = true; + + var blackScreen:FlxSprite; + var credGroup:FlxGroup; + var credTextShit:Alphabet; + var textGroup:FlxGroup; + var ngSpr:FlxSprite; + + var titleTextColors:Array = [0xFF33FFFF, 0xFF3333CC]; + var titleTextAlphas:Array = [1, .64]; + + var curWacky:Array = []; + + var mustUpdate:Bool = false; + + public static var titleJSON:TitleData; + + public static var updateVersion:String = ''; + + override public function create():Void + { + Paths.clearStoredMemory(); + Paths.clearUnusedMemory(); + + MusicBeatState.windowNameSuffix = " - Title Screen"; + + MusicBeatState.windowNamePrefix = Assets.getText(Paths.txt("windowTitleBase", "preload")); + + curWacky = FlxG.random.getObject(getIntroTextShit()); + + // DEBUG BULLSHIT + + swagShader = new ColorSwap(); + super.create(); + + #if (CHECK_FOR_UPDATES) + if (ClientPrefs.checkForUpdates && !closedState && !Main.askedToUpdate) + { + trace('Checking for a update...'); + var http = new haxe.Http("https://raw.githubusercontent.com/JordanSantiagoYT/FNF-JS-Engine/main/THECHANGELOG.md"); + var returnedData:Array = []; + + http.onData = function(data:String) { + var versionEndIndex:Int = data.indexOf(';'); + returnedData[0] = data.substring(0, versionEndIndex); + + // Extract the changelog after the version number + returnedData[1] = data.substring(versionEndIndex + 1, data.length); + updateVersion = returnedData[0]; + final curVersion:String = MainMenuState.psychEngineJSVersion.trim(); + final cleanVersion:String = curVersion.split(" (")[0]; // Removes everything after " (" + trace(cleanVersion); + trace('Current version online: ' + updateVersion + ', your version: ' + cleanVersion); + if (updateVersion != cleanVersion && CoolUtil.isVersionNewer(updateVersion, cleanVersion)) + { + trace("Versions don't match!"); + OutdatedState.currChanges = returnedData[1]; + mustUpdate = true; + Main.askedToUpdate = true; + } + if (updateVersion == curVersion) + { + trace('The versions match!'); + } + else + trace('$updateVersion is less than $cleanVersion. Skipping update as it\'s likely a nightly version'); + } + + http.onError = function(error) { + trace("[ERROR] it's a error $error"); + } + + http.request(); + } + #end + + Highscore.load(); + + // IGNORE THIS!!! + titleJSON = Json.parse(Paths.getTextFromFile('images/gfDanceTitle.json')); + + if (!initialized) + { + if (FlxG.save.data != null && FlxG.save.data.fullscreen) + { + FlxG.fullscreen = FlxG.save.data.fullscreen; + } + persistentUpdate = true; + persistentDraw = true; + } + + if (FlxG.save.data.weekCompleted != null) + { + StoryMenuState.weekCompleted = FlxG.save.data.weekCompleted; + } + + FlxG.mouse.visible = false; + #if FREEPLAY + FlxG.switchState(FreeplayState.new); + #elseif CHARTING + FlxG.switchState(ChartingState.new); + #else + if (FlxG.save.data.flashing == null && !FlashingState.leftState) + { + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; + FlxG.switchState(FlashingState.new); + } + else + { + if (initialized) startIntro(); + else + { + new FlxTimer().start(1, function(tmr:FlxTimer) { + startIntro(); + }); + } + } + #end + } + + var logoBl:FlxSprite; + var gfDance:FlxSprite; + var danceLeft:Bool = false; + var titleText:FlxSprite; + var swagShader:ColorSwap = null; + + function startIntro() + { + if (!initialized) + { + Paths.playMenuMusic(true, 0); + } + + switch (ClientPrefs.daMenuMusic) // change this if you're making a source mod, like add your own or something + { + case 'Mashup' | 'VS Impostor' | 'VS Nonsense V2': + Conductor.changeBPM(102); + case 'Dave & Bambi': + Conductor.changeBPM(148); + case 'Dave & Bambi (Old)': + Conductor.changeBPM(150); + case 'DDTO+': + Conductor.changeBPM(120); + case 'Anniversary': + Conductor.changeBPM(115); + case 'Base Game' | 'Default' | 'None' | 'Christmas': // just in case you're not making a source mod & wanna change this + Conductor.changeBPM(titleJSON.bpm); + default: // fallback + Conductor.changeBPM(titleJSON.bpm); + } + persistentUpdate = true; + + var bg:FlxSprite = new FlxSprite(); + + if (titleJSON.backgroundSprite != null && titleJSON.backgroundSprite.length > 0 && titleJSON.backgroundSprite != "none") + { + bg.loadGraphic(Paths.image(titleJSON.backgroundSprite)); + } + else + { + bg.makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); + } + add(bg); + + logoBl = new FlxSprite(titleJSON.titlex, titleJSON.titley); + logoBl.frames = Paths.getSparrowAtlas('logoBumpin'); + + logoBl.antialiasing = ClientPrefs.globalAntialiasing; + logoBl.animation.addByPrefix('bump', 'logo bumpin', 24, false); + logoBl.animation.play('bump'); + logoBl.updateHitbox(); + + swagShader = new ColorSwap(); + gfDance = new FlxSprite(titleJSON.gfx, titleJSON.gfy); + + gfDance.frames = Paths.getSparrowAtlas('gfDanceTitle'); + gfDance.animation.addByIndices('danceLeft', 'gfDance', [30, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], "", 24, false); + gfDance.animation.addByIndices('danceRight', 'gfDance', [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "", 24, false); + gfDance.antialiasing = ClientPrefs.globalAntialiasing; + + add(gfDance); + gfDance.shader = swagShader.shader; + add(logoBl); + logoBl.shader = swagShader.shader; + + titleText = new FlxSprite(titleJSON.startx, titleJSON.starty); + #if (desktop && MODS_ALLOWED) + var path = "mods/" + Paths.currentModDirectory + "/images/titleEnter.png"; + // trace(path, FileSystem.exists(path)); + if (!FileSystem.exists(path)) + { + path = "mods/images/titleEnter.png"; + } + // trace(path, FileSystem.exists(path)); + if (!FileSystem.exists(path)) + { + path = "assets/images/titleEnter.png"; + } + // trace(path, FileSystem.exists(path)); + titleText.frames = FlxAtlasFrames.fromSparrow(BitmapData.fromFile(path), File.getContent(StringTools.replace(path, ".png", ".xml"))); + #else + titleText.frames = Paths.getSparrowAtlas('titleEnter'); + #end + var animFrames:Array = []; + @:privateAccess { + titleText.animation.findByPrefix(animFrames, "ENTER IDLE"); + titleText.animation.findByPrefix(animFrames, "ENTER FREEZE"); + } + + if (animFrames.length > 0) + { + newTitle = true; + + titleText.animation.addByPrefix('idle', "ENTER IDLE", 24); + titleText.animation.addByPrefix('press', ClientPrefs.flashing ? "ENTER PRESSED" : "ENTER FREEZE", 24); + } + else + { + newTitle = false; + + titleText.animation.addByPrefix('idle', "Press Enter to Begin", 24); + titleText.animation.addByPrefix('press', "ENTER PRESSED", 24); + } + + titleText.antialiasing = ClientPrefs.globalAntialiasing; + titleText.animation.play('idle'); + titleText.updateHitbox(); + add(titleText); + + credGroup = new FlxGroup(); + add(credGroup); + textGroup = new FlxGroup(); + + blackScreen = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.BLACK); + credGroup.add(blackScreen); + + credTextShit = new Alphabet(0, 0, "", true); + credTextShit.screenCenter(); + + credTextShit.visible = false; + + ngSpr = new FlxSprite(0, FlxG.height * 0.52).loadGraphic(Paths.image('newgrounds_logo')); + add(ngSpr); + ngSpr.visible = false; + ngSpr.setGraphicSize(Std.int(ngSpr.width * 0.8)); + ngSpr.updateHitbox(); + ngSpr.screenCenter(X); + ngSpr.antialiasing = ClientPrefs.globalAntialiasing; + + FlxTween.tween(credTextShit, {y: credTextShit.y + 20}, 2.9, {ease: FlxEase.quadInOut, type: PINGPONG}); + + if (initialized) skipIntro(); + else + initialized = true; + } + + function getIntroTextShit():Array> + { + var fullText:String = Assets.getText(Paths.txt('introText')); + + var firstArray:Array = fullText.split('\n'); + var swagGoodArray:Array> = []; + + for (i in firstArray) + { + swagGoodArray.push(i.split('--')); + } + + return swagGoodArray; + } + + var transitioning:Bool = false; + + var newTitle:Bool = false; + var titleTimer:Float = 0; + + override function update(elapsed:Float) + { + if (FlxG.sound.music != null) Conductor.songPosition = FlxG.sound.music.time; + + var pressedEnter:Bool = FlxG.keys.justPressed.ENTER || controls.ACCEPT; + + var gamepad:FlxGamepad = FlxG.gamepads.lastActive; + + if (gamepad != null) + { + if (gamepad.justPressed.START) pressedEnter = true; + } + + if (newTitle) + { + titleTimer += CoolUtil.boundTo(elapsed, 0, 1); + if (titleTimer > 2) titleTimer -= 2; + } + + if (initialized && !transitioning && skippedIntro) + { + if (newTitle && !pressedEnter) + { + var timer:Float = titleTimer; + if (timer >= 1) timer = (-timer) + 2; + + timer = FlxEase.quadInOut(timer); + + titleText.color = FlxColor.interpolate(titleTextColors[0], titleTextColors[1], timer); + titleText.alpha = FlxMath.lerp(titleTextAlphas[0], titleTextAlphas[1], timer); + } + + if (pressedEnter) + { + titleText.color = FlxColor.WHITE; + titleText.alpha = 1; + + if (titleText != null) titleText.animation.play('press'); + + FlxG.camera.flash(ClientPrefs.flashing ? FlxColor.WHITE : 0x4CFFFFFF, 1); + FlxG.sound.play(Paths.sound('confirmMenu'), 0.7); + + transitioning = true; + + new FlxTimer().start(1, function(tmr:FlxTimer) { + if (mustUpdate) + { + FlxG.switchState(OutdatedState.new); + } + else + { + FlxG.switchState(MainMenuState.new); + } + closedState = true; + }); + } + } + + if (initialized && pressedEnter && !skippedIntro) + { + skipIntro(); + } + + if (swagShader != null) + { + if (controls.UI_LEFT) swagShader.hue -= elapsed * 0.1; + if (controls.UI_RIGHT) swagShader.hue += elapsed * 0.1; + } + + super.update(elapsed); + } + + function createCoolText(textArray:Array, ?offset:Float = 0) + { + for (i in 0...textArray.length) + { + var money:Alphabet = new Alphabet(0, 0, textArray[i], true); + money.screenCenter(X); + money.y += (i * 60) + 200 + offset; + if (credGroup != null && textGroup != null) + { + credGroup.add(money); + textGroup.add(money); + } + } + } + + function addMoreText(text:String, ?offset:Float = 0) + { + if (textGroup != null && credGroup != null) + { + var coolText:Alphabet = new Alphabet(0, 0, text, true); + coolText.screenCenter(X); + coolText.y += (textGroup.length * 60) + 200 + offset; + credGroup.add(coolText); + textGroup.add(coolText); + } + } + + function deleteCoolText() + { + while (textGroup.members.length > 0) + { + credGroup.remove(textGroup.members[0], true); + textGroup.remove(textGroup.members[0], true); + } + } + + private var sickBeats:Int = 0; // Basically curBeat but won't be skipped if you hold the tab or resize the screen + + public static var closedState:Bool = false; + + override function beatHit() + { + super.beatHit(); + + FlxG.camera.zoom += 0.015; + + FlxTween.tween(FlxG.camera, {zoom: 1}, Conductor.crochet / 1200, {ease: FlxEase.quadOut}); + + if (logoBl != null) logoBl.animation.play('bump', true); + + if (gfDance != null) + { + danceLeft = !danceLeft; + if (danceLeft) gfDance.animation.play('danceRight'); + else + gfDance.animation.play('danceLeft'); + } + + if (!closedState) + { + sickBeats++; + switch (sickBeats) + { + case 1: + FlxG.sound.music.fadeIn(4, 0, 0.7); + case 2: + #if PSYCH_WATERMARKS + createCoolText(['JS Engine by'], 15); + #else + createCoolText(['ninjamuffin99', 'phantomArcade', 'kawaisprite', 'evilsk8er']); + #end + case 4: + #if PSYCH_WATERMARKS + addMoreText('Jordan Santiago', 15); + addMoreText('Moxie', 15); + #else + addMoreText('present'); + #end + case 5: + deleteCoolText(); + case 6: + #if PSYCH_WATERMARKS + createCoolText(['Not associated', 'with'], -40); + #else + createCoolText(['In association', 'with'], -40); + #end + case 8: + addMoreText('newgrounds', -40); + ngSpr.visible = true; + case 9: + deleteCoolText(); + ngSpr.visible = false; + case 10: + createCoolText([curWacky[0]]); + case 12: + addMoreText(curWacky[1]); + case 13: + deleteCoolText(); + case 14: + addMoreText('Friday'); + case 15: + addMoreText('Night'); + case 16: + addMoreText('Funkin'); + + case 17: + skipIntro(); + } + } + } + + var skippedIntro:Bool = false; + var increaseVolume:Bool = false; + + function skipIntro():Void + { + if (!skippedIntro) + { + FlxTween.tween(logoBl, {y: titleJSON.endY}, 1.4, {ease: FlxEase.expoInOut}); + + logoBl.angle = -4; + + new FlxTimer().start(0.01, function(tmr:FlxTimer) { + if (logoBl.angle == -4) FlxTween.angle(logoBl, logoBl.angle, 4, 4, {ease: FlxEase.quartInOut}); + if (logoBl.angle == 4) FlxTween.angle(logoBl, logoBl.angle, -4, 4, {ease: FlxEase.quartInOut}); + }, 0); + + remove(ngSpr); + remove(credGroup); + FlxG.camera.flash(FlxColor.WHITE, 4); + skippedIntro = true; + } + } } diff --git a/source/VideoSprite.hx b/source/VideoSprite.hx index 6b2fc994855..5623df868ee 100644 --- a/source/VideoSprite.hx +++ b/source/VideoSprite.hx @@ -5,201 +5,204 @@ import flixel.addons.display.FlxPieDial; import hxvlc.flixel.FlxVideoSprite; #end -class VideoSprite extends FlxSpriteGroup { - #if VIDEOS_ALLOWED - public var finishCallback:Void->Void = null; - public var onSkip:Void->Void = null; - - final _timeToSkip:Float = 1; - public var holdingTime:Float = 0; - public var videoSprite:FunkinVideoSprite; - public var skipSprite:FlxPieDial; - public var cover:FlxSprite; - public var canSkip(default, set):Bool = false; - - private var videoName:String; - // private var autoPause:Bool = true; - - public var waiting:Bool = false; - - public function new(videoName:String, isWaiting:Bool, canSkip:Bool = false, shouldLoop:Dynamic = false, autoPause = true) { - super(); - - this.videoName = videoName; - scrollFactor.set(); - cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; - - waiting = isWaiting; - if(!waiting) - { - cover = new FlxSprite().makeGraphic(1, 1, FlxColor.BLACK); - cover.scale.set(FlxG.width + 100, FlxG.height + 100); - cover.screenCenter(); - cover.scrollFactor.set(); - add(cover); - } - - // initialize sprites - videoSprite = new FunkinVideoSprite(); - videoSprite.antialiasing = ClientPrefs.globalAntialiasing; - videoSprite.autoPause = autoPause; - add(videoSprite); - if(canSkip) this.canSkip = true; - - // callbacks - if(!shouldLoop) videoSprite.bitmap.onEndReached.add(destroy); - - videoSprite.bitmap.onFormatSetup.add(function() - { - /* - #if hxvlc - var wd:Int = videoSprite.bitmap.formatWidth; - var hg:Int = videoSprite.bitmap.formatHeight; - trace('Video Resolution: ${wd}x${hg}'); - videoSprite.scale.set(FlxG.width / wd, FlxG.height / hg); - #end - */ - videoSprite.setGraphicSize(FlxG.width); - videoSprite.updateHitbox(); - videoSprite.screenCenter(); - }); - - // start video and adjust resolution to screen size - videoSprite.load(videoName, shouldLoop ? ['input-repeat=65545'] : null); - } - - var alreadyDestroyed:Bool = false; - override function destroy() - { - if(alreadyDestroyed) - return; - - trace('Video destroyed'); - if(cover != null) - { - remove(cover); - cover.destroy(); - } - - if(finishCallback != null) - finishCallback(); - onSkip = null; - - if(FlxG.state != null) - { - if(FlxG.state.members.contains(this)) - FlxG.state.remove(this); - - if(FlxG.state.subState != null && FlxG.state.subState.members.contains(this)) - FlxG.state.subState.remove(this); - } - super.destroy(); - alreadyDestroyed = true; - } - - override function update(elapsed:Float) - { - if(canSkip) - { - if(Controls.instance != null && Controls.instance.ACCEPT_P) - { - holdingTime = Math.max(0, Math.min(_timeToSkip, holdingTime + elapsed)); - } - else if (holdingTime > 0) - { - holdingTime = Math.max(0, FlxMath.lerp(holdingTime, -0.1, FlxMath.bound(elapsed * 3, 0, 1))); - } - updateSkipAlpha(); - - if(holdingTime >= _timeToSkip) - { - if(onSkip != null) onSkip(); - finishCallback = null; - videoSprite.bitmap.onEndReached.dispatch(); - trace('Skipped video'); - return; - } - } - super.update(elapsed); - } - - function set_canSkip(newValue:Bool) - { - canSkip = newValue; - if(canSkip) - { - if(skipSprite == null) - { - skipSprite = new FlxPieDial(0, 0, 40, FlxColor.WHITE, 40, true, 24); - skipSprite.replaceColor(FlxColor.BLACK, FlxColor.TRANSPARENT); - skipSprite.x = FlxG.width - (skipSprite.width + 80); - skipSprite.y = FlxG.height - (skipSprite.height + 72); - skipSprite.amount = 0; - add(skipSprite); - } - } - else if(skipSprite != null) - { - remove(skipSprite); - skipSprite.destroy(); - skipSprite = null; - } - return canSkip; - } - - function updateSkipAlpha() - { - if(skipSprite == null) return; - - skipSprite.amount = Math.min(1, Math.max(0, (holdingTime / _timeToSkip) * 1.025)); - skipSprite.alpha = FlxMath.remapToRange(skipSprite.amount, 0.025, 1, 0, 1); - } - - public function play() videoSprite?.play(); - public function resume() videoSprite?.resume(); - public function pause() videoSprite?.pause(); - #end +class VideoSprite extends FlxSpriteGroup +{ + #if VIDEOS_ALLOWED + public var finishCallback:Void->Void = null; + public var onSkip:Void->Void = null; + + final _timeToSkip:Float = 1; + + public var holdingTime:Float = 0; + public var videoSprite:FunkinVideoSprite; + public var skipSprite:FlxPieDial; + public var cover:FlxSprite; + public var canSkip(default, set):Bool = false; + + private var videoName:String; + + // private var autoPause:Bool = true; + public var waiting:Bool = false; + + public function new(videoName:String, isWaiting:Bool, canSkip:Bool = false, shouldLoop:Dynamic = false, autoPause = true) + { + super(); + + this.videoName = videoName; + scrollFactor.set(); + cameras = [FlxG.cameras.list[FlxG.cameras.list.length - 1]]; + + waiting = isWaiting; + if (!waiting) + { + cover = new FlxSprite().makeGraphic(1, 1, FlxColor.BLACK); + cover.scale.set(FlxG.width + 100, FlxG.height + 100); + cover.screenCenter(); + cover.scrollFactor.set(); + add(cover); + } + + // initialize sprites + videoSprite = new FunkinVideoSprite(); + videoSprite.antialiasing = ClientPrefs.globalAntialiasing; + videoSprite.autoPause = autoPause; + add(videoSprite); + if (canSkip) this.canSkip = true; + + // callbacks + if (!shouldLoop) videoSprite.bitmap.onEndReached.add(destroy); + + videoSprite.bitmap.onFormatSetup.add(function() { + /* + #if hxvlc + var wd:Int = videoSprite.bitmap.formatWidth; + var hg:Int = videoSprite.bitmap.formatHeight; + trace('Video Resolution: ${wd}x${hg}'); + videoSprite.scale.set(FlxG.width / wd, FlxG.height / hg); + #end + */ + videoSprite.setGraphicSize(FlxG.width); + videoSprite.updateHitbox(); + videoSprite.screenCenter(); + }); + + // start video and adjust resolution to screen size + videoSprite.load(videoName, shouldLoop ? ['input-repeat=65545'] : null); + } + + var alreadyDestroyed:Bool = false; + + override function destroy() + { + if (alreadyDestroyed) return; + + trace('Video destroyed'); + if (cover != null) + { + remove(cover); + cover.destroy(); + } + + if (finishCallback != null) finishCallback(); + onSkip = null; + + if (FlxG.state != null) + { + if (FlxG.state.members.contains(this)) FlxG.state.remove(this); + + if (FlxG.state.subState != null && FlxG.state.subState.members.contains(this)) FlxG.state.subState.remove(this); + } + super.destroy(); + alreadyDestroyed = true; + } + + override function update(elapsed:Float) + { + if (canSkip) + { + if (Controls.instance != null && Controls.instance.ACCEPT_P) + { + holdingTime = Math.max(0, Math.min(_timeToSkip, holdingTime + elapsed)); + } + else if (holdingTime > 0) + { + holdingTime = Math.max(0, FlxMath.lerp(holdingTime, -0.1, FlxMath.bound(elapsed * 3, 0, 1))); + } + updateSkipAlpha(); + + if (holdingTime >= _timeToSkip) + { + if (onSkip != null) onSkip(); + finishCallback = null; + videoSprite.bitmap.onEndReached.dispatch(); + trace('Skipped video'); + return; + } + } + super.update(elapsed); + } + + function set_canSkip(newValue:Bool) + { + canSkip = newValue; + if (canSkip) + { + if (skipSprite == null) + { + skipSprite = new FlxPieDial(0, 0, 40, FlxColor.WHITE, 40, true, 24); + skipSprite.replaceColor(FlxColor.BLACK, FlxColor.TRANSPARENT); + skipSprite.x = FlxG.width - (skipSprite.width + 80); + skipSprite.y = FlxG.height - (skipSprite.height + 72); + skipSprite.amount = 0; + add(skipSprite); + } + } + else if (skipSprite != null) + { + remove(skipSprite); + skipSprite.destroy(); + skipSprite = null; + } + return canSkip; + } + + function updateSkipAlpha() + { + if (skipSprite == null) return; + + skipSprite.amount = Math.min(1, Math.max(0, (holdingTime / _timeToSkip) * 1.025)); + skipSprite.alpha = FlxMath.remapToRange(skipSprite.amount, 0.025, 1, 0, 1); + } + + public function play() + videoSprite?.play(); + + public function resume() + videoSprite?.resume(); + + public function pause() + videoSprite?.pause(); + #end } @:nullSafety class FunkinVideoSprite extends FlxVideoSprite { - public var autoPause:Bool = true; // literally to just fix one measily little issue - /* - @:noCompletion - override private function onFocusGained():Void - { - #if !mobile - if (!FlxG.autoPause) - return; - #end - - if (resumeOnFocus) - { - resumeOnFocus = false; - - resume(); - } - super.onFocusGained(); - } - */ - - @:noCompletion - override function onFocusLost():Void - { - #if !mobile - if (!FlxG.autoPause) - return; - #end - - if (autoPause) - { - resumeOnFocus = bitmap.isPlaying; - pause(); - } - else - resumeOnFocus = false; - - super.onFocusLost(); - } + public var autoPause:Bool = true; // literally to just fix one measily little issue + + /* + @:noCompletion + override private function onFocusGained():Void + { + #if !mobile + if (!FlxG.autoPause) + return; + #end + + if (resumeOnFocus) + { + resumeOnFocus = false; + + resume(); + } + super.onFocusGained(); + } + */ + @:noCompletion + override function onFocusLost():Void + { + #if !mobile + if (!FlxG.autoPause) return; + #end + + if (autoPause) + { + resumeOnFocus = bitmap.isPlaying; + pause(); + } + else + resumeOnFocus = false; + + super.onFocusLost(); + } } diff --git a/source/WeekData.hx b/source/WeekData.hx index c8497f6ffb1..6841c9b5a5c 100644 --- a/source/WeekData.hx +++ b/source/WeekData.hx @@ -4,279 +4,306 @@ import tjson.TJSON as Json; typedef WeekFile = { - // JSON variables - var songs:Array; - var weekCharacters:Array; - var weekBackground:String; - var weekBefore:String; - var storyName:String; - var weekName:String; - var freeplayColor:Array; - var startUnlocked:Bool; - var ?hiddenUntilUnlocked:Null; - var hideStoryMode:Bool; - var hideFreeplay:Bool; - var ?difficulties:String; + // JSON variables + var songs:Array; + var weekCharacters:Array; + var weekBackground:String; + var weekBefore:String; + var storyName:String; + var weekName:String; + var freeplayColor:Array; + var startUnlocked:Bool; + var ?hiddenUntilUnlocked:Null; + var hideStoryMode:Bool; + var hideFreeplay:Bool; + var ?difficulties:String; } -class WeekData { - public static var weeksLoaded:Map = new Map(); - public static var weeksList:Array = []; - public var folder:String = ''; - - // JSON variables - public var songs:Array; - public var weekCharacters:Array; - public var weekBackground:String; - public var weekBefore:String; - public var storyName:String; - public var weekName:String; - public var freeplayColor:Array; - public var startUnlocked:Bool; - public var hiddenUntilUnlocked:Null; - public var hideStoryMode:Bool; - public var hideFreeplay:Bool; - public var difficulties:String; - - public var fileName:String; - - public static function createWeekFile():WeekFile { - var weekFile:WeekFile = { - songs: [["Bopeebo", "dad", [146, 113, 253]], ["Fresh", "dad", [146, 113, 253]], ["Dad Battle", "dad", [146, 113, 253]]], - weekCharacters: ['dad', 'bf', 'gf'], - weekBackground: 'stage', - weekBefore: 'tutorial', - storyName: 'Your New Week', - weekName: 'Custom Week', - freeplayColor: [146, 113, 253], - startUnlocked: true, - hiddenUntilUnlocked: false, - hideStoryMode: false, - hideFreeplay: false, - difficulties: '' - }; - return weekFile; - } - - public function new(weekFile:WeekFile, fileName:String) { - var template = createWeekFile(); - for (i in Reflect.fields(weekFile)) { - if (Reflect.hasField(template, i)) { //just doing Reflect.hasField on itself doesnt work for some reason so we are doing it on a template - Reflect.setProperty(this, i, Reflect.field(weekFile, i)); - } - } - - if (hiddenUntilUnlocked == null) { - hiddenUntilUnlocked = false; - } - - this.fileName = fileName; - } - - public static function reloadWeekFiles(isStoryMode:Null = false) - { - weeksList = []; - weeksLoaded.clear(); - #if MODS_ALLOWED - var disabledMods:Array = []; - - var modsListPath:String = 'modsList.txt'; - var directories:Array = [Paths.mods(), Paths.getPreloadPath()]; - - var originalLength:Int = directories.length; - if(FileSystem.exists(modsListPath)) - { - var stuff:Array = CoolUtil.coolTextFile(modsListPath); - for (i in 0...stuff.length) - { - var splitName:Array = stuff[i].trim().split('|'); - if(splitName[1] == '0') // Disable mod - { - disabledMods.push(splitName[0]); - } - else // Sort mod loading order based on modsList.txt file - { - var path = haxe.io.Path.join([Paths.mods(), splitName[0]]); - //trace('trying to push: ' + splitName[0]); - if (sys.FileSystem.isDirectory(path) && !Paths.ignoreModFolders.contains(splitName[0]) && !disabledMods.contains(splitName[0]) && !directories.contains(path + '/')) - { - directories.push(path + '/'); - //trace('pushed Directory: ' + splitName[0]); - } - } - } - } - - var modsDirectories:Array = Paths.getModDirectories(); - for (folder in modsDirectories) - { - var pathThing:String = haxe.io.Path.join([Paths.mods(), folder]) + '/'; - if (!disabledMods.contains(folder) && !directories.contains(pathThing)) - { - directories.push(pathThing); - //trace('pushed Directory: ' + folder); - } - } - #else - var directories:Array = [Paths.getPreloadPath()]; - var originalLength:Int = directories.length; - #end - - var sexList:Array = CoolUtil.coolTextFile(Paths.getPreloadPath('weeks/weekList.txt')); - for (i in 0...sexList.length) { - for (j in 0...directories.length) { - var fileToCheck:String = directories[j] + 'weeks/' + sexList[i] + '.json'; - if(!weeksLoaded.exists(sexList[i])) { - var week:WeekFile = getWeekFile(fileToCheck); - if(week != null) { - var weekFile:WeekData = new WeekData(week, sexList[i]); - - #if MODS_ALLOWED - if(j >= originalLength) { - weekFile.folder = directories[j].substring(Paths.mods().length, directories[j].length-1); - } - #end - - if(weekFile != null && (isStoryMode == null || (isStoryMode && !weekFile.hideStoryMode) || (!isStoryMode && !weekFile.hideFreeplay))) { - weeksLoaded.set(sexList[i], weekFile); - weeksList.push(sexList[i]); - } - } - } - } - } - - #if MODS_ALLOWED - for (i in 0...directories.length) { - var directory:String = directories[i] + 'weeks/'; - if(FileSystem.exists(directory)) { - var listOfWeeks:Array = CoolUtil.coolTextFile(directory + 'weekList.txt'); - for (daWeek in listOfWeeks) - { - var path:String = directory + daWeek + '.json'; - if(sys.FileSystem.exists(path)) - { - addWeek(daWeek, path, directories[i], i, originalLength); - } - } - - for (file in FileSystem.readDirectory(directory)) - { - var path = haxe.io.Path.join([directory, file]); - if (!sys.FileSystem.isDirectory(path) && file.endsWith('.json')) - { - addWeek(file.substr(0, file.length - 5), path, directories[i], i, originalLength); - } - } - } - } - #end - } - - private static function isValidWeekJson(data:Dynamic):Bool - { - if (data == null) return false; - - final requiredFields = ["songs", "weekCharacters", "weekName"]; - - for (field in requiredFields) - { - if (!Reflect.hasField(data, field)) - return false; - } - - final songs = Reflect.field(data, "songs"); - final chars = Reflect.field(data, "weekCharacters"); - if (songs == null || chars == null || songs.length <= 0 || chars.length <= 0) - return false; - - return true; - } - - private static function addWeek(weekToCheck:String, path:String, directory:String, i:Int, originalLength:Int) - { - if(!weeksLoaded.exists(weekToCheck)) - { - var week:WeekFile = getWeekFile(path); - if(week != null) - { - var weekFile:WeekData = new WeekData(week, weekToCheck); - if(i >= originalLength) - { - #if MODS_ALLOWED - weekFile.folder = directory.substring(Paths.mods().length, directory.length-1); - #end - } - if((PlayState.isStoryMode && !weekFile.hideStoryMode) || (!PlayState.isStoryMode && !weekFile.hideFreeplay)) - { - weeksLoaded.set(weekToCheck, weekFile); - weeksList.push(weekToCheck); - } - } - } - } - - private static function getWeekFile(path:String):WeekFile { - var rawJson:String = null; - #if MODS_ALLOWED - if(FileSystem.exists(path)) { - rawJson = File.getContent(path); - } - #else - if(OpenFlAssets.exists(path)) { - rawJson = Assets.getText(path); - } - #end - - if (rawJson != null && rawJson.length > 0) - { - var parsed:Dynamic = haxe.Json.parse(rawJson); - if (isValidWeekJson(parsed)) - return cast parsed; - else - return null; // Skip invalid week jsons - } - return null; - } - - // FUNCTIONS YOU WILL PROBABLY NEVER NEED TO USE - - //To use on PlayState.hx or Highscore stuff - public static function getWeekFileName():String { - return weeksList[PlayState.storyWeek]; - } - - //Used on LoadingState, nothing really too relevant - public static function getCurrentWeek():WeekData { - return weeksLoaded.get(weeksList[PlayState.storyWeek]); - } - - public static function setDirectoryFromWeek(?data:WeekData = null) { - Paths.currentModDirectory = ''; - if(data != null && data.folder != null && data.folder.length > 0) { - Paths.currentModDirectory = data.folder; - } - } - - public static function loadTheFirstEnabledMod() - { - Paths.currentModDirectory = ''; - - #if (MODS_ALLOWED) - if (FileSystem.exists("modsList.txt")) - { - var list:Array = CoolUtil.listFromString(File.getContent("modsList.txt")); - var foundTheTop = false; - for (i in list) - { - var dat = i.split("|"); - if (dat[1] == "1" && !foundTheTop) - { - foundTheTop = true; - Paths.currentModDirectory = dat[0]; - } - } - } - #end - } +class WeekData +{ + public static var weeksLoaded:Map = new Map(); + public static var weeksList:Array = []; + + public var folder:String = ''; + + // JSON variables + public var songs:Array; + public var weekCharacters:Array; + public var weekBackground:String; + public var weekBefore:String; + public var storyName:String; + public var weekName:String; + public var freeplayColor:Array; + public var startUnlocked:Bool; + public var hiddenUntilUnlocked:Null; + public var hideStoryMode:Bool; + public var hideFreeplay:Bool; + public var difficulties:String; + + public var fileName:String; + + public static function createWeekFile():WeekFile + { + var weekFile:WeekFile = + { + songs: [ + ["Bopeebo", "dad", [146, 113, 253]], + ["Fresh", "dad", [146, 113, 253]], + ["Dad Battle", "dad", [146, 113, 253]] + ], + weekCharacters: ['dad', 'bf', 'gf'], + weekBackground: 'stage', + weekBefore: 'tutorial', + storyName: 'Your New Week', + weekName: 'Custom Week', + freeplayColor: [146, 113, 253], + startUnlocked: true, + hiddenUntilUnlocked: false, + hideStoryMode: false, + hideFreeplay: false, + difficulties: '' + }; + return weekFile; + } + + public function new(weekFile:WeekFile, fileName:String) + { + var template = createWeekFile(); + for (i in Reflect.fields(weekFile)) + { + if (Reflect.hasField(template, i)) + { // just doing Reflect.hasField on itself doesnt work for some reason so we are doing it on a template + Reflect.setProperty(this, i, Reflect.field(weekFile, i)); + } + } + + if (hiddenUntilUnlocked == null) + { + hiddenUntilUnlocked = false; + } + + this.fileName = fileName; + } + + public static function reloadWeekFiles(isStoryMode:Null = false) + { + weeksList = []; + weeksLoaded.clear(); + #if MODS_ALLOWED + var disabledMods:Array = []; + + var modsListPath:String = 'modsList.txt'; + var directories:Array = [Paths.mods(), Paths.getPreloadPath()]; + + var originalLength:Int = directories.length; + if (FileSystem.exists(modsListPath)) + { + var stuff:Array = CoolUtil.coolTextFile(modsListPath); + for (i in 0...stuff.length) + { + var splitName:Array = stuff[i].trim().split('|'); + if (splitName[1] == '0') // Disable mod + { + disabledMods.push(splitName[0]); + } + else // Sort mod loading order based on modsList.txt file + { + var path = haxe.io.Path.join([Paths.mods(), splitName[0]]); + // trace('trying to push: ' + splitName[0]); + if (sys.FileSystem.isDirectory(path) + && !Paths.ignoreModFolders.contains(splitName[0]) + && !disabledMods.contains(splitName[0]) + && !directories.contains(path + '/')) + { + directories.push(path + '/'); + // trace('pushed Directory: ' + splitName[0]); + } + } + } + } + + var modsDirectories:Array = Paths.getModDirectories(); + for (folder in modsDirectories) + { + var pathThing:String = haxe.io.Path.join([Paths.mods(), folder]) + '/'; + if (!disabledMods.contains(folder) && !directories.contains(pathThing)) + { + directories.push(pathThing); + // trace('pushed Directory: ' + folder); + } + } + #else + var directories:Array = [Paths.getPreloadPath()]; + var originalLength:Int = directories.length; + #end + + var sexList:Array = CoolUtil.coolTextFile(Paths.getPreloadPath('weeks/weekList.txt')); + for (i in 0...sexList.length) + { + for (j in 0...directories.length) + { + var fileToCheck:String = directories[j] + 'weeks/' + sexList[i] + '.json'; + if (!weeksLoaded.exists(sexList[i])) + { + var week:WeekFile = getWeekFile(fileToCheck); + if (week != null) + { + var weekFile:WeekData = new WeekData(week, sexList[i]); + + #if MODS_ALLOWED + if (j >= originalLength) + { + weekFile.folder = directories[j].substring(Paths.mods().length, directories[j].length - 1); + } + #end + + if (weekFile != null + && (isStoryMode == null || (isStoryMode && !weekFile.hideStoryMode) || (!isStoryMode && !weekFile.hideFreeplay))) + { + weeksLoaded.set(sexList[i], weekFile); + weeksList.push(sexList[i]); + } + } + } + } + } + + #if MODS_ALLOWED + for (i in 0...directories.length) + { + var directory:String = directories[i] + 'weeks/'; + if (FileSystem.exists(directory)) + { + var listOfWeeks:Array = CoolUtil.coolTextFile(directory + 'weekList.txt'); + for (daWeek in listOfWeeks) + { + var path:String = directory + daWeek + '.json'; + if (sys.FileSystem.exists(path)) + { + addWeek(daWeek, path, directories[i], i, originalLength); + } + } + + for (file in FileSystem.readDirectory(directory)) + { + var path = haxe.io.Path.join([directory, file]); + if (!sys.FileSystem.isDirectory(path) && file.endsWith('.json')) + { + addWeek(file.substr(0, file.length - 5), path, directories[i], i, originalLength); + } + } + } + } + #end + } + + private static function isValidWeekJson(data:Dynamic):Bool + { + if (data == null) return false; + + final requiredFields = ["songs", "weekCharacters", "weekName"]; + + for (field in requiredFields) + { + if (!Reflect.hasField(data, field)) return false; + } + + final songs = Reflect.field(data, "songs"); + final chars = Reflect.field(data, "weekCharacters"); + if (songs == null || chars == null || songs.length <= 0 || chars.length <= 0) return false; + + return true; + } + + private static function addWeek(weekToCheck:String, path:String, directory:String, i:Int, originalLength:Int) + { + if (!weeksLoaded.exists(weekToCheck)) + { + var week:WeekFile = getWeekFile(path); + if (week != null) + { + var weekFile:WeekData = new WeekData(week, weekToCheck); + if (i >= originalLength) + { + #if MODS_ALLOWED + weekFile.folder = directory.substring(Paths.mods().length, directory.length - 1); + #end + } + if ((PlayState.isStoryMode && !weekFile.hideStoryMode) || (!PlayState.isStoryMode && !weekFile.hideFreeplay)) + { + weeksLoaded.set(weekToCheck, weekFile); + weeksList.push(weekToCheck); + } + } + } + } + + private static function getWeekFile(path:String):WeekFile + { + var rawJson:String = null; + #if MODS_ALLOWED + if (FileSystem.exists(path)) + { + rawJson = File.getContent(path); + } + #else + if (OpenFlAssets.exists(path)) + { + rawJson = Assets.getText(path); + } + #end + + if (rawJson != null && rawJson.length > 0) + { + var parsed:Dynamic = haxe.Json.parse(rawJson); + if (isValidWeekJson(parsed)) return cast parsed; + else + return null; // Skip invalid week jsons + } + return null; + } + + // FUNCTIONS YOU WILL PROBABLY NEVER NEED TO USE + // To use on PlayState.hx or Highscore stuff + public static function getWeekFileName():String + { + return weeksList[PlayState.storyWeek]; + } + + // Used on LoadingState, nothing really too relevant + public static function getCurrentWeek():WeekData + { + return weeksLoaded.get(weeksList[PlayState.storyWeek]); + } + + public static function setDirectoryFromWeek(?data:WeekData = null) + { + Paths.currentModDirectory = ''; + if (data != null && data.folder != null && data.folder.length > 0) + { + Paths.currentModDirectory = data.folder; + } + } + + public static function loadTheFirstEnabledMod() + { + Paths.currentModDirectory = ''; + + #if (MODS_ALLOWED) + if (FileSystem.exists("modsList.txt")) + { + var list:Array = CoolUtil.listFromString(File.getContent("modsList.txt")); + var foundTheTop = false; + for (i in list) + { + var dat = i.split("|"); + if (dat[1] == "1" && !foundTheTop) + { + foundTheTop = true; + Paths.currentModDirectory = dat[0]; + } + } + } + #end + } } diff --git a/source/psychlua/CallbackHandler.hx b/source/psychlua/CallbackHandler.hx index 82df5876d8d..c5f438e2295 100644 --- a/source/psychlua/CallbackHandler.hx +++ b/source/psychlua/CallbackHandler.hx @@ -1,54 +1,42 @@ +#if LUA_ALLOWED package psychlua; -class CallbackHandler -{ - public static inline function call(l:State, fname:String):Int - { - try - { - //trace('calling $fname'); +class CallbackHandler { + public static inline function call(l:State, fname:String):Int { + try { var cbf:Dynamic = Lua_helper.callbacks.get(fname); - //Local functions have the lowest priority - //This is to prevent a "for" loop being called in every single operation, - //so that it only loops on reserved/special functions - if(cbf == null) - { - //trace('looping thru scripts'); - for (script in PlayState.instance.luaArray) - if(script != null && script.lua == l) - { - //trace('found script'); - cbf = script.callbacks.get(fname); - break; + if (cbf == null) { + if (PlayState.instance != null && PlayState.instance.luaArray != null) { + for (script in PlayState.instance.luaArray) { + if (script != null && script.lua == l) { + cbf = script.callbacks.get(fname); + break; + } } + } } - - if(cbf == null) return 0; - var nparams:Int = Lua.gettop(l); - var args:Array = []; + if (cbf == null) return 0; - for (i in 0...nparams) { - args[i] = Convert.fromLua(l, i + 1); - } - - var ret:Dynamic = null; - /* return the number of results */ + var args:Array = [for (i in 0 ... Lua.gettop(l)) Convert.fromLua(l, i + 1)]; - ret = Reflect.callMethod(null,cbf,args); + var ret:Dynamic = Reflect.callMethod(null, cbf, args); - if(ret != null){ + if (ret != null) { Convert.toLua(l, ret); return 1; } } - catch(e:Dynamic) - { - if(Lua_helper.sendErrorsToLua) {LuaL.error(l, 'CALLBACK ERROR! ${if(e.message != null) e.message else e}');return 0;} + catch (e:Dynamic) { + if (Lua_helper.sendErrorsToLua) { + LuaL.error(l, 'CALLBACK ERROR! ${if (Reflect.hasField(e, "message")) e.message else e}'); + return 0; + } trace(e); - throw(e); + throw e; } return 0; } -} \ No newline at end of file +} +#end diff --git a/source/shaders/VFDOverlay.hx b/source/shaders/VFDOverlay.hx new file mode 100644 index 00000000000..ef8d54f599a --- /dev/null +++ b/source/shaders/VFDOverlay.hx @@ -0,0 +1,68 @@ +package shaders; + +import openfl.display.GraphicsShader; + +class VFDOverlay extends GraphicsShader +{ + public var elapsedTime(default, set):Float = 0; + + function set_elapsedTime(value:Float):Float + { + u_time.value = [value]; + return value; + } + + @:glFragmentSource('#pragma header + const vec2 s = vec2(1, 1.7320508); + + uniform float u_time; + + float rand(float co) { return fract(sin(co*(91.3458)) * 47453.5453); } + float rand(vec2 co){ return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); } + + void main(void) { + vec4 col = texture2D (bitmap, openfl_TextureCoordv); + vec2 game_res = vec2(1280.0, 720.0); + const float tileAmount = 10.; + + vec2 uv = (2. * openfl_TextureCoordv.xy * -1.); + uv *= 50.; + + vec4 hexCenter = floor(vec4(uv, uv - vec2(0.5, 1.0)) / s.xyxy) + 0.5; + vec4 offset = vec4(uv - hexCenter.xy * s, uv - (hexCenter.zw + 0.5) * s) + 0.0; + vec4 hexInfo = dot(offset.xy, offset.xy) < dot(offset.zw, offset.zw) ? vec4(offset.xy, hexCenter.xy) : vec4(offset.zw, hexCenter.zw); + + // Distance to the nearest edge of a hexagon + vec2 p = abs(hexInfo.xy) ; + float edgeDist = max(dot(p, normalize(vec2(1.0, sqrt(3.0)))), p.x); + float edgeWidth = 0.05 * tileAmount; // Adjust edge width based on tile amount + float edgeSharpness = 0.011 * tileAmount; + + float outline = smoothstep(edgeWidth - edgeSharpness, edgeWidth, edgeDist); + float color_mix = mix(0.0, 0.3, outline); // Mix black outline with white fill + + float flicker = (sin(u_time) * 0.05) + 1.0; + float sinshit = smoothstep(-3.0, 1.0, sin(uv.y * 3.)); + + col = vec4(vec3(0.0), color_mix); + col = mix(col, vec4(0., 0., 0., sinshit), 0.5 * flicker); + + float specs = rand(uv.xy); + vec4 noise = vec4(0., 0., 0., specs); + col = mix(col, noise, 0.1); + + gl_FragColor = col; + } + ') + public function new() + { + super(); + + this.elapsedTime = 0; + } + + public function update(elapsed:Float):Void + { + this.elapsedTime += elapsed; + } +}