diff --git a/.github/workflows/build-shit.yml b/.github/workflows/build-shit.yml index 49bab1ac1..e217d1f18 100644 --- a/.github/workflows/build-shit.yml +++ b/.github/workflows/build-shit.yml @@ -13,8 +13,9 @@ jobs: apt update apt install -y sudo git curl unzip - name: Fix git config on posix runner + # this can't be {{ github.workspace }} because that's not docker-aware run: | - git config --global --add safe.directory ${{ github.workspace }} + git config --global --add safe.directory $GITHUB_WORKSPACE - name: Get checkout token uses: actions/create-github-app-token@v1 id: app_token @@ -90,8 +91,9 @@ jobs: runs-on: [self-hosted, macos] steps: - name: Fix git config on posix runner + # this can't be {{ github.workspace }} because that's not docker-aware run: | - git config --global --add safe.directory ${{ github.workspace }} + git config --global --add safe.directory $GITHUB_WORKSPACE - name: Get checkout token uses: actions/create-github-app-token@v1 id: app_token diff --git a/.prettierignore b/.prettierignore index 657ff3812..2e132c06f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,7 @@ export # Ignore all JSONS in the images folder (including FlxAnimate JSONs) assets/preload/images assets/shared/images +assets/weekend1/images # Don't ignore data files # TODO: These don't work. diff --git a/checkstyle.json b/checkstyle.json index d41a6d617..5300d94ad 100644 --- a/checkstyle.json +++ b/checkstyle.json @@ -79,7 +79,7 @@ { "props": { "ignoreExtern": true, - "format": "^[A-Z][A-Z0-9]*(_[A-Z0-9_]+)*$", + "format": "^[a-z][A-Z][A-Z0-9]*(_[A-Z0-9_]+)*$", "tokens": ["INLINE", "NOTINLINE"] }, "type": "ConstantName" diff --git a/hmm.json b/hmm.json index a75dee432..0dfe88ded 100644 --- a/hmm.json +++ b/hmm.json @@ -146,7 +146,7 @@ "name": "polymod", "type": "git", "dir": null, - "ref": "5547763a22858a1f10939e082de421d587c862bf", + "ref": "8553b800965f225bb14c7ab8f04bfa9cdec362ac", "url": "https://github.com/larsiusprime/polymod" }, { diff --git a/source/Main.hx b/source/Main.hx index 758edcc65..f4c5d9eb2 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -11,9 +11,11 @@ import openfl.display.Sprite; import openfl.events.Event; import openfl.Lib; import openfl.media.Video; -import funkin.util.CLIUtil; import openfl.net.NetStream; +/** + * The main class which initializes HaxeFlixel and starts the game in its initial state. + */ class Main extends Sprite { var gameWidth:Int = 1280; // Width of the game in pixels (might be less / more in actual pixels depending on your zoom). @@ -76,26 +78,27 @@ class Main extends Sprite var netStream:NetStream; var overlay:Sprite; + /** + * A frame counter displayed at the top left. + */ public static var fpsCounter:FPS; + + /** + * A RAM counter displayed at the top left. + */ public static var memoryCounter:MemoryCounter; function setupGame():Void { - /** - * The `zoom` argument of FlxGame was removed in the dev branch of Flixel, - * since it was considered confusing and unintuitive. - * If you want to change how the game scales when you resize the window, - * you can use `FlxG.scaleMode`. - * -Eric - */ - initHaxeUI(); + // addChild gets called by the user settings code. fpsCounter = new FPS(10, 3, 0xFFFFFF); - // addChild(fpsCounter); // Handled by Preferences.init + #if !html5 + // addChild gets called by the user settings code. + // TODO: disabled on HTML5 (todo: find another method that works?) memoryCounter = new MemoryCounter(10, 13, 0xFFFFFF); - // addChild(memoryCounter); #end // George recommends binding the save before FlxGame is created. @@ -112,6 +115,8 @@ class Main extends Sprite #if hxcpp_debug_server trace('hxcpp_debug_server is enabled! You can now connect to the game with a debugger.'); + #else + trace('hxcpp_debug_server is disabled! This build does not support debugging.'); #end } diff --git a/source/Postbuild.hx b/source/Postbuild.hx index f1827c4ab..405d0400b 100644 --- a/source/Postbuild.hx +++ b/source/Postbuild.hx @@ -3,34 +3,37 @@ package source; // Yeah, I know... import sys.FileSystem; import sys.io.File; +/** + * A script which executes after the game is built. + */ class Postbuild { - static inline final buildTimeFile = '.build_time'; + static inline final BUILD_TIME_FILE:String = '.build_time'; - static function main() + static function main():Void { printBuildTime(); } - static function printBuildTime() + static function printBuildTime():Void { // get buildEnd before fs operations since they are blocking var end:Float = Sys.time(); - if (FileSystem.exists(buildTimeFile)) + if (FileSystem.exists(BUILD_TIME_FILE)) { - var fi = File.read(buildTimeFile); + var fi:sys.io.FileInput = File.read(BUILD_TIME_FILE); var start:Float = fi.readDouble(); fi.close(); - sys.FileSystem.deleteFile(buildTimeFile); + sys.FileSystem.deleteFile(BUILD_TIME_FILE); - var buildTime = roundToTwoDecimals(end - start); + var buildTime:Float = roundToTwoDecimals(end - start); trace('Build took: ${buildTime} seconds'); } } - private static function roundToTwoDecimals(value:Float):Float + static function roundToTwoDecimals(value:Float):Float { return Math.round(value * 100) / 100; } diff --git a/source/Prebuild.hx b/source/Prebuild.hx index 18a5e2076..6856c414c 100644 --- a/source/Prebuild.hx +++ b/source/Prebuild.hx @@ -2,20 +2,23 @@ package source; // Yeah, I know... import sys.io.File; +/** + * A script which executes before the game is built. + */ class Prebuild { - static inline final buildTimeFile = '.build_time'; + static inline final BUILD_TIME_FILE:String = '.build_time'; - static function main() + static function main():Void { saveBuildTime(); trace('Building...'); } - static function saveBuildTime() + static function saveBuildTime():Void { - var fo = File.write(buildTimeFile); - var now = Sys.time(); + var fo:sys.io.FileOutput = File.write(BUILD_TIME_FILE); + var now:Float = Sys.time(); fo.writeDouble(now); fo.close(); } diff --git a/source/flixel/addons/transition/FlxTransitionableSubState.hx b/source/flixel/addons/transition/FlxTransitionableSubState.hx deleted file mode 100644 index ab416adbc..000000000 --- a/source/flixel/addons/transition/FlxTransitionableSubState.hx +++ /dev/null @@ -1,234 +0,0 @@ -package flixel.addons.transition; - -import flixel.FlxSubState; -import flixel.addons.transition.FlxTransitionableState; - -/** - * A `FlxSubState` which can perform visual transitions - * - * Usage: - * - * First, extend `FlxTransitionableSubState` as ie, `FooState`. - * - * Method 1: - * - * ```haxe - * var in:TransitionData = new TransitionData(...); // add your data where "..." is - * var out:TransitionData = new TransitionData(...); - * - * FlxG.switchState(() -> new FooState(in,out)); - * ``` - * - * Method 2: - * - * ```haxe - * FlxTransitionableSubState.defaultTransIn = new TransitionData(...); - * FlxTransitionableSubState.defaultTransOut = new TransitionData(...); - * - * FlxG.switchState(() -> new FooState()); - * ``` - */ -class FlxTransitionableSubState extends FlxSubState -{ - // global default transitions for ALL states, used if transIn/transOut are null - public static var defaultTransIn(get, set):TransitionData; - - static function get_defaultTransIn():TransitionData - { - return FlxTransitionableState.defaultTransIn; - } - - static function set_defaultTransIn(value:TransitionData):TransitionData - { - return FlxTransitionableState.defaultTransIn = value; - } - - public static var defaultTransOut(get, set):TransitionData; - - static function get_defaultTransOut():TransitionData - { - return FlxTransitionableState.defaultTransOut; - } - - static function set_defaultTransOut(value:TransitionData):TransitionData - { - return FlxTransitionableState.defaultTransOut = value; - } - - public static var skipNextTransIn(get, set):Bool; - - static function get_skipNextTransIn():Bool - { - return FlxTransitionableState.skipNextTransIn; - } - - static function set_skipNextTransIn(value:Bool):Bool - { - return FlxTransitionableState.skipNextTransIn = value; - } - - public static var skipNextTransOut(get, set):Bool; - - static function get_skipNextTransOut():Bool - { - return FlxTransitionableState.skipNextTransOut; - } - - static function set_skipNextTransOut(value:Bool):Bool - { - return FlxTransitionableState.skipNextTransOut = value; - } - - // beginning & ending transitions for THIS state: - public var transIn:TransitionData; - public var transOut:TransitionData; - - public var hasTransIn(get, never):Bool; - public var hasTransOut(get, never):Bool; - - /** - * Create a state with the ability to do visual transitions - * @param TransIn Plays when the state begins - * @param TransOut Plays when the state ends - */ - public function new(?TransIn:TransitionData, ?TransOut:TransitionData) - { - transIn = TransIn; - transOut = TransOut; - - if (transIn == null && defaultTransIn != null) - { - transIn = defaultTransIn; - } - if (transOut == null && defaultTransOut != null) - { - transOut = defaultTransOut; - } - super(); - } - - override function destroy():Void - { - super.destroy(); - transIn = null; - transOut = null; - _onExit = null; - } - - override function create():Void - { - super.create(); - transitionIn(); - } - - override function startOutro(onOutroComplete:() -> Void) - { - if (!hasTransOut) onOutroComplete(); - else if (!_exiting) - { - // play the exit transition, and when it's done call FlxG.switchState - _exiting = true; - transitionOut(onOutroComplete); - - if (skipNextTransOut) - { - skipNextTransOut = false; - finishTransOut(); - } - } - } - - /** - * Starts the in-transition. Can be called manually at any time. - */ - public function transitionIn():Void - { - if (transIn != null && transIn.type != NONE) - { - if (skipNextTransIn) - { - skipNextTransIn = false; - if (finishTransIn != null) - { - finishTransIn(); - } - return; - } - - var _trans = createTransition(transIn); - - _trans.setStatus(FULL); - openSubState(_trans); - - _trans.finishCallback = finishTransIn; - _trans.start(OUT); - } - } - - /** - * Starts the out-transition. Can be called manually at any time. - */ - public function transitionOut(?OnExit:Void->Void):Void - { - _onExit = OnExit; - if (hasTransOut) - { - var _trans = createTransition(transOut); - - _trans.setStatus(EMPTY); - openSubState(_trans); - - _trans.finishCallback = finishTransOut; - _trans.start(IN); - } - else - { - _onExit(); - } - } - - var transOutFinished:Bool = false; - - var _exiting:Bool = false; - var _onExit:Void->Void; - - function get_hasTransIn():Bool - { - return transIn != null && transIn.type != NONE; - } - - function get_hasTransOut():Bool - { - return transOut != null && transOut.type != NONE; - } - - function createTransition(data:TransitionData):Transition - { - return switch (data.type) - { - case TILES: new Transition(data); - case FADE: new Transition(data); - default: null; - } - } - - function finishTransIn() - { - closeSubState(); - } - - function finishTransOut() - { - transOutFinished = true; - - if (!_exiting) - { - closeSubState(); - } - - if (_onExit != null) - { - _onExit(); - } - } -} diff --git a/source/funkin/Conductor.hx b/source/funkin/Conductor.hx index 383a49084..7ac6382f2 100644 --- a/source/funkin/Conductor.hx +++ b/source/funkin/Conductor.hx @@ -3,7 +3,6 @@ package funkin; import funkin.util.Constants; import flixel.util.FlxSignal; import flixel.math.FlxMath; -import funkin.play.song.Song.SongDifficulty; import funkin.data.song.SongData.SongTimeChange; import funkin.data.song.SongDataUtils; @@ -144,6 +143,9 @@ class Conductor return beatLengthMs / timeSignatureNumerator; } + /** + * The numerator for the current time signature (the `3` in `3/4`). + */ public var timeSignatureNumerator(get, never):Int; function get_timeSignatureNumerator():Int @@ -153,6 +155,9 @@ class Conductor return currentTimeChange.timeSignatureNum; } + /** + * The denominator for the current time signature (the `4` in `3/4`). + */ public var timeSignatureDenominator(get, never):Int; function get_timeSignatureDenominator():Int @@ -253,7 +258,7 @@ class Conductor * WARNING: Avoid this for things like setting the BPM of the title screen music, * you should have a metadata file for it instead. */ - public function forceBPM(?bpm:Float = null) + public function forceBPM(?bpm:Float):Void { if (bpm != null) { @@ -261,7 +266,7 @@ class Conductor } else { - // trace('[CONDUCTOR] Resetting BPM to default'); + trace('[CONDUCTOR] Resetting BPM to default'); } this.bpmOverride = bpm; @@ -274,7 +279,7 @@ class Conductor * @param songPosition The current position in the song in milliseconds. * Leave blank to use the FlxG.sound.music position. */ - public function update(?songPos:Float) + public function update(?songPos:Float):Void { if (songPos == null) { @@ -282,9 +287,9 @@ class Conductor songPos = (FlxG.sound.music != null) ? (FlxG.sound.music.time + instrumentalOffset + formatOffset) : 0.0; } - var oldMeasure = this.currentMeasure; - var oldBeat = this.currentBeat; - var oldStep = this.currentStep; + var oldMeasure:Float = this.currentMeasure; + var oldBeat:Float = this.currentBeat; + var oldStep:Float = this.currentStep; // Set the song position we are at (for purposes of calculating note positions, etc). this.songPosition = songPos; @@ -346,39 +351,43 @@ class Conductor } } - public function mapTimeChanges(songTimeChanges:Array) + /** + * Apply the `SongTimeChange` data from the song metadata to this Conductor. + * @param songTimeChanges The SongTimeChanges. + */ + public function mapTimeChanges(songTimeChanges:Array):Void { timeChanges = []; // Sort in place just in case it's out of order. SongDataUtils.sortTimeChanges(songTimeChanges); - for (currentTimeChange in songTimeChanges) + for (songTimeChange in songTimeChanges) { // TODO: Maybe handle this different? // Do we care about BPM at negative timestamps? // Without any custom handling, `currentStepTime` becomes non-zero at `songPosition = 0`. - if (currentTimeChange.timeStamp < 0.0) currentTimeChange.timeStamp = 0.0; + if (songTimeChange.timeStamp < 0.0) songTimeChange.timeStamp = 0.0; - if (currentTimeChange.timeStamp <= 0.0) + if (songTimeChange.timeStamp <= 0.0) { - currentTimeChange.beatTime = 0.0; + songTimeChange.beatTime = 0.0; } else { // Calculate the beat time of this timestamp. - currentTimeChange.beatTime = 0.0; + songTimeChange.beatTime = 0.0; - if (currentTimeChange.timeStamp > 0.0 && timeChanges.length > 0) + if (songTimeChange.timeStamp > 0.0 && timeChanges.length > 0) { var prevTimeChange:SongTimeChange = timeChanges[timeChanges.length - 1]; - currentTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime - + ((currentTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC), + songTimeChange.beatTime = FlxMath.roundDecimal(prevTimeChange.beatTime + + ((songTimeChange.timeStamp - prevTimeChange.timeStamp) * prevTimeChange.bpm / Constants.SECS_PER_MIN / Constants.MS_PER_SEC), 4); } } - timeChanges.push(currentTimeChange); + timeChanges.push(songTimeChange); } if (timeChanges.length > 0) @@ -392,6 +401,8 @@ class Conductor /** * Given a time in milliseconds, return a time in steps. + * @param ms The time in milliseconds. + * @return The time in steps. */ public function getTimeInSteps(ms:Float):Float { @@ -421,7 +432,7 @@ class Conductor var lastStepLengthMs:Float = ((Constants.SECS_PER_MIN / lastTimeChange.bpm) * Constants.MS_PER_SEC) / timeSignatureNumerator; var resultFractionalStep:Float = (ms - lastTimeChange.timeStamp) / lastStepLengthMs; - resultStep += resultFractionalStep; // Math.floor(); + resultStep += resultFractionalStep; return resultStep; } @@ -429,6 +440,8 @@ class Conductor /** * Given a time in steps and fractional steps, return a time in milliseconds. + * @param stepTime The time in steps. + * @return The time in milliseconds. */ public function getStepTimeInMs(stepTime:Float):Float { @@ -465,6 +478,8 @@ class Conductor /** * Given a time in beats and fractional beats, return a time in milliseconds. + * @param beatTime The time in beats. + * @return The time in milliseconds. */ public function getBeatTimeInMs(beatTime:Float):Float { @@ -499,13 +514,16 @@ class Conductor } } + /** + * Add variables of the current Conductor instance to the Flixel debugger. + */ public static function watchQuick():Void { - FlxG.watch.addQuick("songPosition", Conductor.instance.songPosition); - FlxG.watch.addQuick("bpm", Conductor.instance.bpm); - FlxG.watch.addQuick("currentMeasureTime", Conductor.instance.currentMeasureTime); - FlxG.watch.addQuick("currentBeatTime", Conductor.instance.currentBeatTime); - FlxG.watch.addQuick("currentStepTime", Conductor.instance.currentStepTime); + FlxG.watch.addQuick('songPosition', Conductor.instance.songPosition); + FlxG.watch.addQuick('bpm', Conductor.instance.bpm); + FlxG.watch.addQuick('currentMeasureTime', Conductor.instance.currentMeasureTime); + FlxG.watch.addQuick('currentBeatTime', Conductor.instance.currentBeatTime); + FlxG.watch.addQuick('currentStepTime', Conductor.instance.currentStepTime); } /** diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 8837b578d..6ea77ec18 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -12,7 +12,6 @@ import flixel.math.FlxRect; import flixel.FlxSprite; import flixel.system.debug.log.LogStyle; import flixel.util.FlxColor; -import funkin.ui.options.PreferencesMenu; import funkin.util.macro.MacroUtil; import funkin.util.WindowUtil; import funkin.play.PlayStatePlaylist; @@ -32,7 +31,6 @@ import funkin.ui.title.TitleState; import funkin.util.CLIUtil; import funkin.util.CLIUtil.CLIParams; import funkin.util.TimerUtil; -import funkin.ui.transition.LoadingState; import funkin.util.TrackerUtil; #if discord_rpc import Discord.DiscordClient; @@ -171,8 +169,9 @@ class InitState extends FlxState AlbumRegistry.instance.loadEntries(); StageRegistry.instance.loadEntries(); - // TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers. - CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry. + // TODO: CharacterDataParser doesn't use json2object, so it's way slower than the other parsers and more prone to syntax errors. + // Move it to use a BaseRegistry. + CharacterDataParser.loadCharacterCache(); ModuleHandler.buildModuleCallbacks(); ModuleHandler.loadModuleCache(); @@ -190,25 +189,35 @@ class InitState extends FlxState */ function startGame():Void { - #if SONG // -DSONG=bopeebo + #if SONG + // -DSONG=bopeebo startSong(defineSong(), defineDifficulty()); - #elseif LEVEL // -DLEVEL=week1 -DDIFFICULTY=hard + #elseif LEVEL + // -DLEVEL=week1 -DDIFFICULTY=hard startLevel(defineLevel(), defineDifficulty()); - #elseif FREEPLAY // -DFREEPLAY + #elseif FREEPLAY + // -DFREEPLAY FlxG.switchState(() -> new funkin.ui.freeplay.FreeplayState()); - #elseif DIALOGUE // -DDIALOGUE + #elseif DIALOGUE + // -DDIALOGUE FlxG.switchState(() -> new funkin.ui.debug.dialogue.ConversationDebugState()); - #elseif ANIMATE // -DANIMATE + #elseif ANIMATE + // -DANIMATE FlxG.switchState(() -> new funkin.ui.debug.anim.FlxAnimateTest()); - #elseif WAVEFORM // -DWAVEFORM + #elseif WAVEFORM + // -DWAVEFORM FlxG.switchState(() -> new funkin.ui.debug.WaveformTestState()); - #elseif CHARTING // -DCHARTING + #elseif CHARTING + // -DCHARTING FlxG.switchState(() -> new funkin.ui.debug.charting.ChartEditorState()); - #elseif STAGEBUILD // -DSTAGEBUILD + #elseif STAGEBUILD + // -DSTAGEBUILD FlxG.switchState(() -> new funkin.ui.debug.stage.StageBuilderState()); - #elseif ANIMDEBUG // -DANIMDEBUG + #elseif ANIMDEBUG + // -DANIMDEBUG FlxG.switchState(() -> new funkin.ui.debug.anim.DebugBoundingState()); - #elseif LATENCY // -DLATENCY + #elseif LATENCY + // -DLATENCY FlxG.switchState(() -> new funkin.LatencyState()); #else startGameNormally(); diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx index 6006939be..d2c4833f2 100644 --- a/source/funkin/Paths.hx +++ b/source/funkin/Paths.hx @@ -11,144 +11,144 @@ class Paths { static var currentLevel:String; - static public function setCurrentLevel(name:String) + public static function setCurrentLevel(name:String):Void { currentLevel = name.toLowerCase(); } public static function stripLibrary(path:String):String { - var parts = path.split(':'); + var parts:Array = path.split(':'); if (parts.length < 2) return path; return parts[1]; } public static function getLibrary(path:String):String { - var parts = path.split(':'); - if (parts.length < 2) return "preload"; + var parts:Array = path.split(':'); + if (parts.length < 2) return 'preload'; return parts[0]; } - static function getPath(file:String, type:AssetType, library:Null) + static function getPath(file:String, type:AssetType, library:Null):String { if (library != null) return getLibraryPath(file, library); if (currentLevel != null) { - var levelPath = getLibraryPathForce(file, currentLevel); + var levelPath:String = getLibraryPathForce(file, currentLevel); if (OpenFlAssets.exists(levelPath, type)) return levelPath; } - var levelPath = getLibraryPathForce(file, "shared"); + var levelPath:String = getLibraryPathForce(file, 'shared'); if (OpenFlAssets.exists(levelPath, type)) return levelPath; return getPreloadPath(file); } - static public function getLibraryPath(file:String, library = "preload") + public static function getLibraryPath(file:String, library = 'preload'):String { - return if (library == "preload" || library == "default") getPreloadPath(file); else getLibraryPathForce(file, library); + return if (library == 'preload' || library == 'default') getPreloadPath(file); else getLibraryPathForce(file, library); } - inline static function getLibraryPathForce(file:String, library:String) + static inline function getLibraryPathForce(file:String, library:String):String { return '$library:assets/$library/$file'; } - inline static function getPreloadPath(file:String) + static inline function getPreloadPath(file:String):String { return 'assets/$file'; } - inline static public function file(file:String, type:AssetType = TEXT, ?library:String) + public static function file(file:String, type:AssetType = TEXT, ?library:String):String { return getPath(file, type, library); } - public static inline function animateAtlas(path:String, ?library:String) + public static function animateAtlas(path:String, ?library:String):String { return getLibraryPath('images/$path', library); } - inline static public function txt(key:String, ?library:String) + public static function txt(key:String, ?library:String):String { return getPath('data/$key.txt', TEXT, library); } - inline static public function frag(key:String, ?library:String) + public static function frag(key:String, ?library:String):String { return getPath('shaders/$key.frag', TEXT, library); } - inline static public function vert(key:String, ?library:String) + public static function vert(key:String, ?library:String):String { return getPath('shaders/$key.vert', TEXT, library); } - inline static public function xml(key:String, ?library:String) + public static function xml(key:String, ?library:String):String { return getPath('data/$key.xml', TEXT, library); } - inline static public function json(key:String, ?library:String) + public static function json(key:String, ?library:String):String { return getPath('data/$key.json', TEXT, library); } - static public function sound(key:String, ?library:String) + public static function sound(key:String, ?library:String):String { return getPath('sounds/$key.${Constants.EXT_SOUND}', SOUND, library); } - inline static public function soundRandom(key:String, min:Int, max:Int, ?library:String) + public static function soundRandom(key:String, min:Int, max:Int, ?library:String):String { return sound(key + FlxG.random.int(min, max), library); } - inline static public function music(key:String, ?library:String) + public static function music(key:String, ?library:String):String { return getPath('music/$key.${Constants.EXT_SOUND}', MUSIC, library); } - inline static public function videos(key:String, ?library:String) + public static function videos(key:String, ?library:String):String { return getPath('videos/$key.${Constants.EXT_VIDEO}', BINARY, library); } - inline static public function voices(song:String, ?suffix:String = '') + public static function voices(song:String, ?suffix:String = ''):String { - if (suffix == null) suffix = ""; // no suffix, for a sorta backwards compatibility with older-ish voice files + if (suffix == null) suffix = ''; // no suffix, for a sorta backwards compatibility with older-ish voice files return 'songs:assets/songs/${song.toLowerCase()}/Voices$suffix.${Constants.EXT_SOUND}'; } - inline static public function inst(song:String, ?suffix:String = '') + public static function inst(song:String, ?suffix:String = ''):String { return 'songs:assets/songs/${song.toLowerCase()}/Inst$suffix.${Constants.EXT_SOUND}'; } - inline static public function image(key:String, ?library:String) + public static function image(key:String, ?library:String):String { return getPath('images/$key.png', IMAGE, library); } - inline static public function font(key:String) + public static function font(key:String):String { return 'assets/fonts/$key'; } - inline static public function ui(key:String, ?library:String) + public static function ui(key:String, ?library:String):String { return xml('ui/$key', library); } - static public function getSparrowAtlas(key:String, ?library:String) + public static function getSparrowAtlas(key:String, ?library:String):FlxAtlasFrames { return FlxAtlasFrames.fromSparrow(image(key, library), file('images/$key.xml', library)); } - inline static public function getPackerAtlas(key:String, ?library:String) + public static function getPackerAtlas(key:String, ?library:String):FlxAtlasFrames { return FlxAtlasFrames.fromSpriteSheetPacker(image(key, library), file('images/$key.txt', library)); } diff --git a/source/funkin/PlayerSettings.hx b/source/funkin/PlayerSettings.hx index 5a9351bba..b4c871c29 100644 --- a/source/funkin/PlayerSettings.hx +++ b/source/funkin/PlayerSettings.hx @@ -13,6 +13,7 @@ import flixel.util.FlxSignal; */ class PlayerSettings { + // TODO: Finish implementation of second player. public static var numPlayers(default, null) = 0; public static var numAvatars(default, null) = 0; public static var player1(default, null):PlayerSettings; @@ -21,12 +22,21 @@ class PlayerSettings public static var onAvatarAdd(default, null) = new FlxTypedSignalVoid>(); public static var onAvatarRemove(default, null) = new FlxTypedSignalVoid>(); + /** + * The player number associated with this settings object. + */ public var id(default, null):Int; + /** + * The controls handler for this player. + */ public var controls(default, null):Controls; /** * Return the PlayerSettings for the given player number, or `null` if that player isn't active. + * + * @param id The player number this represents. + * @return The PlayerSettings for the given player number, or `null` if that player isn't active. */ public static function get(id:Int):Null { @@ -38,6 +48,9 @@ class PlayerSettings }; } + /** + * Initialize the PlayerSettings singletons for each player. + */ public static function init():Void { if (player1 == null) @@ -56,22 +69,30 @@ class PlayerSettings } } - public static function reset() + /** + * Forcibly destroy the PlayerSettings singletons for each player. + */ + public static function reset():Void { player1 = null; player2 = null; numPlayers = 0; } - static function onGamepadAdded(gamepad:FlxGamepad) + /** + * Callback invoked when a gamepad is added. + * @param gamepad The gamepad that was added. + */ + static function onGamepadAdded(gamepad:FlxGamepad):Void { + // TODO: Make this detect and handle multiple players player1.addGamepad(gamepad); } /** * @param id The player number this represents. This was refactored to START AT `1`. */ - private function new(id:Int) + function new(id:Int) { trace('loading player settings for id: $id'); @@ -83,11 +104,11 @@ class PlayerSettings function addKeyboard():Void { - var useDefault = true; + var useDefault:Bool = true; if (Save.instance.hasControls(id, Keys)) { var keyControlData = Save.instance.getControls(id, Keys); - trace("keyControlData: " + haxe.Json.stringify(keyControlData)); + trace('Loading keyboard control scheme from user save'); useDefault = false; controls.fromSaveData(keyControlData, Keys); } @@ -98,7 +119,7 @@ class PlayerSettings if (useDefault) { - trace("Loading default keyboard control scheme"); + trace('Loading default keyboard control scheme'); controls.setKeyboardScheme(Solo); } @@ -109,13 +130,13 @@ class PlayerSettings * Called after an FlxGamepad has been detected. * @param gamepad The gamepad that was detected. */ - function addGamepad(gamepad:FlxGamepad) + function addGamepad(gamepad:FlxGamepad):Void { var useDefault = true; if (Save.instance.hasControls(id, Gamepad(gamepad.id))) { var padControlData = Save.instance.getControls(id, Gamepad(gamepad.id)); - trace("padControlData: " + haxe.Json.stringify(padControlData)); + trace('Loading gamepad control scheme from user save'); useDefault = false; controls.addGamepadWithSaveData(gamepad.id, padControlData); } @@ -126,7 +147,7 @@ class PlayerSettings if (useDefault) { - trace("Loading gamepad control scheme"); + trace('Loading default gamepad control scheme'); controls.addDefaultGamepad(gamepad.id); } PreciseInputManager.instance.initializeButtons(controls, gamepad); @@ -135,12 +156,12 @@ class PlayerSettings /** * Save this player's controls to the game's persistent save. */ - public function saveControls() + public function saveControls():Void { var keyData = controls.createSaveData(Keys); if (keyData != null) { - trace("saving key data: " + haxe.Json.stringify(keyData)); + trace('Saving keyboard control scheme to user save'); Save.instance.setControls(id, Keys, keyData); } @@ -149,7 +170,7 @@ class PlayerSettings var padData = controls.createSaveData(Gamepad(controls.gamepadsAdded[0])); if (padData != null) { - trace("saving pad data: " + haxe.Json.stringify(padData)); + trace('Saving gamepad control scheme to user save'); Save.instance.setControls(id, Gamepad(controls.gamepadsAdded[0]), padData); } } diff --git a/source/funkin/Preferences.hx b/source/funkin/Preferences.hx index 60c7a996a..d8066c581 100644 --- a/source/funkin/Preferences.hx +++ b/source/funkin/Preferences.hx @@ -20,7 +20,7 @@ class Preferences static function set_naughtyness(value:Bool):Bool { - var save = Save.instance; + var save:Save = Save.instance; save.options.naughtyness = value; save.flush(); return value; @@ -39,7 +39,7 @@ class Preferences static function set_downscroll(value:Bool):Bool { - var save = Save.instance; + var save:Save = Save.instance; save.options.downscroll = value; save.flush(); return value; @@ -58,7 +58,7 @@ class Preferences static function set_flashingLights(value:Bool):Bool { - var save = Save.instance; + var save:Save = Save.instance; save.options.flashingLights = value; save.flush(); return value; @@ -77,7 +77,7 @@ class Preferences static function set_zoomCamera(value:Bool):Bool { - var save = Save.instance; + var save:Save = Save.instance; save.options.zoomCamera = value; save.flush(); return value; @@ -122,15 +122,20 @@ class Preferences { if (value != Save.instance.options.autoPause) FlxG.autoPause = value; - var save = Save.instance; + var save:Save = Save.instance; save.options.autoPause = value; save.flush(); return value; } + /** + * Loads the user's preferences from the save data and apply them. + */ public static function init():Void { + // Apply the autoPause setting (enables automatic pausing on focus lost). FlxG.autoPause = Preferences.autoPause; + // Apply the debugDisplay setting (enables the FPS and RAM display). toggleDebugDisplay(Preferences.debugDisplay); } diff --git a/source/funkin/Preloader.hx b/source/funkin/Preloader.hx index 2a73d8199..157015ba6 100644 --- a/source/funkin/Preloader.hx +++ b/source/funkin/Preloader.hx @@ -3,7 +3,6 @@ package funkin; import flash.Lib; import flash.display.Bitmap; import flash.display.BitmapData; -import flash.display.BlendMode; import flash.display.Sprite; import flixel.system.FlxBasePreloader; import openfl.display.Sprite; @@ -12,7 +11,8 @@ import openfl.text.TextField; import openfl.text.TextFormat; import flixel.system.FlxAssets; -@:bitmap("art/preloaderArt.png") class LogoImage extends BitmapData {} +@:bitmap('art/preloaderArt.png') +class LogoImage extends BitmapData {} class Preloader extends FlxBasePreloader { diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index c64240909..687860265 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -8,6 +8,7 @@ import funkin.util.tools.ICloneable; import funkin.data.song.SongData.SongMusicData; import funkin.data.song.SongRegistry; import funkin.audio.waveform.WaveformData; +import openfl.media.SoundMixer; import funkin.audio.waveform.WaveformDataParser; import flixel.math.FlxMath; import openfl.Assets; @@ -18,6 +19,7 @@ import openfl.utils.AssetType; /** * A FlxSound which adds additional functionality: * - Delayed playback via negative song position. + * - Easy functions for immediate playback and recycling. */ @:nullSafety class FunkinSound extends FlxSound implements ICloneable @@ -286,15 +288,28 @@ class FunkinSound extends FlxSound implements ICloneable * Creates a new `FunkinSound` object and loads it as the current music track. * * @param key The key of the music you want to play. Music should be at `music//.ogg`. - * @param overrideExisting Whether to override music if it is already playing. - * @param mapTimeChanges Whether to check for `SongMusicData` to update the Conductor with. + * @param params A set of additional optional parameters. * Data should be at `music//-metadata.json`. + * @return Whether the music was started. `false` if music was already playing or could not be started */ - public static function playMusic(key:String, overrideExisting:Bool = false, mapTimeChanges:Bool = true):Void + public static function playMusic(key:String, params:FunkinSoundPlayMusicParams):Bool { - if (!overrideExisting && FlxG.sound.music?.playing) return; + if (!(params.overrideExisting ?? false) && (FlxG.sound.music?.exists ?? false) && FlxG.sound.music.playing) return false; - if (mapTimeChanges) + if (!(params.restartTrack ?? false) && FlxG.sound.music?.playing) + { + if (FlxG.sound.music != null && Std.isOfType(FlxG.sound.music, FunkinSound)) + { + var existingSound:FunkinSound = cast FlxG.sound.music; + // Stop here if we would play a matching music track. + if (existingSound._label == Paths.music('$key/$key')) + { + return false; + } + } + } + + if (params?.mapTimeChanges ?? true) { var songMusicData:Null = SongRegistry.instance.parseMusicData(key); // Will fall back and return null if the metadata doesn't exist or can't be parsed. @@ -308,10 +323,26 @@ class FunkinSound extends FlxSound implements ICloneable } } - FlxG.sound.music = FunkinSound.load(Paths.music('$key/$key')); + if (FlxG.sound.music != null) + { + FlxG.sound.music.stop(); + FlxG.sound.music.kill(); + } - // Prevent repeat update() and onFocus() calls. - FlxG.sound.list.remove(FlxG.sound.music); + var music = FunkinSound.load(Paths.music('$key/$key'), params?.startingVolume ?? 1.0, params.loop ?? true, false, true); + if (music != null) + { + FlxG.sound.music = music; + + // Prevent repeat update() and onFocus() calls. + FlxG.sound.list.remove(FlxG.sound.music); + + return true; + } + else + { + return false; + } } /** @@ -326,11 +357,18 @@ class FunkinSound extends FlxSound implements ICloneable * @param autoPlay Whether to play the sound immediately or wait for a `play()` call. * @param onComplete Called when the sound finished playing. * @param onLoad Called when the sound finished loading. Called immediately for succesfully loaded embedded sounds. - * @return A `FunkinSound` object. + * @return A `FunkinSound` object, or `null` if the sound could not be loaded. */ public static function load(embeddedSound:FlxSoundAsset, volume:Float = 1.0, looped:Bool = false, autoDestroy:Bool = false, autoPlay:Bool = false, - ?onComplete:Void->Void, ?onLoad:Void->Void):FunkinSound + ?onComplete:Void->Void, ?onLoad:Void->Void):Null { + @:privateAccess + if (SoundMixer.__soundChannels.length >= SoundMixer.MAX_ACTIVE_CHANNELS) + { + FlxG.log.error('FunkinSound could not play sound, channels exhausted! Found ${SoundMixer.__soundChannels.length} active sound channels.'); + return null; + } + var sound:FunkinSound = pool.recycle(construct); // Load the sound. @@ -341,6 +379,10 @@ class FunkinSound extends FlxSound implements ICloneable { sound._label = embeddedSound; } + else + { + sound._label = 'unknown'; + } sound.volume = volume; sound.group = FlxG.sound.defaultSoundGroup; @@ -355,6 +397,36 @@ class FunkinSound extends FlxSound implements ICloneable return sound; } + public override function destroy():Void + { + // trace('[FunkinSound] Destroying sound "${this._label}"'); + super.destroy(); + } + + /** + * Play a sound effect once, then destroy it. + * @param key + * @param volume + * @return static function construct():FunkinSound + */ + public static function playOnce(key:String, volume:Float = 1.0, ?onComplete:Void->Void, ?onLoad:Void->Void):Void + { + var result = FunkinSound.load(key, volume, false, true, true, onComplete, onLoad); + } + + /** + * Stop all sounds in the pool and allow them to be recycled. + */ + public static function stopAllAudio(musicToo:Bool = false):Void + { + for (sound in pool) + { + if (sound == null) continue; + if (!musicToo && sound == FlxG.sound.music) continue; + sound.destroy(); + } + } + static function construct():FunkinSound { var sound:FunkinSound = new FunkinSound(); @@ -365,3 +437,39 @@ class FunkinSound extends FlxSound implements ICloneable return sound; } } + +/** + * Additional parameters for `FunkinSound.playMusic()` + */ +typedef FunkinSoundPlayMusicParams = +{ + /** + * The volume you want the music to start at. + * @default `1.0` + */ + var ?startingVolume:Float; + + /** + * Whether to override music if a different track is already playing. + * @default `false` + */ + var ?overrideExisting:Bool; + + /** + * Whether to override music if the same track is already playing. + * @default `false` + */ + var ?restartTrack:Bool; + + /** + * Whether the music should loop or play once. + * @default `true` + */ + var ?loop:Bool; + + /** + * Whether to check for `SongMusicData` to update the Conductor with. + * @default `true` + */ + var ?mapTimeChanges:Bool; +} diff --git a/source/funkin/audio/SoundGroup.hx b/source/funkin/audio/SoundGroup.hx index a26537c2a..9a754049b 100644 --- a/source/funkin/audio/SoundGroup.hx +++ b/source/funkin/audio/SoundGroup.hx @@ -1,7 +1,6 @@ package funkin.audio; import flixel.group.FlxGroup.FlxTypedGroup; -import flixel.sound.FlxSound; import funkin.audio.FunkinSound; import flixel.tweens.FlxTween; diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx index a88476d4d..78f660d3f 100644 --- a/source/funkin/modding/PolymodHandler.hx +++ b/source/funkin/modding/PolymodHandler.hx @@ -209,7 +209,6 @@ class PolymodHandler // Add import aliases for certain classes. // NOTE: Scripted classes are automatically aliased to their parent class. Polymod.addImportAlias('flixel.math.FlxPoint', flixel.math.FlxPoint.FlxBasePoint); - Polymod.addImportAlias('flixel.system.FlxSound', flixel.sound.FlxSound); // Add blacklisting for prohibited classes and packages. // `polymod.*` diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index 747565100..10636afdf 100644 --- a/source/funkin/play/Countdown.hx +++ b/source/funkin/play/Countdown.hx @@ -9,6 +9,7 @@ import funkin.modding.module.ModuleHandler; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEvent.CountdownScriptEvent; import flixel.util.FlxTimer; +import funkin.audio.FunkinSound; class Countdown { @@ -282,7 +283,7 @@ class Countdown if (soundPath == null) return; - FlxG.sound.play(Paths.sound(soundPath), Constants.COUNTDOWN_VOLUME); + FunkinSound.playOnce(Paths.sound(soundPath), Constants.COUNTDOWN_VOLUME); } public static function decrement(step:CountdownStep):CountdownStep diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index b3e815a41..a1796e912 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -3,7 +3,6 @@ package funkin.play; import flixel.FlxG; import flixel.FlxObject; import flixel.FlxSprite; -import flixel.sound.FlxSound; import funkin.audio.FunkinSound; import flixel.util.FlxColor; import flixel.util.FlxTimer; @@ -97,14 +96,14 @@ class GameOverSubState extends MusicBeatSubState /** * Reset the game over configuration to the default. */ - public static function reset() + public static function reset():Void { - animationSuffix = ""; - musicSuffix = ""; - blueBallSuffix = ""; + animationSuffix = ''; + musicSuffix = ''; + blueBallSuffix = ''; } - override public function create() + override public function create():Void { if (instance != null) { @@ -132,26 +131,27 @@ class GameOverSubState extends MusicBeatSubState // Pluck Boyfriend from the PlayState and place him (in the same position) in the GameOverSubState. // We can then play the character's `firstDeath` animation. - boyfriend = playState.currentStage.getBoyfriend(true); - boyfriend.isDead = true; - add(boyfriend); - boyfriend.resetCharacter(); + if (PlayState.instance.isMinimalMode) {} + else + { + boyfriend = PlayState.instance.currentStage.getBoyfriend(true); + boyfriend.isDead = true; + add(boyfriend); + boyfriend.resetCharacter(); - // Cancel camera tweening if it's currently active. - playState.cancelAllCameraTweens(); + // Assign a camera follow point to the boyfriend's position. + cameraFollowPoint = new FlxObject(PlayState.instance.cameraFollowPoint.x, PlayState.instance.cameraFollowPoint.y, 1, 1); + cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x; + cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y; + var offsets:Array = boyfriend.getDeathCameraOffsets(); + cameraFollowPoint.x += offsets[0]; + cameraFollowPoint.y += offsets[1]; + add(cameraFollowPoint); - // Assign a camera follow point to the boyfriend's position. - cameraFollowPoint = new FlxObject(playState.cameraFollowPoint.x, playState.cameraFollowPoint.y, 1, 1); - cameraFollowPoint.x = boyfriend.getGraphicMidpoint().x; - cameraFollowPoint.y = boyfriend.getGraphicMidpoint().y; - var offsets:Array = boyfriend.getDeathCameraOffsets(); - cameraFollowPoint.x += offsets[0]; - cameraFollowPoint.y += offsets[1]; - add(cameraFollowPoint); - - FlxG.camera.target = null; - FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01); - targetCameraZoom = PlayState?.instance?.currentStage?.camZoom * boyfriend.getDeathCameraZoom(); + FlxG.camera.target = null; + FlxG.camera.follow(cameraFollowPoint, LOCKON, 0.01); + targetCameraZoom = PlayState?.instance?.currentStage?.camZoom * boyfriend.getDeathCameraZoom(); + } // // Set up the audio @@ -169,21 +169,29 @@ class GameOverSubState extends MusicBeatSubState var hasStartedAnimation:Bool = false; - override function update(elapsed:Float) + override function update(elapsed:Float):Void { if (!hasStartedAnimation) { hasStartedAnimation = true; - if (boyfriend.hasAnimation('fakeoutDeath') && FlxG.random.bool((1 / 4096) * 100)) + if (PlayState.instance.isMinimalMode) { - boyfriend.playAnimation('fakeoutDeath', true, false); + // Play the "blue balled" sound. May play a variant if one has been assigned. + playBlueBalledSFX(); } else { - boyfriend.playAnimation('firstDeath', true, false); // ignoreOther is set to FALSE since you WANT to be able to mash and confirm game over! - // Play the "blue balled" sound. May play a variant if one has been assigned. - playBlueBalledSFX(); + if (boyfriend.hasAnimation('fakeoutDeath') && FlxG.random.bool((1 / 4096) * 100)) + { + boyfriend.playAnimation('fakeoutDeath', true, false); + } + else + { + boyfriend.playAnimation('firstDeath', true, false); // ignoreOther is set to FALSE since you WANT to be able to mash and confirm game over! + // Play the "blue balled" sound. May play a variant if one has been assigned. + playBlueBalledSFX(); + } } } @@ -246,27 +254,34 @@ class GameOverSubState extends MusicBeatSubState } else { - // Music hasn't started yet. - switch (PlayStatePlaylist.campaignId) + if (PlayState.instance.isMinimalMode) { - // TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded. - // This will simplify the class and make it easier for mods to add death quotes. - case "week7": - if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote) - { - playingJeffQuote = true; - playJeffQuote(); - // Start music at lower volume - startDeathMusic(0.2, false); - boyfriend.playAnimation('deathLoop' + animationSuffix); - } - default: - // Start music at normal volume once the initial death animation finishes. - if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished()) - { - startDeathMusic(1.0, false); - boyfriend.playAnimation('deathLoop' + animationSuffix); - } + // startDeathMusic(1.0, false); + } + else + { + // Music hasn't started yet. + switch (PlayStatePlaylist.campaignId) + { + // TODO: Make the behavior for playing Jeff's voicelines generic or un-hardcoded. + // This will simplify the class and make it easier for mods to add death quotes. + case 'week7': + if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished() && !playingJeffQuote) + { + playingJeffQuote = true; + playJeffQuote(); + // Start music at lower volume + startDeathMusic(0.2, false); + boyfriend.playAnimation('deathLoop' + animationSuffix); + } + default: + // Start music at normal volume once the initial death animation finishes. + if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && boyfriend.isAnimationFinished()) + { + startDeathMusic(1.0, false); + boyfriend.playAnimation('deathLoop' + animationSuffix); + } + } } } @@ -284,7 +299,11 @@ class GameOverSubState extends MusicBeatSubState isEnding = true; startDeathMusic(1.0, true); // isEnding changes this function's behavior. - boyfriend.playAnimation('deathConfirm' + animationSuffix, true); + if (PlayState.instance.isMinimalMode) {} + else + { + boyfriend.playAnimation('deathConfirm' + animationSuffix, true); + } // After the animation finishes... new FlxTimer().start(0.7, function(tmr:FlxTimer) { @@ -294,10 +313,14 @@ class GameOverSubState extends MusicBeatSubState FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true); PlayState.instance.needsReset = true; - // Readd Boyfriend to the stage. - boyfriend.isDead = false; - remove(boyfriend); - PlayState.instance.currentStage.addCharacter(boyfriend, BF); + if (PlayState.instance.isMinimalMode) {} + else + { + // Readd Boyfriend to the stage. + boyfriend.isDead = false; + remove(boyfriend); + PlayState.instance.currentStage.addCharacter(boyfriend, BF); + } // Snap reset the camera which may have changed because of the player character data. resetCameraZoom(); @@ -394,7 +417,7 @@ class GameOverSubState extends MusicBeatSubState blueballed = true; if (Assets.exists(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix))) { - FlxG.sound.play(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix)); + FunkinSound.playOnce(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix)); } else { @@ -414,7 +437,7 @@ class GameOverSubState extends MusicBeatSubState if (!Preferences.naughtyness) randomCensor = [1, 3, 8, 13, 17, 21]; - FlxG.sound.play(Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, randomCensor)), 1, false, null, true, function() { + FunkinSound.playOnce(Paths.sound('jeffGameover/jeffGameover-' + FlxG.random.int(1, 25, randomCensor)), function() { // Once the quote ends, fade in the game over music. if (!isEnding && gameOverMusic != null) { diff --git a/source/funkin/play/GitarooPause.hx b/source/funkin/play/GitarooPause.hx index eae56a9c3..6abe78cd8 100644 --- a/source/funkin/play/GitarooPause.hx +++ b/source/funkin/play/GitarooPause.hx @@ -26,7 +26,11 @@ class GitarooPause extends MusicBeatState override function create():Void { - if (FlxG.sound.music != null) FlxG.sound.music.stop(); + if (FlxG.sound.music != null) + { + FlxG.sound.music.destroy(); + FlxG.sound.music = null; + } var bg:FunkinSprite = FunkinSprite.create('pauseAlt/pauseBG'); add(bg); diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 03681ce13..f16aa00d8 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -366,7 +366,7 @@ class PauseSubState extends MusicBeatSubState */ function changeSelection(change:Int = 0):Void { - FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); + FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); currentEntry += change; diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index a5152e727..a8cb879a3 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1,24 +1,15 @@ package funkin.play; -import funkin.audio.FunkinSound; -import flixel.addons.display.FlxPieDial; import flixel.addons.display.FlxPieDial; import flixel.addons.transition.FlxTransitionableState; -import flixel.addons.transition.FlxTransitionableState; -import flixel.addons.transition.FlxTransitionableSubState; -import flixel.addons.transition.FlxTransitionableSubState; -import flixel.addons.transition.Transition; import flixel.addons.transition.Transition; import flixel.FlxCamera; import flixel.FlxObject; import flixel.FlxState; -import funkin.graphics.FunkinSprite; import flixel.FlxSubState; -import funkin.graphics.FunkinSprite; import flixel.math.FlxMath; import flixel.math.FlxPoint; import flixel.math.FlxRect; -import funkin.graphics.FunkinSprite; import flixel.text.FlxText; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; @@ -26,18 +17,19 @@ import flixel.ui.FlxBar; import flixel.util.FlxColor; import flixel.util.FlxTimer; import funkin.api.newgrounds.NGio; -import funkin.audio.VoicesGroup; +import funkin.audio.FunkinSound; import funkin.audio.VoicesGroup; import funkin.data.dialogue.ConversationRegistry; import funkin.data.event.SongEventRegistry; import funkin.data.notestyle.NoteStyleData; import funkin.data.notestyle.NoteStyleRegistry; -import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.song.SongData.SongCharacterData; import funkin.data.song.SongData.SongEventData; import funkin.data.song.SongData.SongNoteData; import funkin.data.song.SongRegistry; import funkin.data.stage.StageRegistry; +import funkin.graphics.FunkinCamera; +import funkin.graphics.FunkinSprite; import funkin.Highscore.Tallies; import funkin.input.PreciseInputManager; import funkin.modding.events.ScriptEvent; @@ -48,14 +40,11 @@ import funkin.play.components.ComboMilestone; import funkin.play.components.HealthIcon; import funkin.play.components.PopUpStuff; import funkin.play.cutscene.dialogue.Conversation; -import funkin.play.cutscene.dialogue.Conversation; import funkin.play.cutscene.VanillaCutscenes; import funkin.play.cutscene.VideoCutscene; import funkin.play.notes.NoteDirection; import funkin.play.notes.NoteSplash; import funkin.play.notes.NoteSprite; -import funkin.play.notes.NoteSprite; -import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.Strumline; import funkin.play.notes.SustainTrail; @@ -69,7 +58,6 @@ import funkin.ui.mainmenu.MainMenuState; import funkin.ui.MusicBeatSubState; import funkin.ui.options.PreferencesMenu; import funkin.ui.story.StoryMenuState; -import funkin.graphics.FunkinCamera; import funkin.ui.transition.LoadingState; import funkin.util.SerializerUtil; import haxe.Int64; @@ -948,8 +936,8 @@ class PlayState extends MusicBeatSubState var pauseSubState:FlxSubState = new PauseSubState({mode: isChartingMode ? Charting : Standard}); - FlxTransitionableSubState.skipNextTransIn = true; - FlxTransitionableSubState.skipNextTransOut = true; + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; pauseSubState.camera = camHUD; openSubState(pauseSubState); // boyfriendPos.put(); // TODO: Why is this here? @@ -1072,8 +1060,8 @@ class PlayState extends MusicBeatSubState isChartingMode: isChartingMode, transparent: persistentDraw }); - FlxTransitionableSubState.skipNextTransIn = true; - FlxTransitionableSubState.skipNextTransOut = true; + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; openSubState(gameOverSubState); } @@ -1297,16 +1285,35 @@ class PlayState extends MusicBeatSubState currentStage = null; } - // Stop the instrumental. - if (FlxG.sound.music != null) + if (!overrideMusic) { - FlxG.sound.music.stop(); - } + // Stop the instrumental. + if (FlxG.sound.music != null) + { + FlxG.sound.music.destroy(); + FlxG.sound.music = null; + } - // Stop the vocals. - if (vocals != null && vocals.exists) + // Stop the vocals. + if (vocals != null && vocals.exists) + { + vocals.destroy(); + vocals = null; + } + } + else { - vocals.stop(); + // Stop the instrumental. + if (FlxG.sound.music != null) + { + FlxG.sound.music.stop(); + } + + // Stop the vocals. + if (vocals != null && vocals.exists) + { + vocals.stop(); + } } super.debug_refreshModules(); @@ -1357,7 +1364,10 @@ class PlayState extends MusicBeatSubState } // Only zoom camera if we are zoomed by less than 35%. - if (FlxG.camera.zoom < (1.35 * defaultCameraZoom) && cameraZoomRate > 0 && Conductor.instance.currentBeat % cameraZoomRate == 0) + if (Preferences.zoomCamera + && FlxG.camera.zoom < (1.35 * defaultCameraZoom) + && cameraZoomRate > 0 + && Conductor.instance.currentBeat % cameraZoomRate == 0) { // Zoom camera in (1.5%) currentCameraZoom += cameraZoomIntensity * defaultCameraZoom; @@ -1550,6 +1560,7 @@ class PlayState extends MusicBeatSubState public function resetCameraZoom():Void { + if (PlayState.instance.isMinimalMode) return; // Apply camera zoom level from stage data. defaultCameraZoom = currentStage.camZoom; currentCameraZoom = defaultCameraZoom; @@ -1902,6 +1913,12 @@ class PlayState extends MusicBeatSubState currentChart.playInst(1.0, false); } + if (FlxG.sound.music == null) + { + FlxG.log.error('PlayState failed to initialize instrumental!'); + return; + } + FlxG.sound.music.onComplete = endSong.bind(false); // A negative instrumental offset means the song skips the first few milliseconds of the track. // This just gets added into the startTimestamp behavior so we don't need to do anything extra. @@ -2092,8 +2109,7 @@ class PlayState extends MusicBeatSubState holdNote.handledMiss = true; // We dropped a hold note. - // Mute vocals and play miss animation, but don't penalize. - vocals.opponentVolume = 0; + // Play miss animation, but don't penalize. currentStage.getOpponent().playSingAnimation(holdNote.noteData.getDirection(), true); } } @@ -2431,7 +2447,7 @@ class PlayState extends MusicBeatSubState if (playSound) { vocals.playerVolume = 0; - FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2)); + FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.5, 0.6)); } } @@ -2486,7 +2502,7 @@ class PlayState extends MusicBeatSubState if (event.playSound) { vocals.playerVolume = 0; - FlxG.sound.play(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2)); + FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2)); } } @@ -2667,8 +2683,8 @@ class PlayState extends MusicBeatSubState var pauseSubState:FlxSubState = new PauseSubState({mode: Conversation}); persistentUpdate = false; - FlxTransitionableSubState.skipNextTransIn = true; - FlxTransitionableSubState.skipNextTransOut = true; + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; pauseSubState.camera = camCutscene; openSubState(pauseSubState); } @@ -2683,8 +2699,8 @@ class PlayState extends MusicBeatSubState var pauseSubState:FlxSubState = new PauseSubState({mode: Cutscene}); persistentUpdate = false; - FlxTransitionableSubState.skipNextTransIn = true; - FlxTransitionableSubState.skipNextTransOut = true; + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; pauseSubState.camera = camCutscene; openSubState(pauseSubState); } @@ -2769,7 +2785,11 @@ class PlayState extends MusicBeatSubState if (targetSongId == null) { - FunkinSound.playMusic('freakyMenu'); + FunkinSound.playMusic('freakyMenu', + { + overrideExisting: true, + restartTrack: false + }); // transIn = FlxTransitionableState.defaultTransIn; // transOut = FlxTransitionableState.defaultTransOut; @@ -2847,7 +2867,7 @@ class PlayState extends MusicBeatSubState camHUD.visible = false; isInCutscene = true; - FlxG.sound.play(Paths.sound('Lights_Shut_off'), function() { + FunkinSound.playOnce(Paths.sound('Lights_Shut_off'), function() { // no camFollow so it centers on horror tree var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId); LoadingState.loadPlayState( diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index a78d61583..c695b1db4 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -13,6 +13,7 @@ import flixel.text.FlxBitmapText; import flixel.tweens.FlxEase; import funkin.ui.freeplay.FreeplayState; import flixel.tweens.FlxTween; +import funkin.audio.FunkinSound; import flixel.util.FlxGradient; import flixel.util.FlxTimer; import funkin.graphics.shaders.LeftMaskShader; @@ -48,9 +49,13 @@ class ResultState extends MusicBeatSubState else resultsVariation = NORMAL; - var loops:Bool = resultsVariation != SHIT; - - FlxG.sound.playMusic(Paths.music("results" + resultsVariation), 1, loops); + FunkinSound.playMusic('results$resultsVariation', + { + startingVolume: 1.0, + overrideExisting: true, + restartTrack: true, + loop: resultsVariation != SHIT + }); // TEMP-ish, just used to sorta "cache" the 3000x3000 image! var cacheBullShit:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/soundSystem")); @@ -348,9 +353,12 @@ class ResultState extends MusicBeatSubState if (controls.PAUSE) { FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8); - FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1, {onComplete: _ -> { - FlxTween.tween(FlxG.sound.music, {pitch: 0.5}, 0.4); - }}); + FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1, + { + onComplete: _ -> { + FlxTween.tween(FlxG.sound.music, {pitch: 0.5}, 0.4); + } + }); if (params.storyMode) { openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker))); diff --git a/source/funkin/play/components/ComboMilestone.hx b/source/funkin/play/components/ComboMilestone.hx index 4119e45c2..22ce2d671 100644 --- a/source/funkin/play/components/ComboMilestone.hx +++ b/source/funkin/play/components/ComboMilestone.hx @@ -4,6 +4,7 @@ import flixel.FlxSprite; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import flixel.util.FlxTimer; +import funkin.audio.FunkinSound; class ComboMilestone extends FlxTypedSpriteGroup { @@ -78,7 +79,7 @@ class ComboMilestone extends FlxTypedSpriteGroup function setupCombo(daCombo:Int) { - FlxG.sound.play(Paths.sound('comboSound')); + FunkinSound.playOnce(Paths.sound('comboSound')); wasComboSetup = true; var loopNum:Int = 0; diff --git a/source/funkin/play/cutscene/VanillaCutscenes.hx b/source/funkin/play/cutscene/VanillaCutscenes.hx index a332d0795..7a4349e6a 100644 --- a/source/funkin/play/cutscene/VanillaCutscenes.hx +++ b/source/funkin/play/cutscene/VanillaCutscenes.hx @@ -4,6 +4,7 @@ import flixel.FlxSprite; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.util.FlxColor; +import funkin.audio.FunkinSound; import flixel.util.FlxTimer; /** @@ -40,7 +41,7 @@ class VanillaCutscenes FlxG.camera.zoom = 2.5; // Play the Sound effect. - FlxG.sound.play(Paths.sound('Lights_Turn_On'), function() { + FunkinSound.playOnce(Paths.sound('Lights_Turn_On'), function() { // Fade in the HUD. trace('SFX done...'); PlayState.instance.camHUD.visible = true; diff --git a/source/funkin/play/cutscene/VideoCutscene.hx b/source/funkin/play/cutscene/VideoCutscene.hx index ff56e0919..3da51185f 100644 --- a/source/funkin/play/cutscene/VideoCutscene.hx +++ b/source/funkin/play/cutscene/VideoCutscene.hx @@ -5,6 +5,7 @@ import flixel.FlxSprite; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; import flixel.util.FlxColor; +import flixel.util.FlxSignal; import flixel.util.FlxTimer; #if html5 import funkin.graphics.video.FlxVideo; @@ -28,6 +29,31 @@ class VideoCutscene static var vid:FlxVideoSprite; #end + /** + * Called when the video is started. + */ + public static final onVideoStarted:FlxSignal = new FlxSignal(); + + /** + * Called if the video is paused. + */ + public static final onVideoPaused:FlxSignal = new FlxSignal(); + + /** + * Called if the video is resumed. + */ + public static final onVideoResumed:FlxSignal = new FlxSignal(); + + /** + * Called if the video is restarted. onVideoStarted is not called. + */ + public static final onVideoRestarted:FlxSignal = new FlxSignal(); + + /** + * Called when the video is ended or skipped. + */ + public static final onVideoEnded:FlxSignal = new FlxSignal(); + /** * Play a video cutscene. * TODO: Currently this is hardcoded to start the countdown after the video is done. @@ -94,6 +120,8 @@ class VideoCutscene PlayState.instance.add(vid); PlayState.instance.refresh(); + + onVideoStarted.dispatch(); } else { @@ -129,6 +157,8 @@ class VideoCutscene vid.y = 0; // vid.scale.set(0.5, 0.5); }); + + onVideoStarted.dispatch(); } else { @@ -143,6 +173,7 @@ class VideoCutscene if (vid != null) { vid.restartVideo(); + onVideoRestarted.dispatch(); } #end @@ -156,6 +187,8 @@ class VideoCutscene // Resume the video if it was paused. vid.resume(); } + + onVideoRestarted.dispatch(); } #end } @@ -166,6 +199,7 @@ class VideoCutscene if (vid != null) { vid.pauseVideo(); + onVideoPaused.dispatch(); } #end @@ -173,6 +207,45 @@ class VideoCutscene if (vid != null) { vid.pause(); + onVideoPaused.dispatch(); + } + #end + } + + public static function hideVideo():Void + { + #if html5 + if (vid != null) + { + vid.visible = false; + blackScreen.visible = false; + } + #end + + #if hxCodec + if (vid != null) + { + vid.visible = false; + blackScreen.visible = false; + } + #end + } + + public static function showVideo():Void + { + #if html5 + if (vid != null) + { + vid.visible = true; + blackScreen.visible = false; + } + #end + + #if hxCodec + if (vid != null) + { + vid.visible = true; + blackScreen.visible = false; } #end } @@ -183,6 +256,7 @@ class VideoCutscene if (vid != null) { vid.resumeVideo(); + onVideoResumed.dispatch(); } #end @@ -190,6 +264,7 @@ class VideoCutscene if (vid != null) { vid.resume(); + onVideoResumed.dispatch(); } #end } @@ -240,6 +315,7 @@ class VideoCutscene { ease: FlxEase.quadInOut, onComplete: function(twn:FlxTween) { + onVideoEnded.dispatch(); onCutsceneFinish(cutsceneType); } }); diff --git a/source/funkin/play/cutscene/dialogue/Conversation.hx b/source/funkin/play/cutscene/dialogue/Conversation.hx index f865f3b7b..c520c3e25 100644 --- a/source/funkin/play/cutscene/dialogue/Conversation.hx +++ b/source/funkin/play/cutscene/dialogue/Conversation.hx @@ -1,28 +1,28 @@ package funkin.play.cutscene.dialogue; -import funkin.data.IRegistryEntry; +import flixel.addons.display.FlxPieDial; import flixel.FlxSprite; import flixel.group.FlxSpriteGroup; -import flixel.util.FlxColor; -import funkin.graphics.FunkinSprite; -import flixel.tweens.FlxTween; import flixel.tweens.FlxEase; -import flixel.sound.FlxSound; -import funkin.util.SortUtil; +import flixel.tweens.FlxTween; +import flixel.util.FlxColor; import flixel.util.FlxSort; -import funkin.modding.events.ScriptEvent; -import funkin.modding.IScriptedClass.IEventHandler; -import funkin.play.cutscene.dialogue.DialogueBox; -import funkin.modding.IScriptedClass.IDialogueScriptedClass; -import funkin.modding.events.ScriptEventDispatcher; -import flixel.addons.display.FlxPieDial; +import funkin.audio.FunkinSound; import funkin.data.dialogue.ConversationData; import funkin.data.dialogue.ConversationData.DialogueEntryData; import funkin.data.dialogue.ConversationRegistry; -import funkin.data.dialogue.SpeakerData; -import funkin.data.dialogue.SpeakerRegistry; import funkin.data.dialogue.DialogueBoxData; import funkin.data.dialogue.DialogueBoxRegistry; +import funkin.data.dialogue.SpeakerData; +import funkin.data.dialogue.SpeakerRegistry; +import funkin.data.IRegistryEntry; +import funkin.graphics.FunkinSprite; +import funkin.modding.events.ScriptEvent; +import funkin.modding.events.ScriptEventDispatcher; +import funkin.modding.IScriptedClass.IDialogueScriptedClass; +import funkin.modding.IScriptedClass.IEventHandler; +import funkin.play.cutscene.dialogue.DialogueBox; +import funkin.util.SortUtil; /** * A high-level handler for dialogue. @@ -90,7 +90,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl /** * AUDIO */ - var music:FlxSound; + var music:FunkinSound; /** * GRAPHICS @@ -129,8 +129,7 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl { if (_data.music == null) return; - music = new FlxSound().loadEmbedded(Paths.music(_data.music.asset), true, true); - music.volume = 0; + music = FunkinSound.load(Paths.music(_data.music.asset), 0.0, true, true, true); if (_data.music.fadeTime > 0.0) { @@ -140,9 +139,6 @@ class Conversation extends FlxSpriteGroup implements IDialogueScriptedClass impl { music.volume = 1.0; } - - FlxG.sound.list.add(music); - music.play(); } public function pauseMusic():Void diff --git a/source/funkin/play/event/ZoomCameraSongEvent.hx b/source/funkin/play/event/ZoomCameraSongEvent.hx index d1741a463..b913aebe7 100644 --- a/source/funkin/play/event/ZoomCameraSongEvent.hx +++ b/source/funkin/play/event/ZoomCameraSongEvent.hx @@ -52,6 +52,11 @@ class ZoomCameraSongEvent extends SongEvent super('ZoomCamera'); } + static final DEFAULT_ZOOM:Float = 1.0; + static final DEFAULT_DURATION:Float = 4.0; + static final DEFAULT_MODE:String = 'direct'; + static final DEFAULT_EASE:String = 'linear'; + public override function handleEvent(data:SongEventData):Void { // Does nothing if there is no PlayState camera or stage. @@ -60,25 +65,20 @@ class ZoomCameraSongEvent extends SongEvent // Does nothing if we are minimal mode. if (PlayState.instance.isMinimalMode) return; - var zoom:Null = data.getFloat('zoom'); - if (zoom == null) zoom = 1.0; + var zoom:Float = data.getFloat('zoom') ?? DEFAULT_ZOOM; - var duration:Null = data.getFloat('duration'); - if (duration == null) duration = 4.0; + var duration:Float = data.getFloat('duration') ?? DEFAULT_DURATION; - var mode:Null = data.getString('mode'); - if (mode == null) mode = 'additive'; + var mode:String = data.getString('mode') ?? DEFAULT_MODE; + var isDirectMode:Bool = mode == 'direct'; - var ease:Null = data.getString('ease'); - if (ease == null) ease = 'linear'; - - var directMode:Bool = mode == 'direct'; + var ease:String = data.getString('ease') ?? DEFAULT_EASE; // If it's a string, check the value. switch (ease) { case 'INSTANT': - PlayState.instance.tweenCameraZoom(zoom, 0, directMode); + PlayState.instance.tweenCameraZoom(zoom, 0, isDirectMode); default: var durSeconds = Conductor.instance.stepLengthMs * duration / 1000; @@ -89,7 +89,7 @@ class ZoomCameraSongEvent extends SongEvent return; } - PlayState.instance.tweenCameraZoom(zoom, durSeconds, directMode, easeFunction); + PlayState.instance.tweenCameraZoom(zoom, durSeconds, isDirectMode, easeFunction); } } @@ -130,7 +130,7 @@ class ZoomCameraSongEvent extends SongEvent { name: 'mode', title: 'Mode', - defaultValue: 'additive', + defaultValue: 'direct', type: SongEventFieldType.ENUM, keys: ['Additive' => 'additive', 'Direct' => 'direct'] }, diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index b0b477ff4..0248e09ee 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -383,12 +383,17 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry):Null { - if (variations == null) possibleVariations = variations; + if (possibleVariations == null) + { + possibleVariations = variations; + possibleVariations.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_VARIATION_LIST)); + } if (diffId == null) diffId = listDifficulties(null, possibleVariations)[0]; - for (variation in variations) + for (variationId in possibleVariations) { - if (difficulties.exists('$diffId-$variation')) return variation; + var variationSuffix = (variationId != Constants.DEFAULT_VARIATION) ? '-$variationId' : ''; + if (difficulties.exists('$diffId$variationSuffix')) return variationId; } return null; diff --git a/source/funkin/ui/Alphabet.hx b/source/funkin/ui/Alphabet.hx index 66b95f5b8..e0492bee5 100644 --- a/source/funkin/ui/Alphabet.hx +++ b/source/funkin/ui/Alphabet.hx @@ -5,6 +5,7 @@ import flixel.group.FlxSpriteGroup; import flixel.math.FlxMath; import flixel.util.FlxTimer; import funkin.util.MathUtil; +import funkin.audio.FunkinSound; /** * Loosley based on FlxTypeText lolol @@ -200,7 +201,7 @@ class Alphabet extends FlxSpriteGroup if (FlxG.random.bool(40)) { var daSound:String = "GF_"; - FlxG.sound.play(Paths.soundRandom(daSound, 1, 4)); + FunkinSound.playOnce(Paths.soundRandom(daSound, 1, 4)); } add(letter); diff --git a/source/funkin/ui/MenuList.hx b/source/funkin/ui/MenuList.hx index 3ffe3c330..63a688778 100644 --- a/source/funkin/ui/MenuList.hx +++ b/source/funkin/ui/MenuList.hx @@ -5,6 +5,7 @@ import flixel.effects.FlxFlicker; import flixel.group.FlxGroup; import flixel.math.FlxPoint; import flixel.util.FlxSignal; +import funkin.audio.FunkinSound; class MenuTypedList extends FlxTypedGroup { @@ -93,7 +94,7 @@ class MenuTypedList extends FlxTypedGroup if (newIndex != selectedIndex) { - FlxG.sound.play(Paths.sound('scrollMenu')); + FunkinSound.playOnce(Paths.sound('scrollMenu')); selectItem(newIndex); } @@ -163,7 +164,7 @@ class MenuTypedList extends FlxTypedGroup else { busy = true; - FlxG.sound.play(Paths.sound('confirmMenu')); + FunkinSound.playOnce(Paths.sound('confirmMenu')); FlxFlicker.flicker(selected, 1, 0.06, true, false, function(_) { busy = false; selected.callback(); diff --git a/source/funkin/ui/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx index 98197a0e7..2cdc747ef 100644 --- a/source/funkin/ui/MusicBeatState.hx +++ b/source/funkin/ui/MusicBeatState.hx @@ -7,6 +7,7 @@ import flixel.FlxSubState; import flixel.addons.transition.FlxTransitionableState; import flixel.text.FlxText; import flixel.util.FlxColor; +import funkin.audio.FunkinSound; import flixel.util.FlxSort; import funkin.modding.PolymodHandler; import funkin.modding.events.ScriptEvent; @@ -151,6 +152,8 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler } else { + FunkinSound.stopAllAudio(); + onComplete(); } } diff --git a/source/funkin/ui/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx index dc742874f..e9effdbee 100644 --- a/source/funkin/ui/MusicBeatSubState.hx +++ b/source/funkin/ui/MusicBeatSubState.hx @@ -1,6 +1,6 @@ package funkin.ui; -import flixel.addons.transition.FlxTransitionableSubState; +import flixel.addons.transition.FlxTransitionableState; import flixel.FlxSubState; import flixel.text.FlxText; import funkin.ui.mainmenu.MainMenuState; diff --git a/source/funkin/ui/debug/DebugMenuSubState.hx b/source/funkin/ui/debug/DebugMenuSubState.hx index 375fb8f5c..56a05eb86 100644 --- a/source/funkin/ui/debug/DebugMenuSubState.hx +++ b/source/funkin/ui/debug/DebugMenuSubState.hx @@ -4,6 +4,7 @@ import flixel.math.FlxPoint; import flixel.FlxObject; import flixel.FlxSprite; import funkin.ui.MusicBeatSubState; +import funkin.audio.FunkinSound; import funkin.ui.TextMenuList; import funkin.ui.debug.charting.ChartEditorState; import funkin.ui.MusicBeatSubState; @@ -71,7 +72,7 @@ class DebugMenuSubState extends MusicBeatSubState if (controls.BACK) { - FlxG.sound.play(Paths.sound('cancelMenu')); + FunkinSound.playOnce(Paths.sound('cancelMenu')); exitDebugMenu(); } } diff --git a/source/funkin/ui/debug/anim/DebugBoundingState.hx b/source/funkin/ui/debug/anim/DebugBoundingState.hx index 46a095a65..86e84117a 100644 --- a/source/funkin/ui/debug/anim/DebugBoundingState.hx +++ b/source/funkin/ui/debug/anim/DebugBoundingState.hx @@ -12,7 +12,6 @@ import flixel.graphics.frames.FlxAtlasFrames; import flixel.graphics.frames.FlxFrame; import flixel.group.FlxGroup; import flixel.math.FlxPoint; -import flixel.sound.FlxSound; import flixel.text.FlxText; import flixel.util.FlxColor; import funkin.util.MouseUtil; @@ -179,7 +178,7 @@ class DebugBoundingState extends FlxState var objShit = js.html.URL.createObjectURL(swagList.item(0)); trace(objShit); - var funnysound = new FlxSound().loadStream('https://cdn.discordapp.com/attachments/767500676166451231/817821618251759666/Flutter.mp3', false, false, + var funnysound = new FunkinSound().loadStream('https://cdn.discordapp.com/attachments/767500676166451231/817821618251759666/Flutter.mp3', false, false, null, function() { trace('LOADED SHIT??'); }); diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 565488da3..888398f34 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -15,7 +15,6 @@ import flixel.input.mouse.FlxMouseEvent; import flixel.math.FlxMath; import flixel.math.FlxPoint; import flixel.math.FlxRect; -import flixel.sound.FlxSound; import flixel.system.debug.log.LogStyle; import flixel.system.FlxAssets.FlxSoundAsset; import flixel.text.FlxText; @@ -1091,7 +1090,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState * The chill audio track that plays in the chart editor. * Plays when the main music is NOT being played. */ - var welcomeMusic:FlxSound = new FlxSound(); + var welcomeMusic:FunkinSound = new FunkinSound(); /** * The audio track for the instrumental. @@ -3888,8 +3887,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState function handleCursor():Void { // Mouse sounds - if (FlxG.mouse.justPressed) FlxG.sound.play(Paths.sound("chartingSounds/ClickDown")); - if (FlxG.mouse.justReleased) FlxG.sound.play(Paths.sound("chartingSounds/ClickUp")); + if (FlxG.mouse.justPressed) FunkinSound.playOnce(Paths.sound("chartingSounds/ClickDown")); + if (FlxG.mouse.justReleased) FunkinSound.playOnce(Paths.sound("chartingSounds/ClickUp")); // Note: If a menu is open in HaxeUI, don't handle cursor behavior. var shouldHandleCursor:Bool = !(isHaxeUIFocused || playbarHeadDragging || isHaxeUIDialogOpen) diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 1e1d165f3..26e246371 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -1,7 +1,6 @@ package funkin.ui.debug.charting.handlers; import flixel.system.FlxAssets.FlxSoundAsset; -import flixel.sound.FlxSound; import funkin.audio.VoicesGroup; import funkin.audio.FunkinSound; import funkin.play.character.BaseCharacter.CharacterType; @@ -302,7 +301,8 @@ class ChartEditorAudioHandler trace('WARN: Failed to play sound $path, asset not found.'); return; } - var snd:FunkinSound = FunkinSound.load(asset); + var snd:Null = FunkinSound.load(asset); + if (snd == null) return; snd.autoDestroy = true; snd.play(true); snd.volume = volume; diff --git a/source/funkin/ui/debug/latency/LatencyState.hx b/source/funkin/ui/debug/latency/LatencyState.hx index 9ebd29537..7b2eabb1c 100644 --- a/source/funkin/ui/debug/latency/LatencyState.hx +++ b/source/funkin/ui/debug/latency/LatencyState.hx @@ -71,8 +71,6 @@ class LatencyState extends MusicBeatSubState // trace("EVENT LISTENER: " + key); }); - // FlxG.sound.playMusic(Paths.sound('soundTest')); - // funnyStatsGraph.hi Conductor.instance.forceBPM(60); @@ -242,13 +240,6 @@ class LatencyState extends MusicBeatSubState } } - /* if (FlxG.keys.justPressed.SPACE) - { - FlxG.sound.music.stop(); - - FlxG.resetState(); - }*/ - noteGrp.forEach(function(daNote:NoteSprite) { daNote.y = (strumLine.y - ((Conductor.instance.songPosition - Conductor.instance.instrumentalOffset) - daNote.noteData.time) * 0.45); daNote.x = strumLine.x + 30; diff --git a/source/funkin/ui/debug/stage/StageBuilderState.hx b/source/funkin/ui/debug/stage/StageBuilderState.hx index 074914f58..b556b9fde 100644 --- a/source/funkin/ui/debug/stage/StageBuilderState.hx +++ b/source/funkin/ui/debug/stage/StageBuilderState.hx @@ -37,8 +37,6 @@ class StageBuilderState extends MusicBeatState FlxG.mouse.visible = true; - // var alsoSnd:FlxSound = new FlxSound(); - // snd = new Sound(); // var swagBytes:ByteArray = new ByteArray(8192); diff --git a/source/funkin/ui/freeplay/DJBoyfriend.hx b/source/funkin/ui/freeplay/DJBoyfriend.hx index 55f43d2ef..8cd63dba1 100644 --- a/source/funkin/ui/freeplay/DJBoyfriend.hx +++ b/source/funkin/ui/freeplay/DJBoyfriend.hx @@ -4,8 +4,8 @@ import flixel.FlxSprite; import flixel.util.FlxSignal; import funkin.util.assets.FlxAnimationUtil; import funkin.graphics.adobeanimate.FlxAtlasSprite; -import flixel.sound.FlxSound; import flixel.util.FlxTimer; +import funkin.audio.FunkinSound; import funkin.audio.FlxStreamSound; class DJBoyfriend extends FlxAtlasSprite @@ -178,7 +178,7 @@ class DJBoyfriend extends FlxAtlasSprite if (cartoonSnd == null) { // tv is OFF, but getting turned on - FlxG.sound.play(Paths.sound('tv_on')); + FunkinSound.playOnce(Paths.sound('tv_on')); cartoonSnd = new FlxStreamSound(); FlxG.sound.defaultSoundGroup.add(cartoonSnd); @@ -187,7 +187,7 @@ class DJBoyfriend extends FlxAtlasSprite { // plays it smidge after the click new FlxTimer().start(0.1, function(_) { - FlxG.sound.play(Paths.sound('channel_switch')); + FunkinSound.playOnce(Paths.sound('channel_switch')); }); } // cartoonSnd.loadEmbedded(Paths.sound("cartoons/peck")); diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index fc11eec28..f7554197f 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -174,7 +174,11 @@ class FreeplayState extends MusicBeatSubState isDebug = true; #end - FunkinSound.playMusic('freakyMenu'); + FunkinSound.playMusic('freakyMenu', + { + overrideExisting: true, + restartTrack: false + }); // Add a null entry that represents the RANDOM option songs.push(null); @@ -867,7 +871,7 @@ class FreeplayState extends MusicBeatSubState FlxTimer.globalManager.clear(); dj.onIntroDone.removeAll(); - FlxG.sound.play(Paths.sound('cancelMenu')); + FunkinSound.playOnce(Paths.sound('cancelMenu')); var longestTimer:Float = 0; @@ -1013,7 +1017,14 @@ class FreeplayState extends MusicBeatSubState // Set the difficulty star count on the right. albumRoll.setDifficultyStars(daSong?.songRating); - albumRoll.albumId = daSong?.albumId ?? Constants.DEFAULT_ALBUM_ID; + + // Set the album graphic and play the animation if relevant. + var newAlbumId:String = daSong?.albumId ?? Constants.DEFAULT_ALBUM_ID; + if (albumRoll.albumId != newAlbumId) + { + albumRoll.albumId = newAlbumId; + albumRoll.playIntro(); + } } // Clears the cache of songs, frees up memory, they' ll have to be loaded in later tho function clearDaCache(actualSongTho:String) @@ -1051,7 +1062,7 @@ class FreeplayState extends MusicBeatSubState trace('No songs available!'); busy = false; letterSort.inputEnabled = true; - FlxG.sound.play(Paths.sound('cancelMenu')); + FunkinSound.playOnce(Paths.sound('cancelMenu')); return; } @@ -1084,7 +1095,7 @@ class FreeplayState extends MusicBeatSubState PlayStatePlaylist.campaignId = cap.songData.levelId; // Visual and audio effects. - FlxG.sound.play(Paths.sound('confirmMenu')); + FunkinSound.playOnce(Paths.sound('confirmMenu')); dj.confirm(); new FlxTimer().start(1, function(tmr:FlxTimer) { @@ -1126,8 +1137,7 @@ class FreeplayState extends MusicBeatSubState function changeSelection(change:Int = 0):Void { - FlxG.sound.play(Paths.sound('scrollMenu'), 0.4); - // FlxG.sound.playMusic(Paths.inst(songs[curSelected].songName)); + FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); var prevSelected:Int = curSelected; @@ -1170,15 +1180,25 @@ class FreeplayState extends MusicBeatSubState { if (curSelected == 0) { - FlxG.sound.playMusic(Paths.music('freeplay/freeplayRandom'), 0); + FunkinSound.playMusic('freeplayRandom', + { + startingVolume: 0.0, + overrideExisting: true, + restartTrack: true + }); FlxG.sound.music.fadeIn(2, 0, 0.8); } else { // TODO: Stream the instrumental of the selected song? - if (prevSelected == 0) + var didReplace:Bool = FunkinSound.playMusic('freakyMenu', + { + startingVolume: 0.0, + overrideExisting: true, + restartTrack: false + }); + if (didReplace) { - FunkinSound.playMusic('freakyMenu'); FlxG.sound.music.fadeIn(2, 0, 0.8); } } diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index 1892bdec1..a8c2039ab 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -1,6 +1,6 @@ package funkin.ui.mainmenu; -import flixel.addons.transition.FlxTransitionableSubState; +import flixel.addons.transition.FlxTransitionableState; import funkin.ui.debug.DebugMenuSubState; import flixel.FlxObject; import flixel.FlxSprite; @@ -103,8 +103,8 @@ class MainMenuState extends MusicBeatState persistentDraw = true; persistentUpdate = false; // Freeplay has its own custom transition - FlxTransitionableSubState.skipNextTransIn = true; - FlxTransitionableSubState.skipNextTransOut = true; + FlxTransitionableState.skipNextTransIn = true; + FlxTransitionableState.skipNextTransOut = true; openSubState(new FreeplayState()); }); @@ -155,7 +155,11 @@ class MainMenuState extends MusicBeatState function playMenuMusic():Void { - FunkinSound.playMusic('freakyMenu'); + FunkinSound.playMusic('freakyMenu', + { + overrideExisting: true, + restartTrack: false + }); } function resetCamStuff() @@ -321,7 +325,7 @@ class MainMenuState extends MusicBeatState if (controls.BACK && menuItems.enabled && !menuItems.busy) { - FlxG.sound.play(Paths.sound('cancelMenu')); + FunkinSound.playOnce(Paths.sound('cancelMenu')); FlxG.switchState(() -> new TitleState()); } } diff --git a/source/funkin/ui/options/OptionsState.hx b/source/funkin/ui/options/OptionsState.hx index 7b233f03d..0f33a0780 100644 --- a/source/funkin/ui/options/OptionsState.hx +++ b/source/funkin/ui/options/OptionsState.hx @@ -5,9 +5,11 @@ import flixel.FlxSubState; import flixel.addons.transition.FlxTransitionableState; import flixel.group.FlxGroup; import flixel.util.FlxSignal; +import funkin.audio.FunkinSound; import funkin.ui.mainmenu.MainMenuState; import funkin.ui.MusicBeatState; import funkin.util.WindowUtil; +import funkin.audio.FunkinSound; import funkin.input.Controls; class OptionsState extends MusicBeatState @@ -143,7 +145,7 @@ class Page extends FlxGroup { if (canExit && controls.BACK) { - FlxG.sound.play(Paths.sound('cancelMenu')); + FunkinSound.playOnce(Paths.sound('cancelMenu')); exit(); } } diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index a5e133725..d3aa68c49 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -231,7 +231,11 @@ class StoryMenuState extends MusicBeatState function playMenuMusic():Void { - FunkinSound.playMusic('freakyMenu'); + FunkinSound.playMusic('freakyMenu', + { + overrideExisting: true, + restartTrack: false + }); } function updateData():Void @@ -382,7 +386,7 @@ class StoryMenuState extends MusicBeatState if (controls.BACK && !exitingMenu && !selectedLevel) { - FlxG.sound.play(Paths.sound('cancelMenu')); + FunkinSound.playOnce(Paths.sound('cancelMenu')); exitingMenu = true; FlxG.switchState(() -> new MainMenuState()); } @@ -515,7 +519,7 @@ class StoryMenuState extends MusicBeatState { if (!currentLevel.isUnlocked()) { - FlxG.sound.play(Paths.sound('cancelMenu')); + FunkinSound.playOnce(Paths.sound('cancelMenu')); return; } @@ -523,7 +527,7 @@ class StoryMenuState extends MusicBeatState selectedLevel = true; - FlxG.sound.play(Paths.sound('confirmMenu')); + FunkinSound.playOnce(Paths.sound('confirmMenu')); currentLevelTitle.isFlashing = true; diff --git a/source/funkin/ui/title/AttractState.hx b/source/funkin/ui/title/AttractState.hx index 63a9e63ad..0af97afd9 100644 --- a/source/funkin/ui/title/AttractState.hx +++ b/source/funkin/ui/title/AttractState.hx @@ -22,7 +22,11 @@ class AttractState extends MusicBeatState public override function create():Void { // Pause existing music. - FlxG.sound.music.stop(); + if (FlxG.sound.music != null) + { + FlxG.sound.music.destroy(); + FlxG.sound.music = null; + } #if html5 playVideoHTML5(ATTRACT_VIDEO_PATH); diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index 9dca759be..1a4e13ab1 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -222,10 +222,14 @@ class TitleState extends MusicBeatState { var shouldFadeIn = (FlxG.sound.music == null); // Load music. Includes logic to handle BPM changes. - FunkinSound.playMusic('freakyMenu', false, true); - FlxG.sound.music.volume = 0; + FunkinSound.playMusic('freakyMenu', + { + startingVolume: 0.0, + overrideExisting: true, + restartTrack: true + }); // Fade from 0.0 to 0.7 over 4 seconds - if (shouldFadeIn) FlxG.sound.music.fadeIn(4, 0, 0.7); + if (shouldFadeIn) FlxG.sound.music.fadeIn(4.0, 0.0, 0.7); } function getIntroTextShit():Array> @@ -323,7 +327,7 @@ class TitleState extends MusicBeatState if (Date.now().getDay() == 5) NGio.unlockMedal(61034); titleText.animation.play('press'); FlxG.camera.flash(FlxColor.WHITE, 1); - FlxG.sound.play(Paths.sound('confirmMenu'), 0.7); + FunkinSound.playOnce(Paths.sound('confirmMenu'), 0.7); transitioning = true; var targetState:NextState = () -> new MainMenuState(); @@ -338,7 +342,7 @@ class TitleState extends MusicBeatState // ngSpr?? FlxG.switchState(targetState); }); - // FlxG.sound.play(Paths.music('titleShoot'), 0.7); + // FunkinSound.playOnce(Paths.music('titleShoot'), 0.7); } if (pressedEnter && !skippedIntro && initialized) skipIntro(); @@ -385,14 +389,12 @@ class TitleState extends MusicBeatState { cheatActive = true; - FlxG.sound.playMusic(Paths.music('tutorialTitle'), 1); - var spec:SpectogramSprite = new SpectogramSprite(FlxG.sound.music); add(spec); Conductor.instance.forceBPM(190); FlxG.camera.flash(FlxColor.WHITE, 1); - FlxG.sound.play(Paths.sound('confirmMenu'), 0.7); + FunkinSound.playOnce(Paths.sound('confirmMenu'), 0.7); } function createCoolText(textArray:Array) @@ -453,9 +455,9 @@ class TitleState extends MusicBeatState switch (i + 1) { case 1: - createCoolText(['ninjamuffin99', 'phantomArcade', 'kawaisprite', 'evilsk8r']); + createCoolText(['The', 'Funkin Crew Inc']); case 3: - addMoreText('present'); + addMoreText('presents'); case 4: deleteCoolText(); case 5: diff --git a/source/funkin/ui/transition/LoadingState.hx b/source/funkin/ui/transition/LoadingState.hx index 23b3db6a9..980c264e3 100644 --- a/source/funkin/ui/transition/LoadingState.hx +++ b/source/funkin/ui/transition/LoadingState.hx @@ -171,7 +171,12 @@ class LoadingState extends MusicBeatState function onLoad():Void { - if (stopMusic && FlxG.sound.music != null) FlxG.sound.music.stop(); + // Stop the instrumental. + if (stopMusic && FlxG.sound.music != null) + { + FlxG.sound.music.destroy(); + FlxG.sound.music = null; + } FlxG.switchState(target); } @@ -200,7 +205,8 @@ class LoadingState extends MusicBeatState // All assets preloaded, switch directly to play state (defualt on other targets). if (shouldStopMusic && FlxG.sound.music != null) { - FlxG.sound.music.stop(); + FlxG.sound.music.destroy(); + FlxG.sound.music = null; } // Load and cache the song's charts. diff --git a/source/funkin/ui/transition/StickerSubState.hx b/source/funkin/ui/transition/StickerSubState.hx index 981a30e09..0b5e16f97 100644 --- a/source/funkin/ui/transition/StickerSubState.hx +++ b/source/funkin/ui/transition/StickerSubState.hx @@ -18,6 +18,7 @@ import flixel.addons.transition.FlxTransitionableState; import openfl.display.BitmapData; import funkin.ui.freeplay.FreeplayState; import openfl.geom.Matrix; +import funkin.audio.FunkinSound; import openfl.display.Sprite; import openfl.display.Bitmap; import flixel.FlxState; @@ -137,7 +138,7 @@ class StickerSubState extends MusicBeatSubState new FlxTimer().start(sticker.timing, _ -> { sticker.visible = false; var daSound:String = FlxG.random.getObject(sounds); - FlxG.sound.play(Paths.sound(daSound)); + FunkinSound.playOnce(Paths.sound(daSound)); if (grpStickers == null || ind == grpStickers.members.length - 1) { @@ -227,7 +228,7 @@ class StickerSubState extends MusicBeatSubState sticker.visible = true; var daSound:String = FlxG.random.getObject(sounds); - FlxG.sound.play(Paths.sound(daSound)); + FunkinSound.playOnce(Paths.sound(daSound)); var frameTimer:Int = FlxG.random.int(0, 2); diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index c9b99ed46..c7bc03139 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -157,6 +157,11 @@ class Constants */ public static final DEFAULT_VARIATION:String = 'default'; + /** + * Standard variations used by the game. + */ + public static final DEFAULT_VARIATION_LIST:Array = ['default', 'erect', 'pico']; + /** * The default intensity for camera zooms. */ diff --git a/source/funkin/util/MathUtil.hx b/source/funkin/util/MathUtil.hx index 72c592e8b..5db8f8d76 100644 --- a/source/funkin/util/MathUtil.hx +++ b/source/funkin/util/MathUtil.hx @@ -13,12 +13,25 @@ class MathUtil /** * 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 smoothLerp 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 smoothLerp instead') public static function cameraLerp(lerp:Float):Float { return lerp * (FlxG.elapsed / (1 / 60)); @@ -30,26 +43,30 @@ class MathUtil * @param value The value to get the logarithm of. * @return `log_base(value)` */ - public static function logBase(base:Float, value:Float) + public static function logBase(base:Float, value:Float):Float { return Math.log(value) / Math.log(base); } /** - * @returns `2^x` + * Get the base-2 logarithm of a value. + * @param x value + * @return `2^x` */ - public static function exp2(x:Float) + public static function exp2(x:Float):Float { return Math.pow(2, x); } /** * Linearly interpolate between two values. + * * @param base The starting value, when `progress <= 0`. * @param target The ending value, when `progress >= 1`. * @param progress Value used to interpolate between `base` and `target`. + * @return The interpolated value. */ - public static function lerp(base:Float, target:Float, progress:Float) + public static function lerp(base:Float, target:Float, progress:Float):Float { return base + progress * (target - base); } @@ -67,6 +84,7 @@ class MathUtil */ 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)); diff --git a/source/funkin/util/MemoryUtil.hx b/source/funkin/util/MemoryUtil.hx index 6b5f7deea..f5935ed67 100644 --- a/source/funkin/util/MemoryUtil.hx +++ b/source/funkin/util/MemoryUtil.hx @@ -16,7 +16,7 @@ class MemoryUtil public static function buildGCInfo():String { #if cpp - var result = "HXCPP-Immix:"; + var result:String = 'HXCPP-Immix:'; result += '\n- Memory Used: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_USAGE)} bytes'; result += '\n- Memory Reserved: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_RESERVED)} bytes'; result += '\n- Memory Current Pool: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_CURRENT)} bytes'; @@ -35,10 +35,10 @@ class MemoryUtil result += '\n- HXCPP C++11: ${#if HXCPP_CPP11 'Enabled' #else 'Disabled' #end}'; result += '\n- Source Annotation: ${#if annotate_source 'Enabled' #else 'Disabled' #end}'; #elseif js - var result = "JS-MNS:"; + var result:String = 'JS-MNS:'; result += '\n- Memory Used: ${getMemoryUsed()} bytes'; #else - var result = "Unknown GC"; + var result:String = 'Unknown GC'; #end return result; @@ -66,7 +66,7 @@ class MemoryUtil #if cpp cpp.vm.Gc.enable(true); #else - throw "Not implemented!"; + throw 'Not implemented!'; #end } @@ -78,7 +78,7 @@ class MemoryUtil #if cpp cpp.vm.Gc.enable(false); #else - throw "Not implemented!"; + throw 'Not implemented!'; #end } @@ -92,7 +92,7 @@ class MemoryUtil #if cpp cpp.vm.Gc.run(major); #else - throw "Not implemented!"; + throw 'Not implemented!'; #end } @@ -107,7 +107,7 @@ class MemoryUtil #if cpp cpp.vm.Gc.compact(); #else - throw "Not implemented!"; + throw 'Not implemented!'; #end } } diff --git a/source/funkin/util/MouseUtil.hx b/source/funkin/util/MouseUtil.hx index c3f46819e..5248437a8 100644 --- a/source/funkin/util/MouseUtil.hx +++ b/source/funkin/util/MouseUtil.hx @@ -38,6 +38,9 @@ class MouseUtil } } + /** + * Increment the zoom level of the current camera by the mouse wheel scroll value. + */ public static function mouseWheelZoom():Void { if (FlxG.mouse.wheel != 0) FlxG.camera.zoom += FlxG.mouse.wheel * (0.1 * FlxG.camera.zoom); diff --git a/source/funkin/util/PlatformUtil.hx b/source/funkin/util/PlatformUtil.hx index eecf6a7ea..ae8c1dcee 100644 --- a/source/funkin/util/PlatformUtil.hx +++ b/source/funkin/util/PlatformUtil.hx @@ -16,9 +16,9 @@ class PlatformUtil #if mac return true; #elseif html5 - return js.Browser.window.navigator.platform.startsWith("Mac") - || js.Browser.window.navigator.platform.startsWith("iPad") - || js.Browser.window.navigator.platform.startsWith("iPhone"); + return js.Browser.window.navigator.platform.startsWith('Mac') + || js.Browser.window.navigator.platform.startsWith('iPad') + || js.Browser.window.navigator.platform.startsWith('iPhone'); #else return false; #end @@ -27,7 +27,7 @@ class PlatformUtil /** * Detects and returns the current host platform. * Always returns `HTML5` on web, regardless of the computer running that browser. - * Returns `null` if the platform could not be detected. + * @return The host platform, or `null` if the platform could not be detected. */ public static function detectHostPlatform():Null { diff --git a/source/funkin/util/SortUtil.hx b/source/funkin/util/SortUtil.hx index d87592c91..c5ac175be 100644 --- a/source/funkin/util/SortUtil.hx +++ b/source/funkin/util/SortUtil.hx @@ -27,29 +27,53 @@ class SortUtil /** * You can use this function in FlxTypedGroup.sort() to sort FlxObjects by their z-index values. * The value defaults to 0, but by assigning it you can easily rearrange objects as desired. + * + * @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING` + * @param a The first FlxObject to compare. + * @param b The second FlxObject to compare. + * @return 1 if `a` has a higher z-index, -1 if `b` has a higher z-index. */ - public static inline function byZIndex(Order:Int, Obj1:FlxBasic, Obj2:FlxBasic):Int + public static inline function byZIndex(order:Int, a:FlxBasic, b:FlxBasic):Int { - if (Obj1 == null || Obj2 == null) return 0; - return FlxSort.byValues(Order, Obj1.zIndex, Obj2.zIndex); + if (a == null || b == null) return 0; + return FlxSort.byValues(order, a.zIndex, b.zIndex); } /** * Given two Notes, returns 1 or -1 based on whether `a` or `b` has an earlier strumtime. * * @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING` + * @param a The first Note to compare. + * @param b The second Note to compare. + * @return 1 if `a` has an earlier strumtime, -1 if `b` has an earlier strumtime. */ - public static inline function byStrumtime(order:Int, a:NoteSprite, b:NoteSprite) + public static inline function byStrumtime(order:Int, a:NoteSprite, b:NoteSprite):Int { return noteDataByTime(order, a.noteData, b.noteData); } - public static inline function noteDataByTime(order:Int, a:SongNoteData, b:SongNoteData) + /** + * Given two Note Data objects, returns 1 or -1 based on whether `a` or `b` has an earlier time. + * + * @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING` + * @param a The first Event to compare. + * @param b The second Event to compare. + * @return 1 if `a` has an earlier time, -1 if `b` has an earlier time. + */ + public static inline function noteDataByTime(order:Int, a:SongNoteData, b:SongNoteData):Int { return FlxSort.byValues(order, a.time, b.time); } - public static inline function eventDataByTime(order:Int, a:SongEventData, b:SongEventData) + /** + * Given two Event Data objects, returns 1 or -1 based on whether `a` or `b` has an earlier time. + * + * @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING` + * @param a The first Event to compare. + * @param b The second Event to compare. + * @return 1 if `a` has an earlier time, -1 if `b` has an earlier time. + */ + public static inline function eventDataByTime(order:Int, a:SongEventData, b:SongEventData):Int { return FlxSort.byValues(order, a.time, b.time); } @@ -58,8 +82,11 @@ class SortUtil * Given two FlxFrames, sort their names alphabetically. * * @param order Either `FlxSort.ASCENDING` or `FlxSort.DESCENDING` + * @param a The first Frame to compare. + * @param b The second Frame to compare. + * @return 1 if `a` has an earlier time, -1 if `b` has an earlier time. */ - public static inline function byFrameName(a:FlxFrame, b:FlxFrame) + public static inline function byFrameName(a:FlxFrame, b:FlxFrame):Int { return alphabetically(a.name, b.name); } @@ -68,6 +95,7 @@ class SortUtil * Sort predicate for sorting strings alphabetically. * @param a The first string to compare. * @param b The second string to compare. + * @return 1 if `a` comes before `b`, -1 if `b` comes before `a`, 0 if they are equal */ public static function alphabetically(a:String, b:String):Int { @@ -81,9 +109,11 @@ class SortUtil /** * Sort predicate which sorts two strings alphabetically, but prioritizes a specific string first. * Example usage: `array.sort(defaultThenAlphabetical.bind('test'))` will sort the array so that the string 'test' is first. + * + * @param defaultValue The value to prioritize. * @param a The first string to compare. * @param b The second string to compare. - * @param defaultValue The value to prioritize. + * @return 1 if `a` comes before `b`, -1 if `b` comes before `a`, 0 if they are equal */ public static function defaultThenAlphabetically(defaultValue:String, a:String, b:String):Int { @@ -96,9 +126,11 @@ class SortUtil /** * Sort predicate which sorts two strings alphabetically, but prioritizes a specific string first. * Example usage: `array.sort(defaultsThenAlphabetical.bind(['test']))` will sort the array so that the string 'test' is first. + * + * @param defaultValues The values to prioritize. * @param a The first string to compare. * @param b The second string to compare. - * @param defaultValues The values to prioritize. + * @return 1 if `a` comes before `b`, -1 if `b` comes before `a`, 0 if they are equal */ public static function defaultsThenAlphabetically(defaultValues:Array, a:String, b:String):Int { diff --git a/source/funkin/util/TimerUtil.hx b/source/funkin/util/TimerUtil.hx index caf49341b..eb2690e16 100644 --- a/source/funkin/util/TimerUtil.hx +++ b/source/funkin/util/TimerUtil.hx @@ -5,23 +5,42 @@ import haxe.Timer; class TimerUtil { + /** + * Store the current time. + */ public static function start():Float { return Timer.stamp(); } - private static function took(start:Float, ?end:Float):Float + /** + * Return the elapsed time. + */ + static function took(start:Float, ?end:Float):Float { var endOrNow:Float = end != null ? end : Timer.stamp(); return endOrNow - start; } + /** + * Return the elapsed time in seconds as a string. + * @param start The start time. + * @param end The end time. + * @param precision The number of decimal places to round to. + * @return The elapsed time in seconds as a string. + */ public static function seconds(start:Float, ?end:Float, ?precision = 2):String { var seconds:Float = FloatTools.round(took(start, end), precision); return '${seconds} seconds'; } + /** + * Return the elapsed time in milliseconds as a string. + * @param start The start time. + * @param end The end time. + * @return The elapsed time in milliseconds as a string. + */ public static function ms(start:Float, ?end:Float):String { var seconds:Float = took(start, end); diff --git a/source/funkin/util/TrackerUtil.hx b/source/funkin/util/TrackerUtil.hx index ffe374c5f..a2552f63e 100644 --- a/source/funkin/util/TrackerUtil.hx +++ b/source/funkin/util/TrackerUtil.hx @@ -18,7 +18,7 @@ class TrackerUtil public static function initTrackers():Void { #if FLX_DEBUG - Tracker.addProfile(new TrackerProfile(Highscore, ["tallies"])); + Tracker.addProfile(new TrackerProfile(Highscore, ['tallies'])); FlxG.console.registerClass(Highscore); #end } diff --git a/source/funkin/util/VersionUtil.hx b/source/funkin/util/VersionUtil.hx index e8d3d3652..247ba19db 100644 --- a/source/funkin/util/VersionUtil.hx +++ b/source/funkin/util/VersionUtil.hx @@ -15,6 +15,9 @@ class VersionUtil /** * Checks that a given verison number satisisfies a given version rule. * Version rule can be complex, e.g. "1.0.x" or ">=1.0.0,<1.1.0", or anything NPM supports. + * @param version The semantic version to validate. + * @param versionRule The version rule to validate against. + * @return `true` if the version satisfies the rule, `false` otherwise. */ public static function validateVersion(version:thx.semver.Version, versionRule:thx.semver.VersionRule):Bool { @@ -32,6 +35,9 @@ class VersionUtil /** * Checks that a given verison number satisisfies a given version rule. * Version rule can be complex, e.g. "1.0.x" or ">=1.0.0,<1.1.0", or anything NPM supports. + * @param version The semantic version to validate. + * @param versionRule The version rule to validate against. + * @return `true` if the version satisfies the rule, `false` otherwise. */ public static function validateVersionStr(version:String, versionRule:String):Bool { @@ -56,7 +62,7 @@ class VersionUtil public static function getVersionFromJSON(input:Null):Null { if (input == null) return null; - var parsed = SerializerUtil.fromJSON(input); + var parsed:Dynamic = SerializerUtil.fromJSON(input); if (parsed == null) return null; if (parsed.version == null) return null; var versionStr:String = parsed.version; // Dynamic -> String cast @@ -64,6 +70,11 @@ class VersionUtil return version; } + /** + * Get and parse the semantic version from a JSON string. + * @param input The JSON string to parse. + * @return The semantic version, or null if it could not be parsed. + */ public static function parseVersion(input:Dynamic):Null { if (input == null) return null; diff --git a/source/funkin/util/WindowUtil.hx b/source/funkin/util/WindowUtil.hx index 9f623c39d..f73fe11ce 100644 --- a/source/funkin/util/WindowUtil.hx +++ b/source/funkin/util/WindowUtil.hx @@ -24,7 +24,7 @@ class WindowUtil { #if CAN_OPEN_LINKS #if linux - Sys.command('/usr/bin/xdg-open', [targetUrl, "&"]); + Sys.command('/usr/bin/xdg-open', [targetUrl, '&']); #else // This should work on Windows and HTML5. FlxG.openURL(targetUrl); @@ -42,7 +42,7 @@ class WindowUtil { #if CAN_OPEN_LINKS #if windows - Sys.command('explorer', [targetPath.replace("/", "\\")]); + Sys.command('explorer', [targetPath.replace('/', '\\')]); #elseif mac Sys.command('open', [targetPath]); #elseif linux @@ -61,9 +61,9 @@ class WindowUtil { #if CAN_OPEN_LINKS #if windows - Sys.command('explorer', ["/select," + targetPath.replace("/", "\\")]); + Sys.command('explorer', ['/select,' + targetPath.replace('/', '\\')]); #elseif mac - Sys.command('open', ["-R", targetPath]); + Sys.command('open', ['-R', targetPath]); #elseif linux // TODO: unsure of the linux equivalent to opening a folder and then "selecting" a file. Sys.command('open', [targetPath]); @@ -82,7 +82,7 @@ class WindowUtil * Wires up FlxSignals that happen based on window activity. * For example, we can run a callback when the window is closed. */ - public static function initWindowEvents() + public static function initWindowEvents():Void { // onUpdate is called every frame just before rendering. @@ -95,7 +95,7 @@ class WindowUtil /** * Turns off that annoying "Report to Microsoft" dialog that pops up when the game crashes. */ - public static function disableCrashHandler() + public static function disableCrashHandler():Void { #if (cpp && windows) untyped __cpp__('SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);'); diff --git a/source/funkin/util/plugins/ScreenshotPlugin.hx b/source/funkin/util/plugins/ScreenshotPlugin.hx index d7e8109b8..9ac21d4b8 100644 --- a/source/funkin/util/plugins/ScreenshotPlugin.hx +++ b/source/funkin/util/plugins/ScreenshotPlugin.hx @@ -174,7 +174,7 @@ class ScreenshotPlugin extends FlxBasic FlxTween.tween(flashSpr, {alpha: 0}, 0.15, {ease: FlxEase.quadOut, onComplete: _ -> FlxG.stage.removeChild(flashSpr)}); // Play a sound (auto-play is true). - FunkinSound.load(Paths.sound('screenshot'), 1.0, false, true, true); + FunkinSound.playOnce(Paths.sound('screenshot'), 1.0); } static final PREVIEW_INITIAL_DELAY = 0.25; // How long before the preview starts fading in. diff --git a/source/haxe/ui/backend/flixel/UIStateBase.hx b/source/haxe/ui/backend/flixel/UIStateBase.hx index 9f1a53722..38377206c 100644 --- a/source/haxe/ui/backend/flixel/UIStateBase.hx +++ b/source/haxe/ui/backend/flixel/UIStateBase.hx @@ -1,3 +1,6 @@ package haxe.ui.backend.flixel; +/** + * Override HaxeUI to use `MusicBeatState` instead of `FlxState`. + */ typedef UIStateBase = funkin.ui.MusicBeatState; diff --git a/source/haxe/ui/backend/flixel/UISubStateBase.hx b/source/haxe/ui/backend/flixel/UISubStateBase.hx index 306c9b633..52c9a7231 100644 --- a/source/haxe/ui/backend/flixel/UISubStateBase.hx +++ b/source/haxe/ui/backend/flixel/UISubStateBase.hx @@ -1,3 +1,6 @@ package haxe.ui.backend.flixel; +/** + * Override HaxeUI to use `MusicBeatSubState` instead of `FlxSubState`. + */ typedef UISubStateBase = funkin.ui.MusicBeatSubState; diff --git a/tests/unit/assets/shared/images/arrows.xml b/tests/unit/assets/shared/images/arrows.xml index 8f3355462..96a73a388 100644 --- a/tests/unit/assets/shared/images/arrows.xml +++ b/tests/unit/assets/shared/images/arrows.xml @@ -1,32 +1,27 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + +