From 7a2b023f307c673d212ddec1817e97ef8933167e Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 12 Jun 2024 15:29:44 -0400 Subject: [PATCH 01/48] gitmodules for dev repo --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index be5e0aaa8..452c0089b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "assets"] path = assets - url = https://github.com/FunkinCrew/funkin.assets + url = https://github.com/FunkinCrew/Funkin-Assets-secret [submodule "art"] path = art - url = https://github.com/FunkinCrew/funkin.art + url = https://github.com/FunkinCrew/Funkin-Art-secret From 80981eee3733c3507250a37e2bff9807a481aa0c Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 12 Jun 2024 15:41:42 -0400 Subject: [PATCH 02/48] inputs null fix --- source/funkin/input/Controls.hx | 370 +++++++++++++++++++------------- 1 file changed, 218 insertions(+), 152 deletions(-) diff --git a/source/funkin/input/Controls.hx b/source/funkin/input/Controls.hx index e2cae5613..f6c881f6d 100644 --- a/source/funkin/input/Controls.hx +++ b/source/funkin/input/Controls.hx @@ -31,6 +31,7 @@ class Controls extends FlxActionSet * Uses FlxActions to funnel various inputs to a single action. */ var _ui_up = new FunkinAction(Action.UI_UP); + var _ui_left = new FunkinAction(Action.UI_LEFT); var _ui_right = new FunkinAction(Action.UI_RIGHT); var _ui_down = new FunkinAction(Action.UI_DOWN); @@ -325,19 +326,18 @@ class Controls extends FlxActionSet add(_volume_down); add(_volume_mute); - for (action in digitalActions) { - if (Std.isOfType(action, FunkinAction)) { + for (action in digitalActions) + { + if (Std.isOfType(action, FunkinAction)) + { var funkinAction:FunkinAction = cast action; byName[funkinAction.name] = funkinAction; - if (funkinAction.namePressed != null) - byName[funkinAction.namePressed] = funkinAction; - if (funkinAction.nameReleased != null) - byName[funkinAction.nameReleased] = funkinAction; + if (funkinAction.namePressed != null) byName[funkinAction.namePressed] = funkinAction; + if (funkinAction.nameReleased != null) byName[funkinAction.nameReleased] = funkinAction; } } - if (scheme == null) - scheme = None; + if (scheme == null) scheme = None; setKeyboardScheme(scheme, false); } @@ -350,38 +350,38 @@ class Controls extends FlxActionSet public function check(name:Action, trigger:FlxInputState = JUST_PRESSED, gamepadOnly:Bool = false):Bool { #if debug - if (!byName.exists(name)) - throw 'Invalid name: $name'; + if (!byName.exists(name)) throw 'Invalid name: $name'; #end var action = byName[name]; - if (gamepadOnly) - return action.checkFiltered(trigger, GAMEPAD); + if (gamepadOnly) return action.checkFiltered(trigger, GAMEPAD); else return action.checkFiltered(trigger); } - public function getKeysForAction(name:Action):Array { + public function getKeysForAction(name:Action):Array + { #if debug - if (!byName.exists(name)) - throw 'Invalid name: $name'; + if (!byName.exists(name)) throw 'Invalid name: $name'; #end // TODO: Revert to `.map().filter()` once HashLink doesn't complain anymore. var result:Array = []; - for (input in byName[name].inputs) { + for (input in byName[name].inputs) + { if (input.device == KEYBOARD) result.push(input.inputID); } return result; } - public function getButtonsForAction(name:Action):Array { + public function getButtonsForAction(name:Action):Array + { #if debug - if (!byName.exists(name)) - throw 'Invalid name: $name'; + if (!byName.exists(name)) throw 'Invalid name: $name'; #end var result:Array = []; - for (input in byName[name].inputs) { + for (input in byName[name].inputs) + { if (input.device == GAMEPAD) result.push(input.inputID); } return result; @@ -405,7 +405,7 @@ class Controls extends FlxActionSet function getActionFromControl(control:Control):FlxActionDigital { - return switch(control) + return switch (control) { case UI_UP: _ui_up; case UI_DOWN: _ui_down; @@ -448,7 +448,7 @@ class Controls extends FlxActionSet */ function forEachBound(control:Control, func:FlxActionDigital->FlxInputState->Void) { - switch(control) + switch (control) { case UI_UP: func(_ui_up, PRESSED); @@ -519,10 +519,9 @@ class Controls extends FlxActionSet public function replaceBinding(control:Control, device:Device, toAdd:Int, toRemove:Int) { - if (toAdd == toRemove) - return; + if (toAdd == toRemove) return; - switch(device) + switch (device) { case Keys: forEachBound(control, function(action, state) replaceKey(action, toAdd, toRemove, state)); @@ -534,7 +533,8 @@ class Controls extends FlxActionSet function replaceKey(action:FlxActionDigital, toAdd:FlxKey, toRemove:FlxKey, state:FlxInputState) { - if (action.inputs.length == 0) { + if (action.inputs.length == 0) + { // Add the keybind, don't replace. addKeys(action, [toAdd], state); return; @@ -548,34 +548,44 @@ class Controls extends FlxActionSet if (input.device == KEYBOARD && input.inputID == toRemove) { - if (toAdd == FlxKey.NONE) { + if (toAdd == FlxKey.NONE) + { // Remove the keybind, don't replace. action.inputs.remove(input); - } else { + } + else + { // Replace the keybind. @:privateAccess action.inputs[i].inputID = toAdd; } hasReplaced = true; - } else if (input.device == KEYBOARD && input.inputID == toAdd) { + } + else if (input.device == KEYBOARD && input.inputID == toAdd) + { // This key is already bound! - if (hasReplaced) { + if (hasReplaced) + { // Remove the duplicate keybind, don't replace. action.inputs.remove(input); - } else { + } + else + { hasReplaced = true; } } } - if (!hasReplaced) { + if (!hasReplaced) + { addKeys(action, [toAdd], state); } } function replaceButton(action:FlxActionDigital, deviceID:Int, toAdd:FlxGamepadInputID, toRemove:FlxGamepadInputID, state:FlxInputState) { - if (action.inputs.length == 0) { + if (action.inputs.length == 0) + { addButtons(action, [toAdd], state, deviceID); return; } @@ -594,7 +604,8 @@ class Controls extends FlxActionSet } } - if (!hasReplaced) { + if (!hasReplaced) + { addButtons(action, [toAdd], state, deviceID); } } @@ -606,18 +617,16 @@ class Controls extends FlxActionSet var action = controls.byName[name]; for (input in action.inputs) { - if (device == null || isDevice(input, device)) - byName[name].add(cast input); + if (device == null || isDevice(input, device)) byName[name].add(cast input); } } - switch(device) + switch (device) { case null: // add all for (gamepad in controls.gamepadsAdded) - if (gamepadsAdded.indexOf(gamepad) == -1) - gamepadsAdded.push(gamepad); + if (gamepadsAdded.indexOf(gamepad) == -1) gamepadsAdded.push(gamepad); mergeKeyboardScheme(controls.keyboardScheme); @@ -637,7 +646,7 @@ class Controls extends FlxActionSet { if (scheme != None) { - switch(keyboardScheme) + switch (keyboardScheme) { case None: keyboardScheme = scheme; @@ -672,7 +681,8 @@ class Controls extends FlxActionSet static function addKeys(action:FlxActionDigital, keys:Array, state:FlxInputState) { - for (key in keys) { + for (key in keys) + { if (key == FlxKey.NONE) continue; // Ignore unbound keys. action.addKey(key, state); } @@ -684,15 +694,13 @@ class Controls extends FlxActionSet while (i-- > 0) { var input = action.inputs[i]; - if (input.device == KEYBOARD && keys.indexOf(cast input.inputID) != -1) - action.remove(input); + if (input.device == KEYBOARD && keys.indexOf(cast input.inputID) != -1) action.remove(input); } } public function setKeyboardScheme(scheme:KeyboardScheme, reset = true) { - if (reset) - removeKeyboard(); + if (reset) removeKeyboard(); keyboardScheme = scheme; @@ -724,10 +732,13 @@ class Controls extends FlxActionSet bindMobileLol(); } - function getDefaultKeybinds(scheme:KeyboardScheme, control:Control):Array { - switch (scheme) { + function getDefaultKeybinds(scheme:KeyboardScheme, control:Control):Array + { + switch (scheme) + { case Solo: - switch (control) { + switch (control) + { case Control.UI_UP: return [W, FlxKey.UP]; case Control.UI_DOWN: return [S, FlxKey.DOWN]; case Control.UI_LEFT: return [A, FlxKey.LEFT]; @@ -754,7 +765,8 @@ class Controls extends FlxActionSet case Control.VOLUME_MUTE: return [ZERO, NUMPADZERO]; } case Duo(true): - switch (control) { + switch (control) + { case Control.UI_UP: return [W]; case Control.UI_DOWN: return [S]; case Control.UI_LEFT: return [A]; @@ -779,10 +791,10 @@ class Controls extends FlxActionSet case Control.VOLUME_UP: return [PLUS]; case Control.VOLUME_DOWN: return [MINUS]; case Control.VOLUME_MUTE: return [ZERO]; - } case Duo(false): - switch (control) { + switch (control) + { case Control.UI_UP: return [FlxKey.UP]; case Control.UI_DOWN: return [FlxKey.DOWN]; case Control.UI_LEFT: return [FlxKey.LEFT]; @@ -807,7 +819,6 @@ class Controls extends FlxActionSet case Control.VOLUME_UP: return [NUMPADPLUS]; case Control.VOLUME_DOWN: return [NUMPADMINUS]; case Control.VOLUME_MUTE: return [NUMPADZERO]; - } default: // Fallthrough. @@ -834,8 +845,7 @@ class Controls extends FlxActionSet #end #if android - forEachBound(Control.BACK, function(action, pres) - { + forEachBound(Control.BACK, function(action, pres) { action.add(new FlxActionInputDigitalAndroid(FlxAndroidKey.BACK, JUST_PRESSED)); }); #end @@ -849,8 +859,7 @@ class Controls extends FlxActionSet while (i-- > 0) { var input = action.inputs[i]; - if (input.device == KEYBOARD) - action.remove(input); + if (input.device == KEYBOARD) action.remove(input); } } } @@ -862,11 +871,13 @@ class Controls extends FlxActionSet fromSaveData(padData, Gamepad(id)); } - public function getGamepadIds():Array { + public function getGamepadIds():Array + { return gamepadsAdded; } - public function getGamepads():Array { + public function getGamepads():Array + { return [for (id in gamepadsAdded) FlxG.gamepads.getByID(id)]; } @@ -886,8 +897,7 @@ class Controls extends FlxActionSet while (i-- > 0) { var input = action.inputs[i]; - if (isGamepad(input, deviceID)) - action.remove(input); + if (isGamepad(input, deviceID)) action.remove(input); } } @@ -924,32 +934,58 @@ class Controls extends FlxActionSet ]); } - function getDefaultGamepadBinds(control:Control):Array { - switch(control) { - case Control.ACCEPT: return [#if switch B #else A #end]; - case Control.BACK: return [#if switch A #else B #end]; - case Control.UI_UP: return [DPAD_UP, LEFT_STICK_DIGITAL_UP]; - case Control.UI_DOWN: return [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN]; - case Control.UI_LEFT: return [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT]; - case Control.UI_RIGHT: return [DPAD_RIGHT, LEFT_STICK_DIGITAL_RIGHT]; - case Control.NOTE_UP: return [DPAD_UP, Y, LEFT_STICK_DIGITAL_UP, RIGHT_STICK_DIGITAL_UP]; - case Control.NOTE_DOWN: return [DPAD_DOWN, A, LEFT_STICK_DIGITAL_DOWN, RIGHT_STICK_DIGITAL_DOWN]; - case Control.NOTE_LEFT: return [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT]; - case Control.NOTE_RIGHT: return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT]; - case Control.PAUSE: return [START]; - case Control.RESET: return [FlxGamepadInputID.BACK]; // Back (i.e. Select) - case Control.WINDOW_FULLSCREEN: []; - case Control.WINDOW_SCREENSHOT: []; - case Control.CUTSCENE_ADVANCE: return [A]; - case Control.FREEPLAY_FAVORITE: [FlxGamepadInputID.BACK]; // Back (i.e. Select) - case Control.FREEPLAY_LEFT: [LEFT_SHOULDER]; - case Control.FREEPLAY_RIGHT: [RIGHT_SHOULDER]; - case Control.VOLUME_UP: []; - case Control.VOLUME_DOWN: []; - case Control.VOLUME_MUTE: []; - case Control.DEBUG_MENU: []; - case Control.DEBUG_CHART: []; - case Control.DEBUG_STAGE: []; + function getDefaultGamepadBinds(control:Control):Array + { + switch (control) + { + case Control.ACCEPT: + return [#if switch B #else A #end]; + case Control.BACK: + return [#if switch A #else B #end]; + case Control.UI_UP: + return [DPAD_UP, LEFT_STICK_DIGITAL_UP]; + case Control.UI_DOWN: + return [DPAD_DOWN, LEFT_STICK_DIGITAL_DOWN]; + case Control.UI_LEFT: + return [DPAD_LEFT, LEFT_STICK_DIGITAL_LEFT]; + case Control.UI_RIGHT: + return [DPAD_RIGHT, LEFT_STICK_DIGITAL_RIGHT]; + case Control.NOTE_UP: + return [DPAD_UP, Y, LEFT_STICK_DIGITAL_UP, RIGHT_STICK_DIGITAL_UP]; + case Control.NOTE_DOWN: + return [DPAD_DOWN, A, LEFT_STICK_DIGITAL_DOWN, RIGHT_STICK_DIGITAL_DOWN]; + case Control.NOTE_LEFT: + return [DPAD_LEFT, X, LEFT_STICK_DIGITAL_LEFT, RIGHT_STICK_DIGITAL_LEFT]; + case Control.NOTE_RIGHT: + return [DPAD_RIGHT, B, LEFT_STICK_DIGITAL_RIGHT, RIGHT_STICK_DIGITAL_RIGHT]; + case Control.PAUSE: + return [START]; + case Control.RESET: + return [FlxGamepadInputID.BACK]; // Back (i.e. Select) + case Control.WINDOW_FULLSCREEN: + []; + case Control.WINDOW_SCREENSHOT: + []; + case Control.CUTSCENE_ADVANCE: + return [A]; + case Control.FREEPLAY_FAVORITE: + [FlxGamepadInputID.BACK]; // Back (i.e. Select) + case Control.FREEPLAY_LEFT: + [LEFT_SHOULDER]; + case Control.FREEPLAY_RIGHT: + [RIGHT_SHOULDER]; + case Control.VOLUME_UP: + []; + case Control.VOLUME_DOWN: + []; + case Control.VOLUME_MUTE: + []; + case Control.DEBUG_MENU: + []; + case Control.DEBUG_CHART: + []; + case Control.DEBUG_STAGE: + []; default: // Fallthrough. } @@ -967,8 +1003,7 @@ class Controls extends FlxActionSet public function touchShit(control:Control, id) { - forEachBound(control, function(action, state) - { + forEachBound(control, function(action, state) { // action }); } @@ -984,7 +1019,8 @@ class Controls extends FlxActionSet inline static function addButtons(action:FlxActionDigital, buttons:Array, state, id) { - for (button in buttons) { + for (button in buttons) + { if (button == FlxGamepadInputID.NONE) continue; // Ignore unbound keys. action.addGamepad(button, state, id); } @@ -996,29 +1032,25 @@ class Controls extends FlxActionSet while (i-- > 0) { var input = action.inputs[i]; - if (isGamepad(input, gamepadID) && buttons.indexOf(cast input.inputID) != -1) - action.remove(input); + if (isGamepad(input, gamepadID) && buttons.indexOf(cast input.inputID) != -1) action.remove(input); } } public function getInputsFor(control:Control, device:Device, ?list:Array):Array { - if (list == null) - list = []; + if (list == null) list = []; - switch(device) + switch (device) { case Keys: for (input in getActionFromControl(control).inputs) { - if (input.device == KEYBOARD) - list.push(input.inputID); + if (input.device == KEYBOARD) list.push(input.inputID); } case Gamepad(id): for (input in getActionFromControl(control).inputs) { - if (isGamepad(input, id)) - list.push(input.inputID); + if (isGamepad(input, id)) list.push(input.inputID); } } return list; @@ -1026,7 +1058,7 @@ class Controls extends FlxActionSet public function removeDevice(device:Device) { - switch(device) + switch (device) { case Keys: setKeyboardScheme(None); @@ -1040,27 +1072,32 @@ class Controls extends FlxActionSet * An EMPTY array means the control is uninitialized and needs to be reset to default. * An array with a single FlxKey.NONE means the control was intentionally unbound by the user. */ - public function fromSaveData(data:Dynamic, device:Device) + public function fromSaveData(data:Dynamic, device:Device):Void { for (control in Control.createAll()) { var inputs:Array = Reflect.field(data, control.getName()); - inputs = inputs.distinct(); + inputs = inputs?.distinct(); if (inputs != null) { - if (inputs.length == 0) { + if (inputs.length == 0) + { trace('Control ${control} is missing bindings, resetting to default.'); - switch(device) + switch (device) { case Keys: bindKeys(control, getDefaultKeybinds(Solo, control)); case Gamepad(id): bindButtons(control, id, getDefaultGamepadBinds(control)); } - } else if (inputs == [FlxKey.NONE]) { + } + else if (inputs == [FlxKey.NONE]) + { trace('Control ${control} is unbound, leaving it be.'); - } else { - switch(device) + } + else + { + switch (device) { case Keys: bindKeys(control, inputs.copy()); @@ -1068,9 +1105,11 @@ class Controls extends FlxActionSet bindButtons(control, id, inputs.copy()); } } - } else { + } + else + { trace('Control ${control} is missing bindings, resetting to default.'); - switch(device) + switch (device) { case Keys: bindKeys(control, getDefaultKeybinds(Solo, control)); @@ -1095,9 +1134,12 @@ class Controls extends FlxActionSet var inputs = getInputsFor(control, device); isEmpty = isEmpty && inputs.length == 0; - if (inputs.length == 0) { + if (inputs.length == 0) + { inputs = [FlxKey.NONE]; - } else { + } + else + { inputs = inputs.distinct(); } @@ -1109,7 +1151,7 @@ class Controls extends FlxActionSet static function isDevice(input:FlxActionInput, device:Device) { - return switch(device) + return switch (device) { case Keys: input.device == KEYBOARD; case Gamepad(id): isGamepad(input, id); @@ -1141,7 +1183,8 @@ typedef Swipes = * - Combining `pressed` and `released` inputs into one action. * - Filtering by input method (`KEYBOARD`, `MOUSE`, `GAMEPAD`, etc). */ -class FunkinAction extends FlxActionDigital { +class FunkinAction extends FlxActionDigital +{ public var namePressed(default, null):Null; public var nameReleased(default, null):Null; @@ -1158,83 +1201,102 @@ class FunkinAction extends FlxActionDigital { /** * Input checks default to whether the input was just pressed, on any input device. */ - public override function check():Bool { + public override function check():Bool + { return checkFiltered(JUST_PRESSED); } /** * Check whether the input is currently being held. */ - public function checkPressed():Bool { + public function checkPressed():Bool + { return checkFiltered(PRESSED); } /** * Check whether the input is currently being held, and was not held last frame. */ - public function checkJustPressed():Bool { + public function checkJustPressed():Bool + { return checkFiltered(JUST_PRESSED); } /** * Check whether the input is not currently being held. */ - public function checkReleased():Bool { + public function checkReleased():Bool + { return checkFiltered(RELEASED); } /** * Check whether the input is not currently being held, and was held last frame. */ - public function checkJustReleased():Bool { + public function checkJustReleased():Bool + { return checkFiltered(JUST_RELEASED); } /** * Check whether the input is currently being held by a gamepad device. */ - public function checkPressedGamepad():Bool { + public function checkPressedGamepad():Bool + { return checkFiltered(PRESSED, GAMEPAD); } /** * Check whether the input is currently being held by a gamepad device, and was not held last frame. */ - public function checkJustPressedGamepad():Bool { + public function checkJustPressedGamepad():Bool + { return checkFiltered(JUST_PRESSED, GAMEPAD); } /** * Check whether the input is not currently being held by a gamepad device. */ - public function checkReleasedGamepad():Bool { + public function checkReleasedGamepad():Bool + { return checkFiltered(RELEASED, GAMEPAD); } /** * Check whether the input is not currently being held by a gamepad device, and was held last frame. */ - public function checkJustReleasedGamepad():Bool { + public function checkJustReleasedGamepad():Bool + { return checkFiltered(JUST_RELEASED, GAMEPAD); } - public function checkMultiFiltered(?filterTriggers:Array, ?filterDevices:Array):Bool { - if (filterTriggers == null) { + public function checkMultiFiltered(?filterTriggers:Array, ?filterDevices:Array):Bool + { + if (filterTriggers == null) + { filterTriggers = [PRESSED, JUST_PRESSED]; } - if (filterDevices == null) { + if (filterDevices == null) + { filterDevices = []; } // Perform checkFiltered for each combination. - for (i in filterTriggers) { - if (filterDevices.length == 0) { - if (checkFiltered(i)) { + for (i in filterTriggers) + { + if (filterDevices.length == 0) + { + if (checkFiltered(i)) + { return true; } - } else { - for (j in filterDevices) { - if (checkFiltered(i, j)) { + } + else + { + for (j in filterDevices) + { + if (checkFiltered(i, j)) + { return true; } } @@ -1249,52 +1311,56 @@ class FunkinAction extends FlxActionDigital { * @param filterTrigger Optionally filter by trigger condition (`JUST_PRESSED`, `PRESSED`, `JUST_RELEASED`, `RELEASED`). * @param filterDevice Optionally filter by device (`KEYBOARD`, `MOUSE`, `GAMEPAD`, `OTHER`). */ - public function checkFiltered(?filterTrigger:FlxInputState, ?filterDevice:FlxInputDevice):Bool { + public function checkFiltered(?filterTrigger:FlxInputState, ?filterDevice:FlxInputDevice):Bool + { // The normal // Make sure we only update the inputs once per frame. var key = '${filterTrigger}:${filterDevice}'; var cacheEntry = cache.get(key); - if (cacheEntry != null && cacheEntry.timestamp == FlxG.game.ticks) { + if (cacheEntry != null && cacheEntry.timestamp == FlxG.game.ticks) + { return cacheEntry.value; } // Use a for loop instead so we can remove inputs while iterating. // We don't return early because we need to call check() on ALL inputs. var result = false; - var len = inputs != null ? inputs.length : 0; - for (i in 0...len) - { - var j = len - i - 1; - var input = inputs[j]; + var len = inputs != null ? inputs.length : 0; + for (i in 0...len) + { + var j = len - i - 1; + var input = inputs[j]; // Filter out dead inputs. - if (input.destroyed) - { - inputs.splice(j, 1); - continue; - } + if (input.destroyed) + { + inputs.splice(j, 1); + continue; + } // Update the input. input.update(); // Check whether the input is the right trigger. - if (filterTrigger != null && input.trigger != filterTrigger) { + if (filterTrigger != null && input.trigger != filterTrigger) + { continue; } // Check whether the input is the right device. - if (filterDevice != null && input.device != filterDevice) { + if (filterDevice != null && input.device != filterDevice) + { continue; } // Check whether the input has triggered. - if (input.check(this)) - { - result = true; - } - } + if (input.check(this)) + { + result = true; + } + } // We need to cache this result. cache.set(key, {timestamp: FlxG.game.ticks, value: result}); @@ -1391,12 +1457,12 @@ class FlxActionInputDigitalMobileSwipeGameplay extends FlxActionInputDigital { var degAngle = FlxAngle.asDegrees(swp.touchAngle); - switch(trigger) + switch (trigger) { case JUST_PRESSED: if (swp.touchLength >= activateLength) { - switch(inputID) + switch (inputID) { case FlxDirectionFlags.UP: if (degAngle >= 45 && degAngle <= 90 + 45) return properTouch(swp); @@ -1440,7 +1506,7 @@ class FlxActionInputDigitalAndroid extends FlxActionInputDigital override public function check(Action:FlxAction):Bool { - return switch(trigger) + return switch (trigger) { #if android case PRESSED: FlxG.android.checkStatus(inputID, PRESSED) || FlxG.android.checkStatus(inputID, PRESSED); From 1c45c9b03b86f2c4dc1049f0a41fd61b5e167af5 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 12 Jun 2024 15:50:59 -0400 Subject: [PATCH 03/48] git modules --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 452c0089b..be5e0aaa8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "assets"] path = assets - url = https://github.com/FunkinCrew/Funkin-Assets-secret + url = https://github.com/FunkinCrew/funkin.assets [submodule "art"] path = art - url = https://github.com/FunkinCrew/Funkin-Art-secret + url = https://github.com/FunkinCrew/funkin.art From a63b60d458bedca9bb980e6d7c8e47b73357adfc Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 12 Jun 2024 20:31:27 -0400 Subject: [PATCH 04/48] fix for release, no bf printing directly to stdout! --- source/funkin/util/logging/AnsiTrace.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/util/logging/AnsiTrace.hx b/source/funkin/util/logging/AnsiTrace.hx index 9fdc19e1b..2c18d494d 100644 --- a/source/funkin/util/logging/AnsiTrace.hx +++ b/source/funkin/util/logging/AnsiTrace.hx @@ -51,7 +51,7 @@ class AnsiTrace public static function traceBF() { - #if sys + #if (sys && debug) if (colorSupported) { for (line in ansiBF) From 24bdf2db644b9f225764b825ba01b6287e618199 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 16 Jun 2024 18:34:10 -0400 Subject: [PATCH 05/48] Fix a bug where songs with no notes would crash the results screen. --- source/funkin/play/ResultState.hx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index 48fb3b04e..a2c5f7e62 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -464,7 +464,9 @@ class ResultState extends MusicBeatSubState { bgFlash.visible = true; FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24); - var clearPercentFloat = (params.scoreData.tallies.sick + params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes * 100; + // NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors. + var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : (params.scoreData.tallies.sick + + params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes * 100; clearPercentTarget = Math.floor(clearPercentFloat); // Prevent off-by-one errors. From 42192dfe97712f24a43c9ec45694049a6c5f4c70 Mon Sep 17 00:00:00 2001 From: Karim Akra <144803230+KarimAkra@users.noreply.github.com> Date: Sun, 9 Jun 2024 12:47:17 +0300 Subject: [PATCH 06/48] remove the library strip --- source/funkin/audio/FunkinSound.hx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index c70f195d2..11b713f4d 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -491,8 +491,10 @@ class FunkinSound extends FlxSound implements ICloneable var promise:lime.app.Promise> = new lime.app.Promise>(); // split the path and get only after first : - // we are bypassing the openfl/lime asset library fuss + // we are bypassing the openfl/lime asset library fuss on web only + #if web path = Paths.stripLibrary(path); + #end var soundRequest = FlxPartialSound.partialLoadFromFile(path, start, end); From e2be19039c2a11bcf2050dd823b61b043c950fc5 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 17 Jun 2024 12:22:49 -0400 Subject: [PATCH 07/48] Get rid of a warning about missing BPM info --- source/funkin/ui/freeplay/FreeplayState.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 0caaf4591..68c63efc4 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1888,6 +1888,7 @@ class FreeplayState extends MusicBeatSubState startingVolume: 0.0, overrideExisting: true, restartTrack: false, + mapTimeChanges: false, // The music metadata is not alongside the audio file so this won't work. pathsFunction: INST, suffix: potentiallyErect, partialParams: From 0ed103b2f0c18061c45b50eebac8e30450ee2773 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Sat, 15 Jun 2024 06:33:30 -0400 Subject: [PATCH 08/48] submoduel --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 2e1594ee4..15a816fb8 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 2e1594ee4c04c7148628bae471bdd061c9deb6b7 +Subproject commit 15a816fb833555c22be92bd408eaf147ed4c64be From cf44774b218c3b323b975c4dd075be8e04c96826 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Sat, 15 Jun 2024 06:53:41 -0400 Subject: [PATCH 09/48] submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 15a816fb8..01a285b38 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 15a816fb833555c22be92bd408eaf147ed4c64be +Subproject commit 01a285b38ecf10218153a1d40a2e4ea5b2132d7b From 78bbf5a7aab2ea50df4a817039062922fec562fd Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Sat, 15 Jun 2024 06:55:08 -0400 Subject: [PATCH 10/48] submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 01a285b38..4b9507525 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 01a285b38ecf10218153a1d40a2e4ea5b2132d7b +Subproject commit 4b95075255baeaba3585fabff7052c257856fafe From dd413d42ab5bdd63b18e2663f69d8182b032c76e Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 19 Jun 2024 23:47:11 -0400 Subject: [PATCH 11/48] more retro week 6 stuff --- assets | 2 +- source/funkin/effects/RetroCameraFade.hx | 106 ++++++++++++++++++ source/funkin/graphics/FunkinSprite.hx | 101 +++++++++++++++++ source/funkin/play/Countdown.hx | 19 +++- source/funkin/play/GameOverSubState.hx | 25 ++++- .../play/character/MultiSparrowCharacter.hx | 2 + .../funkin/play/character/PackerCharacter.hx | 2 + .../funkin/play/character/SparrowCharacter.hx | 2 + source/funkin/play/components/PopUpStuff.hx | 30 +++-- source/funkin/play/stage/Stage.hx | 4 + 10 files changed, 277 insertions(+), 16 deletions(-) create mode 100644 source/funkin/effects/RetroCameraFade.hx diff --git a/assets b/assets index 4b9507525..225e248f1 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 4b95075255baeaba3585fabff7052c257856fafe +Subproject commit 225e248f148a92500a6fe90e4f10e4cd2acee782 diff --git a/source/funkin/effects/RetroCameraFade.hx b/source/funkin/effects/RetroCameraFade.hx new file mode 100644 index 000000000..d4c1da5ef --- /dev/null +++ b/source/funkin/effects/RetroCameraFade.hx @@ -0,0 +1,106 @@ +package funkin.effects; + +import flixel.util.FlxTimer; +import flixel.FlxCamera; +import openfl.filters.ColorMatrixFilter; + +class RetroCameraFade +{ + // im lazy, but we only use this for week 6 + // and also sorta yoinked for djflixel, lol ! + public static function fadeWhite(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void + { + var steps:Int = 0; + var stepsTotal:Int = camSteps; + + new FlxTimer().start(time / stepsTotal, _ -> { + var V:Float = (1 / stepsTotal) * steps; + if (steps == stepsTotal) V = 1; + + var matrix = [ + 1, 0, 0, 0, V * 255, + 0, 1, 0, 0, V * 255, + 0, 0, 1, 0, V * 255, + 0, 0, 0, 1, 0 + ]; + camera.filters = [new ColorMatrixFilter(matrix)]; + steps++; + }, stepsTotal + 1); + } + + public static function fadeFromWhite(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void + { + var steps:Int = camSteps; + var stepsTotal:Int = camSteps; + + var matrixDerp = [ + 1, 0, 0, 0, 1.0 * 255, + 0, 1, 0, 0, 1.0 * 255, + 0, 0, 1, 0, 1.0 * 255, + 0, 0, 0, 1, 0 + ]; + camera.filters = [new ColorMatrixFilter(matrixDerp)]; + + new FlxTimer().start(time / stepsTotal, _ -> { + var V:Float = (1 / stepsTotal) * steps; + if (steps == stepsTotal) V = 1; + + var matrix = [ + 1, 0, 0, 0, V * 255, + 0, 1, 0, 0, V * 255, + 0, 0, 1, 0, V * 255, + 0, 0, 0, 1, 0 + ]; + camera.filters = [new ColorMatrixFilter(matrix)]; + steps--; + }, camSteps); + } + + public static function fadeToBlack(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void + { + var steps:Int = 0; + var stepsTotal:Int = camSteps; + + new FlxTimer().start(time / stepsTotal, _ -> { + var V:Float = (1 / stepsTotal) * steps; + if (steps == stepsTotal) V = 1; + + var matrix = [ + 1, 0, 0, 0, -V * 255, + 0, 1, 0, 0, -V * 255, + 0, 0, 1, 0, -V * 255, + 0, 0, 0, 1, 0 + ]; + camera.filters = [new ColorMatrixFilter(matrix)]; + steps++; + }, camSteps); + } + + public static function fadeBlack(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void + { + var steps:Int = camSteps; + var stepsTotal:Int = camSteps; + + var matrixDerp = [ + 1, 0, 0, 0, -1.0 * 255, + 0, 1, 0, 0, -1.0 * 255, + 0, 0, 1, 0, -1.0 * 255, + 0, 0, 0, 1, 0 + ]; + camera.filters = [new ColorMatrixFilter(matrixDerp)]; + + new FlxTimer().start(time / stepsTotal, _ -> { + var V:Float = (1 / stepsTotal) * steps; + if (steps == stepsTotal) V = 1; + + var matrix = [ + 1, 0, 0, 0, -V * 255, + 0, 1, 0, 0, -V * 255, + 0, 0, 1, 0, -V * 255, + 0, 0, 0, 1, 0 + ]; + camera.filters = [new ColorMatrixFilter(matrix)]; + steps--; + }, camSteps + 1); + } +} diff --git a/source/funkin/graphics/FunkinSprite.hx b/source/funkin/graphics/FunkinSprite.hx index bfd2e8028..521553527 100644 --- a/source/funkin/graphics/FunkinSprite.hx +++ b/source/funkin/graphics/FunkinSprite.hx @@ -7,6 +7,10 @@ import flixel.tweens.FlxTween; import openfl.display3D.textures.TextureBase; import funkin.graphics.framebuffer.FixedBitmapData; import openfl.display.BitmapData; +import flixel.math.FlxRect; +import flixel.math.FlxPoint; +import flixel.graphics.frames.FlxFrame; +import flixel.FlxCamera; /** * An FlxSprite with additional functionality. @@ -269,6 +273,103 @@ class FunkinSprite extends FlxSprite return result; } + @:access(flixel.FlxCamera) + override function getBoundingBox(camera:FlxCamera):FlxRect + { + getScreenPosition(_point, camera); + + _rect.set(_point.x, _point.y, width, height); + _rect = camera.transformRect(_rect); + + if (isPixelPerfectRender(camera)) + { + _rect.width = _rect.width / this.scale.x; + _rect.height = _rect.height / this.scale.y; + _rect.x = _rect.x / this.scale.x; + _rect.y = _rect.y / this.scale.y; + _rect.floor(); + _rect.x = _rect.x * this.scale.x; + _rect.y = _rect.y * this.scale.y; + _rect.width = _rect.width * this.scale.x; + _rect.height = _rect.height * this.scale.y; + } + + return _rect; + } + + /** + * Returns the screen position of this object. + * + * @param result Optional arg for the returning point + * @param camera The desired "screen" coordinate space. If `null`, `FlxG.camera` is used. + * @return The screen position of this object. + */ + public override function getScreenPosition(?result:FlxPoint, ?camera:FlxCamera):FlxPoint + { + if (result == null) result = FlxPoint.get(); + + if (camera == null) camera = FlxG.camera; + + result.set(x, y); + if (pixelPerfectPosition) + { + _rect.width = _rect.width / this.scale.x; + _rect.height = _rect.height / this.scale.y; + _rect.x = _rect.x / this.scale.x; + _rect.y = _rect.y / this.scale.y; + _rect.round(); + _rect.x = _rect.x * this.scale.x; + _rect.y = _rect.y * this.scale.y; + _rect.width = _rect.width * this.scale.x; + _rect.height = _rect.height * this.scale.y; + } + + return result.subtract(camera.scroll.x * scrollFactor.x, camera.scroll.y * scrollFactor.y); + } + + override function drawSimple(camera:FlxCamera):Void + { + getScreenPosition(_point, camera).subtractPoint(offset); + if (isPixelPerfectRender(camera)) + { + _point.x = _point.x / this.scale.x; + _point.y = _point.y / this.scale.y; + _point.round(); + + _point.x = _point.x * this.scale.x; + _point.y = _point.y * this.scale.y; + } + + _point.copyToFlash(_flashPoint); + camera.copyPixels(_frame, framePixels, _flashRect, _flashPoint, colorTransform, blend, antialiasing); + } + + override function drawComplex(camera:FlxCamera):Void + { + _frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, checkFlipX(), checkFlipY()); + _matrix.translate(-origin.x, -origin.y); + _matrix.scale(scale.x, scale.y); + + if (bakedRotationAngle <= 0) + { + updateTrig(); + + if (angle != 0) _matrix.rotateWithTrig(_cosAngle, _sinAngle); + } + + getScreenPosition(_point, camera).subtractPoint(offset); + _point.add(origin.x, origin.y); + _matrix.translate(_point.x, _point.y); + + if (isPixelPerfectRender(camera)) + { + _matrix.tx = Math.round(_matrix.tx / this.scale.x) * this.scale.x; + _matrix.ty = Math.round(_matrix.ty / this.scale.y) * this.scale.y; + } + + camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader); + } + public override function destroy():Void { frames = null; diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index 10636afdf..55c2a8992 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.util.EaseUtil; import funkin.audio.FunkinSound; class Countdown @@ -117,7 +118,7 @@ class Countdown * * If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event. */ - public static function pauseCountdown() + public static function pauseCountdown():Void { if (countdownTimer != null && !countdownTimer.finished) { @@ -130,7 +131,7 @@ class Countdown * * If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event. */ - public static function resumeCountdown() + public static function resumeCountdown():Void { if (countdownTimer != null && !countdownTimer.finished) { @@ -143,7 +144,7 @@ class Countdown * * If you want to call this from a module, it's better to use the event system and cancel the onCountdownStart event. */ - public static function stopCountdown() + public static function stopCountdown():Void { if (countdownTimer != null) { @@ -156,7 +157,7 @@ class Countdown /** * Stops the current countdown, then starts the song for you. */ - public static function skipCountdown() + public static function skipCountdown():Void { stopCountdown(); // This will trigger PlayState.startSong() @@ -185,8 +186,11 @@ class Countdown { var spritePath:String = null; + var fadeEase = FlxEase.cubeInOut; + if (isPixelStyle) { + fadeEase = EaseUtil.stepped(8); switch (index) { case TWO: @@ -227,7 +231,7 @@ class Countdown countdownSprite.screenCenter(); // Fade sprite in, then out, then destroy it. - FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.instance.beatLengthMs / 1000, + FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100}, Conductor.instance.beatLengthMs / 1000, { ease: FlxEase.cubeInOut, onComplete: function(twn:FlxTween) { @@ -235,6 +239,11 @@ class Countdown } }); + FlxTween.tween(countdownSprite, {alpha: 0}, Conductor.instance.beatLengthMs / 1000, + { + ease: fadeEase + }); + PlayState.instance.add(countdownSprite); } diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index c84d5b154..1e1c91dc5 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -16,6 +16,7 @@ import funkin.ui.MusicBeatSubState; import funkin.ui.story.StoryMenuState; import funkin.util.MathUtil; import openfl.utils.Assets; +import funkin.effects.RetroCameraFade; /** * A substate which renders over the PlayState when the player dies. @@ -331,9 +332,12 @@ class GameOverSubState extends MusicBeatSubState // After the animation finishes... new FlxTimer().start(0.7, function(tmr:FlxTimer) { // ...fade out the graphics. Then after that happens... - FlxG.camera.fade(FlxColor.BLACK, 2, false, function() { + + var resetPlaying = function(pixel:Bool = false) { // ...close the GameOverSubState. - FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true); + if (pixel) RetroCameraFade.fadeBlack(FlxG.camera, 10, 1); + else + FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true); PlayState.instance.needsReset = true; if (PlayState.instance.isMinimalMode || boyfriend == null) {} @@ -350,7 +354,22 @@ class GameOverSubState extends MusicBeatSubState // Close the substate. close(); - }); + }; + + if (musicSuffix == '-pixel') + { + RetroCameraFade.fadeToBlack(FlxG.camera, 10, 2); + new FlxTimer().start(2, _ -> { + FlxG.camera.filters = []; + resetPlaying(true); + }); + } + else + { + FlxG.camera.fade(FlxColor.BLACK, 2, false, function() { + resetPlaying(); + }); + } }); } } diff --git a/source/funkin/play/character/MultiSparrowCharacter.hx b/source/funkin/play/character/MultiSparrowCharacter.hx index 48c5afb58..41c96fbfa 100644 --- a/source/funkin/play/character/MultiSparrowCharacter.hx +++ b/source/funkin/play/character/MultiSparrowCharacter.hx @@ -41,6 +41,8 @@ class MultiSparrowCharacter extends BaseCharacter { this.isPixel = true; this.antialiasing = false; + pixelPerfectRender = true; + pixelPerfectPosition = true; } else { diff --git a/source/funkin/play/character/PackerCharacter.hx b/source/funkin/play/character/PackerCharacter.hx index 2bfac800a..22edbe339 100644 --- a/source/funkin/play/character/PackerCharacter.hx +++ b/source/funkin/play/character/PackerCharacter.hx @@ -43,6 +43,8 @@ class PackerCharacter extends BaseCharacter { this.isPixel = true; this.antialiasing = false; + pixelPerfectRender = true; + pixelPerfectPosition = true; } else { diff --git a/source/funkin/play/character/SparrowCharacter.hx b/source/funkin/play/character/SparrowCharacter.hx index a36aed84d..81d98b138 100644 --- a/source/funkin/play/character/SparrowCharacter.hx +++ b/source/funkin/play/character/SparrowCharacter.hx @@ -46,6 +46,8 @@ class SparrowCharacter extends BaseCharacter { this.isPixel = true; this.antialiasing = false; + pixelPerfectRender = true; + pixelPerfectPosition = true; } else { diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx index b7e206e97..1bdfd98a8 100644 --- a/source/funkin/play/components/PopUpStuff.hx +++ b/source/funkin/play/components/PopUpStuff.hx @@ -7,8 +7,9 @@ import flixel.util.FlxDirection; import funkin.graphics.FunkinSprite; import funkin.play.PlayState; import funkin.util.TimerUtil; +import funkin.util.EaseUtil; -class PopUpStuff extends FlxTypedGroup +class PopUpStuff extends FlxTypedGroup { public var offsets:Array = [0, 0]; @@ -17,7 +18,7 @@ class PopUpStuff extends FlxTypedGroup super(); } - public function displayRating(daRating:String) + public function displayRating(daRating:String):Void { var perfStart:Float = TimerUtil.start(); @@ -40,10 +41,15 @@ class PopUpStuff extends FlxTypedGroup add(rating); + var fadeEase = null; + if (PlayState.instance.currentStageId.startsWith('school')) { rating.setGraphicSize(Std.int(rating.width * Constants.PIXEL_ART_SCALE * 0.7)); rating.antialiasing = false; + rating.pixelPerfectRender = true; + rating.pixelPerfectPosition = true; + fadeEase = EaseUtil.stepped(2); } else { @@ -61,7 +67,8 @@ class PopUpStuff extends FlxTypedGroup remove(rating, true); rating.destroy(); }, - startDelay: Conductor.instance.beatLengthMs * 0.001 + startDelay: Conductor.instance.beatLengthMs * 0.001, + ease: fadeEase }); trace('displayRating took: ${TimerUtil.seconds(perfStart)}'); @@ -92,10 +99,15 @@ class PopUpStuff extends FlxTypedGroup // add(comboSpr); + var fadeEase = null; + if (PlayState.instance.currentStageId.startsWith('school')) { - comboSpr.setGraphicSize(Std.int(comboSpr.width * Constants.PIXEL_ART_SCALE * 0.7)); + comboSpr.setGraphicSize(Std.int(comboSpr.width * Constants.PIXEL_ART_SCALE * 1)); comboSpr.antialiasing = false; + comboSpr.pixelPerfectRender = true; + comboSpr.pixelPerfectPosition = true; + fadeEase = EaseUtil.stepped(2); } else { @@ -110,7 +122,8 @@ class PopUpStuff extends FlxTypedGroup remove(comboSpr, true); comboSpr.destroy(); }, - startDelay: Conductor.instance.beatLengthMs * 0.001 + startDelay: Conductor.instance.beatLengthMs * 0.001, + ease: fadeEase }); var seperatedScore:Array = []; @@ -133,8 +146,10 @@ class PopUpStuff extends FlxTypedGroup if (PlayState.instance.currentStageId.startsWith('school')) { - numScore.setGraphicSize(Std.int(numScore.width * Constants.PIXEL_ART_SCALE * 0.7)); + numScore.setGraphicSize(Std.int(numScore.width * Constants.PIXEL_ART_SCALE * 1)); numScore.antialiasing = false; + numScore.pixelPerfectRender = true; + numScore.pixelPerfectPosition = true; } else { @@ -156,7 +171,8 @@ class PopUpStuff extends FlxTypedGroup remove(numScore, true); numScore.destroy(); }, - startDelay: Conductor.instance.beatLengthMs * 0.002 + startDelay: Conductor.instance.beatLengthMs * 0.002, + ease: fadeEase }); daLoop++; diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index 4f8ab4434..f4e22e380 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -249,6 +249,10 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements // If pixel, disable antialiasing. propSprite.antialiasing = !dataProp.isPixel; + // If pixel, we render it pixel perfect so there's less "mixels" + propSprite.pixelPerfectRender = dataProp.isPixel; + propSprite.pixelPerfectPosition = dataProp.isPixel; + propSprite.scrollFactor.x = dataProp.scroll[0]; propSprite.scrollFactor.y = dataProp.scroll[1]; From aa24dc4aa03b1fc91ea6af000f9d2d87dc755e7a Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 13:34:33 -0400 Subject: [PATCH 12/48] remove bopper trace --- source/funkin/play/stage/Bopper.hx | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/funkin/play/stage/Bopper.hx b/source/funkin/play/stage/Bopper.hx index 262aff7bc..11fb9e499 100644 --- a/source/funkin/play/stage/Bopper.hx +++ b/source/funkin/play/stage/Bopper.hx @@ -200,12 +200,10 @@ class Bopper extends StageProp implements IPlayStateScriptedClass { if (hasDanced) { - trace('DanceRight (alternate)'); playAnimation('danceRight$idleSuffix', forceRestart); } else { - trace('DanceLeft (alternate)'); playAnimation('danceLeft$idleSuffix', forceRestart); } hasDanced = !hasDanced; From aa3e3eb9b65dc26f8a2332cdb3f03050fa3e459b Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 14:13:30 -0400 Subject: [PATCH 13/48] fix: bf v pose playing during a gameover --- source/funkin/play/GameOverSubState.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index c84d5b154..e8568aac6 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -144,6 +144,7 @@ class GameOverSubState extends MusicBeatSubState else { boyfriend = PlayState.instance.currentStage.getBoyfriend(true); + boyfriend.canPlayOtherAnims = true; boyfriend.isDead = true; add(boyfriend); boyfriend.resetCharacter(); From 81cada675c99359c461da88193564ba22b32bbba Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 12 Jun 2024 20:31:27 -0400 Subject: [PATCH 14/48] fix for release, no bf printing directly to stdout! --- source/funkin/util/logging/AnsiTrace.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/util/logging/AnsiTrace.hx b/source/funkin/util/logging/AnsiTrace.hx index 9fdc19e1b..2c18d494d 100644 --- a/source/funkin/util/logging/AnsiTrace.hx +++ b/source/funkin/util/logging/AnsiTrace.hx @@ -51,7 +51,7 @@ class AnsiTrace public static function traceBF() { - #if sys + #if (sys && debug) if (colorSupported) { for (line in ansiBF) From d44275c2c7050ed207cb031328f0d9966b812c85 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Sun, 16 Jun 2024 18:34:10 -0400 Subject: [PATCH 15/48] Fix a bug where songs with no notes would crash the results screen. --- source/funkin/play/ResultState.hx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index 48fb3b04e..a2c5f7e62 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -464,7 +464,9 @@ class ResultState extends MusicBeatSubState { bgFlash.visible = true; FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24); - var clearPercentFloat = (params.scoreData.tallies.sick + params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes * 100; + // NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors. + var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : (params.scoreData.tallies.sick + + params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes * 100; clearPercentTarget = Math.floor(clearPercentFloat); // Prevent off-by-one errors. From 983d241e47845e97f2e059358c1a31b0ff5d18e3 Mon Sep 17 00:00:00 2001 From: Karim Akra <144803230+KarimAkra@users.noreply.github.com> Date: Sun, 9 Jun 2024 12:47:17 +0300 Subject: [PATCH 16/48] remove the library strip --- source/funkin/audio/FunkinSound.hx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index c70f195d2..11b713f4d 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -491,8 +491,10 @@ class FunkinSound extends FlxSound implements ICloneable var promise:lime.app.Promise> = new lime.app.Promise>(); // split the path and get only after first : - // we are bypassing the openfl/lime asset library fuss + // we are bypassing the openfl/lime asset library fuss on web only + #if web path = Paths.stripLibrary(path); + #end var soundRequest = FlxPartialSound.partialLoadFromFile(path, start, end); From 4e65c49e0e1a0f7c0bf8edb3c2c5a7d55a5828ef Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Mon, 17 Jun 2024 12:22:49 -0400 Subject: [PATCH 17/48] Get rid of a warning about missing BPM info --- source/funkin/ui/freeplay/FreeplayState.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 0caaf4591..68c63efc4 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1888,6 +1888,7 @@ class FreeplayState extends MusicBeatSubState startingVolume: 0.0, overrideExisting: true, restartTrack: false, + mapTimeChanges: false, // The music metadata is not alongside the audio file so this won't work. pathsFunction: INST, suffix: potentiallyErect, partialParams: From 0f972cb11c1d4bdf3e376f09a1229436dcb3fe47 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Sat, 15 Jun 2024 06:33:30 -0400 Subject: [PATCH 18/48] submoduel --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 2e1594ee4..15a816fb8 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 2e1594ee4c04c7148628bae471bdd061c9deb6b7 +Subproject commit 15a816fb833555c22be92bd408eaf147ed4c64be From 92917e17c689a6f43b0d7230e019d5cd5c0a63bc Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Sat, 15 Jun 2024 06:53:41 -0400 Subject: [PATCH 19/48] submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 15a816fb8..01a285b38 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 15a816fb833555c22be92bd408eaf147ed4c64be +Subproject commit 01a285b38ecf10218153a1d40a2e4ea5b2132d7b From 93aa02868eb9af3ca1701aeb1467a997e7cf90a0 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Sat, 15 Jun 2024 06:55:08 -0400 Subject: [PATCH 20/48] submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 01a285b38..4b9507525 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 01a285b38ecf10218153a1d40a2e4ea5b2132d7b +Subproject commit 4b95075255baeaba3585fabff7052c257856fafe From de6ba0c741854adc8ce6cb394cebc13f77c0876b Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 14:32:40 -0400 Subject: [PATCH 21/48] flxpartialsound hmm update --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index 68e0c5cb0..9daba9902 100644 --- a/hmm.json +++ b/hmm.json @@ -44,7 +44,7 @@ "name": "FlxPartialSound", "type": "git", "dir": null, - "ref": "f986332ba5ab02abd386ce662578baf04904604a", + "ref": "a1eab7b9bf507b87200a3341719054fe427f3b15", "url": "https://github.com/FunkinCrew/FlxPartialSound.git" }, { From 47b7aa702a0b0cccf803632d81829db6040e962c Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 14:46:09 -0400 Subject: [PATCH 22/48] submod --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index be5e0aaa8..452c0089b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "assets"] path = assets - url = https://github.com/FunkinCrew/funkin.assets + url = https://github.com/FunkinCrew/Funkin-Assets-secret [submodule "art"] path = art - url = https://github.com/FunkinCrew/funkin.art + url = https://github.com/FunkinCrew/Funkin-Art-secret From a5442b407ea09c4fccf0d6366841f05355164a3b Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 21:53:56 -0400 Subject: [PATCH 23/48] haxeui --- hmm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hmm.json b/hmm.json index 9daba9902..278541e1f 100644 --- a/hmm.json +++ b/hmm.json @@ -75,14 +75,14 @@ "name": "haxeui-core", "type": "git", "dir": null, - "ref": "0212d8fdfcafeb5f0d5a41e1ddba8ff21d0e183b", + "ref": "5dc4c933bdc029f6139a47962e3b8c754060f210", "url": "https://github.com/haxeui/haxeui-core" }, { "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "63a906a6148958dbfde8c7b48d90b0693767fd95", + "ref": "57c1604d6b5174839d7e0e012a4dd5dcbfc129da", "url": "https://github.com/haxeui/haxeui-flixel" }, { From bdbc1364973543cb2186f67420a7420021df1165 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 23:43:34 -0400 Subject: [PATCH 24/48] fix the camera not panning when opening then closing the debug menu during transition --- source/funkin/ui/mainmenu/MainMenuState.hx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index d09536eea..2eba406d9 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -153,6 +153,9 @@ class MainMenuState extends MusicBeatState resetCamStuff(); + // reset camera when debug menu is closed + subStateClosed.add(_ -> resetCamStuff(false)); + subStateOpened.add(sub -> { if (Type.getClass(sub) == FreeplayState) { @@ -182,10 +185,11 @@ class MainMenuState extends MusicBeatState }); } - function resetCamStuff():Void + function resetCamStuff(?snap:Bool = true):Void { FlxG.camera.follow(camFollow, null, 0.06); - FlxG.camera.snapToTarget(); + + if (snap) FlxG.camera.snapToTarget(); } function createMenuItem(name:String, atlas:String, callback:Void->Void, fireInstantly:Bool = false):Void @@ -344,8 +348,6 @@ class MainMenuState extends MusicBeatState persistentUpdate = false; FlxG.state.openSubState(new DebugMenuSubState()); - // reset camera when debug menu is closed - subStateClosed.addOnce(_ -> resetCamStuff()); } #end From da834dab8d243b0da3b99b48f07b1a436333586f Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 16:26:42 -0400 Subject: [PATCH 25/48] fix for heart icons being on the wrong frames --- source/funkin/ui/freeplay/SongMenuItem.hx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx index 7708b3bcf..65a5d2e2d 100644 --- a/source/funkin/ui/freeplay/SongMenuItem.hx +++ b/source/funkin/ui/freeplay/SongMenuItem.hx @@ -213,6 +213,7 @@ class SongMenuItem extends FlxSpriteGroup favIconBlurred.frames = Paths.getSparrowAtlas('freeplay/favHeart'); favIconBlurred.animation.addByPrefix('fav', 'favorite heart', 24, false); favIconBlurred.animation.play('fav'); + favIconBlurred.setGraphicSize(50, 50); favIconBlurred.blend = BlendMode.ADD; favIconBlurred.shader = new GaussianBlurShader(1.2); @@ -516,6 +517,9 @@ class SongMenuItem extends FlxSpriteGroup updateDifficultyRating(songData?.difficultyRating ?? 0); updateScoringRank(songData?.scoringRank); newText.visible = songData?.isNew; + favIcon.animation.curAnim.curFrame = favIcon.animation.curAnim.numFrames - 1; + favIconBlurred.animation.curAnim.curFrame = favIconBlurred.animation.curAnim.numFrames - 1; + // Update opacity, offsets, etc. updateSelected(); From 458e51c327dcd7162ae1a39742e36c38cebaf61b Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 21:53:56 -0400 Subject: [PATCH 26/48] haxeui --- hmm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hmm.json b/hmm.json index 9daba9902..278541e1f 100644 --- a/hmm.json +++ b/hmm.json @@ -75,14 +75,14 @@ "name": "haxeui-core", "type": "git", "dir": null, - "ref": "0212d8fdfcafeb5f0d5a41e1ddba8ff21d0e183b", + "ref": "5dc4c933bdc029f6139a47962e3b8c754060f210", "url": "https://github.com/haxeui/haxeui-core" }, { "name": "haxeui-flixel", "type": "git", "dir": null, - "ref": "63a906a6148958dbfde8c7b48d90b0693767fd95", + "ref": "57c1604d6b5174839d7e0e012a4dd5dcbfc129da", "url": "https://github.com/haxeui/haxeui-flixel" }, { From 9b4f9db73e2f65be52e4129579fd0f8f73837a83 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 22:01:44 -0400 Subject: [PATCH 27/48] update thx related haxelibs --- hmm.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hmm.json b/hmm.json index 278541e1f..db00b878c 100644 --- a/hmm.json +++ b/hmm.json @@ -174,15 +174,15 @@ "name": "thx.core", "type": "git", "dir": null, - "ref": "22605ff44f01971d599641790d6bae4869f7d9f4", - "url": "https://github.com/FunkinCrew/thx.core" + "ref": "6240b6e136f7490d9298edbe8c1891374bd7cdf2", + "url": "https://github.com/fponticelli/thx.core" }, { "name": "thx.semver", "type": "git", "dir": null, - "ref": "cf8d213589a2c7ce4a59b0fdba9e8ff36bc029fa", - "url": "https://github.com/FunkinCrew/thx.semver" + "ref": "bdb191fe7cf745c02a980749906dbf22719e200b", + "url": "https://github.com/fponticelli/thx.semver" } ] } From 39c3af58f24fb01b4c48db0864f46993e8d6dd38 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 14:13:30 -0400 Subject: [PATCH 28/48] fix: bf v pose playing during a gameover --- source/funkin/play/GameOverSubState.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index c84d5b154..e8568aac6 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -144,6 +144,7 @@ class GameOverSubState extends MusicBeatSubState else { boyfriend = PlayState.instance.currentStage.getBoyfriend(true); + boyfriend.canPlayOtherAnims = true; boyfriend.isDead = true; add(boyfriend); boyfriend.resetCharacter(); From 57514302616b8033604e452c506bde9c419f2ff2 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 23:43:34 -0400 Subject: [PATCH 29/48] fix the camera not panning when opening then closing the debug menu during transition --- source/funkin/ui/mainmenu/MainMenuState.hx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index d09536eea..2eba406d9 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -153,6 +153,9 @@ class MainMenuState extends MusicBeatState resetCamStuff(); + // reset camera when debug menu is closed + subStateClosed.add(_ -> resetCamStuff(false)); + subStateOpened.add(sub -> { if (Type.getClass(sub) == FreeplayState) { @@ -182,10 +185,11 @@ class MainMenuState extends MusicBeatState }); } - function resetCamStuff():Void + function resetCamStuff(?snap:Bool = true):Void { FlxG.camera.follow(camFollow, null, 0.06); - FlxG.camera.snapToTarget(); + + if (snap) FlxG.camera.snapToTarget(); } function createMenuItem(name:String, atlas:String, callback:Void->Void, fireInstantly:Bool = false):Void @@ -344,8 +348,6 @@ class MainMenuState extends MusicBeatState persistentUpdate = false; FlxG.state.openSubState(new DebugMenuSubState()); - // reset camera when debug menu is closed - subStateClosed.addOnce(_ -> resetCamStuff()); } #end From ab2f949d7e4fd2011c4d228fc147e5ac70b2f2d9 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 16:26:42 -0400 Subject: [PATCH 30/48] fix for heart icons being on the wrong frames --- source/funkin/ui/freeplay/SongMenuItem.hx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx index 7708b3bcf..65a5d2e2d 100644 --- a/source/funkin/ui/freeplay/SongMenuItem.hx +++ b/source/funkin/ui/freeplay/SongMenuItem.hx @@ -213,6 +213,7 @@ class SongMenuItem extends FlxSpriteGroup favIconBlurred.frames = Paths.getSparrowAtlas('freeplay/favHeart'); favIconBlurred.animation.addByPrefix('fav', 'favorite heart', 24, false); favIconBlurred.animation.play('fav'); + favIconBlurred.setGraphicSize(50, 50); favIconBlurred.blend = BlendMode.ADD; favIconBlurred.shader = new GaussianBlurShader(1.2); @@ -516,6 +517,9 @@ class SongMenuItem extends FlxSpriteGroup updateDifficultyRating(songData?.difficultyRating ?? 0); updateScoringRank(songData?.scoringRank); newText.visible = songData?.isNew; + favIcon.animation.curAnim.curFrame = favIcon.animation.curAnim.numFrames - 1; + favIconBlurred.animation.curAnim.curFrame = favIconBlurred.animation.curAnim.numFrames - 1; + // Update opacity, offsets, etc. updateSelected(); From 1757532097efb747e5e64366095da6ef5e12ca30 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Wed, 19 Jun 2024 23:47:11 -0400 Subject: [PATCH 31/48] more retro week 6 stuff --- assets | 2 +- source/funkin/effects/RetroCameraFade.hx | 106 ++++++++++++++++++ source/funkin/graphics/FunkinSprite.hx | 101 +++++++++++++++++ source/funkin/play/Countdown.hx | 19 +++- source/funkin/play/GameOverSubState.hx | 25 ++++- .../play/character/MultiSparrowCharacter.hx | 2 + .../funkin/play/character/PackerCharacter.hx | 2 + .../funkin/play/character/SparrowCharacter.hx | 2 + source/funkin/play/components/PopUpStuff.hx | 30 +++-- source/funkin/play/stage/Stage.hx | 4 + 10 files changed, 277 insertions(+), 16 deletions(-) create mode 100644 source/funkin/effects/RetroCameraFade.hx diff --git a/assets b/assets index 4b9507525..225e248f1 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 4b95075255baeaba3585fabff7052c257856fafe +Subproject commit 225e248f148a92500a6fe90e4f10e4cd2acee782 diff --git a/source/funkin/effects/RetroCameraFade.hx b/source/funkin/effects/RetroCameraFade.hx new file mode 100644 index 000000000..d4c1da5ef --- /dev/null +++ b/source/funkin/effects/RetroCameraFade.hx @@ -0,0 +1,106 @@ +package funkin.effects; + +import flixel.util.FlxTimer; +import flixel.FlxCamera; +import openfl.filters.ColorMatrixFilter; + +class RetroCameraFade +{ + // im lazy, but we only use this for week 6 + // and also sorta yoinked for djflixel, lol ! + public static function fadeWhite(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void + { + var steps:Int = 0; + var stepsTotal:Int = camSteps; + + new FlxTimer().start(time / stepsTotal, _ -> { + var V:Float = (1 / stepsTotal) * steps; + if (steps == stepsTotal) V = 1; + + var matrix = [ + 1, 0, 0, 0, V * 255, + 0, 1, 0, 0, V * 255, + 0, 0, 1, 0, V * 255, + 0, 0, 0, 1, 0 + ]; + camera.filters = [new ColorMatrixFilter(matrix)]; + steps++; + }, stepsTotal + 1); + } + + public static function fadeFromWhite(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void + { + var steps:Int = camSteps; + var stepsTotal:Int = camSteps; + + var matrixDerp = [ + 1, 0, 0, 0, 1.0 * 255, + 0, 1, 0, 0, 1.0 * 255, + 0, 0, 1, 0, 1.0 * 255, + 0, 0, 0, 1, 0 + ]; + camera.filters = [new ColorMatrixFilter(matrixDerp)]; + + new FlxTimer().start(time / stepsTotal, _ -> { + var V:Float = (1 / stepsTotal) * steps; + if (steps == stepsTotal) V = 1; + + var matrix = [ + 1, 0, 0, 0, V * 255, + 0, 1, 0, 0, V * 255, + 0, 0, 1, 0, V * 255, + 0, 0, 0, 1, 0 + ]; + camera.filters = [new ColorMatrixFilter(matrix)]; + steps--; + }, camSteps); + } + + public static function fadeToBlack(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void + { + var steps:Int = 0; + var stepsTotal:Int = camSteps; + + new FlxTimer().start(time / stepsTotal, _ -> { + var V:Float = (1 / stepsTotal) * steps; + if (steps == stepsTotal) V = 1; + + var matrix = [ + 1, 0, 0, 0, -V * 255, + 0, 1, 0, 0, -V * 255, + 0, 0, 1, 0, -V * 255, + 0, 0, 0, 1, 0 + ]; + camera.filters = [new ColorMatrixFilter(matrix)]; + steps++; + }, camSteps); + } + + public static function fadeBlack(camera:FlxCamera, camSteps:Int = 5, time:Float = 1):Void + { + var steps:Int = camSteps; + var stepsTotal:Int = camSteps; + + var matrixDerp = [ + 1, 0, 0, 0, -1.0 * 255, + 0, 1, 0, 0, -1.0 * 255, + 0, 0, 1, 0, -1.0 * 255, + 0, 0, 0, 1, 0 + ]; + camera.filters = [new ColorMatrixFilter(matrixDerp)]; + + new FlxTimer().start(time / stepsTotal, _ -> { + var V:Float = (1 / stepsTotal) * steps; + if (steps == stepsTotal) V = 1; + + var matrix = [ + 1, 0, 0, 0, -V * 255, + 0, 1, 0, 0, -V * 255, + 0, 0, 1, 0, -V * 255, + 0, 0, 0, 1, 0 + ]; + camera.filters = [new ColorMatrixFilter(matrix)]; + steps--; + }, camSteps + 1); + } +} diff --git a/source/funkin/graphics/FunkinSprite.hx b/source/funkin/graphics/FunkinSprite.hx index bfd2e8028..521553527 100644 --- a/source/funkin/graphics/FunkinSprite.hx +++ b/source/funkin/graphics/FunkinSprite.hx @@ -7,6 +7,10 @@ import flixel.tweens.FlxTween; import openfl.display3D.textures.TextureBase; import funkin.graphics.framebuffer.FixedBitmapData; import openfl.display.BitmapData; +import flixel.math.FlxRect; +import flixel.math.FlxPoint; +import flixel.graphics.frames.FlxFrame; +import flixel.FlxCamera; /** * An FlxSprite with additional functionality. @@ -269,6 +273,103 @@ class FunkinSprite extends FlxSprite return result; } + @:access(flixel.FlxCamera) + override function getBoundingBox(camera:FlxCamera):FlxRect + { + getScreenPosition(_point, camera); + + _rect.set(_point.x, _point.y, width, height); + _rect = camera.transformRect(_rect); + + if (isPixelPerfectRender(camera)) + { + _rect.width = _rect.width / this.scale.x; + _rect.height = _rect.height / this.scale.y; + _rect.x = _rect.x / this.scale.x; + _rect.y = _rect.y / this.scale.y; + _rect.floor(); + _rect.x = _rect.x * this.scale.x; + _rect.y = _rect.y * this.scale.y; + _rect.width = _rect.width * this.scale.x; + _rect.height = _rect.height * this.scale.y; + } + + return _rect; + } + + /** + * Returns the screen position of this object. + * + * @param result Optional arg for the returning point + * @param camera The desired "screen" coordinate space. If `null`, `FlxG.camera` is used. + * @return The screen position of this object. + */ + public override function getScreenPosition(?result:FlxPoint, ?camera:FlxCamera):FlxPoint + { + if (result == null) result = FlxPoint.get(); + + if (camera == null) camera = FlxG.camera; + + result.set(x, y); + if (pixelPerfectPosition) + { + _rect.width = _rect.width / this.scale.x; + _rect.height = _rect.height / this.scale.y; + _rect.x = _rect.x / this.scale.x; + _rect.y = _rect.y / this.scale.y; + _rect.round(); + _rect.x = _rect.x * this.scale.x; + _rect.y = _rect.y * this.scale.y; + _rect.width = _rect.width * this.scale.x; + _rect.height = _rect.height * this.scale.y; + } + + return result.subtract(camera.scroll.x * scrollFactor.x, camera.scroll.y * scrollFactor.y); + } + + override function drawSimple(camera:FlxCamera):Void + { + getScreenPosition(_point, camera).subtractPoint(offset); + if (isPixelPerfectRender(camera)) + { + _point.x = _point.x / this.scale.x; + _point.y = _point.y / this.scale.y; + _point.round(); + + _point.x = _point.x * this.scale.x; + _point.y = _point.y * this.scale.y; + } + + _point.copyToFlash(_flashPoint); + camera.copyPixels(_frame, framePixels, _flashRect, _flashPoint, colorTransform, blend, antialiasing); + } + + override function drawComplex(camera:FlxCamera):Void + { + _frame.prepareMatrix(_matrix, FlxFrameAngle.ANGLE_0, checkFlipX(), checkFlipY()); + _matrix.translate(-origin.x, -origin.y); + _matrix.scale(scale.x, scale.y); + + if (bakedRotationAngle <= 0) + { + updateTrig(); + + if (angle != 0) _matrix.rotateWithTrig(_cosAngle, _sinAngle); + } + + getScreenPosition(_point, camera).subtractPoint(offset); + _point.add(origin.x, origin.y); + _matrix.translate(_point.x, _point.y); + + if (isPixelPerfectRender(camera)) + { + _matrix.tx = Math.round(_matrix.tx / this.scale.x) * this.scale.x; + _matrix.ty = Math.round(_matrix.ty / this.scale.y) * this.scale.y; + } + + camera.drawPixels(_frame, framePixels, _matrix, colorTransform, blend, antialiasing, shader); + } + public override function destroy():Void { frames = null; diff --git a/source/funkin/play/Countdown.hx b/source/funkin/play/Countdown.hx index 10636afdf..55c2a8992 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.util.EaseUtil; import funkin.audio.FunkinSound; class Countdown @@ -117,7 +118,7 @@ class Countdown * * If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event. */ - public static function pauseCountdown() + public static function pauseCountdown():Void { if (countdownTimer != null && !countdownTimer.finished) { @@ -130,7 +131,7 @@ class Countdown * * If you want to call this from a module, it's better to use the event system and cancel the onCountdownStep event. */ - public static function resumeCountdown() + public static function resumeCountdown():Void { if (countdownTimer != null && !countdownTimer.finished) { @@ -143,7 +144,7 @@ class Countdown * * If you want to call this from a module, it's better to use the event system and cancel the onCountdownStart event. */ - public static function stopCountdown() + public static function stopCountdown():Void { if (countdownTimer != null) { @@ -156,7 +157,7 @@ class Countdown /** * Stops the current countdown, then starts the song for you. */ - public static function skipCountdown() + public static function skipCountdown():Void { stopCountdown(); // This will trigger PlayState.startSong() @@ -185,8 +186,11 @@ class Countdown { var spritePath:String = null; + var fadeEase = FlxEase.cubeInOut; + if (isPixelStyle) { + fadeEase = EaseUtil.stepped(8); switch (index) { case TWO: @@ -227,7 +231,7 @@ class Countdown countdownSprite.screenCenter(); // Fade sprite in, then out, then destroy it. - FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100, alpha: 0}, Conductor.instance.beatLengthMs / 1000, + FlxTween.tween(countdownSprite, {y: countdownSprite.y += 100}, Conductor.instance.beatLengthMs / 1000, { ease: FlxEase.cubeInOut, onComplete: function(twn:FlxTween) { @@ -235,6 +239,11 @@ class Countdown } }); + FlxTween.tween(countdownSprite, {alpha: 0}, Conductor.instance.beatLengthMs / 1000, + { + ease: fadeEase + }); + PlayState.instance.add(countdownSprite); } diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index e8568aac6..6eb954c0f 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -16,6 +16,7 @@ import funkin.ui.MusicBeatSubState; import funkin.ui.story.StoryMenuState; import funkin.util.MathUtil; import openfl.utils.Assets; +import funkin.effects.RetroCameraFade; /** * A substate which renders over the PlayState when the player dies. @@ -332,9 +333,12 @@ class GameOverSubState extends MusicBeatSubState // After the animation finishes... new FlxTimer().start(0.7, function(tmr:FlxTimer) { // ...fade out the graphics. Then after that happens... - FlxG.camera.fade(FlxColor.BLACK, 2, false, function() { + + var resetPlaying = function(pixel:Bool = false) { // ...close the GameOverSubState. - FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true); + if (pixel) RetroCameraFade.fadeBlack(FlxG.camera, 10, 1); + else + FlxG.camera.fade(FlxColor.BLACK, 1, true, null, true); PlayState.instance.needsReset = true; if (PlayState.instance.isMinimalMode || boyfriend == null) {} @@ -351,7 +355,22 @@ class GameOverSubState extends MusicBeatSubState // Close the substate. close(); - }); + }; + + if (musicSuffix == '-pixel') + { + RetroCameraFade.fadeToBlack(FlxG.camera, 10, 2); + new FlxTimer().start(2, _ -> { + FlxG.camera.filters = []; + resetPlaying(true); + }); + } + else + { + FlxG.camera.fade(FlxColor.BLACK, 2, false, function() { + resetPlaying(); + }); + } }); } } diff --git a/source/funkin/play/character/MultiSparrowCharacter.hx b/source/funkin/play/character/MultiSparrowCharacter.hx index 48c5afb58..41c96fbfa 100644 --- a/source/funkin/play/character/MultiSparrowCharacter.hx +++ b/source/funkin/play/character/MultiSparrowCharacter.hx @@ -41,6 +41,8 @@ class MultiSparrowCharacter extends BaseCharacter { this.isPixel = true; this.antialiasing = false; + pixelPerfectRender = true; + pixelPerfectPosition = true; } else { diff --git a/source/funkin/play/character/PackerCharacter.hx b/source/funkin/play/character/PackerCharacter.hx index 2bfac800a..22edbe339 100644 --- a/source/funkin/play/character/PackerCharacter.hx +++ b/source/funkin/play/character/PackerCharacter.hx @@ -43,6 +43,8 @@ class PackerCharacter extends BaseCharacter { this.isPixel = true; this.antialiasing = false; + pixelPerfectRender = true; + pixelPerfectPosition = true; } else { diff --git a/source/funkin/play/character/SparrowCharacter.hx b/source/funkin/play/character/SparrowCharacter.hx index a36aed84d..81d98b138 100644 --- a/source/funkin/play/character/SparrowCharacter.hx +++ b/source/funkin/play/character/SparrowCharacter.hx @@ -46,6 +46,8 @@ class SparrowCharacter extends BaseCharacter { this.isPixel = true; this.antialiasing = false; + pixelPerfectRender = true; + pixelPerfectPosition = true; } else { diff --git a/source/funkin/play/components/PopUpStuff.hx b/source/funkin/play/components/PopUpStuff.hx index b7e206e97..1bdfd98a8 100644 --- a/source/funkin/play/components/PopUpStuff.hx +++ b/source/funkin/play/components/PopUpStuff.hx @@ -7,8 +7,9 @@ import flixel.util.FlxDirection; import funkin.graphics.FunkinSprite; import funkin.play.PlayState; import funkin.util.TimerUtil; +import funkin.util.EaseUtil; -class PopUpStuff extends FlxTypedGroup +class PopUpStuff extends FlxTypedGroup { public var offsets:Array = [0, 0]; @@ -17,7 +18,7 @@ class PopUpStuff extends FlxTypedGroup super(); } - public function displayRating(daRating:String) + public function displayRating(daRating:String):Void { var perfStart:Float = TimerUtil.start(); @@ -40,10 +41,15 @@ class PopUpStuff extends FlxTypedGroup add(rating); + var fadeEase = null; + if (PlayState.instance.currentStageId.startsWith('school')) { rating.setGraphicSize(Std.int(rating.width * Constants.PIXEL_ART_SCALE * 0.7)); rating.antialiasing = false; + rating.pixelPerfectRender = true; + rating.pixelPerfectPosition = true; + fadeEase = EaseUtil.stepped(2); } else { @@ -61,7 +67,8 @@ class PopUpStuff extends FlxTypedGroup remove(rating, true); rating.destroy(); }, - startDelay: Conductor.instance.beatLengthMs * 0.001 + startDelay: Conductor.instance.beatLengthMs * 0.001, + ease: fadeEase }); trace('displayRating took: ${TimerUtil.seconds(perfStart)}'); @@ -92,10 +99,15 @@ class PopUpStuff extends FlxTypedGroup // add(comboSpr); + var fadeEase = null; + if (PlayState.instance.currentStageId.startsWith('school')) { - comboSpr.setGraphicSize(Std.int(comboSpr.width * Constants.PIXEL_ART_SCALE * 0.7)); + comboSpr.setGraphicSize(Std.int(comboSpr.width * Constants.PIXEL_ART_SCALE * 1)); comboSpr.antialiasing = false; + comboSpr.pixelPerfectRender = true; + comboSpr.pixelPerfectPosition = true; + fadeEase = EaseUtil.stepped(2); } else { @@ -110,7 +122,8 @@ class PopUpStuff extends FlxTypedGroup remove(comboSpr, true); comboSpr.destroy(); }, - startDelay: Conductor.instance.beatLengthMs * 0.001 + startDelay: Conductor.instance.beatLengthMs * 0.001, + ease: fadeEase }); var seperatedScore:Array = []; @@ -133,8 +146,10 @@ class PopUpStuff extends FlxTypedGroup if (PlayState.instance.currentStageId.startsWith('school')) { - numScore.setGraphicSize(Std.int(numScore.width * Constants.PIXEL_ART_SCALE * 0.7)); + numScore.setGraphicSize(Std.int(numScore.width * Constants.PIXEL_ART_SCALE * 1)); numScore.antialiasing = false; + numScore.pixelPerfectRender = true; + numScore.pixelPerfectPosition = true; } else { @@ -156,7 +171,8 @@ class PopUpStuff extends FlxTypedGroup remove(numScore, true); numScore.destroy(); }, - startDelay: Conductor.instance.beatLengthMs * 0.002 + startDelay: Conductor.instance.beatLengthMs * 0.002, + ease: fadeEase }); daLoop++; diff --git a/source/funkin/play/stage/Stage.hx b/source/funkin/play/stage/Stage.hx index 4f8ab4434..f4e22e380 100644 --- a/source/funkin/play/stage/Stage.hx +++ b/source/funkin/play/stage/Stage.hx @@ -249,6 +249,10 @@ class Stage extends FlxSpriteGroup implements IPlayStateScriptedClass implements // If pixel, disable antialiasing. propSprite.antialiasing = !dataProp.isPixel; + // If pixel, we render it pixel perfect so there's less "mixels" + propSprite.pixelPerfectRender = dataProp.isPixel; + propSprite.pixelPerfectPosition = dataProp.isPixel; + propSprite.scrollFactor.x = dataProp.scroll[0]; propSprite.scrollFactor.y = dataProp.scroll[1]; From 8a144352a030d24f78eea554bb5ee89daaeff815 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 13:34:33 -0400 Subject: [PATCH 32/48] remove bopper trace --- source/funkin/play/stage/Bopper.hx | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/funkin/play/stage/Bopper.hx b/source/funkin/play/stage/Bopper.hx index 262aff7bc..11fb9e499 100644 --- a/source/funkin/play/stage/Bopper.hx +++ b/source/funkin/play/stage/Bopper.hx @@ -200,12 +200,10 @@ class Bopper extends StageProp implements IPlayStateScriptedClass { if (hasDanced) { - trace('DanceRight (alternate)'); playAnimation('danceRight$idleSuffix', forceRestart); } else { - trace('DanceLeft (alternate)'); playAnimation('danceLeft$idleSuffix', forceRestart); } hasDanced = !hasDanced; From e7dd724be6e0f95324ffbdbb1d19e4a5075619da Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Tue, 25 Jun 2024 13:56:42 -0400 Subject: [PATCH 33/48] assets --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 225e248f1..4b9507525 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 225e248f148a92500a6fe90e4f10e4cd2acee782 +Subproject commit 4b95075255baeaba3585fabff7052c257856fafe From ef7612d1c25921f92f076058bc460ccae3bccac5 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 22:05:16 -0400 Subject: [PATCH 34/48] discord RPC haxelib --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index db00b878c..bedfd7447 100644 --- a/hmm.json +++ b/hmm.json @@ -5,7 +5,7 @@ "type": "git", "dir": null, "ref": "2d83fa863ef0c1eace5f1cf67c3ac315d1a3a8a5", - "url": "https://github.com/Aidan63/linc_discord-rpc" + "url": "https://github.com/FunkinCrew/linc_discord-rpc" }, { "name": "flixel", From e7f39ea08183eb0f2a2a532e2736ebfbfebd4706 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 24 Jun 2024 22:07:01 -0400 Subject: [PATCH 35/48] hxp hmm update --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index bedfd7447..8eaf24212 100644 --- a/hmm.json +++ b/hmm.json @@ -112,7 +112,7 @@ { "name": "hxp", "type": "haxelib", - "version": "1.2.2" + "version": "1.3.0" }, { "name": "json2object", From 08d35eb00d0f1a7a2c1af823fedab384e9c176f1 Mon Sep 17 00:00:00 2001 From: Hundrec Date: Tue, 25 Jun 2024 18:07:09 -0400 Subject: [PATCH 36/48] Fix tankmanpixel icon in Chart Editor --- source/funkin/play/character/CharacterData.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index 7d3d6cfb9..8b1649e26 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -305,8 +305,8 @@ class CharacterDataParser icon = "darnell"; case "senpai-angry": icon = "senpai"; - case "tankman" | "tankman-atlas": - icon = "tankmen"; + case "tankman-atlas": + icon = "tankman"; } var path = Paths.image("freeplay/icons/" + icon + "pixel"); From dad8b0815dc790181abd375b25946b05c41a1bda Mon Sep 17 00:00:00 2001 From: cyn Date: Mon, 13 May 2024 11:23:16 -0700 Subject: [PATCH 37/48] flash fix --- source/funkin/ui/MenuItem.hx | 23 +++++++++++------------ source/funkin/ui/story/LevelTitle.hx | 24 ++++++++++++------------ 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/source/funkin/ui/MenuItem.hx b/source/funkin/ui/MenuItem.hx index ba5cc066b..2a483ea78 100644 --- a/source/funkin/ui/MenuItem.hx +++ b/source/funkin/ui/MenuItem.hx @@ -11,7 +11,6 @@ class MenuItem extends FlxSpriteGroup { public var targetY:Float = 0; public var week:FlxSprite; - public var flashingInt:Int = 0; public function new(x:Float, y:Float, weekNum:Int = 0, weekType:WeekType) { @@ -30,28 +29,28 @@ class MenuItem extends FlxSpriteGroup } var isFlashing:Bool = false; + var flashTick:Float = 0; + final flashFramerate:Float = 20; public function startFlashing():Void { isFlashing = true; } - // if it runs at 60fps, fake framerate will be 6 - // if it runs at 144 fps, fake framerate will be like 14, and will update the graphic every 0.016666 * 3 seconds still??? - // so it runs basically every so many seconds, not dependant on framerate?? - // I'm still learning how math works thanks whoever is reading this lol - var fakeFramerate:Int = Math.round((1 / FlxG.elapsed) / 10); - override function update(elapsed:Float) { super.update(elapsed); y = MathUtil.coolLerp(y, (targetY * 120) + 480, 0.17); - if (isFlashing) flashingInt += 1; - - if (flashingInt % fakeFramerate >= Math.floor(fakeFramerate / 2)) week.color = 0xFF33ffff; - else - week.color = FlxColor.WHITE; + if (isFlashing) + { + flashTick += elapsed; + if (flashTick >= 1 / flashFramerate) + { + flashTick %= 1 / flashFramerate; + week.color = (week.color == FlxColor.WHITE) ? 0xFF33ffff : FlxColor.WHITE; + } + } } } diff --git a/source/funkin/ui/story/LevelTitle.hx b/source/funkin/ui/story/LevelTitle.hx index e6f989016..2be2da154 100644 --- a/source/funkin/ui/story/LevelTitle.hx +++ b/source/funkin/ui/story/LevelTitle.hx @@ -13,13 +13,10 @@ class LevelTitle extends FlxSpriteGroup public final level:Level; public var targetY:Float; - public var isFlashing:Bool = false; var title:FlxSprite; var lock:FlxSprite; - var flashingInt:Int = 0; - public function new(x:Int, y:Int, level:Level) { super(x, y); @@ -46,20 +43,23 @@ class LevelTitle extends FlxSpriteGroup } } - // if it runs at 60fps, fake framerate will be 6 - // if it runs at 144 fps, fake framerate will be like 14, and will update the graphic every 0.016666 * 3 seconds still??? - // so it runs basically every so many seconds, not dependant on framerate?? - // I'm still learning how math works thanks whoever is reading this lol - var fakeFramerate:Int = Math.round((1 / FlxG.elapsed) / 10); + public var isFlashing:Bool = false; + var flashTick:Float = 0; + final flashFramerate:Float = 20; public override function update(elapsed:Float):Void { this.y = MathUtil.coolLerp(y, targetY, 0.17); - if (isFlashing) flashingInt += 1; - if (flashingInt % fakeFramerate >= Math.floor(fakeFramerate / 2)) title.color = 0xFF33ffff; - else - title.color = FlxColor.WHITE; + if (isFlashing) + { + flashTick += elapsed; + if (flashTick >= 1 / flashFramerate) + { + flashTick %= 1 / flashFramerate; + title.color = (title.color == FlxColor.WHITE) ? 0xFF33ffff : FlxColor.WHITE; + } + } } public function showLock():Void From 6f26fbba5f55a1f7e1fbfdf918dfd4b8e806c3b2 Mon Sep 17 00:00:00 2001 From: NotHyper-474 Date: Mon, 10 Jun 2024 23:12:28 -0300 Subject: [PATCH 38/48] Fix trace --- source/funkin/ui/transition/LoadingState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/ui/transition/LoadingState.hx b/source/funkin/ui/transition/LoadingState.hx index bc26ad97a..0f2ce1076 100644 --- a/source/funkin/ui/transition/LoadingState.hx +++ b/source/funkin/ui/transition/LoadingState.hx @@ -346,7 +346,7 @@ class LoadingState extends MusicBeatSubState return 'Done precaching ${path}'; }, true); - trace("Queued ${path} for precaching"); + trace('Queued ${path} for precaching'); // FunkinSprite.cacheTexture(path); } From 32deb8d190b4adf1797ad6a1fbe2eb7d9a24e3fb Mon Sep 17 00:00:00 2001 From: gamerbross <55158797+gamerbross@users.noreply.github.com> Date: Thu, 20 Jun 2024 07:24:36 +0200 Subject: [PATCH 39/48] Fix crash after pressing F5 and coming back from stickers --- source/funkin/ui/freeplay/FreeplayState.hx | 2 +- source/funkin/ui/story/StoryMenuState.hx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 68c63efc4..19bf601e3 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -214,7 +214,7 @@ class FreeplayState extends MusicBeatSubState prepForNewRank = true; } - if (stickers != null) + if (stickers?.members != null) { stickerSubState = stickers; } diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 06a83ab4d..7707850ce 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -113,7 +113,7 @@ class StoryMenuState extends MusicBeatState { super(); - if (stickers != null) + if (stickers?.members != null) { stickerSubState = stickers; } From 7e6ef61169ed79cefec39cf5539eb96e172dda09 Mon Sep 17 00:00:00 2001 From: gamerbross <55158797+gamerbross@users.noreply.github.com> Date: Sun, 9 Jun 2024 00:29:55 +0200 Subject: [PATCH 40/48] Fix Stack Overflow if song doesn't have "normal" difficulty --- source/funkin/ui/freeplay/FreeplayState.hx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 68c63efc4..a330c5eed 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -2039,6 +2039,8 @@ class FreeplaySongData function set_currentDifficulty(value:String):String { + if (currentDifficulty == value) return value; + currentDifficulty = value; updateValues(displayedVariations); return value; From 432c7090750d7abefad68b411ac83bb99543682b Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 27 Jun 2024 19:50:38 -0400 Subject: [PATCH 41/48] Animated pixel icons for freeplay mend --- assets | 2 +- source/funkin/ui/freeplay/FreeplayState.hx | 2 +- source/funkin/ui/freeplay/SongMenuItem.hx | 42 ++++++++++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/assets b/assets index 225e248f1..c274cf6d5 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 225e248f148a92500a6fe90e4f10e4cd2acee782 +Subproject commit c274cf6d5a9d7e9e275f1f22d87d628eb379da5b diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 68c63efc4..eae123401 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1738,7 +1738,7 @@ class FreeplayState extends MusicBeatSubState dj.confirm(); grpCapsules.members[curSelected].forcePosition(); - grpCapsules.members[curSelected].songText.flickerText(); + grpCapsules.members[curSelected].confirm(); // FlxTween.color(bgDad, 0.33, 0xFFFFFFFF, 0xFF555555, {ease: FlxEase.quadOut}); FlxTween.color(pinkBack, 0.33, 0xFFFFD0D5, 0xFF171831, {ease: FlxEase.quadOut}); diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx index 65a5d2e2d..68525a5f7 100644 --- a/source/funkin/ui/freeplay/SongMenuItem.hx +++ b/source/funkin/ui/freeplay/SongMenuItem.hx @@ -543,8 +543,6 @@ class SongMenuItem extends FlxSpriteGroup charPath += 'monsterpixel'; case 'mom-car': charPath += 'mommypixel'; - case 'dad': - charPath += 'daddypixel'; case 'darnell-blazin': charPath += 'darnellpixel'; case 'senpai-angry': @@ -559,7 +557,17 @@ class SongMenuItem extends FlxSpriteGroup return; } - pixelIcon.loadGraphic(Paths.image(charPath)); + var isAnimated = openfl.utils.Assets.exists(Paths.file('images/$charPath.xml')); + + if (isAnimated) + { + pixelIcon.frames = Paths.getSparrowAtlas(charPath); + } + else + { + pixelIcon.loadGraphic(Paths.image(charPath)); + } + pixelIcon.scale.x = pixelIcon.scale.y = 2; switch (char) @@ -571,6 +579,22 @@ class SongMenuItem extends FlxSpriteGroup } // pixelIcon.origin.x = capsule.origin.x; // pixelIcon.offset.x -= pixelIcon.origin.x; + + if (isAnimated) + { + pixelIcon.active = true; + + pixelIcon.animation.addByPrefix('idle', 'idle0', 10, true); + pixelIcon.animation.addByPrefix('confirm', 'confirm0', 10, false); + pixelIcon.animation.addByPrefix('confirm-hold', 'confirm-hold0', 10, true); + + pixelIcon.animation.finishCallback = function(name:String):Void { + trace('Finish pixel animation: ${name}'); + if (name == 'confirm') pixelIcon.animation.play('confirm-hold'); + }; + + pixelIcon.animation.play('idle'); + } } var frameInTicker:Float = 0; @@ -711,6 +735,18 @@ class SongMenuItem extends FlxSpriteGroup super.update(elapsed); } + /** + * Play any animations associated with selecting this song. + */ + public function confirm():Void + { + if (songText != null) songText.flickerText(); + if (pixelIcon != null) + { + pixelIcon.animation.play('confirm'); + } + } + public function intendedY(index:Int):Float { return (index * ((height * realScaled) + 10)) + 120; From bcaeae27f12be0a170d10b69e607eec29eba740c Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 1 Jul 2024 22:07:50 -0400 Subject: [PATCH 42/48] xml fix derp --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index c274cf6d5..0dbd2e96c 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit c274cf6d5a9d7e9e275f1f22d87d628eb379da5b +Subproject commit 0dbd2e96ca25ab5966cef05db6c76fe7fb145abf From 837efcea369467bf69c98e9d7cc5d0e57ad43c64 Mon Sep 17 00:00:00 2001 From: AppleHair <95587502+AppleHair@users.noreply.github.com> Date: Mon, 10 Jun 2024 19:42:27 +0300 Subject: [PATCH 43/48] [BUGFIX] Made freeplay use the metadata to get the instrumental suffix Song previews in freeplay will now use the instrumental suffix from the current difficulty's corresponding song variation metadata instead of using the variation id as an instrumental suffix and checking only the "erect" variation. --- source/funkin/ui/freeplay/FreeplayState.hx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 3b003cf89..92410622f 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1882,7 +1882,10 @@ class FreeplayState extends MusicBeatSubState } else { - var potentiallyErect:String = (currentDifficulty == "erect") || (currentDifficulty == "nightmare") ? "-erect" : ""; + var previewSong:Null = SongRegistry.instance.fetchEntry(daSongCapsule.songData.songId); + var instSuffix:String = previewSong?.getDifficulty(currentDifficulty, + previewSong?.variations ?? Constants.DEFAULT_VARIATION_LIST)?.characters?.instrumental ?? ''; + instSuffix = (instSuffix != '') ? '-$instSuffix' : ''; FunkinSound.playMusic(daSongCapsule.songData.songId, { startingVolume: 0.0, @@ -1890,7 +1893,7 @@ class FreeplayState extends MusicBeatSubState restartTrack: false, mapTimeChanges: false, // The music metadata is not alongside the audio file so this won't work. pathsFunction: INST, - suffix: potentiallyErect, + suffix: instSuffix, partialParams: { loadPartial: true, From b1021530d8bb97e259c9443a62d306c96f54147d Mon Sep 17 00:00:00 2001 From: AppleHair <95587502+AppleHair@users.noreply.github.com> Date: Wed, 12 Jun 2024 11:23:45 +0300 Subject: [PATCH 44/48] now using getVariationsByCharId instead --- source/funkin/ui/freeplay/FreeplayState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 92410622f..69b499e80 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1884,7 +1884,7 @@ class FreeplayState extends MusicBeatSubState { var previewSong:Null = SongRegistry.instance.fetchEntry(daSongCapsule.songData.songId); var instSuffix:String = previewSong?.getDifficulty(currentDifficulty, - previewSong?.variations ?? Constants.DEFAULT_VARIATION_LIST)?.characters?.instrumental ?? ''; + previewSong?.getVariationsByCharId(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST)?.characters?.instrumental ?? ''; instSuffix = (instSuffix != '') ? '-$instSuffix' : ''; FunkinSound.playMusic(daSongCapsule.songData.songId, { From 72a00b9ae20ff2f2ee20fc58bd7c6fb7f0687696 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 18 Jun 2024 17:56:24 -0400 Subject: [PATCH 45/48] Implemented playable character registry, added Freeplay character filtering, added alt instrumental support --- assets | 2 +- source/funkin/InitState.hx | 2 + .../funkin/data/freeplay/player/CHANGELOG.md | 9 ++ .../funkin/data/freeplay/player/PlayerData.hx | 63 ++++++++ .../data/freeplay/player/PlayerRegistry.hx | 151 ++++++++++++++++++ source/funkin/modding/PolymodHandler.hx | 6 +- source/funkin/play/song/Song.hx | 34 ++-- .../handlers/ChartEditorDialogHandler.hx | 7 +- source/funkin/ui/freeplay/FreeplayState.hx | 80 +++++++--- .../freeplay/charselect/PlayableCharacter.hx | 108 +++++++++++++ .../charselect/ScriptedPlayableCharacter.hx | 8 + source/funkin/util/VersionUtil.hx | 1 - 12 files changed, 433 insertions(+), 38 deletions(-) create mode 100644 source/funkin/data/freeplay/player/CHANGELOG.md create mode 100644 source/funkin/data/freeplay/player/PlayerData.hx create mode 100644 source/funkin/data/freeplay/player/PlayerRegistry.hx create mode 100644 source/funkin/ui/freeplay/charselect/PlayableCharacter.hx create mode 100644 source/funkin/ui/freeplay/charselect/ScriptedPlayableCharacter.hx diff --git a/assets b/assets index 0dbd2e96c..bc1650ba7 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 0dbd2e96ca25ab5966cef05db6c76fe7fb145abf +Subproject commit bc1650ba789d675683a8c0cc27b1e2a42cb686cf diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 49b15ddf6..c2a56bdc2 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -1,5 +1,6 @@ package funkin; +import funkin.data.freeplay.player.PlayerRegistry; import funkin.ui.debug.charting.ChartEditorState; import funkin.ui.transition.LoadingState; import flixel.FlxState; @@ -164,6 +165,7 @@ class InitState extends FlxState SongRegistry.instance.loadEntries(); LevelRegistry.instance.loadEntries(); NoteStyleRegistry.instance.loadEntries(); + PlayerRegistry.instance.loadEntries(); ConversationRegistry.instance.loadEntries(); DialogueBoxRegistry.instance.loadEntries(); SpeakerRegistry.instance.loadEntries(); diff --git a/source/funkin/data/freeplay/player/CHANGELOG.md b/source/funkin/data/freeplay/player/CHANGELOG.md new file mode 100644 index 000000000..7a31e11ca --- /dev/null +++ b/source/funkin/data/freeplay/player/CHANGELOG.md @@ -0,0 +1,9 @@ +# Freeplay Playable Character Data Schema Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] +Initial release. diff --git a/source/funkin/data/freeplay/player/PlayerData.hx b/source/funkin/data/freeplay/player/PlayerData.hx new file mode 100644 index 000000000..d7b814584 --- /dev/null +++ b/source/funkin/data/freeplay/player/PlayerData.hx @@ -0,0 +1,63 @@ +package funkin.data.freeplay.player; + +import funkin.data.animation.AnimationData; + +@:nullSafety +class PlayerData +{ + /** + * The sematic version number of the player data JSON format. + * Supports fancy comparisons like NPM does it's neat. + */ + @:default(funkin.data.freeplay.player.PlayerRegistry.PLAYER_DATA_VERSION) + public var version:String; + + /** + * A readable name for this playable character. + */ + public var name:String = 'Unknown'; + + /** + * The character IDs this character is associated with. + * Only songs that use these characters will show up in Freeplay. + */ + @:default([]) + public var ownedChars:Array = []; + + /** + * Whether to show songs with character IDs that aren't associated with any specific character. + */ + @:optional + @:default(false) + public var showUnownedChars:Bool = false; + + /** + * Whether this character is unlocked by default. + * Use a ScriptedPlayableCharacter to add custom logic. + */ + @:optional + @:default(true) + public var unlocked:Bool = true; + + public function new() + { + this.version = PlayerRegistry.PLAYER_DATA_VERSION; + } + + /** + * Convert this StageData into a JSON string. + */ + public function serialize(pretty:Bool = true):String + { + // Update generatedBy and version before writing. + updateVersionToLatest(); + + var writer = new json2object.JsonWriter(); + return writer.write(this, pretty ? ' ' : null); + } + + public function updateVersionToLatest():Void + { + this.version = PlayerRegistry.PLAYER_DATA_VERSION; + } +} diff --git a/source/funkin/data/freeplay/player/PlayerRegistry.hx b/source/funkin/data/freeplay/player/PlayerRegistry.hx new file mode 100644 index 000000000..3de9efd41 --- /dev/null +++ b/source/funkin/data/freeplay/player/PlayerRegistry.hx @@ -0,0 +1,151 @@ +package funkin.data.freeplay.player; + +import funkin.data.freeplay.player.PlayerData; +import funkin.ui.freeplay.charselect.PlayableCharacter; +import funkin.ui.freeplay.charselect.ScriptedPlayableCharacter; + +class PlayerRegistry extends BaseRegistry +{ + /** + * The current version string for the stage data format. + * Handle breaking changes by incrementing this value + * and adding migration to the `migratePlayerData()` function. + */ + public static final PLAYER_DATA_VERSION:thx.semver.Version = "1.0.0"; + + public static final PLAYER_DATA_VERSION_RULE:thx.semver.VersionRule = "1.0.x"; + + public static var instance(get, never):PlayerRegistry; + static var _instance:Null = null; + + static function get_instance():PlayerRegistry + { + if (_instance == null) _instance = new PlayerRegistry(); + return _instance; + } + + /** + * A mapping between stage character IDs and Freeplay playable character IDs. + */ + var ownedCharacterIds:Map = []; + + public function new() + { + super('PLAYER', 'players', PLAYER_DATA_VERSION_RULE); + } + + public override function loadEntries():Void + { + super.loadEntries(); + + for (playerId in listEntryIds()) + { + var player = fetchEntry(playerId); + if (player == null) continue; + + var currentPlayerCharIds = player.getOwnedCharacterIds(); + for (characterId in currentPlayerCharIds) + { + ownedCharacterIds.set(characterId, playerId); + } + } + + log('Loaded ${countEntries()} playable characters with ${ownedCharacterIds.size()} associations.'); + } + + /** + * Get the playable character associated with a given stage character. + * @param characterId The stage character ID. + * @return The playable character. + */ + public function getCharacterOwnerId(characterId:String):String + { + return ownedCharacterIds[characterId]; + } + + /** + * Return true if the given stage character is associated with a specific playable character. + * If so, the level should only appear if that character is selected in Freeplay. + * @param characterId The stage character ID. + * @return Whether the character is owned by any one character. + */ + public function isCharacterOwned(characterId:String):Bool + { + return ownedCharacterIds.exists(characterId); + } + + /** + * Read, parse, and validate the JSON data and produce the corresponding data object. + */ + public function parseEntryData(id:String):Null + { + // JsonParser does not take type parameters, + // otherwise this function would be in BaseRegistry. + var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; + + switch (loadEntryFile(id)) + { + case {fileName: fileName, contents: contents}: + parser.fromJson(contents, fileName); + default: + return null; + } + + if (parser.errors.length > 0) + { + printErrors(parser.errors, id); + return null; + } + return parser.value; + } + + /** + * Parse and validate the JSON data and produce the corresponding data object. + * + * NOTE: Must be implemented on the implementation class. + * @param contents The JSON as a string. + * @param fileName An optional file name for error reporting. + */ + public function parseEntryDataRaw(contents:String, ?fileName:String):Null + { + var parser = new json2object.JsonParser(); + parser.ignoreUnknownVariables = false; + parser.fromJson(contents, fileName); + + if (parser.errors.length > 0) + { + printErrors(parser.errors, fileName); + return null; + } + return parser.value; + } + + function createScriptedEntry(clsName:String):PlayableCharacter + { + return ScriptedPlayableCharacter.init(clsName, "unknown"); + } + + function getScriptedClassNames():Array + { + return ScriptedPlayableCharacter.listScriptClasses(); + } + + /** + * A list of all the playable characters from the base game, in order. + */ + public function listBaseGamePlayerIds():Array + { + return ["bf", "pico"]; + } + + /** + * A list of all installed playable characters that are not from the base game. + */ + public function listModdedPlayerIds():Array + { + return listEntryIds().filter(function(id:String):Bool { + return listBaseGamePlayerIds().indexOf(id) == -1; + }); + } +} diff --git a/source/funkin/modding/PolymodHandler.hx b/source/funkin/modding/PolymodHandler.hx index ae754b780..c352aa606 100644 --- a/source/funkin/modding/PolymodHandler.hx +++ b/source/funkin/modding/PolymodHandler.hx @@ -8,6 +8,7 @@ import funkin.data.event.SongEventRegistry; import funkin.data.story.level.LevelRegistry; import funkin.data.notestyle.NoteStyleRegistry; import funkin.data.song.SongRegistry; +import funkin.data.freeplay.player.PlayerRegistry; import funkin.data.stage.StageRegistry; import funkin.data.freeplay.album.AlbumRegistry; import funkin.modding.module.ModuleHandler; @@ -369,15 +370,18 @@ class PolymodHandler // These MUST be imported at the top of the file and not referred to by fully qualified name, // to ensure build macros work properly. + SongEventRegistry.loadEventCache(); + SongRegistry.instance.loadEntries(); LevelRegistry.instance.loadEntries(); NoteStyleRegistry.instance.loadEntries(); - SongEventRegistry.loadEventCache(); + PlayerRegistry.instance.loadEntries(); ConversationRegistry.instance.loadEntries(); DialogueBoxRegistry.instance.loadEntries(); SpeakerRegistry.instance.loadEntries(); AlbumRegistry.instance.loadEntries(); StageRegistry.instance.loadEntries(); + CharacterDataParser.loadCharacterCache(); // TODO: Migrate characters to BaseRegistry. ModuleHandler.loadModuleCache(); } diff --git a/source/funkin/play/song/Song.hx b/source/funkin/play/song/Song.hx index dde5ee7b8..91d35d8fa 100644 --- a/source/funkin/play/song/Song.hx +++ b/source/funkin/play/song/Song.hx @@ -14,6 +14,7 @@ import funkin.data.song.SongData.SongTimeFormat; import funkin.data.song.SongRegistry; import funkin.modding.IScriptedClass.IPlayStateScriptedClass; import funkin.modding.events.ScriptEvent; +import funkin.ui.freeplay.charselect.PlayableCharacter; import funkin.util.SortUtil; import openfl.utils.Assets; @@ -401,11 +402,11 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry):Null + public function getFirstValidVariation(?diffId:String, ?currentCharacter:PlayableCharacter, ?possibleVariations:Array):Null { if (possibleVariations == null) { - possibleVariations = variations; + possibleVariations = getVariationsByCharacter(currentCharacter); possibleVariations.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_VARIATION_LIST)); } if (diffId == null) diffId = listDifficulties(null, possibleVariations)[0]; @@ -422,22 +423,29 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry + public function getVariationsByCharacter(?char:PlayableCharacter):Array { - if (charId == null) charId = Constants.DEFAULT_CHARACTER; + if (char == null) return variations; - if (variations.contains(charId)) + var result = []; + trace('Evaluating variations for ${this.id} ${char.id}: ${this.variations}'); + for (variation in variations) { - return [charId]; - } - else - { - // TODO: How to exclude character variations while keeping other custom variations? - return variations; + var metadata = _metadata.get(variation); + + var playerCharId = metadata?.playData?.characters?.player; + if (playerCharId == null) continue; + + if (char.shouldShowCharacter(playerCharId)) + { + result.push(variation); + } } + + return result; } /** @@ -455,6 +463,8 @@ class Song implements IPlayStateScriptedClass implements IRegistryEntry = song.listDifficulties(displayedVariations, false); - trace(availableDifficultiesForSong); + trace('Available Difficulties: $availableDifficultiesForSong'); if (availableDifficultiesForSong.length == 0) continue; songs.push(new FreeplaySongData(levelId, songId, song, displayedVariations)); @@ -454,7 +458,7 @@ class FreeplayState extends MusicBeatSubState }); // TODO: Replace this. - if (currentCharacter == 'pico') dj.visible = false; + if (currentCharacterId == 'pico') dj.visible = false; add(dj); @@ -1195,6 +1199,16 @@ class FreeplayState extends MusicBeatSubState rankAnimStart(fromResultsParams); } + if (FlxG.keys.justPressed.P) + { + FlxG.switchState(FreeplayState.build( + { + { + character: currentCharacterId == "pico" ? "bf" : "pico", + } + })); + } + // if (FlxG.keys.justPressed.H) // { // rankDisplayNew(fromResultsParams); @@ -1302,9 +1316,9 @@ class FreeplayState extends MusicBeatSubState { if (busy) return; - var upP:Bool = controls.UI_UP_P && !FlxG.keys.pressed.CONTROL; - var downP:Bool = controls.UI_DOWN_P && !FlxG.keys.pressed.CONTROL; - var accepted:Bool = controls.ACCEPT && !FlxG.keys.pressed.CONTROL; + var upP:Bool = controls.UI_UP_P; + var downP:Bool = controls.UI_DOWN_P; + var accepted:Bool = controls.ACCEPT; if (FlxG.onMobile) { @@ -1378,7 +1392,7 @@ class FreeplayState extends MusicBeatSubState } #end - if (!FlxG.keys.pressed.CONTROL && (controls.UI_UP || controls.UI_DOWN)) + if ((controls.UI_UP || controls.UI_DOWN)) { if (spamming) { @@ -1440,13 +1454,13 @@ class FreeplayState extends MusicBeatSubState } #end - if (controls.UI_LEFT_P && !FlxG.keys.pressed.CONTROL) + if (controls.UI_LEFT_P) { dj.resetAFKTimer(); changeDiff(-1); generateSongList(currentFilter, true); } - if (controls.UI_RIGHT_P && !FlxG.keys.pressed.CONTROL) + if (controls.UI_RIGHT_P) { dj.resetAFKTimer(); changeDiff(1); @@ -1720,7 +1734,7 @@ class FreeplayState extends MusicBeatSubState return; } var targetDifficultyId:String = currentDifficulty; - var targetVariation:String = targetSong.getFirstValidVariation(targetDifficultyId); + var targetVariation:String = targetSong.getFirstValidVariation(targetDifficultyId, currentCharacter); PlayStatePlaylist.campaignId = cap.songData.levelId; var targetDifficulty:SongDifficulty = targetSong.getDifficulty(targetDifficultyId, targetVariation); @@ -1730,8 +1744,18 @@ class FreeplayState extends MusicBeatSubState return; } - // TODO: Change this with alternate instrumentals - var targetInstId:String = targetDifficulty.characters.instrumental; + var baseInstrumentalId:String = targetDifficulty?.characters?.instrumental ?? ''; + var altInstrumentalIds:Array = targetDifficulty?.characters?.altInstrumentals ?? []; + + var targetInstId:String = baseInstrumentalId; + + // TODO: Make this a UI element. + #if (debug || FORCE_DEBUG_VERSION) + if (altInstrumentalIds.length > 0 && FlxG.keys.pressed.CONTROL) + { + targetInstId = altInstrumentalIds[0]; + } + #end // Visual and audio effects. FunkinSound.playOnce(Paths.sound('confirmMenu')); @@ -1883,9 +1907,23 @@ class FreeplayState extends MusicBeatSubState else { var previewSong:Null = SongRegistry.instance.fetchEntry(daSongCapsule.songData.songId); - var instSuffix:String = previewSong?.getDifficulty(currentDifficulty, - previewSong?.getVariationsByCharId(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST)?.characters?.instrumental ?? ''; + var songDifficulty = previewSong?.getDifficulty(currentDifficulty, + previewSong?.getVariationsByCharacter(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST); + var baseInstrumentalId:String = songDifficulty?.characters?.instrumental ?? ''; + var altInstrumentalIds:Array = songDifficulty?.characters?.altInstrumentals ?? []; + + var instSuffix:String = baseInstrumentalId; + + // TODO: Make this a UI element. + #if (debug || FORCE_DEBUG_VERSION) + if (altInstrumentalIds.length > 0 && FlxG.keys.pressed.CONTROL) + { + instSuffix = altInstrumentalIds[0]; + } + #end + instSuffix = (instSuffix != '') ? '-$instSuffix' : ''; + FunkinSound.playMusic(daSongCapsule.songData.songId, { startingVolume: 0.0, @@ -1914,7 +1952,7 @@ class FreeplayState extends MusicBeatSubState public static function build(?params:FreeplayStateParams, ?stickers:StickerSubState):MusicBeatState { var result:MainMenuState; - if (params?.fromResults.playRankAnim) result = new MainMenuState(true); + if (params?.fromResults?.playRankAnim) result = new MainMenuState(true); else result = new MainMenuState(false); @@ -1952,8 +1990,8 @@ class DifficultySelector extends FlxSprite override function update(elapsed:Float):Void { - if (flipX && controls.UI_RIGHT_P && !FlxG.keys.pressed.CONTROL) moveShitDown(); - if (!flipX && controls.UI_LEFT_P && !FlxG.keys.pressed.CONTROL) moveShitDown(); + if (flipX && controls.UI_RIGHT_P) moveShitDown(); + if (!flipX && controls.UI_LEFT_P) moveShitDown(); super.update(elapsed); } diff --git a/source/funkin/ui/freeplay/charselect/PlayableCharacter.hx b/source/funkin/ui/freeplay/charselect/PlayableCharacter.hx new file mode 100644 index 000000000..743345004 --- /dev/null +++ b/source/funkin/ui/freeplay/charselect/PlayableCharacter.hx @@ -0,0 +1,108 @@ +package funkin.ui.freeplay.charselect; + +import funkin.data.IRegistryEntry; +import funkin.data.freeplay.player.PlayerData; +import funkin.data.freeplay.player.PlayerRegistry; + +/** + * An object used to retrieve data about a playable character (also known as "weeks"). + * Can be scripted to override each function, for custom behavior. + */ +class PlayableCharacter implements IRegistryEntry +{ + /** + * The ID of the playable character. + */ + public final id:String; + + /** + * Playable character data as parsed from the JSON file. + */ + public final _data:PlayerData; + + /** + * @param id The ID of the JSON file to parse. + */ + public function new(id:String) + { + this.id = id; + _data = _fetchData(id); + + if (_data == null) + { + throw 'Could not parse playable character data for id: $id'; + } + } + + /** + * Retrieve the readable name of the playable character. + */ + public function getName():String + { + // TODO: Maybe add localization support? + return _data.name; + } + + /** + * Retrieve the list of stage character IDs associated with this playable character. + * @return The list of associated character IDs + */ + public function getOwnedCharacterIds():Array + { + return _data.ownedChars; + } + + /** + * Return `true` if, when this character is selected in Freeplay, + * songs unassociated with a specific character should appear. + */ + public function shouldShowUnownedChars():Bool + { + return _data.showUnownedChars; + } + + public function shouldShowCharacter(id:String):Bool + { + if (_data.ownedChars.contains(id)) + { + return true; + } + + if (_data.showUnownedChars) + { + var result = !PlayerRegistry.instance.isCharacterOwned(id); + return result; + } + + return false; + } + + /** + * Returns whether this character is unlocked. + */ + public function isUnlocked():Bool + { + return _data.unlocked; + } + + /** + * Called when the character is destroyed. + * TODO: Document when this gets called + */ + public function destroy():Void {} + + public function toString():String + { + return 'PlayableCharacter($id)'; + } + + /** + * Retrieve and parse the JSON data for a playable character by ID. + * @param id The ID of the character + * @return The parsed player data, or null if not found or invalid + */ + static function _fetchData(id:String):Null + { + return PlayerRegistry.instance.parseEntryDataWithMigration(id, PlayerRegistry.instance.fetchEntryVersion(id)); + } +} diff --git a/source/funkin/ui/freeplay/charselect/ScriptedPlayableCharacter.hx b/source/funkin/ui/freeplay/charselect/ScriptedPlayableCharacter.hx new file mode 100644 index 000000000..f75a58092 --- /dev/null +++ b/source/funkin/ui/freeplay/charselect/ScriptedPlayableCharacter.hx @@ -0,0 +1,8 @@ +package funkin.ui.freeplay.charselect; + +/** + * A script that can be tied to a PlayableCharacter. + * Create a scripted class that extends PlayableCharacter to use this. + */ +@:hscriptClass +class ScriptedPlayableCharacter extends funkin.ui.freeplay.charselect.PlayableCharacter implements polymod.hscript.HScriptedClass {} diff --git a/source/funkin/util/VersionUtil.hx b/source/funkin/util/VersionUtil.hx index 832ce008a..9bf46a188 100644 --- a/source/funkin/util/VersionUtil.hx +++ b/source/funkin/util/VersionUtil.hx @@ -24,7 +24,6 @@ class VersionUtil try { var versionRaw:thx.semver.Version.SemVer = version; - trace('${versionRaw} satisfies (${versionRule})? ${version.satisfies(versionRule)}'); return version.satisfies(versionRule); } catch (e) From 5c2bad888d520117d81264d4690dd046cb1d208b Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Tue, 18 Jun 2024 20:07:27 -0400 Subject: [PATCH 46/48] Make Boyfriend DJ animations data driven --- .../funkin/data/freeplay/player/PlayerData.hx | 93 +++++ source/funkin/ui/freeplay/DJBoyfriend.hx | 371 ------------------ source/funkin/ui/freeplay/FreeplayDJ.hx | 369 +++++++++++++++++ source/funkin/ui/freeplay/FreeplayState.hx | 24 +- .../freeplay/charselect/PlayableCharacter.hx | 5 + source/funkin/util/tools/MapTools.hx | 4 + 6 files changed, 483 insertions(+), 383 deletions(-) delete mode 100644 source/funkin/ui/freeplay/DJBoyfriend.hx create mode 100644 source/funkin/ui/freeplay/FreeplayDJ.hx diff --git a/source/funkin/data/freeplay/player/PlayerData.hx b/source/funkin/data/freeplay/player/PlayerData.hx index d7b814584..10fc54b78 100644 --- a/source/funkin/data/freeplay/player/PlayerData.hx +++ b/source/funkin/data/freeplay/player/PlayerData.hx @@ -31,6 +31,12 @@ class PlayerData @:default(false) public var showUnownedChars:Bool = false; + /** + * Data for displaying this character in the Freeplay menu. + * If null, display no DJ. + */ + public var freeplayDJ:Null = null; + /** * Whether this character is unlocked by default. * Use a ScriptedPlayableCharacter to add custom logic. @@ -61,3 +67,90 @@ class PlayerData this.version = PlayerRegistry.PLAYER_DATA_VERSION; } } + +class PlayerFreeplayDJData +{ + var assetPath:String; + var animations:Array; + + @:jignored + var animationMap:Map; + + @:optional + var cartoon:Null; + + public function new() + { + animationMap = new Map(); + } + + function mapAnimations() + { + if (animationMap == null) animationMap = new Map(); + + animationMap.clear(); + for (anim in animations) + { + animationMap.set(anim.name, anim); + } + } + + public function getAtlasPath():String + { + return Paths.animateAtlas(assetPath); + } + + public function getAnimationPrefix(name:String):Null + { + if (animationMap.size() == 0) mapAnimations(); + + var anim = animationMap.get(name); + if (anim == null) return null; + return anim.prefix; + } + + public function getAnimationOffsets(name:String):Null> + { + if (animationMap.size() == 0) mapAnimations(); + + var anim = animationMap.get(name); + if (anim == null) return null; + return anim.offsets; + } + + // TODO: These should really be frame labels, ehe. + + public function getCartoonSoundClickFrame():Int + { + return cartoon?.soundClickFrame ?? 80; + } + + public function getCartoonSoundCartoonFrame():Int + { + return cartoon?.soundCartoonFrame ?? 85; + } + + public function getCartoonLoopBlinkFrame():Int + { + return cartoon?.loopBlinkFrame ?? 112; + } + + public function getCartoonLoopFrame():Int + { + return cartoon?.loopFrame ?? 166; + } + + public function getCartoonChannelChangeFrame():Int + { + return cartoon?.channelChangeFrame ?? 60; + } +} + +typedef PlayerFreeplayDJCartoonData = +{ + var soundClickFrame:Int; + var soundCartoonFrame:Int; + var loopBlinkFrame:Int; + var loopFrame:Int; + var channelChangeFrame:Int; +} diff --git a/source/funkin/ui/freeplay/DJBoyfriend.hx b/source/funkin/ui/freeplay/DJBoyfriend.hx deleted file mode 100644 index bbf043dd4..000000000 --- a/source/funkin/ui/freeplay/DJBoyfriend.hx +++ /dev/null @@ -1,371 +0,0 @@ -package funkin.ui.freeplay; - -import flixel.FlxSprite; -import flixel.util.FlxSignal; -import funkin.util.assets.FlxAnimationUtil; -import funkin.graphics.adobeanimate.FlxAtlasSprite; -import funkin.audio.FunkinSound; -import flixel.util.FlxTimer; -import funkin.audio.FunkinSound; -import funkin.audio.FlxStreamSound; - -class DJBoyfriend extends FlxAtlasSprite -{ - // Represents the sprite's current status. - // Without state machines I would have driven myself crazy years ago. - public var currentState:DJBoyfriendState = Intro; - - // A callback activated when the intro animation finishes. - public var onIntroDone:FlxSignal = new FlxSignal(); - - // A callback activated when Boyfriend gets spooked. - public var onSpook:FlxSignal = new FlxSignal(); - - // playAnim stolen from Character.hx, cuz im lazy lol! - // TODO: Switch this class to use SwagSprite instead. - public var animOffsets:Map>; - - var gotSpooked:Bool = false; - - static final SPOOK_PERIOD:Float = 60.0; - static final TV_PERIOD:Float = 120.0; - - // Time since dad last SPOOKED you. - var timeSinceSpook:Float = 0; - - public function new(x:Float, y:Float) - { - super(x, y, Paths.animateAtlas("freeplay/freeplay-boyfriend", "preload")); - - animOffsets = new Map>(); - - anim.callback = function(name, number) { - switch (name) - { - case "Boyfriend DJ watchin tv OG": - if (number == 80) - { - FunkinSound.playOnce(Paths.sound('remote_click')); - } - if (number == 85) - { - runTvLogic(); - } - default: - } - }; - - setupAnimations(); - - FlxG.debugger.track(this); - FlxG.console.registerObject("dj", this); - - anim.onComplete = onFinishAnim; - - FlxG.console.registerFunction("tv", function() { - currentState = TV; - }); - } - - /* - [remote hand under,boyfriend top head,brim piece,arm cringe l,red lazer,dj arm in,bf fist pump arm,hand raised right,forearm left,fist shaking,bf smile eyes closed face,arm cringe r,bf clenched face,face shrug,boyfriend falling,blue tint 1,shirt sleeve,bf clenched fist,head BF relaxed,blue tint 2,hand down left,blue tint 3,blue tint 4,head less smooshed,blue tint 5,boyfriend freeplay,BF head slight turn,blue tint 6,arm shrug l,blue tint 7,shoulder raised w sleeve,blue tint 8,fist pump face,blue tint 9,foot rested light,hand turnaround,arm chill right,Boyfriend DJ,arm shrug r,head back bf,hat top piece,dad bod,face surprise snap,Boyfriend DJ fist pump,office chair,foot rested right,chest down,office chair upright,body chill,bf dj afk,head mouth open dad,BF Head defalt HAIR BLOWING,hand shrug l,face piece,foot wag,turn table,shoulder up left,turntable lights,boyfriend dj body shirt blowing,body chunk turned,hand down right,dj arm out,hand shrug r,body chest out,rave hand,palm,chill face default,head back semi bf,boyfriend bottom head,DJ arm,shoulder right dad,bf surprise,boyfriend dj body,hs1,Boyfriend DJ watchin tv OG,spinning disk,hs2,arm chill left,boyfriend dj intro,hs3,hs4,chill face extra,hs5,remote hand upright,hs6,pant over table,face surprise,bf arm peace,arm turnaround,bf eyes 1,arm slammed table,eye squit,leg BF,head mid piece,arm backing,arm swoopin in,shoe right lowering,forearm right,hand out,blue tint 10,body falling back,remote thumb press,shoulder,hair spike single,bf bent - arm,crt,foot raised right,dad hand,chill face 1,chill face 2,clenched fist,head SMOOSHED,shoulder left dad,df1,body chunk upright,df2,df3,df4,hat front piece,df5,foot rested right 2,hand in,arm spun,shoe raised left,bf 1 finger hand,bf mouth 1,Boyfriend DJ confirm,forearm down ,hand raised left,remote thumb up] - */ - override public function listAnimations():Array - { - var anims:Array = []; - @:privateAccess - for (animKey in anim.symbolDictionary) - { - anims.push(animKey.name); - } - return anims; - } - - var lowPumpLoopPoint:Int = 4; - - public override function update(elapsed:Float):Void - { - super.update(elapsed); - - switch (currentState) - { - case Intro: - // Play the intro animation then leave this state immediately. - if (getCurrentAnimation() != 'boyfriend dj intro') playFlashAnimation('boyfriend dj intro', true); - timeSinceSpook = 0; - case Idle: - // We are in this state the majority of the time. - if (getCurrentAnimation() != 'Boyfriend DJ') - { - playFlashAnimation('Boyfriend DJ', true); - } - - if (getCurrentAnimation() == 'Boyfriend DJ' && this.isLoopFinished()) - { - if (timeSinceSpook >= SPOOK_PERIOD && !gotSpooked) - { - currentState = Spook; - } - else if (timeSinceSpook >= TV_PERIOD) - { - currentState = TV; - } - } - timeSinceSpook += elapsed; - case Confirm: - if (getCurrentAnimation() != 'Boyfriend DJ confirm') playFlashAnimation('Boyfriend DJ confirm', false); - timeSinceSpook = 0; - case PumpIntro: - if (getCurrentAnimation() != 'Boyfriend DJ fist pump') playFlashAnimation('Boyfriend DJ fist pump', false); - if (getCurrentAnimation() == 'Boyfriend DJ fist pump' && anim.curFrame >= 4) - { - anim.play("Boyfriend DJ fist pump", true, false, 0); - } - case FistPump: - - case Spook: - if (getCurrentAnimation() != 'bf dj afk') - { - onSpook.dispatch(); - playFlashAnimation('bf dj afk', false); - gotSpooked = true; - } - timeSinceSpook = 0; - case TV: - if (getCurrentAnimation() != 'Boyfriend DJ watchin tv OG') playFlashAnimation('Boyfriend DJ watchin tv OG', true); - timeSinceSpook = 0; - default: - // I shit myself. - } - - if (FlxG.keys.pressed.CONTROL) - { - if (FlxG.keys.justPressed.LEFT) - { - this.offsetX -= FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0); - } - - if (FlxG.keys.justPressed.RIGHT) - { - this.offsetX += FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0); - } - - if (FlxG.keys.justPressed.UP) - { - this.offsetY -= FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0); - } - - if (FlxG.keys.justPressed.DOWN) - { - this.offsetY += FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0); - } - - if (FlxG.keys.justPressed.SPACE) - { - currentState = (currentState == Idle ? TV : Idle); - } - } - } - - function onFinishAnim():Void - { - var name = anim.curSymbol.name; - switch (name) - { - case "boyfriend dj intro": - // trace('Finished intro'); - currentState = Idle; - onIntroDone.dispatch(); - case "Boyfriend DJ": - // trace('Finished idle'); - case "bf dj afk": - // trace('Finished spook'); - currentState = Idle; - case "Boyfriend DJ confirm": - - case "Boyfriend DJ fist pump": - currentState = Idle; - - case "Boyfriend DJ loss reaction 1": - currentState = Idle; - - case "Boyfriend DJ watchin tv OG": - var frame:Int = FlxG.random.bool(33) ? 112 : 166; - - // BF switches channels when the video ends, or at a 10% chance each time his idle loops. - if (FlxG.random.bool(5)) - { - frame = 60; - // boyfriend switches channel code? - // runTvLogic(); - } - trace('Replay idle: ${frame}'); - anim.play("Boyfriend DJ watchin tv OG", true, false, frame); - // trace('Finished confirm'); - } - } - - public function resetAFKTimer():Void - { - timeSinceSpook = 0; - gotSpooked = false; - } - - var offsetX:Float = 0.0; - var offsetY:Float = 0.0; - - function setupAnimations():Void - { - // Intro - addOffset('boyfriend dj intro', 8.0 - 1.3, 3.0 - 0.4); - - // Idle - addOffset('Boyfriend DJ', 0, 0); - - // Confirm - addOffset('Boyfriend DJ confirm', 0, 0); - - // AFK: Spook - addOffset('bf dj afk', 649.5, 58.5); - - // AFK: TV - addOffset('Boyfriend DJ watchin tv OG', 0, 0); - } - - var cartoonSnd:Null = null; - - public var playingCartoon:Bool = false; - - public function runTvLogic() - { - if (cartoonSnd == null) - { - // tv is OFF, but getting turned on - FunkinSound.playOnce(Paths.sound('tv_on'), 1.0, function() { - loadCartoon(); - }); - } - else - { - // plays it smidge after the click - FunkinSound.playOnce(Paths.sound('channel_switch'), 1.0, function() { - cartoonSnd.destroy(); - loadCartoon(); - }); - } - - // loadCartoon(); - } - - function loadCartoon() - { - cartoonSnd = FunkinSound.load(Paths.sound(getRandomFlashToon()), 1.0, false, true, true, function() { - anim.play("Boyfriend DJ watchin tv OG", true, false, 60); - }); - - // Fade out music to 40% volume over 1 second. - // This helps make the TV a bit more audible. - FlxG.sound.music.fadeOut(1.0, 0.1); - - // Play the cartoon at a random time between the start and 5 seconds from the end. - cartoonSnd.time = FlxG.random.float(0, Math.max(cartoonSnd.length - (5 * Constants.MS_PER_SEC), 0.0)); - } - - final cartoonList:Array = openfl.utils.Assets.list().filter(function(path) return path.startsWith("assets/sounds/cartoons/")); - - function getRandomFlashToon():String - { - var randomFile = FlxG.random.getObject(cartoonList); - - // Strip folder prefix - randomFile = randomFile.replace("assets/sounds/", ""); - // Strip file extension - randomFile = randomFile.substring(0, randomFile.length - 4); - - return randomFile; - } - - public function confirm():Void - { - currentState = Confirm; - } - - public function fistPump():Void - { - currentState = PumpIntro; - } - - public function pumpFist():Void - { - currentState = FistPump; - anim.play("Boyfriend DJ fist pump", true, false, 4); - } - - public function pumpFistBad():Void - { - currentState = FistPump; - anim.play("Boyfriend DJ loss reaction 1", true, false, 4); - } - - public inline function addOffset(name:String, x:Float = 0, y:Float = 0) - { - animOffsets[name] = [x, y]; - } - - override public function getCurrentAnimation():String - { - if (this.anim == null || this.anim.curSymbol == null) return ""; - return this.anim.curSymbol.name; - } - - public function playFlashAnimation(id:String, ?Force:Bool = false, ?Reverse:Bool = false, ?Frame:Int = 0):Void - { - anim.play(id, Force, Reverse, Frame); - applyAnimOffset(); - } - - function applyAnimOffset() - { - var AnimName = getCurrentAnimation(); - var daOffset = animOffsets.get(AnimName); - if (animOffsets.exists(AnimName)) - { - var xValue = daOffset[0]; - var yValue = daOffset[1]; - if (AnimName == "Boyfriend DJ watchin tv OG") - { - xValue += offsetX; - yValue += offsetY; - } - - offset.set(xValue, yValue); - } - else - { - offset.set(0, 0); - } - } - - public override function destroy():Void - { - super.destroy(); - - if (cartoonSnd != null) - { - cartoonSnd.destroy(); - cartoonSnd = null; - } - } -} - -enum DJBoyfriendState -{ - Intro; - Idle; - Confirm; - PumpIntro; - FistPump; - Spook; - TV; -} diff --git a/source/funkin/ui/freeplay/FreeplayDJ.hx b/source/funkin/ui/freeplay/FreeplayDJ.hx new file mode 100644 index 000000000..f9effe793 --- /dev/null +++ b/source/funkin/ui/freeplay/FreeplayDJ.hx @@ -0,0 +1,369 @@ +package funkin.ui.freeplay; + +import flixel.FlxSprite; +import flixel.util.FlxSignal; +import funkin.util.assets.FlxAnimationUtil; +import funkin.graphics.adobeanimate.FlxAtlasSprite; +import funkin.audio.FunkinSound; +import flixel.util.FlxTimer; +import funkin.data.freeplay.player.PlayerRegistry; +import funkin.data.freeplay.player.PlayerData.PlayerFreeplayDJData; +import funkin.audio.FunkinSound; +import funkin.audio.FlxStreamSound; + +class FreeplayDJ extends FlxAtlasSprite +{ + // Represents the sprite's current status. + // Without state machines I would have driven myself crazy years ago. + public var currentState:DJBoyfriendState = Intro; + + // A callback activated when the intro animation finishes. + public var onIntroDone:FlxSignal = new FlxSignal(); + + // A callback activated when the idle easter egg plays. + public var onIdleEasterEgg:FlxSignal = new FlxSignal(); + + var seenIdleEasterEgg:Bool = false; + + static final IDLE_EGG_PERIOD:Float = 60.0; + static final IDLE_CARTOON_PERIOD:Float = 120.0; + + // Time since last special idle animation you. + var timeIdling:Float = 0; + + final characterId:String = Constants.DEFAULT_CHARACTER; + final playableCharData:PlayerFreeplayDJData; + + public function new(x:Float, y:Float, characterId:String) + { + this.characterId = characterId; + + var playableChar = PlayerRegistry.instance.fetchEntry(characterId); + playableCharData = playableChar.getFreeplayDJData(); + + super(x, y, playableCharData.getAtlasPath()); + + anim.callback = function(name, number) { + if (name == playableCharData.getAnimationPrefix('cartoon')) + { + if (number == playableCharData.getCartoonSoundClickFrame()) + { + FunkinSound.playOnce(Paths.sound('remote_click')); + } + if (number == playableCharData.getCartoonSoundCartoonFrame()) + { + runTvLogic(); + } + } + }; + + FlxG.debugger.track(this); + FlxG.console.registerObject("dj", this); + + anim.onComplete = onFinishAnim; + + FlxG.console.registerFunction("freeplayCartoon", function() { + currentState = Cartoon; + }); + } + + override public function listAnimations():Array + { + var anims:Array = []; + @:privateAccess + for (animKey in anim.symbolDictionary) + { + anims.push(animKey.name); + } + return anims; + } + + var lowPumpLoopPoint:Int = 4; + + public override function update(elapsed:Float):Void + { + super.update(elapsed); + + switch (currentState) + { + case Intro: + // Play the intro animation then leave this state immediately. + var animPrefix = playableCharData.getAnimationPrefix('intro'); + if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, true); + timeIdling = 0; + case Idle: + // We are in this state the majority of the time. + var animPrefix = playableCharData.getAnimationPrefix('idle'); + if (getCurrentAnimation() != animPrefix) + { + playFlashAnimation(animPrefix, true); + } + + if (getCurrentAnimation() == animPrefix && this.isLoopFinished()) + { + if (timeIdling >= IDLE_EGG_PERIOD && !seenIdleEasterEgg) + { + currentState = IdleEasterEgg; + } + else if (timeIdling >= IDLE_CARTOON_PERIOD) + { + currentState = Cartoon; + } + } + timeIdling += elapsed; + case Confirm: + var animPrefix = playableCharData.getAnimationPrefix('confirm'); + if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, false); + timeIdling = 0; + case FistPumpIntro: + var animPrefix = playableCharData.getAnimationPrefix('fistPump'); + if (getCurrentAnimation() != animPrefix) playFlashAnimation('Boyfriend DJ fist pump', false); + if (getCurrentAnimation() == animPrefix && anim.curFrame >= 4) + { + anim.play("Boyfriend DJ fist pump", true, false, 0); + } + case FistPump: + + case IdleEasterEgg: + var animPrefix = playableCharData.getAnimationPrefix('idleEasterEgg'); + if (getCurrentAnimation() != animPrefix) + { + onIdleEasterEgg.dispatch(); + playFlashAnimation(animPrefix, false); + seenIdleEasterEgg = true; + } + timeIdling = 0; + case Cartoon: + var animPrefix = playableCharData.getAnimationPrefix('cartoon'); + if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, true); + timeIdling = 0; + default: + // I shit myself. + } + + if (FlxG.keys.pressed.CONTROL) + { + if (FlxG.keys.justPressed.LEFT) + { + this.offsetX -= FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0); + } + + if (FlxG.keys.justPressed.RIGHT) + { + this.offsetX += FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0); + } + + if (FlxG.keys.justPressed.UP) + { + this.offsetY -= FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0); + } + + if (FlxG.keys.justPressed.DOWN) + { + this.offsetY += FlxG.keys.pressed.ALT ? 0.1 : (FlxG.keys.pressed.SHIFT ? 10.0 : 1.0); + } + + if (FlxG.keys.justPressed.SPACE) + { + currentState = (currentState == Idle ? Cartoon : Idle); + } + } + } + + function onFinishAnim():Void + { + var name = anim.curSymbol.name; + + if (name == playableCharData.getAnimationPrefix('intro')) + { + currentState = Idle; + onIntroDone.dispatch(); + } + else if (name == playableCharData.getAnimationPrefix('idle')) + { + // trace('Finished idle'); + } + else if (name == playableCharData.getAnimationPrefix('confirm')) + { + // trace('Finished confirm'); + } + else if (name == playableCharData.getAnimationPrefix('fistPump')) + { + // trace('Finished fist pump'); + currentState = Idle; + } + else if (name == playableCharData.getAnimationPrefix('idleEasterEgg')) + { + // trace('Finished spook'); + currentState = Idle; + } + else if (name == playableCharData.getAnimationPrefix('loss')) + { + // trace('Finished loss reaction'); + currentState = Idle; + } + else if (name == playableCharData.getAnimationPrefix('cartoon')) + { + // trace('Finished cartoon'); + + var frame:Int = FlxG.random.bool(33) ? playableCharData.getCartoonLoopBlinkFrame() : playableCharData.getCartoonLoopFrame(); + + // Character switches channels when the video ends, or at a 10% chance each time his idle loops. + if (FlxG.random.bool(5)) + { + frame = playableCharData.getCartoonChannelChangeFrame(); + // boyfriend switches channel code? + // runTvLogic(); + } + trace('Replay idle: ${frame}'); + anim.play(playableCharData.getAnimationPrefix('cartoon'), true, false, frame); + // trace('Finished confirm'); + } + else + { + trace('Finished ${name}'); + } + } + + public function resetAFKTimer():Void + { + timeIdling = 0; + seenIdleEasterEgg = false; + } + + var offsetX:Float = 0.0; + var offsetY:Float = 0.0; + + var cartoonSnd:Null = null; + + public var playingCartoon:Bool = false; + + public function runTvLogic() + { + if (cartoonSnd == null) + { + // tv is OFF, but getting turned on + FunkinSound.playOnce(Paths.sound('tv_on'), 1.0, function() { + loadCartoon(); + }); + } + else + { + // plays it smidge after the click + FunkinSound.playOnce(Paths.sound('channel_switch'), 1.0, function() { + cartoonSnd.destroy(); + loadCartoon(); + }); + } + + // loadCartoon(); + } + + function loadCartoon() + { + cartoonSnd = FunkinSound.load(Paths.sound(getRandomFlashToon()), 1.0, false, true, true, function() { + anim.play("Boyfriend DJ watchin tv OG", true, false, 60); + }); + + // Fade out music to 40% volume over 1 second. + // This helps make the TV a bit more audible. + FlxG.sound.music.fadeOut(1.0, 0.1); + + // Play the cartoon at a random time between the start and 5 seconds from the end. + cartoonSnd.time = FlxG.random.float(0, Math.max(cartoonSnd.length - (5 * Constants.MS_PER_SEC), 0.0)); + } + + final cartoonList:Array = openfl.utils.Assets.list().filter(function(path) return path.startsWith("assets/sounds/cartoons/")); + + function getRandomFlashToon():String + { + var randomFile = FlxG.random.getObject(cartoonList); + + // Strip folder prefix + randomFile = randomFile.replace("assets/sounds/", ""); + // Strip file extension + randomFile = randomFile.substring(0, randomFile.length - 4); + + return randomFile; + } + + public function confirm():Void + { + currentState = Confirm; + } + + public function fistPump():Void + { + currentState = FistPumpIntro; + } + + public function pumpFist():Void + { + currentState = FistPump; + anim.play("Boyfriend DJ fist pump", true, false, 4); + } + + public function pumpFistBad():Void + { + currentState = FistPump; + anim.play("Boyfriend DJ loss reaction 1", true, false, 4); + } + + override public function getCurrentAnimation():String + { + if (this.anim == null || this.anim.curSymbol == null) return ""; + return this.anim.curSymbol.name; + } + + public function playFlashAnimation(id:String, ?Force:Bool = false, ?Reverse:Bool = false, ?Frame:Int = 0):Void + { + anim.play(id, Force, Reverse, Frame); + applyAnimOffset(); + } + + function applyAnimOffset() + { + var AnimName = getCurrentAnimation(); + var daOffset = playableCharData.getAnimationOffsets(AnimName); + if (daOffset != null) + { + var xValue = daOffset[0]; + var yValue = daOffset[1]; + if (AnimName == "Boyfriend DJ watchin tv OG") + { + xValue += offsetX; + yValue += offsetY; + } + + trace('Successfully applied offset: ' + xValue + ', ' + yValue); + offset.set(xValue, yValue); + } + else + { + trace('No offset found, defaulting to: 0, 0'); + offset.set(0, 0); + } + } + + public override function destroy():Void + { + super.destroy(); + + if (cartoonSnd != null) + { + cartoonSnd.destroy(); + cartoonSnd = null; + } + } +} + +enum DJBoyfriendState +{ + Intro; + Idle; + Confirm; + FistPumpIntro; + FistPump; + IdleEasterEgg; + Cartoon; +} diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 9f23440df..98e48b338 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -167,7 +167,7 @@ class FreeplayState extends MusicBeatSubState var curCapsule:SongMenuItem; var curPlaying:Bool = false; - var dj:DJBoyfriend; + var dj:FreeplayDJ; var ostName:FlxText; var albumRoll:AlbumRoll; @@ -211,6 +211,7 @@ class FreeplayState extends MusicBeatSubState { currentCharacterId = params?.character ?? Constants.DEFAULT_CHARACTER; currentCharacter = PlayerRegistry.instance.fetchEntry(currentCharacterId); + if (currentCharacter == null) throw 'Could not build Freeplay state for character: $currentCharacterId'; fromResultsParams = params?.fromResults; @@ -450,17 +451,16 @@ class FreeplayState extends MusicBeatSubState add(cardGlow); - dj = new DJBoyfriend(640, 366); - exitMovers.set([dj], - { - x: -dj.width * 1.6, - speed: 0.5 - }); - - // TODO: Replace this. - if (currentCharacterId == 'pico') dj.visible = false; - - add(dj); + if (currentCharacter?.getFreeplayDJData() != null) + { + dj = new FreeplayDJ(640, 366, currentCharacterId); + exitMovers.set([dj], + { + x: -dj.width * 1.6, + speed: 0.5 + }); + add(dj); + } bgDad = new FlxSprite(pinkBack.width * 0.74, 0).loadGraphic(Paths.image('freeplay/freeplayBGdad')); bgDad.shader = new AngleMask(); diff --git a/source/funkin/ui/freeplay/charselect/PlayableCharacter.hx b/source/funkin/ui/freeplay/charselect/PlayableCharacter.hx index 743345004..282e35d7a 100644 --- a/source/funkin/ui/freeplay/charselect/PlayableCharacter.hx +++ b/source/funkin/ui/freeplay/charselect/PlayableCharacter.hx @@ -77,6 +77,11 @@ class PlayableCharacter implements IRegistryEntry return false; } + public function getFreeplayDJData():PlayerFreeplayDJData + { + return _data.freeplayDJ; + } + /** * Returns whether this character is unlocked. */ diff --git a/source/funkin/util/tools/MapTools.hx b/source/funkin/util/tools/MapTools.hx index b98cb0adf..807f0aebd 100644 --- a/source/funkin/util/tools/MapTools.hx +++ b/source/funkin/util/tools/MapTools.hx @@ -14,6 +14,7 @@ class MapTools */ public static function size(map:Map):Int { + if (map == null) return 0; return map.keys().array().length; } @@ -22,6 +23,7 @@ class MapTools */ public static function values(map:Map):Array { + if (map == null) return []; return [for (i in map.iterator()) i]; } @@ -30,6 +32,7 @@ class MapTools */ public static function clone(map:Map):Map { + if (map == null) return null; return map.copy(); } @@ -76,6 +79,7 @@ class MapTools */ public static function keyValues(map:Map):Array { + if (map == null) return []; return map.keys().array(); } } From ad57e64994dd8a9f5494d4c6984c9686448ef105 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Thu, 20 Jun 2024 16:17:53 -0400 Subject: [PATCH 47/48] Working Pico DJ --- source/funkin/Paths.hx | 11 +- .../funkin/data/freeplay/player/PlayerData.hx | 42 ++- source/funkin/play/scoring/Scoring.hx | 4 +- source/funkin/ui/freeplay/FreeplayDJ.hx | 14 +- source/funkin/ui/freeplay/FreeplayState.hx | 280 ++++++++++-------- .../freeplay/charselect/PlayableCharacter.hx | 5 + source/funkin/ui/mainmenu/MainMenuState.hx | 5 +- source/funkin/util/SortUtil.hx | 2 +- 8 files changed, 217 insertions(+), 146 deletions(-) diff --git a/source/funkin/Paths.hx b/source/funkin/Paths.hx index b0a97c4fa..285af7ca2 100644 --- a/source/funkin/Paths.hx +++ b/source/funkin/Paths.hx @@ -11,9 +11,16 @@ class Paths { static var currentLevel:Null = null; - public static function setCurrentLevel(name:String):Void + public static function setCurrentLevel(name:Null):Void { - currentLevel = name.toLowerCase(); + if (name == null) + { + currentLevel = null; + } + else + { + currentLevel = name.toLowerCase(); + } } public static function stripLibrary(path:String):String diff --git a/source/funkin/data/freeplay/player/PlayerData.hx b/source/funkin/data/freeplay/player/PlayerData.hx index 10fc54b78..c461c9555 100644 --- a/source/funkin/data/freeplay/player/PlayerData.hx +++ b/source/funkin/data/freeplay/player/PlayerData.hx @@ -35,6 +35,7 @@ class PlayerData * Data for displaying this character in the Freeplay menu. * If null, display no DJ. */ + @:optional public var freeplayDJ:Null = null; /** @@ -73,9 +74,25 @@ class PlayerFreeplayDJData var assetPath:String; var animations:Array; + @:optional + @:default("BOYFRIEND") + var text1:String; + + @:optional + @:default("HOT BLOODED IN MORE WAYS THAN ONE") + var text2:String; + + @:optional + @:default("PROTECT YO NUTS") + var text3:String; + + @:jignored var animationMap:Map; + @:jignored + var prefixToOffsetsMap:Map>; + @:optional var cartoon:Null; @@ -87,11 +104,14 @@ class PlayerFreeplayDJData function mapAnimations() { if (animationMap == null) animationMap = new Map(); + if (prefixToOffsetsMap == null) prefixToOffsetsMap = new Map(); animationMap.clear(); + prefixToOffsetsMap.clear(); for (anim in animations) { animationMap.set(anim.name, anim); + prefixToOffsetsMap.set(anim.prefix, anim.offsets); } } @@ -100,6 +120,15 @@ class PlayerFreeplayDJData return Paths.animateAtlas(assetPath); } + public function getFreeplayDJText(index:Int):String { + switch (index) { + case 1: return text1; + case 2: return text2; + case 3: return text3; + default: return ''; + } + } + public function getAnimationPrefix(name:String):Null { if (animationMap.size() == 0) mapAnimations(); @@ -109,13 +138,16 @@ class PlayerFreeplayDJData return anim.prefix; } - public function getAnimationOffsets(name:String):Null> + public function getAnimationOffsetsByPrefix(?prefix:String):Array { - if (animationMap.size() == 0) mapAnimations(); + if (prefixToOffsetsMap.size() == 0) mapAnimations(); + if (prefix == null) return [0, 0]; + return prefixToOffsetsMap.get(prefix); + } - var anim = animationMap.get(name); - if (anim == null) return null; - return anim.offsets; + public function getAnimationOffsets(name:String):Array + { + return getAnimationOffsetsByPrefix(getAnimationPrefix(name)); } // TODO: These should really be frame labels, ehe. diff --git a/source/funkin/play/scoring/Scoring.hx b/source/funkin/play/scoring/Scoring.hx index dc2c40647..02e5750bc 100644 --- a/source/funkin/play/scoring/Scoring.hx +++ b/source/funkin/play/scoring/Scoring.hx @@ -590,7 +590,7 @@ enum abstract ScoringRank(String) } } - public function getFreeplayRankIconAsset():Null + public function getFreeplayRankIconAsset():String { switch (abstract) { @@ -607,7 +607,7 @@ enum abstract ScoringRank(String) case SHIT: return 'LOSS'; default: - return null; + return 'LOSS'; } } diff --git a/source/funkin/ui/freeplay/FreeplayDJ.hx b/source/funkin/ui/freeplay/FreeplayDJ.hx index f9effe793..72eddd0ca 100644 --- a/source/funkin/ui/freeplay/FreeplayDJ.hx +++ b/source/funkin/ui/freeplay/FreeplayDJ.hx @@ -135,8 +135,12 @@ class FreeplayDJ extends FlxAtlasSprite timeIdling = 0; case Cartoon: var animPrefix = playableCharData.getAnimationPrefix('cartoon'); - if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, true); - timeIdling = 0; + if (animPrefix == null) { + currentState = IdleEasterEgg; + } else { + if (getCurrentAnimation() != animPrefix) playFlashAnimation(animPrefix, true); + timeIdling = 0; + } default: // I shit myself. } @@ -324,7 +328,7 @@ class FreeplayDJ extends FlxAtlasSprite function applyAnimOffset() { var AnimName = getCurrentAnimation(); - var daOffset = playableCharData.getAnimationOffsets(AnimName); + var daOffset = playableCharData.getAnimationOffsetsByPrefix(AnimName); if (daOffset != null) { var xValue = daOffset[0]; @@ -335,12 +339,12 @@ class FreeplayDJ extends FlxAtlasSprite yValue += offsetY; } - trace('Successfully applied offset: ' + xValue + ', ' + yValue); + trace('Successfully applied offset ($AnimName): ' + xValue + ', ' + yValue); offset.set(xValue, yValue); } else { - trace('No offset found, defaulting to: 0, 0'); + trace('No offset found ($AnimName), defaulting to: 0, 0'); offset.set(0, 0); } } diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 98e48b338..5725101cd 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1,54 +1,55 @@ package funkin.ui.freeplay; -import funkin.graphics.adobeanimate.FlxAtlasSprite; import flixel.addons.transition.FlxTransitionableState; import flixel.addons.ui.FlxInputText; import flixel.FlxCamera; import flixel.FlxSprite; import flixel.group.FlxGroup; -import funkin.graphics.shaders.GaussianBlurShader; import flixel.group.FlxGroup.FlxTypedGroup; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import flixel.input.touch.FlxTouch; import flixel.math.FlxAngle; import flixel.math.FlxPoint; -import openfl.display.BlendMode; import flixel.system.debug.watch.Tracker.TrackerProfile; import flixel.text.FlxText; import flixel.tweens.FlxEase; import flixel.tweens.FlxTween; +import flixel.tweens.misc.ShakeTween; import flixel.util.FlxColor; import flixel.util.FlxSpriteUtil; import flixel.util.FlxTimer; import funkin.audio.FunkinSound; -import funkin.data.story.level.LevelRegistry; -import funkin.data.song.SongRegistry; import funkin.data.freeplay.player.PlayerRegistry; +import funkin.data.song.SongRegistry; +import funkin.data.story.level.LevelRegistry; +import funkin.effects.IntervalShake; +import funkin.graphics.adobeanimate.FlxAtlasSprite; import funkin.graphics.FunkinCamera; import funkin.graphics.FunkinSprite; import funkin.graphics.shaders.AngleMask; +import funkin.graphics.shaders.GaussianBlurShader; import funkin.graphics.shaders.HSVShader; import funkin.graphics.shaders.PureColor; import funkin.graphics.shaders.StrokeShader; import funkin.input.Controls; import funkin.play.PlayStatePlaylist; +import funkin.play.scoring.Scoring; +import funkin.play.scoring.Scoring.ScoringRank; import funkin.play.song.Song; -import funkin.ui.story.Level; import funkin.save.Save; import funkin.save.Save.SaveScoreData; import funkin.ui.AtlasText; -import funkin.play.scoring.Scoring; -import funkin.play.scoring.Scoring.ScoringRank; +import funkin.ui.freeplay.charselect.PlayableCharacter; +import funkin.ui.freeplay.SongMenuItem.FreeplayRank; import funkin.ui.mainmenu.MainMenuState; import funkin.ui.MusicBeatSubState; +import funkin.ui.story.Level; import funkin.ui.transition.LoadingState; import funkin.ui.transition.StickerSubState; import funkin.util.MathUtil; +import funkin.util.SortUtil; import lime.utils.Assets; -import flixel.tweens.misc.ShakeTween; -import funkin.effects.IntervalShake; -import funkin.ui.freeplay.SongMenuItem.FreeplayRank; -import funkin.ui.freeplay.charselect.PlayableCharacter; +import openfl.display.BlendMode; /** * Parameters used to initialize the FreeplayState. @@ -94,6 +95,7 @@ typedef FromResultsParams = /** * The state for the freeplay menu, allowing the player to select any song to play. */ +@:nullSafety class FreeplayState extends MusicBeatSubState { // @@ -164,10 +166,9 @@ class FreeplayState extends MusicBeatSubState var grpSongs:FlxTypedGroup; var grpCapsules:FlxTypedGroup; - var curCapsule:SongMenuItem; var curPlaying:Bool = false; - var dj:FreeplayDJ; + var dj:Null = null; var ostName:FlxText; var albumRoll:AlbumRoll; @@ -175,7 +176,7 @@ class FreeplayState extends MusicBeatSubState var letterSort:LetterSort; var exitMovers:ExitMoverData = new Map(); - var stickerSubState:StickerSubState; + var stickerSubState:Null = null; public static var rememberedDifficulty:Null = Constants.DEFAULT_DIFFICULTY; public static var rememberedSongId:Null = 'tutorial'; @@ -210,8 +211,12 @@ class FreeplayState extends MusicBeatSubState public function new(?params:FreeplayStateParams, ?stickers:StickerSubState) { currentCharacterId = params?.character ?? Constants.DEFAULT_CHARACTER; - currentCharacter = PlayerRegistry.instance.fetchEntry(currentCharacterId); - if (currentCharacter == null) throw 'Could not build Freeplay state for character: $currentCharacterId'; + var fetchPlayableCharacter = function():PlayableCharacter { + var result = PlayerRegistry.instance.fetchEntry(params?.character ?? Constants.DEFAULT_CHARACTER); + if (result == null) throw 'No valid playable character with id ${params?.character}'; + return result; + }; + currentCharacter = fetchPlayableCharacter(); fromResultsParams = params?.fromResults; @@ -220,12 +225,54 @@ class FreeplayState extends MusicBeatSubState prepForNewRank = true; } + super(FlxColor.TRANSPARENT); + if (stickers?.members != null) { stickerSubState = stickers; } - super(FlxColor.TRANSPARENT); + // We build a bunch of sprites BEFORE create() so we can guarantee they aren't null later on. + albumRoll = new AlbumRoll(); + fp = new FreeplayScore(460, 60, 7, 100); + cardGlow = new FlxSprite(-30, -30).loadGraphic(Paths.image('freeplay/cardGlow')); + confirmGlow = new FlxSprite(-30, 240).loadGraphic(Paths.image('freeplay/confirmGlow')); + confirmTextGlow = new FlxSprite(-8, 115).loadGraphic(Paths.image('freeplay/glowingText')); + rankCamera = new FunkinCamera('rankCamera', 0, 0, FlxG.width, FlxG.height); + funnyCam = new FunkinCamera('freeplayFunny', 0, 0, FlxG.width, FlxG.height); + funnyScroll = new BGScrollingText(0, 220, currentCharacter.getFreeplayDJText(1), FlxG.width / 2, false, 60); + funnyScroll2 = new BGScrollingText(0, 335, currentCharacter.getFreeplayDJText(1), FlxG.width / 2, false, 60); + grpCapsules = new FlxTypedGroup(); + grpDifficulties = new FlxTypedSpriteGroup(-300, 80); + letterSort = new LetterSort(400, 75); + grpSongs = new FlxTypedGroup(); + moreWays = new BGScrollingText(0, 160, currentCharacter.getFreeplayDJText(2), FlxG.width, true, 43); + moreWays2 = new BGScrollingText(0, 397, currentCharacter.getFreeplayDJText(2), FlxG.width, true, 43); + pinkBack = FunkinSprite.create('freeplay/pinkBack'); + rankBg = new FunkinSprite(0, 0); + rankVignette = new FlxSprite(0, 0).loadGraphic(Paths.image('freeplay/rankVignette')); + sparks = new FlxSprite(0, 0); + sparksADD = new FlxSprite(0, 0); + txtCompletion = new AtlasText(1185, 87, '69', AtlasFont.FREEPLAY_CLEAR); + txtNuts = new BGScrollingText(0, 285, currentCharacter.getFreeplayDJText(3), FlxG.width / 2, true, 43); + + ostName = new FlxText(8, 8, FlxG.width - 8 - 8, 'OFFICIAL OST', 48); + + orangeBackShit = new FunkinSprite(84, 440).makeSolidColor(Std.int(pinkBack.width), 75, 0xFFFEDA00); + + bgDad = new FlxSprite(pinkBack.width * 0.74, 0).loadGraphic(Paths.image('freeplay/freeplayBGdad')); + alsoOrangeLOL = new FunkinSprite(0, orangeBackShit.y).makeSolidColor(100, Std.int(orangeBackShit.height), 0xFFFFD400); + confirmGlow2 = new FlxSprite(confirmGlow.x, confirmGlow.y).loadGraphic(Paths.image('freeplay/confirmGlow2')); + funnyScroll3 = new BGScrollingText(0, orangeBackShit.y + 10, currentCharacter.getFreeplayDJText(1), FlxG.width / 2, 60); + backingTextYeah = new FlxAtlasSprite(640, 370, Paths.animateAtlas("freeplay/backing-text-yeah"), + { + FrameRate: 24.0, + Reversed: false, + // ?OnComplete:Void -> Void, + ShowPivot: false, + Antialiasing: true, + ScrollFactor: new FlxPoint(1, 1), + }); } override function create():Void @@ -236,12 +283,6 @@ class FreeplayState extends MusicBeatSubState FlxTransitionableState.skipNextTransIn = true; - // dedicated camera for the state so we don't need to fuk around with camera scrolls from the mainmenu / elsewhere - funnyCam = new FunkinCamera('freeplayFunny', 0, 0, FlxG.width, FlxG.height); - funnyCam.bgColor = FlxColor.TRANSPARENT; - FlxG.cameras.add(funnyCam, false); - this.cameras = [funnyCam]; - if (stickerSubState != null) { this.persistentUpdate = true; @@ -277,7 +318,7 @@ class FreeplayState extends MusicBeatSubState // programmatically adds the songs via LevelRegistry and SongRegistry for (levelId in LevelRegistry.instance.listSortedLevelIds()) { - var level:Level = LevelRegistry.instance.fetchEntry(levelId); + var level:Null = LevelRegistry.instance.fetchEntry(levelId); if (level == null) { @@ -287,7 +328,7 @@ class FreeplayState extends MusicBeatSubState for (songId in level.getSongs()) { - var song:Song = SongRegistry.instance.fetchEntry(songId); + var song:Null = SongRegistry.instance.fetchEntry(songId); if (song == null) { @@ -319,17 +360,14 @@ class FreeplayState extends MusicBeatSubState trace(FlxG.camera.initialZoom); trace(FlxCamera.defaultZoom); - pinkBack = FunkinSprite.create('freeplay/pinkBack'); pinkBack.color = 0xFFFFD4E9; // sets it to pink! pinkBack.x -= pinkBack.width; FlxTween.tween(pinkBack, {x: 0}, 0.6, {ease: FlxEase.quartOut}); add(pinkBack); - orangeBackShit = new FunkinSprite(84, 440).makeSolidColor(Std.int(pinkBack.width), 75, 0xFFFEDA00); add(orangeBackShit); - alsoOrangeLOL = new FunkinSprite(0, orangeBackShit.y).makeSolidColor(100, Std.int(orangeBackShit.height), 0xFFFFD400); add(alsoOrangeLOL); exitMovers.set([pinkBack, orangeBackShit, alsoOrangeLOL], @@ -344,15 +382,11 @@ class FreeplayState extends MusicBeatSubState orangeBackShit.visible = false; alsoOrangeLOL.visible = false; - confirmTextGlow = new FlxSprite(-8, 115).loadGraphic(Paths.image('freeplay/glowingText')); confirmTextGlow.blend = BlendMode.ADD; confirmTextGlow.visible = false; - confirmGlow = new FlxSprite(-30, 240).loadGraphic(Paths.image('freeplay/confirmGlow')); confirmGlow.blend = BlendMode.ADD; - confirmGlow2 = new FlxSprite(confirmGlow.x, confirmGlow.y).loadGraphic(Paths.image('freeplay/confirmGlow2')); - confirmGlow.visible = false; confirmGlow2.visible = false; @@ -367,7 +401,6 @@ class FreeplayState extends MusicBeatSubState FlxG.debugger.addTrackerProfile(new TrackerProfile(BGScrollingText, ['x', 'y', 'speed', 'size'])); - moreWays = new BGScrollingText(0, 160, 'HOT BLOODED IN MORE WAYS THAN ONE', FlxG.width, true, 43); moreWays.funnyColor = 0xFFFFF383; moreWays.speed = 6.8; grpTxtScrolls.add(moreWays); @@ -378,7 +411,6 @@ class FreeplayState extends MusicBeatSubState speed: 0.4, }); - funnyScroll = new BGScrollingText(0, 220, 'BOYFRIEND', FlxG.width / 2, false, 60); funnyScroll.funnyColor = 0xFFFF9963; funnyScroll.speed = -3.8; grpTxtScrolls.add(funnyScroll); @@ -391,7 +423,6 @@ class FreeplayState extends MusicBeatSubState wait: 0 }); - txtNuts = new BGScrollingText(0, 285, 'PROTECT YO NUTS', FlxG.width / 2, true, 43); txtNuts.speed = 3.5; grpTxtScrolls.add(txtNuts); exitMovers.set([txtNuts], @@ -400,7 +431,6 @@ class FreeplayState extends MusicBeatSubState speed: 0.4, }); - funnyScroll2 = new BGScrollingText(0, 335, 'BOYFRIEND', FlxG.width / 2, false, 60); funnyScroll2.funnyColor = 0xFFFF9963; funnyScroll2.speed = -3.8; grpTxtScrolls.add(funnyScroll2); @@ -411,7 +441,6 @@ class FreeplayState extends MusicBeatSubState speed: 0.5, }); - moreWays2 = new BGScrollingText(0, 397, 'HOT BLOODED IN MORE WAYS THAN ONE', FlxG.width, true, 43); moreWays2.funnyColor = 0xFFFFF383; moreWays2.speed = 6.8; grpTxtScrolls.add(moreWays2); @@ -422,7 +451,6 @@ class FreeplayState extends MusicBeatSubState speed: 0.4 }); - funnyScroll3 = new BGScrollingText(0, orangeBackShit.y + 10, 'BOYFRIEND', FlxG.width / 2, 60); funnyScroll3.funnyColor = 0xFFFEA400; funnyScroll3.speed = -3.8; grpTxtScrolls.add(funnyScroll3); @@ -433,19 +461,8 @@ class FreeplayState extends MusicBeatSubState speed: 0.3 }); - backingTextYeah = new FlxAtlasSprite(640, 370, Paths.animateAtlas("freeplay/backing-text-yeah"), - { - FrameRate: 24.0, - Reversed: false, - // ?OnComplete:Void -> Void, - ShowPivot: false, - Antialiasing: true, - ScrollFactor: new FlxPoint(1, 1), - }); - add(backingTextYeah); - cardGlow = new FlxSprite(-30, -30).loadGraphic(Paths.image('freeplay/cardGlow')); cardGlow.blend = BlendMode.ADD; cardGlow.visible = false; @@ -462,7 +479,6 @@ class FreeplayState extends MusicBeatSubState add(dj); } - bgDad = new FlxSprite(pinkBack.width * 0.74, 0).loadGraphic(Paths.image('freeplay/freeplayBGdad')); bgDad.shader = new AngleMask(); bgDad.visible = false; @@ -488,17 +504,13 @@ class FreeplayState extends MusicBeatSubState blackOverlayBullshitLOLXD.shader = bgDad.shader; - rankBg = new FunkinSprite(0, 0); rankBg.makeSolidColor(FlxG.width, FlxG.height, 0xD3000000); add(rankBg); - grpSongs = new FlxTypedGroup(); add(grpSongs); - grpCapsules = new FlxTypedGroup(); add(grpCapsules); - grpDifficulties = new FlxTypedSpriteGroup(-300, 80); add(grpDifficulties); exitMovers.set([grpDifficulties], @@ -525,7 +537,6 @@ class FreeplayState extends MusicBeatSubState if (diffSprite.difficultyId == currentDifficulty) diffSprite.visible = true; } - albumRoll = new AlbumRoll(); albumRoll.albumId = null; add(albumRoll); @@ -540,7 +551,6 @@ class FreeplayState extends MusicBeatSubState fnfFreeplay.font = 'VCR OSD Mono'; fnfFreeplay.visible = false; - ostName = new FlxText(8, 8, FlxG.width - 8 - 8, 'OFFICIAL OST', 48); ostName.font = 'VCR OSD Mono'; ostName.alignment = RIGHT; ostName.visible = false; @@ -572,7 +582,6 @@ class FreeplayState extends MusicBeatSubState tmr.time = FlxG.random.float(20, 60); }, 0); - fp = new FreeplayScore(460, 60, 7, 100); fp.visible = false; add(fp); @@ -580,11 +589,9 @@ class FreeplayState extends MusicBeatSubState clearBoxSprite.visible = false; add(clearBoxSprite); - txtCompletion = new AtlasText(1185, 87, '69', AtlasFont.FREEPLAY_CLEAR); txtCompletion.visible = false; add(txtCompletion); - letterSort = new LetterSort(400, 75); add(letterSort); letterSort.visible = false; @@ -632,7 +639,7 @@ class FreeplayState extends MusicBeatSubState // be careful not to "add()" things in here unless it's to a group that's already added to the state // otherwise it won't be properly attatched to funnyCamera (relavent code should be at the bottom of create()) - dj.onIntroDone.add(function() { + var onDJIntroDone = function() { // when boyfriend hits dat shiii albumRoll.playIntro(); @@ -679,20 +686,27 @@ class FreeplayState extends MusicBeatSubState cardGlow.visible = true; FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.45, {ease: FlxEase.sineOut}); - if (prepForNewRank) + if (prepForNewRank && fromResultsParams != null) { rankAnimStart(fromResultsParams); } - }); + }; + + if (dj != null) + { + dj.onIntroDone.add(onDJIntroDone); + } + else + { + onDJIntroDone(); + } generateSongList(null, false); // dedicated camera for the state so we don't need to fuk around with camera scrolls from the mainmenu / elsewhere - funnyCam = new FunkinCamera('freeplayFunny', 0, 0, FlxG.width, FlxG.height); funnyCam.bgColor = FlxColor.TRANSPARENT; FlxG.cameras.add(funnyCam, false); - rankVignette = new FlxSprite(0, 0).loadGraphic(Paths.image('freeplay/rankVignette')); rankVignette.scale.set(2, 2); rankVignette.updateHitbox(); rankVignette.blend = BlendMode.ADD; @@ -704,7 +718,6 @@ class FreeplayState extends MusicBeatSubState bs.cameras = [funnyCam]; }); - rankCamera = new FunkinCamera('rankCamera', 0, 0, FlxG.width, FlxG.height); rankCamera.bgColor = FlxColor.TRANSPARENT; FlxG.cameras.add(rankCamera, false); rankBg.cameras = [rankCamera]; @@ -716,8 +729,8 @@ class FreeplayState extends MusicBeatSubState } } - var currentFilter:SongFilter = null; - var currentFilteredSongs:Array = []; + var currentFilter:Null = null; + var currentFilteredSongs:Array> = []; /** * Given the current filter, rebuild the current song list. @@ -728,7 +741,7 @@ class FreeplayState extends MusicBeatSubState */ public function generateSongList(filterStuff:Null, force:Bool = false, onlyIfChanged:Bool = true):Void { - var tempSongs:Array = songs; + var tempSongs:Array> = songs; // Remember just the difficulty because it's important for song sorting. if (rememberedDifficulty != null) @@ -790,11 +803,12 @@ class FreeplayState extends MusicBeatSubState for (i in 0...tempSongs.length) { - if (tempSongs[i] == null) continue; + var tempSong = tempSongs[i]; + if (tempSong == null) continue; var funnyMenu:SongMenuItem = grpCapsules.recycle(SongMenuItem); - funnyMenu.init(FlxG.width, 0, tempSongs[i]); + funnyMenu.init(FlxG.width, 0, tempSong); funnyMenu.onConfirm = function() { capsuleOnConfirmDefault(funnyMenu); }; @@ -803,8 +817,8 @@ class FreeplayState extends MusicBeatSubState funnyMenu.ID = i; funnyMenu.capsule.alpha = 0.5; funnyMenu.songText.visible = false; - funnyMenu.favIcon.visible = tempSongs[i].isFav; - funnyMenu.favIconBlurred.visible = tempSongs[i].isFav; + funnyMenu.favIcon.visible = tempSong.isFav; + funnyMenu.favIconBlurred.visible = tempSong.isFav; funnyMenu.hsvShader = hsvShader; funnyMenu.newText.animation.curAnim.curFrame = 45 - ((i * 4) % 45); @@ -828,13 +842,10 @@ class FreeplayState extends MusicBeatSubState * @param songFilter The filter to apply * @return Array */ - public function sortSongs(songsToFilter:Array, songFilter:SongFilter):Array + public function sortSongs(songsToFilter:Array>, songFilter:SongFilter):Array> { - var filterAlphabetically = function(a:FreeplaySongData, b:FreeplaySongData):Int { - if (a?.songName.toLowerCase() < b?.songName.toLowerCase()) return -1; - else if (a?.songName.toLowerCase() > b?.songName.toLowerCase()) return 1; - else - return 0; + var filterAlphabetically = function(a:Null, b:Null):Int { + return SortUtil.alphabetically(a?.songName ?? '', b?.songName ?? ''); }; switch (songFilter.filterType) @@ -858,7 +869,7 @@ class FreeplayState extends MusicBeatSubState songsToFilter = songsToFilter.filter(str -> { if (str == null) return true; // Random - return str.songName.toLowerCase().startsWith(songFilter.filterData); + return str.songName.toLowerCase().startsWith(songFilter.filterData ?? ''); }); case ALL: // no filter! @@ -880,32 +891,28 @@ class FreeplayState extends MusicBeatSubState var sparks:FlxSprite; var sparksADD:FlxSprite; - function rankAnimStart(fromResults:Null):Void + function rankAnimStart(fromResults:FromResultsParams):Void { busy = true; grpCapsules.members[curSelected].sparkle.alpha = 0; // grpCapsules.members[curSelected].forcePosition(); - if (fromResults != null) - { - rememberedSongId = fromResults.songId; - rememberedDifficulty = fromResults.difficultyId; - changeSelection(); - changeDiff(); - } + rememberedSongId = fromResults.songId; + rememberedDifficulty = fromResults.difficultyId; + changeSelection(); + changeDiff(); - dj.fistPump(); + if (dj != null) dj.fistPump(); // rankCamera.fade(FlxColor.BLACK, 0.5, true); rankCamera.fade(0xFF000000, 0.5, true, null, true); if (FlxG.sound.music != null) FlxG.sound.music.volume = 0; rankBg.alpha = 1; - if (fromResults?.oldRank != null) + if (fromResults.oldRank != null) { grpCapsules.members[curSelected].fakeRanking.rank = fromResults.oldRank; grpCapsules.members[curSelected].fakeBlurredRanking.rank = fromResults.oldRank; - sparks = new FlxSprite(0, 0); sparks.frames = Paths.getSparrowAtlas('freeplay/sparks'); sparks.animation.addByPrefix('sparks', 'sparks', 24, false); sparks.visible = false; @@ -915,7 +922,6 @@ class FreeplayState extends MusicBeatSubState add(sparks); sparks.cameras = [rankCamera]; - sparksADD = new FlxSprite(0, 0); sparksADD.visible = false; sparksADD.frames = Paths.getSparrowAtlas('freeplay/sparksadd'); sparksADD.animation.addByPrefix('sparks add', 'sparks add', 24, false); @@ -980,14 +986,14 @@ class FreeplayState extends MusicBeatSubState grpCapsules.members[curSelected].ranking.scale.set(20, 20); grpCapsules.members[curSelected].blurredRanking.scale.set(20, 20); - if (fromResults?.newRank != null) + if (fromResults != null && fromResults.newRank != null) { grpCapsules.members[curSelected].ranking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true); } FlxTween.tween(grpCapsules.members[curSelected].ranking, {"scale.x": 1, "scale.y": 1}, 0.1); - if (fromResults?.newRank != null) + if (fromResults != null && fromResults.newRank != null) { grpCapsules.members[curSelected].blurredRanking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true); } @@ -1078,11 +1084,11 @@ class FreeplayState extends MusicBeatSubState if (fromResultsParams?.newRank == SHIT) { - dj.pumpFistBad(); + if (dj != null) dj.pumpFistBad(); } else { - dj.pumpFist(); + if (dj != null) dj.pumpFist(); } rankCamera.zoom = 0.8; @@ -1196,7 +1202,13 @@ class FreeplayState extends MusicBeatSubState #if debug if (FlxG.keys.justPressed.T) { - rankAnimStart(fromResultsParams); + rankAnimStart(fromResultsParams ?? + { + playRankAnim: true, + newRank: PERFECT_GOLD, + songId: "tutorial", + difficultyId: "hard" + }); } if (FlxG.keys.justPressed.P) @@ -1427,7 +1439,7 @@ class FreeplayState extends MusicBeatSubState } spamTimer += elapsed; - dj.resetAFKTimer(); + if (dj != null) dj.resetAFKTimer(); } else { @@ -1438,31 +1450,31 @@ class FreeplayState extends MusicBeatSubState #if !html5 if (FlxG.mouse.wheel != 0) { - dj.resetAFKTimer(); + if (dj != null) dj.resetAFKTimer(); changeSelection(-Math.round(FlxG.mouse.wheel)); } #else if (FlxG.mouse.wheel < 0) { - dj.resetAFKTimer(); + if (dj != null) dj.resetAFKTimer(); changeSelection(-Math.round(FlxG.mouse.wheel / 8)); } else if (FlxG.mouse.wheel > 0) { - dj.resetAFKTimer(); + if (dj != null) dj.resetAFKTimer(); changeSelection(-Math.round(FlxG.mouse.wheel / 8)); } #end if (controls.UI_LEFT_P) { - dj.resetAFKTimer(); + if (dj != null) dj.resetAFKTimer(); changeDiff(-1); generateSongList(currentFilter, true); } if (controls.UI_RIGHT_P) { - dj.resetAFKTimer(); + if (dj != null) dj.resetAFKTimer(); changeDiff(1); generateSongList(currentFilter, true); } @@ -1472,7 +1484,7 @@ class FreeplayState extends MusicBeatSubState busy = true; FlxTween.globalManager.clear(); FlxTimer.globalManager.clear(); - dj.onIntroDone.removeAll(); + if (dj != null) dj.onIntroDone.removeAll(); FunkinSound.playOnce(Paths.sound('cancelMenu')); @@ -1498,7 +1510,8 @@ class FreeplayState extends MusicBeatSubState for (grpSpr in exitMovers.keys()) { - var moveData:MoveData = exitMovers.get(grpSpr); + var moveData:Null = exitMovers.get(grpSpr); + if (moveData == null) continue; for (spr in grpSpr) { @@ -1506,14 +1519,14 @@ class FreeplayState extends MusicBeatSubState var funnyMoveShit:MoveData = moveData; - if (moveData.x == null) funnyMoveShit.x = spr.x; - if (moveData.y == null) funnyMoveShit.y = spr.y; - if (moveData.speed == null) funnyMoveShit.speed = 0.2; - if (moveData.wait == null) funnyMoveShit.wait = 0; + var moveDataX = funnyMoveShit.x ?? spr.x; + var moveDataY = funnyMoveShit.y ?? spr.y; + var moveDataSpeed = funnyMoveShit.speed ?? 0.2; + var moveDataWait = funnyMoveShit.wait ?? 0; - FlxTween.tween(spr, {x: funnyMoveShit.x, y: funnyMoveShit.y}, funnyMoveShit.speed, {ease: FlxEase.expoIn}); + FlxTween.tween(spr, {x: moveDataX, y: moveDataY}, moveDataSpeed, {ease: FlxEase.expoIn}); - longestTimer = Math.max(longestTimer, funnyMoveShit.speed + funnyMoveShit.wait); + longestTimer = Math.max(longestTimer, moveDataSpeed + moveDataWait); } } @@ -1586,19 +1599,18 @@ class FreeplayState extends MusicBeatSubState var daSong:Null = grpCapsules.members[curSelected].songData; if (daSong != null) { - // TODO: Make this actually be the variation you're focused on. We don't need to fetch the song metadata just to calculate it. - var targetSong:Song = SongRegistry.instance.fetchEntry(grpCapsules.members[curSelected].songData.songId); + var targetSong:Null = SongRegistry.instance.fetchEntry(daSong.songId); if (targetSong == null) { - FlxG.log.warn('WARN: could not find song with id (${grpCapsules.members[curSelected].songData.songId})'); + FlxG.log.warn('WARN: could not find song with id (${daSong.songId})'); return; } - var targetVariation:String = targetSong.getFirstValidVariation(currentDifficulty); + var targetVariation:String = targetSong.getFirstValidVariation(currentDifficulty) ?? ''; // TODO: This line of code makes me sad, but you can't really fix it without a breaking migration. var suffixedDifficulty = (targetVariation != Constants.DEFAULT_VARIATION && targetVariation != 'erect') ? '$currentDifficulty-${targetVariation}' : currentDifficulty; - var songScore:SaveScoreData = Save.instance.getSongScore(grpCapsules.members[curSelected].songData.songId, suffixedDifficulty); + var songScore:Null = Save.instance.getSongScore(daSong.songId, suffixedDifficulty); intendedScore = songScore?.score ?? 0; intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes); rememberedDifficulty = currentDifficulty; @@ -1660,7 +1672,7 @@ class FreeplayState extends MusicBeatSubState } // Set the album graphic and play the animation if relevant. - var newAlbumId:String = daSong?.albumId; + var newAlbumId:Null = daSong?.albumId; if (albumRoll.albumId != newAlbumId) { albumRoll.albumId = newAlbumId; @@ -1698,7 +1710,7 @@ class FreeplayState extends MusicBeatSubState }); trace('Available songs: ${availableSongCapsules.map(function(cap) { - return cap.songData.songName; + return cap?.songData?.songName; })}'); if (availableSongCapsules.length == 0) @@ -1727,17 +1739,20 @@ class FreeplayState extends MusicBeatSubState PlayStatePlaylist.isStoryMode = false; - var targetSong:Song = SongRegistry.instance.fetchEntry(cap.songData.songId); - if (targetSong == null) + var targetSongId:String = cap?.songData?.songId ?? 'unknown'; + var targetSongNullable:Null = SongRegistry.instance.fetchEntry(targetSongId); + if (targetSongNullable == null) { - FlxG.log.warn('WARN: could not find song with id (${cap.songData.songId})'); + FlxG.log.warn('WARN: could not find song with id (${targetSongId})'); return; } + var targetSong:Song = targetSongNullable; var targetDifficultyId:String = currentDifficulty; - var targetVariation:String = targetSong.getFirstValidVariation(targetDifficultyId, currentCharacter); - PlayStatePlaylist.campaignId = cap.songData.levelId; + var targetVariation:Null = targetSong.getFirstValidVariation(targetDifficultyId, currentCharacter); + var targetLevelId:Null = cap?.songData?.levelId; + PlayStatePlaylist.campaignId = targetLevelId ?? null; - var targetDifficulty:SongDifficulty = targetSong.getDifficulty(targetDifficultyId, targetVariation); + var targetDifficulty:Null = targetSong.getDifficulty(targetDifficultyId, targetVariation); if (targetDifficulty == null) { FlxG.log.warn('WARN: could not find difficulty with id (${targetDifficultyId})'); @@ -1759,7 +1774,7 @@ class FreeplayState extends MusicBeatSubState // Visual and audio effects. FunkinSound.playOnce(Paths.sound('confirmMenu')); - dj.confirm(); + if (dj != null) dj.confirm(); grpCapsules.members[curSelected].forcePosition(); grpCapsules.members[curSelected].confirm(); @@ -1801,7 +1816,7 @@ class FreeplayState extends MusicBeatSubState new FlxTimer().start(1, function(tmr:FlxTimer) { FunkinSound.emptyPartialQueue(); - Paths.setCurrentLevel(cap.songData.levelId); + Paths.setCurrentLevel(cap?.songData?.levelId); LoadingState.loadPlayState( { targetSong: targetSong, @@ -1856,7 +1871,7 @@ class FreeplayState extends MusicBeatSubState var daSongCapsule:SongMenuItem = grpCapsules.members[curSelected]; if (daSongCapsule.songData != null) { - var songScore:SaveScoreData = Save.instance.getSongScore(daSongCapsule.songData.songId, currentDifficulty); + var songScore:Null = Save.instance.getSongScore(daSongCapsule.songData.songId, currentDifficulty); intendedScore = songScore?.score ?? 0; intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes); diffIdsCurrent = daSongCapsule.songData.songDifficulties; @@ -1906,7 +1921,10 @@ class FreeplayState extends MusicBeatSubState } else { - var previewSong:Null = SongRegistry.instance.fetchEntry(daSongCapsule.songData.songId); + var previewSongId:Null = daSongCapsule?.songData?.songId; + if (previewSongId == null) return; + + var previewSong:Null = SongRegistry.instance.fetchEntry(previewSongId); var songDifficulty = previewSong?.getDifficulty(currentDifficulty, previewSong?.getVariationsByCharacter(currentCharacter) ?? Constants.DEFAULT_VARIATION_LIST); var baseInstrumentalId:String = songDifficulty?.characters?.instrumental ?? ''; @@ -1924,7 +1942,9 @@ class FreeplayState extends MusicBeatSubState instSuffix = (instSuffix != '') ? '-$instSuffix' : ''; - FunkinSound.playMusic(daSongCapsule.songData.songId, + trace('Attempting to play partial preview: ${previewSongId}:${instSuffix}'); + + FunkinSound.playMusic(previewSongId, { startingVolume: 0.0, overrideExisting: true, @@ -1952,7 +1972,7 @@ class FreeplayState extends MusicBeatSubState public static function build(?params:FreeplayStateParams, ?stickers:StickerSubState):MusicBeatState { var result:MainMenuState; - if (params?.fromResults?.playRankAnim) result = new MainMenuState(true); + if (params?.fromResults?.playRankAnim ?? false) result = new MainMenuState(true); else result = new MainMenuState(false); diff --git a/source/funkin/ui/freeplay/charselect/PlayableCharacter.hx b/source/funkin/ui/freeplay/charselect/PlayableCharacter.hx index 282e35d7a..6d7b96c58 100644 --- a/source/funkin/ui/freeplay/charselect/PlayableCharacter.hx +++ b/source/funkin/ui/freeplay/charselect/PlayableCharacter.hx @@ -82,6 +82,11 @@ class PlayableCharacter implements IRegistryEntry return _data.freeplayDJ; } + public function getFreeplayDJText(index:Int):String + { + return _data.freeplayDJ.getFreeplayDJText(index); + } + /** * Returns whether this character is unlocked. */ diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index 2eba406d9..9bf465484 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -117,7 +117,10 @@ class MainMenuState extends MusicBeatState FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransOut = true; - openSubState(new FreeplayState()); + openSubState(new FreeplayState( + { + character: FlxG.keys.pressed.SHIFT ? 'pico' : 'bf', + })); }); #if CAN_OPEN_LINKS diff --git a/source/funkin/util/SortUtil.hx b/source/funkin/util/SortUtil.hx index c5ac175be..f6d3721f0 100644 --- a/source/funkin/util/SortUtil.hx +++ b/source/funkin/util/SortUtil.hx @@ -97,7 +97,7 @@ class SortUtil * @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 + public static function alphabetically(?a:String, ?b:String):Int { a = a.toUpperCase(); b = b.toUpperCase(); From bd17d965f57ef1a62638def0f0f5ff35e14613a2 Mon Sep 17 00:00:00 2001 From: AppleHair <95587502+AppleHair@users.noreply.github.com> Date: Fri, 14 Jun 2024 16:53:33 +0300 Subject: [PATCH 48/48] [BUGFIX] Fixed Ranks not appearing in freeplay for custom variations Freeplay tries to access a song's rank using the current difficulty name alone, but custom variation ranks are being saved with a variation prefix. This PR makes freeplay look for the variation prefix when necessary. --- source/funkin/ui/freeplay/FreeplayState.hx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 5725101cd..e36b6942f 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -2158,8 +2158,13 @@ class FreeplaySongData { this.albumId = songDifficulty.album; } + + // TODO: This line of code makes me sad, but you can't really fix it without a breaking migration. + // `easy`, `erect`, `normal-pico`, etc. + var suffixedDifficulty = (songDifficulty.variation != Constants.DEFAULT_VARIATION + && songDifficulty.variation != 'erect') ? '$currentDifficulty-${songDifficulty.variation}' : currentDifficulty; - this.scoringRank = Save.instance.getSongRank(songId, currentDifficulty); + this.scoringRank = Save.instance.getSongRank(songId, suffixedDifficulty); this.isNew = song.isSongNew(currentDifficulty); }