From 2833827fbf4fcbfffe09b7badc66f2f3777583c0 Mon Sep 17 00:00:00 2001 From: sector-a <82838084+sector-a@users.noreply.github.com> Date: Fri, 25 Jul 2025 01:05:05 +0300 Subject: [PATCH 01/89] Change the way game searches for pixel icons --- source/funkin/play/character/CharacterData.hx | 41 +++++++----------- source/funkin/ui/PixelatedIcon.hx | 43 ++++++++----------- 2 files changed, 33 insertions(+), 51 deletions(-) diff --git a/source/funkin/play/character/CharacterData.hx b/source/funkin/play/character/CharacterData.hx index d4448dcda..5b533c86a 100644 --- a/source/funkin/play/character/CharacterData.hx +++ b/source/funkin/play/character/CharacterData.hx @@ -288,33 +288,22 @@ class CharacterDataParser { var charPath:String = "freeplay/icons/"; - // FunkinCrew please dont skin me alive for copying pixelated icon and changing it a tiny bit - switch (char) + final charIDParts:Array = char.split("-"); + var iconName:String = ""; + for (i in 0...charIDParts.length) { - case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf" | "bf-dark": - charPath += "bfpixel"; - case "monster-christmas": - charPath += "monsterpixel"; - case "mom" | "mom-car": - charPath += "mommypixel"; - case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene": - charPath += "picopixel"; - case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen" | "gf-dark": - charPath += "gfpixel"; - case "dad": - charPath += "dadpixel"; - case "darnell-blazin": - charPath += "darnellpixel"; - case "senpai-angry": - charPath += "senpaipixel"; - case "spooky-dark": - charPath += "spookypixel"; - case "tankman-atlas" | "tankman-bloody": - charPath += "tankmanpixel"; - case "pico-christmas" | "pico-dark": - charPath += "picopixel"; - default: - charPath += '${char}pixel'; + iconName += charIDParts[i]; + + if (Assets.exists(Paths.image(charPath + '${iconName}pixel'))) + { + charPath += '${iconName}pixel'; + break; + } + else + { + if (i < charIDParts.length - 1) iconName += '-'; + continue; + } } if (!Assets.exists(Paths.image(charPath))) diff --git a/source/funkin/ui/PixelatedIcon.hx b/source/funkin/ui/PixelatedIcon.hx index 880ed7897..d1858c19c 100644 --- a/source/funkin/ui/PixelatedIcon.hx +++ b/source/funkin/ui/PixelatedIcon.hx @@ -1,5 +1,6 @@ package funkin.ui; +import openfl.utils.Assets; import funkin.graphics.FlxFilteredSprite; /** @@ -21,33 +22,25 @@ class PixelatedIcon extends FlxFilteredSprite { var charPath:String = "freeplay/icons/"; - switch (char) + final charIDParts:Array = char.split("-"); + var iconName:String = ""; + for (i in 0...charIDParts.length) { - case "bf-christmas" | "bf-car" | "bf-pixel" | "bf-holding-gf": - charPath += "bfpixel"; - case "monster-christmas": - charPath += "monsterpixel"; - case "mom" | "mom-car": - charPath += "mommypixel"; - case "pico-blazin" | "pico-playable" | "pico-speaker" | "pico-pixel" | "pico-holding-nene": - charPath += "picopixel"; - case "gf-christmas" | "gf-car" | "gf-pixel" | "gf-tankmen": - charPath += "gfpixel"; - case "dad": - charPath += "dadpixel"; - case "darnell-blazin": - charPath += "darnellpixel"; - case "senpai-angry": - charPath += "senpaipixel"; - case "spooky-dark": - charPath += "spookypixel"; - case "tankman-atlas" | "tankman-bloody": - charPath += "tankmanpixel"; - default: - charPath += '${char}pixel'; + iconName += charIDParts[i]; + + if (Assets.exists(Paths.image(charPath + '${iconName}pixel'))) + { + charPath += '${iconName}pixel'; + break; + } + else + { + if (i < charIDParts.length - 1) iconName += '-'; + continue; + } } - if (!openfl.utils.Assets.exists(Paths.image(charPath))) + if (!Assets.exists(Paths.image(charPath))) { trace('[WARN] Character ${char} has no freeplay icon.'); this.visible = false; @@ -58,7 +51,7 @@ class PixelatedIcon extends FlxFilteredSprite this.visible = true; } - var isAnimated = openfl.utils.Assets.exists(Paths.file('images/$charPath.xml')); + var isAnimated = Assets.exists(Paths.file('images/$charPath.xml')); if (isAnimated) { From 463afb43c07679c197c4aac389abcdb12a4903eb Mon Sep 17 00:00:00 2001 From: sector-a <82838084+sector-a@users.noreply.github.com> Date: Fri, 25 Jul 2025 01:06:26 +0300 Subject: [PATCH 02/89] assets submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 69c02fa50..4aadf7992 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 69c02fa5019f603324a7d2ae362327a1eef9d109 +Subproject commit 4aadf799232330f903400b306d111352cd9875c2 From 9fa70775b90341e723ed220863b85518a04a1e4d Mon Sep 17 00:00:00 2001 From: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:15:06 +0300 Subject: [PATCH 03/89] Use `funkin.Assets` instead of `openfl.utils.Assets`. --- source/funkin/play/GameOverSubState.hx | 1 - source/funkin/ui/PixelatedIcon.hx | 1 - source/funkin/ui/freeplay/backcards/BackingCard.hx | 1 - source/funkin/ui/freeplay/backcards/NewCharacterCard.hx | 1 - source/funkin/ui/story/StoryMenuState.hx | 1 - tests/unit/source/FunkinTest.hx | 1 - tests/unit/source/funkin/play/notes/notestyle/NoteStyleTest.hx | 1 - tests/unit/source/funkin/util/assets/DataAssetsTest.hx | 1 - 8 files changed, 8 deletions(-) diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 875cb598e..858ad787b 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -21,7 +21,6 @@ import funkin.util.MathUtil; import funkin.effects.RetroCameraFade; import flixel.math.FlxPoint; import funkin.util.TouchUtil; -import openfl.utils.Assets; #if FEATURE_MOBILE_ADVERTISEMENTS import funkin.mobile.util.AdMobUtil; #end diff --git a/source/funkin/ui/PixelatedIcon.hx b/source/funkin/ui/PixelatedIcon.hx index d1858c19c..7a01a023a 100644 --- a/source/funkin/ui/PixelatedIcon.hx +++ b/source/funkin/ui/PixelatedIcon.hx @@ -1,6 +1,5 @@ package funkin.ui; -import openfl.utils.Assets; import funkin.graphics.FlxFilteredSprite; /** diff --git a/source/funkin/ui/freeplay/backcards/BackingCard.hx b/source/funkin/ui/freeplay/backcards/BackingCard.hx index 34e5c5398..a005e315e 100644 --- a/source/funkin/ui/freeplay/backcards/BackingCard.hx +++ b/source/funkin/ui/freeplay/backcards/BackingCard.hx @@ -16,7 +16,6 @@ import funkin.modding.IScriptedClass.IStateChangingScriptedClass; import funkin.modding.events.ScriptEvent; import funkin.ui.FullScreenScaleMode; import funkin.util.BitmapUtil; -import openfl.utils.Assets; /** * A class for the backing cards so they dont have to be part of freeplayState...... diff --git a/source/funkin/ui/freeplay/backcards/NewCharacterCard.hx b/source/funkin/ui/freeplay/backcards/NewCharacterCard.hx index b703b0561..bb05cfe22 100644 --- a/source/funkin/ui/freeplay/backcards/NewCharacterCard.hx +++ b/source/funkin/ui/freeplay/backcards/NewCharacterCard.hx @@ -10,7 +10,6 @@ import funkin.graphics.adobeanimate.FlxAtlasSprite; import funkin.modding.events.ScriptEvent; import openfl.display.BlendMode; import funkin.util.BitmapUtil; -import openfl.utils.Assets; class NewCharacterCard extends BackingCard { diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index e160173e8..82d9f1568 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -25,7 +25,6 @@ import funkin.ui.transition.stickers.StickerSubState; import funkin.util.MathUtil; import funkin.util.SwipeUtil; import funkin.util.TouchUtil; -import openfl.utils.Assets; import funkin.ui.FullScreenScaleMode; #if FEATURE_DISCORD_RPC import funkin.api.discord.DiscordClient; diff --git a/tests/unit/source/FunkinTest.hx b/tests/unit/source/FunkinTest.hx index 8f47a7d36..046beef40 100644 --- a/tests/unit/source/FunkinTest.hx +++ b/tests/unit/source/FunkinTest.hx @@ -1,6 +1,5 @@ package; -import openfl.utils.Assets; import openfl.errors.Error; import flixel.FlxG; import flixel.FlxState; diff --git a/tests/unit/source/funkin/play/notes/notestyle/NoteStyleTest.hx b/tests/unit/source/funkin/play/notes/notestyle/NoteStyleTest.hx index f2d3e570c..33f2a4c43 100644 --- a/tests/unit/source/funkin/play/notes/notestyle/NoteStyleTest.hx +++ b/tests/unit/source/funkin/play/notes/notestyle/NoteStyleTest.hx @@ -11,7 +11,6 @@ import massive.munit.async.AsyncFactory; import funkin.data.notestyle.NoteStyleRegistry; import funkin.play.notes.notestyle.NoteStyle; import flixel.animation.FlxAnimationController; -import openfl.utils.Assets; import flixel.math.FlxPoint; @:access(funkin.play.notes.notestyle.NoteStyle) diff --git a/tests/unit/source/funkin/util/assets/DataAssetsTest.hx b/tests/unit/source/funkin/util/assets/DataAssetsTest.hx index b3df036e5..579ae9903 100644 --- a/tests/unit/source/funkin/util/assets/DataAssetsTest.hx +++ b/tests/unit/source/funkin/util/assets/DataAssetsTest.hx @@ -1,6 +1,5 @@ package funkin.util.assets; -import openfl.utils.Assets; import massive.munit.util.Timer; import massive.munit.Assert; import massive.munit.async.AsyncFactory; From 0d57f61ffa6a5d08c78b23e128280b4549e93e04 Mon Sep 17 00:00:00 2001 From: sector-a <82838084+sector-a@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:19:46 +0300 Subject: [PATCH 04/89] Scale up the bg in debug menu on widescreen Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com> --- source/funkin/ui/debug/DebugMenuSubState.hx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/funkin/ui/debug/DebugMenuSubState.hx b/source/funkin/ui/debug/DebugMenuSubState.hx index 3f5183819..3b06a4c58 100644 --- a/source/funkin/ui/debug/DebugMenuSubState.hx +++ b/source/funkin/ui/debug/DebugMenuSubState.hx @@ -4,6 +4,7 @@ import flixel.math.FlxPoint; import flixel.FlxObject; import flixel.FlxSprite; import funkin.ui.MusicBeatSubState; +import funkin.ui.FullScreenScaleMode; import funkin.audio.FunkinSound; import funkin.ui.TextMenuList; import funkin.ui.debug.charting.ChartEditorState; @@ -37,7 +38,7 @@ class DebugMenuSubState extends MusicBeatSubState // Create the green background. var menuBG = new FlxSprite().loadGraphic(Paths.image('menuDesat')); menuBG.color = 0xFF4CAF50; - menuBG.setGraphicSize(Std.int(menuBG.width * 1.1)); + menuBG.setGraphicSize(Std.int(menuBG.width * 1.1 * FullScreenScaleMode.wideScale.x)); menuBG.updateHitbox(); menuBG.screenCenter(); menuBG.scrollFactor.set(0, 0); From 1f18a3d1f4314335c7011f7b464528a20308445f Mon Sep 17 00:00:00 2001 From: sector-a <82838084+sector-a@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:37:39 +0300 Subject: [PATCH 05/89] Fix diff dots alignment. Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com> --- source/funkin/ui/freeplay/FreeplayState.hx | 31 ++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index e67394e3b..11d3288d1 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -119,6 +119,11 @@ class FreeplayState extends MusicBeatSubState */ public static final SONGS_POS_MULTI:Float = 0.75; + /** + * For positioning the difficulty dots. + */ + public static final DEFAULT_DOTS_GROUP_POS:Array = [260, 170]; + var songs:Array> = []; var curSelected:Int = 0; @@ -315,7 +320,7 @@ class FreeplayState extends MusicBeatSubState grpCapsules = new FlxTypedGroup(); grpDifficulties = new FlxTypedSpriteGroup(-300, 80); - difficultyDots = new FlxTypedSpriteGroup(203, 170); + difficultyDots = new FlxTypedSpriteGroup(DEFAULT_DOTS_GROUP_POS[0], DEFAULT_DOTS_GROUP_POS[1]); letterSort = new LetterSort((CUTOUT_WIDTH * SONGS_POS_MULTI) + 400, 75); rankBg = new FunkinSprite(0, 0); rankVignette = new FlxSprite(0, 0).loadGraphic(Paths.image('freeplay/rankVignette')); @@ -1295,9 +1300,22 @@ class FreeplayState extends MusicBeatSubState function refreshDots(amount:Int, index:Int, prevIndex:Int):Void { var distance:Int = 30; + var groupOffset:Float = 14.7; var shiftAmt:Float = (distance * amount) / 2; var daSong:Null = currentCapsule.freeplayData; + final maxDotsPerRow:Int = 8; + if (difficultyDots.group.members.length > maxDotsPerRow) + { + difficultyDots.x = DEFAULT_DOTS_GROUP_POS[0] - groupOffset * (maxDotsPerRow - 1); + } + else + { + difficultyDots.x = DEFAULT_DOTS_GROUP_POS[0] - groupOffset * (difficultyDots.group.members.length - 1); + } + + var curRow:Int = 0; + var curDot:Int = 0; for (i in 0...difficultyDots.group.members.length) { // if (difficultyDots.group.members[i] == null) continue; @@ -1329,7 +1347,16 @@ class FreeplayState extends MusicBeatSubState } difficultyDots.group.members[i].visible = true; - difficultyDots.group.members[i].x = (CUTOUT_WIDTH * DJ_POS_MULTI) + ((difficultyDots.x + (distance * i)) - shiftAmt); + difficultyDots.group.members[i].x = (CUTOUT_WIDTH * DJ_POS_MULTI) + ((difficultyDots.x + (distance * curDot)) - shiftAmt); + difficultyDots.group.members[i].y = DEFAULT_DOTS_GROUP_POS[1] + distance * curRow; + + curDot++; + + if (curDot >= maxDotsPerRow) + { + curDot = 0; + curRow++; + } if (daSong?.data.hasDifficulty(diffId, daSong?.data.getFirstValidVariation(diffId, currentCharacter)) == false) { From 2d7a2202f5164f87964ee74f721fd3db4d70365e Mon Sep 17 00:00:00 2001 From: Karim Akra <144803230+KarimAkra@users.noreply.github.com> Date: Fri, 25 Jul 2025 16:45:55 +0300 Subject: [PATCH 06/89] Fix song restarting bug (for good??). Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com> --- hmm.json | 2 +- source/funkin/play/PlayState.hx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/hmm.json b/hmm.json index 984a05d0d..2f833c0d2 100644 --- a/hmm.json +++ b/hmm.json @@ -170,7 +170,7 @@ "name": "lime", "type": "git", "dir": null, - "ref": "c750ebf6b48c4bc018abe9855fbae5ffdbc4771a", + "ref": "6bf7bcda72c0bff89cac1049bd7dbe45dac5a80a", "url": "https://github.com/FunkinCrew/lime-mobile" }, { diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index d02e05412..6944b64a2 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1063,10 +1063,10 @@ class PlayState extends MusicBeatSubState // If, after updating the conductor, the instrumental has finished, end the song immediately. // This helps prevent a major bug where the level suddenly loops back to the start or middle. - if (Conductor.instance.songPosition >= (FlxG.sound.music.endTime ?? FlxG.sound.music.length)) - { - if (mayPauseGame && !isSongEnd) endSong(skipEndingTransition); - } + // if (Conductor.instance.songPosition >= (FlxG.sound.music.endTime ?? FlxG.sound.music.length)) + // { + // if (mayPauseGame && !isSongEnd) endSong(skipEndingTransition); + // } } var pauseButtonCheck:Bool = false; From 76c0dea92b79b839936f95c87fb3940ed1e88169 Mon Sep 17 00:00:00 2001 From: sector-a <82838084+sector-a@users.noreply.github.com> Date: Fri, 25 Jul 2025 17:23:04 +0300 Subject: [PATCH 07/89] Move results and death haptics to scripts. --- art | 2 +- assets | 2 +- source/funkin/play/GameOverSubState.hx | 119 +------------------------ source/funkin/play/ResultState.hx | 101 --------------------- 4 files changed, 4 insertions(+), 220 deletions(-) diff --git a/art b/art index 67e550dbd..490e97f4c 160000 --- a/art +++ b/art @@ -1 +1 @@ -Subproject commit 67e550dbd22a8ea429eecc8ce078a36f74ea7723 +Subproject commit 490e97f4c6e673a52ee4f9af98325b1aa2d0c3fe diff --git a/assets b/assets index 4aadf7992..79396dc5d 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 4aadf799232330f903400b306d111352cd9875c2 +Subproject commit 79396dc5da448e9928ab7d5155b267f83702abe3 diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 858ad787b..ad30e99f1 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -101,18 +101,6 @@ class GameOverSubState extends MusicBeatSubState var canInput:Bool = false; - var justDied:Bool = true; - - var isSpecialAnimation:Bool = false; - - var gameOverVibrationPreset:VibrationPreset = - { - period: 0, - duration: Constants.DEFAULT_VIBRATION_DURATION, - amplitude: Constants.MIN_VIBRATION_AMPLITUDE, - sharpness: Constants.DEFAULT_VIBRATION_SHARPNESS - }; - public function new(params:GameOverParams) { super(); @@ -197,6 +185,8 @@ class GameOverSubState extends MusicBeatSubState addBackButton(FlxG.width - 230, FlxG.height - 200, FlxColor.WHITE, goBack); #end + HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION); + // Allow input a second later to prevent accidental gameover skips. new FlxTimer().start(1, function(tmr:FlxTimer) { canInput = true; @@ -329,9 +319,6 @@ class GameOverSubState extends MusicBeatSubState } } - // Handle vibrations on update. - if (HapticUtil.hapticsAvailable) handleAnimationVibrations(); - // Start death music before firstDeath gets replaced super.update(elapsed); } @@ -614,108 +601,6 @@ class GameOverSubState extends MusicBeatSubState var hasPlayedDeathQuote:Bool = false; - /** - * Used for death haptics. - */ - var startedTimerHaptics:Bool = false; - - /** - * Unique vibrations for each death animation. - */ - function handleAnimationVibrations():Void - { - if ((parentPlayState?.isMinimalMode ?? true) || boyfriend == null) return; - - if (justDied) - { - if (isSpecialAnimation) - { - HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION * 5); - trace("It's a special game over animation."); - } - else - { - HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION); - } - justDied = false; - } - - if (boyfriend.animation == null) return; - - final curFrame:Int = (boyfriend.animation.curAnim != null) ? boyfriend.animation.curAnim.curFrame : -1; - if (boyfriend.characterId.startsWith("bf")) - { - // BF's mic drops. - if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && curFrame == 27) - { - HapticUtil.vibrateByPreset(gameOverVibrationPreset); - } - - // BF's balls pulsating. - if (boyfriend.getCurrentAnimation().startsWith('deathLoop') && (curFrame == 0 || curFrame == 18)) - { - HapticUtil.vibrateByPreset(gameOverVibrationPreset); - } - - return; - } - - // Pico dies because of Darnell beating him up. - if (boyfriend.characterId == "pico-blazin") - { - if (!startedTimerHaptics) - { - startedTimerHaptics = true; - - new FlxTimer().start(0.5, function(tmr:FlxTimer) { - // Pico falls on his knees. - HapticUtil.vibrateByPreset(gameOverVibrationPreset); - - new FlxTimer().start(0.6, function(tmr:FlxTimer) { - // Pico falls "asleep". :) - HapticUtil.vibrateByPreset(gameOverVibrationPreset); - }); - }); - - return; - } - } - else if (boyfriend.characterId.startsWith("pico") && boyfriend.characterId != "pico-holding-nene") - { - if (isSpecialAnimation) - { - if (startedTimerHaptics) return; - - startedTimerHaptics = true; - - // Death by Darnell's can. - new FlxTimer().start(1.85, function(tmr:FlxTimer) { - // Pico falls on his knees. - HapticUtil.vibrateByPreset(gameOverVibrationPreset); - }); - } - else - { - // Pico falls on his back. - if (boyfriend.getCurrentAnimation().startsWith('firstDeath') && curFrame == 20) - { - HapticUtil.vibrateByPreset(gameOverVibrationPreset); - } - - // Blood firework woohoo!!!! - if (boyfriend.getCurrentAnimation().startsWith('deathLoop') && curFrame % 2 == 0) - { - final randomAmplitude:Float = FlxG.random.float(Constants.MIN_VIBRATION_AMPLITUDE / 100, Constants.MIN_VIBRATION_AMPLITUDE); - final randomDuration:Float = FlxG.random.float(Constants.DEFAULT_VIBRATION_DURATION / 10, Constants.DEFAULT_VIBRATION_DURATION); - - HapticUtil.vibrate(0, randomDuration, randomAmplitude); - } - } - - return; - } - } - public override function destroy():Void { super.destroy(); diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index 9db60589c..bf2b9864f 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -751,105 +751,6 @@ class ResultState extends MusicBeatSubState // maskShaderSongName.frameUV = songName.frame.uv; } - private function handleAnimationVibrations() - { - for (atlas in characterAtlasAnimations) - { - if (atlas == null || atlas.sprite == null) continue; - - switch (rank) - { - case ScoringRank.PERFECT | ScoringRank.PERFECT_GOLD: - switch (playerCharacterId) - { - // Feel the bed fun :freaky: - case "bf": - if (atlas.sprite.anim.curFrame > 87 && atlas.sprite.anim.curFrame % 5 == 0) - { - HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE); - break; - } - - // GF slams into the wall. - if (atlas.sprite.anim.curFrame == 51) - { - HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5); - break; - } - - // Pico drop-kicking Nene. - case "pico": - if (atlas.sprite.anim.curFrame == 52) - { - HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION * 5, Constants.MAX_VIBRATION_AMPLITUDE); - break; - } - - default: - break; - } - - case ScoringRank.GREAT | ScoringRank.EXCELLENT: - switch (playerCharacterId) - { - // Pico explodes the targets with a rocket launcher. - case "pico": - // Pico shoots. - if (atlas.sprite.anim.curFrame == 45) - { - HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5); - break; - } - - // The targets explode. - if (atlas.sprite.anim.curFrame == 50) - { - HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION, Constants.MAX_VIBRATION_AMPLITUDE); - break; - } - - default: - break; - } - - case ScoringRank.GOOD: - switch (playerCharacterId) - { - // Pico shooting the targets. - case "pico": - if (atlas.sprite.anim.curFrame % 2 != 0) continue; - - final frames:Array> = [[40, 50], [80, 90], [140, 157]]; - for (i in 0...frames.length) - { - if (atlas.sprite.anim.curFrame < frames[i][0] || atlas.sprite.anim.curFrame > frames[i][1]) continue; - - HapticUtil.vibrate(0, 0.01, Constants.MAX_VIBRATION_AMPLITUDE); - break; - } - - default: - break; - } - - case ScoringRank.SHIT: - switch (playerCharacterId) - { - // BF falling and GF slams on BF with her ass. - case "bf": - if (atlas.sprite.anim.curFrame == 5 || atlas.sprite.anim.curFrame == 90) - { - HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD * 2, Constants.DEFAULT_VIBRATION_DURATION * 2, Constants.MAX_VIBRATION_AMPLITUDE); - break; - } - - default: - break; - } - } - } - } - override function update(elapsed:Float):Void { maskShaderDifficulty.swagSprX = difficulty.x; @@ -1035,8 +936,6 @@ class ResultState extends MusicBeatSubState #end } - if (HapticUtil.hapticsAvailable) handleAnimationVibrations(); - super.update(elapsed); } From 530bd1f11de120383fcbdf719ea5f0467d9996c6 Mon Sep 17 00:00:00 2001 From: sector-a <82838084+sector-a@users.noreply.github.com> Date: Fri, 25 Jul 2025 17:36:34 +0300 Subject: [PATCH 08/89] Add onPause callback to pause substate. Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com> --- source/funkin/play/PauseSubState.hx | 11 ++++++++++- source/funkin/play/PlayState.hx | 10 ++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index e681f42d2..5f121106f 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -213,6 +213,11 @@ class PauseSubState extends MusicBeatSubState */ var menuEntryText:FlxTypedSpriteGroup; + /** + * Callback that gets called once substate gets open. + */ + var onPause:Void->Void; + // =============== // Audio Variables // =============== @@ -222,10 +227,11 @@ class PauseSubState extends MusicBeatSubState // Constructor // =============== - public function new(?params:PauseSubStateParams) + public function new(?params:PauseSubStateParams, ?onPause:Void->Void) { super(); this.currentMode = params?.mode ?? Standard; + this.onPause = onPause; } // =============== @@ -244,6 +250,8 @@ class PauseSubState extends MusicBeatSubState AdMobUtil.addBanner(extension.admob.AdmobBannerSize.BANNER, extension.admob.AdmobBannerAlign.TOP_LEFT); #end + if (onPause != null) onPause(); + super.create(); startPauseMusic(); @@ -286,6 +294,7 @@ class PauseSubState extends MusicBeatSubState hapticTimer.cancel(); hapticTimer = null; pauseMusic.stop(); + onPause = null; } // =============== diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 6944b64a2..615bf93f1 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1234,14 +1234,12 @@ class PlayState extends MusicBeatSubState switch (mode) { case Conversation: - currentConversation.pauseMusic(); preparePauseUI(); - openPauseSubState(Conversation, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene); + openPauseSubState(Conversation, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene, () -> currentConversation.pauseMusic()); case Cutscene: - VideoCutscene.pauseVideo(); preparePauseUI(); - openPauseSubState(Cutscene, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene); + openPauseSubState(Cutscene, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene, () -> VideoCutscene.pauseVideo()); default: // also known as standard if (!isInCountdown || isInCutscene) return; @@ -1299,9 +1297,9 @@ class PlayState extends MusicBeatSubState #end } - function openPauseSubState(mode:PauseMode, cam:FlxCamera):Void + function openPauseSubState(mode:PauseMode, cam:FlxCamera, ?onPause:Void->Void):Void { - final pauseSubState = new PauseSubState({mode: mode}); + final pauseSubState = new PauseSubState({mode: mode}, onPause); FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransOut = true; pauseSubState.camera = cam; From 6abeefb15d0593bdf87b53c257dbe30e6cb28fb5 Mon Sep 17 00:00:00 2001 From: sector-a <82838084+sector-a@users.noreply.github.com> Date: Fri, 25 Jul 2025 17:43:34 +0300 Subject: [PATCH 09/89] Set cliprect for clear percent. Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com> --- source/funkin/play/ResultState.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index bf2b9864f..74088ff17 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -744,6 +744,7 @@ class ResultState extends MusicBeatSubState super.draw(); songName.clipRect = FlxRect.get(Math.max(0, 520 - songName.x), 0, FlxG.width, songName.height); + clearPercentSmall.forEachAlive(spr -> spr.clipRect = FlxRect.get(Math.max(0, 520 - spr.x), 0, FlxG.width, spr.height)); // PROBABLY SHOULD FIX MEMORY FREE OR WHATEVER THE PUT() FUNCTION DOES !!!! FEELS LIKE IT STUTTERS!!! From 444697620c53bcb269de486b08263a0323348692 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Sat, 26 Jul 2025 03:04:00 -0400 Subject: [PATCH 10/89] Use `round` instead of `floor` to fix rounding errors in Preferences. Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com> --- source/funkin/ui/options/items/NumberPreferenceItem.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/ui/options/items/NumberPreferenceItem.hx b/source/funkin/ui/options/items/NumberPreferenceItem.hx index 2f292f909..2972c9d44 100644 --- a/source/funkin/ui/options/items/NumberPreferenceItem.hx +++ b/source/funkin/ui/options/items/NumberPreferenceItem.hx @@ -148,6 +148,6 @@ class NumberPreferenceItem extends TextMenuItem function toFixed(value:Float):Float { var multiplier:Float = Math.pow(10, precision); - return Math.floor(value * multiplier) / multiplier; + return Math.round(value * multiplier) / multiplier; } } From b9852c316b6b602938fa490ffb1309dd526f6be0 Mon Sep 17 00:00:00 2001 From: Kade <26305836+Kade-github@users.noreply.github.com> Date: Sat, 26 Jul 2025 00:36:29 -0700 Subject: [PATCH 11/89] offset menu changes Co-authored-by: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com> --- project.hxp | 6 +++--- source/funkin/play/PauseSubState.hx | 2 +- source/funkin/ui/options/OffsetMenu.hx | 14 ++++++++++---- source/funkin/ui/options/OptionsState.hx | 8 ++++---- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/project.hxp b/project.hxp index 16fb0160e..e41f22086 100644 --- a/project.hxp +++ b/project.hxp @@ -330,10 +330,10 @@ class Project extends HXProject static final FEATURE_HAPTICS:FeatureFlag = "FEATURE_HAPTICS"; /** - * `-DFEATURE_INPUT_OFFSETS` + * `-DFEATURE_LAG_ADJUSTMENT` * If this flag is enabled, the input offsets menu will be available to configure your audio and visual offsets. */ - static final FEATURE_INPUT_OFFSETS:FeatureFlag = "FEATURE_INPUT_OFFSETS"; + static final FEATURE_LAG_ADJUSTMENT:FeatureFlag = "FEATURE_LAG_ADJUSTMENT"; /** * `-DFEATURE_LOG_TRACE` @@ -829,7 +829,7 @@ class Project extends HXProject FEATURE_STAGE_EDITOR.apply(this, !(isWeb() || isMobile())); // Should be true except on web builds (some asset stuff breaks it) - FEATURE_INPUT_OFFSETS.apply(this, !isWeb()); + FEATURE_LAG_ADJUSTMENT.apply(this, !isWeb()); // Should be true except on web and mobile builds. // Screenshots doesn't work there, and mobile has its own screenshots anyway. diff --git a/source/funkin/play/PauseSubState.hx b/source/funkin/play/PauseSubState.hx index 5f121106f..e3766791c 100644 --- a/source/funkin/play/PauseSubState.hx +++ b/source/funkin/play/PauseSubState.hx @@ -453,7 +453,7 @@ class PauseSubState extends MusicBeatSubState offsetText.y = FlxG.height - (offsetText.height + offsetText.height + 40); offsetTextInfo.y = offsetText.y + offsetText.height + 4; - #if !mobile + #if (!mobile && FEATURE_LAG_ADJUSTMENT) metadata.add(offsetText); metadata.add(offsetTextInfo); #end diff --git a/source/funkin/ui/options/OffsetMenu.hx b/source/funkin/ui/options/OffsetMenu.hx index 7e6a1af8b..78419ddc4 100644 --- a/source/funkin/ui/options/OffsetMenu.hx +++ b/source/funkin/ui/options/OffsetMenu.hx @@ -545,6 +545,7 @@ class OffsetMenu extends Page if (FlxG.sound.music.time < _lastTime) { localConductor.update(FlxG.sound.music.time, !calibrating); + b = localConductor.currentBeatTime; // Update arrows to be the correct distance away from the receptor. var lastArrowBeat:Float = 0; @@ -558,7 +559,7 @@ class OffsetMenu extends Page } if (calibrating) { - arrowBeat = lastArrowBeat + 2; + arrowBeat = lastArrowBeat; } else arrowBeat = 4; @@ -566,6 +567,10 @@ class OffsetMenu extends Page testStrumline.clean(); testStrumline.noteData = []; testStrumline.nextNoteIndex = 0; + trace('Restarting conductor'); + + _lastTime = FlxG.sound.music.time; + return; } _lastBeat = b; @@ -608,7 +613,7 @@ class OffsetMenu extends Page countText.text = 'Current Offset: ' + Std.int(appliedOffsetLerp) + 'ms'; var toRemove:Array = []; - + var _lastArrowBeat:Float = 0; // Update arrows for (i in 0...arrows.length) { @@ -629,12 +634,13 @@ class OffsetMenu extends Page arrow.sprite.alpha -= elapsed * 5; } - if (arrow.sprite.alpha <= 0) + if (arrow.beat == _lastArrowBeat || arrow.sprite.alpha <= 0) { toRemove.push(arrow); arrow.sprite.kill(); - // arrow.debugText.kill(); + continue; } + _lastArrowBeat = arrow.beat; } // Remove arrows that are marked for removal. diff --git a/source/funkin/ui/options/OptionsState.hx b/source/funkin/ui/options/OptionsState.hx index ed456034d..dedbece9e 100644 --- a/source/funkin/ui/options/OptionsState.hx +++ b/source/funkin/ui/options/OptionsState.hx @@ -72,7 +72,7 @@ class OptionsState extends MusicBeatState var options:OptionsMenu = optionsCodex.addPage(Options, new OptionsMenu()); var preferences:PreferencesMenu = optionsCodex.addPage(Preferences, new PreferencesMenu()); var controls:ControlsMenu = optionsCodex.addPage(Controls, new ControlsMenu()); - #if FEATURE_INPUT_OFFSETS + #if FEATURE_LAG_ADJUSTMENT var offsets:OffsetMenu = optionsCodex.addPage(Offsets, new OffsetMenu()); #end @@ -81,7 +81,7 @@ class OptionsState extends MusicBeatState options.onExit.add(exitToMainMenu); controls.onExit.add(exitControls); preferences.onExit.add(optionsCodex.switchPage.bind(Options)); - #if FEATURE_INPUT_OFFSETS + #if FEATURE_LAG_ADJUSTMENT offsets.onExit.add(exitOffsets); #end } @@ -172,8 +172,8 @@ class OptionsMenu extends Page // createItem("CONTROL SCHEMES", function() { // FlxG.state.openSubState(new ControlsSchemeMenu()); // }); - #if FEATURE_INPUT_OFFSETS - createItem("INPUT OFFSETS", function() { + #if FEATURE_LAG_ADJUSTMENT + createItem("LAG ADJUSTMENT", function() { FlxG.sound.music.fadeOut(0.5, 0, function(tw) { FunkinSound.playMusic('offsetsLoop', { From 87b40c1203b6465b54b27a65bebaa05f97db2905 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 18 Jul 2025 09:43:27 -0400 Subject: [PATCH 12/89] Obsolete `NewgroundsCredentials.hx` for storing secret keys in favor of `.env` --- source/Prebuild.hx | 28 ------------------- .../funkin/api/newgrounds/NewgroundsClient.hx | 19 ++++++------- 2 files changed, 9 insertions(+), 38 deletions(-) diff --git a/source/Prebuild.hx b/source/Prebuild.hx index 7102d826b..fd35bb6cf 100644 --- a/source/Prebuild.hx +++ b/source/Prebuild.hx @@ -9,16 +9,6 @@ class Prebuild { static inline final BUILD_TIME_FILE:String = '.build_time'; - static final NG_CREDS_PATH:String = './source/funkin/api/newgrounds/NewgroundsCredentials.hx'; - - static final NG_CREDS_TEMPLATE:String = "package funkin.api.newgrounds; - -class NewgroundsCredentials -{ - public static final APP_ID:String = #if API_NG_APP_ID haxe.macro.Compiler.getDefine(\"API_NG_APP_ID\") #else 'INSERT APP ID HERE' #end; - public static final ENCRYPTION_KEY:String = #if API_NG_ENC_KEY haxe.macro.Compiler.getDefine(\"API_NG_ENC_KEY\") #else 'INSERT ENCRYPTION KEY HERE' #end; -}"; - static function main():Void { var start:Float = Sys.time(); @@ -26,8 +16,6 @@ class NewgroundsCredentials saveBuildTime(); - buildCredsFile(); - var end:Float = Sys.time(); var duration:Float = end - start; trace('[PREBUILD] Finished pre-build tasks in $duration seconds.'); @@ -41,20 +29,4 @@ class NewgroundsCredentials fo.writeDouble(now); fo.close(); } - - static function buildCredsFile():Void - { - if (sys.FileSystem.exists(NG_CREDS_PATH)) - { - trace('[PREBUILD] NewgroundsCredentials.hx already exists, skipping.'); - } - else - { - trace('[PREBUILD] Creating NewgroundsCredentials.hx...'); - - var fileContents:String = NG_CREDS_TEMPLATE; - - sys.io.File.saveContent(NG_CREDS_PATH, fileContents); - } - } } diff --git a/source/funkin/api/newgrounds/NewgroundsClient.hx b/source/funkin/api/newgrounds/NewgroundsClient.hx index 8bd993ff6..b74802b9b 100644 --- a/source/funkin/api/newgrounds/NewgroundsClient.hx +++ b/source/funkin/api/newgrounds/NewgroundsClient.hx @@ -1,5 +1,6 @@ package funkin.api.newgrounds; +import funkin.util.macro.EnvironmentConfigMacro; import funkin.save.Save; import funkin.api.newgrounds.Medals.Medal; #if FEATURE_NEWGROUNDS @@ -16,6 +17,9 @@ import io.newgrounds.objects.User; @:nullSafety class NewgroundsClient { + static var APP_ID:String = EnvironmentConfigMacro.environmentConfig.get("API_NG_APP_ID"); + static var ENCRYPTION_KEY:String = EnvironmentConfigMacro.environmentConfig.get("API_NG_ENC_KEY"); + public static var instance(get, never):NewgroundsClient; static var _instance:Null = null; @@ -35,8 +39,8 @@ class NewgroundsClient trace('[NEWGROUNDS] Initializing client...'); #if FEATURE_NEWGROUNDS_DEBUG - trace('[NEWGROUNDS] App ID: ${NewgroundsCredentials.APP_ID}'); - trace('[NEWGROUNDS] Encryption Key: ${NewgroundsCredentials.ENCRYPTION_KEY}'); + trace('[NEWGROUNDS] App ID: ${APP_ID}'); + trace('[NEWGROUNDS] Encryption Key: ${ENCRYPTION_KEY}'); #end if (!hasValidCredentials()) @@ -46,8 +50,8 @@ class NewgroundsClient } var debug = #if FEATURE_NEWGROUNDS_DEBUG true #else false #end; - NG.create(NewgroundsCredentials.APP_ID, getSessionId(), debug, onLoginResolved); - NG.core.setupEncryption(NewgroundsCredentials.ENCRYPTION_KEY); + NG.create(APP_ID, getSessionId(), debug, onLoginResolved); + NG.core.setupEncryption(ENCRYPTION_KEY); } public function init() @@ -166,12 +170,7 @@ class NewgroundsClient */ static function hasValidCredentials():Bool { - return !(NewgroundsCredentials.APP_ID == null - || NewgroundsCredentials.APP_ID == "" - || NewgroundsCredentials.APP_ID.contains(" ") - || NewgroundsCredentials.ENCRYPTION_KEY == null - || NewgroundsCredentials.ENCRYPTION_KEY == "" - || NewgroundsCredentials.ENCRYPTION_KEY.contains(" ")); + return !(APP_ID == null || APP_ID == "" || APP_ID.contains(" ") || ENCRYPTION_KEY == null || ENCRYPTION_KEY == "" || ENCRYPTION_KEY.contains(" ")); } function onLoginResolved(outcome:LoginOutcome):Void From 139ce3847c6dcb9be7d1a273124decef2c4fe0e1 Mon Sep 17 00:00:00 2001 From: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> Date: Sat, 26 Jul 2025 11:08:39 +0300 Subject: [PATCH 13/89] Fix for env vars that contain `=` as one of thier characters. --- source/funkin/util/macro/EnvironmentConfigMacro.hx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/source/funkin/util/macro/EnvironmentConfigMacro.hx b/source/funkin/util/macro/EnvironmentConfigMacro.hx index 247f3df45..d0a60e687 100644 --- a/source/funkin/util/macro/EnvironmentConfigMacro.hx +++ b/source/funkin/util/macro/EnvironmentConfigMacro.hx @@ -61,11 +61,17 @@ class EnvironmentConfigMacro { if (line == "" || line.startsWith("#")) continue; - var parts = line.split('='); - if (parts.length != 2) continue; + var index:Int = line.indexOf('='); - envFields.push(parts[0]); - envValues.push(parts[1]); + if (index == -1) continue; + + var field:String = line.substr(0, index); + var value:String = line.substr(index + 1); + + if (value == "") continue; + + envFields.push(field); + envValues.push(value); } var newFields = fields.copy(); From cb2015f51a5026ffe5407c4ecc9e17fff619d4c3 Mon Sep 17 00:00:00 2001 From: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> Date: Sat, 26 Jul 2025 11:21:52 +0300 Subject: [PATCH 14/89] Fix a workflow compile error. --- source/funkin/api/newgrounds/NewgroundsClient.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/api/newgrounds/NewgroundsClient.hx b/source/funkin/api/newgrounds/NewgroundsClient.hx index b74802b9b..59b234315 100644 --- a/source/funkin/api/newgrounds/NewgroundsClient.hx +++ b/source/funkin/api/newgrounds/NewgroundsClient.hx @@ -17,8 +17,8 @@ import io.newgrounds.objects.User; @:nullSafety class NewgroundsClient { - static var APP_ID:String = EnvironmentConfigMacro.environmentConfig.get("API_NG_APP_ID"); - static var ENCRYPTION_KEY:String = EnvironmentConfigMacro.environmentConfig.get("API_NG_ENC_KEY"); + static var APP_ID:Null = EnvironmentConfigMacro.environmentConfig.get("API_NG_APP_ID"); + static var ENCRYPTION_KEY:Null = EnvironmentConfigMacro.environmentConfig.get("API_NG_ENC_KEY"); public static var instance(get, never):NewgroundsClient; static var _instance:Null = null; From a89fa9c1211224c7951c0a8d140c4602c7273785 Mon Sep 17 00:00:00 2001 From: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> Date: Sat, 26 Jul 2025 11:49:02 +0300 Subject: [PATCH 15/89] Acually fix it (oops). --- .../funkin/api/newgrounds/NewgroundsClient.hx | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/source/funkin/api/newgrounds/NewgroundsClient.hx b/source/funkin/api/newgrounds/NewgroundsClient.hx index 59b234315..bca5656ed 100644 --- a/source/funkin/api/newgrounds/NewgroundsClient.hx +++ b/source/funkin/api/newgrounds/NewgroundsClient.hx @@ -17,10 +17,11 @@ import io.newgrounds.objects.User; @:nullSafety class NewgroundsClient { - static var APP_ID:Null = EnvironmentConfigMacro.environmentConfig.get("API_NG_APP_ID"); - static var ENCRYPTION_KEY:Null = EnvironmentConfigMacro.environmentConfig.get("API_NG_ENC_KEY"); + static final APP_ID:Null = EnvironmentConfigMacro.environmentConfig?.get("API_NG_APP_ID"); + static final ENCRYPTION_KEY:Null = EnvironmentConfigMacro.environmentConfig?.get("API_NG_ENC_KEY"); public static var instance(get, never):NewgroundsClient; + static var _instance:Null = null; static function get_instance():NewgroundsClient @@ -49,9 +50,12 @@ class NewgroundsClient return; } - var debug = #if FEATURE_NEWGROUNDS_DEBUG true #else false #end; - NG.create(APP_ID, getSessionId(), debug, onLoginResolved); - NG.core.setupEncryption(ENCRYPTION_KEY); + @:nullSafety(Off) + { + NG.create(APP_ID, getSessionId(), #if FEATURE_NEWGROUNDS_DEBUG true #else false #end, onLoginResolved); + + NG.core.setupEncryption(ENCRYPTION_KEY); + } } public function init() @@ -170,7 +174,12 @@ class NewgroundsClient */ static function hasValidCredentials():Bool { - return !(APP_ID == null || APP_ID == "" || APP_ID.contains(" ") || ENCRYPTION_KEY == null || ENCRYPTION_KEY == "" || ENCRYPTION_KEY.contains(" ")); + return !(APP_ID == null + || APP_ID == "" + || (APP_ID != null && APP_ID.contains(" ")) + || ENCRYPTION_KEY == null + || ENCRYPTION_KEY == "" + || (ENCRYPTION_KEY != null && ENCRYPTION_KEY.contains(" "))); } function onLoginResolved(outcome:LoginOutcome):Void From 3aa6a36b5e9d9fea511649e7cbd853ac52789d7b Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 21 Jul 2025 04:13:35 -0400 Subject: [PATCH 16/89] small function renaming --- source/funkin/ui/freeplay/FreeplayState.hx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 11d3288d1..3fcef086a 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1609,12 +1609,10 @@ class FreeplayState extends MusicBeatSubState } if (controls.FREEPLAY_FAVORITE && controls.active) favoriteSong(); - if (controls.FREEPLAY_JUMP_TO_TOP && controls.active) changeSelection(-curSelected); - if (controls.FREEPLAY_JUMP_TO_BOTTOM && controls.active) changeSelection(grpCapsules.countLiving() - curSelected - 1); - calculateCompletion(); + lerpScoreDisplays(); handleInputs(elapsed); @@ -1624,7 +1622,7 @@ class FreeplayState extends MusicBeatSubState if (allowPicoBulletsVibration) HapticUtil.vibrate(0, 0.01, (Constants.MAX_VIBRATION_AMPLITUDE / 3) * 2.5); } - function calculateCompletion():Void + function lerpScoreDisplays():Void { lerpScore = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpScore, intendedScore, FlxG.elapsed, 0.2), intendedScore, 1); lerpCompletion = MathUtil.snap(MathUtil.smoothLerpPrecision(lerpCompletion, intendedCompletion, FlxG.elapsed, 0.5), intendedCompletion, 1 / 100); From e71062b914b9ebfcb97b57708d7c2153ef50a927 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 21 Jul 2025 04:23:32 -0400 Subject: [PATCH 17/89] some lil variable renaming in SongMenuItem.hx --- source/funkin/ui/freeplay/SongMenuItem.hx | 47 ++++++++++------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx index a91394aec..61f966f42 100644 --- a/source/funkin/ui/freeplay/SongMenuItem.hx +++ b/source/funkin/ui/freeplay/SongMenuItem.hx @@ -49,8 +49,6 @@ class SongMenuItem extends FlxSpriteGroup public var fakeRanking:FreeplayRank; - var ranks:Array = ["fail", "average", "great", "excellent", "perfect", "perfectsick"]; - public var targetPos:FlxPoint = new FlxPoint(); public var doLerp:Bool = false; public var doJumpIn:Bool = false; @@ -69,12 +67,9 @@ class SongMenuItem extends FlxSpriteGroup public var newText:FlxSprite; - // public var weekType:FlxSprite; - public var bigNumbers:Array = []; - - public var smallNumbers:Array = []; - - public var weekNumbers:Array = []; + var difficultyNumbers:Array = []; // referred to as "bignumbers" in the .fla file! + var bpmNumbers:Array = []; // referred to as "smallnumbers" in the .fla file! + var weekNumbers:Array = []; var impactThing:FunkinSprite; @@ -131,18 +126,18 @@ class SongMenuItem extends FlxSpriteGroup for (i in 0...2) { - var bigNumber:CapsuleNumber = new CapsuleNumber(466 + (i * 30), 32, true, 0); - add(bigNumber); + var num:CapsuleNumber = new CapsuleNumber(466 + (i * 30), 32, true, 0); + add(num); - bigNumbers.push(bigNumber); + difficultyNumbers.push(num); } for (i in 0...3) { - var smallNumber:CapsuleNumber = new CapsuleNumber(185 + (i * 11), 88.5, false, 0); - add(smallNumber); + var num:CapsuleNumber = new CapsuleNumber(185 + (i * 11), 88.5, false, 0); + add(num); - smallNumbers.push(smallNumber); + bpmNumbers.push(num); } // doesn't get added, simply is here to help with visibility of things for the pop in! @@ -334,38 +329,38 @@ class SongMenuItem extends FlxSpriteGroup shiftX = 186; } - for (i in 0...smallNumbers.length) + for (i in 0...bpmNumbers.length) { - smallNumbers[i].x = this.x + (shiftX + (i * 11)); + bpmNumbers[i].x = this.x + (shiftX + (i * 11)); switch (i) { case 0: if (newBPM < 100) { - smallNumbers[i].digit = 0; + bpmNumbers[i].digit = 0; } else { - smallNumbers[i].digit = Math.floor(newBPM / 100) % 10; + bpmNumbers[i].digit = Math.floor(newBPM / 100) % 10; } case 1: if (newBPM < 10) { - smallNumbers[i].digit = 0; + bpmNumbers[i].digit = 0; } else { - smallNumbers[i].digit = Math.floor(newBPM / 10) % 10; + bpmNumbers[i].digit = Math.floor(newBPM / 10) % 10; if (Math.floor(newBPM / 10) % 10 == 1) tempShift = -4; } case 2: - smallNumbers[i].digit = newBPM % 10; + bpmNumbers[i].digit = newBPM % 10; default: trace('why the fuck is this being called'); } - smallNumbers[i].x += tempShift; + bpmNumbers[i].x += tempShift; } // diffRatingSprite.loadGraphic(Paths.image('freeplay/diffRatings/diff${ratingPadded}')); // diffRatingSprite.visible = false; @@ -439,21 +434,21 @@ class SongMenuItem extends FlxSpriteGroup { var ratingPadded:String = newRating < 10 ? '0$newRating' : '$newRating'; - for (i in 0...bigNumbers.length) + for (i in 0...difficultyNumbers.length) { switch (i) { case 0: if (newRating < 10) { - bigNumbers[i].digit = 0; + difficultyNumbers[i].digit = 0; } else { - bigNumbers[i].digit = Math.floor(newRating / 10); + difficultyNumbers[i].digit = Math.floor(newRating / 10); } case 1: - bigNumbers[i].digit = newRating % 10; + difficultyNumbers[i].digit = newRating % 10; default: trace('why the fuck is this being called'); } From 52f3c6307108f5896f3abb2304a67f36c32efff3 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 21 Jul 2025 04:27:21 -0400 Subject: [PATCH 18/89] mobile fix where the capsules don't properly shift away from the letter sorting crap --- source/funkin/ui/freeplay/FreeplayState.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 3fcef086a..d1b6a261f 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -2679,6 +2679,7 @@ class FreeplayState extends MusicBeatSubState capsule.targetPos.y = capsule.intendedY(index - curSelectedFloat); capsule.targetPos.x = capsule.intendedX(index - curSelectedFloat) + (CUTOUT_WIDTH * SONGS_POS_MULTI); + if (index + 0.5 < curSelectedFloat) capsule.targetPos.y -= 100; } if (curSelected != prevSelected) @@ -2750,8 +2751,7 @@ class FreeplayState extends MusicBeatSubState capsule.targetPos.y = capsule.intendedY(index - curSelected); capsule.targetPos.x = capsule.intendedX(index - curSelected) + (CUTOUT_WIDTH * SONGS_POS_MULTI); - if (index < curSelected #if FEATURE_TOUCH_CONTROLS - && ControlsHandler.usingExternalInputDevice #end) capsule.targetPos.y -= 100; // another 100 for good measure + if (index < curSelected) capsule.targetPos.y -= 100; // another 100 for good measure } if (grpCapsules.countLiving() > 0 && !prepForNewRank && controls.active) From c185f10e65c066516d7d4f9ad0a3a42e553f77f7 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 18 Jul 2025 09:54:10 -0400 Subject: [PATCH 19/89] Move Discord client ID to .env --- source/funkin/api/discord/DiscordClient.hx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/source/funkin/api/discord/DiscordClient.hx b/source/funkin/api/discord/DiscordClient.hx index b439a8f1d..96297cb81 100644 --- a/source/funkin/api/discord/DiscordClient.hx +++ b/source/funkin/api/discord/DiscordClient.hx @@ -11,7 +11,7 @@ import sys.thread.Thread; @:nullSafety class DiscordClient { - static final CLIENT_ID:String = "816168432860790794"; + static final CLIENT_ID:String = EnvironmentConfigMacro.environmentConfig.get("DISCORD_CLIENT_ID"); public static var instance(get, never):DiscordClient; static var _instance:Null = null; @@ -40,12 +40,20 @@ class DiscordClient { trace('[DISCORD] Initializing connection...'); - // Discord.initialize(CLIENT_ID, handlers, true, null); - Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, ""); + if () // Discord.initialize(CLIENT_ID, handlers, true, null); + Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, ""); createDaemon(); } + /** + * @returns `false` if the client ID is invalid. + */ + static function hasValidCredentials():Bool + { + return !(CLIENT_ID == null || CLIENT_ID == "" || CLIENT_ID.contains(" ")); + } + var daemon:Null = null; function createDaemon():Void From a94bd7f36c664fb738c6f1262ed416b9bcc80321 Mon Sep 17 00:00:00 2001 From: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> Date: Sat, 26 Jul 2025 12:11:41 +0300 Subject: [PATCH 20/89] Fix compiling. --- source/funkin/api/discord/DiscordClient.hx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/source/funkin/api/discord/DiscordClient.hx b/source/funkin/api/discord/DiscordClient.hx index 96297cb81..e3d528f7d 100644 --- a/source/funkin/api/discord/DiscordClient.hx +++ b/source/funkin/api/discord/DiscordClient.hx @@ -1,5 +1,6 @@ package funkin.api.discord; +import funkin.util.macro.EnvironmentConfigMacro; #if FEATURE_DISCORD_RPC import hxdiscord_rpc.Discord; import hxdiscord_rpc.Types.DiscordButton; @@ -11,7 +12,7 @@ import sys.thread.Thread; @:nullSafety class DiscordClient { - static final CLIENT_ID:String = EnvironmentConfigMacro.environmentConfig.get("DISCORD_CLIENT_ID"); + static final CLIENT_ID:Null = EnvironmentConfigMacro.environmentConfig?.get("DISCORD_CLIENT_ID"); public static var instance(get, never):DiscordClient; static var _instance:Null = null; @@ -40,8 +41,16 @@ class DiscordClient { trace('[DISCORD] Initializing connection...'); - if () // Discord.initialize(CLIENT_ID, handlers, true, null); + if (!hasValidCredentials()) + { + FlxG.log.warn("Tried to initialize Discord connection, but credentials are invalid!"); + return; + } + + @:nullSafety(Off) + { Discord.Initialize(CLIENT_ID, cpp.RawPointer.addressOf(handlers), 1, ""); + } createDaemon(); } @@ -51,7 +60,7 @@ class DiscordClient */ static function hasValidCredentials():Bool { - return !(CLIENT_ID == null || CLIENT_ID == "" || CLIENT_ID.contains(" ")); + return !(CLIENT_ID == null || CLIENT_ID == "" || (CLIENT_ID != null && CLIENT_ID.contains(" "))); } var daemon:Null = null; From e8d41c938c61b1a86ec888d726166b932c047f93 Mon Sep 17 00:00:00 2001 From: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> Date: Sat, 26 Jul 2025 12:58:34 +0300 Subject: [PATCH 21/89] Use `no-traces` define instead of the flag. --- project.hxp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.hxp b/project.hxp index e41f22086..72a951d23 100644 --- a/project.hxp +++ b/project.hxp @@ -890,7 +890,7 @@ class Project extends HXProject if (FEATURE_LOG_TRACE.isDisabled(this)) { - addHaxeFlag("--no-traces"); + setHaxedef("no-traces"); } // Disable the built in pause screen when unfocusing the game. From 832f01345f441631a5372dbc3382dc8a13c150d6 Mon Sep 17 00:00:00 2001 From: TechnikTil Date: Wed, 30 Apr 2025 11:05:58 -0600 Subject: [PATCH 22/89] Add Save Data Options, also allow user to load and save with Newgrounds Save Slots. --- source/funkin/api/newgrounds/NGSaveSlot.hx | 104 ++++++++++++++++ .../funkin/api/newgrounds/NewgroundsClient.hx | 17 +++ source/funkin/save/Save.hx | 17 +++ source/funkin/ui/options/OptionsState.hx | 34 +----- source/funkin/ui/options/SaveDataMenu.hx | 111 ++++++++++++++++++ 5 files changed, 255 insertions(+), 28 deletions(-) create mode 100644 source/funkin/api/newgrounds/NGSaveSlot.hx create mode 100644 source/funkin/ui/options/SaveDataMenu.hx diff --git a/source/funkin/api/newgrounds/NGSaveSlot.hx b/source/funkin/api/newgrounds/NGSaveSlot.hx new file mode 100644 index 000000000..7740d4715 --- /dev/null +++ b/source/funkin/api/newgrounds/NGSaveSlot.hx @@ -0,0 +1,104 @@ +package funkin.api.newgrounds; + +import io.newgrounds.utils.SaveSlotList; +import io.newgrounds.objects.SaveSlot; +import io.newgrounds.Call.CallError; +import io.newgrounds.objects.events.Outcome; +import funkin.save.Save; + +@:nullSafety +@:access(funkin.save.Save) +class NGSaveSlot +{ + public static var instance(get, never):NGSaveSlot; + static var _instance:Null = null; + + static function get_instance():NGSaveSlot + { + if (_instance == null) + { + return loadInstance(); + } + return _instance; + } + + public static function loadInstance():NGSaveSlot + { + trace("[NEWGROUNDS] Loading save slot..."); + + var loadedSave:NGSaveSlot = loadSlot(Save.BASE_SAVE_SLOT); + if (_instance == null) _instance = loadedSave; + + return loadedSave; + } + + static function loadSlot(slot:Int):NGSaveSlot + { + trace('[NEWGROUNDS] Loading save slot from ID $slot'); + + var saveSlot:Null = NewgroundsClient.instance.saveSlots?.getById(slot); + + if (saveSlot != null && !saveSlot.isEmpty()) + { + // Precache Slots + saveSlot.load(function(outcome:SaveSlotOutcome):Void { + switch (outcome) + { + case SUCCESS(value): + trace('[NEWGROUNDS] Loaded save slot with the ID of ${saveSlot.id}!'); + #if FEATURE_DEBUG_FUNCTIONS + trace('Save Slot Data:'); + trace(value); + #end + case FAIL(error): + trace('[NEWGROUNDS] Failed to load save slot with the ID of ${saveSlot.id}!'); + trace(error); + } + }); + } + + var saveSlotObj:NGSaveSlot = new NGSaveSlot(saveSlot); + return saveSlotObj; + } + + public var ngSaveSlot:Null = null; + + public function new(?ngSaveSlot:Null) + { + this.ngSaveSlot = ngSaveSlot; + + #if FLX_DEBUG + FlxG.console.registerClass(NGSaveSlot); + FlxG.console.registerClass(Save); + #end + } + + /** + * Saves `data` to the newgrounds save slot. + * @param data The raw save data. + */ + public function save(data:RawSaveData):Void + { + var encodedData:String = haxe.Serializer.run(data); + + ngSaveSlot?.save(encodedData, function(outcome:Outcome) { + switch (outcome) + { + case SUCCESS: + trace('[NEWGROUNDS] Successfully saved save data to save slot!'); + case FAIL(error): + trace('[NEWGROUNDS] Failed to save data to save slot!'); + trace(error); + } + }); + } + + public function load():Dynamic + { + var encodedData:Null = ngSaveSlot?.contents; + + if (encodedData == null) return null; + + return haxe.Unserializer.run(encodedData); + } +} diff --git a/source/funkin/api/newgrounds/NewgroundsClient.hx b/source/funkin/api/newgrounds/NewgroundsClient.hx index bca5656ed..2e57bfb85 100644 --- a/source/funkin/api/newgrounds/NewgroundsClient.hx +++ b/source/funkin/api/newgrounds/NewgroundsClient.hx @@ -11,6 +11,7 @@ import io.newgrounds.NGLite.LoginOutcome; import io.newgrounds.NGLite.LoginFail; import io.newgrounds.objects.events.Outcome; import io.newgrounds.utils.MedalList; +import io.newgrounds.utils.SaveSlotList; import io.newgrounds.utils.ScoreBoardList; import io.newgrounds.objects.User; @@ -34,6 +35,7 @@ class NewgroundsClient public var user(get, never):Null; public var medals(get, never):Null; public var leaderboards(get, never):Null; + public var saveSlots(get, never):Null; private function new() { @@ -244,6 +246,8 @@ class NewgroundsClient trace('[NEWGROUNDS] Submitting leaderboard request...'); NG.core.scoreBoards.loadList(onFetchedLeaderboards); + trace('[NEWGROUNDS] Submitting save slot request...'); + NG.core.saveSlots.loadList(onFetchedSaveSlots); } function onLoginFailed(result:LoginFail):Void @@ -309,6 +313,13 @@ class NewgroundsClient // trace(funkin.api.newgrounds.Leaderboards.listLeaderboardData()); } + function onFetchedSaveSlots(outcome:Outcome):Void + { + trace('[NEWGROUNDS] Fetched save slots!'); + + NGSaveSlot.instance.load(); + } + function get_user():Null { if (NG.core == null || !this.isLoggedIn()) return null; @@ -327,6 +338,12 @@ class NewgroundsClient return NG.core.scoreBoards; } + function get_saveSlots():Null + { + if (NG.core == null || !this.isLoggedIn()) return null; + return NG.core.saveSlots; + } + static function getSessionId():Null { #if js diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 1f6c66f3a..5ec8c0475 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -1356,6 +1356,23 @@ class Save { FileUtil.saveFile(haxe.io.Bytes.ofString(this.serialize()), [FileUtil.FILE_FILTER_JSON], null, null, './save.json', 'Write save data as JSON...'); } + public function saveToNewgrounds():Void + { + trace('[SAVE] Saving Save Data to Newgrounds...'); + funkin.api.newgrounds.NGSaveSlot.instance.save(data); + } + + public function loadFromNewgrounds():Void + { + trace('[SAVE] Loading Save Data from Newgrounds...'); + var data = funkin.api.newgrounds.NGSaveSlot.instance.load(); + + if (data == null) return; + + var gameSave = SaveDataMigrator.migrate(data); + FlxG.save.erase(); + FlxG.save.mergeData(gameSave.data, true); + } } /** diff --git a/source/funkin/ui/options/OptionsState.hx b/source/funkin/ui/options/OptionsState.hx index dedbece9e..d22335cf2 100644 --- a/source/funkin/ui/options/OptionsState.hx +++ b/source/funkin/ui/options/OptionsState.hx @@ -75,6 +75,7 @@ class OptionsState extends MusicBeatState #if FEATURE_LAG_ADJUSTMENT var offsets:OffsetMenu = optionsCodex.addPage(Offsets, new OffsetMenu()); #end + var saveData:SaveDataMenu = optionsCodex.addPage(SaveData, new SaveDataMenu()); if (options.hasMultipleOptions()) { @@ -84,6 +85,7 @@ class OptionsState extends MusicBeatState #if FEATURE_LAG_ADJUSTMENT offsets.onExit.add(exitOffsets); #end + saveData.onExit.add(optionsCodex.switchPage.bind(Options)); } else { @@ -227,8 +229,9 @@ class OptionsMenu extends Page }); } #end - createItem("CLEAR SAVE DATA", function() { - promptClearSaveData(); + + createItem("SAVE DATA OPTIONS", function() { + codex.switchPage(SaveData); }); #if NO_FEATURE_TOUCH_CONTROLS createItem("EXIT", exit); @@ -277,7 +280,6 @@ class OptionsMenu extends Page override function update(elapsed:Float):Void { - enabled = (prompt == null); #if FEATURE_TOUCH_CONTROLS backButton.active = (!goingBack) ? !items.busy : true; #end @@ -298,31 +300,6 @@ class OptionsMenu extends Page { return items.length > 2; } - - var prompt:Prompt; - - function promptClearSaveData():Void - { - if (prompt != null) return; - prompt = new Prompt("This will delete - \nALL your save data. - \nAre you sure? - ", Custom("Delete", "Cancel")); - prompt.create(); - prompt.createBgFromMargin(100, 0xFFFAFD6D); - prompt.back.scrollFactor.set(0, 0); - add(prompt); - prompt.onYes = function() { - // Clear the save data. - funkin.save.Save.clearData(); - FlxG.switchState(() -> new funkin.InitState()); - }; - prompt.onNo = function() { - prompt.close(); - prompt.destroy(); - prompt = null; - }; - } } enum abstract OptionsMenuPageName(String) to PageName @@ -333,4 +310,5 @@ enum abstract OptionsMenuPageName(String) to PageName var Mods = "mods"; var Preferences = "preferences"; var Offsets = "offsets"; + var SaveData = "saveData"; } diff --git a/source/funkin/ui/options/SaveDataMenu.hx b/source/funkin/ui/options/SaveDataMenu.hx new file mode 100644 index 000000000..91a5e11ed --- /dev/null +++ b/source/funkin/ui/options/SaveDataMenu.hx @@ -0,0 +1,111 @@ +package funkin.ui.options; + +#if FEATURE_NEWGROUNDS +import funkin.api.newgrounds.NewgroundsClient; +#end +import funkin.ui.Page.PageName; + +/** + * Our default Page when we enter the OptionsState, a bit of the root + */ +class SaveDataMenu extends Page +{ + var items:TextMenuList; + + public function new() + { + super(); + + add(items = new TextMenuList()); + + #if FEATURE_NEWGROUNDS + if (NewgroundsClient.instance.isLoggedIn()) + { + createItem("LOAD FROM NEWGROUNDS", function() { + openConfirmPrompt("This will overwrite + \nALL your save data. + \nAre you sure? + ", "Overwrite", function() { + funkin.save.Save.instance.loadFromNewgrounds(); + + FlxG.switchState(() -> new funkin.InitState()); + }); + }); + + createItem("SAVE TO NEWGROUNDS", function() { + openConfirmPrompt("This will overwrite + \nALL save data saved + \non Newgrounds. + \nAre you sure? + ", "Overwrite", function() { + funkin.save.Save.instance.saveToNewgrounds(); + }); + }); + } + #end + + createItem("CLEAR SAVE DATA", function() { + openConfirmPrompt("This will delete + \nALL your save data. + \nAre you sure? + ", "Delete", function() { + // Clear the save data. + funkin.save.Save.clearData(); + + FlxG.switchState(() -> new funkin.InitState()); + }); + }); + + createItem("EXIT", exit); + } + + function createItem(name:String, callback:Void->Void, fireInstantly = false) + { + var item = items.createItem(0, 100 + items.length * 100, name, BOLD, callback); + item.fireInstantly = fireInstantly; + item.screenCenter(X); + return item; + } + + override function update(elapsed:Float) + { + enabled = (prompt == null); + super.update(elapsed); + } + + override function set_enabled(value:Bool) + { + items.enabled = value; + return super.set_enabled(value); + } + + var prompt:Prompt; + + function openConfirmPrompt(text:String, yesText:String, onYes:Void->Void):Void + { + if (prompt != null) return; + + prompt = new Prompt(text, Custom(yesText, "Cancel")); + prompt.create(); + prompt.createBgFromMargin(100, 0xFFFAFD6D); + prompt.back.scrollFactor.set(0, 0); + add(prompt); + + prompt.onYes = function() { + onYes(); + + if (prompt != null) + { + prompt.close(); + prompt.destroy(); + prompt = null; + } + }; + + prompt.onNo = function() { + prompt.close(); + prompt.destroy(); + prompt = null; + } + } +} From 4a07d0a3474e175c91b889ef6eb398ea86960c01 Mon Sep 17 00:00:00 2001 From: TechnikTil Date: Wed, 30 Apr 2025 13:32:35 -0600 Subject: [PATCH 23/89] make newgrounds functions in Save static and only available if newgrounds feature flag is enabled, make sure base save slot is loaded and is not empty, and recover save data in case newgrounds doesnt wanna play nice --- source/funkin/save/Save.hx | 20 ++++++++++++++++---- source/funkin/ui/options/SaveDataMenu.hx | 8 ++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 5ec8c0475..0783101fb 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -1356,23 +1356,35 @@ class Save { FileUtil.saveFile(haxe.io.Bytes.ofString(this.serialize()), [FileUtil.FILE_FILTER_JSON], null, null, './save.json', 'Write save data as JSON...'); } - public function saveToNewgrounds():Void + #if FEATURE_NEWGROUNDS + public static function saveToNewgrounds():Void { + if (_instance == null) return; trace('[SAVE] Saving Save Data to Newgrounds...'); - funkin.api.newgrounds.NGSaveSlot.instance.save(data); + funkin.api.newgrounds.NGSaveSlot.instance.save(_instance.data); } - public function loadFromNewgrounds():Void + public static function loadFromNewgrounds():Void { trace('[SAVE] Loading Save Data from Newgrounds...'); var data = funkin.api.newgrounds.NGSaveSlot.instance.load(); if (data == null) return; + FlxG.save.bind('$SAVE_NAME${BASE_SAVE_SLOT}', SAVE_PATH); + + if (FlxG.save.status != EMPTY) + { + // best i can do in case NG decides to not play nice + var backupSlot:Int = Save.archiveBadSaveData(FlxG.save.data); + + FlxG.save.erase(); + } + var gameSave = SaveDataMigrator.migrate(data); - FlxG.save.erase(); FlxG.save.mergeData(gameSave.data, true); } + #end } /** diff --git a/source/funkin/ui/options/SaveDataMenu.hx b/source/funkin/ui/options/SaveDataMenu.hx index 91a5e11ed..6575aa4a0 100644 --- a/source/funkin/ui/options/SaveDataMenu.hx +++ b/source/funkin/ui/options/SaveDataMenu.hx @@ -3,7 +3,7 @@ package funkin.ui.options; #if FEATURE_NEWGROUNDS import funkin.api.newgrounds.NewgroundsClient; #end -import funkin.ui.Page.PageName; +import funkin.save.Save; /** * Our default Page when we enter the OptionsState, a bit of the root @@ -26,7 +26,7 @@ class SaveDataMenu extends Page \nALL your save data. \nAre you sure? ", "Overwrite", function() { - funkin.save.Save.instance.loadFromNewgrounds(); + Save.loadFromNewgrounds(); FlxG.switchState(() -> new funkin.InitState()); }); @@ -38,7 +38,7 @@ class SaveDataMenu extends Page \non Newgrounds. \nAre you sure? ", "Overwrite", function() { - funkin.save.Save.instance.saveToNewgrounds(); + Save.saveToNewgrounds(); }); }); } @@ -50,7 +50,7 @@ class SaveDataMenu extends Page \nAre you sure? ", "Delete", function() { // Clear the save data. - funkin.save.Save.clearData(); + Save.clearData(); FlxG.switchState(() -> new funkin.InitState()); }); From d661d9318483a40ff8c13e6b049d6454a1184c26 Mon Sep 17 00:00:00 2001 From: TechnikTil Date: Wed, 30 Apr 2025 14:55:39 -0600 Subject: [PATCH 24/89] rewrite NGSaveSlot.load to be async, dont precache assets, error handling --- source/funkin/api/newgrounds/NGSaveSlot.hx | 100 +++++++++++------- .../funkin/api/newgrounds/NewgroundsClient.hx | 2 +- source/funkin/save/Save.hx | 30 +++--- 3 files changed, 83 insertions(+), 49 deletions(-) diff --git a/source/funkin/api/newgrounds/NGSaveSlot.hx b/source/funkin/api/newgrounds/NGSaveSlot.hx index 7740d4715..8f39be482 100644 --- a/source/funkin/api/newgrounds/NGSaveSlot.hx +++ b/source/funkin/api/newgrounds/NGSaveSlot.hx @@ -24,8 +24,6 @@ class NGSaveSlot public static function loadInstance():NGSaveSlot { - trace("[NEWGROUNDS] Loading save slot..."); - var loadedSave:NGSaveSlot = loadSlot(Save.BASE_SAVE_SLOT); if (_instance == null) _instance = loadedSave; @@ -34,29 +32,10 @@ class NGSaveSlot static function loadSlot(slot:Int):NGSaveSlot { - trace('[NEWGROUNDS] Loading save slot from ID $slot'); + trace('[NEWGROUNDS] Getting save slot from ID $slot'); var saveSlot:Null = NewgroundsClient.instance.saveSlots?.getById(slot); - if (saveSlot != null && !saveSlot.isEmpty()) - { - // Precache Slots - saveSlot.load(function(outcome:SaveSlotOutcome):Void { - switch (outcome) - { - case SUCCESS(value): - trace('[NEWGROUNDS] Loaded save slot with the ID of ${saveSlot.id}!'); - #if FEATURE_DEBUG_FUNCTIONS - trace('Save Slot Data:'); - trace(value); - #end - case FAIL(error): - trace('[NEWGROUNDS] Failed to load save slot with the ID of ${saveSlot.id}!'); - trace(error); - } - }); - } - var saveSlotObj:NGSaveSlot = new NGSaveSlot(saveSlot); return saveSlotObj; } @@ -81,24 +60,73 @@ class NGSaveSlot { var encodedData:String = haxe.Serializer.run(data); - ngSaveSlot?.save(encodedData, function(outcome:Outcome) { - switch (outcome) - { - case SUCCESS: - trace('[NEWGROUNDS] Successfully saved save data to save slot!'); - case FAIL(error): - trace('[NEWGROUNDS] Failed to save data to save slot!'); - trace(error); - } - }); + try + { + ngSaveSlot?.save(encodedData, function(outcome:Outcome) { + switch (outcome) + { + case SUCCESS: + trace('[NEWGROUNDS] Successfully saved save data to save slot!'); + case FAIL(error): + trace('[NEWGROUNDS] Failed to save data to save slot!'); + trace(error); + } + }); + } + catch (error:String) + { + trace('[NEWGROUNDS] Failed to save data to save slot!'); + trace(error); + } } - public function load():Dynamic + public function load(?onComplete:NullVoid>, ?onError:NullVoid>):Void { - var encodedData:Null = ngSaveSlot?.contents; + try + { + ngSaveSlot?.load(function(outcome:SaveSlotOutcome):Void { + switch (outcome) + { + case SUCCESS(value): + trace('[NEWGROUNDS] Loaded save slot with the ID of ${ngSaveSlot?.id}!'); + #if FEATURE_DEBUG_FUNCTIONS + trace('Save Slot Data:'); + trace(value); + #end - if (encodedData == null) return null; + if (onComplete != null && value != null) + { + var decodedData:Dynamic = haxe.Unserializer.run(value); + onComplete(decodedData); + } + case FAIL(error): + trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!'); + trace(error); - return haxe.Unserializer.run(encodedData); + if (onError != null) + { + onError(error); + } + } + }); + } + catch (error:String) + { + trace('[NEWGROUNDS] Failed to load save slot with the ID of ${ngSaveSlot?.id}!'); + trace(error); + + if (onError != null) + { + onError(RESPONSE({message: error, code: 500})); + } + } + } + + public function checkSlot():Void + { + trace('[NEWGROUNDS] Checking save slot with the ID of ${ngSaveSlot?.id}...'); + + trace(' Is null? ${ngSaveSlot != null}'); + trace(' Is empty? ${ngSaveSlot?.isEmpty() ?? false}'); } } diff --git a/source/funkin/api/newgrounds/NewgroundsClient.hx b/source/funkin/api/newgrounds/NewgroundsClient.hx index 2e57bfb85..60491a652 100644 --- a/source/funkin/api/newgrounds/NewgroundsClient.hx +++ b/source/funkin/api/newgrounds/NewgroundsClient.hx @@ -317,7 +317,7 @@ class NewgroundsClient { trace('[NEWGROUNDS] Fetched save slots!'); - NGSaveSlot.instance.load(); + NGSaveSlot.instance.checkSlot(); } function get_user():Null diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 0783101fb..a04cac82c 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -1356,6 +1356,7 @@ class Save { FileUtil.saveFile(haxe.io.Bytes.ofString(this.serialize()), [FileUtil.FILE_FILTER_JSON], null, null, './save.json', 'Write save data as JSON...'); } + #if FEATURE_NEWGROUNDS public static function saveToNewgrounds():Void { @@ -1367,22 +1368,27 @@ class Save public static function loadFromNewgrounds():Void { trace('[SAVE] Loading Save Data from Newgrounds...'); - var data = funkin.api.newgrounds.NGSaveSlot.instance.load(); + funkin.api.newgrounds.NGSaveSlot.instance.load(function(data:Dynamic) { + FlxG.save.bind('$SAVE_NAME${BASE_SAVE_SLOT}', SAVE_PATH); - if (data == null) return; + if (FlxG.save.status != EMPTY) + { + // best i can do in case the NG file is corrupted or something along those lines + var backupSlot:Int = Save.archiveBadSaveData(FlxG.save.data); - FlxG.save.bind('$SAVE_NAME${BASE_SAVE_SLOT}', SAVE_PATH); + FlxG.save.erase(); + } - if (FlxG.save.status != EMPTY) - { - // best i can do in case NG decides to not play nice - var backupSlot:Int = Save.archiveBadSaveData(FlxG.save.data); + var gameSave = SaveDataMigrator.migrate(data); + FlxG.save.mergeData(gameSave.data, true); + }, function(error:io.newgrounds.Call.CallError) { + var errorMsg:String = io.newgrounds.Call.CallErrorTools.toString(error); - FlxG.save.erase(); - } - - var gameSave = SaveDataMigrator.migrate(data); - FlxG.save.mergeData(gameSave.data, true); + var msg = 'There was an error loading your save data from Newgrounds.'; + msg += '\n${errorMsg}'; + msg += '\nAre you sure you are connected to the internet?'; + lime.app.Application.current.window.alert(msg, "Newgrounds Save Slot Failure"); + }); } #end } From eb1986b246402bdecb655b2bee70e19cd0504bd3 Mon Sep 17 00:00:00 2001 From: TechnikTil Date: Wed, 30 Apr 2025 19:50:33 -0600 Subject: [PATCH 25/89] move clear save data to the top, add clear ng save data --- source/funkin/api/newgrounds/NGSaveSlot.hx | 22 ++++++++ source/funkin/save/Save.hx | 1 + source/funkin/ui/options/SaveDataMenu.hx | 61 ++++++++++++---------- 3 files changed, 55 insertions(+), 29 deletions(-) diff --git a/source/funkin/api/newgrounds/NGSaveSlot.hx b/source/funkin/api/newgrounds/NGSaveSlot.hx index 8f39be482..4b941e0d6 100644 --- a/source/funkin/api/newgrounds/NGSaveSlot.hx +++ b/source/funkin/api/newgrounds/NGSaveSlot.hx @@ -122,6 +122,28 @@ class NGSaveSlot } } + public function clear():Void + { + try + { + ngSaveSlot?.clear(function(outcome:Outcome) { + switch (outcome) + { + case SUCCESS: + trace('[NEWGROUNDS] Successfully cleared save slot!'); + case FAIL(error): + trace('[NEWGROUNDS] Failed to clear save slot!'); + trace(error); + } + }); + } + catch (error:String) + { + trace('[NEWGROUNDS] Failed to clear save slot!'); + trace(error); + } + } + public function checkSlot():Void { trace('[NEWGROUNDS] Checking save slot with the ID of ${ngSaveSlot?.id}...'); diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index a04cac82c..5586fed08 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -1381,6 +1381,7 @@ class Save var gameSave = SaveDataMigrator.migrate(data); FlxG.save.mergeData(gameSave.data, true); + FlxG.save.flush(); }, function(error:io.newgrounds.Call.CallError) { var errorMsg:String = io.newgrounds.Call.CallErrorTools.toString(error); diff --git a/source/funkin/ui/options/SaveDataMenu.hx b/source/funkin/ui/options/SaveDataMenu.hx index 6575aa4a0..f81a59e74 100644 --- a/source/funkin/ui/options/SaveDataMenu.hx +++ b/source/funkin/ui/options/SaveDataMenu.hx @@ -5,9 +5,6 @@ import funkin.api.newgrounds.NewgroundsClient; #end import funkin.save.Save; -/** - * Our default Page when we enter the OptionsState, a bit of the root - */ class SaveDataMenu extends Page { var items:TextMenuList; @@ -18,32 +15,6 @@ class SaveDataMenu extends Page add(items = new TextMenuList()); - #if FEATURE_NEWGROUNDS - if (NewgroundsClient.instance.isLoggedIn()) - { - createItem("LOAD FROM NEWGROUNDS", function() { - openConfirmPrompt("This will overwrite - \nALL your save data. - \nAre you sure? - ", "Overwrite", function() { - Save.loadFromNewgrounds(); - - FlxG.switchState(() -> new funkin.InitState()); - }); - }); - - createItem("SAVE TO NEWGROUNDS", function() { - openConfirmPrompt("This will overwrite - \nALL save data saved - \non Newgrounds. - \nAre you sure? - ", "Overwrite", function() { - Save.saveToNewgrounds(); - }); - }); - } - #end - createItem("CLEAR SAVE DATA", function() { openConfirmPrompt("This will delete \nALL your save data. @@ -56,6 +27,38 @@ class SaveDataMenu extends Page }); }); + #if FEATURE_NEWGROUNDS + if (NewgroundsClient.instance.isLoggedIn()) + { + createItem("LOAD FROM NG", function() { + openConfirmPrompt("This will overwrite + \nALL your save data. + \nAre you sure? + ", "Overwrite", function() { + Save.loadFromNewgrounds(); + + FlxG.switchState(() -> new funkin.InitState()); + }); + }); + + createItem("SAVE TO NG", function() { + openConfirmPrompt("This will overwrite + \nALL save data saved + \non NG. Are you sure?", "Overwrite", function() { + Save.saveToNewgrounds(); + }); + }); + + createItem("CLEAR NG SAVE DATA", function() { + openConfirmPrompt("This will delete + \nALL save data saved + \non NG. Are you sure?", "Delete", function() { + funkin.api.newgrounds.NGSaveSlot.instance.clear(); + }); + }); + } + #end + createItem("EXIT", exit); } From dbc26fd6213435514acfde3b89e358c34271443e Mon Sep 17 00:00:00 2001 From: TechnikTil Date: Fri, 2 May 2025 15:57:09 -0600 Subject: [PATCH 26/89] hopefully fix everything with load from ng --- source/funkin/save/Save.hx | 9 +++++---- source/funkin/ui/options/SaveDataMenu.hx | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 5586fed08..85af44b0f 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -1365,7 +1365,7 @@ class Save funkin.api.newgrounds.NGSaveSlot.instance.save(_instance.data); } - public static function loadFromNewgrounds():Void + public static function loadFromNewgrounds(onFinish:Void->Void):Void { trace('[SAVE] Loading Save Data from Newgrounds...'); funkin.api.newgrounds.NGSaveSlot.instance.load(function(data:Dynamic) { @@ -1375,13 +1375,14 @@ class Save { // best i can do in case the NG file is corrupted or something along those lines var backupSlot:Int = Save.archiveBadSaveData(FlxG.save.data); - - FlxG.save.erase(); + trace('[SAVE] Backed up current save data in case of emergency to $backupSlot!'); } var gameSave = SaveDataMigrator.migrate(data); - FlxG.save.mergeData(gameSave.data, true); + @:privateAccess + FlxG.save.data = gameSave.data; FlxG.save.flush(); + onFinish(); }, function(error:io.newgrounds.Call.CallError) { var errorMsg:String = io.newgrounds.Call.CallErrorTools.toString(error); diff --git a/source/funkin/ui/options/SaveDataMenu.hx b/source/funkin/ui/options/SaveDataMenu.hx index f81a59e74..2b63254ae 100644 --- a/source/funkin/ui/options/SaveDataMenu.hx +++ b/source/funkin/ui/options/SaveDataMenu.hx @@ -35,9 +35,9 @@ class SaveDataMenu extends Page \nALL your save data. \nAre you sure? ", "Overwrite", function() { - Save.loadFromNewgrounds(); - - FlxG.switchState(() -> new funkin.InitState()); + Save.loadFromNewgrounds(function() { + FlxG.switchState(() -> new funkin.InitState()); + }); }); }); From 19c5de8fe21577704491008037f897adef76968b Mon Sep 17 00:00:00 2001 From: TechnikTil Date: Fri, 2 May 2025 21:50:13 -0600 Subject: [PATCH 27/89] HOPEFULLY FIX EVERYTHING WRONG (this time it was tested between two computers) --- source/funkin/api/newgrounds/NGSaveSlot.hx | 2 +- source/funkin/save/Save.hx | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/source/funkin/api/newgrounds/NGSaveSlot.hx b/source/funkin/api/newgrounds/NGSaveSlot.hx index 4b941e0d6..31fb8d0a0 100644 --- a/source/funkin/api/newgrounds/NGSaveSlot.hx +++ b/source/funkin/api/newgrounds/NGSaveSlot.hx @@ -148,7 +148,7 @@ class NGSaveSlot { trace('[NEWGROUNDS] Checking save slot with the ID of ${ngSaveSlot?.id}...'); - trace(' Is null? ${ngSaveSlot != null}'); + trace(' Is null? ${ngSaveSlot == null}'); trace(' Is empty? ${ngSaveSlot?.isEmpty() ?? false}'); } } diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 85af44b0f..0ca56c18e 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -1378,10 +1378,12 @@ class Save trace('[SAVE] Backed up current save data in case of emergency to $backupSlot!'); } + FlxG.save.erase(); + FlxG.save.bind('$SAVE_NAME${BASE_SAVE_SLOT}', SAVE_PATH); // forces regeneration of the file as erase deletes it + var gameSave = SaveDataMigrator.migrate(data); - @:privateAccess - FlxG.save.data = gameSave.data; - FlxG.save.flush(); + FlxG.save.mergeData(gameSave.data, true); + _instance = gameSave; onFinish(); }, function(error:io.newgrounds.Call.CallError) { var errorMsg:String = io.newgrounds.Call.CallErrorTools.toString(error); From 054a5cd00b9d22eae350b7af89bb8d353ee98818 Mon Sep 17 00:00:00 2001 From: TechnikTil Date: Fri, 2 May 2025 23:02:22 -0600 Subject: [PATCH 28/89] fix fullscreen bug and show clear save data if logged out of ng --- source/funkin/ui/options/OptionsState.hx | 34 +++++++++++++++++----- source/funkin/ui/options/SaveDataMenu.hx | 37 +++++++++++++++--------- source/funkin/util/WindowUtil.hx | 7 +++++ 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/source/funkin/ui/options/OptionsState.hx b/source/funkin/ui/options/OptionsState.hx index d22335cf2..cc57ec1db 100644 --- a/source/funkin/ui/options/OptionsState.hx +++ b/source/funkin/ui/options/OptionsState.hx @@ -69,13 +69,13 @@ class OptionsState extends MusicBeatState optionsCodex = new Codex(Options); add(optionsCodex); - var options:OptionsMenu = optionsCodex.addPage(Options, new OptionsMenu()); + var saveData:SaveDataMenu = optionsCodex.addPage(SaveData, new SaveDataMenu()); + var options:OptionsMenu = optionsCodex.addPage(Options, new OptionsMenu(saveData)); var preferences:PreferencesMenu = optionsCodex.addPage(Preferences, new PreferencesMenu()); var controls:ControlsMenu = optionsCodex.addPage(Controls, new ControlsMenu()); #if FEATURE_LAG_ADJUSTMENT var offsets:OffsetMenu = optionsCodex.addPage(Offsets, new OffsetMenu()); #end - var saveData:SaveDataMenu = optionsCodex.addPage(SaveData, new SaveDataMenu()); if (options.hasMultipleOptions()) { @@ -161,7 +161,19 @@ class OptionsMenu extends Page final CAMERA_MARGIN:Int = 150; - public function new() + #if FEATURE_TOUCH_CONTROLS + var backButton:FunkinBackButton; + var goingBack:Bool = false; + #end + + /** + * Camera focus point + */ + var camFocusPoint:FlxObject; + + final CAMERA_MARGIN:Int = 150; + + public function new(saveDataMenu:SaveDataMenu) { super(); add(items = new TextMenuList()); @@ -230,10 +242,18 @@ class OptionsMenu extends Page } #end - createItem("SAVE DATA OPTIONS", function() { - codex.switchPage(SaveData); - }); - #if NO_FEATURE_TOUCH_CONTROLS + // no need to show an entire new menu for just one option + if (saveDataMenu.hasMultipleOptions()) + { + createItem("SAVE DATA OPTIONS", function() { + codex.switchPage(SaveData); + }); + } + else + { + createItem("CLEAR SAVE DATA", saveDataMenu.openSaveDataPrompt); + } + createItem("EXIT", exit); #else backButton = new FunkinBackButton(FlxG.width - 230, FlxG.height - 200, exit, 1.0); diff --git a/source/funkin/ui/options/SaveDataMenu.hx b/source/funkin/ui/options/SaveDataMenu.hx index 2b63254ae..e5f3d34ca 100644 --- a/source/funkin/ui/options/SaveDataMenu.hx +++ b/source/funkin/ui/options/SaveDataMenu.hx @@ -15,17 +15,7 @@ class SaveDataMenu extends Page add(items = new TextMenuList()); - createItem("CLEAR SAVE DATA", function() { - openConfirmPrompt("This will delete - \nALL your save data. - \nAre you sure? - ", "Delete", function() { - // Clear the save data. - Save.clearData(); - - FlxG.switchState(() -> new funkin.InitState()); - }); - }); + createItem("CLEAR SAVE DATA", openSaveDataPrompt); #if FEATURE_NEWGROUNDS if (NewgroundsClient.instance.isLoggedIn()) @@ -84,7 +74,7 @@ class SaveDataMenu extends Page var prompt:Prompt; - function openConfirmPrompt(text:String, yesText:String, onYes:Void->Void):Void + function openConfirmPrompt(text:String, yesText:String, onYes:Void->Void, ?groupToOpenOn:Null):Void { if (prompt != null) return; @@ -92,7 +82,7 @@ class SaveDataMenu extends Page prompt.create(); prompt.createBgFromMargin(100, 0xFFFAFD6D); prompt.back.scrollFactor.set(0, 0); - add(prompt); + FlxG.state.add(prompt); prompt.onYes = function() { onYes(); @@ -111,4 +101,25 @@ class SaveDataMenu extends Page prompt = null; } } + public function openSaveDataPrompt() + { + openConfirmPrompt("This will delete + \nALL your save data. + \nAre you sure? + ", "Delete", function() { + // Clear the save data. + Save.clearData(); + + FlxG.switchState(() -> new funkin.InitState()); + }); + } + + /** + * True if this page has multiple options, excluding the exit option. + * If false, there's no reason to ever show this page. + */ + public function hasMultipleOptions():Bool + { + return items.length > 2; + } } diff --git a/source/funkin/util/WindowUtil.hx b/source/funkin/util/WindowUtil.hx index 85734c6fc..b7b60d040 100644 --- a/source/funkin/util/WindowUtil.hx +++ b/source/funkin/util/WindowUtil.hx @@ -102,12 +102,19 @@ class WindowUtil */ public static final windowExit:FlxTypedSignalVoid> = new FlxTypedSignalVoid>(); + /** + * Has `initWindowEvents()` been called already? + * This is to prevent multiple instances of the same function. + */ + private static var _initializedWindowEvents:Bool = false; + /** * Wires up FlxSignals that happen based on window activity. * For example, we can run a callback when the window is closed. */ public static function initWindowEvents():Void { + if (_initializedWindowEvents) return; // Fix that annoying // onUpdate is called every frame just before rendering. // onExit is called when the game window is closed. From c527fed70c080e6442e79908689be0cefe9f6a42 Mon Sep 17 00:00:00 2001 From: Abnormal <86753001+AbnormalPoof@users.noreply.github.com> Date: Tue, 22 Jul 2025 08:24:23 -0500 Subject: [PATCH 29/89] conflict stuffs lol --- source/funkin/ui/options/OptionsState.hx | 12 ------------ source/funkin/util/WindowUtil.hx | 1 + 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/source/funkin/ui/options/OptionsState.hx b/source/funkin/ui/options/OptionsState.hx index cc57ec1db..528a3b7b8 100644 --- a/source/funkin/ui/options/OptionsState.hx +++ b/source/funkin/ui/options/OptionsState.hx @@ -161,18 +161,6 @@ class OptionsMenu extends Page final CAMERA_MARGIN:Int = 150; - #if FEATURE_TOUCH_CONTROLS - var backButton:FunkinBackButton; - var goingBack:Bool = false; - #end - - /** - * Camera focus point - */ - var camFocusPoint:FlxObject; - - final CAMERA_MARGIN:Int = 150; - public function new(saveDataMenu:SaveDataMenu) { super(); diff --git a/source/funkin/util/WindowUtil.hx b/source/funkin/util/WindowUtil.hx index b7b60d040..2ee34ee80 100644 --- a/source/funkin/util/WindowUtil.hx +++ b/source/funkin/util/WindowUtil.hx @@ -144,6 +144,7 @@ class WindowUtil } }); #end + _initializedWindowEvents = true; } /** From 70d5730b706b9ac494855d02d34bf129d3819b2a Mon Sep 17 00:00:00 2001 From: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> Date: Sun, 27 Jul 2025 08:43:07 +0300 Subject: [PATCH 30/89] Fix compile. --- source/funkin/ui/options/OptionsState.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/ui/options/OptionsState.hx b/source/funkin/ui/options/OptionsState.hx index 528a3b7b8..82aa62fe5 100644 --- a/source/funkin/ui/options/OptionsState.hx +++ b/source/funkin/ui/options/OptionsState.hx @@ -242,6 +242,7 @@ class OptionsMenu extends Page createItem("CLEAR SAVE DATA", saveDataMenu.openSaveDataPrompt); } + #if NO_FEATURE_TOUCH_CONTROLS createItem("EXIT", exit); #else backButton = new FunkinBackButton(FlxG.width - 230, FlxG.height - 200, exit, 1.0); From 4ac851c38746702dce8c48081b450b7cd3c1cc01 Mon Sep 17 00:00:00 2001 From: Karim Akra Date: Sun, 27 Jul 2025 23:44:42 +0300 Subject: [PATCH 31/89] add support for target specific environment values and renamed `GLOBAL_ADMOB_PUBLISHER` to `MOBILE_GLOBAL_ADMOB_PUBLISHER` !! --- source/funkin/mobile/util/AdMobUtil.hx | 2 +- .../util/macro/EnvironmentConfigMacro.hx | 25 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/source/funkin/mobile/util/AdMobUtil.hx b/source/funkin/mobile/util/AdMobUtil.hx index 6bce88c4b..47a4a8a32 100644 --- a/source/funkin/mobile/util/AdMobUtil.hx +++ b/source/funkin/mobile/util/AdMobUtil.hx @@ -29,7 +29,7 @@ class AdMobUtil /** * AdMob publisher ID used for the application. */ - static final ADMOB_PUBLISHER:String = EnvironmentConfigMacro.environmentConfig.get("GLOBAL_ADMOB_PUBLISHER"); + static final ADMOB_PUBLISHER:String = EnvironmentConfigMacro.environmentConfig.get("MOBILE_GLOBAL_ADMOB_PUBLISHER"); /** * Test ad unit IDs for development and testing purposes. diff --git a/source/funkin/util/macro/EnvironmentConfigMacro.hx b/source/funkin/util/macro/EnvironmentConfigMacro.hx index d0a60e687..130237ee3 100644 --- a/source/funkin/util/macro/EnvironmentConfigMacro.hx +++ b/source/funkin/util/macro/EnvironmentConfigMacro.hx @@ -59,7 +59,7 @@ class EnvironmentConfigMacro for (line in envFile.split('\n')) { - if (line == "" || line.startsWith("#")) continue; + if (line.length <= 0 || line.startsWith("#") || shouldExcludeKey(line)) continue; var index:Int = line.indexOf('='); @@ -70,6 +70,8 @@ class EnvironmentConfigMacro if (value == "") continue; + Sys.println('[ENV] Found a key for environment value $field!'); + envFields.push(field); envValues.push(value); } @@ -106,6 +108,27 @@ class EnvironmentConfigMacro return newFields; } + + private static function shouldExcludeKey(key:String):Bool + { + final android:Bool = key.startsWith('ANDROID_'); + final ios:Bool = key.startsWith('IOS_'); + final mobile:Bool = key.startsWith('MOBILE_') || ios || android; + final web:Bool = key.startsWith('WEB_'); + final desktop:Bool = key.startsWith('DESKTOP_'); + + #if html5 + if (mobile || desktop) return true; + #elseif desktop + if (mobile || web) return true; + #elseif android + if (mobile || web || desktop) return true; + #elseif ios + if (android || web || desktop) return true; + #end + + return false; + } #end } From 65a3ccc8fe15ae6a9df84bc16275d5ea02c17904 Mon Sep 17 00:00:00 2001 From: Karim Akra Date: Sun, 27 Jul 2025 23:50:20 +0300 Subject: [PATCH 32/89] add the same changes to the .env parser on project.hxp !! --- project.hxp | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/project.hxp b/project.hxp index 72a951d23..991c77d97 100644 --- a/project.hxp +++ b/project.hxp @@ -1740,17 +1740,42 @@ class Project extends HXProject var env = new Map(); for (line in envFile.split('\n')) { - if (line == "" || line.startsWith("#")) continue; + if (line.length <= 0 || line.startsWith("#") || shouldExcludeKey(line)) continue; - var parts = line.split('='); - if (parts.length != 2) continue; + var index:Int = line.indexOf('='); - env.set(parts[0], parts[1]); + if (index == -1) continue; + + var field:String = line.substr(0, index); + var value:String = line.substr(index + 1); + + env.set(field, value); } return env; } + private static function shouldExcludeEnvKey(key:String):Bool + { + final android:Bool = key.startsWith('ANDROID_'); + final ios:Bool = key.startsWith('IOS_'); + final mobile:Bool = key.startsWith('MOBILE_') || ios || android; + final web:Bool = key.startsWith('WEB_'); + final desktop:Bool = key.startsWith('DESKTOP_'); + + #if html5 + if (mobile || desktop) return true; + #elseif desktop + if (mobile || web) return true; + #elseif android + if (mobile || web || desktop) return true; + #elseif ios + if (android || web || desktop) return true; + #end + + return false; + } + public function readASTCExclusion():Void { astcExcludes = File.getContent('./compression-excludes.txt').trim().split('\n'); From 79478080f68dbdd15adda4b915b102e306b192a9 Mon Sep 17 00:00:00 2001 From: Karim Akra Date: Mon, 28 Jul 2025 00:19:09 +0300 Subject: [PATCH 33/89] oops --- project.hxp | 13 ++++--------- source/funkin/util/macro/EnvironmentConfigMacro.hx | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/project.hxp b/project.hxp index 991c77d97..411da07b6 100644 --- a/project.hxp +++ b/project.hxp @@ -1763,15 +1763,10 @@ class Project extends HXProject final web:Bool = key.startsWith('WEB_'); final desktop:Bool = key.startsWith('DESKTOP_'); - #if html5 - if (mobile || desktop) return true; - #elseif desktop - if (mobile || web) return true; - #elseif android - if (mobile || web || desktop) return true; - #elseif ios - if (android || web || desktop) return true; - #end + if (isWeb() && (mobile || desktop)) return true; + if (isDesktop() && (mobile || web)) return true; + if (isAndroid() && (ios || web || desktop)) return true; + if (isIOS() && (android || web || desktop)) return true; return false; } diff --git a/source/funkin/util/macro/EnvironmentConfigMacro.hx b/source/funkin/util/macro/EnvironmentConfigMacro.hx index 130237ee3..ecf4516e2 100644 --- a/source/funkin/util/macro/EnvironmentConfigMacro.hx +++ b/source/funkin/util/macro/EnvironmentConfigMacro.hx @@ -122,7 +122,7 @@ class EnvironmentConfigMacro #elseif desktop if (mobile || web) return true; #elseif android - if (mobile || web || desktop) return true; + if (ios || web || desktop) return true; #elseif ios if (android || web || desktop) return true; #end From 34bae4a431906c56cd5a59050cea1bdd87154eaf Mon Sep 17 00:00:00 2001 From: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:22:49 +0300 Subject: [PATCH 34/89] Fix compile once again. --- project.hxp | 4 ++-- source/funkin/api/discord/DiscordClient.hx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project.hxp b/project.hxp index 411da07b6..ca2fd453e 100644 --- a/project.hxp +++ b/project.hxp @@ -1740,7 +1740,7 @@ class Project extends HXProject var env = new Map(); for (line in envFile.split('\n')) { - if (line.length <= 0 || line.startsWith("#") || shouldExcludeKey(line)) continue; + if (line.length <= 0 || line.startsWith("#") || shouldExcludeEnvKey(line)) continue; var index:Int = line.indexOf('='); @@ -1755,7 +1755,7 @@ class Project extends HXProject return env; } - private static function shouldExcludeEnvKey(key:String):Bool + private function shouldExcludeEnvKey(key:String):Bool { final android:Bool = key.startsWith('ANDROID_'); final ios:Bool = key.startsWith('IOS_'); diff --git a/source/funkin/api/discord/DiscordClient.hx b/source/funkin/api/discord/DiscordClient.hx index e3d528f7d..7df9d1b33 100644 --- a/source/funkin/api/discord/DiscordClient.hx +++ b/source/funkin/api/discord/DiscordClient.hx @@ -12,7 +12,7 @@ import sys.thread.Thread; @:nullSafety class DiscordClient { - static final CLIENT_ID:Null = EnvironmentConfigMacro.environmentConfig?.get("DISCORD_CLIENT_ID"); + static final CLIENT_ID:Null = EnvironmentConfigMacro.environmentConfig?.get("DESKTOP_DISCORD_CLIENT_ID"); public static var instance(get, never):DiscordClient; static var _instance:Null = null; From 962519e7f0e961060dbe2dd1ec7e8e344c3bcb5e Mon Sep 17 00:00:00 2001 From: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:33:07 +0300 Subject: [PATCH 35/89] Fix android compile. --- source/funkin/api/newgrounds/NGSaveSlot.hx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/funkin/api/newgrounds/NGSaveSlot.hx b/source/funkin/api/newgrounds/NGSaveSlot.hx index 31fb8d0a0..72d70ac0d 100644 --- a/source/funkin/api/newgrounds/NGSaveSlot.hx +++ b/source/funkin/api/newgrounds/NGSaveSlot.hx @@ -1,5 +1,6 @@ package funkin.api.newgrounds; +#if FEATURE_NEWGROUNDS import io.newgrounds.utils.SaveSlotList; import io.newgrounds.objects.SaveSlot; import io.newgrounds.Call.CallError; @@ -152,3 +153,4 @@ class NGSaveSlot trace(' Is empty? ${ngSaveSlot?.isEmpty() ?? false}'); } } +#end From 4f9b63f22c84ea0ce1279a927d200f7e32df2c3b Mon Sep 17 00:00:00 2001 From: Karim Akra <144803230+KarimAkra@users.noreply.github.com> Date: Thu, 31 Jul 2025 21:17:29 +0300 Subject: [PATCH 36/89] Configure the android adaptive icons through lime & moved the icons stuff to art submodule Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com> --- hmm.json | 2 +- project.hxp | 25 +-- .../res/drawable/ic_launcher_background.xml | 170 ------------------ .../res/drawable/ic_launcher_foreground.xml | 30 ---- .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 - .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 6032 -> 0 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 11396 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 8051 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 3480 -> 0 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 6769 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 4612 -> 0 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 9057 -> 0 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 16119 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 11877 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 14799 -> 0 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 27541 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 19695 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 21103 -> 0 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 40405 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 28441 -> 0 bytes .../app/src/main/res/values/colors.xml | 10 -- .../res/values/ic_launcher_background.xml | 4 - 23 files changed, 4 insertions(+), 247 deletions(-) delete mode 100644 templates/android/template/app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 templates/android/template/app/src/main/res/drawable/ic_launcher_foreground.xml delete mode 100644 templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png delete mode 100644 templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 templates/android/template/app/src/main/res/values/colors.xml delete mode 100644 templates/android/template/app/src/main/res/values/ic_launcher_background.xml diff --git a/hmm.json b/hmm.json index 2f833c0d2..82e458768 100644 --- a/hmm.json +++ b/hmm.json @@ -170,7 +170,7 @@ "name": "lime", "type": "git", "dir": null, - "ref": "6bf7bcda72c0bff89cac1049bd7dbe45dac5a80a", + "ref": "0f1193c37761f1a80a888fb84e752490b5d0b656", "url": "https://github.com/FunkinCrew/lime-mobile" }, { diff --git a/project.hxp b/project.hxp index ca2fd453e..1596dd839 100644 --- a/project.hxp +++ b/project.hxp @@ -1251,27 +1251,7 @@ class Project extends HXProject if (isAndroid()) { // Adaptive icons - // TODO: Add Adapative Icons in Lime - templatePaths.push("templates"); - - var androidTheme:String = "@style/LimeAppMainTheme"; - if (window.fullscreen != null && window.fullscreen) - { - androidTheme += "Fullscreen"; - } - - final iconXmlChild:Dynamic = { - "android:label": meta.title, - "android:allowBackup": "true", - "android:theme": androidTheme, - "android:hardwareAccelerated": "true", - "android:allowNativeHeapPointerTagging": ANDROID_TARGET_SDK_VERSION >= 30 ? "false" : null, - "android:largeHeap": "true", - "android:icon": "@mipmap/ic_launcher", - "android:roundIcon": "@mipmap/ic_launcher_round" - }; - - config.set('android.application', iconXmlChild); + adaptiveIcon = new AdaptiveIcon('art/icons/android/', true); } else if (isIOS()) { @@ -1303,8 +1283,9 @@ class Project extends HXProject addIcon("art/icons/icon64.png", 64); addIcon("art/icons/iconOG.png"); - info('Done configuring icons.'); } + + info('Done configuring icons.'); } /** diff --git a/templates/android/template/app/src/main/res/drawable/ic_launcher_background.xml b/templates/android/template/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9cb..000000000 --- a/templates/android/template/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/templates/android/template/app/src/main/res/drawable/ic_launcher_foreground.xml b/templates/android/template/app/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d114..000000000 --- a/templates/android/template/app/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 036d09bc5..000000000 --- a/templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 036d09bc5..000000000 --- a/templates/android/template/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher.png b/templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 548e1f239d467611d1ce7e5c3ac20be69c84c44b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6032 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 $0Gc={3vF|&&Gt< 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 A$^ZZW diff --git a/templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/templates/android/template/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 1e13a4061b1e1400c36cd4213ede9307158825be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11877 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 diff --git a/templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index c38b4a3e4cd3d884270789f0a154df11ca9586e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14799 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 LELkaT6!iZ9DN}_= diff --git a/templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/templates/android/template/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index b2f1a7e4c32175002ff9658aa5b6ec5146a21bda..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19695 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 diff --git a/templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/templates/android/template/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index a5f4b953c49afd8dde39efeb4a7331aa10622727..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21103 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 QT^l7z9^O`3Ov??;#8tAkm{WNN z?|j@3nb?^_QX8-!Tkt~%AdV(;X2WLq>nKehGsDml8L3A6Y&9`$txcwEi(Sps@FW3M z8JtQV3`N4`O9~G8$h`*OC_-OwsDRynBQN1G4h9&K$y5#h{{6qQy@!1Aq(`->y>cxw zZJQUpZDCpNbk@dPhIB@ielQQ^h^%Eq3z(Ir58A5_%Nz-?5@yITgo0;sKVPt>w?(iD zgZ=o+F-%=JhL5xes4zPgmUGiP^FYuH3%kng`fjpr7C^Qq0*n}cE75<<0I^hl3vu{~ z`WQJX0JS=hmdeJ@5w&H4h>|#3AVAX-b|`0y8!IuaC*)82qC1?dlN|J<{D?oZ%r4cFijs$C^dMgPXJU89|>^>&6H1SNX2=5yw7dUzn%urEl>p{ zFEFF3Ux0}PNeKwWq55F>Jy@oaGPyx>q%4^eEu--MyugUpL1QZ) z*YY7C*zC`<08@Cfsg_Kt<|1ZD(6dknYkf9^_%~N;RdBny=tNygR73ch7Y-8wMjcSu z7%bG|WjTyL@v^7W_`7Ue%_2`V^LN(WVdrd_xDVaSn~^brdT5r@-t)`L0%T<-D=huW zD;v(wM;w!Bi-4rFn7X6=$w(lb&5M~Qg{^f*jTwv)r3DRo5z6zh44qS3|FUvWT!Se3WO`*XH$9~^ba2k z!YZ!QADF11cMw_Ou9o`!tZCCq7l%CVz@v;B1h{4#!*+j_lUmL=r~vc(D_^U%GY$37 zm|HYh_EtybY85@xw_3&gq`lNLiM`ma9H7f&9{Bfk;Ma?(w@G&rRF%pfn*i1N3X9v- zm9ylTA`{fVNT)FfP7?dqRSmBr46n#TKogrep~cuAiLKOPvUW{3#4Ax;4dr>^vcK)& z-+nv~lp`(gPoV!;Z4A)dtFTJT_EX%L`gR83@!@RXa{5MH8z&9&T; zrdS&;V+@t16??c)OrfN}%tU)ePzN;rEKMtrh9G?wuA2MGzQyLRM711h?H$r6;5eg$ zTrH?lg{C`?i4-=84A~LVYvo#vTDv>zF@W{5;(cj<%d=3Xp>X@gq&L*E_xeu66a^Wp zLB#BtR!%t#vwn;G=c0`Zd$jF2iV49Axbrw3@UPx(E}!k^3#>>zTe;94DVOyvn0`niDq5Vk+!Ai=$PDkNr9{Gb89SP zE1eKJ^H{ExoA6qAcwD^PcR1P^+g5>D8=31hbz4Q|cjLl1Mbc1Jf4yYB!-Q%8O=eH` zbt&}9#CKaN!lWMbpuEI27{g%;-{Mz4o0&&00^w=0mIw?0^ekk@KD*tFXRLC_|1I{n z;NKL9Qe7osAM0Ug69|(6QoR3Ge>};$%We0A3-|Mg4}D(2r-CAv=igaZaDB^FJ|R7q zwcdH{+aurJ4*ATS$!EtZzM2N4{?=<74qHXlZ{Y0 zTsd2mGx&t*C4(o z%*~P4r3<84vM^}wjfbcZC^Jb$d^|*i_)3uJlYRLCp8b29+5fk2Z~qN$3SC`$6_=MV zG0wT77XB;>leiYa6fP;{41)1@XrhN8#_L+$|=$x)Yzjq57UQ z$^Tjj;EfW&eOU%8P0ge*Yif!YBElT`Tm?@LOSN$8r~mr^&#uZwX#ZwrO-RAwQlY`Y zxcJu@CHb!0M&6)7$hWpuCph&24$KU4VxllN6AFMKuVxM23(TE1lMBmIBEgYg_X)^d z5|9|4oW6rwJF;;82ki-X$0nUckhd~Z*TLR`uL!RC-Zc60+6h1*f|3eB>^!m0N(p4= zAOEvHU#WtYg;%xl#X#}+tEc|uM0#}jHoCR%$UnEUwZ+1jbCg75enH;IQQWRqBe$bH zq@zbex@jAvTX!PwyXnjkj^?J1+Hvf9IqNs0XQ$5m`o2EixOD8SfXuq#5uYvg_|zTT z+;NSM1piaWxPTlqfY&5SIboj0R0gT68X?yD8=wHvq(uopAp#Ks87lao8Z&MBbPrIU z%wGtst*o(f-Xg_F?UGsZV8_fAik-J%4(gLkm6g|iS}1PzxVk|4$02osGG8LEabxBf ztMmr!nvVw#Kwvr9s&UmLBXRTWWf5{~M^4s5Zt_r0(WONciMqnaDUMA#hrcGC#qqEH z#mTQPDV9n`n^SoVCREc6M-fbYkDz^Ic=Cm<{gf;ejdRCMn2u@vqaUTq%s7 zS|P(`9Etk%;&K1Nb>Vp$$i4OmPye}# zTf45amYxxN>J=PiWMBQ!CLZH&8r2N(;q_3rQXM2kHb820bM#2;g|VH+Wsq_3i6RjjnzOpnLZKYxdV}!kEa2>^o^$ zq*h%7pjRR&3L5uG_kHDbG6wc5&qp^+R7g)C;#aRy1yjdQ!oeMTaQ^T~{Po*@Y+SYq ztL87pg(IhwBx60mJAM1{mY?kNQA36*NyzN>yoL?bT5?T0KQFbZfVdo^m}$iBBS&I^ zyUcWIud+8n>$G${&AKN%M}ALNAMmITMW1NQ;ndfcv19mNe3gSO?A)&y@TkW zXiV)jou4lk!8=W4JI7OlF@17X0Vjk=&(FD&hW)=L-ss&uAeNnL-=+<3boD|2X5rx` zmh3NeNgj``H9J%s0T2vE1O=do&YbDh)YdSSgJzyJNQlRVr7MvmG6*Gx?W;F1GRWpJ zlW4Av1Alp3q(j7cFa+8}W7iaFr_KIm( z;oS%s&Jk(@QJ=i`zG#wM?zIQFyZ1W&oh@@_Pk(b6^9C$JgDMSpNLKTCbphG>!&(nl z%uw4jWCtn+`eDJWXaN^Ak$ac;k)HqXS~Kk25O-nXn2rl` zFY}xrN{`@9C$rBt)qWozI!D3QOxa6WT1v2I^LBOOGbj80!JB*`EIOa;cp8b03yb4v z*s9N}3VUlyzRhc787~BTy1`b7-XtXf>V%d=ZF+DW}Y+JBl4R2cNVCph$5JSvAd3eu;^ zW~XJrZ=K5&5S&`sXIJm>-XJIIKAxPV7Wx(+)VjOxDn@@WjGGE{9kR=B|C1#6IU>M@sb+MrPrX?^YJ(hzTr%k5l5Y>7Aaq_{Ctp z$=|I>!};HTMt_WDeLEsLz)$VG*w9#HG|E8dmSH%)KS?L@7vMi?P!I2|k8z(z3?z;v zBse>p=#c=(4zuv`;wF$gL%@X-jSfD&*__>cdTucuRDoC7qU#D8#HvB2*gycjz+Z;F`)SXEFQF+PiEOO z<{)3Tgsw${Q!9U3q!iDgju#ZaZa|vg?&97?9i=t z6t=zJ8`mZb!&8xc%~bJqJLS?Sgdl$pj2;$&8~-*I)VDSiwEnr*&o{=JWw9sNb%Kav z9v&w4CNcr&g%3bCVa{L^8ENmy{`$}SrfP5MvdD2MjxV4JolT-w1e(lYWu` z1N=uKC`nI1&`Ahl--~_r9H15FtW?%1b#-y(Ax-)Jp9i$T+^YFYWaK|~26^4OtB>}w z-^v*}6i-gwWMkf$F)hHqGiJX%$*U1dsmtY>a{S) z$Wu)XkS#`$AV+t1TVM9KJVV6VWwGl{9cq9l zH=6OTj2?&{t|Ve84gY|C^WK#vJa_%m4@1=Q_BBCsj2&&P;P2wZ971oTR;h@0lj>l2 zY7*W~h(&~-m)6i>a#9lR{d*14U3-yNFHSjt#fY0%F%;6D*Ft(jWp)?yE1n)YgRJWH zaMZODwt3XSTAzkEKI#A#y}JV5F6#TNMOCeazxN#E16en>UBj8Fm$Bl1>yQwUDC*$) zI5}WS`)1s(*Cck4_{3w55Qf$7J#6obL3grXy|JwaWe(uUO`O@H^{5kspDzn+qxCz0@DbIr~3Gs9}+?;f=;Kfo(MacxHa{Hm2B^BMW` zpIy3_pZRlU{;>(?@;^);obT%3D!3N{Yx(mR_UWLwf1NO6Wd5^x!}7DY4b0Cu(X@f|8;_A7^_;qtVO#iyt3U*Ib7(xp;MLs5RjJT#34zRo^%peAe26?-?TZOQ{ zVG@a?%80>XAFN$g>l7)P%%Z!jOO)G#6fG(#Xtgj}G7p4D&kq`s-?%as9ov-QljTh` zT1IM3X$cit;EEQs`hC?DzEazN7q{a-+SK~MGUP7$8acGn<9A=+I&&7kY}FV6?k<9B zk@=L?q$$obXE%TGGQiUV>(hl8Tq%L3c=3#FSr6fmU@c|j%BeYc_`9j zTJZHi<-ZY8DGbN=9zxEghsa!dm5<;yi*7EMrl<-#D+x}I`~W%Z{zWl;EOKU!LC&0! z$X)Xhr2F1N-anm@f2XCOeslp5p6vYlS*f^qG8y}KC1CkaF{cOrFZ3hg4!ftY0xo%* z$hf6<=8wFn_5yAUWgHwVE85vvR;CAGs4?WDii78VAN|YPWwp<3Umth-;O@knBYP9` z4(~~nzA)@n!S9LEBRb&F?nLQ?F%i-VLAFx%R;4BypwGPAoTc5`G?Ff!`lA@YpOAmiBUV6mVR(gbeC&%HC zVCmu28>NSL{UHRM)1n1G?oV@xbg@&2bb@Og>4aK6rDkSkir;Z^bd=8edYbgl&3mO= zzSu5Z_0cA2a@EFyXVZk6gRS)UPXT3>A`tzq=#0f zOAl>sEIqutzVO`_A9;)exw|&kyS-*n|_zUVujE92h(p{H?4^LOeaWl1T*B7^+)XSky{x;M1yaufX2@dIelIY0XxF-I zMx8qD8MR{EGoq`zWK^%BL^MN6Qb0zp&M_JNy4ETZeLL66XqXV7b`3dVqTMrEw~Wr{ z*Q22fT@y0;MSEuSW8bNvNoD0Rn&?o6pVOBe*E!lRqjhu;N6R|FYQL3GCm^G5*QAVI ztrIeOq$Ol@ZdQjsTTHY|MjiH?%(P?2cxEIecxE(7@ySSQ;+N4f%`c;KYdSBO{g&`O zy=Y|g4CvRlM__N_0&#-4K^!5j5NC)x#GwK%5vPb-dfEER3m+3Ok#$piWY|RZ02M4P zO~O4qBvm~;Y)J%F~n zwxTKm>au9mj$*eoNan{-jv>%Qrf_;#>gJ4ib3q&+E)XY(8^jUf3UP+GLmV;~^2>`{ z1Z1ySn#e}($cCebB307^`k2U6P7iFvn+xIqae+8N+#rq+SBNvj9paEdkzci__6GDY zeBOYGOxe`6QCN!-o8+K}H{$h&YMm1YhzrCC;s$YqxMFavzP3USpxPtQgHb4p9+J8@ z0zI@5Zyx0JPh22Q5I0m?e1j*yYEcz}9Q3e87@^9E*N*sw75T#&@n*wVjKx@t#aN8R zSd7J3jKx@t#aN8RSd7J3jKx@t#aN8RSd7J3jKx@t#aN8RSd7J3p1=GbA2hYtR58xI=Ka;2Id*CAhoXA^$%6?y7t4 z`*}DI)T*L}>0T{gcYnQzR8f*fMkGLlfPg@jm61?`fPk!d|AB`E|K_(68v_KyR|r`N zQ4LR{;~Y3obxp#d*`KnrU(RIdnG26QtQXsueI{{KR(yP;f+F-?0zXKiqqzj?qw_TP zRBnh^>2i!1y`AOit0lQkC^|~b-n{1R-%;)Fj4OV=%Dq~1J;jIp5Gg^CC_{zue~G zKx)z*O4QXS6%t`>5*~)_`0Dh>ne64aj)gNPy}>TsR)$xwDI=_-ppjr_SlmK&&5D3$0`WyI;6 zA&UKlEBk>iuRPjBnN%G*5|V1H zYjnAM;|jso^75dB|M!Nvxw&Tt%7p)Q-~a9LB=|a=itMzXgM8(*w@;a|eq|XbL7@q? z>Fm!Gw-c+5c%?6A`e{eyp7DckD7APzvT6?BkH;=OluPpm4l#;EhJ?8%-2t!FImW3z zFE}cgC3GRk?@U08Ti^c}87y>uWV^Hs;C(VO680|uz;UpAd3;6efb#u^<$UaER-z2H z3}~HItfiMpVmRRoU{055WjC~olP0AbbjE4y67oA%TZkB8aR47>s-=?iY@P#2ESDngTqDuFmZX$l{b_-3iR|50{D`|rzy(58uJ#Uuoh)s zSjJ0Gc##tkuNcsq^bO4B;WSan;&wx@=5MGt0`Fe~bL zg(Lv<4?a{$029E`nXsn)|COj5*P#34KDidn5mw`$vOgGD-87MZIe5FQ@#UMKL96PT z?0Vo-u8Ecb82$pr+AyC-F1)MQMwg@*yXuzjk%ZOn;Tt9!wn*ZtL_3C%`dar4x0R$~=Dc?gyI$-g9 z($C(tiYh`?K1L%nG|90qs?l0oGNhW!;-I$F?%-j(vyOZvH1$Z=_Zw{@L&uzBUlJst z+2|;V5VpMJujx7u%fA1udzJq`?!5y}lsq`X7ORx~djXP4t!VB+b_93NYZx)eFEd2LHj799$?)uLdBSh2rbX?u7S?K(3lF>bEEP390{qI7^`#>R4==9`^vc)1 z!p4gl*amO&LJh}Qr89@mz3sU3fCdjw=KX4T!0ydT?q%Jk%dMW*5^w!YV=SkyC8hY} zlPCCQeO&Yr^s+U^uD2A)k+6M!IBtihqkl8on!Nk=ME;joRM4)-nr?->% z4*f^dN#VHTMxV}?_)qtr9jx2$yan1@>X40$*9cgUk^|nGKnQM3crs`yc0a-{WECnwwgj6A9U?3u-drgDb*URjd?@cw^848f_ zs5}(956@vYebYZRvApTm9kwzEn*V0DHv72ywKq3oe|r1Q>HqReAnuHq>RGG!KQL4K zpDKS%w44q&G=H^(#VF-Ccvc#T%Tm3ZU=C(=uDjE)B(w&9IAEo&ArXvDZRW%)pF|u6 zd}~^TaD>)~77SGh3&V;Ed>m8Q z28X!c7*!Ye2Niss0>tpEeL12g=+`pEf5G@PA~kA+^bI)=TpHeib*Q+d-F3v(UHG85 zx^=sSHgo%Mv~H@}14>OjsN8^YWWC1KT5G~o>nAp4_|J{b7UbH!Sd>Gdc22mH)0T@j z^nbm{e_^(Mu3zU^*raye{v>yT47MsSe=}d)4sjxXHIBD~!Q$WA+QkbmLGL)~`oyCD z68^sq_iW^H-o=hmz9tLv{sb(p-zef15@;|C!e+J51^yju3ix?();L^4KVyaFkPsH% zsCO<>(qqSR|DehVnUW%C1?3=dS!;-I5^lK7BiZh)@OuwojU!9C) zL#YB?*Z$6H&tXTp&#BGVo)^r-Bw#7lUAYd@6doExeb67g2D-mx7qmh!(`1-?*a_^g(7SRtx4l5sRR(j8l z?dPDoxJu$DkLCuJ8|%((ya^mNJGT{f+itZwU6JhJ?Vz(vIAGzsYAZ~8a@_cg*f}xJ zs_o+Y1mJ9*VDr*cZvg?d-hCnzsZA5r9FqbzH{xwIkALv6Z0f_0Ra?FE5s8W2%MV}{{fO%~yJ+}F0Z$Lu`V2zw`ga`9f_7FVYh z#i}{s$^5v)LfMg|S8CCB)*$i7(MJkcvdQ1?@CN$AXW@;t!Z>y0v*lJZ( z8S611ZktUj*}6z&LWN!($?HUCDLQ%HHQt)64z-`bA6yu&gO06k!UH83Sn0J@c@CWC zFs$7z_akG+Ix|5vnxvonnlM>d#zegJ7FUPN?y@pN^qgJ9h;qwyF^%WSqdSl@H&hCL zY8L#_$gTPAUSg^a9Ge*A{QEZpoiv=r6eomne`Vor2_z|P6{eX}-SiNi%j@xbA}{;F zNO!U1H9H^}EE&}_pbLHgy@{WfUUkp+&5Ost#|wwUE(|_3rTTYG20I^%RL5IGhbU$K z7WGJ)A9B{l_L3VL4>e2&Wgoib<%yp>ARyk2HQ?2swx{cPOiL>sPlhT$zQvn?$xXsq z+ob@b_l?vW>Z)i3QudHPj$>UtukBLX8t`0eQO$T05h!t4{B;=$8*ZiK zZ^p!>7ooQMYr{XBsWTs*`8TAflT4C~43uFz%;lc~%@_qvI>m?^t&j&T5iUFh7?GY* z8T~tFs2Et@=-(E6bQ@jV%%BK#nT=3q5E=<;H}Zd84$tUyGWM;3fwN)p_aWwo$Q@RC zr3J;!ihleYbz%o#_1}iY8*AegzVgl`$peQ03uR9gvR3Jk&7KAgVA(o^^}(xV>PE$- zaNXM#g&2np*60+G09=M{Bp#nzaO1|EIWCc2UcW>&(s4CIPy8Fqq?KLgZQrv z7wBDn3m;%GXVJ0osWez}G0XvFea+ey7A1Xu9!BCzyn&VL@JM!ZBHGj$wUG<$%Djb2 zX_{|az1|~XMubJ5*b`)4ryg?BP~ny6e2nlNp6|2WF#T_hXg?lj9VDxUR19d*Qt6!N zJ^oN4-aRy^*oZjnn+S_d#ql6m3ek0gt8X%LEH<<{zNj3BTx$8YgS#oS#@mB{;DK1q zAM&k|f2~kIdm)$uD=k2-!;CC(MD2ZmZL@h*zv|7XLZqYRK>lg*MWF1ofs7d>Qx(#`Z<#`f_ zqVl47ZmaD-vESz88u5n}Tt6#*@GiplkRudVJk@YM-pHMMN|5^)UrnL8U6Uy?RQcD4 zxHZViA3Lt#1k9a;#NP04&&Hg3c3CO6=Pto9Vm`J>%G+memB)rj1N zH0jb*XIav|HVz@P5A!kb6*MKjYMEZ?-K~_@p>~kPxAB@M>?w9rt-Lb=}9_e3Z3T7YG(1*6m!sW%I_* zOZ;ZAmoeeI2g3d{XjPop0V!lusPswlA|szQiU{z2Tj1pmyg1 zh9kSRZUkTFdJcs5$PFeVtePTx=}a0jtUt1UY*JsT#FfYj&=oQ#OY~(1GX)q#6D^!k z4|)K-TRjaC!e8R2eRlw@raF`2QDRUUWuaN&g(b0_gQnXAU1h~f?DEEbYtDtK6l&hD zs_U7#Ltn0~G26y3PaO$Eg_e}%xN}3#Qm`1q3t}#(13{Q_#45p2cH19`0YHn*GTi0w zrY~&y6wZ>LozWbXl+W77GEN1@YT`T#f zvWTu3oUEv4X5V+KQw&|Xko1Ho`a{r*KVKjy8(8&Cbrvz*C)_rbvD)EtI-v}e@!|7U zM*zaa{v0~Bh8E$3VwYK{YCx?MyNNOuWIgNE{+Pr&FQ6mc{T)7&9 z)>Pd-b?D%{O^>ugO`x=Xz|o06-{zHP1Mv6?mGR~y^Qru0kuw>qcnb$V3opr;ubze! zAwszO$L6*7f^sC@5RMZs3xPp3u_lzrNE~Sa#8Bcv+a6n#1p4rqUWu>9oTR@S^u{Fn!(5XA5@_u1>r_7m{z|d0n%aX2mwrK+Vg%p_?kMmcRm5x{5rA(Uwd%I0wlE3l*YMCB~|sJ*MeH` zXl&DJ_R_Z;{(k4GI5YI`O{TUpya*ih;JG{Cv5hYPXKWRC4pQ z870%wL?jP7e+8U`?RBb$#Ng^#iDjKh&701+zg;QQ2&uCsR@h_P?4^D{Q0iZ6>I>X zx4*@w(?xCr!+l!Q%?#|2Z`?o%jUb8LVC3*O$~H=^VvSsE&S%k-VXFI2loY)Yes8yJ zDpdxl?8b{FJ1izQsF6FVSa3duU#doij#z(*$|b$#UoeY_+ayR~2P%rJE!GgPRZh#7 zv`4;{*dyaU=k>5$1Ky_8>E!*l-2Pok`q9XZu2@@sf7;x!8h*Rqn_b%2+|l=6YFn%SN?g+BjdD?@nhqOTL! zbBX+s4>D`(YX1pcu`hb)tU!OAyx^a)yH^IDP#>ww2b@M6$=)Y~lNX6Vj0n2?_jzAL%Aa|Yaol~~!%i`2b zRTrtk0{l2Ck3N!-`}yEQmf5>na=aPoMZEL)J%2qV_A%*^2q*lH&N`-x3OiG`7av3A z?dJRO?cBVilQBRdk*=}&W!Vl(7}lAM{iQ!Eo$Fi(vr868hU}Y$YWWe}mbXjf;D}0R z(FYm@@_D1D@dv)`tIkN?iDLDoCoqNRIQ}Tm z4?(Dgr<3-!|90?nsuSdJ^I>HFIQA&?WyHGvtfzBU_?uzq$T#QlV1%5I&t{aIloE9I zVqRauv;i&1IznKomWqgAM(GPf$HcyHmWQ!@JP2cTUY;7`+^qF*T~^U*VM z{FM!HN?a;IY9~o)Q~3mjE@X{_x5ZzE)=Jhum^}xsH3*yc|q=yGhj$_+(CIHlRB~k%`#2d z>X%+DZ>LAX=x4eOB*LOsRYnSoVa!dLSBaid1~kT8dxYp{?43nBf(0rXjZ!be{meQ@ zq)O*9S$m@LSQ$b00cfw#_?$Xl9Y+K+Q7Et*xHTJ6N4z>s+rfQ*E-yQ z0hDa2jI-RcmbT4A9Y(Hh`h_62x7KqeLS+;;phRPi#K*?3pHig=hkrgSsPJ}77FMr} zkfBlzp={AcjI(kDEV?=Y8xnYLwMpG1Jv|dtrjBjRkbBNjEz~&dRda0T5NQb;d!{Q; zr~?`J;Cu?6dFcueRV0|%;)jvSsZ+=a<5$;p=z`T&n6IjUDUZ_s`Eu|g>+ff-S1(;q z_I|okD<%E(LuS}2WpB;?GgSNf!l;%sv6ViP4K_}JJFTO}rhDTFNMvgOz#%ZG}@okREfZ9;_)B$A7I~ z;)#$~%MdF?;oV!20TAP>O%$e+rHif_DN_btWk`g=iveq&HH9B1o)n03Mic>HE)40l zRL*i{yj^ziQ^Py+RpYczebJiqSd9U4uoCL@(5#cl9Yy)M2-Sw`Y5DK2ek6!IK)9o} za{>;J>4iS4~P=N!H$;ita5i_j}9oE#P8VGN-G&!y4LV*szVX5QG-Qb?q<9ICO zIZZ7;`{cHjcio95jH&rk!!Y^#kg{gsKC^rIwyKmI%W`LhKoTAu+OevPV4`D1aO=8- zKn(QJK;{8KTExFzjiWGXDCkWzxjq#brU?z2rNkd1BXXFWrCkx~+ofuL`p>#i{ zj7TiCBi>#&L`7LDJv8h4KpfP;I0gyn*=~hQH0l^G{@1WJJr<=xLY^Mo@4}D(hE2H? zlOk_yOy^3?)DmBz8Mt*oG{gt5Sy~Cr7YS=aP02nFRYQ0)63&p#nb)wibAi{pz6$( zRX?2j^xLWrcNGo$uW!L+!@W+nTOFO#{%@5{A#YRBV;1TNY*_}G0<(JhNE%KbIXL?y z6&#@8tBHE;^QBFSH7gMC9uq~pQ^x$nhe!T&L;EPM??hgWp>i)Ui%{>oU!gA6OZv8_e7J$Kp7vwx zjFFO^m8oLWXgw;>9XvWH!~CjjTB+SpI{1hSutwe8+Uot7zX8I;#R^g6pXU)=ZRS1< zwQ1^p?Cc~mZ+eaXaa$R(VDSYoWMb$^n_ox8Kq5KaQOh41HsjNq5&;`@ww%FL6{f$t zC=D3+XUYk;nWE$iZ50mvSMG_Q2a}!6+gc}$AiVrR5nl~`pDLVXvsarOl*2TN3W{>; z`O`3>%W>@t*zX(t>|CwQXpjU~#>WUKANz0Sm+l1`idAeeup_2AJq5l&WzZU6Ey|qx z5SmN>v`x3h@2uss{TF09*3?SW9%9zKwGa0vaS_p}PCu=K-F7>E)G{OLBW#=4@tCT) zh|6a<6T#}USc6ar|3D1V3HreDO@2a1)@&QFQg%qjW)1uG1Cj(gLwUxQu7lP9e?j8h zgK!37uOjB^oxR7on)v5i{^YS?&+x0MFlJ?S#y$ot93PNLRH!XCk-mPcyq^BkR-?Z7 zIQL!yG%T!1|DJK&UO8TJPI)iWoDSax+h7 z$Hq2*2r5I$3+FsA_Px^xDr|8EcsRU1=hG^PUcyLxz{*vuF0DHE7i`OE#jF*`5{QGB zMUNY1%Z$bA_=`*K$=5LGzZP-5#>ZyEgmOr z+~xu+moL_cF^#M)?U`4QX#>D1z0p`R53(#`))+}9j^z5bh>6p`YvWkT;qs9OQ8g@% z{z#7&vqKy*hbH*Ic9~8Z`n6~vr4Me}aqR>? z@=p^NHlg#(C{g%b?-Ig#)uvU`*@;)hMgUloFNRn2=;jmAp8k~uXMrjyFUY}JkOdXUwe<$(@g|Bw+o$uMM$|E+y%ejPs2YDAmJyk7kfx_p2 z&ch@HFR!#zkF~IXab&{xGV&vC0K5ssX8{^{ zf*j3j^!K{I*lquGtM;|YdSx3m&SSG!Nlt2mMcJMNXP7+Xn*H_j zhKKyE9rL)2%ayAeK^A>`4dDa&p7e9iSrC6IB(3<8Tr$zGW+_u>FOeE{8!Mz2iR(q5^lgiZUK7ORs6iyvB8~Zd=zjy~7 z+)(QY_@1G31-hlky#?dY4Vx2NC_&_LQyq+XBFX&R*mf!+UH!feY`(OCJIP=4dM~V1 z@j&W!BjOT#CRCG2VNDVN4s{4hFY<5Vsr*^TE$>BOuRvb<;Ziw9UW_9IaRj?%USgB? zVzOyS^e`hV2~LF%^w4Hs&28@)`O0>=#U1SD9i5rEH?kfZ z&0zdDZ#Ob|bL8pWNjqq^*vpFIv7=frC!8y{r=l0Phm^ISYeSAjL>MV*_562Fi-sq} z#zTHDVDmx6J2&V=GQ<5cB*AI2zoTv1dp@>?hIhSYR!YSLf+qCGW^s*U?-|AQg?-Q~ z4rl-!SIdkPLr>qcqg926OYB^7P@wwPAEqXyi@`lM7`7zb&(18&v{(7#`NSX4aLfJ9 zY)o~`^OHUg_L(k~k9zoc2clUBT0Y~xncW}eWVF6~5k=qO)-OP4Cc$A`8!(W9o4W#ZH!c3iCC}_?NQgq8V&1hft+irDqOt&`v z2Xz0H&pCi}<7|2?62g<9!VtL;{IhV|>Z(=Gy)Wd=o+J0HO%zUxMmwy_!L@nE5}TWp zLCBLkAD*T+*96dW)2MuT@R`TZb+G64?qfp$lBRwaQkhqd{SUQ*u|kUEAGV}S|ck%2Ylml|7h)aPeS6oBtEGI*fRyZ}awV>GXtsJjh^N<+ z>aI~RO+8IuhN(E#(SF;4TrEYKNywFxZZYD5cXH_M4g-~l%Bd79!eFVF%ALc9*AIC zh5spZlqB{oWKS@6@nyAuHH0@x476g37)L)YBaEdsj9^MIaB-pe+}cmUJy5`>*Bk?e zkYs_tA^I`!`;MIPq~ZoR*M9tQ5Z01-NlC+jbtGr0enC- zI45mlTeP16OW}E7q3X6Ko59HO@eXlV$P&KUwLfGv+%nrMr*j~oyH+gSkmLE|MV~Bzvh_meGdBV5Q?J0pbCc~9w6LA@9 zp0WCg=AI?nN-&Lzs-EmUFS3)GiLF`>bp4-0BrX-ioL#U(d~IA$c9+CAj$lL0!Y771 zmBRtpYfgpJG4x^^=OMjsaby7xj^rkM=Ad!`w#~Lxx|IMK+teKFxYB}#`Nyss{(>Vb zgmpuyg5enZN?j11(%tW4uSrL;Aw|_nQ!ayMI(QVet)2`PF-arCQp?224~)_>RFgmO zZH})-fA7Vg`H=DIe1Zr*@o$$wQ1F$c-ad+$=zVhN=dnMilUGKaRVM05NMAuR0qATH z-b|n8{luBDsA3IzINcL z{3UxPbD#0zP$5%xJ;X;?jKYhia)V-=^+OiAxSFRmOQ>6xLw#%KT^}qzRo3omB4i+B zMzeJ>8k?Z$&Y^>t^JNHn`7g7T^ftELGe0^(U#!^)Bc02r6G^Q?4-dn_maQ?GRv!0U zb6d;H^O2jx0;EY|)}I4=!pEvj2_wFJ#`Y9YMXK(_EOO13f@D0BOsYz;zkZo(eD zHtvc=L-ZMEG$_J&DUoD$wnjW8*K!x?2BPq_H|W`Vr1K4}Y(qtTXeFnL zQegeVzZPKoj!f;1HX5SgmH44Xtln)hGUDrf;KSpa^lsq+c@6s3$-_&n^`3r<)#9fT%9z6VjjtrFyoECis~iYN#XhJH0(R+lfBjPrrR37~?*+ zTZh|kR7uvK@tyCxw?CpfczY}u)l#fl^sJj!rKh*DHUyp<5pqA>U;+=r-Xq|1=KSP#HR51sSwtC+Jilols?mAC4>~#KCj(pK=L;GU+uo{t#CZ zf74YCrLUr4l8dJI1W`ca_*27X*9C!_W8Y{Q9Fvd*U*OflQ;rGZjAN)_wfWHAjcJbQ zyv;C7D75)?UKbMNupy%RDQ-}#@WI`0?cXGV$iw6Og*`P08TW_so*uo5ApBKhUerH{ zisV95yi+a~hOyt-+(__@1GPPH=pmWFO3o@I!ru~Gf;9jBK&nRx$6rmi`Th*`{F(gFrI`gJGpG) zC=CE#*LF5=K$=9Zu_A$>EC;1y(YF?n(@wxXyC$mBLkrJffI1sfjn)m9`Ol(NP|>*? z+l*P!h#Vo^=E1QeK3roDUr_)&BN%j~X_E){8{j7Kb20*<5TV=|s@2#VPBn6drUTUC zB>8G0hKb94d~%Eyf!jeyF=ksI#*U5@1!fo-hno_Dt!qMt5TUH%sTgoHJYmk2ya0|x zeC%K$b>o*50?;@b^0)r`7y^w;3pt3H#R=#7{y@!b*N&Nx(C77Uc*>3m`8g;8fn(DL zH+lu-*KeL?i;xAG!5<%ilM7K=tR0^@Q)=sx(SEzIiNC;eL1cVeutn!a$z|vH8d*e1-*UWg#lT z*xYRH`ZT^cBhq^b8d*45lGtu#wA+qNb*Oc9T~ESU+DyXHSF%1yJ;ZMc{e#WGMJS0F z8{8Lq2V&D@#(P+v6f^l8UTdro2d=DRoB^gibF}9^#8`!45EtT5>xiN1VMX4O&D~6YOphS6);pVl)2fsTZyh#r<{NVUNtJ}_~e__Us&9S zR&HDV_~IBJluR4i{Ue=Tf7F_{qbbJQ9f_p*80Oi)DaLt%0W&ye3pQUFj)&1psDbs9 zdu@-U8||vEDSc@~Gl>}5CAEv&sfbzlL&T_ISVS+kT=ye%PfL9FiZ_M=z5_Z|)#-0A zX-AVT8i}N&#Am=!eDqA?M$#9QR}PU;Y9RSauw+=Wf+tYX@h2ze;SMx=uvIE%GBXYy z`F|3s4kqA6vF(RxUs%rC(jnqb8cRyeyMY$*j%s#5@ku9|5lGijGMb~>&NbMH!`g_V zLrp7Tzki5PtK3S-*f5NxA+4sngHv1f;58YB0Zj4bO$yX>2y<`>>RXRvdWZLy8ix5& z4|(!b5GyGCqDNqZEf`9&?%(*4>|%WHuS*}mc^zulY&t>UyKc+sRY2qLk8XQoC#TC7Zb&>{*88#M~6YJzd3z*F+{Bw_r`5HXJD(Jq#LBelloUA*|D~ z?9~W81MhYIic?plUjryu6XueE$TJ&nebI`I&;o;P+$Dt5*o)jwDbS>-vMujJ}0W|IZjrsCiA3)nL_O&Xn(e^bqv&S=wIk z0|Nu$f9as(e2a}}{sbRSI(R7y?o$>We#5cR0*X<03dHqKL`>u0#gCSqdN-6mNiBq; z7QUvqL^z}%7h0&Q8e^EFh<(8IAuX+}34V)&l!Lnb$(5+0O~+`S@VQpc5FtJ zJ<;pN0(Xkmw+$Uv?we14w|=XB^eeqwM{*sE7|?fFNM$^Cm@TD4WXyZAn5DKS{t2s7 zvpQ_#0_IiDagd7_zz->XB^EN6p)q}WF|B)#&Dc_KqzchZ(RCvtNGlrVt8n8KA!Bwh zFM58Hs|@G5*2oU)HgZA5W6XIo>oaLR6V$}gK6O11WE#2AW9V@QC+c7Rtf=XiGVRS> z?Y)~8*Tfsk4=0z8cD`~rGf~_xuVI`C2#l$8v)P#k+sDC?40H*mte;HuR+8B@;HhpU ziq@d)OFSp1v1yX=(b*l)zG;vVsUy9U4A;=~)_^O=a#r5j*b38N_LCL9{Fw#u%|*_P znkINZBcnJeTwIEu{y0HQ6Fgg2VPUg02LpTj?ks=OM zWNbu8{$bVFvzJHM06}&8!i^9&{yP(SAu5X*IbK1yi~X^1wH-V|oXI09=M91a-XtcA zqW|FN0Smp^LR*D=7Xt5pBD3Gzvv1ZLu)DR*AnF_W>6r>r<}OD)Rp~@I5h_WkW>6(~ zcHfb&qLGj(c{^a^QG@umT@R&FH~{j6(X6|$)Ofb2zh{CB73x$&doRA2fMGt>xmq5h zBt($f&t?c>d_J9<9#TCH3klm#m(%g7>)>s#+{R^_TjuW!iD-`*%t6%Nf+0Q80`}}= zZcm8^-{=PLubgeJFp#Q)=dsIA`q95UR-!Uryt^~e zicy$0yQ+nNwd*bdzYvE$56o4Xg&O62yEymor1k*3$~p&7KNs~dhrWeQe`1rWaT}=m z{Ygi0H3I5nW>Ju3NCYE?A6csKDp0*@&s%EH4L$A%@jd)3dSt)W^VK?`9a@AUPK<)b z)$X0(+)78)d4*5cq?eudI;(2bmD0Ew;>>Y=FLKUEicx(b9V9U_7L_$k<@+6M)(W0m zsDgZMkp7c+9s3FSV*;LqPCM z0oOD-+1Eg5eTvP~4z_nyxZ02(AgrvfmN-rv!?c_$H7G&n=5Ik?53)tK2@CJ|V(ONv@#^%#xQSVe2vG{=Nckhqz~4yX28p< z%Zyjwtq)tWf{;D672QEl+x4qBM5G)6ZAF7As?SN{B5r9<^u>PhB>5OaRg4)WS6w37 zk8JSsIlAU#(6aSr%>F(+q74+#m14W&c{D zz?6m$l=}y7_Yl<3%xw!OCq!R&sJs?L4Q9jW1YPwirbdG?Y1ucQ4Dl{rKgB#*5Dq$l zqUp1u+1xzXf0FWr{N103IJ+PZ4UgA&;KuM%)TsO!q|Dy?QPilZ6A$Rn?@>aix?8Y> zncB^cgg(7Q8Hs;Uiw?RRKoUDQBU-;^sjF{*^J|`swRt!0XtyP9cbYIG>Dt)A^^_2J zpeMm15eQ9mvK=2@3MOa8F$FQUu|2+2;=TQGRCx3wMf5oBYoHurJGqvB1EzYW1>+AnEKF!Gl>qBE;K3R9J_Y{&AEKS zw@X8=GtBlr2a$W)B5(2LrO!&Pt)gL>4c;3rhsdp*W%K{N2r+YCx4SQ2o)g8}PU!}C zsiT8^K6t$yJ}^1m_g5Y7;-~d`5kLDu%SnXHD2{KG=|_~F1i1O_*Ke#FZG*`nUw2rR z&A`e`Q+EWI+Y=vtEGHFT)FSno*; zGUWQ+haF2h2DXKi_ri3MJMenT%~iO+O{l8x7apodEXqkV>TH<@`)$JG)NdNGnxub0k5*wp2XM6>-rr~Qt~Q|2SbMcPM8RQt20tVG(fI2_ z;R0~z{_NIlL>SB&O+hLMduZJoZFd#;nCe{z*l{Z4Ck6>G9=H#|_4?q2#=m~u9~As5 zJR24vIszlCz0GHOH{dSTpxgPJ72Dq|bG&*Ug;vsV@BAQ?nWd*E)*EU<4Su`t#W3zD zE}-f`l?sL(^|IqWV_tbI|>F7Vp7|uR16ndUBK1sqX;Q!>kvzO|{0H^e3@U zoGopfc|YM?DjlT{$PKJV@{Co&If+l0a2N8OQOieTZbk{!X1ofEV++^bq$h+_AI<%i z5F;4`oTi_(w1QlC!tGBIw$4kByPrcXd(pgolpOt06%QA%;s#7sBUFd0Xu03_r~^$6 z7Cpz|H+*b%Ur6gJ6+^ymS<}2QXgOjB<}l-#`4^pPA;<1!HdRJ)M6!w_3;k z037R}@E;o`P*jNj@A!&Ph~bvT{j^c(bXclihp@uG`+$i33+1HPLAcvF`)3m02mQF^ zWw;7bT^$fc!Se@4P_QD~H)wsZ3VaQnR6Wtt6_LI!e-c143ZL%t@HC!(_mlbxIS13( zd#}AwL&Yv3^GbT%{J7W5URAWW!ANL+ z=0cL5q_gTaR(96X044&4~wt^I4*KNN;=>-au(g_}?#m}T+YN+b*_F6p(r zgnr#p{+XwtXUwX8R9=@jZyK>7KOtj#^lX&D$kGuYWJK&K@QZ3M4YoF~tj8n{<^tTA z>^zH^Zq*QFKVJ!Pqn#|@6C7HNq+gjyNb6!}X@^1#;?31Jnw-J@n{EsMr>=Ep+UY|i z7(r3O->#zrq=+#(3!jAY=qp*1!Lm#!vEc+?ik+D}@ZaWW1OYF$&ng~7?t|AoWjiDF z97FQMA8IHZ(2Nu&2kJ7#Yko|?3(nd>=S+3rMfWF2bO0&3}t8 zbkH*2G+5$n2zZ*=7-&8jsSQFFR(%*4a>N3=Wi6{1@yzYRy9c0Ihsok{S9cgE;b8IE z51U9szi;s<RWZh(!#Blz(Rl0xevys z|4`eohQ{Q`j-vbv+?u8{o{Ow0X7D(YIA-1RI%c=vX}_IO8*tnaD`egA(Fk2L4>CU@ zP^P~|l8+kR@qYvl)nnvK!4`)LN<(R^X{oT^&(@71RLTp5mKmKC!lh8Ww!__V!8%84 z@7tpkFZ`R{zFMwREZ;c=NQ8{Gk@e1E{0X%W?cSpO`P`vQ*B~+QbI9+=AWm?u;Psp^ zWcwkgR{14J9|_mQWxG}^!9~_gw%o?6Qjlq!C~g3GaBFxX)bq!>X0y}ANru3s(OByN zyFtJWhJni}gMr}P2L(SM@u1KPrxeptkm;n;z*LbyLf6<`@+#b?4*Z^;OCMe2!@2I; zYONGl(qCJH(9~=f?&izf1Dg-7Xz1Sg1KDXr%-}~XJ$c&X>_$FHc}QRenhM!u-^up|PIhVbsmNAj#(2L(69035K!eeM*JSGs2jFE$JA&I(%`rhNx%Rg*RsG ziTqeX7Lq+QUw_>zE(Mw)KM=u>LDR{XdgsepuY=hVciT>SI&8367=}Y zD+t>0kZ5=K+F$2Zwc&fBd&0J^@+|voxHfLHjy6{pmNw5P0(pJtni_K77lb3^HZDCC zjt;S*{2xBB9mX4nbZ@cJ_-;Q%Rh)?%fA7;&hJOfgiYVRwL&tsNKh{kffF39R`>*#E z2Uz~ql6w8z_j;?v)f#AlHJXFIjx*f3s2qDN!vXX&2;0)~l9r@~|7 zP70j&OscNU7T?RvH*xmY*HYXus|RdX3oCv^B_d3yXk%VCj6S2Am&c%0>n=p~8vGZM zWx94LPw9A{PtxCIJL9tjzO-(_Y=T0vKcNP|CF}a5#<2U#pu|1j;@JI(@c3a+vKWfM0o-)^=7p!D_({GRvW#%j!FgT}Vqn2l{V+SqRFq-or!v2AW_+eTw;tY<&p z-~V}izc_QwoVjM^nmKciI=n9>F8B9* z%7v;j_f!#P$SJ@ByoO)mi|4JDw0MPczXy}0A|6dk8UN%RVpulsm)25(ShksJ@nNC+ znct-mn?{XEjP;VlBO@TEYe@ys5T{$lFcMuq20{!?7ilDI|Kx@b=#WBWd^disr69O5 zUD?og5Pm|td(i2nC}Eh9E^s5Fe~d4dpj?vJR3+@lPbKl_3jdb>%KbA&IQ9e)YGiOj z{Dpscl2k~!#^lxE?{ZoF_24MC*j3&qsT;Yv$Z>>b^q&j#9=`c}h-&dfSV7SuX&t4& zHgvns+K>6F+wvCxN}lt7sVWW{=}=1V$nqgWF2S%0DaPP{SDzghL_5>j`jA^%idmb% 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 @oo1S z*Yt2bB{bjIGL>ICRWnWXA{8*CfVv~u5n?{{G~wpR0-_SHWA!p(_`SEI{05w~n5!bWDhwP;hKV-R4G zRYmOVvQ#`nz_DvOO>La@pJnwDPy2SM6fyxBxB3h=zqF?^_VWcb{9UutB%vcZ;ExZ9 z^*hPo_-?b2<}y#+7qs}tT1K!#9Z^?+SGwJ8hPIRIMHmh^>$-k+{c5RVI)XzPdT}6} z!Oc^Uv75(eU)`1TA&+B?&)rw5qJJ1~|eF%Dcf_z9G()X7^Htc7g1z*NY zZ9}Kc7+N0)$FlMncFy{%(F}84MFoXHk@cX4fG%xjd7CL{(bL56R`*4JBVPQDW!T3c zRDnnrq{5e4%Cj%&_ihC4oorp_MS3oC#eX|{+6v*TkLFhq-=+Vz6Zn{4cG{|p))dOQ z6|eQw?k3Hhu3;|lDM3tW#~PjN9{PSY<74?RHl{i_tn{-9fStl0rRwdokQB%xX-hdX(gj78n<=T+D5 zJa)f*uQ1TX%+S}?)7(0nMafNDnr|E--6p+6!pR}$QWm{N`Q!OXWb4ESZosr;{|@d%MXPv9 zujdJ?QL|O)U^PTB^LrW z=yY>IDr)KLL;LSotdyEabb<=Ovk!D|v z98?O!F}f(ZFrT?}>Q#cC-(NO*)-736ic`7G24ZsmtrkJGHQN3Jd5rG>V|K2>j>V?N zGF})fj@-P%Ar3F2qjle*qbwefJ9o&R^Pc9nY<_6%`Ixl3Ng<5~0N_j+d`2Bz5KC<{ z%GKKI`nQ!Q5{CGYF490qKie!x>S{kn_;h=dSXEdv?{mRFt_*vp0g3vMYefy7r0$?% zZ@Jlc>Fk%D{k}OIw0W#Bq+rSm%fcj#Aa+*$Xm9Fm{B;fITyH)SbBXY@y^xRI$8eTO zm1FnvT;_dQ5MaHg&f|EF8u-kglVpN4i7ZMMUi|6^)U7u=*50^nt;fG?p4x#<4eRHy zZM{`eTK-j_kEP&_u0^y~06HopQ&M~)@f8jL#h=Gbe5@v99_Tu}Dw;d7Ur_fgt`gpQ zhiu__VuU_sTi*+o%G?2|?@6m8 zZwj&BeU-KTi-$r!9S(6Z@`1Tz=%khzbBwavmpQdpbo0%Yg@nJRe_JY!S84GBu!$?Q z1yIo^xeOeO*t#6ihfai-A23`F6X+11Uu~!Ec(Sa|6l-kH9?rhQdFS93!d>WOUEP$u zTFMJ{q*eQpb!cv-G%KOsSEO~LinEt#(8kc-;aNdMjp9FCL`Ssw*E`{ONcVgU;?OkH1e#k^G{yo5*s52nZknyf9n@d$IGPSr&OZoZ3 zV5PifVs&p;{^?yQKmh)}DS;wx;d8A(FsOG%BJ;G{+02h$)i~hLa#^TKnSJMfcLO|? z@rWOekDluRxPiR+8SmS;UEH6MOr~153wQdvU}`i=^0m7vq6eZdw268?N@OTqLR)@f z4T95Vp_?$M1>pFc)tQIjsLlT2;iF6;-z;1l7#}&FEBIZkgPk7aDAdat18AzqGBPN3 zt1kD6jCFgxu@)Hc_9m&g_OIQC*49YtDRcDKLrw!22Q&11TK42kI;T8Ni)chXR!#Ju zJE$VxEQ_^E#rZv=;i{oFbay)!DJ{$*KwSVP!eRWpNduZzghk&idvhk^$CV^_8XpA4 zv(5++fHhn$jJgs=f6E6uAZ;QG>amsQ!y&ddIrO#Kh(H z0h+7zHY#uzrgOhW=sBR{NcN$VgxdxN$vxiq*Sq$w9GeGry$-G}T17kz`LvDCJkSFgU*3AjM}oTBE(hkTBp@s`(h6u?5OwNX(r2P z{h8dmPAm9E0UfEd+hR;LlrAo9T=?q{WbxgiO33mPR{Dp^e`rvx>xF_!r`Yj7hiU@Q z)X+0x9bT${@+RObO~327;Z!z!mVj~jW(VE*6!jc7srR@BmZiEw^Rby*tj0TnokP)7 za-2msFtN>*LkHe8fC-A6l$+{gbe+uiT=&O&Qk6$@hOj}^5IQ6guB~Sa7Wu>D4KB>C zPCFgk(P+Mz_vmtlBy8pPm6I2l&61+!YhiqdbS-<8WvXeNd3vKl)O!=ugi&2FUUnZtW*>#pGTt-N1$WmH$|nRx7| zX!_@h+MO)8Blx&m{PQgcupCoYFCX8U80)!sUK)Sz*|%h{mYq*WMFg69N?Q2&&UKMB zZ^cM~SGKnGN(np?d#F>A=}muq0Xw@ zD{1l|;7iktUlxk!P}rczFM5u^QK|8E*y4Kt;mh~D&MT}(Z%- zOM&3xdBuz3C+;!WCw4WFQgVY16+`5zlu)QESBW;ZgUnatQ>D`z8ZMQd4Uhm_8 zrao#ak(R~f{af#caidEYK2&>Vv^0@c&~keMe@~3U>zx4s*zKomIf-S%r$KK6A1y_( zO?_WDWELf@5QQ_A$&cS%N~FMuTKUsw{ZT&~huCbCQZ^{ZzKhOePh z&oGgpbmuSBe-#M#4<>zy;5nR&XB0#TAe1Igr%AcQGG(fQaL_)uUl(B>@m&%Tuv6-3Q~eogOD%Ve~O+6gMu9H2(ORFrfK|^p)r- zjE6WmhEH5_Xr029X-A|#k~G4Th1Gu9T!5)mxlF-4GMgx|1z{@l`bOgC(ptF`}VN$^xx5`bruV)Z-DKQw;v^H!Jz!570@>2i|aME51Xp=qeFgZHf>?^5AhShoOzNMNSW=`)0~c_0yA7wVGB4g;tWOG*ntbJ-BfWSf6ka@Kz*RA)`7-FV3uE-XrGv8PVq?m-!&zh1d%MY z2tzT-PfuvPU(8@=iaDk~syWpMYb8fLqwMnY{pk6#()XR@&ofUqy#ix8zxJ7qoDVn9 z^lY+lujQct|IeoBc^OsfW?pO~ZkP2IZ2V-#eMPj!KLVi=3jM67esrpe%8Ka@XYql% zZUP-3@H*?mSUWC;xH-wH*eY0S}A{{khL6cxEoL8&Y#Jd9F(*0cr9TM*3lL0 zE;ZVV<{#C%lIT{YeebaKWb@5ZFw~P8->N`;L-u_*` z=65f>ZNJfX!#;*eOqjF`-%qxS%WBuU+cLI*LkBA?juBNX!Uf%`1V3b9&d0a4vcAb~ zLB=L({L@;em+94bEFd2~0)oGloH&UCy8ee5-4O=TSZ}sd?cI3S`Lu$2USnk}*XqmC z;5h|D%LC!={-O@8%JSpr)n`p&f9-A7Txlw!6YGZv7%RhCx*!m0Pppo~`c|QY9a>*I z;(>)SBPxoqebvqFGW46X<5CZed1aJDT@nU>Q?Ssw>1zHUIcH zR`;y(V}l+GTt%FRUTW+%t4HOlJlooLPXehj#VHvP7@8YxSF~$K!z-BrD0;%eg8yjt zmD<(!f2HvQ32%m848^~JaIhT;UKXU2$#EUc*XfuM)5WbEVY5l&zwPcd5VI%sLZ?MX zzaH72Uk(`2%-Tvnb5sIh1MHb7aFAnW= zES`Lp`Ye-lSfw=An^0`B=8$J7!>Ma9>E>NNizufU+6Y?yMxPqTg+DtvIUts4Op9bXMSJCjfv>yXjozw4K#DpT)X-lcIB;oGRw z!voUgu0Qi=A=GB?$BIkyTr&bQ>U&$inX?1ms>DrXe62jM zM;e0QSZSo6J{>MG3V0Cv62gj5JOUso5*?HoJ4!4=k^_*G^>C6Y7I^V7C!>!Rltt}h z?`D|hbaNe zmASs9L5KnttAwf_8irfz!NI3R+u_1?DovikD_pIdEtYN)my=A6z}sq>miqX$RJM-x zTLJFz7t+z>@u_noQq6RZe96KhUe0G){E%@nPF^gf55$jxPG&ppPcCi=4olqJ^c{XD zEcnW^=|9~d3Y*0u)FiE{wdQdzY_9`1B)l8s#6q=O+>S9&M_L)%`kosKXS@9UaMLy& zfDo<&L4#1S<-Om3@L}84wejU?%$=By&~UHQY~<8-v^F9zSbi2S%_~}9Hvc%Xkz_EN zW9>_fjioa6BiLMLbJ+P$VKo)@Hyn(o=+wRaLwbsH-!q?elfJ`|CCpqCzQdOxzPt_u*TWl zG?0INcB5$@P}Dst`Tgx0fWuS(GAtqrb-@#pnR=2c>;&{}gzk7c(X(%Ge2(3zoW=$_ zdhB+=gq24x41M~sy(+^2xPJJn`rnyAudX7;o7p9ZgQ$gb~w`7`g!;=<>5Q>$5c0JwCZw+GW~g7Xl^`Pzu(& z0p4Vf_>d4l6N^ws;G#3hA9t7>c2_xtS%b(AhT4z2$(oL41pMb1eEtjnXIRmMSB`K%SL=_4cTC#o@LZ6sLqEMumO|udt-ultMOG4 zQD3X&lR0z6<3+xZi6?%xQ#cA=c7L=iRU`eg^A_e_;zPG-x{>v1DS5)Is+joN1XtGw z1`D~a@$y&F;6{REW4e9dTiJHx8-|}d%u$O77x1n6Nf;p$G;6XM)zOy5^M`bu8?^n$;)>f#8!vvL|K8 zz5@3$pNh9{E^kdz2c-0?LQs+7h^9#bDn4|G1`}6xp*0__ZZ1(+@7on=4H&C;`+t}j zLshGH?WVosmS?Y4eR!1N-0BASYq+(DLSKv4(N7n26{zK8=-QjD6_zcU`!f9Cn2 zLtB0Y6R7~1AfL$(5*6J6#@sW>UIl>E_$UP{1v7nEEb0R$BHEz5$O z$CfkQi!2OAHH;5J>}Tg3W=n%*Z7w~Z8@wa|=XQfDj9LjY$5gp93%JT-+)`A+S^%V} zzCUI=nRT>!t8nQYw{gAd%fEE(9Xw#UOT$J)d_(&HMda_#%xr}Qgp};ajIsxJ6P>(G z)@?1WvIlj-`s5L<+7?Y>3wgL~Z{d2D0bgM@*fN!SZ}c*{)^rf#^S?!`1$vxN1H1$8@WbuHe^J@ry9OpP z+Z{dEo%A)d)!Dgf;HbCOxtT=Gj@nS^rq0VW=;NEwjhwly2Qt>;F9!yU8 zHLL0yR9u6|ggxI{sFtc??LMfN^$T`83taI{gS{P5fB&L-&n3MFR)sO2yHJ#8I7b%F zcORB`nY&3{_wo%qsrt1)NA^%w{-Zeqxw#nOFmJ>0;D@VI8U5=Mjwa?`I;VxLOeDa% zB6e=^ieO+{Vm=t-v#L)6ght$27TPpv#O(4#%axk>mGfS|R3Z1O(6=c62!KbD_dUHmxD{VlLtryS(sMD!pnspIGt0hkEt;W0{R;;QH6*W41J<9w} z<$Dg(ZxDqQ6y)WGVk9XTtaZO%=*y`u_{?Kz$!AD*;mmo#qUJwo5s|)O`G->|&|*a~ zOBwpo2zI#Hq0nx2X?2ZL#0>U>>=6?;5=_^h@}6Q>lEX0QtBDO4;q+oE&WK7Ej&|<+ML+(`CC|IoriJ^K z#)eg&*PHidaK z5!G;;G8^KpDJ-{D-GpyqZ~fo{uORx1OOpH9?0D&E)mU^FqRtb5xg6Di?=Ur%7|kIh zt6{jwtge62%d{q<75P?${##sXa4xUbqv;={{?x z8q**fg@iMg8GlwDXhf4LG34H=(ojQ@nN{&icJR6Y7g&{19L^+{>L`|a?**CN%XYS* ziW)DL5jCao{J_}VDcx6MA}$d=fkoFqOb^(l&n_;BV**_Ku^pc#8>3&x2zz8mAgxDU z+5L@QE!OFdJ1-LZvrS8@q`e`$O^K(3CRTd4idb2MmB}xA;)7nFHfNF#jg<3OJqK=z z$)gp^soRKelk0XfnO?$z?8mA-@LpxOlzm2eG9ka3}s)i;4%Or5ny#1vdPa_4D8u(=uV>J-3pDY^mT`JH+8WNh)15*@!ps|tfbUj``#qp6K}dVwboXpgN~ zfD}G@Dl#>6Decpwa8?(sDsR6uT5m2VzJLJKR(X*7T>{AII)jLsVsdM^nrP+!p3S7}=loi5Iuk zAE@tf_4R_+GQMCeNKNhfS&Gu|9XECeB>mNS&cNvjr}l^%J3>|bA#B4{xR`rS8t>r! z1KExu?g+N!CEi1wo&S1oD>t~(LTGjD8(HagL{xnEyWrZU4H zR2Z$UFJ(<8bibCP;QKO4s%D)i%OARDtpzFO(7F!-2MN{`rR}j5_5kNb;M3ZSJ-GHlPklSSw@xudh}) zkiwGT4brG{ne(!D9DMj__W!kaRS!{p(OSA292#kG2oa=1X;4CxkQiE|OF&w>q)TJ~ zX_W3Eh5?45q(O3sA*G}l%HhuM;rJg>d` zv!Eb)x@k0K&cb(ICtVIaJ`Iu3ehBPckaE=r9GTp7j0C2!wM@{NF_LpW^?ET-R9Dz{ z;p~(2@7H|&grf26hCF(Q1jDK%c=Czknp|wBC{0{`l-2jn`BVn680T_}f_9m+q&AgP zjBsG%P-wbh6*4Htc_wmq5TBOv<)Y?+Nn9oG*>K@Bpsnv);;hlBN~AWu$X6;c(zB|~ zXi5tUu*caE8MvA&ncYS%!)20z~kO1#f9IJP{d z*TXN&fA^YbfOYD+%3zT&;LP<8ceZ1x{S?e7mP~+C8a{9XwjIW<_YWAin#7qET1jTP zT}i>LmPv<7t8{w0&!J;81CUfiUzCii>n6~V5$~5QvPHd>s2Fv?p@ArUU2>Eo-ZRIzWQW-uL~Z!ACwk&W|)Nt^LLwGY1;v_6?1KT_|7TeRW^cge5Z z$%K&R_i=L`kbv@&RA;h?AadB=^0D_LkQkRS5!nJ`7CkB!d8p`*kyKgv0?vTca4Pg;YWM6O<<*v^L+ELoVY_gZRL=a;pY=u46okWtZytXVC0L9r9U3mja% zXPA1dueV#S63U1k`8CG8_aZpu3cWg%Dcywj)c7MzFs_!S2f_rCR@|a#R|7xou-8m! zCG=BVpS{vpSqT&xAofUX8 z1PT7Zc@vb*AtG}31mt85&2ZcpI2kj3bbeuA=6Jk%!v8F$cf;~=e+GPZ(xUnKa ze*$qm*d*;!9@$(+yy^{-N8)%A*Xaj0L+T~Pvc2UD6lMd0u+^SbrrQZ{-r(@ zZjMEANK*3cl}~nOU8|ImAe{Qr_ucWYul1kMnu-J=CshmP)hb!hT|O7gW0FyZzy~Zq zk?)LLf|cSk){}RbP3fS%HL)_8h37Z^OxmUOBU{1e;+^tr!eubv=vMp9KHJDmfTXFB zW0*X9n>A28S@(ubZqb3p)j6Xgd_^bFR_)&K%0XSA)m`TD?$h%sw7~KaQ5y4a?*dh| z0IdROcQoAw;t}5}9rq?)D!r|XV)v{&GsePtUa;y{DV3D5L(_?S92RbyarfTkyV>P= zm_NS}K9OyopUT7!3+I`iAQGHW78G`fr04zAvoTKR*sc)3Lc?ikoT-Fc_d=P+?&CD1 zMVplkea>6`+TV1HKGXpTY}o72SKUBsON7hSQH-b4eMru~9QAcLBR02GpJ}-zrpTf) z6`HU517ohPB{({XktFXwC?aqQL|+xas&G=2OmB zCt|jY)i=Bk<8tD=f-o+4(&w(YD5^1920$EE$&VxcRi_hx&;s+WY> zA&g>F>~UJmQycHmgYo5sC@^#|?+MS==_YPkG>LHeKW=&%5J4bJLDn#*Mro#KYoPLP@!6I49NxaCf(&(Z2j}U# z$n3J^zQGo5RNiR1oKZkeR?~viDYc*57mn~~@mynE3quJPO|Bmj>3JOc-Xs0e?g>j@ z6U1&M`}DOB(D!oo)8AEGQC&3@Dv9V5w*SOVMsu4aN=(P@pVQd@UnDA=jbG-&5H3*#TD%D*cfymi%%aOk4`2tC z?q*H=;Ywm1Z#GAbp8PKY3K_TY&SW;kqD`rTZAOdV&X`&5oxitWImY=DZ89X`weng< zXunVJHr~jdB1=?f#+<%5K8wvq?LS-jZ|v`GEZFdhX1^6mVf;4IJ_fW z;pxi;rHjeBR~ws0wz~92ROSf=*V=W5*#s5x^l?1pR0N-;7&n^Y>{b+ATfV z3Nd?=rF6ZuE9-f#PX4F3aa0_9XMbpDi#Ups+@JkrWpoAzZ?D#J4knYC3fAo%clbIj z3~;*XTOv;cKEGLEs0SLWe4xT{l0Mn0H9yw33A_LiMgU%}%r_W>MsFd`rD7Y6ch3t$Vbnk{Iz(3wL$S}=6YW&>E8&}F-@Ocv%2v3x)^sE?X9n(W+Rb z=V{dLa`_P9>=bYOI`mbrYH9=#j`P;FN!-QHjYFu$Cx2N|D!*tHChdtiQ@xV?yk4AZ z)Z*IgxtaN+R4>Ox*SEs@X>tyN{{n-GuN3$c<9iy5Y1T=nrv5d4JkP|N{a&Q^&QZPK z1^CdFM!$>E2>l_3AGSwjaub`Yz#io#z<-q?tk0~}!h~b2!G9^M`?-2Y?PXDlWF0oVkb_W~qbC&CSnR#p;|O^U1G%5Fr7J+kJ_U z)lR%={x9&9F1)qXD$d6l!iffmm)+&hLp}wzahJF9lLCZ3Hz0!bVdfO)E=V%(z!7+q zLe5eU+nN(zsd=4Qx{4L#kDs0X7V%A-Paw441J;;StK#TM zeW7xS4NQ;m?in8gkBEs8`*b&(7i!V(ir5N!7LhxXGIU%zlYlMZ;dqe(D2=fT3%d_$>B5Y#F#IRa8&6zHc(sBJc^sPc%bd56{_onziDGv|T+sRuW#{ zwEC2@;v|mc%P7#;YfLH1T{iT<-`)t7Sgi0ja1${E&aD64K|(phG)O>10U&fm;byUF z;0<^mLAlO5_cE%6@+vdd)ksmO#15oC42s4_?O*Xw7~!|NH?DZJ_-$U6Cd(@y=da38 zi#7Iph7A~)v&k_^o*tsUKF8XtR2#tEKmGcMK2n4I1ON}7KA!64iOG>nZT;5$!n(#d zCr#~1iKRRJZ1$&Z4AlxIANlDeu zdr`*)$k<09nn`AEsDE#v@f1DLg0B(9zP_Fx~RzHmL@z0+xxJvVmJF6>AdblNzX?EtD1A)vL z;bDp_u4XEdi@sBb20|as{w%!}_%Ray|v_Qq6)C=ps4a@X>Y>68;kd$y_ ztwoDvditi77N)q+)Uu-2IrBa3Dvz7r@XWjGO?E&_7`?tF(QhvHf#lUwv zovRwKKDdRo?D&Ss@O6J-?(42cFDKT}#rTyWlQ3(aNS$jAfZbHurQxr-4J89!;~5XE z`MTts287>E66PXpgA11~7Y(j%($Q9LoWu(x%#zDy+aVW!%2eoTCp(-ZXf-sw%8 zZYQ(tYCFTHGTOVu)zg(vknF0q|7E{hY!C*Pz6{nAft27`17VJG=F&t`ShQ~>dK=%1 zC_mcgl)gxqcng`Mq+~+*xqhjC-2kv6BdReoqN%=vTG`ruYn;LQ&VhD5O)7173zIbl zj^xfpbP4TE{omLvt+wV4pFl0V#iEuFJ?DnG@7k}%d{`6-!v;L{lUXv&rHy&%niNys z_7d%J2N-?GN`Y7luaMmat#5}_f)JhpemXAK`r|333zlrb@@?f3a-@0tzMErjwEu!v zAeVCl)wZ3=2iF2fi(|3Z8{1XHuXfMH8)Wut=Q$BWV-M3!rIZhL$Pl@s3;o*(dy*nC-93`4xv?aaVjo?LLyCBonuwl_!s6x<_h z{@zs%Bd(=P9Zz{%L*H7^s;r;n1;BmX91UsROk(`lMaBHyBUaR}1`P4tyBk=$+GNOQ zn?3aTCw3|AlzG0j;vCDh@)sfz@CdzJ#`XrpGm_VNHwqr|5qtmYJrGC(KbM_U|5|HJ4rV#qWRdhHR$;K33RSFF`-f6j`?jr@+8>9vzU>=&;EA03!27w1B>p8`tG z#a!dpL(-o*^lyCEC8kK~Jq@X}vMs$|D4ai=q68&_xB_kmgj8DkhWe7yqgE=!(IDHa zE=R2Fpo3?F@5_&`w;S1(UJ*`+rK0H97XwIf3YW2C?(r77Ia_t&+a#x4r>q{=`!g=m zil`tf@@;iYT4<`^%@rJ;e2W;r{c^3md?Dk0a`oI{ zUW6UqZhb~wOGB?;@J$f-IPl_O+Hl5Ncmn0Dk$y6p;Cr+ujy8vJ4Yyz}8B8h6&$s1j zsgf1*x1M|_q%9uFF4N}yo3A_@fSDu(9dTK5d2I}M=^KYaV`hKjwV`vAH623#%C!)C z^D9{?R7bYGS+u_7(fe@Uf zze=Q#5A^M%Jfl(*8Ab*|OyYOBPbe(&mrZqj_qV3WrT7s!ftxO4yt|WOcwQogJt5;M zZvtwZtwSlxRMv-33lRiaD^hvLQej;68S=D2;`wd_L5FYH9R?o+u|piKG$VE_vo%=N z$N3bn-w|E?F!=f^C%Fg6MTl}%y6iN0a4zg5y#?Yp*j~RvL)(rP$ybOY`SA5j(QYf7 ziX@zLFHx;xEstsA&R0{ekOyj3Ay`8cjX3iL?i}fhLvjfp#!>}d8LU4cnq=pdaAHxE zPVS$dI!4AsK}&^#Np9bg{0i0ag_&kr(QE&b+62eM*0X6C=PTMx0`&sqgX@N-fZ>2O zpUtymob5hK&EYKs0e}s^yauycw`pK7^}s$h5f(=fQhM9DlS8!xWsczWa2eZ*c~gAZ z&D+*eAY`PR?0Gi)Teb)2axrjifh|@wmJNKcu*64DSHMFgWh@j$tXE3Jd&iqe&dSI` zflg?J|Z9b@X-&2?8@zgO>0X&+qn5$;Q_;n>N`JLg!f1>!`f9%e^4ad8vikv|%l}I7J3Eqf ztMbEO&oKAz%H0IvN+T58FOeBa5eJb+g5_aryeV&yM0=WD^64*nIXHi)TIU5r8=Db1 z;H|ra4ZS4xhat zj1GV1fZFR5whQ}2ezm9nJ+9(|(lj6MdwxZ!boMtSl6DKjwM*p9KH9B0wOk4q=V9>DPoDe$0QnKjVZ>ciN^Z38%yln*lX;u#@=J^AXvdp zbN)Mf_wHV~g9A~)u;2IlfaAS)@9q1|?9A-!tcFHat7=uPs#UeBR@JInRjX=Mt*TYE zs#evi+PgFW4NZVyS^(+VQbdSK2j)DyhK45p`cq6=MK7Y3_u$QShQ!M|j?BD7;9W3(rzF z!}HWb1Q*F?_sGwu@I2);`I%2pMDUgXD4x;Z@w0r{F;8XZT%^~)YvDENwaItjcj0%E z@8;hR)7YZ~|9@4js;O*J(o4xB{9h+MLP8&c=7gBT2$mC^CCHW$SW0LWc>7F!@XzG| zzYD(;zZ>si7(7d92G7#G@g9Zu&6sFa!cf(eG=-+;5`=z^q!#AsT9FLdui^DbXou-^u{*8M5z@3JP-% zDn<>w_g&IwAM0kKp60u@!3fqaQdXmJo>a6f0(D*?s>Duai4!YP(hIO8Pk8qiD}e^-N#k_K?P_6t&jOM z&r`Sah>wo#hao;2r6G1{j{R`JNj=(ktp41NeXQvJs?p%1H4gD#CfkE_rZ$iDve2>R zztk#}0kRQ+=~wLIY1rq=-bGb=&yBD33v*N3li~R$m-ywR@k^tbOLMHM^Gdpgs$-R% zr|dITpR2kS_gJZE#7wY}J}-Ql+FtQqsz!tN-Z;^n*UeqXi&()TJ~L@0e8D9_87YeU z{67up*K9j5UQ!5T(OLHIZ>)7`Uo>`QM??lF*letrcvVe&g$=*k^natb)J2F?*%GlV$X~APmR%>=fp!J0><q z(f;{Qk|FO=61*XJ{g8`S50c=~t$4_~9tW9MV8EZn;+1^$koKe>|tFS8Tj4LLsV zaWcIo|9i>;-e_?8Rdz~{vUgb3O3~QEXi-jT57M#E8R8Rs5?UV#h(tv4KEIy`PwzE_ z8|R|o@a}NY( z9D)MOpiu)e2&gXw{*IqF4!44)v9^$!Xa{XuIYRHA?(qG<>M(VDZCJ6OKJ4ES2G`C; z!PC15@H{J#-WLj;dV(TNufTWoHaGS8n};bqmA%WVrbc5rOu8T?bv$eFN6A`Wt(T=H zN^>nWj(tH$g#_EZDHs;~UJpk8=mVYFIYSddOdl^JFf!Bzk+vq_qkk-DlkYb&5`nL` z5fK&}_@cWTOc+%YRuh3dx;LDTGQ|mmTnNZ}%72_PVdYAh`C26e>NTdLa_`2PlTKX6 z8d_p`1%-uvy(oUn~OnDPYbj(g#@gK zK%p=+5P^-gE>!n4fQT?l_~uIwSUks{j#jrX#*on>k=+M0N`&w0)x+fVmybtSYT$*c zgrNAwbkLgz$)2D{Wx&oXTX^$*gCzh@h}p1nV=y%-(A02ulYoIl8#F5k$;$$KHWOnJ z)T?a*ZPFcK+%GlZ_}}5w&p?xiQCfLVl22q@iu2OIld6P3*w~X>`43b5;YI2Vwd{Qr z8u)z!?c;O*L=wBX0rczbL2Yh*eQg0j$_h;aJuOYJ5Q)HDR}6I}`VeMl2u+Pmprx4^ zbhogC{x;Sy%FZ69Iyk{BXJ=UA>Iz%kJYlzcb&6f?)nJR82mI;k28+o5=D0Y+bVnx` zYwrNx+S))bOG{{PZVo9XCJ*pGi)Tp-RD^yPFC}Q zOJ2Tk%cmw})u;tIzO~_H%{ox%R}YHH0dM^3LN2-919I)#l|;*b-o=&L?_#l*;3ca9k_5GBV`C%eWo1RR z^&d_yu-45T_7ifS_40wcK7R15X6XV5K7h zEhW#c0{MEnS`ZRo@nYBz@8%l#Azi!i9~AG5K6sB&wBz!H!yC#Zdm|y z>*zwAKCzCLT6VlPIR>?QC^1zA+1S7~qD8ZbW<~Pk5lvgsNnRS{ljp(bdQ3*7c+lq5=iX{eJ+{t`5_ z0HryeIW|Wcoi z^a(Wg{8xm4A^*#VN%aWnvpzWaiN?oNf#vh-LqG#FFe2K%I1>joy$yMJ(L|%6W;Xf{qC!C4%gM>V z*0o}D<&$ZwavGY1@PB?NvnS_XQ`oe+5k!Vtf`Nf@jgRRFb~-xLkzG!PXdJ5FR1se# zP@}&lWY6(!0C&9u;as&)*yj=n+Z>`{gIx@)wT*?{GYPqZ zg!v@Gqw;X))0ECK@>2oelU5ETCX7lbIrjX~#?a?;cQ90;@zJ)AG%|!~j!y85*!BDu zT?Hvn)1#KZLCl#0#B5nl2+t?-yI`b6BYt?a2onZ#G7{nHshBU2GnL*SkVkwTB{d+V z&ykTItFZ})TV~`)HU8a8u`qdTE%0a@xNt@RJ6k3xDVC-=Q2R}jXlG^( zZ@D_XNc`PfLLfQ8PHBz~W>Y4an9#&7oVMq`^eWUNCSU`&MogTYPEFpMrY{cPn%4)3 zmcmEF^l{9+!bLThaeM7-^gQGci%*rK$u?im%zK>FQegM1(&oxOSMpiG(_~`q#L&EZ zM|(X%SQ9nApT0g#JbP3%ODjMDab}+p+y1O)C~S9(Dlua7J~7v-2{`B&$lo1~;A2Mk z5|fseUO`g`&K{2Fgj`Z*x6BIBm;|@S_u?Ii>2rtC{(K^r1voS=n(YsDYjHwoa-f|* z!p_RjP@<0ycmJn`)^c5Vv6O*}OB z?5W`X0Be}iDtwxM?Af0<|Y5-|CfP| z?E{|xIOZM>>uqCUl@bzuOmsH!;k?VRU<=ubvJ}c4G=;95ie9B5pC72erVtbL$$OmA ziZOlOwQdhqCYU;|7I;-N6c8WtbezRvx_IM}Z*9TL|1SeIKHB)FJi=h3UB%J(e0)dz z04>%EG$%)WI&&|3|CBoqZzaI#gW;W#TNO4c#F#$WH=i}tT2xyVY z9U>VR5Uu}(r4?NF_7%MR|2kgz)u%RoMKX0%VT?sENVFBBx{i(}+`JGi=Kz{QXcFDJ z5OZzavM?v)ScNo&WaTfk3ZAA+kdeR4RU9xsA9Ht5>+?gK=;YRyNMN0-J3OxnYjS|I zwYR(jVW(3Rton$~ZD}ycA{=Z)`kZ&XdrL5f4}?dBnAY;|i6%3VV@#=)3ey z0M@cb)w8|D;PIUV7}CEQNF+jAU#z7GgKcc!rcX`5%m1(ACDHmv+`?gvZRH?7gBey4 z;4U`eyyv;o>+*P!T0|5pWL%4Td0=OFBy#QeasP@D6=LH38tC}urmRPaVSHiCs86wvB*C7LDPdIid+XFpGi*rV3;;Fo`CR{x1K&SrdBBBg@A0S~<>P zaK<@OK&;AwMb5+m?=s@{25<;3)7iI|(kGj5CClqKwmnje=YnwscvIsj|WVDYCrMC*S@@)y=ZbA)JbZ=$`c z6Oy|JLyl7z)z}Y^+(yxq+*t;M;0Cc$C?VvB#yP9XY5p*1V^p2<{$mpl%8ZFKT2+z3zF!I+`bsQHvAv0?m#rqZk3vDk?5i zasl|-v^IwWSeXM0kqNkyD~$-@C~*0Blk=lS`I#XHKT1@H(f%(VG%b_%-@7e@*3Z;n zJ-E3@8-BkkTjzkJLBLf`;&=)wcE4VLm@PkK$80|l7n5!PK{SPaR{IJ1` z0{wfU2~_hi;3Tpk`R6;k(3M$Z9UM4mvRJD!)WDFI?JLP7Lb6lMUR|cy?Zc&7-B@bA zR1~wNJaO4H7S`BRuJ9)tzgtIexPT_{-`hilJdy#qhNHlRV@*ybCtFfq;De3=vY9xo z=b1@q9PO`0xDz*kh>Eo0NId59j3j0cnmW0@bs)jmnDc)G3PepZ8emxxwh++D1H`0x z<0B>f6JoC;;Xf7at`4pm$HKZwGV!GhxHbUmEivyJ7c{M179iz}a!yf80~bN<*&5mj zx%k0mhgordQNh!cO&sm7{AT6IBEwc<95>{J#2#jACts{`#oL!wW?&z~hKH^$Y;yCY zVaDffgz!YeW0>$$)a>fwhDi);s$>&je3*&FocBCyN}b{a0N`0_!L$2`aOP0sUF3Ll zgg?x@0Au@SUyBb0*~Zjw`P-8!x#`@@rnKHB=6Yzz9uiF(tSR}FmB@CIm^qGOZd8a5 z7ad(#Zd?~KFdCzT5dSsYCL_RB>&h(vE;`)V;Ge(u=5k2DsnjA2mEOD%lfP+2AXb0uQSwxEBR_eMljt@({I$A41_reBH`|6k=;;M!I>IyvJ{-06V6CK~fDHdd z+#>?8qmm1Nm!2u-{o?**d>-+E+{4tFfA)qiKu*4QKY+3Q&mE0%A`*F9ru|D4fH_m^ z(h`IktOu;EuMZczY6zUzeI8z5tj%5GSa(1lF!A&d2!W-R7=F?7ftv!ynzrV+bkW2uypJz73T^p(M zTO@Nxn44nXWNt=NI??6Ko8`3@iD9r8b}ub{1= z3BDpDn5ypwS0w)}LHO6AatlC9BTwZ5Py&#Hm@0kkY}E6W3mb$ZN8gJXBy%uCFCHX) zE7Sfe$&bK&wG!iOIS&eYDck9rcN{?UCC4B|E=A4e{bUl!HhQj!MD>FOQ8wl2E8FQk2SIn?QVY zD=2`pQzyWyV>=KV3D*pxAhaOU}r-}nkdd-*A6}&t)&PpPn zzK9zVOFw^FKz=s5ynF#kyLW*{i)J7;=EjrpJI%Fppo6XxY}Ic>9A&Am@u?w$n3CIV z)pLX~1)2ZZ$3}NkU%K##d2b!sI12d00mnCx&Iw#S6ZL%IAN3j|SIgB5l1+Yzo@FNX zlbJsl`4J|*@f;Rh-5(dl4R5s&+ZP+HK_@m`hwD2u-LXwTdS&SR{5iy|Sq&};u^`gd z2Mtz-q9E95=|MML7uaJE47WcqLC7Qmu+OGs0hnPC38T$}1)qOxd}Ue-M4E~SU-wQf zN;#zhZ;_Mtug1a74Z*{atL2s;$Cy6^;=Cs*8|1NnWjDtA45jjq8Fc}-$JCHLm^~rk zPU7hDU!eR&5@1-9IUW%nV77Z1mQ1;}EvqkU`Y(qGUo=%1}z)x%mvn`tl{{OKt zz_cFdDt3*5q2HF23c&?(5(}tZJl-U8!e~D~L&&9K zA+0BR`(iAtTiU1_a`v5bgD|^Jwuh^jc-f;l;m;Bzl8wFFLc!deOT6No;k5=0ImcQz z@Q1c{6>|Y4q1}o5&}vsUbjg0f9XZJFEiasdnqPhnhK|C5TV{kHL-eY_8RCfZ@2N5% z!#D=EC>a4p5u4vpWJod7vSKcvAA*&lSredG7mCj#HeNkQq$PA7+>D2;>v52IB@WZ0 zi!K~(3~QD&oPnH0?!HroD3ka`p!eoc(#{gYpDKjn(d~GMV6dwuV2*9a_kg_Bjny<}*w#Oeq*PPd6L{JC?+!-J&YbiL z{N6?)GnlJiN9FxhWbENN!bo@@u{r1wbMFh|S~T~bPrix(ERkp+696oRHfLI0x^QYA zQJz>3dGzoa@O3YDXqDyw(UDdV5@-(obxfePpE3CP8bb{qWALtSREU*DJlqTqk?R;D zcOxQb36-)nnf))?vn|k5E&E@E0O05NKpHFou+|d84t?d$FcQz1RV_?QM+6DmwhD-@ z3LP#!p#s1lZCw@-ipS9P$xF_0bRK~Sf?uC6K*xZ)JX4PdLb})<{*vGvg76+IDvq;K zBIa2|L7=W3jerH|*}>8ZoG z$eakBo9Clq(e#my07}B2Y5<2SCkJj)Tu)sW8uj77L<~_jALaV z69Dn!J52|0?Ef+VJ@jwB^dJ(gh{U*SN#KY?=y>9+?U%aTNRVq%03vY^G5Y-o4#5p>CV2HjreL-+gwXrFZ-g8w%jj9i=qS*Pe9#EPw8uOvip zzZD5LjbrKRuwKO6G0@bZ0?^H0F0r^>?ge!S&4H?wExw>v3=oSYol21h%%d&Pow zowlGI*ax&i2ZJc`d(ep<0orjBKr?E)eKRXCck_UDu65v?L8RbbD-zD>M?$oY zG~i&Vtq&v3f&^r%i0E!wL!JtuWne)GuM<5Iv_9JinjLq8W|sq?(d`ImbU#XPtQZ_G z)aY^$G*TBF*D!X(P4>zv0Mr52S@uT*r$TicU>4Df!{OcGU~k9OtR2Npf{Ca*#19D7 zVHHg!j*)v>GwAyIm4NU95a;%@BaZ|GFAJK^z69aJH$(jN{V-t4Zdj8t3~pMcK&C;Q zfP5bt=uiEi;{{?3Eo%C-HmqLZO&ZyqJVxBaSd1i+9unzQt43tlKrh_OctZLtod zoj4{Wyb^T0_LTSmLPD1Zz8v=tEJ+;$m+e~!?)hT@$-i2+0a$8Eop^t-HO#e)5)iGT zq7zF1Onf_oX_I+i6tffzlU9SI#U{{ew+nQ-9RSUeM}Q)Y9>+nW<)1G!g1?JL4i6hv z`Ux@3dGjLBe$u^jF-E%Gk#w^Q^&&t-5N=?m@3{$6#@@ad5_?J!Bew z+!5gdF@5T4nNWpcprH+uECK~2tH|g^Od#AU3DZ?f?0SQV!*DQlByb)9rf$E0iRW)% zT4NlTHJk#bF^fQwwh_c_cY}zSJ6xeS{#K*M3DEd#GiXGQ?TH+oFd-0ggG%KHGXkKO zbv^DIsrjQO`3cS*iG+X#W?*Qj4Q?(HNKUYY?+1CojtxQZ=vF)x5Zu*WUr*unV~$Wq zT_?E3%F@JeXlGsSQaL>fb4b{>ML>7~h_8^o^}c|(<&9>G@5AR4Pr$rZli-p=x`3D; z0+?MpP)E9=3j49A$Q6ze$G0+-OZ>`2nhmNWHUbm-@2CJ22UDlvVD2>G@oF}8f6|(Lhzpi zgjWaVo2{(X_iw;5=gyUalj%eY)R0g%N{4W8dmTt)>zjS%S5y9v-b%|Wnv zb!p(Ntq&{p>r*EfUoP6VU+KEh5F~R9t_7~%t_^g`%@vTIjV{?QX+(!l)c1|fQ_sVg zua?7sI=$eQd9r}8>T5|C5J`yXvp}~V?X!EvF|e(YIKYVcR#EV!c@Wq;ekCBb zYWz$_iy>fa`xO|PM}wY$Cx~>+K}$=-%m{BHFevq0KzcgO|G|?CUq4LVp_T(oFl%xh zy4}5o5@MGIu0#kriJf4Sz7MREG=zhOp%BcD2x&+i;)Lxbm_m8^&}#Ps0YN_$TF$!# zBfnV-JA(T|hEcqLyzdIksO_uc0U~1dpzYsWWDnO^(X@j$6+iyZ#$>x@VCytg3DJqk z+ew0e!n0Cjga zGs%0Dd=v!$x3A|Do()`-?Bt-h5lt;EaA+I>@mn@Q^r~f0cklpkiirYKuWBHY2zP_w z179fGv%17CG$kgGjkYk2**P{EIzE0RBt8qBZak%vU3_wWNVJ%L7yACXAJ)eF3>TeR z35Z-)Y|v{&>mp%aV9LfGJ?<>^zh;%o4;byx0?eI;@yI_6jIBF?Sl5bJ79t9fjv*zz zCTrqMoA5)d=L80(Aw)>J7n#@%0z>O==L`)Et;zqzr3!`01VHzvL0lGs>NfzV{7NT>^3|J!8Q z5&`Mi==LHHT5Np(&agUYG5-$q8GQ(rG#djKT-v}rqq6%6Xy-T4mR8wDWzm9Ye|}!o zZKGJ&T#1V2{^;0Rp!tn$x`0T`9sL#1B*ZrWeZv}LbZA4&p@CAf$l>QA6Z>IcX!ZHE z8Z~Nol4EsCBLJGRxT)^UNp-5_KTW135Q`Il(}yC!*@>0CDH1{KhIIlm^3kmzAKKoz z2~jJTlM!MdI7Ehnu9=CF2UQJ=S~a2LlgEN<%RvOM-I>R9mX}Y+kBKf*&%uEuKM2J7+=_8*pqXQ0+k=i0 zT)`m%Km@_i`m2mOq>*EFOXCPL0-(2Yp1)sVPD*xh0)Q3ZzUt|&aD0igifeH(=Uza@ zV$dx=4>~=4N~1gRn>Rq6LH)rgx(S$iR~Muzvw;pVjwW4Rsh%P3mir2tulu-WkTRq$ z&VqKoUxAVR*TT-g{&2%GML=R@IIJHAHkyj!VSRMmRhE4@ZxI8lm5{9>qP=rh7KwiX ziD@Xu%;5v)3=A!jWm=yv0K>t^y7!}o4I2g`mvJ)}4Rr-z@?>cUbnTJ=|Dv3fr^N}t z#p8`RYko0K-}JBT0uq)5ouB2>x}b5JH$uR#Bfupg1|+uDpslAzeGKM1^{p&uUO)eQ zHrgJOE|RIJX!XZU_+iL4*x2MpA_%D}Q!TL2S4WXEXvv=4iU`61Jz*{tgS*Btu)dOn zLzA4oP>|TRC$aOnicecx3v@)>Ad6Vfk&G1ml?#B)*SU=vHHttkmq`G&uL}uyo15|i z*IFtQ0M!1&hIn(zp1Y@|()@Zp0m};=pFE;9fKVWCvAcVVG^o+N3$(d;LvSn`U7oyx z7V9hCD8L7f3Oy&DfCcIQgHzQy3JASaw?3We#lA+{zrDz*#1b!mt64ATV zyru&1R44$CZzq6Pbwx!sbY|l=mTdc%7rMN9Nk)lh(3P4$C1i=pd;vb;Dk_>U%7o5S z&cno>3*kVWFK7ZDW_ku{DI7uzO)+fK3ltEZ4>W^RRg0)7SYnp|bsW)&9Y#!_U_y9S zK{aCPG;JIJcaN-qTZfi{u^~5#sAuSdq~{612r#tm`-TWW9CG=aZyeR04c3%R9ZPm@ z3JxXY&sQ}9tXm$yxe>dNAZVAFAtYY~LAU2`AZ=~MhzOMipUu7oKMmdtU%591T@8il zhjD*Zwm%!UR4kUSNSI}p3_gy7!N@Y5Xm#!mL3Y+=aDL}pD7di`3JKaIM{xeHEi&Yp zK~e!QviYVUG&Hg)a(UP=e+hCM30_juVgz8@Iw}Bp$^-yw|EDzN7O)DPHW^CP#HB$Y z@dNI^fR@{{pyifKXtNL3_CA7+S973q#&hWUlsMY2->9`lfI){VPa$=A#W#ich?{;M z#D)s(Z=xlJb^472WLF9U;y73EXwZ0vG;py=r!jsur);gvVEdv`l=uv`FB%P&W{QGm zI9f=ZVZH$L%?}L?Web2>>jJc7CXm6#l>s4y{I8UmKu36a{W^vcTECx9xOYM4tQXK? zgOIGa+_BlJEJ$0Q4K21jfYy7nq3uZ`7}uXdmq#z4>$5!Q_Bx+a8;C)-*Ll!tw~$~J z8A%K8fp_zP9AdcEiGtCceupD9yQ`IMfw@7eEAXV)w>FVrqR-tuR4f+3Z$rAltIOLs z0`TWHD1913>azQgw(dT(+DS}@e;&~a5xC5;J1b0zqe}BXg@RR45ix2v z=v#2>7zGYkLjTu#?hP2xe;sTI9|CtxRZpzpQb~1-=YD8l^$IUtZb4+U`&%W3L+*u5 z9P%@mI^ru%&Y6zb7>w-(3!LDfpr9D!a*rPN>W=`704$r^us#uhCwu{Tk(Ed@IyGcp zj?Qo6rHch5E;GuE6-+Z44#RKSY`6;y7Upah%qy=}uIlK*r z90P(B}@`d1pWo(6;6F~BZN$fcbpAaDpeBu7_ z)o^UnbeR3yH}LoBiJXz(@}33Y;-DBYP#BD?S}6p8j0Q&5U2^I*tcBI0OD6!GI!R5S znd9r$F3d@}&z}c4urrMA=B*+7a?|S7p!JnYLL!$DShD1OI;)r))NJMZyMB2h3l_usRC9*C zD<{A|8>WJvw<~CCX@b3tIqX<6hGPaLM+b7w(>JUR24;;x->7DhuEY(rbu1tH`dWn` zmvL_k_jdy;&UEdUt%AMt|^C-#rEJGgT=DWU!$fb~hS7LcJfH{ydt-?eNk zWE@=y4Qf~C{4c#}DCAw;E*F9^#6QqxO^C99b%F5bJ#P<*zeZ^VAU>W>gc*F++|K3A zqvQ*00eE^Z0V2YOWLdG?z9}qqPK8D8EntCn8<<^y@VXoK3hqvbV8-jem?Frp{IvIiQoghLov9 z8f@`&wSx=0{*Vj6zEu-IUr(4Ntf3Hqt9Eu`AM(>6B7!P)^#p)f3`1b`GArpHwg6l? z9mO$!bcp?*cJjD@$Yn));3>sQ&?`7NXVQ7%N&wnnPUnpbxMWB?rE^FS}T}ayCX~w`4T2J9t@+K zkAR=s{13kGF&p}Sy&U=sUJqaV^cQp=xfi;OJq#Tto`lb4oQGDku0RX&49ynbquO?L0hEI-bM!cFl|I1cynbtoZdDIs<}y%;H-=cU|f0v=;~;LfkYQp&loBffX8Rn zfvpwSd6uCFeFBk4^DOz@fxtjkA+A&cfKG#DZbl+q5W@(-{F(JQUxt;N+ht}7h+HP* zq+(Rp2mp=H4fp1LvdT)s7R}#uY0+{;C|I{L5vlFM8;{g1OR8tM8tW(>bjPX z?j$UsE5dScJIZ9}(8h`Lg+5(76`P4!8g#mo3#luFomCv-rk;TCp=%*!@uwp=$qO^U z+Amx{cx9-b&=ZmusaFNBBBAjw+c;rN{C(1}Uc~X8238i*2xBcrTe$wqxA0=}Na)=- zh#nIWRErY7;Q9_IBpBAKCFeTTJq_UD&4i*yw-aFV>cEZUGfNqlagB>wGr?2spz>^NpoJnqQdz~4-9)7sh)GI;`&%j_>LW>xx(&6tUYz^--}B>a9!@W09mwEcBT z(EbeU>NSSMKW+%FQPB}k%p_eyZt;Y`dft!_*?Hyi>lSnv?(k z$_BLk?HeWvh^`8T){cZs7X;U-=)eeeHD4mOZ6<_AiC#E!n*z(Tezp_z17iCgg^ByEueF5j)2T%L8t3Ef=*Hg$&2rUPqV(DZ)OdF0~QGWU)j+3w>`x6 z7jEpQ3fekiXfkGx;5ro_S>WG&9KC=3GwF*(RO2%wM_e8;09-7jqvG6-X{^r&LQNtp z%(=R{T5tba7YNtRM!%grq4rngGqdDmIs%kV0AgZnFbZV)$Mm}4g*no_frs?3#)OwB z>_Qp|h+HNR^QZOSrB8cc5He&rC5@528zjs;FZh3@0TJfvO?wCkQ4#|C%@tm&5}@(u z-9&LI$jhLyj|bct{T)XD9#8z45M5gIw@;H`cqKE99*iFfq19dG=j!Na6)pY4AI=_* zd^Y$SuN3l`X+#87=A{wlx=12sgKZtD(}=@r!tehIj;Rc2JZcvh+Hfo3sROUX&jr_dpPupFbgj5$Ua1Jfo$hQS|c|syy*S+W9!zu}CfulYq9WOwug-$>Z4aQ$LG1V^G{?W& z%RK0oS3q-xI^WHOR3)>EP3K$%ORxGgl~!zM0zt$ne&3q0T(FopggwX8i7o8g(n8PJ zoDgZf@VX@c`z!pX^$<1w5Jy>H|F_(cO>3*-#)7opdGD?5+1zpwfY>P~!OX2D=XrXD z2T+rTF935oq|p$mjz|ms>f25JdH3)J%(-vL|KIlRAd=R#KFKXp?-i>y*kkDp89+DszSHtFR49=6JWTjONR?j=t7uouT-z}YI{s| zvRuS3e}S>1)K)aGc7ou+ODg;%S;DN#av!EF&=!dxpw~o5Uy>m}3pp|&jiC9G`_POi z4^$v%eu(uYlz@@MA#Jh#KHWF1bLI=^{<@gL2IRfq0wn%(lm$uiZ-aAi3g>weYI(!s z3Bv>eu>I?%(Wz?(}2m`UrP|S59~i$csteUVOwTeAouL zGpT1{4vqTGuFxYv$agD=@50TBEG=3^hQfte)8L{E`1`rp)8X{&8E|a&EI2%C9_*dH z7`Dz`27k_73-jk~0S`|_wxvT*Fm%Yu685(;6ciA%wfJ{X8u)h|&3P7_0lqYRpg;gl z{n!^AWJ@8xZyKp=1fblEkF~y=l42i3em5`eBpB0ZDAdtW10zAE1P*<_^w=hwa;AQO zSBJL3jwuUp*`f+fuMAzEyrN4X`JKoHrUr!mum&WSj|Rn zsgJTC(#KsunBssYMw>>8JQ{6XT?qJfB(2uR?{fk23!wGE@~f~BF>)*20*kp93qySv z(;^;bwrd8%lcV9M+5Tb8;#j@_{wb&p+;C0%PG4OJVsxV}0QSQE_m#Fcy%!B&_KE88s>7-|6NI9Uf#0`dom# ze7X*s|14z%nn60c+~<|<79)I42JwDg^5^I(b%H4`@tl-In_1*{I~jK|npvt4DAQ;p zC?v$hjQq5TinRQU{4CIr-at{vl>Z4kMg=5)~BgtDIhj_1eY3x<*66GqHh=uZcvDrac6CIK$dr?ddb* zP8tDCiO--RGk=JgUYHPV`z86^CNPi+K&c}@Orwz?nG<2<>S}2%@uhW3@w@d*ETDQ~ zPl%s+;iEdpN%L=kp)Ggf3x%P3SVK5JtRMKh2uB80&?i2`-vd7vki0nHZ0h9JNu29o zvEf#i{uT5)8(49h9l0wHxJS0;+=q{yHH=A%hglt(!Mu*?u)BW`I5hZ6I8FS3Tcd{1 z_&+{F_PC)CQq2X=p#mTlYu&9=$1IrqZdKL@5Ywn5j5d^!k%k5N-HBk0hV%xDqLvrG z2p;QthD@0RhK}571`He`!k|O8nobuBxLBUwdFmkImz`khrkF56Sl0|DzQSrKjX83gzbas+%R<^6Q9AkpZbB9 zEnOT>1wc*yPqfpu8p18 zvZ;XJa!mgf-pu(8-YgssZyEK{y%6s=Ndf@ed1K+t3PSYFzeE1s1(5&G3Me?W4hk-8gu=^P=wcFPRTZtYX0FIU)iE-pJNxka zoO_EwnwK(#;VrziF{l4SC`t)CzDT^akoOag$7 z`T5a=5kQUO3e;5!1S_w7J5 zf!<;azuD0v;6h9u+!+S{$ACB_5EWqszfZ0s*b=qHDfM=I=Y!^;DCxk`B5~eL@M+eE z&JnQh3n4Kugp~TVV0XVB@MNNJMiPOe!NuQaK+naO1=lVK3BO+iQ`Z`T@5OA#`(uCP zd=GwqgtxokSY=3W8m6+6T1lAsYadS7(b&})+TB;a)*JJ&_|NpN@Qm#ucpe0^Hn^CO zs60JKS66Es`RU~8NzEUe!z#1=&ossl&^0#Jz>pI90TBe*ULD)vOmI=``ZaXlUiL%= z!qP$v-w&z|_pim#I-=HA3P(#@R|h&hDSmT1Rp@Y~{HjJ^{5hcKcrYvk{C2#KJh(-BjxC{IHlh)yM1$-Cd@yckEV?q!jrS> z1>{x+TvPk-)LPg)eF|ihW*~1Nz;%$psq}oHR z<|OP`H9-s8u2x^O@X?wO6&>6CvlqbQ>0{*@7b7lTMFxSP@=k$`K50T)=?9#Hl*i z)d~f#q+ZbIo4F7(=_s9$M5Un1+Xrs+&owy2=L41No5#sTIrkFb#Qq5QZG`WS%ugOK?d;db3mj>}+*m`??@_{V+*zv!@P5;9?s2!x_6c3W%*P zx<1BSpwf>3&6eDU{){26|1+L*@FnR`_R94T;pD(0tb?%j#5 zNGCyU=oHq*0O@(F?8#SuC0hBIl(ZHR<%LSxo z@bJ_c2oJ1PoITBfZD4>PH3Bnri>s`S0S1d|3Wr)1w8%4~K;RP4;6V#G+vI9#X~6P% z^#wfQ3jkIsd7hOByElj4^siwYK+d&yaG>VTJB2@$HO36W%sLbRG=rvSNN>2vtC}Gk zB4!T%CK;UnCz9H3vg1MD#;U~Wc9nSjhOQOhcRU+W;|~%uBMrP$dqc{+>o921DcD0y zo#)d=3y8(Xb6SHYxI`gffMl{YF@zpr4e3PGfnSHh_Y05G)MNgACC&$IS=Cg^xAhnAq{KN|w~F>s4)U8-=V^j^58r-PjyHFM+~;+a8=000qy>*u0j z{`7i_$a!vFUJ~RcaHdNo(RdU4hjpQ&ELY#N1)>s`rS&Zf#{+b17qmlX9(P%u885Q1I&?WA6QqVPoN5D zB2~AwggD|KC44>%nvC5C@xNb`FV74gu~iWJN5s^uMg(t!;651SZJY^(*Z-n*F}*yb z8LrCU=S>9Qh!O!H4sXG+mGE-jSXe=X^qc4q7?Bc9^Xu8Z1=i&;H&qA(?#P*P;BQX< ztBL?&x_P~B3YEGe4ujx8bI80JE8r0usQvREC*#`RXWcqGw;<;^xw#pj ziC(Ve57Xq)APs4HG)kJMnVER)*0q69@HDA_I=o6G6@d>E6u{l>yUn$-ms4+lUYHJn zGpqHw{YFG zxbf?^5Lc_ZfV8CsIbTah3?i0xWMF4%1joPcBlvCxc^ioKzowYfIVC>h zAfo;AkFJ3HL(8B5ad;`@A6W{wHcf`Qo{G@~BS<)h&^k%6GEG}Y8=_V&Q}lg)z90aB z{ok<99MCluI`(0~<`6=(c|(6w2oJS@ufK4I=@V-af$#*s8r+Bz0~COHkwxDRs{REz&(+06Vy7YH<~vV*scP~`G1;7eBSG~mr=s^tr8TvXf1?cEJW7J* z$=nU~-le2Et`BapJ`>_+o`+iPhJwW0w%F7QFJhJ`TVbRU(??OeJ8%7TIKO%#GzmqM zN_gG!KwQo0kj0z)l4HgwC^)`~L+Ja*Rzq-o;UpR&psqJ$9^)Rb49_lZhF z6SL~z+R5~PU$kfCCy_o4oG?z&?@wL;J8=-r9<0Wpr=+%zGN5zoD#)sjA;peK8F~{oZmt$eYkd6;8AjpH-V!pVY6H&zf?86sN5N@UR@u{PP;Fh zRrm8}_Yib zdl3L^a%Y%G9A=23GwglkRMa7KNK}pj5&_K%fE5NkAt~0eHOL-pv}!20)neT*VnHgVcTXf_w!Y%^^J@~(4~Vj z+&mvmJL>A0C~$G+?kL`*20FyDXv?%wg!tnK$>u}Bxec7`NL9FbU@0U;1#&`#?}9qM z?oe=LE9CE82(M@V#uWhacfpB21^2)p@7iB*cE=p}ZAe#0Y!U$9ebpLr@Hvzmi+J(x z7MM1&w>;eeC&DHS?FBC{ZQ&fZVeW7;x+r`puXe4Z{r~(8q;JlGrn9fmLc}6oEgyPK zU45wO;tNe`ML|-7WN04P9MXcCLwsm7IM~?{4bKhXV#YxBjW_{UiosiawHWlVr}v=S-JmY*q_-h6<#eI5BYl* z5^c}je^wn}szGXOklchQD^Q4rebZic-5pSHY7M+uG+tr;Ed3u8VA`^hdnhf?xyA2d z?%CSHgpL-kdHe4o*3TJ1M-%2gZ2|-6JHU;}^Rp^w(`7Mnt;eVZ{!j;Jxa$KEy z5AJWyguHt>aR1Ux_;J8s@;+E$QeyHfpRZ)XO9_7%?VoWa4puG-IIJPn{?0NEVjbJ} z=KS&-duG_w!GYReo@0LT?Lda6Jn0P!eSvFdqrgDI)c_5K{U{(i2Ze=nA_mtNV5&0v zTm+VC@7LP{?p}(O=N++ej5u2Eu3Sx3x4JXj*}Fh3jjswAYwy-3j&9`4zo+656J8g_ zeAkWcZrCKGE*xGrm2+JjiEyC`I^lU6W&jEX|GP^IbecQ0h$ek6RUT+IVd5KO^Yii7 z;xS-qteA1Ou3v|)^N60a49q=n7Fjs@SrPJ2>O2E3PQ0Zc|MYwCaL;{0{#+<{@PZ=e zRyOqL*qO7jU3xQn8YJPrJ3i3%M_=;ruCToR-&FgYoaO9=c1Dz{O$!4D`))CR|H~BBVw7{&(zFnIUZK=Kx#D>==rpRWx(_!xU4IJBBj>}si zZ|f{#7R?v_uav;Obm?eC2#0-Ex26XiS~o>-90QCXwQJs(<6v9Wt^o~4{tWt7!Z0WO zzljx0?>-y;{ofrq`LE5q2aon=K|%I&Ir$l!*mDpp%oTGrzCK1ofyD^kA0H?HH!sA% z;<*jBk@LKfb29DE4t;(w0brbA_9)=dv#V>|tqZZ)D5?CNnM2`XAGD~k*BM4JvQ3=ozi3I#?$;`5h#$W$f)4v zEDUFAv9`nIqd~O>uzv71j@ff-!993>{-J>Q4DyLkM6r|_rp z!Rkaa?`qKQ-d#EQQ5e!rpP;1|+0HJeSqIcNgT32Al_uOFaFoHtH?A&Rvj>N52U`mm z*fX7GOX7?wp9EzEMph25p8}x`Ys%y5s^DaA2}d_h6I`P_kwr*8a$qMgH8G&ijrRVy z?|Z-_*=#Z&xFzYhA9{kFHFpNRIEY1Jh^ie8yMH$n(EX(|4);XFS3!O$E|!# z!gjjA-_#I>e9;;*RE&102##-_L06EeOMbkrtFh zY+UvyCzRaxOLxILl|_AuaI`B%eVNA;5^(6h%+0kG`z_Gcdf_C6!v*Yf*zkGP+OQ! zfDZ{I+sy+@sQulXINsrnYQvB(i=V724KD4O5B<8QfvK@{HWv5R{bgWh$W}3VhA9y1 z=KKte>Und*kPI+DP|u?tv$C1b&hZD3pCyd+l!R@o z))7-xApq?d0bnUMjQQhw^0mtYuGre@HzdbZGd0z7(U306Lau$VxPM8F83mXHGuDod zR8qZ%|LEQ6#)X*Id5?rqq0OrsakgA#11m`E*&0%8P6$qiM@7<_F!J*-#5ufw2Ws2$ z3GlIyPXqz;b1-cBQi)bB4RH4N;_i9ybHDcBXlF^xENvtErYQD-??|LW8XxWAb=yEmQ)NL?DB2_$BjKz00#+3%N{KA7w_ZG7Fi$RQb* zFx?rs^ebDS^Kx3JIndA9gy##u)4K@}9?CU+ zI=760=NC5%NKl!9X%}d=bZikN&*8y^E74*8uy*#aVxJdB1e|eJm9k-KU;;zahr#&? zH#j6$g`0oegC~cTPi~bKTUM^6)t;GqsaIPn0F3E#<9rM(o7VtSooge92$x(nDv;x= zs<9nnETLj!WhFI*n#S5kojDwNCpR;Z3IUtU7}~@|6X?V`IqJj70})*EE5Q>Y0HML$ znaSv|dT{s13IP!+6L3N*^XMwr|L6aRzt9T&{XD_gP(mY0w$^6QGBF%B&l{;SrjOBBv^hd-Z^LVUe)(jRS7_Vu z{edf|qM+Yb9n zk<3=dNSTL=Ep?tzD9{JspUVJKq?b(@0%61~Vn40=sx;83-U4* z`8?%;tvCX(?$OjlglhweaPDZ|w$Rt#e&gLAIV0l^F27})J{7|8Rn-Kh5P_qGg&0@u z)LcBL{%<(E$<_`O@l6{F{7j9{Z(fKdnnM^#YEZj6Y+X2t+Rc2DR1`ctx1O%RLQRda zcqTs|fhG<;jhJ`>8QXzp8;z8JBOFSj9>fEJRg2vFmSSTgx(2X!?EAa3rMaE zn8=p75hu3Z&DK{1z7wpct4W>Sqx&LY{I7nKkt;IJ;DlF&H+}exZ3@w-5GF2(#dPVK z--hLlmfXG=i;gTh#KN@$%E+$*Th|0Zbx&c=kFAv%bV`qg!|SFHJ9@p+$)FDjOgl#N zZ0FK3)U?4+B&Hr<)Cw!IIon%7cq2a=!P>oSEM3=&8l8U~1d<+|TeR_qe%%)QyxpkK z@cRHKuB?o$AU(JxY#y>pJ#&XRxwn_xgQv%`OC8%U4R)^E1QNxb1aBq!T5nGsh=f^F z>f!zqe#jNV88_sPj6)UP^x-!qDn!#H2yA7O7(N8G|Jo3|3sabJ*pufA6$ceUQTQ|k z_U{Ml_^FpI&p-|xYLU77Ia zj(X_~?}ER#>;yCEjzL(|wg~fq7R{=Efc%cT5b-CxK1#CNyEe8dgp(FntKHtxQtA(Q zdKx#{w>9JhPIT~{;?e@0r&||epl26X(y78(Oa%G|n1_R@!Aqxn2UqqkEY`Gm7hsyf zzEu-paIco&>1s#K8=q=UkQCJje*UICHES>%6<25#XVW84+utT8&YItUf*zm6lV>od z6Mg*WLy!)qHa>vs#rZA zm`{ZFv~%fibiF}cUk|xctPD6AW^Zl>?ZZ33`a#>^+=S}_k}Jd2X&G>5`8~)z@c{Dg zzdv&af!D{0v-?{z;Ktm0ux8*EurRV@YWm;M)zywiPT1Lr(L!MC{*RjJSze9r56~r) zwzgti&Q`Ns8>iGGyTk8d=&LX%xqwgVvfyQQB5YU@04-ANiJhrD8o~fa1}r9pCd!{g z%ih?(xLh(uQ3y)X?rh+(*Zy8e*A{ed6A#{=jw<8#479bhp@x$W3{3f!o0eP>rbu)4 zpB~AAyo~aV?g&6el;?!_=ul%g6~F)Tqz>Ay8rOD}{<<(JaF3QQViH@0T8Q4FcMk z8cK+Sz;L61zAkm1F|U6>k2F{|<$E}^b~0Suw}?(im4{#;(AGy2=-iGuuw%&>__<#P zhzP0$Hsrf;c9(x|Y-nj}frEtu#MF&}Ilbo7aOR~+cZDQZ215Ls^D(S>AKu)~5s>xu z{Tx`mU@=Visor=La0-xfI_gNufg z8^{sH?yt}xPjwq(3NiBo=-A-IhASfkJv+O%Ik+<{6Z5{y!yIA*(~|C+!8yBKzr1yPZr5o`zqeV_t1)TahpqS0vd*-I?{#HWcS(I~-K5(^4ZV~H&q zjETLXsHoTrY7i_ah^Sa%kGsqH&u^Al?p_wlOHAGQzVnHD%iKHjJ9W;SOC8&l=iezn z3WYoyRZduAKzeb2zYpKP+p|-1zR@H;W-uju_WXqFura+``z90_8bDRNJgi1 z64nfE?9Jm^M*b{k<8lpq#vi58?Z;kKJF2@Wioox!C~|tG@&3QO^cPL)6pKYNys(eE zyOJ|O?{~aA?cN-bjl3cKEzSf*(V-F~y;XY3xtBAVb7tt>-HTAwTAqvaXCbAXJ=OB6 zNgcvE(6~;qw0-Oz8q#tYsT`1~X3sT%9AI)fH#dj!Hs{jA3pa`ZRZa(zcAGc(&z%-u z?D$iOF&6CnODl3X)5)Ht0N~#>?hwuHwxZHw_>6eY$$Lyo{-d~?<64VSfe}J3TEkI!cKQQo@ zkN7-mv&IAndHFIXz<>laXyD#-;}2nnQTP3q8(1tWUSj1%5E$b0p~m#XxBub{(WaFL z>11^)Uj$Y0Rr6E;>2Hx?_2|&_qh=r_zQ3A0w5HmszV1Gh^vUkxz-M$eA)E3xU8Z|K zGH{F#=|!ofcy*U0`Z*gSd{P3@6zJlH$9 zg#zH$R6vrOufj6i`InFQJZqDNN3p~$zB=Q}%ptA`DpV*Pw)(r!?LQrE!hy+4E|z54 zl=S;=K*&oveXubtn;$~M23DrFtvx8bo~h#T^AfIW-J%>fy(Y)hpbg)LQBHa@ZjQOU zQsn&ldV5h?$^p7{;3|)t>=?U`s(YG>;WE3KmmrY?|H8s-y0(ouw->IFe&=2?d^wE_ zZ+9j`qi`}rgpr|hC(?g0mGnnWTJ-@Q=H8;HMh$r%wQJU-vxiR=J4a5i^{*#i;k&}* z&jawoWA1^0e+>9V6X8)g6PSGhdwcufCG$d- zok(p=$Rf2`ck~5glVtCv?Yn?FCX>#%WOTF2-tbI!dh6;-f!rF3bU7Z=Y< zhy23Kip=Gi)IPKWH+yirOGH;XHaWv2?c;MOYccBYT%iYN81R$#k)cZ$GE}NWMx{L& z+4&aapd>@}YNTJVi1ay@K0(&;bgJ&>&-;jO(UR_7D!Hr)=uy@Uy0Si-=M2fg&!3Hf z|HPEDwDSFR%zv4*5_5?;)vDbg;9&O1!T+}icRp{k#su6a&}z+05IFP8z)1&^8$OUM zm_6Aqcpj#BQRhCxbqM>mk2clq5j40@Kl=INHIszRyF&>Rrkb}eVwHFAXGb{jbG|oi zM|_lhi}aUol0G(;44xH=gWejI(UN{foCTn_uii+yvZnO%;qMOS3@8VE5#6N+XRp%j zgL!l#HM0`IS-dwH#C=zz++Hm0Hw{2_sX zbS)#>478o=w~&{o=>rxnOf!7-!7K(iChgBl9NS~*WO%a!m+r-Y{sc0VEkpY4yUcw} zpD@YHXiH|#vjX}j5VRFmlJo8vALrgF_$lQmC4RLyd(hhhhGFiMN@Xo)r85?}ybI?R zb8G{@q{$s)5hC?+a8UTUx!Kod16eyf%&qyVWud!eCef3%!5>U8n_%`})6c%`k-PvD zKKMMKw>RJVsVD?LN(KI~WVu=L-oB7WEu*6NF=TbV{l=T@w=0&wJ9wN7ty+=ODqAYqKBQQ`9z7_Q$p++k+Zfuue3&8)BYM%1t05_6{0 z>A(O;eeo)sQ_QW1_SnELX>ukIi)FmRgqxd^M~K*<2E|VejNiL0;=afx{oS;|bI^Sz z;dgEhr%y-u^LnV|%Q=)7O~UU3d-bA6d8V|4M^}EPX|WUef)tKd_VJ;#JqInpclsQ2 zZX0kQ7Xvr5{|)8c$>3d)j4H?C#|4#kCjHhO^nf|Q70a6=8>|!H3<`8pwC_LWj4fn9HEl@b@$OASIDR>g(@j=hM+$2Wa`c#i#rC=-CHzgE`V_wU{fUG-K{C zhhl}-2L7is!2}|A2uV?>0m(nd#QcI9D7^^V(#grb?%Y{H-|S6_cyQ?_ESyF2avE}A zzG%Ykw(}>W=!5rOo1{O$*R9iA{1`MtlgLPdX<-H+b9fE! zy+=knyJBGGfNtK5^ye;Hf)>mZH`6K;80v)38o3dNjgG@9DZ&dxP3SA}zixx*Yvb9-#ye@c@w0Wt!S7Aqn|-i)dW zzW@?vP~R%OSAQ3FGTZ_aJR>7KkCQ7q7YTPK4>YF1{d}1X|7K!t~PAFzF-FU^B zntK$Ltnb?KEp8IYuLt;H>Hz#V&*#zML*RXw1kDsaLnIG;-)LN?x~xU_uCx zWgScBi(t4O&gkg(httDcN!I3pqhu)OW(oLGG6s056zOLr6az4))91+$9B7jK?Ea7K zUZ$<=*~?14qc*hv0PcfGfnO-`4fmP$Ih}fp)_uDwZ^Ec?v)Gu|@KCr;9te^H-NiMH0iI%(w&(Gc$9UWZhZI|rMtdLhsxBXZDngwgtF+9Q`mnn zdE95*-;e;`i2t8Inoir-Y`&K;Wyb0zbsM%7QeUHXLrMd7aA7Wl$%8o(_P#CB^BXj| zUjVg=STe$Dh%i)9Dit+!IzB1T2EqM$`}A7Au+EOu9g&ZbSXamhze*#VNNvPF=VPtb z4kUKXYSno3=32(3VgP>{tyz{xDksyfFMLC5?zA||#V97#<*U2;kt0aI@S9>t=E=Z|V!q$h>#~ffTFejKBo4x-6n%pE3p;D)D{ucAC> zS4u1EN68-b=uoeCI-5{DE&0sUEK01^jXIUCO7^l3-8Bi1Fy6dy#ejFnI-Ws$Hz(^C zB`nxK=$!%Y3E->k)q!d?Pg;bnFO~Wt#A!2mUT_nP0QD->2EsR}%(Ou@_y&cH0Il#E z7~$K6b$6v~jd*y5+3$s+Q(lAeNz8x`InSIDVAaWmosTHZ_BERXyr#J{d5Ql!Y;WM@I)*GG~6#J-{R}u}u0)R(hExc>L)&x_g4% zXUhdT8r7H9yEmjn_ZIY>+rK%jt=XNM^js=;Cpr}7mhW?Tq^Z#Ao4_;+Xp1FTC7r7~Je@UzaT zhQB|>O`JlRhtudl(k|Na{aX5N!4jG_VImFb*PmKNHKV{IP-P!)3iPi| zZR@qAah+o*Y0PfEl`(l-DruZ~c@R!5nl`1Y((Q=sZ$o7tddL_cPvuIdw6D_s8m%Z} z@=?C2JtHoM_V!pvi5{)Z;1`tIdnsi|wo4wLoI(Af29krl1208VNTgP)=!=-oX;b3b z8*`?`ukHDEk3NMG-voS>CWrwasSGf8Ft!+Dj5WsGCiVZIhJlv)1_-r@ND&rZpqjlI zy;|rWSTI8C78TpYO$wa7dPP|J!Cj4vaF|V6p{?0OLcOBNG1V>19!O}UD8zpJG4<~D z4%H0~W&p21F0Yj4PGm`|y;4HXjbC{`8q;wswGT6;An@OrMp?CJsmVM7_>tr`eL4?a zK4gBu`7)JgyIU9~HyKWuGmC(I;^P!<*UK4TL!E(iDmIO$CZ|m~TQEF&gwavy$SW}j za#pJhT|2&YW=hPYxlxgg+gSi#tqWwphnE6*ucH@_>Zc^ES_5@ zY4eZub9ZhG=Sgca#}|wMDasi_@;fI)h*S>tR6+X+Rr7G8S{2KYudWO^mQc(}5cUDA zn7_yr`8jmLlz zCB_tEYm4ywNt@g(k~4zP2wn=4J?p1dD+3Tdl{m}*MX--g>1f|d&STl}^$awbjP5IB zpvB%!2c(1Ph$kwv2f%NzxHKR6%Mn#y@L4&3bB@8W(GSuyM_!v{Ta=)vwL}4Ih{7M zaf84+qXu=RwR6YP`DIfme@-mjogPDvX2#MlvnGHF7#VL*8)y8w`y0l(zMjmKH#?qY zVvJ?%+@XUp>-cG-e)W2z(X)b)sg)wKvojX-?`Jfg%R$y<0VAX&@aPh9g!0m}=-lTM z=_Jz@drOtNvyL(A0z3O{qkIEqH4SJsz|qdJk$4Xw?N#X9^hIAd55Hoby(&?jj z{un>zOwyE*`6(X_d2sZjVYL6lcWF+y_7w4|4>O61hfo<)QFT8*nmBeGE^qG>FTj`Lb@?RlqdHdp#w=N50ckz&0o8G)tRLer|wLsSbo_j zOu~14`Q`sqWX#H*F+>V@{EK%fd?!S6gtW&yOE#>NKxzgd$rf^e2a_*AMD z5a0Gz6fr|JCMg3n8cmR^tBcfUFlz_^&O@gQZxk97-Md}Xpgv7Q$Jeaj7SDjcl1cb& zjP^4+2~&#s2p2uvbvgb1(<^Vqyv5h|Y}%2(_p=H4Dek)KDWyu~?JiX+C#h7aiyPUw zYiid{OPo1p|B~4Yw|+HsR^sIGpT~bPd{j)|?mY)J3J-7N;o%V>0lNS^2fd3*1;F#v zWV}P+y9m(Z9i`GZK75Unmt(ydQYF)WZr>F|f zZ#MF9?hxv#dfmT_Lvt-V7uOVCE9`bD@%~hdbSnEG4QzU>)G}sOnu<+f7Z#ow&yIJ% zyWpMhZg@v)(q0t>Y-UK5^wF2VWa%O;(goyyh5O%V+qKF|1HH4xD_AbMb)@bYl zJv>w)o}MltI-M%W#YGjYQkj|Kxe3?e9=I3oiF>0D^o2grH=d#JS)_o+v*MZY?05$` z=<%*nX>W__{6m{HtsrFvEcn1gz~T?=WWf+9>j6^&Q4=4933xCu;8X&PkiY;YHul#n zWDulh)Hba}a0-$U|6QC{2yjSq*zLF%?umP&5A=mT(Kns}&w^(XfXB1qneps+2RZ0% z(%#mJwUU?tCW7!81Vac7fkZHv6#yPg3`kvo5fT`{^j4Taib#uK5BZw>G^(A(OtY63iR zhOovIkh%b)U=B!QfK)IEB(%^V)<_%|;06haF7AsytbyGc;BBC{wHMN?F@>Bl1ak-i zU - - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git a/templates/android/template/app/src/main/res/values/ic_launcher_background.xml b/templates/android/template/app/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index 8f19d93a8..000000000 --- a/templates/android/template/app/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #FDF150 - \ No newline at end of file From e9aa6872a3a1c7bee3decc0b2b9da3e93a9a0f5b Mon Sep 17 00:00:00 2001 From: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com> Date: Fri, 1 Aug 2025 08:03:58 +0300 Subject: [PATCH 37/89] Bump libs refs Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com> --- hmm.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hmm.json b/hmm.json index 82e458768..278572b98 100644 --- a/hmm.json +++ b/hmm.json @@ -18,28 +18,28 @@ "name": "extension-admob", "type": "git", "dir": null, - "ref": "a53d5916bdcb2e48913f94d9ae1d949b049dcdc1", + "ref": "02334589ff9603a5f483077a44395009644f6274", "url": "https://github.com/FunkinCrew/extension-admob" }, { "name": "extension-androidtools", "type": "haxelib", - "version": "2.2.1" + "version": "2.2.2" }, { "name": "extension-haptics", "type": "haxelib", - "version": "1.0.3" + "version": "1.0.4" }, { "name": "extension-iapcore", "type": "haxelib", - "version": "1.0.3" + "version": "1.0.4" }, { "name": "extension-iarcore", "type": "haxelib", - "version": "1.0.2" + "version": "1.0.3" }, { "name": "flixel", @@ -111,7 +111,7 @@ "name": "hxcpp", "type": "git", "dir": null, - "ref": "4e24283a047f11bded6affabbc9ec405156e026e", + "ref": "5a0dc3f644dc676a4a092b7e6c8edc8be941f024", "url": "https://github.com/FunkinCrew/hxcpp" }, { From 312b5c8812b4afb55409f8993c22a8df3d0d9931 Mon Sep 17 00:00:00 2001 From: Mihai Alexandru <77043862+MAJigsaw77@users.noreply.github.com> Date: Fri, 1 Aug 2025 08:05:21 +0300 Subject: [PATCH 38/89] Flair adjustments to the build process Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com> --- project.hxp | 30 +++++++++++++------ source/Postbuild.hx | 2 +- source/Prebuild.hx | 4 +-- .../funkin/ui/credits/CreditsDataHandler.hx | 6 ++-- source/funkin/ui/credits/CreditsDataMacro.hx | 2 +- .../util/macro/EnvironmentConfigMacro.hx | 2 +- source/funkin/util/macro/GitCommit.hx | 6 ++-- 7 files changed, 32 insertions(+), 20 deletions(-) diff --git a/project.hxp b/project.hxp index 1596dd839..2bb4e2fba 100644 --- a/project.hxp +++ b/project.hxp @@ -5,6 +5,7 @@ import hxp.*; import lime.tools.*; import sys.FileSystem; import sys.io.File; +import haxe.io.Bytes; import haxe.io.Path; import haxe.ds.Map; @@ -500,13 +501,7 @@ class Project extends HXProject configureHaxelibs(); configureAssets(); configureIcons(); - - readASTCExclusion(); - - if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this)) - { - runASTCCompressor(); - } + configureASTCTextures(); if (FEATURE_MOBILE_ADVERTISEMENTS.isEnabled(this)) { @@ -1288,6 +1283,22 @@ class Project extends HXProject info('Done configuring icons.'); } + /** + * Configure the astc textures. + */ + function configureASTCTextures() + { + if (command != "display") + { + readASTCExclusion(); + + if (FEATURE_COMPRESSED_TEXTURES.isEnabled(this)) + { + runASTCCompressor(); + } + } + } + /** * Configure the platforms' Ad Mob application keys. */ @@ -1684,7 +1695,8 @@ class Project extends HXProject */ public function error(message:String):Void { - Log.error('${message}'); + Sys.stderr().write(Bytes.ofString('[ERROR] ${message}')); + Sys.exit(1); } /** @@ -1694,7 +1706,7 @@ class Project extends HXProject { if (command != "display") { - Log.info('[INFO] ${message}'); + Sys.println('[INFO] ${message}'); } } diff --git a/source/Postbuild.hx b/source/Postbuild.hx index 405d0400b..e778c1792 100644 --- a/source/Postbuild.hx +++ b/source/Postbuild.hx @@ -29,7 +29,7 @@ class Postbuild var buildTime:Float = roundToTwoDecimals(end - start); - trace('Build took: ${buildTime} seconds'); + Sys.println('[INFO] Build took: ${buildTime} seconds'); } } diff --git a/source/Prebuild.hx b/source/Prebuild.hx index fd35bb6cf..6c3a57b03 100644 --- a/source/Prebuild.hx +++ b/source/Prebuild.hx @@ -12,13 +12,13 @@ class Prebuild static function main():Void { var start:Float = Sys.time(); - trace('[PREBUILD] Performing pre-build tasks...'); + Sys.println('[INFO] Performing pre-build tasks...'); saveBuildTime(); var end:Float = Sys.time(); var duration:Float = end - start; - trace('[PREBUILD] Finished pre-build tasks in $duration seconds.'); + Sys.println('[INFO] Finished pre-build tasks in $duration seconds.'); } static function saveBuildTime():Void diff --git a/source/funkin/ui/credits/CreditsDataHandler.hx b/source/funkin/ui/credits/CreditsDataHandler.hx index 844d0f4db..3a28aba16 100644 --- a/source/funkin/ui/credits/CreditsDataHandler.hx +++ b/source/funkin/ui/credits/CreditsDataHandler.hx @@ -19,13 +19,13 @@ class CreditsDataHandler { if (data == null) { - trace('CreditsData(NULL)'); + Sys.println('[INFO] CreditsData(NULL)'); return; } if (data.entries == null || data.entries.length == 0) { - trace('CreditsData(EMPTY)'); + Sys.println('[INFO] CreditsData(EMPTY)'); return; } @@ -36,7 +36,7 @@ class CreditsDataHandler lineCount += entry?.body?.length ?? 0; } - trace('CreditsData($entryCount entries containing $lineCount lines)'); + Sys.println('[INFO] CreditsData($entryCount entries containing $lineCount lines)'); } /** diff --git a/source/funkin/ui/credits/CreditsDataMacro.hx b/source/funkin/ui/credits/CreditsDataMacro.hx index bb1233b36..c49e34d2f 100644 --- a/source/funkin/ui/credits/CreditsDataMacro.hx +++ b/source/funkin/ui/credits/CreditsDataMacro.hx @@ -10,7 +10,7 @@ class CreditsDataMacro public static macro function loadCreditsData():haxe.macro.Expr.ExprOf { #if !display - trace('Hardcoding credits data...'); + Sys.println('[INFO] Hardcoding credits data...'); var json = CreditsDataMacro.fetchJSON(); if (json == null) diff --git a/source/funkin/util/macro/EnvironmentConfigMacro.hx b/source/funkin/util/macro/EnvironmentConfigMacro.hx index ecf4516e2..9a4e70540 100644 --- a/source/funkin/util/macro/EnvironmentConfigMacro.hx +++ b/source/funkin/util/macro/EnvironmentConfigMacro.hx @@ -70,7 +70,7 @@ class EnvironmentConfigMacro if (value == "") continue; - Sys.println('[ENV] Found a key for environment value $field!'); + Sys.println('[INFO] Found a key for environment value $field!'); envFields.push(field); envValues.push(value); diff --git a/source/funkin/util/macro/GitCommit.hx b/source/funkin/util/macro/GitCommit.hx index 04f173c56..8acef82d4 100644 --- a/source/funkin/util/macro/GitCommit.hx +++ b/source/funkin/util/macro/GitCommit.hx @@ -26,7 +26,7 @@ class GitCommit process.close(); - trace('Git Commit ID: ${commitHashSplice}'); + Sys.println('[INFO] Git Commit ID: ${commitHashSplice}'); // Generates a string expression return macro $v{commitHashSplice}; @@ -56,7 +56,7 @@ class GitCommit var branchName:String = branchProcess.stdout.readLine(); branchProcess.close(); - trace('Git Branch Name: ${branchName}'); + Sys.println('[INFO] Git Branch Name: ${branchName}'); // Generates a string expression return macro $v{branchName}; @@ -103,7 +103,7 @@ class GitCommit throw e; } } - trace('Git Status Output: ${output}'); + Sys.println('[INFO] Git Status Output: ${output}'); // Generates a string expression return macro $v{output.length > 0}; From 065ee74aeaaad1e35809bd198ba10f51afaa2bc1 Mon Sep 17 00:00:00 2001 From: Kade <26305836+Kade-github@users.noreply.github.com> Date: Thu, 31 Jul 2025 23:05:11 -0700 Subject: [PATCH 39/89] Scoreable notekinds Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com> --- source/funkin/play/PlayState.hx | 23 ++++++++++++++----- source/funkin/play/notes/NoteSprite.hx | 18 +++++++++++++++ source/funkin/play/notes/Strumline.hx | 3 +++ source/funkin/play/notes/notekind/NoteKind.hx | 7 ++++++ .../play/notes/notekind/NoteKindManager.hx | 16 ++++++++++++- 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 615bf93f1..4296d0cc0 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -43,6 +43,7 @@ import funkin.play.cutscene.VanillaCutscenes; import funkin.play.cutscene.VideoCutscene; import funkin.play.notes.NoteDirection; import funkin.play.notes.notekind.NoteKindManager; +import funkin.play.notes.notekind.NoteKind; import funkin.play.notes.NoteSprite; import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.Strumline; @@ -2240,6 +2241,13 @@ class PlayState extends MusicBeatSubState var strumTime:Float = songNote.time; if (strumTime < startTime) continue; // Skip notes that are before the start time. + var scoreable = true; + if (songNote.kind != null) + { + var noteKind:NoteKind = NoteKindManager.getNoteKind(songNote.kind); + if (noteKind != null) scoreable = noteKind.scoreable; + } + var noteData:Int = songNote.getDirection(); var playerNote:Bool = true; @@ -2250,7 +2258,7 @@ class PlayState extends MusicBeatSubState case 0: playerNoteData.push(songNote); // increment totalNotes for total possible notes able to be hit by the player - Highscore.tallies.totalNotes++; + if (scoreable) Highscore.tallies.totalNotes++; case 1: opponentNoteData.push(songNote); } @@ -2809,14 +2817,13 @@ class PlayState extends MusicBeatSubState } // Send the note hit event. - var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, isComboBreak, Highscore.tallies.combo + 1, noteDiff, + var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, isComboBreak, + note.scoreable ? Highscore.tallies.combo + 1 : Highscore.tallies.combo, noteDiff, daRating == 'sick'); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! if (event.eventCanceled) return; - - Highscore.tallies.totalNotesHit++; // Display the hit on the strums playerStrumline.hitNote(note, !event.isComboBreak); if (event.doesNotesplash) playerStrumline.playNoteSplash(note.noteData.getDirection()); @@ -2824,8 +2831,12 @@ class PlayState extends MusicBeatSubState vocals.playerVolume = 1; // Display the combo meter and add the calculation to the score. - applyScore(event.score, event.judgement, event.healthChange, event.isComboBreak); - popUpScore(event.judgement); + if (note.scoreable) + { + Highscore.tallies.totalNotesHit++; + applyScore(event.score, event.judgement, event.healthChange, event.isComboBreak); + popUpScore(event.judgement); + } } /** diff --git a/source/funkin/play/notes/NoteSprite.hx b/source/funkin/play/notes/NoteSprite.hx index 0962521fa..c6d07d718 100644 --- a/source/funkin/play/notes/NoteSprite.hx +++ b/source/funkin/play/notes/NoteSprite.hx @@ -10,6 +10,9 @@ class NoteSprite extends FunkinSprite { static final DIRECTION_COLORS:Array = ['purple', 'blue', 'green', 'red']; + /** + * The hold note sprite for this note. + */ public var holdNoteSprite:SustainTrail; var hsvShader:HSVShader; @@ -95,8 +98,23 @@ class NoteSprite extends FunkinSprite return this.direction; } + /** + * The note data associated with this note sprite. + * This is used to store the strum time, length, and other properties. + */ public var noteData:SongNoteData; + /** + * If this note kind is scoreable (i.e., counted towards score and accuracy) + * Only accessible in scripts + * Defaults to true + */ + public var scoreable:Bool = true; + + /** + * Whether this note is a hold note. + * This is true if the length is greater than 0. + */ public var isHoldNote(get, never):Bool; function get_isHoldNote():Bool diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 9fcb9594c..95f1ab4c2 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -17,6 +17,7 @@ import funkin.play.notes.NoteVibrationsHandler; import funkin.data.song.SongData.SongNoteData; import funkin.util.SortUtil; import funkin.util.GRhythmUtil; +import funkin.play.notes.notekind.NoteKind; import funkin.play.notes.notekind.NoteKindManager; import flixel.math.FlxPoint; #if mobile @@ -1103,6 +1104,7 @@ class Strumline extends FlxSpriteGroup if (noteSprite != null) { + var noteKind:NoteKind = NoteKindManager.getNoteKind(note.kind); var noteKindStyle:NoteStyle = NoteKindManager.getNoteStyle(note.kind, this.noteStyle.id) ?? this.noteStyle; noteSprite.setupNoteGraphic(noteKindStyle); @@ -1127,6 +1129,7 @@ class Strumline extends FlxSpriteGroup noteSprite.x -= (noteSprite.width - Strumline.STRUMLINE_SIZE) / 2; // Center it noteSprite.x -= NUDGE; noteSprite.y = -9999; + if (noteKind != null) noteSprite.scoreable = noteKind.scoreable; } return noteSprite; diff --git a/source/funkin/play/notes/notekind/NoteKind.hx b/source/funkin/play/notes/notekind/NoteKind.hx index 9cc125a31..700d2f9b3 100644 --- a/source/funkin/play/notes/notekind/NoteKind.hx +++ b/source/funkin/play/notes/notekind/NoteKind.hx @@ -28,6 +28,13 @@ class NoteKind implements INoteScriptedClass */ public var params:Array; + /** + * If this note kind is scoreable (ie, counted towards score and accuracy) + * Only accessible in scripts + * Defaults to true + */ + public var scoreable:Bool = true; + public function new(noteKind:String, description:String = "", ?noteStyleId:String, ?params:Array) { this.noteKind = noteKind; diff --git a/source/funkin/play/notes/notekind/NoteKindManager.hx b/source/funkin/play/notes/notekind/NoteKindManager.hx index f1f0c11fe..3ae55133e 100644 --- a/source/funkin/play/notes/notekind/NoteKindManager.hx +++ b/source/funkin/play/notes/notekind/NoteKindManager.hx @@ -11,7 +11,21 @@ import funkin.play.notes.notekind.NoteKind.NoteKindParam; class NoteKindManager { - static var noteKinds:Map = []; + /** + * A map of all note kinds, keyed by their name. + * This is used to retrieve note kinds by their name. + */ + public static var noteKinds:Map = []; + + /** + * Retrieve a note kind by its name. + * @param noteKind The name of the note kind. + * @return The note kind, or null if it doesn't exist. + */ + public static function getNoteKind(noteKind:String):Null + { + return noteKinds.get(noteKind); + } public static function loadScripts():Void { From 542f59ef72f76563f3cea8a5b42c6448c8ecb59f Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 21 Jul 2025 03:41:39 -0400 Subject: [PATCH 40/89] tallyCompletion() function for cleanup + constency --- source/funkin/play/ResultState.hx | 3 +- source/funkin/play/scoring/Scoring.hx | 25 +++++++++++--- source/funkin/save/Save.hx | 6 ++-- source/funkin/ui/freeplay/FreeplayState.hx | 40 +++++++++------------- 4 files changed, 39 insertions(+), 35 deletions(-) diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index 74088ff17..e104f5eee 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -527,8 +527,7 @@ class ResultState extends MusicBeatSubState bgFlash.visible = true; FlxTween.tween(bgFlash, {alpha: 0}, 5 / 24); // NOTE: Only divide if totalNotes > 0 to prevent divide-by-zero errors. - var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : (params.scoreData.tallies.sick + params.scoreData.tallies.good - - params.scoreData.tallies.missed) / params.scoreData.tallies.totalNotes * 100; + var clearPercentFloat = params.scoreData.tallies.totalNotes == 0 ? 0.0 : Scoring.tallyCompletion(params.scoreData.tallies) * 100; clearPercentTarget = Math.floor(clearPercentFloat); // Prevent off-by-one errors. diff --git a/source/funkin/play/scoring/Scoring.hx b/source/funkin/play/scoring/Scoring.hx index e2dab3b2f..b7691dbb8 100644 --- a/source/funkin/play/scoring/Scoring.hx +++ b/source/funkin/play/scoring/Scoring.hx @@ -384,21 +384,21 @@ class Scoring // Final Grade = (Sick + Good - Miss) / (Total Notes) - var grade = (scoreData.tallies.sick + scoreData.tallies.good - scoreData.tallies.missed) / scoreData.tallies.totalNotes; + var completionAmount:Float = Scoring.tallyCompletion(scoreData.tallies); - if (grade == Constants.RANK_PERFECT_THRESHOLD) + if (completionAmount == Constants.RANK_PERFECT_THRESHOLD) { return ScoringRank.PERFECT; } - else if (grade >= Constants.RANK_EXCELLENT_THRESHOLD) + else if (completionAmount >= Constants.RANK_EXCELLENT_THRESHOLD) { return ScoringRank.EXCELLENT; } - else if (grade >= Constants.RANK_GREAT_THRESHOLD) + else if (completionAmount >= Constants.RANK_GREAT_THRESHOLD) { return ScoringRank.GREAT; } - else if (grade >= Constants.RANK_GOOD_THRESHOLD) + else if (completionAmount >= Constants.RANK_GOOD_THRESHOLD) { return ScoringRank.GOOD; } @@ -407,6 +407,21 @@ class Scoring return ScoringRank.SHIT; } } + + /** + * Calculates the "completion" of a song, based on how many GOOD and SICK notes were hit, minus how many were missed + * Top secret funkin crew patented algorithm + * TODO: Could possibly move more of the "tallying" related handling here. + * In FreeplayState we make sure it's clamped between 0 and 1, and we probably always want to assume that? + * + * @param tallies + * @return Float Completion, as a float value between 0 and 1. If `tallies` is `null`, we return 0; + */ + public static function tallyCompletion(?tallies:SaveScoreTallyData):Float + { + if (tallies == null) return 0.0; + return (tallies.sick + tallies.good - tallies.missed) / tallies.totalNotes; + } } enum abstract ScoringRank(String) diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 0ca56c18e..c19c11af7 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -882,14 +882,12 @@ class Save return; } - var newCompletion = (newScoreData.tallies.sick + newScoreData.tallies.good) / newScoreData.tallies.totalNotes; - var previousCompletion = (previousScoreData.tallies.sick + previousScoreData.tallies.good) / previousScoreData.tallies.totalNotes; - // Set the high score and the high rank separately. var newScore:SaveScoreData = { score: (previousScoreData.score > newScoreData.score) ? previousScoreData.score : newScoreData.score, - tallies: (previousRank > newRank || previousCompletion > newCompletion) ? previousScoreData.tallies : newScoreData.tallies + tallies: (previousRank > newRank + || Scoring.tallyCompletion(previousScoreData.tallies) > Scoring.tallyCompletion(newScoreData.tallies)) ? previousScoreData.tallies : newScoreData.tallies }; song.set(difficultyId, newScore); diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index d1b6a261f..cbe114b16 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -31,6 +31,7 @@ import funkin.input.Controls; import funkin.modding.events.ScriptEvent; import funkin.modding.events.ScriptEventDispatcher; import funkin.play.PlayStatePlaylist; +import funkin.play.scoring.Scoring; import funkin.play.scoring.Scoring.ScoringRank; import funkin.play.song.Song; import funkin.save.Save; @@ -2300,11 +2301,10 @@ class FreeplayState extends MusicBeatSubState var songScore:Null = Save.instance.getSongScore(daSong.data.id, currentDifficulty, currentVariation); intendedScore = songScore?.score ?? 0; - intendedCompletion = songScore == null ? 0.0 : Math.max(0, - ((songScore.tallies.sick + songScore.tallies.good - songScore.tallies.missed) / songScore.tallies.totalNotes)); + intendedCompletion = Math.max(0, Scoring.tallyCompletion(songScore?.tallies)); rememberedDifficulty = currentDifficulty; if (!capsuleAnim) generateSongList(currentFilter, false, true, true); - currentCapsule.refreshDisplay((prepForNewRank == true) ? false : true); + currentCapsule.refreshDisplay(!prepForNewRank); } else { @@ -2719,26 +2719,18 @@ class FreeplayState extends MusicBeatSubState if (!prepForNewRank && curSelected != prevSelected) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); - var daSongCapsule:SongMenuItem = currentCapsule; - if (daSongCapsule.freeplayData != null) - { - var songScore:Null = Save.instance.getSongScore(daSongCapsule.freeplayData.data.id, currentDifficulty, currentVariation); - intendedScore = songScore?.score ?? 0; - intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + - songScore.tallies.good - songScore.tallies.missed) / songScore.tallies.totalNotes); - rememberedSongId = daSongCapsule.freeplayData.data.id; - changeDiff(); - daSongCapsule.refreshDisplay((prepForNewRank == true) ? false : true); - } + var songScore:Null = Save.instance.getSongScore(currentCapsule.freeplayData?.data.id ?? "", currentDifficulty, currentVariation); + intendedScore = songScore?.score ?? 0; + + intendedCompletion = Scoring.tallyCompletion(songScore?.tallies); + rememberedSongId = currentCapsule.freeplayData?.data.id; + + if (currentCapsule.freeplayData == null) albumRoll.albumId = null; + + changeDiff(); + if (currentCapsule.freeplayData == null) currentCapsule.refreshDisplay(); else - { - intendedScore = 0; - intendedCompletion = 0.0; - rememberedSongId = null; - albumRoll.albumId = null; - changeDiff(); - daSongCapsule.refreshDisplay(); - } + currentCapsule.refreshDisplay(!prepForNewRank); for (index => capsule in grpCapsules.members) { @@ -2756,10 +2748,10 @@ class FreeplayState extends MusicBeatSubState if (grpCapsules.countLiving() > 0 && !prepForNewRank && controls.active) { - playCurSongPreview(daSongCapsule); + playCurSongPreview(currentCapsule); currentCapsule.selected = true; - // switchBackingImage(daSongCapsule.freeplayData); + // switchBackingImage(currentCapsule.freeplayData); } // Small vibrations every selection change. From 967397311bd28add730e9e1c859c908ebda2fc81 Mon Sep 17 00:00:00 2001 From: Cameron Taylor Date: Mon, 21 Jul 2025 03:41:39 -0400 Subject: [PATCH 41/89] remove tiny uncommented code, and simplify an unneeded variable initialization --- source/funkin/play/scoring/Scoring.hx | 4 ++-- source/funkin/ui/freeplay/FreeplayState.hx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/source/funkin/play/scoring/Scoring.hx b/source/funkin/play/scoring/Scoring.hx index b7691dbb8..eaa8efbd2 100644 --- a/source/funkin/play/scoring/Scoring.hx +++ b/source/funkin/play/scoring/Scoring.hx @@ -1,6 +1,7 @@ package funkin.play.scoring; import funkin.save.Save.SaveScoreData; +import funkin.save.Save.SaveScoreTallyData; /** * Which system to use when scoring and judging notes. @@ -374,8 +375,7 @@ class Scoring if (scoreData.tallies.totalNotes == 0) return null; // Perfect (Platinum) is a Sick Full Clear - var isPerfectGold = scoreData.tallies.sick == scoreData.tallies.totalNotes; - if (isPerfectGold) + if (scoreData.tallies.sick == scoreData.tallies.totalNotes) { return ScoringRank.PERFECT_GOLD; } diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index cbe114b16..15ee80ebb 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -1276,7 +1276,6 @@ class FreeplayState extends MusicBeatSubState }); new FlxTimer().start(2, _ -> { - // dj.fistPump(); prepForNewRank = false; }); } From e6fd5d9b129f74703d6bd96744f63cb201afc941 Mon Sep 17 00:00:00 2001 From: Karim Akra <144803230+KarimAkra@users.noreply.github.com> Date: Fri, 1 Aug 2025 09:59:02 +0300 Subject: [PATCH 42/89] Make the Android back button close the game when in TitleState Co-authored-by: Hyper_ <40342021+NotHyper-474@users.noreply.github.com> Co-authored-by: MoonDroid <81515012+moondroidcoder@users.noreply.github.com> --- source/funkin/ui/title/TitleState.hx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index b5aef5b44..f90c3f928 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -228,11 +228,11 @@ class TitleState extends MusicBeatState { FlxG.bitmapLog.add(FlxG.camera.buffer); - #if desktop + #if (desktop || android) // Pressing BACK on the title screen should close the game. // This lets you exit without leaving fullscreen mode. - // Only applicable on desktop. - if (controls.BACK) + // Only applicable on desktop and Android. + if (#if android FlxG.android.justReleased.BACK || #end controls.BACK) { openfl.Lib.application.window.close(); } From 9d2cfa9686880170c5c398786c72d878974eaf93 Mon Sep 17 00:00:00 2001 From: SrtHero278 <93018286+SrtHero278@users.noreply.github.com> Date: Sat, 26 Jul 2025 12:24:23 -0700 Subject: [PATCH 43/89] Fix touch pointer positioning when in PlayState --- source/funkin/play/PlayState.hx | 3 -- .../funkin/util/plugins/TouchPointerPlugin.hx | 32 ++++++++++++++----- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 4296d0cc0..59fad09ca 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -715,8 +715,6 @@ class PlayState extends MusicBeatSubState #if mobile // Force allowScreenTimeout to be disabled lime.system.System.allowScreenTimeout = false; - // TODO: For some reason the touch pointer's positioning gets weird in playstate, find a way to fix it! -Zack - funkin.util.plugins.TouchPointerPlugin.enabled = false; #end // This state receives update() even when a substate is active. @@ -1760,7 +1758,6 @@ class PlayState extends MusicBeatSubState #if mobile // Syncing allowScreenTimeout with Preferences option. lime.system.System.allowScreenTimeout = Preferences.screenTimeout; - funkin.util.plugins.TouchPointerPlugin.enabled = true; #end #if !mobile diff --git a/source/funkin/util/plugins/TouchPointerPlugin.hx b/source/funkin/util/plugins/TouchPointerPlugin.hx index baa09a1f6..390f4c8e7 100644 --- a/source/funkin/util/plugins/TouchPointerPlugin.hx +++ b/source/funkin/util/plugins/TouchPointerPlugin.hx @@ -1,8 +1,8 @@ package funkin.util.plugins; -import flixel.FlxCamera; import flixel.FlxG; import flixel.FlxSprite; +import flixel.FlxCamera; import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup; import flixel.input.touch.FlxTouch; import flixel.math.FlxAngle; @@ -33,6 +33,11 @@ class TouchPointerPlugin extends FlxTypedSpriteGroup */ private static var instance:TouchPointerPlugin = null; + /** + * A camera dedicated to displaying the pointers. + */ + private static var pointerCamera:FlxCamera; + public function new() { super(); @@ -43,7 +48,7 @@ class TouchPointerPlugin extends FlxTypedSpriteGroup */ public static function initialize():Void { - var pointerCamera:FlxCamera = new FlxCamera(); + pointerCamera = new FlxCamera(); pointerCamera.bgColor.alpha = 0; instance = new TouchPointerPlugin(); instance.cameras = [pointerCamera]; @@ -125,7 +130,7 @@ class TouchPointerPlugin extends FlxTypedSpriteGroup add(pointer); } - pointer.updateFromTouch(touch); + pointer.updateFromTouch(touch, pointerCamera); } for (pointer in members) @@ -226,6 +231,12 @@ class TouchPointer extends FlxSprite */ public var touchId:Int = -1; + /** + * An internal point for grabbing the view position of the camera. + * Useful for reducing point allocation. + */ + private var viewPoint:FlxPoint; + /** * Stores the last position of the touch pointer. */ @@ -240,6 +251,7 @@ class TouchPointer extends FlxSprite super(); makeGraphic(16, 16, FlxColor.RED); scrollFactor.set(0, 0); + viewPoint = FlxPoint.get(); lastPosition = FlxPoint.get(); } @@ -261,11 +273,14 @@ class TouchPointer extends FlxSprite * * @param touch The FlxTouch object containing the current touch input data. */ - public function updateFromTouch(touch:FlxTouch):Void + public function updateFromTouch(touch:FlxTouch, camera:FlxCamera):Void { + // Grab the view coordinates + touch.getViewPosition(camera, viewPoint); + // Update position - x = touch.viewX - width / 2; - y = touch.viewY - height / 2; + x = viewPoint.x - width / 2; + y = viewPoint.y - height / 2; if (camera.target != null) { @@ -274,7 +289,7 @@ class TouchPointer extends FlxSprite } // Calculate angle if moving - if (lastPosition.distanceTo(FlxPoint.weak(touch.viewX, touch.viewY)) > 3) + if (lastPosition.distanceTo(FlxPoint.weak(viewPoint.x, viewPoint.y)) > 3) { var angle = FlxAngle.angleBetweenPoint(this, lastPosition, true); this.angle = angle; @@ -286,11 +301,12 @@ class TouchPointer extends FlxSprite loadGraphic("assets/images/cursor/michael.png"); } - lastPosition.set(touch.viewX, touch.viewY); + lastPosition.copyFrom(viewPoint); } override public function destroy():Void { + viewPoint.put(); lastPosition.put(); super.destroy(); } From 890d1dafcbbfcd976ca0bd6905829c89fd254239 Mon Sep 17 00:00:00 2001 From: SrtHero278 Date: Sun, 27 Jul 2025 10:56:41 -0700 Subject: [PATCH 44/89] include the camera parameter in the comment --- source/funkin/util/plugins/TouchPointerPlugin.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/util/plugins/TouchPointerPlugin.hx b/source/funkin/util/plugins/TouchPointerPlugin.hx index 390f4c8e7..eec962796 100644 --- a/source/funkin/util/plugins/TouchPointerPlugin.hx +++ b/source/funkin/util/plugins/TouchPointerPlugin.hx @@ -272,6 +272,7 @@ class TouchPointer extends FlxSprite * Used in TouchPointerPlugin's update method. * * @param touch The FlxTouch object containing the current touch input data. + * @param camera The FlxCamera to grab the touch's view position from. */ public function updateFromTouch(touch:FlxTouch, camera:FlxCamera):Void { From e003a38cabb86178a5b9139239faa992fddc9e83 Mon Sep 17 00:00:00 2001 From: EliteMasterEric Date: Fri, 1 Aug 2025 10:43:35 -0400 Subject: [PATCH 45/89] Exclude the debug print function when we aren't in the build step --- source/funkin/ui/credits/CreditsDataHandler.hx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/funkin/ui/credits/CreditsDataHandler.hx b/source/funkin/ui/credits/CreditsDataHandler.hx index 3a28aba16..79ac5fcb3 100644 --- a/source/funkin/ui/credits/CreditsDataHandler.hx +++ b/source/funkin/ui/credits/CreditsDataHandler.hx @@ -15,6 +15,7 @@ class CreditsDataHandler static final CREDITS_DATA_PATH:String = "assets/data/credits.json"; #end + #if macro public static function debugPrint(data:Null):Void { if (data == null) @@ -38,6 +39,7 @@ class CreditsDataHandler Sys.println('[INFO] CreditsData($entryCount entries containing $lineCount lines)'); } + #end /** * If for some reason the full credits won't load, From e5e3270fefc9baa52245223d6a6e20a682ca30e6 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 1 Aug 2025 12:37:46 -0400 Subject: [PATCH 46/89] Update hmm.json to optimize FlxAnimate --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index 278572b98..7d0e4de7a 100644 --- a/hmm.json +++ b/hmm.json @@ -59,7 +59,7 @@ "name": "flxanimate", "type": "git", "dir": null, - "ref": "39c1572add28869c558b218fffed13df1b64f376", + "ref": "b1faf19885dad06c899cb71ffe07b4e40b8c6d0c", "url": "https://github.com/FunkinCrew/flxanimate" }, { From 1b91f3b57cda3310db2f5196650b89c8d88e69be Mon Sep 17 00:00:00 2001 From: AwesomezPro99 Date: Thu, 31 Jul 2025 15:52:33 -0400 Subject: [PATCH 47/89] Fixed DJ Flicker After New Rank --- source/funkin/ui/freeplay/FreeplayDJ.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/freeplay/FreeplayDJ.hx b/source/funkin/ui/freeplay/FreeplayDJ.hx index c91bf991b..bd424f1ba 100644 --- a/source/funkin/ui/freeplay/FreeplayDJ.hx +++ b/source/funkin/ui/freeplay/FreeplayDJ.hx @@ -81,8 +81,6 @@ class FreeplayDJ extends FlxAtlasSprite public override function update(elapsed:Float):Void { - super.update(elapsed); - switch (currentState) { case Intro: @@ -185,6 +183,8 @@ class FreeplayDJ extends FlxAtlasSprite default: // I shit myself. } + + super.update(elapsed); } function onFinishAnim(name:String):Void From 5cb33e895c8e2f5b1af381f72c4a10a5148aaada Mon Sep 17 00:00:00 2001 From: ExcodeFNF Date: Tue, 29 Jul 2025 20:31:34 -0700 Subject: [PATCH 48/89] Freeplay CoolColors Array Removal --- source/funkin/ui/freeplay/FreeplayState.hx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 15ee80ebb..742d35c1c 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -175,17 +175,6 @@ class FreeplayState extends MusicBeatSubState return grpCapsules.members[curSelected]; } - var coolColors:Array = [ - 0xFF9271FD, - 0xFF9271FD, - 0xFF223344, - 0xFF941653, - 0xFFFC96D7, - 0xFFA0D1FF, - 0xFFFF78BF, - 0xFFF6B604 - ]; - var grpCapsules:FlxTypedGroup; var dj:Null = null; From 2dcc5288475921e79dac329fce7cda9a56649fe4 Mon Sep 17 00:00:00 2001 From: Abnormal <86753001+AbnormalPoof@users.noreply.github.com> Date: Fri, 1 Aug 2025 13:16:54 -0500 Subject: [PATCH 49/89] bump polymod to optimize some shiiiiiiiiiiiiiiiit --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index 7d0e4de7a..0495a3f9f 100644 --- a/hmm.json +++ b/hmm.json @@ -219,7 +219,7 @@ "name": "polymod", "type": "git", "dir": null, - "ref": "866f19edbcd872b3358f9a41f2f6a24c71c191d1", + "ref": "d4142dd15a3b57ed4eb149f9f6a2c3ad9935bf7b", "url": "https://github.com/larsiusprime/polymod" }, { From 2b1f346097efadcad19058c4ed9cefe4f84c8b64 Mon Sep 17 00:00:00 2001 From: JackXson-Real <32755192+JackXson-Real@users.noreply.github.com> Date: Tue, 29 Jul 2025 23:10:01 -0400 Subject: [PATCH 50/89] Add enter/exit animation for charSelect nametag part 2 --- source/funkin/ui/charSelect/CharSelectSubState.hx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/source/funkin/ui/charSelect/CharSelectSubState.hx b/source/funkin/ui/charSelect/CharSelectSubState.hx index b1d113e14..692274774 100644 --- a/source/funkin/ui/charSelect/CharSelectSubState.hx +++ b/source/funkin/ui/charSelect/CharSelectSubState.hx @@ -271,6 +271,12 @@ class CharSelectSubState extends MusicBeatSubState nametag.midpointX += cutoutSize; add(nametag); + @:privateAccess + { + nametag.midpointY += 200; + FlxTween.tween(nametag, {midpointY: nametag.midpointY - 200}, 1, {ease: FlxEase.expoOut}); + } + nametag.scrollFactor.set(); FlxG.debugger.addTrackerProfile(new TrackerProfile(FlxSprite, ["x", "y", "alpha", "scale", "blend"])); @@ -739,6 +745,7 @@ class CharSelectSubState extends MusicBeatSubState FlxTween.tween(cursorConfirmed, {alpha: 0}, 0.8, {ease: FlxEase.expoOut}); FlxTween.tween(barthing, {y: barthing.y + 80}, 0.8, {ease: FlxEase.backIn}); + FlxTween.tween(nametag, {y: nametag.y + 80}, 0.8, {ease: FlxEase.backIn}); FlxTween.tween(dipshitBacking, {y: dipshitBacking.y + 210}, 0.8, {ease: FlxEase.backIn}); FlxTween.tween(chooseDipshit, {y: chooseDipshit.y + 200}, 0.8, {ease: FlxEase.backIn}); FlxTween.tween(dipshitBlur, {y: dipshitBlur.y + 220}, 0.8, {ease: FlxEase.backIn}); From 30721c3a30f7e980557d055daa30e7e5501b83e3 Mon Sep 17 00:00:00 2001 From: Kolo <67389779+KoloInDaCrib@users.noreply.github.com> Date: Sat, 26 Jul 2025 17:11:52 +0200 Subject: [PATCH 51/89] tfw changeDiff wasn't even being called --- source/funkin/ui/freeplay/FreeplayState.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 742d35c1c..419187443 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -2193,7 +2193,7 @@ class FreeplayState extends MusicBeatSubState */ function changeDiff(change:Int = 0, force:Bool = false, capsuleAnim:Bool = false):Void { - if (!controls.active) return; + if (!controls.active && !force) return; if (capsuleAnim) { @@ -2715,7 +2715,7 @@ class FreeplayState extends MusicBeatSubState if (currentCapsule.freeplayData == null) albumRoll.albumId = null; - changeDiff(); + changeDiff(0, true); if (currentCapsule.freeplayData == null) currentCapsule.refreshDisplay(); else currentCapsule.refreshDisplay(!prepForNewRank); From f63cbf073ae29e6f3fef4f618d668f8896eef166 Mon Sep 17 00:00:00 2001 From: Hyper_ <40342021+NotHyper-474@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:04:28 -0300 Subject: [PATCH 52/89] Restore a lost change from #4787 --- source/funkin/ui/mainmenu/MainMenuState.hx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index 72bfbb950..1bc3afbec 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -268,10 +268,8 @@ class MainMenuState extends MusicBeatState // reset camera when debug menu is closed subStateClosed.add(_ -> resetCamStuff(false)); - // TODO: Why does this specific function break with null safety? - @:nullSafety(Off) subStateOpened.add((sub:FlxSubState) -> { - if (Type.getClass(sub) == FreeplayState) + if (Std.isOfType(sub, FreeplayState)) { new FlxTimer().start(0.5, _ -> { magenta.visible = false; From da5f6918656d4a1ad3e83233b358034db92ca675 Mon Sep 17 00:00:00 2001 From: anysad Date: Wed, 23 Jul 2025 16:15:02 +0300 Subject: [PATCH 53/89] fix: no more animation editor crash --- source/funkin/ui/debug/anim/DebugBoundingState.hx | 1 + 1 file changed, 1 insertion(+) diff --git a/source/funkin/ui/debug/anim/DebugBoundingState.hx b/source/funkin/ui/debug/anim/DebugBoundingState.hx index 73be8b208..ccef6eb4a 100644 --- a/source/funkin/ui/debug/anim/DebugBoundingState.hx +++ b/source/funkin/ui/debug/anim/DebugBoundingState.hx @@ -100,6 +100,7 @@ class DebugBoundingState extends FlxState offsetAnimationDropdown = offsetEditorDialog.findComponent("animationDropdown", DropDown); offsetEditorDialog.cameras = [hudCam]; + offsetEditorDialog.closable = false; add(offsetEditorDialog); offsetEditorDialog.showDialog(false); From 0d6bc2bedd4494838db288247c4b13ec4031f0a8 Mon Sep 17 00:00:00 2001 From: JVN-Pixels <146671762+JVNpixels@users.noreply.github.com> Date: Tue, 29 Jul 2025 13:49:17 -0700 Subject: [PATCH 54/89] Fix NOR in Freeplay after spamming the letter keybinds --- source/funkin/ui/freeplay/LetterSort.hx | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/source/funkin/ui/freeplay/LetterSort.hx b/source/funkin/ui/freeplay/LetterSort.hx index d1a654c56..6251a74c9 100644 --- a/source/funkin/ui/freeplay/LetterSort.hx +++ b/source/funkin/ui/freeplay/LetterSort.hx @@ -148,18 +148,22 @@ class LetterSort extends FlxSpriteGroup public function changeSelection(diff:Int = 0, playSound:Bool = true):Void { - doLetterChangeAnims(diff); + @:privateAccess + if (instance.controls.active) + { + doLetterChangeAnims(diff); - var multiPosOrNeg:Float = diff > 0 ? 1 : -1; + var multiPosOrNeg:Float = diff > 0 ? 1 : -1; - // if we're moving left (diff < 0), we want control of the right arrow, and vice versa - var arrowToMove:FlxSprite = diff < 0 ? leftArrow : rightArrow; - arrowToMove.offset.x = 3 * multiPosOrNeg; + // if we're moving left (diff < 0), we want control of the right arrow, and vice versa + var arrowToMove:FlxSprite = diff < 0 ? leftArrow : rightArrow; + arrowToMove.offset.x = 3 * multiPosOrNeg; - new FlxTimer().start(2 / 24, function(_) { - arrowToMove.offset.x = 0; - }); - if (playSound && diff != 0) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); + new FlxTimer().start(2 / 24, function(_) { + arrowToMove.offset.x = 0; + }); + if (playSound && diff != 0) FunkinSound.playOnce(Paths.sound('scrollMenu'), 0.4); + } } /** From 3d88fbb11fea2b52d399f37cb8154795329c31c2 Mon Sep 17 00:00:00 2001 From: AwesomezPro99 Date: Fri, 1 Aug 2025 13:15:49 -0400 Subject: [PATCH 55/89] Update PreferencesMenu.hx --- source/funkin/ui/options/PreferencesMenu.hx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/options/PreferencesMenu.hx b/source/funkin/ui/options/PreferencesMenu.hx index e149ac17b..6433c1dba 100644 --- a/source/funkin/ui/options/PreferencesMenu.hx +++ b/source/funkin/ui/options/PreferencesMenu.hx @@ -65,7 +65,6 @@ class PreferencesMenu extends Page createPrefDescription(); camFollow = new FlxObject(FlxG.width / 2, 0, 140, 70); - if (items != null) camFollow.y = items.selectedItem.y; menuCamera.follow(camFollow, null, 0.085); var margin = 160; @@ -73,7 +72,6 @@ class PreferencesMenu extends Page menuCamera.minScrollY = 0; items.onChange.add(function(selected) { - camFollow.y = selected.y; itemDesc.text = preferenceDesc[items.selectedIndex]; }); @@ -204,6 +202,9 @@ class PreferencesMenu extends Page { super.update(elapsed); + // Positions the camera to the selected item. + if (items != null) camFollow.y = items.selectedItem.y; + // Indent the selected item. items.forEach(function(daItem:TextMenuItem) { var thyOffset:Int = 0; From e6d737125e0031d1efabc73266dfbde6d6c758de Mon Sep 17 00:00:00 2001 From: anysad Date: Wed, 14 May 2025 22:40:39 +0300 Subject: [PATCH 56/89] feature: are you sure? --- .../funkin/ui/debug/charting/ChartEditorState.hx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 10ec9a84a..6881562ae 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -94,6 +94,9 @@ import haxe.ui.components.Button; import haxe.ui.components.DropDown; import haxe.ui.components.Label; import haxe.ui.components.Slider; +import haxe.ui.containers.dialogs.Dialogs; +import haxe.ui.containers.dialogs.Dialog.DialogButton; +import haxe.ui.containers.dialogs.MessageBox.MessageBoxType; import haxe.ui.containers.dialogs.CollapsibleDialog; import haxe.ui.containers.menus.Menu; import haxe.ui.containers.menus.MenuBar; @@ -5599,7 +5602,18 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState @:nullSafety(Off) function quitChartEditor():Void { - autoSave(); + if (saveDataDirty) { + Dialogs.messageBox("You are about to leave the editor without saving.\n\nAre you sure?", "Leave Editor", MessageBoxType.TYPE_YESNO, true, function(button:DialogButton) { + if (button == DialogButton.YES) + { + autoSave(); + quitChartEditor(); + } + }); + + return; + } + stopWelcomeMusic(); // TODO: PR Flixel to make onComplete nullable. if (audioInstTrack != null) audioInstTrack.onComplete = null; From 3170346b83e5f73a88b3e7135e75ec1be2384929 Mon Sep 17 00:00:00 2001 From: cyn0x8 Date: Wed, 26 Mar 2025 02:02:39 -0700 Subject: [PATCH 57/89] Create ModStore.hx --- source/funkin/modding/ModStore.hx | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 source/funkin/modding/ModStore.hx diff --git a/source/funkin/modding/ModStore.hx b/source/funkin/modding/ModStore.hx new file mode 100644 index 000000000..981a3498a --- /dev/null +++ b/source/funkin/modding/ModStore.hx @@ -0,0 +1,54 @@ +package funkin.modding; + +import haxe.ds.StringMap; + +/** + * Temporary persistent data storage for mods to use. + */ +@:nullSafety +class ModStore +{ + /** + * All registered stores for this session. + */ + public static final stores:StringMap = new StringMap(); + + /** + * Attempts to register a new store with the given ID and return it. + * If a store with the same ID already exists, that store will be returned instead (discards `data`). + * + * @id The unique ID for this store. + * @data Optional initial data, uses an empty object by default. + * @return The store data at the given ID. + */ + public static function register(id:String, ?data:Dynamic):Dynamic + { + if (stores.exists(id)) return stores.get(id); + stores.set(id, data ??= {}); + return data; + } + + /** + * Helper function to get a store by ID. + * + * @id The target ID of the store. + * @return The store data, or `null` if the store did not exist. + */ + public static function get(id:String):Null + { + return stores.get(id); + } + + /** + * Helper function to remove a store by ID and return it. + * + * @id The target ID of the store. + * @return The store data, or `null` if the store did not exist. + */ + public static function remove(id:String):Null + { + var data:Null = stores.get(id); + stores.remove(id); + return data; + } +} From 661ddb15f72c2dae22f41a922a521740ab3617f0 Mon Sep 17 00:00:00 2001 From: Kolo <67389779+JustKolosaki@users.noreply.github.com> Date: Thu, 10 Jul 2025 07:14:50 +0200 Subject: [PATCH 58/89] me......................................................................... --- source/funkin/ui/debug/stageeditor/handlers/AssetDataHandler.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/ui/debug/stageeditor/handlers/AssetDataHandler.hx b/source/funkin/ui/debug/stageeditor/handlers/AssetDataHandler.hx index 283ff12db..0aca1dfdb 100644 --- a/source/funkin/ui/debug/stageeditor/handlers/AssetDataHandler.hx +++ b/source/funkin/ui/debug/stageeditor/handlers/AssetDataHandler.hx @@ -192,7 +192,7 @@ class AssetDataHandler for (daFrame in obj.frames.frames) { - xml += ' \n'; + xml += ' \n'; } xml += ""; From 743e8b13c28d9f0471b392edc3127973f6fef329 Mon Sep 17 00:00:00 2001 From: Hyper_ <40342021+NotHyper-474@users.noreply.github.com> Date: Sat, 19 Jul 2025 22:19:46 -0300 Subject: [PATCH 59/89] me...................................................................................... --- source/funkin/play/PlayState.hx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 4296d0cc0..0c802e326 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1501,7 +1501,9 @@ class PlayState extends MusicBeatSubState musicPausedBySubState = false; } - forEachPausedSound((s) -> needsReset ? s.destroy() : s.resume()); + // The logic here is that if this sound doesn't auto-destroy + // then it's gonna be reused somewhere, so we just stop it instead. + forEachPausedSound(s -> needsReset ? (s.autoDestroy ? s.destroy() : s.stop()) : s.resume()); // Resume camera tweens if we paused any. for (camTween in cameraTweensPausedBySubState) From 160551442453fccc0961c570701bdba7bee95ef0 Mon Sep 17 00:00:00 2001 From: PurSnake Date: Sat, 2 Aug 2025 00:14:52 +0300 Subject: [PATCH 60/89] Seriously? --- source/funkin/ui/freeplay/FreeplayState.hx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 419187443..f5c350df7 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -2403,11 +2403,6 @@ class FreeplayState extends MusicBeatSubState { trace('RANDOM SELECTED'); - controls.active = false; - #if NO_FEATURE_TOUCH_CONTROLS - letterSort.inputEnabled = false; - #end - var availableSongCapsules:Array = grpCapsules.members.filter(function(cap:SongMenuItem) { // Dead capsules are ones which were removed from the list when changing filters. return cap.alive && cap.freeplayData != null; @@ -2433,6 +2428,10 @@ class FreeplayState extends MusicBeatSubState // Seeing if I can do an animation... curSelected = grpCapsules.members.indexOf(targetSong); changeSelection(0); // Trigger an update. + controls.active = false; + #if NO_FEATURE_TOUCH_CONTROLS + letterSort.inputEnabled = false; + #end // Act like we hit Confirm on that song. capsuleOnConfirmDefault(targetSong); From 32c775ef3f131accd69a04591608420919a607a1 Mon Sep 17 00:00:00 2001 From: Kolo <67389779+JustKolosaki@users.noreply.github.com> Date: Wed, 2 Jul 2025 18:34:15 +0200 Subject: [PATCH 61/89] tooltips....BUT FOR NOTES! --- source/funkin/data/song/SongData.hx | 24 ++++++++++ .../ui/debug/charting/ChartEditorState.hx | 5 ++- .../components/ChartEditorNoteSprite.hx | 45 ++++++++++++++++++- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 136c728a9..522463ca1 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -1117,6 +1117,30 @@ class SongNoteDataRaw implements ICloneable return 'SongNoteData(${this.time}ms, ' + (this.length > 0 ? '[${this.length}ms hold]' : '') + ' ${this.data}' + (this.kind != '' ? ' [kind: ${this.kind}])' : ')'); } + + public function buildTooltip():String + { + var result:String = "Kind: "; + + if ((this.kind?.length ?? 0) == 0) + { + result += "Default"; + return result; + } + + result += this.kind; + + if (this.params.length == 0) return result; + + result += "\nParams:"; + + for (param in params) + { + result += '\n- ${param.name}: ${param.value}'; + } + + return result; + } } /** diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 6881562ae..6d70adcee 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -2522,7 +2522,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState add(gridTiledSprite); gridTiledSprite.zIndex = 10; - gridGhostNote = new ChartEditorNoteSprite(this); + gridGhostNote = new ChartEditorNoteSprite(this, true); gridGhostNote.alpha = 0.6; gridGhostNote.noteData = new SongNoteData(0, 0, 0, "", []); gridGhostNote.visible = false; @@ -3924,6 +3924,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState selectionSquare.width = selectionSquare.height = GRID_SIZE; selectionSquare.color = FlxColor.RED; } + + // Additional cleanup on notes. + if (noteTooltipsDirty) noteSprite.updateTooltipText(); } for (eventSprite in renderedEvents.members) diff --git a/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx index 17cd2bc64..d735d832c 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx @@ -10,6 +10,9 @@ import funkin.data.song.SongData.SongNoteData; import funkin.data.notestyle.NoteStyleRegistry; import funkin.play.notes.notestyle.NoteStyle; import funkin.play.notes.NoteDirection; +import haxe.ui.tooltips.ToolTipRegionOptions; +import funkin.util.HaxeUIUtil; +import haxe.ui.tooltips.ToolTipManager; /** * A sprite that can be used to display a note in a chart. @@ -63,11 +66,16 @@ class ChartEditorNoteSprite extends FlxSprite return overrideData; } - public function new(parent:ChartEditorState) + public var isGhost:Bool = false; + public var tooltip:ToolTipRegionOptions; + + public function new(parent:ChartEditorState, isGhost:Bool = false) { super(); this.parentState = parent; + this.isGhost = isGhost; + this.tooltip = HaxeUIUtil.buildTooltip('N/A'); var entries:Array = NoteStyleRegistry.instance.listEntryIds(); @@ -156,6 +164,7 @@ class ChartEditorNoteSprite extends FlxSprite if (this.noteData == null) { this.kill(); + updateTooltipPosition(); return this.noteData; } @@ -167,7 +176,7 @@ class ChartEditorNoteSprite extends FlxSprite // Update the position to match the note data. updateNotePosition(); - + updateTooltipText(); return this.noteData; } @@ -194,6 +203,38 @@ class ChartEditorNoteSprite extends FlxSprite this.x += origin.x; this.y += origin.y; } + + this.updateTooltipPosition(); + } + + public function updateTooltipText():Void + { + if (this.noteData == null) return; + if (this.isGhost) return; + this.tooltip.tipData = {text: this.noteData.buildTooltip()}; + } + + public function updateTooltipPosition():Void + { + // No tooltip for ghost sprites. + if (this.isGhost) return; + + if (this.noteData == null) + { + // Disable the tooltip. + ToolTipManager.instance.unregisterTooltipRegion(this.tooltip); + } + else + { + // Update the position. + this.tooltip.left = this.x; + this.tooltip.top = this.y; + this.tooltip.width = this.width; + this.tooltip.height = this.height; + + // Enable the tooltip. + ToolTipManager.instance.registerTooltipRegion(this.tooltip); + } } function get_noteStyle():Null From d6fe54ee79c1d0ca855559f5f7442793aa949baa Mon Sep 17 00:00:00 2001 From: Kolo <67389779+JustKolosaki@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:31:10 +0200 Subject: [PATCH 62/89] gyafyhaswguhyshbughsd --- source/funkin/data/song/SongData.hx | 11 ++------- source/funkin/save/Save.hx | 24 +++++++++++++++++++ .../ui/debug/charting/ChartEditorState.hx | 18 +++++++++++++- .../components/ChartEditorNoteSprite.hx | 23 +++++++++++++++++- 4 files changed, 65 insertions(+), 11 deletions(-) diff --git a/source/funkin/data/song/SongData.hx b/source/funkin/data/song/SongData.hx index 522463ca1..565b362ea 100644 --- a/source/funkin/data/song/SongData.hx +++ b/source/funkin/data/song/SongData.hx @@ -1120,16 +1120,9 @@ class SongNoteDataRaw implements ICloneable public function buildTooltip():String { - var result:String = "Kind: "; - - if ((this.kind?.length ?? 0) == 0) - { - result += "Default"; - return result; - } - - result += this.kind; + if ((this.kind?.length ?? 0) == 0) return ""; + var result:String = 'Kind: ${this.kind}'; if (this.params.length == 0) return result; result += "\nParams:"; diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index c19c11af7..e77daa9b5 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -187,6 +187,7 @@ class Save theme: ChartEditorTheme.Light, playtestStartTime: false, downscroll: false, + showNoteKinds: true, metronomeVolume: 1.0, hitsoundVolumePlayer: 1.0, hitsoundVolumeOpponent: 1.0, @@ -358,6 +359,23 @@ class Save return data.optionsChartEditor.downscroll; } + public var chartEditorShowNoteKinds(get, set):Bool; + + function get_chartEditorShowNoteKinds():Bool + { + if (data.optionsChartEditor.showNoteKinds == null) data.optionsChartEditor.showNoteKinds = true; + + return data.optionsChartEditor.showNoteKinds; + } + + function set_chartEditorShowNoteKinds(value:Bool):Bool + { + // Set and apply. + data.optionsChartEditor.showNoteKinds = value; + flush(); + return data.optionsChartEditor.showNoteKinds; + } + public var chartEditorPlaytestStartTime(get, set):Bool; function get_chartEditorPlaytestStartTime():Bool @@ -1842,6 +1860,12 @@ typedef SaveDataChartEditorOptions = */ var ?downscroll:Bool; + /** + * Show Note Kind Indicator in the Chart Editor. + * @default `true` + */ + var ?showNoteKinds:Bool; + /** * Metronome volume in the Chart Editor. * @default `1.0` diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 6d70adcee..14dc92afe 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -634,6 +634,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState return isViewDownscroll; } + /** + * Whether to show an indicator if a note is of a non-default kind. + */ + var showNoteKindIndicators:Bool = false; + /** * The current theme used by the editor. * Dictates the appearance of many UI elements. @@ -1858,6 +1863,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState */ var menubarItemDownscroll:MenuCheckBox; + /** + * The `View -> Note Kind Indicator` menu item. + */ + var menubarItemViewIndicators:MenuCheckBox; + /** * The `View -> Increase Difficulty` menu item. */ @@ -2361,6 +2371,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState noteSnapQuantIndex = save.chartEditorNoteQuant; currentLiveInputStyle = save.chartEditorLiveInputStyle; isViewDownscroll = save.chartEditorDownscroll; + showNoteKindIndicators = save.chartEditorShowNoteKinds; playtestStartTime = save.chartEditorPlaytestStartTime; currentTheme = save.chartEditorTheme; metronomeVolume = save.chartEditorMetronomeVolume; @@ -2390,6 +2401,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState save.chartEditorNoteQuant = noteSnapQuantIndex; save.chartEditorLiveInputStyle = currentLiveInputStyle; save.chartEditorDownscroll = isViewDownscroll; + save.chartEditorShowNoteKinds = showNoteKindIndicators; save.chartEditorPlaytestStartTime = playtestStartTime; save.chartEditorTheme = currentTheme; save.chartEditorMetronomeVolume = metronomeVolume; @@ -3092,6 +3104,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState menubarItemDownscroll.onClick = event -> isViewDownscroll = event.value; menubarItemDownscroll.selected = isViewDownscroll; + menubarItemViewIndicators.onClick = event -> showNoteKindIndicators = menubarItemViewIndicators.selected; + menubarItemViewIndicators.selected = showNoteKindIndicators; + menubarItemDifficultyUp.onClick = _ -> incrementDifficulty(1); menubarItemDifficultyDown.onClick = _ -> incrementDifficulty(-1); @@ -6313,7 +6328,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState { currentScrollEase = Math.max(0, targetScrollPosition); currentScrollEase = Math.min(currentScrollEase, songLengthInPixels); - scrollPositionInPixels = MathUtil.snap(MathUtil.smoothLerpPrecision(scrollPositionInPixels, currentScrollEase, FlxG.elapsed, SCROLL_EASE_DURATION, 1 / 1000), currentScrollEase, 1 / 1000); + scrollPositionInPixels = MathUtil.snap(MathUtil.smoothLerpPrecision(scrollPositionInPixels, currentScrollEase, FlxG.elapsed, SCROLL_EASE_DURATION, + 1 / 1000), currentScrollEase, 1 / 1000); moveSongToScrollPosition(); } diff --git a/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx b/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx index d735d832c..de03dde59 100644 --- a/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx +++ b/source/funkin/ui/debug/charting/components/ChartEditorNoteSprite.hx @@ -1,5 +1,7 @@ package funkin.ui.debug.charting.components; +import flixel.text.FlxText; +import flixel.util.FlxColor; import flixel.FlxObject; import flixel.FlxSprite; import flixel.graphics.frames.FlxFramesCollection; @@ -69,6 +71,11 @@ class ChartEditorNoteSprite extends FlxSprite public var isGhost:Bool = false; public var tooltip:ToolTipRegionOptions; + /** + * An indicator if the note is a note kind different than Default (""). + */ + public var kindIndicator:FlxText = new FlxText(5, 5, 100, '*', 16); + public function new(parent:ChartEditorState, isGhost:Bool = false) { super(); @@ -97,6 +104,8 @@ class ChartEditorNoteSprite extends FlxSprite { addNoteStyleAnimations(fetchNoteStyle(entry)); } + + kindIndicator.setFormat("VCR OSD Mono", 24, FlxColor.YELLOW, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); } static var noteFrameCollection:Null = null; @@ -219,7 +228,7 @@ class ChartEditorNoteSprite extends FlxSprite // No tooltip for ghost sprites. if (this.isGhost) return; - if (this.noteData == null) + if (this.noteData == null || (this.tooltip.tipData?.text ?? "").length == 0) { // Disable the tooltip. ToolTipManager.instance.unregisterTooltipRegion(this.tooltip); @@ -237,6 +246,18 @@ class ChartEditorNoteSprite extends FlxSprite } } + override public function draw() + { + super.draw(); + + if (!parentState.showNoteKindIndicators) return; + if ((this.noteData?.kind ?? "").length == 0) return; // Do not render the note kind indicator if the note kind is default. + + kindIndicator.x = this.x; + kindIndicator.y = this.y; + kindIndicator.draw(); + } + function get_noteStyle():Null { if (this.noteStyle == null) From 44cdbb08c44e8efd933ae6759a0f51ffdf320247 Mon Sep 17 00:00:00 2001 From: Abnormal <86753001+AbnormalPoof@users.noreply.github.com> Date: Sat, 2 Aug 2025 09:15:18 -0500 Subject: [PATCH 63/89] asset changes wooooooooooooooo --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 79396dc5d..a8d15febf 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 79396dc5da448e9928ab7d5155b267f83702abe3 +Subproject commit a8d15febf5e37a4fc7ffeb0d3eff9c4d36457a37 From 56878befef08d1d9cc52faf80a53ed2a8ac97185 Mon Sep 17 00:00:00 2001 From: Abnormal <86753001+AbnormalPoof@users.noreply.github.com> Date: Sat, 2 Aug 2025 13:31:38 -0500 Subject: [PATCH 64/89] update the haxelibs.......yippee...... --- hmm.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hmm.json b/hmm.json index 0495a3f9f..0b853aca3 100644 --- a/hmm.json +++ b/hmm.json @@ -46,7 +46,7 @@ "type": "git", "dir": null, "ref": "08fc955ca87f192a971719a675f1d3b21709725d", - "url": "https://github.com/FunkinCrew/flixel-mobile" + "url": "https://github.com/FunkinCrew/flixel" }, { "name": "flixel-addons", @@ -170,8 +170,8 @@ "name": "lime", "type": "git", "dir": null, - "ref": "0f1193c37761f1a80a888fb84e752490b5d0b656", - "url": "https://github.com/FunkinCrew/lime-mobile" + "ref": "e5f8c27124598505917a001588b560244731adfb", + "url": "https://github.com/FunkinCrew/lime" }, { "name": "mconsole", @@ -213,7 +213,7 @@ "type": "git", "dir": null, "ref": "a0df7c3afe360c9af59a76e45007dbf4e53b5131", - "url": "https://github.com/FunkinCrew/openfl-mobile" + "url": "https://github.com/FunkinCrew/openfl" }, { "name": "polymod", From 705e6b330806c3157bca7301b1e43cd78acdb23a Mon Sep 17 00:00:00 2001 From: AwesomezPro99 Date: Sat, 2 Aug 2025 11:03:34 -0400 Subject: [PATCH 65/89] Add Camera Fade To Song Select --- source/funkin/ui/freeplay/FreeplayState.hx | 40 ++++++++++++---------- source/funkin/ui/story/StoryMenuState.hx | 14 ++++---- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index f5c350df7..a3500171c 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -2599,26 +2599,28 @@ class FreeplayState extends MusicBeatSubState new FlxTimer().start(styleData?.getStartDelay(), function(tmr:FlxTimer) { FunkinSound.emptyPartialQueue(); - Paths.setCurrentLevel(cap?.freeplayData?.levelId); - LoadingState.loadPlayState( - { - targetSong: targetSong, - targetDifficulty: currentDifficulty, - targetVariation: currentVariation, - targetInstrumental: targetInstId, - practiceMode: false, - minimalMode: false, + funnyCam.fade(FlxColor.BLACK, 0.1, false, function() { + Paths.setCurrentLevel(cap?.freeplayData?.levelId); + LoadingState.loadPlayState( + { + targetSong: targetSong, + targetDifficulty: currentDifficulty, + targetVariation: currentVariation, + targetInstrumental: targetInstId, + practiceMode: false, + minimalMode: false, - #if FEATURE_DEBUG_FUNCTIONS - botPlayMode: FlxG.keys.pressed.SHIFT, - #else - botPlayMode: false, - #end - // TODO: Make these an option! It's currently only accessible via chart editor. - // startTimestamp: 0.0, - // playbackRate: 0.5, - // botPlayMode: true, - }, true); + #if FEATURE_DEBUG_FUNCTIONS + botPlayMode: FlxG.keys.pressed.SHIFT, + #else + botPlayMode: false, + #end + // TODO: Make these an option! It's currently only accessible via chart editor. + // startTimestamp: 0.0, + // playbackRate: 0.5, + // botPlayMode: true, + }, true); + }); }); } diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 82d9f1568..50b3e5c94 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -604,12 +604,14 @@ class StoryMenuState extends MusicBeatState var targetVariation:String = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty); - LoadingState.loadPlayState( - { - targetSong: targetSong, - targetDifficulty: PlayStatePlaylist.campaignDifficulty, - targetVariation: targetVariation - }, true); + FlxG.camera.fade(FlxColor.BLACK, 0.1, false, function() { + LoadingState.loadPlayState( + { + targetSong: targetSong, + targetDifficulty: PlayStatePlaylist.campaignDifficulty, + targetVariation: targetVariation + }, true); + }); }); } From 4cbfaefc1ab013756336e2b452dcfac834fc18e3 Mon Sep 17 00:00:00 2001 From: Abnormal <86753001+AbnormalPoof@users.noreply.github.com> Date: Sat, 2 Aug 2025 13:40:58 -0500 Subject: [PATCH 66/89] Slightly longer fade transition Co-authored-by: VirtuGuy <103977942+VirtuGuy@users.noreply.github.com> --- 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 a3500171c..9a2637bdf 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -2599,7 +2599,7 @@ class FreeplayState extends MusicBeatSubState new FlxTimer().start(styleData?.getStartDelay(), function(tmr:FlxTimer) { FunkinSound.emptyPartialQueue(); - funnyCam.fade(FlxColor.BLACK, 0.1, false, function() { + funnyCam.fade(FlxColor.BLACK, 0.2, false, function() { Paths.setCurrentLevel(cap?.freeplayData?.levelId); LoadingState.loadPlayState( { diff --git a/source/funkin/ui/story/StoryMenuState.hx b/source/funkin/ui/story/StoryMenuState.hx index 50b3e5c94..b83e2c89f 100644 --- a/source/funkin/ui/story/StoryMenuState.hx +++ b/source/funkin/ui/story/StoryMenuState.hx @@ -604,7 +604,7 @@ class StoryMenuState extends MusicBeatState var targetVariation:String = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty); - FlxG.camera.fade(FlxColor.BLACK, 0.1, false, function() { + FlxG.camera.fade(FlxColor.BLACK, 0.2, false, function() { LoadingState.loadPlayState( { targetSong: targetSong, From a92567886c64d682548edf6fc8b992f7a4419f9c Mon Sep 17 00:00:00 2001 From: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> Date: Sat, 2 Aug 2025 22:28:30 +0300 Subject: [PATCH 67/89] Relocate the `external` folder outside of `mobile` package. --- project.hxp | 2 +- source/funkin/InitState.hx | 4 ++-- .../funkin/{mobile => }/external/android/CallbackUtil.hx | 4 ++-- .../{mobile => }/external/android/DataFolderUtil.hx | 2 +- source/funkin/{mobile => }/external/android/JNIUtil.hx | 2 +- source/funkin/{mobile => }/external/android/ScreenUtil.hx | 2 +- .../android/java/funkin/extension/CallbackUtil.java | 0 .../android/java/funkin/provider/DataFolderProvider.java | 0 .../external/android/java/funkin/util/DataFolderUtil.java | 0 .../external/android/java/funkin/util/ScreenUtil.java | 0 source/funkin/{mobile => }/external/ios/AudioSession.hx | 4 ++-- source/funkin/{mobile => }/external/ios/ScreenUtil.hx | 4 ++-- source/funkin/{mobile => }/external/ios/project/Build.xml | 0 .../external/ios/project/ios/include/AudioSession.hpp | 0 .../external/ios/project/ios/include/ScreenUtil.hpp | 0 .../external/ios/project/ios/src/AudioSession.mm | 0 .../external/ios/project/ios/src/ScreenUtil.mm | 0 source/funkin/mobile/util/ScreenUtil.hx | 4 ++-- source/funkin/ui/options/OptionsState.hx | 2 +- .../funkin/{mobile/macros => util/macro}/LinkerMacro.hx | 8 ++++---- source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx | 2 +- 21 files changed, 20 insertions(+), 20 deletions(-) rename source/funkin/{mobile => }/external/android/CallbackUtil.hx (94%) rename source/funkin/{mobile => }/external/android/DataFolderUtil.hx (92%) rename source/funkin/{mobile => }/external/android/JNIUtil.hx (99%) rename source/funkin/{mobile => }/external/android/ScreenUtil.hx (97%) rename source/funkin/{mobile => }/external/android/java/funkin/extension/CallbackUtil.java (100%) rename source/funkin/{mobile => }/external/android/java/funkin/provider/DataFolderProvider.java (100%) rename source/funkin/{mobile => }/external/android/java/funkin/util/DataFolderUtil.java (100%) rename source/funkin/{mobile => }/external/android/java/funkin/util/ScreenUtil.java (100%) rename source/funkin/{mobile => }/external/ios/AudioSession.hx (71%) rename source/funkin/{mobile => }/external/ios/ScreenUtil.hx (81%) rename source/funkin/{mobile => }/external/ios/project/Build.xml (100%) rename source/funkin/{mobile => }/external/ios/project/ios/include/AudioSession.hpp (100%) rename source/funkin/{mobile => }/external/ios/project/ios/include/ScreenUtil.hpp (100%) rename source/funkin/{mobile => }/external/ios/project/ios/src/AudioSession.mm (100%) rename source/funkin/{mobile => }/external/ios/project/ios/src/ScreenUtil.mm (100%) rename source/funkin/{mobile/macros => util/macro}/LinkerMacro.hx (78%) diff --git a/project.hxp b/project.hxp index 2bb4e2fba..fc4830540 100644 --- a/project.hxp +++ b/project.hxp @@ -978,7 +978,7 @@ class Project extends HXProject function configureAndroid() { - javaPaths.push(Path.join([SOURCE_DIR, 'funkin/mobile/external/android/java'])); + javaPaths.push(Path.join([SOURCE_DIR, 'funkin/external/android/java'])); if (isRelease()) { diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx index 0cafefb91..3193f9b8d 100644 --- a/source/funkin/InitState.hx +++ b/source/funkin/InitState.hx @@ -114,7 +114,7 @@ class InitState extends FlxState #if ios // Setup Audio session - funkin.mobile.external.ios.AudioSession.initialize(); + funkin.external.ios.AudioSession.initialize(); #end // This ain't a pixel art game! (most of the time) @@ -202,7 +202,7 @@ class InitState extends FlxState // #if android FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK]; - funkin.mobile.external.android.CallbackUtil.init(); + funkin.external.android.CallbackUtil.init(); #end // diff --git a/source/funkin/mobile/external/android/CallbackUtil.hx b/source/funkin/external/android/CallbackUtil.hx similarity index 94% rename from source/funkin/mobile/external/android/CallbackUtil.hx rename to source/funkin/external/android/CallbackUtil.hx index fe04f27d7..4d7be5402 100644 --- a/source/funkin/mobile/external/android/CallbackUtil.hx +++ b/source/funkin/external/android/CallbackUtil.hx @@ -1,4 +1,4 @@ -package funkin.mobile.external.android; +package funkin.external.android; import lime.system.JNI; import flixel.util.FlxSignal; @@ -51,7 +51,7 @@ class CallbackUtil class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end { #if android - @:allow(funkin.mobile.external.android.CallbackUtil) + @:allow(funkin.external.android.CallbackUtil) function new():Void {} /** diff --git a/source/funkin/mobile/external/android/DataFolderUtil.hx b/source/funkin/external/android/DataFolderUtil.hx similarity index 92% rename from source/funkin/mobile/external/android/DataFolderUtil.hx rename to source/funkin/external/android/DataFolderUtil.hx index e18c2de64..a91327b1d 100644 --- a/source/funkin/mobile/external/android/DataFolderUtil.hx +++ b/source/funkin/external/android/DataFolderUtil.hx @@ -1,4 +1,4 @@ -package funkin.mobile.external.android; +package funkin.external.android; /** * A Utility class to manage the Application's Data folder on Android. diff --git a/source/funkin/mobile/external/android/JNIUtil.hx b/source/funkin/external/android/JNIUtil.hx similarity index 99% rename from source/funkin/mobile/external/android/JNIUtil.hx rename to source/funkin/external/android/JNIUtil.hx index 52d66e55e..cfebdfdad 100644 --- a/source/funkin/mobile/external/android/JNIUtil.hx +++ b/source/funkin/external/android/JNIUtil.hx @@ -1,4 +1,4 @@ -package funkin.mobile.external.android; +package funkin.external.android; #if android import lime.system.JNI; diff --git a/source/funkin/mobile/external/android/ScreenUtil.hx b/source/funkin/external/android/ScreenUtil.hx similarity index 97% rename from source/funkin/mobile/external/android/ScreenUtil.hx rename to source/funkin/external/android/ScreenUtil.hx index 54c6e8825..01a063b7a 100644 --- a/source/funkin/mobile/external/android/ScreenUtil.hx +++ b/source/funkin/external/android/ScreenUtil.hx @@ -1,4 +1,4 @@ -package funkin.mobile.external.android; +package funkin.external.android; import lime.math.Rectangle; import lime.system.JNI; diff --git a/source/funkin/mobile/external/android/java/funkin/extension/CallbackUtil.java b/source/funkin/external/android/java/funkin/extension/CallbackUtil.java similarity index 100% rename from source/funkin/mobile/external/android/java/funkin/extension/CallbackUtil.java rename to source/funkin/external/android/java/funkin/extension/CallbackUtil.java diff --git a/source/funkin/mobile/external/android/java/funkin/provider/DataFolderProvider.java b/source/funkin/external/android/java/funkin/provider/DataFolderProvider.java similarity index 100% rename from source/funkin/mobile/external/android/java/funkin/provider/DataFolderProvider.java rename to source/funkin/external/android/java/funkin/provider/DataFolderProvider.java diff --git a/source/funkin/mobile/external/android/java/funkin/util/DataFolderUtil.java b/source/funkin/external/android/java/funkin/util/DataFolderUtil.java similarity index 100% rename from source/funkin/mobile/external/android/java/funkin/util/DataFolderUtil.java rename to source/funkin/external/android/java/funkin/util/DataFolderUtil.java diff --git a/source/funkin/mobile/external/android/java/funkin/util/ScreenUtil.java b/source/funkin/external/android/java/funkin/util/ScreenUtil.java similarity index 100% rename from source/funkin/mobile/external/android/java/funkin/util/ScreenUtil.java rename to source/funkin/external/android/java/funkin/util/ScreenUtil.java diff --git a/source/funkin/mobile/external/ios/AudioSession.hx b/source/funkin/external/ios/AudioSession.hx similarity index 71% rename from source/funkin/mobile/external/ios/AudioSession.hx rename to source/funkin/external/ios/AudioSession.hx index deca7e3ce..44fc859c0 100644 --- a/source/funkin/mobile/external/ios/AudioSession.hx +++ b/source/funkin/external/ios/AudioSession.hx @@ -1,9 +1,9 @@ -package funkin.mobile.external.ios; +package funkin.external.ios; /** * A Utility class to manage iOS audio. */ -@:build(funkin.mobile.macros.LinkerMacro.xml('project/Build.xml')) +@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml')) @:include('AudioSession.hpp') @:unreflective extern class AudioSession diff --git a/source/funkin/mobile/external/ios/ScreenUtil.hx b/source/funkin/external/ios/ScreenUtil.hx similarity index 81% rename from source/funkin/mobile/external/ios/ScreenUtil.hx rename to source/funkin/external/ios/ScreenUtil.hx index 76d93a704..62fded218 100644 --- a/source/funkin/mobile/external/ios/ScreenUtil.hx +++ b/source/funkin/external/ios/ScreenUtil.hx @@ -1,9 +1,9 @@ -package funkin.mobile.external.ios; +package funkin.external.ios; /** * A Utility class to get iOS screen related informations. */ -@:build(funkin.mobile.macros.LinkerMacro.xml('project/Build.xml')) +@:build(funkin.util.macro.LinkerMacro.xml('project/Build.xml')) @:include('ScreenUtil.hpp') @:unreflective extern class ScreenUtil diff --git a/source/funkin/mobile/external/ios/project/Build.xml b/source/funkin/external/ios/project/Build.xml similarity index 100% rename from source/funkin/mobile/external/ios/project/Build.xml rename to source/funkin/external/ios/project/Build.xml diff --git a/source/funkin/mobile/external/ios/project/ios/include/AudioSession.hpp b/source/funkin/external/ios/project/ios/include/AudioSession.hpp similarity index 100% rename from source/funkin/mobile/external/ios/project/ios/include/AudioSession.hpp rename to source/funkin/external/ios/project/ios/include/AudioSession.hpp diff --git a/source/funkin/mobile/external/ios/project/ios/include/ScreenUtil.hpp b/source/funkin/external/ios/project/ios/include/ScreenUtil.hpp similarity index 100% rename from source/funkin/mobile/external/ios/project/ios/include/ScreenUtil.hpp rename to source/funkin/external/ios/project/ios/include/ScreenUtil.hpp diff --git a/source/funkin/mobile/external/ios/project/ios/src/AudioSession.mm b/source/funkin/external/ios/project/ios/src/AudioSession.mm similarity index 100% rename from source/funkin/mobile/external/ios/project/ios/src/AudioSession.mm rename to source/funkin/external/ios/project/ios/src/AudioSession.mm diff --git a/source/funkin/mobile/external/ios/project/ios/src/ScreenUtil.mm b/source/funkin/external/ios/project/ios/src/ScreenUtil.mm similarity index 100% rename from source/funkin/mobile/external/ios/project/ios/src/ScreenUtil.mm rename to source/funkin/external/ios/project/ios/src/ScreenUtil.mm diff --git a/source/funkin/mobile/util/ScreenUtil.hx b/source/funkin/mobile/util/ScreenUtil.hx index 27bf023f3..cb1a9c562 100644 --- a/source/funkin/mobile/util/ScreenUtil.hx +++ b/source/funkin/mobile/util/ScreenUtil.hx @@ -1,9 +1,9 @@ package funkin.mobile.util; #if ios -import funkin.mobile.external.ios.ScreenUtil as NativeScreenUtil; +import funkin.external.ios.ScreenUtil as NativeScreenUtil; #elseif android -import funkin.mobile.external.android.ScreenUtil as NativeScreenUtil; +import funkin.external.android.ScreenUtil as NativeScreenUtil; #end import lime.math.Rectangle; import lime.system.System; diff --git a/source/funkin/ui/options/OptionsState.hx b/source/funkin/ui/options/OptionsState.hx index 82aa62fe5..ebf538dff 100644 --- a/source/funkin/ui/options/OptionsState.hx +++ b/source/funkin/ui/options/OptionsState.hx @@ -198,7 +198,7 @@ class OptionsMenu extends Page #end #if android createItem("OPEN DATA FOLDER", function() { - funkin.mobile.external.android.DataFolderUtil.openDataFolder(); + funkin.external.android.DataFolderUtil.openDataFolder(); }); #end #if FEATURE_NEWGROUNDS diff --git a/source/funkin/mobile/macros/LinkerMacro.hx b/source/funkin/util/macro/LinkerMacro.hx similarity index 78% rename from source/funkin/mobile/macros/LinkerMacro.hx rename to source/funkin/util/macro/LinkerMacro.hx index 009bc193c..4b4ba4d3a 100644 --- a/source/funkin/mobile/macros/LinkerMacro.hx +++ b/source/funkin/util/macro/LinkerMacro.hx @@ -1,4 +1,4 @@ -package funkin.mobile.macros; +package funkin.util.macro; import haxe.macro.Context; import haxe.macro.Expr; @@ -16,14 +16,14 @@ class LinkerMacro { /** * Adds an XML `` element to the class's metadata, pointing to a specified build file. - * @param file_name The name of the XML file to include. Defaults to `Build.xml` if not provided. + * @param fileName The name of the XML file to include. Defaults to `Build.xml` if not provided. * @return An array of fields that are processed during the build. */ - public static macro function xml(?file_name:String = 'Build.xml'):Array + public static macro function xml(?fileName:String = 'Build.xml'):Array { final pos:Position = Context.currentPos(); final sourcePath:String = FileSystem.absolutePath(Context.getPosInfos(pos).file.directory()).removeTrailingSlashes(); - final fileToInclude:String = Path.join([sourcePath, file_name?.length > 0 ? file_name : 'Build.xml']); + final fileToInclude:String = Path.join([sourcePath, fileName?.length > 0 ? fileName : 'Build.xml']); if (!FileSystem.exists(fileToInclude)) Context.error('The specified file "$fileToInclude" could not be found at "$sourcePath".', pos); diff --git a/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx b/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx index 51424c1f3..077ac6672 100644 --- a/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx +++ b/source/funkin/util/plugins/ReloadAssetsDebugPlugin.hx @@ -5,7 +5,7 @@ import flixel.FlxBasic; import funkin.ui.MusicBeatState; import funkin.ui.MusicBeatSubState; #if android -import funkin.mobile.external.android.CallbackUtil; +import funkin.external.android.CallbackUtil; #end /** From c3a837b0548883b067a3062647df0f5d8efa3abc Mon Sep 17 00:00:00 2001 From: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> Date: Sat, 2 Aug 2025 22:32:48 +0300 Subject: [PATCH 68/89] Dont compile the android classes on other targets. --- source/funkin/external/android/CallbackUtil.hx | 6 ++---- source/funkin/external/android/DataFolderUtil.hx | 4 ++-- source/funkin/external/android/ScreenUtil.hx | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/source/funkin/external/android/CallbackUtil.hx b/source/funkin/external/android/CallbackUtil.hx index 4d7be5402..4715dad3f 100644 --- a/source/funkin/external/android/CallbackUtil.hx +++ b/source/funkin/external/android/CallbackUtil.hx @@ -1,5 +1,6 @@ package funkin.external.android; +#if android import lime.system.JNI; import flixel.util.FlxSignal; @@ -9,7 +10,6 @@ import flixel.util.FlxSignal; @:unreflective class CallbackUtil { - #if android /** * The result code for `DATA_FOLDER_CLOSED` activity. */ @@ -42,7 +42,6 @@ class CallbackUtil initCallBackJNI(new CallbackHandler()); } } - #end } /** @@ -50,7 +49,6 @@ class CallbackUtil */ class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end { - #if android @:allow(funkin.external.android.CallbackUtil) function new():Void {} @@ -68,5 +66,5 @@ class CallbackHandler #if (lime >= "8.0.0") implements JNISafety #end { if (CallbackUtil.onActivityResult != null) CallbackUtil.onActivityResult.dispatch(requestCode, resultCode); } - #end } +#end diff --git a/source/funkin/external/android/DataFolderUtil.hx b/source/funkin/external/android/DataFolderUtil.hx index a91327b1d..4d9a89a95 100644 --- a/source/funkin/external/android/DataFolderUtil.hx +++ b/source/funkin/external/android/DataFolderUtil.hx @@ -1,12 +1,12 @@ package funkin.external.android; +#if android /** * A Utility class to manage the Application's Data folder on Android. */ @:unreflective class DataFolderUtil { - #if android /** * Opens the data folder on an Android device using JNI. */ @@ -19,5 +19,5 @@ class DataFolderUtil openDataFolderJNI(CallbackUtil.DATA_FOLDER_CLOSED); } } - #end } +#end diff --git a/source/funkin/external/android/ScreenUtil.hx b/source/funkin/external/android/ScreenUtil.hx index 01a063b7a..fc271a91c 100644 --- a/source/funkin/external/android/ScreenUtil.hx +++ b/source/funkin/external/android/ScreenUtil.hx @@ -1,5 +1,6 @@ package funkin.external.android; +#if android import lime.math.Rectangle; import lime.system.JNI; @@ -9,7 +10,6 @@ import lime.system.JNI; @:unreflective class ScreenUtil { - #if android /** * Retrieves the dimensions of display cutouts (such as notches) on Android devices. * @@ -48,5 +48,5 @@ class ScreenUtil return []; } - #end } +#end From 7e01e7332b8c1169a4c76688d8cf2b8ac00f4f62 Mon Sep 17 00:00:00 2001 From: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> Date: Sat, 2 Aug 2025 22:59:22 +0300 Subject: [PATCH 69/89] Fix html5 compile. --- source/funkin/external/ios/AudioSession.hx | 2 ++ source/funkin/external/ios/ScreenUtil.hx | 2 ++ source/funkin/util/macro/LinkerMacro.hx | 39 +++++++++++++--------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/source/funkin/external/ios/AudioSession.hx b/source/funkin/external/ios/AudioSession.hx index 44fc859c0..bd31b4f65 100644 --- a/source/funkin/external/ios/AudioSession.hx +++ b/source/funkin/external/ios/AudioSession.hx @@ -1,5 +1,6 @@ package funkin.external.ios; +#if ios /** * A Utility class to manage iOS audio. */ @@ -13,3 +14,4 @@ extern class AudioSession @:native('setActive') static function setActive(active:Bool):Void; } +#end diff --git a/source/funkin/external/ios/ScreenUtil.hx b/source/funkin/external/ios/ScreenUtil.hx index 62fded218..8586e5f7a 100644 --- a/source/funkin/external/ios/ScreenUtil.hx +++ b/source/funkin/external/ios/ScreenUtil.hx @@ -1,5 +1,6 @@ package funkin.external.ios; +#if ios /** * A Utility class to get iOS screen related informations. */ @@ -14,3 +15,4 @@ extern class ScreenUtil @:native('getScreenSize') static function getScreenSize(width:cpp.RawPointer, height:cpp.RawPointer):Void; } +#end diff --git a/source/funkin/util/macro/LinkerMacro.hx b/source/funkin/util/macro/LinkerMacro.hx index 4b4ba4d3a..b0df1cee8 100644 --- a/source/funkin/util/macro/LinkerMacro.hx +++ b/source/funkin/util/macro/LinkerMacro.hx @@ -1,14 +1,8 @@ package funkin.util.macro; -import haxe.macro.Context; -import haxe.macro.Expr; -import haxe.xml.Printer; -import sys.FileSystem; - -using haxe.io.Path; - /** * This class provides a macro to include an XML build file in the metadata of a Haxe class. + * * The file must be located relative to the directory of the Haxe class that uses this macro. */ @:nullSafety @@ -19,19 +13,32 @@ class LinkerMacro * @param fileName The name of the XML file to include. Defaults to `Build.xml` if not provided. * @return An array of fields that are processed during the build. */ - public static macro function xml(?fileName:String = 'Build.xml'):Array + public static macro function xml(?fileName:String = 'Build.xml'):Array { - final pos:Position = Context.currentPos(); - final sourcePath:String = FileSystem.absolutePath(Context.getPosInfos(pos).file.directory()).removeTrailingSlashes(); - final fileToInclude:String = Path.join([sourcePath, fileName?.length > 0 ? fileName : 'Build.xml']); + final fields:Array = haxe.macro.Context.getBuildFields(); + final cls:haxe.macro.Type.ClassType = haxe.macro.Context.getLocalClass().get(); + final pos:haxe.macro.Expr.Position = haxe.macro.Context.currentPos(); - if (!FileSystem.exists(fileToInclude)) Context.error('The specified file "$fileToInclude" could not be found at "$sourcePath".', pos); + final sourcePath:String = haxe.io.Path.directory(haxe.macro.Context.getPosInfos(pos).file); + final absSourcePath:String = haxe.io.Path.removeTrailingSlashes(sys.FileSystem.absolutePath(sourcePath)); + final fileToInclude:String = haxe.io.Path.join([absSourcePath, fileName?.length > 0 ? fileName : 'Build.xml']); + + if (!sys.FileSystem.exists(fileToInclude)) + { + haxe.macro.Context.error('The specified file "$fileToInclude" could not be found at "$absSourcePath".', pos); + } final includeElement:Xml = Xml.createElement('include'); - includeElement.set('name', fileToInclude); - Context.getLocalClass().get().meta.add(':buildXml', [ - {expr: EConst(CString(Printer.print(includeElement, true))), pos: pos}], pos); - return Context.getBuildFields(); + includeElement.set('name', fileToInclude); + + cls.meta.add(':buildXml', [ + { + expr: EConst(CString(haxe.xml.Printer.print(includeElement, true))), + pos: pos + } + ], pos); + + return fields; } } From 8cd3785e3c3e913ed791204814eba86eb7142a5d Mon Sep 17 00:00:00 2001 From: AwesomezPro99 Date: Sat, 2 Aug 2025 19:31:44 -0400 Subject: [PATCH 70/89] Fix Blazin' Death Pause --- source/funkin/play/PlayState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index 0c802e326..37bebee57 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -1230,7 +1230,7 @@ class PlayState extends MusicBeatSubState function pause(?mode:PauseMode = Standard):Void { - if (!mayPauseGame || justUnpaused || isGamePaused) return; + if (!mayPauseGame || justUnpaused || isGamePaused || isPlayerDying) return; switch (mode) { From 1d8ed5198c0a336406a8db88a2be61c72096c050 Mon Sep 17 00:00:00 2001 From: "trofim.al" <127919576+Trofem@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:04:20 +1100 Subject: [PATCH 71/89] api version 0.7.0 Update example mod's API version so it actually loads. part 2. --- example_mods/introMod/_polymod_meta.json | 2 +- example_mods/testing123/_polymod_meta.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example_mods/introMod/_polymod_meta.json b/example_mods/introMod/_polymod_meta.json index 74c4a2504..cdc43efbe 100644 --- a/example_mods/introMod/_polymod_meta.json +++ b/example_mods/introMod/_polymod_meta.json @@ -6,7 +6,7 @@ "name": "EliteMasterEric" } ], - "api_version": "0.5.0", + "api_version": "0.7.0", "mod_version": "1.0.0", "license": "Apache-2.0" } diff --git a/example_mods/testing123/_polymod_meta.json b/example_mods/testing123/_polymod_meta.json index b8fc60b11..54a6fd582 100644 --- a/example_mods/testing123/_polymod_meta.json +++ b/example_mods/testing123/_polymod_meta.json @@ -6,7 +6,7 @@ "name": "EliteMasterEric" } ], - "api_version": "0.5.0", + "api_version": "0.7.0", "mod_version": "1.0.0", "license": "Apache-2.0" } From 3f82f3792b056a03724a0d1a320b7549a7b59844 Mon Sep 17 00:00:00 2001 From: Lasercar Date: Sun, 23 Feb 2025 13:53:51 +1000 Subject: [PATCH 72/89] Set playspeed after variation load --- .../ui/debug/charting/ChartEditorState.hx | 40 ++++++++++++++++++- .../handlers/ChartEditorAudioHandler.hx | 4 ++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 14dc92afe..7c614d37b 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -6364,20 +6364,30 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState fadeInWelcomeMusic(WELCOME_MUSIC_FADE_IN_DELAY, WELCOME_MUSIC_FADE_IN_DURATION); - // Reapply the volume. + // Reapply the volume and playback rate. var instTargetVolume:Float = menubarItemVolumeInstrumental.value / 100.0 ?? 1.0; var vocalPlayerTargetVolume:Float = menubarItemVolumeVocalsPlayer.value / 100.0 ?? 1.0; var vocalOpponentTargetVolume:Float = menubarItemVolumeVocalsOpponent.value / 100.0 ?? 1.0; + var playbackRate = ((menubarItemPlaybackSpeed.value ?? 1.0) * 2.0) / 100.0; + playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% + playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% + if (audioInstTrack != null) { audioInstTrack.volume = instTargetVolume; + #if FLX_PITCH + audioInstTrack.pitch = playbackRate; + #end audioInstTrack.onComplete = null; } if (audioVocalTrackGroup != null) { audioVocalTrackGroup.playerVolume = vocalPlayerTargetVolume; audioVocalTrackGroup.opponentVolume = vocalOpponentTargetVolume; + #if FLX_PITCH + audioVocalTrackGroup.pitch = playbackRate; + #end } } @@ -6532,6 +6542,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState public function postLoadInstrumental():Void { + // Reapply the volume and playback rate. + var instTargetVolume:Float = menubarItemVolumeInstrumental.value ?? 1.0; + var playbackRate = ((menubarItemPlaybackSpeed.value ?? 1.0) * 2.0) / 100.0; + playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% + playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% if (audioInstTrack != null) { // Prevent the time from skipping back to 0 when the song ends. @@ -6544,6 +6559,10 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState } audioVocalTrackGroup.pause(); }; + audioInstTrack.volume = instTargetVolume; + #if FLX_PITCH + audioInstTrack.pitch = playbackRate; + #end } else { @@ -6560,6 +6579,25 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState healthIconsDirty = true; } + public function postLoadVocals():Void + { + // Reapply the volume and playback rate. + var vocalPlayerTargetVolume:Float = menubarItemVolumeVocalsPlayer.value ?? 1.0; + var vocalOpponentTargetVolume:Float = menubarItemVolumeVocalsOpponent.value ?? 1.0; + var playbackRate = ((menubarItemPlaybackSpeed.value ?? 1.0) * 2.0) / 100.0; + playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% + playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% + + if (audioVocalTrackGroup != null) + { + audioVocalTrackGroup.playerVolume = vocalPlayerTargetVolume; + audioVocalTrackGroup.opponentVolume = vocalOpponentTargetVolume; + #if FLX_PITCH + audioVocalTrackGroup.pitch = playbackRate; + #end + } + } + function hardRefreshOffsetsToolbox():Void { var offsetsToolbox:ChartEditorOffsetsToolbox = cast this.getToolbox(CHART_EDITOR_TOOLBOX_OFFSETS_LAYOUT); diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 61a831b45..87a5e31e2 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -137,6 +137,10 @@ class ChartEditorAudioHandler result = playVocals(state, DAD, opponentId, instId); // if (!result) return false; + state.postLoadVocals(); + + var perfE:Float = TimerUtil.start(); + state.hardRefreshOffsetsToolbox(); state.hardRefreshFreeplayToolbox(); From cd99905bdfd635eb6d7c3ea193231ef61009e63e Mon Sep 17 00:00:00 2001 From: Lasercar <64717068+Lasercar@users.noreply.github.com> Date: Fri, 16 May 2025 22:11:08 +1000 Subject: [PATCH 73/89] Fix volumes being reset to 100% after playtest --- .../ui/debug/charting/ChartEditorState.hx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 7c614d37b..35165d91c 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -5891,7 +5891,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var startTimestamp:Float = 0; if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs; - var playbackRate:Float = ((menubarItemPlaybackSpeed.value ?? 1.0) * 2.0) / 100.0; + var playbackRate:Float = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0; playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% @@ -6365,11 +6365,11 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState fadeInWelcomeMusic(WELCOME_MUSIC_FADE_IN_DELAY, WELCOME_MUSIC_FADE_IN_DURATION); // Reapply the volume and playback rate. - var instTargetVolume:Float = menubarItemVolumeInstrumental.value / 100.0 ?? 1.0; - var vocalPlayerTargetVolume:Float = menubarItemVolumeVocalsPlayer.value / 100.0 ?? 1.0; - var vocalOpponentTargetVolume:Float = menubarItemVolumeVocalsOpponent.value / 100.0 ?? 1.0; + var instTargetVolume:Float = (menubarItemVolumeInstrumental.value / 100.0) ?? 1.0; + var vocalPlayerTargetVolume:Float = (menubarItemVolumeVocalsPlayer.value / 100.0) ?? 1.0; + var vocalOpponentTargetVolume:Float = (menubarItemVolumeVocalsOpponent.value / 100.0) ?? 1.0; - var playbackRate = ((menubarItemPlaybackSpeed.value ?? 1.0) * 2.0) / 100.0; + var playbackRate = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0; playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% @@ -6543,8 +6543,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState public function postLoadInstrumental():Void { // Reapply the volume and playback rate. - var instTargetVolume:Float = menubarItemVolumeInstrumental.value ?? 1.0; - var playbackRate = ((menubarItemPlaybackSpeed.value ?? 1.0) * 2.0) / 100.0; + var instTargetVolume:Float = ((menubarItemVolumeInstrumental.value / 100) ?? 1.0); + var playbackRate:Float = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0; playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% if (audioInstTrack != null) @@ -6582,9 +6582,9 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState public function postLoadVocals():Void { // Reapply the volume and playback rate. - var vocalPlayerTargetVolume:Float = menubarItemVolumeVocalsPlayer.value ?? 1.0; - var vocalOpponentTargetVolume:Float = menubarItemVolumeVocalsOpponent.value ?? 1.0; - var playbackRate = ((menubarItemPlaybackSpeed.value ?? 1.0) * 2.0) / 100.0; + var vocalPlayerTargetVolume:Float = (menubarItemVolumeVocalsPlayer.value / 100.0) ?? 1.0; + var vocalOpponentTargetVolume:Float = (menubarItemVolumeVocalsOpponent.value / 100.0) ?? 1.0; + var playbackRate:Float = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0; playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% From d72b989bf5b7c24b3d9ba79663820373d9a2cbf9 Mon Sep 17 00:00:00 2001 From: MoonDroid <81515012+moondroidcoder@users.noreply.github.com> Date: Sun, 3 Aug 2025 19:36:36 +0700 Subject: [PATCH 74/89] Delete useless line --- .../ui/debug/charting/handlers/ChartEditorAudioHandler.hx | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx index 87a5e31e2..f01464893 100644 --- a/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx +++ b/source/funkin/ui/debug/charting/handlers/ChartEditorAudioHandler.hx @@ -139,8 +139,6 @@ class ChartEditorAudioHandler state.postLoadVocals(); - var perfE:Float = TimerUtil.start(); - state.hardRefreshOffsetsToolbox(); state.hardRefreshFreeplayToolbox(); From 19fdda7cd9a035dd5cbb835a26b9408d649278f5 Mon Sep 17 00:00:00 2001 From: MoonDroid <81515012+moondroidcoder@users.noreply.github.com> Date: Sun, 3 Aug 2025 19:38:30 +0700 Subject: [PATCH 75/89] fix: Call `Math.round` and `FlxMath.clamp` for better readability and slightly better functionality what a mouthful :( --- .../ui/debug/charting/ChartEditorState.hx | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx index 35165d91c..0f2b6b946 100644 --- a/source/funkin/ui/debug/charting/ChartEditorState.hx +++ b/source/funkin/ui/debug/charting/ChartEditorState.hx @@ -5184,7 +5184,7 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var variationMetadata:Null = songMetadata.get(selectedVariation); if (variationMetadata != null) - variationMetadata.playData.difficulties.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL)); + variationMetadata.playData.difficulties.sort(SortUtil.defaultsThenAlphabetically.bind(Constants.DEFAULT_DIFFICULTY_LIST_FULL)); var difficultyToolbox:ChartEditorDifficultyToolbox = cast this.getToolbox(CHART_EDITOR_TOOLBOX_DIFFICULTY_LAYOUT); if (difficultyToolbox == null) return; @@ -5620,14 +5620,16 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState @:nullSafety(Off) function quitChartEditor():Void { - if (saveDataDirty) { - Dialogs.messageBox("You are about to leave the editor without saving.\n\nAre you sure?", "Leave Editor", MessageBoxType.TYPE_YESNO, true, function(button:DialogButton) { - if (button == DialogButton.YES) - { - autoSave(); - quitChartEditor(); - } - }); + if (saveDataDirty) + { + Dialogs.messageBox("You are about to leave the editor without saving.\n\nAre you sure?", "Leave Editor", MessageBoxType.TYPE_YESNO, true, + function(button:DialogButton) { + if (button == DialogButton.YES) + { + autoSave(); + quitChartEditor(); + } + }); return; } @@ -5892,8 +5894,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState if (playtestStartTime) startTimestamp = scrollPositionInMs + playheadPositionInMs; var playbackRate:Float = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0; - playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% - playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% + playbackRate = Math.round(playbackRate / 0.05) * 0.05; // Round to nearest 5% + playbackRate = FlxMath.clamp(playbackRate, 0.05, 2.0); // Clamp to 5% to 200% var targetSong:Song; try @@ -6370,8 +6372,8 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState var vocalOpponentTargetVolume:Float = (menubarItemVolumeVocalsOpponent.value / 100.0) ?? 1.0; var playbackRate = ((menubarItemPlaybackSpeed.value / 100.0) ?? 0.5) * 2.0; - playbackRate = Math.floor(playbackRate / 0.05) * 0.05; // Round to nearest 5% - playbackRate = Math.max(0.05, Math.min(2.0, playbackRate)); // Clamp to 5% to 200% + playbackRate = Math.round(playbackRate / 0.05) * 0.05; // Round to nearest 5% + playbackRate = FlxMath.clamp(playbackRate, 0.05, 2.0); // Clamp to 5% to 200% if (audioInstTrack != null) { From e281bdddca5f31982bdc620e8ea92b080dcd416a Mon Sep 17 00:00:00 2001 From: MrMadera Date: Sun, 3 Aug 2025 13:15:40 +0100 Subject: [PATCH 76/89] south button now works! --- source/funkin/ui/title/TitleState.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index f90c3f928..3ae94b5c6 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -282,7 +282,7 @@ class TitleState extends MusicBeatState if (gamepad != null) { - if (gamepad.justPressed.START) pressedEnter = true; + if (gamepad.justPressed.START || gamepad.justPressed.ACCEPT) pressedEnter = true; } // If you spam Enter, we should skip the transition. From 86e69cfbd1b1bd3daa49181b827fa813cb716703 Mon Sep 17 00:00:00 2001 From: AwesomezPro99 Date: Sun, 3 Aug 2025 09:32:41 -0400 Subject: [PATCH 77/89] Fixed Window Title When Pressing F4 --- source/funkin/ui/MusicBeatState.hx | 7 ++++++- source/funkin/ui/MusicBeatSubState.hx | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/MusicBeatState.hx b/source/funkin/ui/MusicBeatState.hx index da70ec012..af71b0f6c 100644 --- a/source/funkin/ui/MusicBeatState.hx +++ b/source/funkin/ui/MusicBeatState.hx @@ -12,6 +12,7 @@ import funkin.modding.PolymodHandler; import funkin.modding.events.ScriptEvent; import funkin.modding.module.ModuleHandler; import funkin.util.SortUtil; +import funkin.util.WindowUtil; import funkin.input.Controls; #if mobile import funkin.graphics.FunkinCamera; @@ -156,7 +157,11 @@ class MusicBeatState extends FlxTransitionableState implements IEventHandler function handleFunctionControls():Void { // Emergency exit button. - if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState()); + if (FlxG.keys.justPressed.F4) + { + FlxG.switchState(() -> new MainMenuState()); + WindowUtil.setWindowTitle('Friday Night Funkin\''); + } } override function update(elapsed:Float) diff --git a/source/funkin/ui/MusicBeatSubState.hx b/source/funkin/ui/MusicBeatSubState.hx index 42bf8d249..0298c1097 100644 --- a/source/funkin/ui/MusicBeatSubState.hx +++ b/source/funkin/ui/MusicBeatSubState.hx @@ -10,6 +10,7 @@ import funkin.modding.IScriptedClass.IEventHandler; import funkin.modding.module.ModuleHandler; import funkin.modding.PolymodHandler; import funkin.util.SortUtil; +import funkin.util.WindowUtil; import flixel.util.FlxSort; import funkin.input.Controls; #if mobile @@ -140,7 +141,11 @@ class MusicBeatSubState extends FlxSubState implements IEventHandler super.update(elapsed); // Emergency exit button. - if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState()); + if (FlxG.keys.justPressed.F4) + { + FlxG.switchState(() -> new MainMenuState()); + WindowUtil.setWindowTitle('Friday Night Funkin\''); + } // Display Conductor info in the watch window. FlxG.watch.addQuick("musicTime", FlxG.sound.music?.time ?? 0.0); From b12874107513af0c9e96875e555939ad4a1e85c4 Mon Sep 17 00:00:00 2001 From: Kolo <67389779+JustKolosaki@users.noreply.github.com> Date: Sat, 2 Aug 2025 20:15:22 +0200 Subject: [PATCH 78/89] mb gang --- source/funkin/ui/freeplay/FreeplayState.hx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 9a2637bdf..4f765aefe 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -2193,8 +2193,6 @@ class FreeplayState extends MusicBeatSubState */ function changeDiff(change:Int = 0, force:Bool = false, capsuleAnim:Bool = false):Void { - if (!controls.active && !force) return; - if (capsuleAnim) { if (currentCapsule != null) @@ -2716,7 +2714,7 @@ class FreeplayState extends MusicBeatSubState if (currentCapsule.freeplayData == null) albumRoll.albumId = null; - changeDiff(0, true); + changeDiff(); if (currentCapsule.freeplayData == null) currentCapsule.refreshDisplay(); else currentCapsule.refreshDisplay(!prepForNewRank); From 4ecef6db744bab7e3fe5de0d680d813cc3bd1ea7 Mon Sep 17 00:00:00 2001 From: JackXson-Real <32755192+JackXson-Real@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:38:31 -0400 Subject: [PATCH 79/89] increase preview volume to 80% --- 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 4f765aefe..94c3b3777 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -2793,7 +2793,7 @@ class FreeplayState extends MusicBeatSubState end: 0.2 }, onLoad: function() { - FlxG.sound.music.fadeIn(2, 0, 0.4); + FlxG.sound.music.fadeIn(2, 0, 0.8); } }); if (songDifficulty != null) From 9c1e22309b43e959d7b73d41083d42b4b1c61496 Mon Sep 17 00:00:00 2001 From: JackXson-Real <32755192+JackXson-Real@users.noreply.github.com> Date: Sun, 3 Aug 2025 16:46:16 -0400 Subject: [PATCH 80/89] decrease preview volume from 80% to 70% --- source/funkin/ui/freeplay/FreeplayState.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 94c3b3777..f0a4d4391 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -2756,7 +2756,7 @@ class FreeplayState extends MusicBeatSubState overrideExisting: true, restartTrack: false }); - FlxG.sound.music.fadeIn(2, 0, 0.8); + FlxG.sound.music.fadeIn(2, 0, 0.7); } else { @@ -2793,7 +2793,7 @@ class FreeplayState extends MusicBeatSubState end: 0.2 }, onLoad: function() { - FlxG.sound.music.fadeIn(2, 0, 0.8); + FlxG.sound.music.fadeIn(2, 0, 0.7); } }); if (songDifficulty != null) From 64f4620b5a3488440da705f903930bf56cfcc653 Mon Sep 17 00:00:00 2001 From: Abnormal <86753001+AbnormalPoof@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:25:26 -0500 Subject: [PATCH 81/89] dude........fuck yes. --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index a8d15febf..3f2be8e42 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit a8d15febf5e37a4fc7ffeb0d3eff9c4d36457a37 +Subproject commit 3f2be8e428588f53fc1b2b35300e18936a86300e From 293b70bbed00895bcef4a4535ed5da87363dc01b Mon Sep 17 00:00:00 2001 From: Abnormal <86753001+AbnormalPoof@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:32:21 -0500 Subject: [PATCH 82/89] update the submod --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 3f2be8e42..441f505e7 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 3f2be8e428588f53fc1b2b35300e18936a86300e +Subproject commit 441f505e7a907bc9f4f86f587707a3dda80cab2b From fb4967c94be2ee91a3c7e6e3e270db6f6d932e08 Mon Sep 17 00:00:00 2001 From: Abnormal <86753001+AbnormalPoof@users.noreply.github.com> Date: Mon, 4 Aug 2025 11:46:24 -0500 Subject: [PATCH 83/89] fix the pico blazin death confirm offsets Co-authored-by: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 441f505e7..305a08e14 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 441f505e7a907bc9f4f86f587707a3dda80cab2b +Subproject commit 305a08e145f7dab3db972ace8ac4d4d4b445b686 From 086f28fef11d99c89cd2d6bd3213411b3bddd29a Mon Sep 17 00:00:00 2001 From: Abnormal <86753001+AbnormalPoof@users.noreply.github.com> Date: Mon, 4 Aug 2025 11:51:50 -0500 Subject: [PATCH 84/89] fix invisible pixels on some sprites Co-authored-by: MAJigsaw77 <77043862+MAJigsaw77@users.noreply.github.com> --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index 305a08e14..d3b2c43e9 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 305a08e145f7dab3db972ace8ac4d4d4b445b686 +Subproject commit d3b2c43e9f61352bc14905694612283bfa3599bb From 1d8679941e7b614b2b80df4eacfea9cec6d5ebda Mon Sep 17 00:00:00 2001 From: Abnormal <86753001+AbnormalPoof@users.noreply.github.com> Date: Mon, 4 Aug 2025 11:02:09 -0500 Subject: [PATCH 85/89] fix tankman variant names --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index d3b2c43e9..b7c062e46 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit d3b2c43e9f61352bc14905694612283bfa3599bb +Subproject commit b7c062e46b85bf83a046546b140991fcfdc26359 From 34ce13c5dfd52760675a1d09efa489bcb108e607 Mon Sep 17 00:00:00 2001 From: Hyper_ <40342021+NotHyper-474@users.noreply.github.com> Date: Wed, 23 Apr 2025 00:31:58 -0300 Subject: [PATCH 86/89] chore: Add null safety to `PlayState` and `LoadingState` --- source/funkin/play/GameOverSubState.hx | 12 +- source/funkin/play/GitarooPause.hx | 4 +- source/funkin/play/PlayState.hx | 500 +++++++++++--------- source/funkin/play/components/HealthIcon.hx | 6 +- source/funkin/play/notes/Strumline.hx | 8 +- source/funkin/ui/transition/LoadingState.hx | 54 ++- 6 files changed, 337 insertions(+), 247 deletions(-) diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index ad30e99f1..f5fc04569 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -162,7 +162,7 @@ class GameOverSubState extends MusicBeatSubState if ((parentPlayState?.isMinimalMode ?? true)) {} else { - boyfriend = parentPlayState?.currentStage.getBoyfriend(true); + boyfriend = parentPlayState?.currentStage?.getBoyfriend(true); if (boyfriend != null) { boyfriend.canPlayOtherAnims = true; @@ -193,10 +193,9 @@ class GameOverSubState extends MusicBeatSubState }); } - @:nullSafety(Off) function setCameraTarget():Void { - if ((parentPlayState?.isMinimalMode ?? true) || boyfriend == null) return; + if (parentPlayState == null || parentPlayState.isMinimalMode || boyfriend == null) return; // Assign a camera follow point to the boyfriend's position. cameraFollowPoint = new FlxObject(parentPlayState.cameraFollowPoint.x, parentPlayState.cameraFollowPoint.y, 1, 1); @@ -207,6 +206,7 @@ class GameOverSubState extends MusicBeatSubState cameraFollowPoint.y += offsets[1]; add(cameraFollowPoint); + @:nullSafety(Off) FlxG.camera.target = null; FlxG.camera.follow(cameraFollowPoint, LOCKON, Constants.DEFAULT_CAMERA_FOLLOW_RATE / 2); targetCameraZoom = (parentPlayState?.currentStage?.camZoom ?? 1.0) * boyfriend.getDeathCameraZoom(); @@ -394,7 +394,7 @@ class GameOverSubState extends MusicBeatSubState // Readd Boyfriend to the stage. boyfriend.isDead = false; remove(boyfriend); - parentPlayState?.currentStage.addCharacter(boyfriend, BF); + parentPlayState?.currentStage?.addCharacter(boyfriend, BF); } // Snap reset the camera which may have changed because of the player character data. @@ -564,11 +564,11 @@ class GameOverSubState extends MusicBeatSubState PlayStatePlaylist.reset(); } - var stickerPackId:Null = parentPlayState?.currentChart.stickerPack; + var stickerPackId:Null = parentPlayState?.currentChart?.stickerPack; if (stickerPackId == null) { - var playerCharacterId:Null = PlayerRegistry.instance.getCharacterOwnerId(parentPlayState?.currentChart.characters.player); + var playerCharacterId:Null = PlayerRegistry.instance.getCharacterOwnerId(parentPlayState?.currentChart?.characters.player); var playerCharacter:Null = PlayerRegistry.instance.fetchEntry(playerCharacterId ?? Constants.DEFAULT_CHARACTER); if (playerCharacter != null) diff --git a/source/funkin/play/GitarooPause.hx b/source/funkin/play/GitarooPause.hx index 8a2d7f3f9..9df525629 100644 --- a/source/funkin/play/GitarooPause.hx +++ b/source/funkin/play/GitarooPause.hx @@ -18,9 +18,9 @@ class GitarooPause extends MusicBeatState var replaySelect:Bool = false; - var previousParams:PlayStateParams; + var previousParams:Null; - public function new(previousParams:PlayStateParams):Void + public function new(?previousParams:PlayStateParams):Void { super(); diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index a1f222703..d7679d354 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -146,6 +146,7 @@ typedef PlayStateParams = * The gameplay state, where all the rhythm gaming happens. * SubState so it can be loaded as a child of the chart editor. */ +@:nullSafety class PlayState extends MusicBeatSubState { /** @@ -157,13 +158,13 @@ class PlayState extends MusicBeatSubState * The currently active PlayState. * There should be only one PlayState in existance at a time, we can use a singleton. */ - public static var instance:PlayState = null; + public static var instance:Null; /** * This sucks. We need this because FlxG.resetState(); assumes the constructor has no arguments. * @see https://github.com/HaxeFlixel/flixel/issues/2541 */ - static var lastParams:PlayStateParams = null; + static var lastParams:Null; /** * PUBLIC INSTANCE VARIABLES @@ -173,7 +174,7 @@ class PlayState extends MusicBeatSubState /** * The currently selected stage. */ - public var currentSong:Song = null; + public var currentSong:Song; /** * The currently selected difficulty. @@ -194,7 +195,7 @@ class PlayState extends MusicBeatSubState /** * The currently active Stage. This is the object containing all the props. */ - public var currentStage:Stage = null; + public var currentStage:Null = null; /** * Gets set to true when the PlayState needs to reset (player opted to restart or died). @@ -250,12 +251,12 @@ class PlayState extends MusicBeatSubState * An FlxTween that tweens the camera to the follow point. * Only used when tweening the camera manually, rather than tweening via follow. */ - public var cameraFollowTween:FlxTween; + public var cameraFollowTween:Null; /** * An FlxTween that zooms the camera to the desired amount. */ - public var cameraZoomTween:FlxTween; + public var cameraZoomTween:Null; /** * An FlxTween that changes the additive speed to the desired amount. @@ -266,7 +267,7 @@ class PlayState extends MusicBeatSubState * The camera follow point from the last stage. * Used to persist the position of the `cameraFollowPosition` between levels. */ - public var previousCameraFollowPoint:FlxPoint = null; + public var previousCameraFollowPoint:Null; /** * The current camera zoom level without any modifiers applied. @@ -380,7 +381,7 @@ class PlayState extends MusicBeatSubState /** * The current dialogue. */ - public var currentConversation:Conversation; + public var currentConversation:Null; /** * Key press inputs which have been received but not yet processed. @@ -399,6 +400,11 @@ class PlayState extends MusicBeatSubState */ var justUnpaused:Bool = false; + /** + * The current note style used by the song. + */ + var noteStyle:NoteStyle; + /** * PRIVATE INSTANCE VARIABLES * Private instance variables should be used for information that must be reset or dereferenced @@ -408,7 +414,7 @@ class PlayState extends MusicBeatSubState * The Array containing the upcoming song events. * The `update()` function regularly shifts these out to trigger events. */ - var songEvents:Array; + var songEvents:Array = []; /** * If true, the player is allowed to pause the game. @@ -467,7 +473,7 @@ class PlayState extends MusicBeatSubState /** * A group of audio tracks, used to play the song's vocals. */ - public var vocals:VoicesGroup; + public var vocals:Null; #if FEATURE_DISCORD_RPC // Discord RPC variables @@ -498,12 +504,12 @@ class PlayState extends MusicBeatSubState /** * The health icon representing the player. */ - public var iconP1:HealthIcon; + public var iconP1:Null; /** * The health icon representing the opponent. */ - public var iconP2:HealthIcon; + public var iconP2:Null; /** * The sprite group containing active player's strumline notes. @@ -594,9 +600,9 @@ class PlayState extends MusicBeatSubState * Data for the current difficulty for the current song. * Includes chart data, scroll speed, and other information. */ - public var currentChart(get, never):SongDifficulty; + public var currentChart(get, never):Null; - function get_currentChart():SongDifficulty + function get_currentChart():Null { if (currentSong == null || currentDifficulty == null) return null; return currentSong.getDifficulty(currentDifficulty, currentVariation); @@ -610,8 +616,8 @@ class PlayState extends MusicBeatSubState function get_currentStageId():String { - if (currentChart == null || currentChart.stage == null || currentChart.stage == '') return Constants.DEFAULT_STAGE; - return currentChart.stage; + var stage:String = currentChart?.stage ?? ''; + return stage == '' ? Constants.DEFAULT_STAGE : stage; } /** @@ -621,7 +627,7 @@ class PlayState extends MusicBeatSubState function get_currentSongLengthMs():Float { - return FlxG?.sound?.music?.length; + return FlxG.sound.music?.length ?? 0; } /** @@ -642,27 +648,20 @@ class PlayState extends MusicBeatSubState * @param params The parameters used to initialize the PlayState. * Includes information about what song to play and more. */ - public function new(params:PlayStateParams) + public function new(?params:PlayStateParams) { super(); // Validate parameters. - if (params == null && lastParams == null) - { - throw 'PlayState constructor called with no available parameters.'; - } - else if (params == null) - { - trace('WARNING: PlayState constructor called with no parameters. Reusing previous parameters.'); - params = lastParams; - } - else - { - lastParams = params; - } + var params:PlayStateParams = params ?? + { + trace('WARNING: PlayState constructor called with no parameters. Reusing previous parameters.'); + lastParams ?? throw 'PlayState constructor called with no available parameters.'; + } + lastParams = params; // Apply parameters. - currentSong = params.targetSong; + currentSong = params.targetSong ?? throw "targetSong should not be null"; if (params.targetDifficulty != null) currentDifficulty = params.targetDifficulty; previousDifficulty = currentDifficulty; if (params.targetVariation != null) currentVariation = params.targetVariation; @@ -675,26 +674,7 @@ class PlayState extends MusicBeatSubState overrideMusic = params.overrideMusic ?? false; previousCameraFollowPoint = params.cameraFollowPoint; - // Don't do anything else here! Wait until create() when we attach to the camera. - } - - /** - * Called when the PlayState is switched to. - */ - public override function create():Void - { - if (instance != null) - { - // TODO: Do something in this case? IDK. - trace('WARNING: PlayState instance already exists. This should not happen.'); - } - instance = this; - #if !mobile - // TODO: Figure out how to do the flair for charting mode!! I can't figure it out for the love of god. -Zack - if (!isChartingMode) FlxG.autoPause = false; - #end - - if (!assertChartExists()) return; + // Basic object initialization // TODO: Add something to toggle this on! if (false) @@ -712,6 +692,58 @@ class PlayState extends MusicBeatSubState cameraFollowPoint = new FlxObject(0, 0); } + // Cameras + camGame = new FunkinCamera('playStateCamGame'); + camHUD = new FlxCamera(); + camCutscene = new FlxCamera(); + camCutouts = new FlxCamera(); + + var currentChart = currentSong.getDifficulty(currentDifficulty, currentVariation); + var noteStyleId:Null = currentChart?.noteStyle; + var nulNoteStyle:Null = NoteStyleRegistry.instance.fetchEntry(noteStyleId ?? Constants.DEFAULT_NOTE_STYLE); + if (nulNoteStyle == null) throw "Failed to retrieve both note style and default note style. This shouldn't happen!"; + noteStyle = nulNoteStyle; + + // Strumlines + playerStrumline = new Strumline(noteStyle, !isBotPlayMode, currentChart?.scrollSpeed); + opponentStrumline = new Strumline(noteStyle, false, currentChart?.scrollSpeed); + + // Healthbar + healthBarBG = FunkinSprite.create(0, 0, 'healthBar'); + healthBar = new FlxBar(0, 0, RIGHT_TO_LEFT, Std.int(healthBarBG.width - 8), Std.int(healthBarBG.height - 8), null, 0, 2); + scoreText = new FlxText(0, 0, 0, '', 20); + + // Combo & Pop Up + comboPopUps = new PopUpStuff(noteStyle); + + // Pause sprites + #if mobile + pauseButton = FunkinSprite.createSparrow(0, 0, "pauseButton"); + pauseCircle = FunkinSprite.create(0, 0, 'pauseCircle'); + #end + + // Don't do anything else here! Wait until create() when we attach to the camera. + } + + /** + * Called when the PlayState is switched to. + */ + @:nullSafety(Off) + public override function create():Void + { + if (instance != null) + { + // TODO: Do something in this case? IDK. + trace('WARNING: PlayState instance already exists. This should not happen.'); + } + instance = this; + #if !mobile + // TODO: Figure out how to do the flair for charting mode!! I can't figure it out for the love of god. -Zack + if (!isChartingMode) FlxG.autoPause = false; + #end + + if (!assertChartExists()) return; + #if mobile // Force allowScreenTimeout to be disabled lime.system.System.allowScreenTimeout = false; @@ -723,11 +755,11 @@ class PlayState extends MusicBeatSubState this.persistentDraw = true; // Stop any pre-existing music. - if (!overrideMusic && FlxG.sound.music != null) FlxG.sound.music.stop(); - - // Prepare the current song's instrumental and vocals to be played. - if (!overrideMusic && currentChart != null) + if (!overrideMusic) { + if (FlxG.sound.music != null) FlxG.sound.music.stop(); + + // Prepare the current song's instrumental and vocals to be played. currentChart.cacheInst(currentInstrumental); currentChart.cacheVocals(); } @@ -767,13 +799,16 @@ class PlayState extends MusicBeatSubState { // Initialize the hitbox for mobile controls addHitbox(false); - hitbox.isPixel = currentChart.noteStyle == "pixel"; - - if (Preferences.controlsScheme == FunkinHitboxControlSchemes.Arrows) + if (hitbox != null) { - for (direction in Strumline.DIRECTIONS) + hitbox.isPixel = currentChart.noteStyle == "pixel"; + + if (Preferences.controlsScheme == FunkinHitboxControlSchemes.Arrows) { - hitbox.getFirstHintByDirection(direction).follow(playerStrumline.getByDirection(direction)); + for (direction in Strumline.DIRECTIONS) + { + hitbox.getFirstHintByDirection(direction).follow(playerStrumline.getByDirection(direction)); + } } } } @@ -806,7 +841,7 @@ class PlayState extends MusicBeatSubState startingSong = true; // TODO: We hardcoded the transition into Winter Horrorland. Do this with a ScriptedSong instead. - if ((currentSong?.id ?? '').toLowerCase() == 'winter-horrorland') + if ((currentSong.id ?? '').toLowerCase() == 'winter-horrorland') { // VanillaCutscenes will call startCountdown later. VanillaCutscenes.playHorrorStartCutscene(); @@ -821,31 +856,7 @@ class PlayState extends MusicBeatSubState // Create the pause button. #if mobile - pauseButton = FunkinSprite.createSparrow(0, 0, "pauseButton"); - pauseButton.animation.addByIndices('idle', 'back', [0], "", 24, false); - pauseButton.animation.addByIndices('hold', 'back', [5], "", 24, false); - pauseButton.animation.addByIndices('confirm', 'back', [ - 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 - ], "", 24, false); - pauseButton.scale.set(0.8, 0.8); - pauseButton.updateHitbox(); - pauseButton.animation.play("idle"); - pauseButton.setPosition((FlxG.width - pauseButton.width) - 35, 35); - pauseButton.cameras = [camControls]; - - pauseCircle = FunkinSprite.create(0, 0, 'pauseCircle'); - pauseCircle.scale.set(0.84, 0.8); - pauseCircle.updateHitbox(); - pauseCircle.cameras = [camControls]; - pauseCircle.x = ((pauseButton.x + (pauseButton.width / 2)) - (pauseCircle.width / 2)); - pauseCircle.y = ((pauseButton.y + (pauseButton.height / 2)) - (pauseCircle.height / 2)); - pauseCircle.alpha = 0.1; - - add(pauseCircle); - add(pauseButton); - hitbox?.forEachAlive(function(hint:FunkinHint) { - hint.deadZones.push(pauseButton); - }); + initPauseSprites(); #end // Do this last to prevent beatHit from being called before create() is done. @@ -880,7 +891,7 @@ class PlayState extends MusicBeatSubState function assertChartExists():Bool { // Returns null if the song failed to load or doesn't have the selected difficulty. - if (currentSong == null || currentChart == null || currentChart.notes == null) + if (currentSong == null || currentChart == null || currentChart?.notes == null) { // We have encountered a critical error. Prevent Flixel from trying to run any gameplay logic. criticalFailure = true; @@ -899,7 +910,7 @@ class PlayState extends MusicBeatSubState { message = 'There was a critical error retrieving data for this song on "$currentDifficulty" difficulty with variation "$currentVariation". Click OK to return to the main menu.'; } - else if (currentChart.notes == null) + else if (currentChart?.notes == null) { message = 'There was a critical error retrieving note data for this song on "$currentDifficulty" difficulty with variation "$currentVariation". Click OK to return to the main menu.'; } @@ -914,7 +925,7 @@ class PlayState extends MusicBeatSubState } else { - this.remove(currentStage); + if (currentStage != null) this.remove(currentStage); FlxG.switchState(() -> new MainMenuState()); } return false; @@ -963,26 +974,31 @@ class PlayState extends MusicBeatSubState FlxG.sound.music.pitch = playbackRate; } - if (!overrideMusic) + if (!overrideMusic && vocals != null) { // Stop the vocals if they already exist. - if (vocals != null) vocals.stop(); - vocals = currentChart.buildVocals(currentInstrumental); + vocals.stop(); + vocals = currentChart?.buildVocals(currentInstrumental); - if (vocals.members.length == 0) + if (vocals?.members?.length == 0) { trace('WARNING: No vocals found for this song.'); } } - vocals.pause(); - vocals.time = startTimestamp - Conductor.instance.instrumentalOffset; if (FlxG.sound.music != null) FlxG.sound.music.volume = 1; - vocals.volume = 1; - vocals.playerVolume = 1; - vocals.opponentVolume = 1; - if (currentStage != null) currentStage.resetStage(); + if (vocals != null) + { + vocals.pause(); + vocals.time = startTimestamp - Conductor.instance.instrumentalOffset; + + vocals.volume = 1; + vocals.playerVolume = 1; + vocals.opponentVolume = 1; + } + + currentStage?.resetStage(); if (!fromDeathState) { @@ -1141,7 +1157,7 @@ class PlayState extends MusicBeatSubState if (health <= Constants.HEALTH_MIN && !isPracticeMode && !isPlayerDying) { - vocals.pause(); + vocals?.pause(); if (FlxG.sound.music != null) FlxG.sound.music.pause(); @@ -1226,7 +1242,7 @@ class PlayState extends MusicBeatSubState #end } - function pause(?mode:PauseMode = Standard):Void + function pause(mode:PauseMode = Standard):Void { if (!mayPauseGame || justUnpaused || isGamePaused || isPlayerDying) return; @@ -1234,7 +1250,7 @@ class PlayState extends MusicBeatSubState { case Conversation: preparePauseUI(); - openPauseSubState(Conversation, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene, () -> currentConversation.pauseMusic()); + openPauseSubState(Conversation, FullScreenScaleMode.hasFakeCutouts ? camCutouts : camCutscene, () -> currentConversation?.pauseMusic()); case Cutscene: preparePauseUI(); @@ -1256,7 +1272,7 @@ class PlayState extends MusicBeatSubState if (!isSubState && event.gitaroo) { - this.remove(currentStage); + if (currentStage != null) this.remove(currentStage); FlxG.switchState(() -> new GitarooPause(lastParams)); } else @@ -1324,8 +1340,8 @@ class PlayState extends MusicBeatSubState if (!isMinimalMode) { - if (iconP1 != null) iconP1.updatePosition(); - if (iconP2 != null) iconP2.updatePosition(); + iconP1?.updatePosition(); + iconP2?.updatePosition(); } // Transition to the game over substate. @@ -1343,7 +1359,7 @@ class PlayState extends MusicBeatSubState { // Query and activate song events. // TODO: Check that these work appropriately even when songPosition is less than 0, to play events during countdown. - if (songEvents != null && songEvents.length > 0) + if (songEvents.length > 0) { var songEventsToActivate:Array = SongEventRegistry.queryEvents(songEvents, Conductor.instance.songPosition); @@ -1658,7 +1674,11 @@ class PlayState extends MusicBeatSubState instance = this; funkin.modding.PolymodHandler.forceReloadAssets(); - lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id); + if (lastParams == null) + { + throw "No lastParams to refer to"; + } + lastParams.targetSong = SongRegistry.instance.fetchEntry(currentSong.id) ?? throw "Could not load current song from ID. This shouldn't happen!"; LoadingState.loadPlayState(lastParams); } @@ -1671,8 +1691,8 @@ class PlayState extends MusicBeatSubState if (isGamePaused) return false; - if (iconP1 != null) iconP1.onStepHit(Std.int(Conductor.instance.currentStep)); - if (iconP2 != null) iconP2.onStepHit(Std.int(Conductor.instance.currentStep)); + iconP1?.onStepHit(Std.int(Conductor.instance.currentStep)); + iconP2?.onStepHit(Std.int(Conductor.instance.currentStep)); // Try to call hold note haptics each step hit. Works if atleast one note status is NoteStatus.isHoldNotePressed. playerStrumline.noteVibrations.tryHoldNoteVibration(); @@ -1700,17 +1720,17 @@ class PlayState extends MusicBeatSubState var correctSync:Float = Math.min(FlxG.sound.music.length, Math.max(0, Conductor.instance.songPosition - Conductor.instance.combinedOffset)); var playerVoicesError:Float = 0; var opponentVoicesError:Float = 0; - if (vocals != null && vocals.playing) { + @:nullSafety(Off) @:privateAccess // todo: maybe make the groups public :thinking: { - vocals.playerVoices.forEachAlive(function(voice:FunkinSound) { + vocals.playerVoices?.forEachAlive(function(voice:FunkinSound) { var currentRawVoiceTime:Float = voice.time + vocals.playerVoicesOffset; if (Math.abs(currentRawVoiceTime - correctSync) > Math.abs(playerVoicesError)) playerVoicesError = currentRawVoiceTime - correctSync; }); - vocals.opponentVoices.forEachAlive(function(voice:FunkinSound) { + vocals.opponentVoices?.forEachAlive(function(voice:FunkinSound) { var currentRawVoiceTime:Float = voice.time + vocals.opponentVoicesOffset; if (Math.abs(currentRawVoiceTime - correctSync) > Math.abs(opponentVoicesError)) opponentVoicesError = currentRawVoiceTime - correctSync; }); @@ -1781,13 +1801,11 @@ class PlayState extends MusicBeatSubState */ function initCameras():Void { - camGame = new FunkinCamera('playStateCamGame'); camGame.bgColor = BACKGROUND_COLOR; // Show a pink background behind the stage. - camHUD = new FlxCamera(); camHUD.bgColor.alpha = 0; // Show the game scene behind the camera. - camCutscene = new FlxCamera(); camCutscene.bgColor.alpha = 0; // Show the game scene behind the camera. - camCutouts = new FlxCamera((FlxG.width - FlxG.initialWidth) / 2, (FlxG.height - FlxG.initialHeight) / 2, FlxG.initialWidth, FlxG.initialHeight); + camCutouts.setPosition((FlxG.width - FlxG.initialWidth) / 2, (FlxG.height - FlxG.initialHeight) / 2); + camCutouts.setSize(FlxG.initialWidth, FlxG.initialHeight); camCutouts.bgColor.alpha = 0; // Show the game scene behind the camera. FlxG.cameras.reset(camGame); @@ -1815,21 +1833,24 @@ class PlayState extends MusicBeatSubState && !ControlsHandler.usingExternalInputDevice) healthBarYPos = FlxG.height * 0.1; #end - healthBarBG = FunkinSprite.create(0, healthBarYPos, 'healthBar'); + healthBarBG.y = healthBarYPos; healthBarBG.screenCenter(X); healthBarBG.scrollFactor.set(0, 0); healthBarBG.zIndex = 800; add(healthBarBG); - healthBar = new FlxBar(healthBarBG.x + 4, healthBarBG.y + 4, RIGHT_TO_LEFT, Std.int(healthBarBG.width - 8), Std.int(healthBarBG.height - 8), this, - 'healthLerp', 0, 2); + healthBar.x = healthBarBG.x + 4; + healthBar.y = healthBarBG.y + 4; + healthBar.parent = this; + healthBar.parentVariable = 'healthLerp'; healthBar.scrollFactor.set(); healthBar.createFilledBar(Constants.COLOR_HEALTH_BAR_RED, Constants.COLOR_HEALTH_BAR_GREEN); healthBar.zIndex = 801; add(healthBar); // The score text below the health bar. - scoreText = new FlxText(healthBarBG.x + healthBarBG.width - 190, healthBarBG.y + 30, 0, '', 20); + scoreText.x = healthBarBG.x + healthBarBG.width - 190; + scoreText.y = healthBarBG.y + 30; scoreText.setFormat(Paths.font('vcr.ttf'), 16, FlxColor.WHITE, RIGHT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); scoreText.scrollFactor.set(); scoreText.zIndex = 802; @@ -1897,7 +1918,7 @@ class PlayState extends MusicBeatSubState public function resetCameraZoom():Void { - if (PlayState.instance.isMinimalMode) return; + if (isMinimalMode) return; // Apply camera zoom level from stage data. currentCameraZoom = stageZoom; FlxG.camera.zoom = currentCameraZoom; @@ -1913,15 +1934,21 @@ class PlayState extends MusicBeatSubState { if (currentSong == null || currentChart == null) { - trace('Song difficulty could not be loaded.'); + throw 'Song difficulty could not be loaded.'; } - var currentCharacterData:SongCharacterData = currentChart.characters; // Switch the variation we are playing on by manipulating targetVariation. + var currentCharacterData:Null = currentChart?.characters; + if (currentCharacterData == null) + { + trace('Cannot retrieve character data'); + return; + } + // Switch the variation we are playing on by manipulating targetVariation. // // GIRLFRIEND // - var girlfriend:BaseCharacter = CharacterDataParser.fetchCharacter(currentCharacterData.girlfriend); + var girlfriend:Null = CharacterDataParser.fetchCharacter(currentCharacterData.girlfriend); if (girlfriend != null) { @@ -1939,7 +1966,7 @@ class PlayState extends MusicBeatSubState // // DAD // - var dad:BaseCharacter = CharacterDataParser.fetchCharacter(currentCharacterData.opponent); + var dad:Null = CharacterDataParser.fetchCharacter(currentCharacterData.opponent); if (dad != null) { @@ -1954,7 +1981,7 @@ class PlayState extends MusicBeatSubState iconP2.cameras = [camHUD]; #if FEATURE_DISCORD_RPC - discordRPCAlbum = 'album-${currentChart.album}'; + discordRPCAlbum = 'album-${currentChart?.album}'; discordRPCIcon = 'icon-${currentCharacterData.opponent}'; #end } @@ -1962,7 +1989,7 @@ class PlayState extends MusicBeatSubState // // BOYFRIEND // - var boyfriend:BaseCharacter = CharacterDataParser.fetchCharacter(currentCharacterData.player); + var boyfriend:Null = CharacterDataParser.fetchCharacter(currentCharacterData.player); if (boyfriend != null) { @@ -2023,13 +2050,7 @@ class PlayState extends MusicBeatSubState */ function initStrumlines():Void { - var noteStyleId:String = currentChart.noteStyle; - var noteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId); - if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault(); - - playerStrumline = new Strumline(noteStyle, !isBotPlayMode); playerStrumline.onNoteIncoming.add(onStrumlineNoteIncoming); - opponentStrumline = new Strumline(noteStyle, false); opponentStrumline.onNoteIncoming.add(onStrumlineNoteIncoming); add(playerStrumline); add(opponentStrumline); @@ -2074,6 +2095,7 @@ class PlayState extends MusicBeatSubState playerStrumline.strumlineScale.set(playerStrumlineScale, playerStrumlineScale); playerStrumline.setNoteSpacing(playerNoteSpacing); + @:nullSafety(Off) // Who thought it'd be a good idea to make the iterator nullable? for (strum in playerStrumline) { strum.width *= 2; @@ -2082,7 +2104,7 @@ class PlayState extends MusicBeatSubState playerStrumline.x = (FlxG.width - playerStrumline.width) / 2 + Constants.STRUMLINE_X_OFFSET; playerStrumline.y = (FlxG.height - playerStrumline.height) * 0.95 - Constants.STRUMLINE_Y_OFFSET; - if (currentChart.noteStyle != "pixel") + if (currentChart?.noteStyle != "pixel") { #if android playerStrumline.y += 10; #end } @@ -2093,6 +2115,35 @@ class PlayState extends MusicBeatSubState opponentStrumline.y = Constants.STRUMLINE_Y_OFFSET * 0.3; opponentStrumline.x -= 30; } + + function initPauseSprites() + { + pauseButton.animation.addByIndices('idle', 'back', [0], "", 24, false); + pauseButton.animation.addByIndices('hold', 'back', [5], "", 24, false); + pauseButton.animation.addByIndices('confirm', 'back', [ + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 + ], "", 24, false); + pauseButton.scale.set(0.8, 0.8); + pauseButton.updateHitbox(); + pauseButton.animation.play("idle"); + pauseButton.setPosition((FlxG.width - pauseButton.width) - 35, 35); + @:nullSafety(Off) // AAAAAAA why did camControls have to be nullable AAAAAAAAAA + pauseButton.cameras = [camControls]; + + pauseCircle.scale.set(0.84, 0.8); + pauseCircle.updateHitbox(); + @:nullSafety(Off) + pauseCircle.cameras = [camControls]; + pauseCircle.x = ((pauseButton.x + (pauseButton.width / 2)) - (pauseCircle.width / 2)); + pauseCircle.y = ((pauseButton.y + (pauseButton.height / 2)) - (pauseCircle.height / 2)); + pauseCircle.alpha = 0.1; + + add(pauseCircle); + add(pauseButton); + hitbox?.forEachAlive(function(hint:FunkinHint) { + hint.deadZones.push(pauseButton); + }); + } #end /** @@ -2100,11 +2151,7 @@ class PlayState extends MusicBeatSubState */ function initPopups():Void { - var noteStyleId:String = currentChart.noteStyle; - var noteStyle:NoteStyle = NoteStyleRegistry.instance.fetchEntry(noteStyleId); - if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault(); // Initialize the judgements and combo meter. - comboPopUps = new PopUpStuff(noteStyle); comboPopUps.zIndex = 900; add(comboPopUps); comboPopUps.cameras = [camHUD]; @@ -2170,8 +2217,12 @@ class PlayState extends MusicBeatSubState function buildDiscordRPCState():String { - var discordRPCDifficulty = PlayState.instance.currentDifficulty.replace('-', ' ').toTitleCase(); - return '${currentChart.songName} [${discordRPCDifficulty}]'; + if (currentChart == null) + { + trace("WARNING: Difficulty data for RPC is null."); + } + var discordRPCDifficulty = PlayState.instance?.currentDifficulty?.replace('-', ' ')?.toTitleCase() ?? '???'; + return '${currentChart?.songName ?? '???'} [${discordRPCDifficulty}]'; } function initPreciseInputs():Void @@ -2188,7 +2239,7 @@ class PlayState extends MusicBeatSubState { if (currentChart == null) { - trace('Song difficulty could not be loaded.'); + throw 'Song difficulty could not be loaded.'; } // Conductor.instance.forceBPM(currentChart.getStartingBPM()); @@ -2196,10 +2247,10 @@ class PlayState extends MusicBeatSubState if (!overrideMusic) { // Stop the vocals if they already exist. - if (vocals != null) vocals.stop(); - vocals = currentChart.buildVocals(currentInstrumental); + vocals?.stop(); + vocals = currentChart?.buildVocals(currentInstrumental); - if (vocals.members.length == 0) + if (vocals?.members?.length == 0) { trace('WARNING: No vocals found for this song.'); } @@ -2218,9 +2269,16 @@ class PlayState extends MusicBeatSubState */ function regenNoteData(startTime:Float = 0):Void { + if (currentChart == null) + { + trace('Cannot regenerate note data for null chart'); + return; + } + Highscore.tallies.combo = 0; Highscore.tallies = new Tallies(); + @:nullSafety(Off) var event:SongLoadScriptEvent = new SongLoadScriptEvent(currentChart.song.id, currentChart.difficulty, currentChart.notes.copy(), currentChart.getEvents()); dispatchEvent(event); @@ -2243,7 +2301,7 @@ class PlayState extends MusicBeatSubState var scoreable = true; if (songNote.kind != null) { - var noteKind:NoteKind = NoteKindManager.getNoteKind(songNote.kind); + var noteKind:Null = NoteKindManager.getNoteKind(songNote.kind ?? ''); if (noteKind != null) scoreable = noteKind.scoreable; } @@ -2346,7 +2404,7 @@ class PlayState extends MusicBeatSubState if (!overrideMusic && !isGamePaused && currentChart != null) { - currentChart.playInst(1.0, currentInstrumental, false); + currentChart?.playInst(1.0, currentInstrumental, false); } if (FlxG.sound.music == null) @@ -2367,19 +2425,23 @@ class PlayState extends MusicBeatSubState FlxG.sound.music.volume = 1.0; if (FlxG.sound.music.fadeTween != null) FlxG.sound.music.fadeTween.cancel(); - trace('Playing vocals...'); - add(vocals); + if (vocals != null) + { + trace('Playing vocals...'); + add(vocals); - vocals.time = startTimestamp - Conductor.instance.instrumentalOffset; - vocals.pitch = playbackRate; - vocals.volume = 1.0; + vocals.time = startTimestamp - Conductor.instance.instrumentalOffset; + vocals.pitch = playbackRate; + vocals.volume = 1.0; - // trace('STARTING SONG AT:'); - // trace('${FlxG.sound.music.time}'); - // trace('${vocals.time}'); + // trace('STARTING SONG AT:'); + // trace('${FlxG.sound.music.time}'); + // trace('${vocals.time}'); + + vocals.play(); + } FlxG.sound.music.play(); - vocals.play(); #if FEATURE_DISCORD_RPC // Updating Discord Rich Presence (with Time Left) @@ -2493,7 +2555,7 @@ class PlayState extends MusicBeatSubState */ function processNotes(elapsed:Float):Void { - if (playerStrumline?.notes?.members == null || opponentStrumline?.notes?.members == null) return; + if (playerStrumline.notes?.members == null || opponentStrumline.notes?.members == null) return; // Process notes on the opponent's side. for (note in opponentStrumline.notes.members) @@ -2522,7 +2584,7 @@ class PlayState extends MusicBeatSubState // Process hold notes on the opponent's side. for (holdNote in opponentStrumline.holdNotes.members) { - if (holdNote == null || !holdNote.alive) continue; + if (holdNote == null || !holdNote.alive || holdNote.noteData == null) continue; // While the hold note is being hit, and there is length on the hold note... if (holdNote.hitNote && !holdNote.missedNote && holdNote.sustainLength > 0) @@ -2541,7 +2603,7 @@ class PlayState extends MusicBeatSubState // We dropped a hold note. // Play miss animation, but don't penalize. - currentStage.getOpponent().playSingAnimation(holdNote.noteData.getDirection(), true); + if (currentStage != null) currentStage.getOpponent().playSingAnimation(holdNote.noteData.getDirection(), true); } } @@ -2654,7 +2716,7 @@ class PlayState extends MusicBeatSubState applyScore(event.score, '', event.healthChange, event.isComboBreak); // Play the miss sound. - vocals.playerVolume = 0; + if (vocals != null) vocals.playerVolume = 0; FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.5, 0.6)); } else @@ -2712,7 +2774,8 @@ class PlayState extends MusicBeatSubState while (inputPressQueue.length > 0) { - var input:PreciseInputEvent = inputPressQueue.shift(); + var input:Null = inputPressQueue.shift(); + if (input == null) continue; playerStrumline.pressKey(input.noteDirection); @@ -2764,7 +2827,8 @@ class PlayState extends MusicBeatSubState while (inputReleaseQueue.length > 0) { - var input:PreciseInputEvent = inputReleaseQueue.shift(); + var input:Null = inputReleaseQueue.shift(); + if (input == null) continue; // Play the strumline animation. playerStrumline.playStatic(input.noteDirection); @@ -2817,8 +2881,7 @@ class PlayState extends MusicBeatSubState // Send the note hit event. var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, isComboBreak, - note.scoreable ? Highscore.tallies.combo + 1 : Highscore.tallies.combo, noteDiff, - daRating == 'sick'); + note.scoreable ? Highscore.tallies.combo + 1 : Highscore.tallies.combo, noteDiff, daRating == 'sick'); dispatchEvent(event); // Calling event.cancelEvent() skips all the other logic! Neat! @@ -2827,7 +2890,7 @@ class PlayState extends MusicBeatSubState playerStrumline.hitNote(note, !event.isComboBreak); if (event.doesNotesplash) playerStrumline.playNoteSplash(note.noteData.getDirection()); if (note.isHoldNote && note.holdNoteSprite != null) playerStrumline.playNoteHoldCover(note.holdNoteSprite); - vocals.playerVolume = 1; + if (vocals != null) vocals.playerVolume = 1; // Display the combo meter and add the calculation to the score. if (note.scoreable) @@ -2862,13 +2925,13 @@ class PlayState extends MusicBeatSubState if (pressArray[i]) indices.push(i); } } - vocals.playerVolume = 0; + if (vocals != null) vocals.playerVolume = 0; applyScore(Scoring.getMissScore(), 'miss', healthChange, true); if (playSound) { - vocals.playerVolume = 0; + if (vocals != null) vocals.playerVolume = 0; FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.5, 0.6)); } } @@ -2914,7 +2977,7 @@ class PlayState extends MusicBeatSubState if (event.playSound) { - vocals.playerVolume = 0; + if (vocals != null) vocals.playerVolume = 0; FunkinSound.playOnce(Paths.soundRandom('missnote', 1, 3), FlxG.random.float(0.1, 0.2)); } } @@ -2949,7 +3012,7 @@ class PlayState extends MusicBeatSubState } else { - this.remove(currentStage); + if (currentStage != null) this.remove(currentStage); FlxG.switchState(() -> new ChartEditorState( { targetSongId: currentSong.id, @@ -3057,7 +3120,7 @@ class PlayState extends MusicBeatSubState comboPopUps.displayRating(daRating); if (combo >= 10) comboPopUps.displayCombo(combo); - vocals.playerVolume = 1; + if (vocals != null) vocals.playerVolume = 1; } /** @@ -3120,7 +3183,7 @@ class PlayState extends MusicBeatSubState public function endSong(rightGoddamnNow:Bool = false):Void { if (FlxG.sound.music != null) FlxG.sound.music.volume = 0; - vocals.volume = 0; + if (vocals != null) vocals.volume = 0; mayPauseGame = false; isSongEnd = true; @@ -3201,7 +3264,7 @@ class PlayState extends MusicBeatSubState if (Date.now().getDay() == 5) Medals.award(FridayNight); // Determine the score rank for this song we just finished. - var scoreRank:ScoringRank = Scoring.calculateRank( + var scoreRank:Null = Scoring.calculateRank( { score: songScore, tallies: @@ -3227,7 +3290,7 @@ class PlayState extends MusicBeatSubState if (currentVariation == 'pico' && !PlayStatePlaylist.isStoryMode) Medals.award(FreeplayPicoMix); if (currentVariation == 'pico' && currentSong.id == 'stress') Medals.award(FreeplayStressPico); - Events.logEarnRank(scoreRank.toString()); + if (scoreRank != null) Events.logEarnRank(scoreRank.toString()); } #end @@ -3243,7 +3306,7 @@ class PlayState extends MusicBeatSubState // Pop the next song ID from the list. // Returns null if the list is empty. - var targetSongId:String = PlayStatePlaylist.playlistSongIds.shift(); + var targetSongId:Null = PlayStatePlaylist.playlistSongIds.shift(); if (targetSongId == null) { @@ -3267,20 +3330,23 @@ class PlayState extends MusicBeatSubState }, }; - #if FEATURE_NEWGROUNDS - // Award a medal for beating a Story level. - Medals.awardStoryLevel(PlayStatePlaylist.campaignId); - - // Submit the score for the Story level to Newgrounds. - Leaderboards.submitLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, PlayStatePlaylist.campaignScore); - - Events.logCompleteLevel(PlayStatePlaylist.campaignId); - #end - - if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data)) + if (PlayStatePlaylist.campaignId != null) { - Save.instance.setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data); - isNewHighscore = true; + #if FEATURE_NEWGROUNDS + // Award a medal for beating a Story level. + Medals.awardStoryLevel(PlayStatePlaylist.campaignId); + + // Submit the score for the Story level to Newgrounds. + Leaderboards.submitLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, PlayStatePlaylist.campaignScore); + + Events.logCompleteLevel(PlayStatePlaylist.campaignId); + #end + + if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data)) + { + Save.instance.setLevelScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data); + isNewHighscore = true; + } } } @@ -3309,8 +3375,8 @@ class PlayState extends MusicBeatSubState FlxTransitionableState.skipNextTransIn = true; FlxTransitionableState.skipNextTransOut = true; - if (FlxG.sound.music != null) FlxG.sound.music.stop(); - vocals.stop(); + FlxG.sound.music?.stop(); + vocals?.stop(); // TODO: Softcode this cutscene. if (currentSong.id == 'eggnog') @@ -3324,13 +3390,13 @@ class PlayState extends MusicBeatSubState FunkinSound.playOnce(Paths.sound('Lights_Shut_off'), function() { // no camFollow so it centers on horror tree - var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId); + var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId) ?? throw 'Could not find a song with the ID $targetSongId'; var targetVariation:String = currentVariation; if (!targetSong.hasDifficulty(PlayStatePlaylist.campaignDifficulty, currentVariation)) { targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION; } - this.remove(currentStage); + if (currentStage != null) this.remove(currentStage); LoadingState.loadPlayState( { targetSong: targetSong, @@ -3342,13 +3408,13 @@ class PlayState extends MusicBeatSubState } else { - var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId); + var targetSong:Song = SongRegistry.instance.fetchEntry(targetSongId) ?? throw 'Could not find a song with ID $targetSongId'; var targetVariation:String = currentVariation; if (!targetSong.hasDifficulty(PlayStatePlaylist.campaignDifficulty, currentVariation)) { targetVariation = targetSong.getFirstValidVariation(PlayStatePlaylist.campaignDifficulty) ?? Constants.DEFAULT_VARIATION; } - this.remove(currentStage); + if (currentStage != null) this.remove(currentStage); LoadingState.loadPlayState( { targetSong: targetSong, @@ -3470,20 +3536,24 @@ class PlayState extends MusicBeatSubState // If the opponent is GF, zoom in on the opponent. // Else, if there is no GF, zoom in on BF. // Else, zoom in on GF. - var targetDad:Bool = currentStage.getDad() != null && currentStage.getDad().characterId == 'gf'; - var targetBF:Bool = currentStage.getGirlfriend() == null && !targetDad; + var boyfriend:Null = currentStage?.getBoyfriend(); + var girlfriend:Null = currentStage?.getGirlfriend(); + var dad:Null = currentStage?.getDad(); - if (targetBF) + var targetDad:Bool = dad?.characterId == 'gf'; + var targetBF:Bool = girlfriend == null && !targetDad; + + if (targetBF && boyfriend != null) { - FlxG.camera.follow(currentStage.getBoyfriend(), null, 0.05); + FlxG.camera.follow(boyfriend, null, 0.05); } - else if (targetDad) + else if (targetDad && dad != null) { - FlxG.camera.follow(currentStage.getDad(), null, 0.05); + FlxG.camera.follow(dad, null, 0.05); } - else + else if (girlfriend != null) { - FlxG.camera.follow(currentStage.getGirlfriend(), null, 0.05); + FlxG.camera.follow(girlfriend, null, 0.05); } // TODO: Make target offset configurable. @@ -3505,15 +3575,15 @@ class PlayState extends MusicBeatSubState new FlxTimer().start(0.8, function(_) { if (targetBF) { - currentStage.getBoyfriend().animation.play('hey'); + boyfriend?.animation.play('hey'); } else if (targetDad) { - currentStage.getDad().animation.play('cheer'); + dad?.animation.play('cheer'); } else { - currentStage.getGirlfriend().animation.play('cheer'); + girlfriend?.animation.play('cheer'); } // Zoom over to the Results screen. @@ -3532,12 +3602,17 @@ class PlayState extends MusicBeatSubState */ function moveToResultsScreen(isNewHighscore:Bool, ?prevScoreData:SaveScoreData):Void { + var currentChart:SongDifficulty = currentChart ?? + { + trace('ERROR: Cannot move to results screen with a null chart.'); + return; + } + persistentUpdate = false; - vocals.stop(); + vocals?.stop(); camHUD.alpha = 1; var talliesToUse:Tallies = PlayStatePlaylist.isStoryMode ? Highscore.talliesLevel : Highscore.tallies; - var res:ResultState = new ResultState( { storyMode: PlayStatePlaylist.isStoryMode, @@ -3583,7 +3658,7 @@ class PlayState extends MusicBeatSubState /** * Resets the camera's zoom level and focus point. */ - public function resetCamera(?resetZoom:Bool = true, ?cancelTweens:Bool = true, ?snap:Bool = true):Void + public function resetCamera(resetZoom:Bool = true, cancelTweens:Bool = true, snap:Bool = true):Void { // Cancel camera tweens if any are active. if (cancelTweens) @@ -3606,7 +3681,7 @@ class PlayState extends MusicBeatSubState /** * Sets the camera follow point's position and tweens the camera there. */ - public function tweenCameraToPosition(?x:Float, ?y:Float, ?duration:Float, ?ease:NullFloat>):Void + public function tweenCameraToPosition(x:Float = 0, y:Float = 0, duration:Float = 0, ?ease:NullFloat>):Void { cameraFollowPoint.setPosition(x, y); tweenCameraToFollowPoint(duration, ease); @@ -3615,7 +3690,7 @@ class PlayState extends MusicBeatSubState /** * Disables camera following and tweens the camera to the follow point manually. */ - public function tweenCameraToFollowPoint(?duration:Float, ?ease:NullFloat>):Void + public function tweenCameraToFollowPoint(duration:Float = 0, ?ease:NullFloat>):Void { // Cancel the current tween if it's active. cancelCameraFollowTween(); @@ -3628,6 +3703,7 @@ class PlayState extends MusicBeatSubState else { // Disable camera following for the duration of the tween. + @:nullSafety(Off) FlxG.camera.target = null; // Follow tween! Caching it so we can cancel/pause it later if needed. @@ -3653,7 +3729,7 @@ class PlayState extends MusicBeatSubState /** * Tweens the camera zoom to the desired amount. */ - public function tweenCameraZoom(?zoom:Float, ?duration:Float, ?direct:Bool, ?ease:NullFloat>):Void + public function tweenCameraZoom(zoom:Float = 1, duration:Float = 0, direct:Bool = false, ?ease:NullFloat>):Void { // Cancel the current tween if it's active. cancelCameraZoomTween(); @@ -3674,7 +3750,7 @@ class PlayState extends MusicBeatSubState } } - public function cancelCameraZoomTween() + public function cancelCameraZoomTween():Void { if (cameraZoomTween != null) { @@ -3714,7 +3790,7 @@ class PlayState extends MusicBeatSubState for (i in strumlines) { - var value:Float = speed; + var value:Float = speed ?? 0; var strum:Strumline = Reflect.getProperty(this, i); if (duration == 0) diff --git a/source/funkin/play/components/HealthIcon.hx b/source/funkin/play/components/HealthIcon.hx index 459869bd9..2d0b4cc78 100644 --- a/source/funkin/play/components/HealthIcon.hx +++ b/source/funkin/play/components/HealthIcon.hx @@ -151,10 +151,12 @@ class HealthIcon extends FunkinSprite */ public function toggleOldIcon():Void { + final playState:Null = PlayState.instance; + if (playState == null || playState.currentStage == null) return; if (characterId == 'bf-old') { - isPixel = PlayState.instance.currentStage.getBoyfriend().isPixel; - PlayState.instance.currentStage.getBoyfriend().initHealthIcon(false); + isPixel = playState.currentStage.getBoyfriend()?.isPixel ?? false; + playState.currentStage.getBoyfriend()?.initHealthIcon(false); } else { diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index 95f1ab4c2..f59321a5c 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -97,9 +97,9 @@ class Strumline extends FlxSpriteGroup /** * Reset the scroll speed to the current chart's scroll speed. */ - public function resetScrollSpeed():Void + public function resetScrollSpeed(?newScrollSpeed:Float):Void { - scrollSpeed = PlayState.instance?.currentChart?.scrollSpeed ?? 1.0; + scrollSpeed = newScrollSpeed ?? PlayState.instance?.currentChart?.scrollSpeed ?? 1.0; } var _conductorInUse:Null; @@ -184,7 +184,7 @@ class Strumline extends FlxSpriteGroup static final BACKGROUND_PAD:Int = 16; - public function new(noteStyle:NoteStyle, isPlayer:Bool) + public function new(noteStyle:NoteStyle, isPlayer:Bool, ?scrollSpeed:Float) { super(); @@ -243,7 +243,7 @@ class Strumline extends FlxSpriteGroup this.refresh(); this.onNoteIncoming = new FlxTypedSignalVoid>(); - resetScrollSpeed(); + resetScrollSpeed(scrollSpeed); for (i in 0...KEY_COUNT) { diff --git a/source/funkin/ui/transition/LoadingState.hx b/source/funkin/ui/transition/LoadingState.hx index fd8579a42..be0d17240 100644 --- a/source/funkin/ui/transition/LoadingState.hx +++ b/source/funkin/ui/transition/LoadingState.hx @@ -12,6 +12,7 @@ import funkin.graphics.shaders.ScreenWipeShader; import funkin.play.PlayState; import funkin.play.PlayStatePlaylist; import funkin.play.song.Song.SongDifficulty; +import funkin.play.stage.Stage; import haxe.io.Path; import lime.app.Future; import lime.app.Promise; @@ -21,6 +22,7 @@ import lime.utils.Assets as LimeAssets; import openfl.filters.ShaderFilter; import openfl.utils.Assets as OpenFLAssets; +@:nullSafety class LoadingState extends MusicBeatSubState { inline static var MIN_TIME = 1.0; @@ -30,18 +32,21 @@ class LoadingState extends MusicBeatSubState var target:NextState; var playParams:Null; var stopMusic:Bool = false; - var callbacks:MultiCallback; + var callbacks:Null; var danceLeft:Bool = false; var loadBar:FlxSprite; var funkay:FlxSprite; - function new(target:NextState, stopMusic:Bool, playParams:Null = null) + function new(target:NextState, stopMusic:Bool, ?playParams:PlayStateParams) { super(); this.target = target; this.playParams = playParams; this.stopMusic = stopMusic; + + this.loadBar = new FunkinSprite(0, FlxG.height - 20).makeSolidColor(0, 10, 0xFFff16d2); + this.funkay = FunkinSprite.create('funkay'); } override function create():Void @@ -49,14 +54,12 @@ class LoadingState extends MusicBeatSubState var bg:FunkinSprite = new FunkinSprite().makeSolidColor(FlxG.width, FlxG.height, 0xFFcaff4d); add(bg); - funkay = FunkinSprite.create('funkay'); funkay.setGraphicSize(0, FlxG.height); funkay.updateHitbox(); add(funkay); funkay.scrollFactor.set(); funkay.screenCenter(); - loadBar = new FunkinSprite(0, FlxG.height - 20).makeSolidColor(0, 10, 0xFFff16d2); add(loadBar); initSongsManifest().onComplete(function(lib) { @@ -66,15 +69,21 @@ class LoadingState extends MusicBeatSubState if (playParams != null) { // Load and cache the song's charts. - if (playParams.targetSong != null) + if (playParams.targetSong == null) { - playParams.targetSong.cacheCharts(true); + throw 'Invalid parameter: Target song should not be null'; } + playParams.targetSong.cacheCharts(true); + // Preload the song for the play state. var difficulty:String = playParams.targetDifficulty ?? Constants.DEFAULT_DIFFICULTY; var variation:String = playParams.targetVariation ?? Constants.DEFAULT_VARIATION; - var targetChart:SongDifficulty = playParams.targetSong?.getDifficulty(difficulty, variation); + var targetChart:Null = playParams.targetSong.getDifficulty(difficulty, variation); + if (targetChart == null) + { + throw 'Couldn\'t retrieve chart data for song "${playParams.targetSong.songName}" on difficulty "$difficulty" and variation "$variation"'; + } var instPath:String = targetChart.getInstPath(playParams.targetInstrumental); var voicesPaths:Array = targetChart.buildVoiceList(); @@ -105,9 +114,9 @@ class LoadingState extends MusicBeatSubState // library.types.set(symbolPath, SOUND); // @:privateAccess // library.pathGroups.set(symbolPath, [library.__cacheBreak(symbolPath)]); - var callback = callbacks.add('song:' + path); + var callback = callbacks?.add('song:' + path); Assets.loadSound(path).onComplete(function(_) { - callback(); + if (callback != null) callback(); }); } } @@ -120,9 +129,9 @@ class LoadingState extends MusicBeatSubState @:privateAccess if (!LimeAssets.libraryPaths.exists(library)) throw 'Missing library: ' + library; - var callback = callbacks.add('library:' + library); + var callback = callbacks?.add('library:' + library); Assets.loadLibrary(library).onComplete(function(_) { - callback(); + if (callback != null) callback(); }); } } @@ -177,6 +186,7 @@ class LoadingState extends MusicBeatSubState function onLoad():Void { // Stop the instrumental. + @:nullSafety(Off) if (stopMusic && FlxG.sound.music != null) { FlxG.sound.music.destroy(); @@ -197,7 +207,7 @@ class LoadingState extends MusicBeatSubState static function getSongPath():String { - return Paths.inst(PlayState.instance.currentSong.id); + return Paths.inst(PlayState.instance?.currentSong.id ?? throw 'Cannot retrieve song path'); } static var stageDirectory:String = "shared"; @@ -211,10 +221,10 @@ class LoadingState extends MusicBeatSubState */ public static function loadPlayState(params:PlayStateParams, shouldStopMusic = false, asSubState = false, ?onConstruct:PlayState->Void):Void { - var daChart:Null = params.targetSong.getDifficulty(params.targetDifficulty ?? Constants.DEFAULT_DIFFICULTY, + var daChart:Null = params.targetSong?.getDifficulty(params.targetDifficulty ?? Constants.DEFAULT_DIFFICULTY, params.targetVariation ?? Constants.DEFAULT_VARIATION); - var daStage = funkin.data.stage.StageRegistry.instance.fetchEntry(daChart?.stage ?? Constants.DEFAULT_STAGE); + var daStage:Null = funkin.data.stage.StageRegistry.instance.fetchEntry(daChart?.stage ?? Constants.DEFAULT_STAGE); stageDirectory = daStage?._data?.directory ?? "shared"; Paths.setCurrentLevel(stageDirectory); @@ -249,6 +259,7 @@ class LoadingState extends MusicBeatSubState } #else // All assets preloaded, switch directly to play state (defualt on other targets). + @:nullSafety(Off) if (shouldStopMusic && FlxG.sound.music != null) { FlxG.sound.music.destroy(); @@ -257,7 +268,7 @@ class LoadingState extends MusicBeatSubState // Load and cache the song's charts. // Don't do this if we already provided the music and charts. - if (params?.targetSong != null && !params.overrideMusic) + if (!(params.overrideMusic ?? false)) { params.targetSong.cacheCharts(true); } @@ -272,7 +283,8 @@ class LoadingState extends MusicBeatSubState var songDifficulty = params.targetSong.getDifficulty(params.targetDifficulty, params.targetVariation); if (songDifficulty != null) { - var noteStyle = NoteStyleRegistry.instance.fetchEntry(songDifficulty.noteStyle); + var noteStyle = NoteStyleRegistry.instance.fetchEntry(songDifficulty.noteStyle ?? ''); + if (noteStyle == null) noteStyle = NoteStyleRegistry.instance.fetchDefault(); FunkinMemory.cacheNoteStyle(noteStyle); } @@ -439,7 +451,7 @@ class LoadingState extends MusicBeatSubState var libraryPaths = LimeAssets.libraryPaths; if (libraryPaths.exists(id)) { - path = libraryPaths[id]; + path = libraryPaths[id] ?? path; rootPath = Path.directory(path); } else @@ -490,17 +502,18 @@ class LoadingState extends MusicBeatSubState } } +@:nullSafety class MultiCallback { public var callback:Void->Void; - public var logId:String = null; + public var logId:Null; public var length(default, null) = 0; public var numRemaining(default, null) = 0; var unfired = new MapVoid>(); var fired = new Array(); - public function new(callback:Void->Void, logId:String = null) + public function new(callback:Void->Void, ?logId:String) { this.callback = callback; this.logId = logId; @@ -511,8 +524,7 @@ class MultiCallback id = '$length:$id'; length++; numRemaining++; - var func:Void->Void = null; - func = function() { + var func:Void->Void = function() { if (unfired.exists(id)) { unfired.remove(id); From 4ea99656f28fa9475436a50f473ea7acbc2a2d7f Mon Sep 17 00:00:00 2001 From: Abnormal <86753001+AbnormalPoof@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:54:23 -0500 Subject: [PATCH 87/89] no more PROPER TURNTABLE LIGHTS FIX *vine boom* --- hmm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hmm.json b/hmm.json index 0b853aca3..849244b5c 100644 --- a/hmm.json +++ b/hmm.json @@ -59,7 +59,7 @@ "name": "flxanimate", "type": "git", "dir": null, - "ref": "b1faf19885dad06c899cb71ffe07b4e40b8c6d0c", + "ref": "dc302f74c0365ac43480844197f4119a727513ea", "url": "https://github.com/FunkinCrew/flxanimate" }, { From 55358463f8e54503d747e515312d0d4794f2d478 Mon Sep 17 00:00:00 2001 From: Herman Khomich <56314743+PurSnake@users.noreply.github.com> Date: Sun, 3 Aug 2025 22:50:14 +0300 Subject: [PATCH 88/89] Remove repeated creation of strumlineScale in a row / Strumline.hx --- source/funkin/play/notes/Strumline.hx | 1 - 1 file changed, 1 deletion(-) diff --git a/source/funkin/play/notes/Strumline.hx b/source/funkin/play/notes/Strumline.hx index f59321a5c..23ff49582 100644 --- a/source/funkin/play/notes/Strumline.hx +++ b/source/funkin/play/notes/Strumline.hx @@ -236,7 +236,6 @@ class Strumline extends FlxSpriteGroup if (inArrowContorlSchemeMode && isPlayer) this.background.x -= 100; #end this.add(this.background); - strumlineScale = new FlxCallbackPoint(strumlineScaleCallback); strumlineScale = new FlxCallbackPoint(strumlineScaleCallback); From 8d14df77ffc1da9545de0f502347184e591eeeae Mon Sep 17 00:00:00 2001 From: Abnormal <86753001+AbnormalPoof@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:46:54 -0500 Subject: [PATCH 89/89] bump the submod wooHOOOOOOOOO --- assets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets b/assets index b7c062e46..26d1cffe1 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit b7c062e46b85bf83a046546b140991fcfdc26359 +Subproject commit 26d1cffe19793c67a89f299b7a60be95c5656282

PvG}QG|pNw-Ywi7&7rf=5&7~Xp!`_D*VlBTu_oNKOW?X`D1L5#d`Bwn z^_swI5}(=od8!CtrG*pmo{0*BWP9S4q4jZ{oaH!4uENAG_(t^bHO#}Ff3;g^z7uv% zK;1qt@0($ diff --git a/templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/templates/android/template/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 2486946b37cf3fcdac392ba5e3e7c5fb46da2867..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8051 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 Bh^7Dl diff --git a/templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher.png b/templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index c9a4c6bc094cb1ff528bae13ee105a676c197058..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3480 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 diff --git a/templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/templates/android/template/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 610e28840f8f0bdaf7051012d2b73e3f9772d1a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4612 zcmV+f68r6mP)WoBQtD``mNREzqaW|IvUxeSwJnz_fw%Q~Y;v z>)e(;EB^a`X26nufcpUP`_xbwA%{0(v``mh$bw#zd*bb=(LQKj;`jgGtS91V2s1-N zNGuw|wh0?TFEI5Xg-m^D1-Y)Jpx5*s{f)L^+mdIx^zmN^p+nzsO}+d&G)7n-dWsNh zBmg1VYp~CdXK5cO=gIc;j(^QWao%y=7&e1=HD8RhP*4}j^aiwTS3#lqv`5 z4|q(~Ge5kk3=%N4p>uoU@Ilu3K|=U@k=NV2)-?o(7?cz*O^3Byj$U8m~qg~Z7T2TRMI{K(5 z9uz0@A*iu%!eir6xS;EaLmIxwR`+9ZK;562rC`MjAEttm2j25Kb9=9eYH(|PsFNsR z+e%*u9^LZ7tT;oA&{n`cJwsGj{&R1)5^tAtoN-;>9l2UQ__3X|I5rP*W%M;q4%J5|&n#qdz*s##{k2NqpyKu95f(?9 z>S*Xn$nziE@I+l{;0;2J<=EqlEgGKInvm(D(0N-KBut)f4ksl!?4Do(G^*$jlpykY0T?vt-1b(irNrv4PtsFN@*i?vz>rCweDp=!SUn66gqQm7tp5Y zbwyB&$ou=#9bu;;i%Z64@RV18l%gUg$!X$=yn8oNAA(<0dM_YL-4}i`23V5Jp=&3m z(;HNOfvMZJG5&y8=TCyx{AHYiqcg`2Dv+;*kIde$u1sOe*;lsyV$ zytHfFNAf1PkYxi6&4~Joi1@ry~~#chLkknx7Ej1IhWauEBv5rKJmu=EQ+psFeMOAkfS2MT$t*d}0)8jpoIX>p8~GsB`! z&Dp3xV3KJAL!Y-eE> zSyyfN#%5NV0K#1~0u7~s+eLMseqKNC1}n0L`AymlUrSoTe9C0Ry?x6Hm~f*GV-8nh z+{H5Z>^lMONI%3Yb5Sho-Hos~{*dKifxI;qDLJtrB2HKwQF7*xSJQ-&i5=uj92r?t zXUOQ;?IM&rmiW~AzNrYhED})nA^LJuY9u11jgZlc@S%t#AtY`25hQtgFks>- zrf=*O^c{crD&hRDl%7BDF4t5QT*xNLQ-8)`s2X(5G-1h+huQ!MES2EExxX^3W+x)P zr3JCo&13|sSOMag{M}>hh}??XCb8XwJd*arlmU7;rr?HyJ&^4Q^9Ff9RXZ69nx8;k zBY~0Di^M>~;Nn%Qtd%bA^bo$skHL0+5{%tEplM_R zMH3SQp3g%9Ux3IB|{BabHK_@gNe+~??_mR_@*XA zJh%_uPe(BJ-d*t)szZI-!=rsQ4^W?=H@N}rj)gj9doz>5$9fE z;%{|`DQHB*+3IeDdqU8z5*T{N!C^=s_6D!RJ%bqBQ}X@WFrqWDr>r5|ob7Pw;6^N- zGl`LwcOWA$vv$`ON4Au*_H*_~y*HJ?6%i4J7?qmJbS6KwnU8T7MB!47kc@KnI&^^`2+-~e}{0QlsXrx(A#&+v?eD9Qq4ekrE+J70A zk6njYmnpC|*2lC+Uj(^1A$R8rMpb1HG&MCUH?46n?hx?VXV%JO@+!0+WsEU5Z*&nb zrJ@O8KUT6t0@I(vYwe|O1pg8lWia%b1o9e4D{Hc1M8AcTk%`*!5aYHNA#BgfZqGW* z+zhUn0is7aAvwSk7d9?p9J$&^|87WeOpLL5hX7)dM-Mn(F&SZlDRs471WdeB&vq7; zO+0>}9QFz8;J)Gv*wGLt+XLLcIs@rJgINK6`}c=)(w=T@Xh|OPQx(E+q7GaK{%+Y4YCFasW|@>uV7BO?a$(7EW>uRkk*!YzCjmhHZQl-;)wyXF{Nl6R2) z>k(Je(%~!-0(KNam27-6;>P1xVl2K64`AH5`q;AGHiPsvkmcUKl<51#r)G(q_*m+7 zVQgcg&|_Z)JTo&fwY8;fG)U$bU>WCErm(6OABQ?@kE(;MN4l+QT)m)%*skdx9XXH0bLVqT5BV)m^zi6g(>;TH8pTqu?aesTv&4z zG0VdRjo+quiE!kFW`t`#ADOvXW%MDA>yk|XHpPaz-s6hCVdd@FC zix*ciF*iC0JJ-&KwW|xlF5MX+25z&84+SZ z65BF%7UYyPFw1ui&TYy=*7m({aWW;Od7ZrP^d;rkZBtLJlOnCs0-syefB3_3h%(=i zgpo;DAU>XHjPhb%oIEldc2?S0^LYXr$CQ`|twpDio3PU7gng5&DZNg25 z63oti&oN`WGRgI;twYHMrzK;@P-qTT6wIH?K|@&}&K(^IduvTx$X<`8f|Fgyi9qAK z@WNi?Wv#;-5;lBtul8Op?i0f67EQyKDvs@1iR8qI zu;XyxWjh)_OxuNf>x%GfS1DdytwPJohA!k=${LtA59^t+yb!V?ow;d z2Rc-SH!r*Awu>O;HAFzqN$>ya$qMoD(uUh`GqP59A#ptx%jQjCL6dp$oM^;F`oPiF z3>*VvIGQ*iDQF>Xt$v8&)EB5cSBbYp^*EZ9gU7%A-bTLSHC`9j3Mx-lBmesn_zwv< zN;nu%F7J)|_gVW4ElFT}`ShsFx0OMx2zow4M6iG0pkj*djz&)AFf9GT0&_^B(`2Xk zB+KQSZqTmz|J{(x~KCg9roTc})r2F)`UK(JypS{@eT%Y|Ry?&X^_WP}a(U-ADW zi^Tiuo?=hpLF#!E%E8^e)Bg8EjGe06L>L^(a9&;er++ggPRm+nAtEvK{PGp4UguAY z#KzUu@ENUvl_^WHctJ9DZ`gt6l^Y>2G=jix2pC00ypD~>jM%BdlHc?BO!Zq~D@ow8 zdnL`g*QT$b@1*ZOzvTQsBQG)r>}7!sFuSQx_)743x2_ zebT;vnD(AOVEkJ~N26VcG=TH6H!qY4c))XK((j;Vg+ z?pa+v(zUGQ^u@wQd-q?@HL%F?RyR-4)YJ?h&(f0x+K2Qs2Zw=6mb4w({wLPyeNkl0yXu^<>Ms92&g#TcWBu|$oDMnyjpOA?|HYt$HP?1_mn z#vY9YyMPp>39AU)oPTB(kll-Z7S`nd;yllJ7Vh3MbIv>O`_9~ZXIr&;SzeZxxq9*Q<_jqKI@cFo>)gJT1P7VKMUGE)ppl&@qltu zaVtLO;Eo{ErrbSOX*kJ)l7#8h@@qfJ+WO<1;;qJz6WqRFvC2ioG}X>QoD(GR&7Vo-f2o>=r#iqceotKF&=e$v60 za@X<#l+lYcQvo%h=xQv!U+0T=-*Q5LuMu=~gi3@qg;XGbwO9mCnGB&iI_RONj}!w# z3@|iipNaZ}a9ut4YH7n!B7vT!CNxyeK?Q2(ppE{iHds906X%adJaKO)X}bCas8zE7 z)TJEzITSM{yTG3iE+f{esj0!pkr9G@2zi5yj4{#70t+pzvEJ4mM;zND&&30B_l~&l z-U$x~YA8H9q1K~wBk2EhJN>?z;2uFfIqs*nPWZyw7Gq4z5T>IG6QPg^L&cDg-qgWe z57S9+qpnP~zD+^0MD8zj3qUWTdf}G9&GXS%G`9o%d=yqr8&64SM=Z6!p%Iqb*y6IY z8}7PwWJGd=(3S=jsfvhLb)Y4nE=>c5h9XRv*#433i3xC5jRGi! zI**aYq-tX==`T zp;#m!1Fa}tJ18hOs8pi>4Hr?p@U!6BpV62!-Whg;cxqkdq}wMGKfTG;0l9>9+IUq$ z)Qu{)E+}m8i;L|7@w;6J4%mcak5wdgSw>;4MF{jY#Y}{atGe>qL(w1rO+){&0?_nt zai%9+oOK~2#5+r*c#rt=6U541w53y#D_p&C&B-5!Y{RgV5WUen7VFL8cvx)~g8*r3 zCcsBiTzT!HP^$psZ(KTD8sp7y*9zAqWn3B)cJ}prg@? zB!5>W=4nw!thmO-8`VxexYy1XatD7@*#)4oO(5>t1fhb$I+%UlCEMtCw;Tc=8C0JI z>j)xcHcWs$TLXCQ{znL~W0N0Djm0n^!~D6#>gm8qmCWWq191(>NbWoOpvvB#kQszh zlKR~?4o8uG6bf~sQK+pzfp&}nT8~GL!IKBrCq;^mBNJfdmtMTKt5pE*2?Y3cUogzg zBrqd6{UR$HBT;$*8s!8`;L{>fwrG2S#|g zRWNQFMW9G8N@2~-5Z6+;p&Ns5&G{rg4?ZyLN?fZ3G6vZ~b&~FH6M#AMWm|(0ofiTF~AY6U>-^8L@wz`-?d;2pqOnO08Ju5&jd@{za9Vg3qZ4- zr0%VmwbR)|xr-O%#D|a9wPgZ$P5uB5y?R1ZEQYH<7g^FyxULn&BTF5~BLZwUYgqvn znS>BS6CgIqoZXxF+Xcw_yfY&@QriFpmVwNd)5cGdII-;!Yu6yXybS*H=Rw!D4a8(1 zmLzPAy)u8~YN^)+78hB&1>9c1?^c-RhP4tZahr|F1I=e}Ce@$%P*bBDHe4(!)g7d%uJOPH5 z-^b9q)p+IRb#$6A9#T^i$Vj8oMQw0O7Rn>>d63g26& z_fAy+dPC-g3s7(=mgWBz0tqf_DPq7;tq|A9B_uIMT;$8yss9-&VQZA~FMcQnAD zv^EG0Hi2t2%iAhmMU6 z`kp_pB*2h6_mQ-=2#KqU&~rl(QhqAIfJ+tVedH7z`=vrmGA0Ls7M4jnD#ev&0X=?| zQ3$MsI+$tb&m;3`L0yo9^j6x|R>Z|>NOhs1Zw-NQTWFZJ=QK>5py(2Q}#VEfHYVLnN&sVr(oXxPidQ*=Q5o z6SKY2kf+;?N9xmnro3T7Q}ot$WC2)}(5FRqeGnG7#6u9j6dL`uqE-Lh&=|NEtp@Go zS`9jYR(&^CMW)58UjSTX$w+;Kf$Kr-TJdfP}1{&07ie2C=)FZAZl1JvVV5M0U3&MIX??cyT z8MMQ)Ax-!mqSSp53^~lT8h#wD2JWj0P4A^%0q8J%Lv~m8Fch7Fhzc`9M!GG#v97Bj z#44#5aK#-c(_e;NZ4?WlO34R(3Tzmol8N3`P z+NC!J?B8U;u+z}OAYq$EQLgL75I4jp^H~sOH3quoqZsL8l4OX*Lh`pZ**22Xx0(}wge+1IYN|Ai_3p9OuE=VIEpt=Q}R1_ns%U`Pgm zd!_zLJ!5YRxuGuBZxA>9R$R9NBGnkACX!@y2=(8ed@)n3>x(K%R!ez5wN&=j2 z0XIa0aBE#U&!vH+m_U@B*@Dx%zJRH*K7=9@PTz8hdIi{+?afK0n&|Z21Rmi^s3q3@ zvx;@ER^qtP|T7*>cegCr+NI7tuN941h$JaMt(K(7(rUp8sO>1;dN84wxyKFbe zsCdhBWJ7ciHbA#MyT9S?Y=^B`GjL+ZQphjtL{fABiF0C3*K8>C%t;m$*;zdT3>`!w zJqf1&jR1KS3*;w#oU3OjVpR&T_f8I(eKNvm^^+5b_Gtr zKIY53A39q-zNvoU~8Yg2qZcRU>Jt>E3k33*4>$DRgs};n2NYt&;xDlwK*d$eaKgT^8=^j*w}n~E8|Kg9*?42zW{jM&7ehWdi~;jcB5mOr z^!w}_Qa(Qir=ULU+-nz2LVBNQM7cS^%ibFM*UrVU?Mon)h&cKfj;DuyRa%Nw%M0+! z-eBnJ3OR?~y^!(Xp&wVpkh1D#{d)Mv`w^A-J8yeSz-R1=M(d~05NN_{bfJ5XUcJ5b_NET>nh#$v|#A{*xnDfpMw^D%#pzbX3vS<&n$H}>;0unS2+JZZe@ch7@_Z}MU88P-UC101`L|V3PR=WjZ_oOx*%_`#uVEcDDD{V)WX?|E>Yu zmR%$nmNrX2k4zkc`-?up&M`ya-_{m)=1oPamj@>!3Ce*Tf$9+;B+vw6Jzb98(NZP7 z0YfVvuqlR~>xpKB-3@|Ub2P!hBU;&rjtQv|;EEer5^G70oa zIxZyFpr8B4jzGF!XHF^+VCQ#!EiXXH&F+=-4ZLioQ7?;I0%G77`eHS z237L~D6K})YGnavejYUGJ7^hOF<%`za}TevsjG6)Yt^IORKhoT&@!=NWKZfAj#v+8 zB?0J5elrrIc>+`|oPp#n9uR43BW}m`hjk@AwwH>bs3P{W)1<8;*cj^LwQzrA4eEnS zGsdHI{xo!Vb5;c5-@;m6fZI3XDs{9K0?_%Kq4)10 zO{^XE3M4AMU{quvs#h(*gH4O^;D;>K99)gsnFUQ1UhfxU?#5<0rV(m;=+?-&6+oY!K>len8!oJ*d07m*GD7pH3lkXm1a# z))s8$mezCd?R$!Q)x0`BM9iX-tOw}p3DL<@AMUPtpoUq0J#Jt>8}&RDQCFT+X<;sb zmW4TI+_3{BG7)+uSmM;LVc5GhfK{N&)G0jjnE*5feA~PLDcO1WaK#bqo;?@CgMFAE zr+(GL$^x@{cf%i(-@@h37GlDRQ%c!P^ups1O10T_GyBFjx|dJ;bZ2i}((M${Azd1Y zr#Q3cD>jgFXk!6g9T~f4L8r}WJqKN@Y%+c+g!kyr;X-<4$047P-nJ4EpB^G9@AuFk zhSDR}0(lo>*xC~(ehI~jrJb=j(}TPIXS8|*ppU0pTgjNp5)x=8yzPXtJjHubzwTE| z0nkTFRLQ3pDz9NVb|ZNtbMbrgE4-g|1ivo(2IZg4X5>*nIx#K`LV0djUBBQYFpj13?Y z(={c8EP(rcI)~LW?R0r#9YbgCg|_L#^JtJdX&GC>sI4bE=AAbk*=^Z|`{NB$^=f@E zi>=k5E#$_Ni{CAH_>$VilM$?vZm*7LOg)uQQ-{6>N?6bge18MJB7nFc|43Az1a3hS4UJVn9h!^{$e(2PHo1tv5F}Jia_sr z0f-B#*}EK97EZy+k!dj3*F|bVC^SiLb06~>t7poOiU$Rv7aWJFt3P`wJ3Pb$fqq8p zeP){D({ob8pf92{C13plkRI^IZ{cK+;m4^yl1G>g-IE@B9)2dPXpEXf+;(d3}2{9{*tTQbzQtAC|$~ z)R2827uxk<54gM=1;4LHbg&n5O^3f<1S10-v`g))$bdE}dMC}w*fT$*j`Se|nM{Bn zeWcQxy6|w*!<`%5UaSB#1~gnm^}_UxK@(#!d-{3kedWBG=GjpjiPbwO9$BH5 zw05~Sd!AH8d};R3R7U)R?-rx>=te@|Zbnws`5j14jv?`&{voJlph?`qdu_ZlFKw@5Nwt5SFK~L5F{H+317W9B$S0iRMea@V2 zB%j_drrldW{H=)e5V|f;;pJF%%(@lc?5S!mM_b%F@QqTSY{0#XJMn7&cw&XhB8bSC zqffVBj7d*q`%&SlS7)O3&+WW(G$5Z?eq>N*V*Ook?a*qrE%&!|aCWeUv1bQFt=tHy zz9~C@dn*_0o^%jbmgb}USP5=l%0pCWIP2wq9u4Oa|F;W3F>jVTtGvt5p%_ZW0_hbM za2b}tf|r|%F7|BkZ`^mwI$xNYh?!rf7I}yGZB@b=K&|;d(_Vw2{=>;OYK=>0y`{YG zrCmy?W&>&oeq28vPK4;O!}_4&>^8Rj!hsbC^H;q2u1O4p)|37n;u?xWGfv?8id(3< zP=?y#Du(Y?u4M~AeYvO;ukHT`0q8Pd^Z{K>GBAnWya|r4^n;LO8U2#2*(*$RANqR# zoEdH`2H0AgW7UH9PU)#bu88P z|Lp=$pE&m;cV^Y}y)dyvh_}bIBdMw)Slug*$Aqy?tQ&_C%R9e+IU_@rw8w$^@UP~K zLrQ!oc4vRW`@RA43p=oxjC84mk4Wo|>%`^Io^bhKHbxScW}v6T2=CI?8wT12us3tS z;$c}hpLqiX+X_*gdsm5kimNBivC;47;r7k?|G!-Tips(s=oW3xEajEH)+nzZCs1r& z;{!T=2=aDE;jxW8(w+v@#N9L}Ug7bL_;&GR^ok22B86b@>N&V~VJC|vUwkqaZS5?X zfmj>cU`D^$_;uz<%o&i0{ZkL)@{)X%{CpcVw<>w$Q(QUmC+mF!Qf(f)V3XPec+%x+ zuKW?j$Uk>f`Bna`#hsWiF(KY4KJguoyk|iT5rJkhd$MQaoe_P}Bf=LMsd21#RGi() zT+ZRmi;&zsgx;@(wnQ7}wi1dV=D`ufzVi=4K^&k;S;Opd%FGhZYGjp#n;wi3vc?)-bEXIR8zROX? z_0tzv+8Y;T&U-Va0cscEd6z6JASvjmVfHXIplPWlygVE+VN@!9BLkw+)8GGBF@D*& zfIW%!Rz@#;KI3&9+>puQ0zIze^ky90wiuJ&=nrpCXEx2CCDB6Hc0O1*cnMB_da054 z%Zu}IYil9oIpsW}c_^wv^^Lo@wEF@{e?`p2{x<>Cwq)7!3ebSx4uoJ>x*f}!L}a8X zm5AA#&BW3DnB~&^oMtf8+M9x=j)liIu)k?KNqf-V)e86HM+R3ishGJ59jOjtI>jOD z)olDZ|5_vQmp;o!?)n1UJyVLBqDovnc^;)#3;3khgZ7M)UvA^t@>@9k(J2aS^_*~qh@Zc)>J}8hX^6&>Kw`FOft zYHSKl&A&(l7=jSDP=(8BApWPTsQu|MYIANjl6>>bC3NlVg`MAiuWa@CcTu>ji1n66 z0qe1U$|1bjeH=?&>Am0YR{JQmL1CO~7Jw~X+zf#&3A&c^{X6j}z8XgcanU@`Ie7Y% z9$gojuEy2D%^i7XFC%l@e6%4}y6yel$lpv{&CV03>z4*jTN|8E2u@cQbrUC{HunyW z?*9et9UYN#>`Wv1H3fH3w5Jf)SKcCNsUk~i4$DWchDIC(*he<=<8r_x!L7GKT7ermpnBgN)aibVLyT>CYJOafz zxv1U!BRG2pMtGf@8)~z@LEWS&;7m=~w%U*o42VxaVqAB=|8L@oD$kapcwZ6g5tkMz z_k#YxihB-p(OJqx)8*@F4_GwE!;f;;A_A<>@^P&xO)P0Dz>5%pW*U@uIU{kgExyjl3AKe4B(Rd;yJ!jO zWHMaS)j@mWmPTic;8}mwxiS>)Ex^y2zv9!unMeyBfJC33c!hkv*J}zkj@yp&nK!Xw z^cps>qjOz!nx&}#c{#DAvp(?fqTD^F0PN>CG=qXHOsk6G&(<~b6DH~sf%^Y<$2!8y zR00VhJ|r**tClTC?wQM|BtE>D1XMY3A2#e`_HI2QG()a*YK-HA_mD!Arfd{{><45;z?#iv!3hgbFpdSRwVnU!p7K!47@bi zJqsNP&CnFHrTSX>_%LM_4$VA);i`!;R3Zc))(psyd--qDFOHZx>S46aB9Lr1r*4fPP-zAZ)s`{14E5OlCs zEQdy6Y0(-tcOAfAF>xS40)O>Q0eAW$YVs>lGA|pa^i z{4paP(kNF62`0LfyCyt7R}nfn>6(zwcA5L=iZ);SGp2%av2J;nHNpO7(UhZh?R4l5xwXtM>D3piq{P`-%aVMyBj{jhsT^Q*Bb76{*QKDFMEwDW2{|DeeTFJxx;*<6Ke(CVML(Ingu4yR#|Vhwd8@ z+Ljz2oHEeYGG!q7e<1m8V6OnOEmVQH&M9f`++1o; z{&pmAA)vpP^=}5{z>#u7xgmc`TPe6wOQEXLpsa0WrE6?wr)^4yQbvs>bk4Ll@uhuA`&2e`0(MYw3L|2`+M=f`wQIrGn|a+^uq_ic4;wTH8=g! zOn6N-N!$=@mqTK@T)(h3*RBZcGCw`#6N0=d>U#4NJxtH@whZ$~i99xHYU(Ph6FuQ7 z@S@WFD__mYdYkJRbBmwf>y^O7)9)77^RsbC4Tt02s9ZFW?*Ffgw9b#jk{|q~7d*Hu z6&8tsN6U)OLb10keh=3xd4iM0NBjKN*eMPW)5U%5M`*=zW8p{LJRgYJ^K1>>^Xx0| zngIXa(7AuDB?`JE+Z7oOO`LJeX_abrs%4+*|ZUVvGkp>8!l^uHfGyT%2QQwVpl0!a~7*7H}TACPXypoA4M zYMtgt%g*63i}Y&OgoRr?S}-M{<+b`jk8un53zB3?ZX;+yfPkRP_gRwE+C6sMz$oRU+c{g8C$sSds`(F@q zattMMw*LBM=K#E#3~iPq(#&8>(WaZ70J1Af@YejpW23!7&neQ)zYEh(06~l4R%*h_ z)X)ndzXL$EOV;t*_JVfcNsD0f9NHVvpYU=k`mL5BD%Jz=_gafM_@B-K%JsDzh`3IE zMWNUyr%XBO`$5C(us;+uHFw0uAw03Fv40YqVe#+YxL7nHC)U{`%X*V zi$`}X;0f6v_4}>+fBAEONF#|2nk4^VTprf>iwua{XEycY z9FZ5y>DBA{OR`}bcFwKV-6`l_tTYa5ne!ykZmC^`*6iw%jPs?oUV;jqkJH&jLeiDn z>u@vpUy~z9pTvOi=B5Yk&1(@;>BG|}L=_+8FTL|#Fq)_5F;v}GjzPx~NDHW!7h<&; zgakZ1Ic1qltz~LT`RfRhZ6uVnI%7Kyci2HiG9zA}bL*Fsh1 z_(r*6Uj_qmG+|)*TXEvU&KRf%F9V|<2dDV>AWXW++wQwv){407-zdXH2hfB906~{d zg(&0s)1-m11cZ%j8djN96pO8B%afZz&Jmfhf`CPW;7xWdr@(aU_E5OA#C0DH)G-_{A9DjTGxH+{`B&#=Zc^F5#c_L45_1$Rd56&rTEie{qYcpO z&&JtPGXahye5y2_c6(~x9E)U_f1rd@jUa6gA-bum^w8zpB&RG52k)jQh4$b9lu)vK z59a=yFX|#z;>~^?usHe3Tuqzae_C;g=qt?8#y`(@!gMr8VirhXhI++2ApbfUOKcfJ z^V~dCEet>V%)rvgxHaCV