From d43dfac3e4a40d4c5e926bdf9ff5b3501bca1ba0 Mon Sep 17 00:00:00 2001 From: KarimAkra Date: Mon, 14 Apr 2025 22:38:08 +0300 Subject: [PATCH] Added vibrations to most ResultState ranks and more! * Vibrations for most ranks * Vibrations when scrolling through FreeplayState * Vibrations for notes hits * Vibrations for game over Co-Authored-By: sector-a <82838084+sector-a@users.noreply.github.com> --- project.hxp | 11 ++- .../ui/options/MobileControlsSchemeMenu.hx | 3 +- source/funkin/mobile/util/HapticUtil.hx | 12 ++- source/funkin/play/GameOverSubState.hx | 8 +- source/funkin/play/PlayState.hx | 6 ++ source/funkin/play/ResultState.hx | 92 ++++++++++++++++++- source/funkin/save/Save.hx | 3 + source/funkin/ui/freeplay/FreeplayState.hx | 37 +++++++- source/funkin/ui/title/TitleState.hx | 4 +- source/funkin/util/Constants.hx | 10 +- 10 files changed, 164 insertions(+), 22 deletions(-) diff --git a/project.hxp b/project.hxp index 079bb9c05..da4634df3 100644 --- a/project.hxp +++ b/project.hxp @@ -286,7 +286,13 @@ class Project extends HXProject { */ static final TOUCH_CONTROLS:FeatureFlag = "TOUCH_CONTROLS"; - /** + /** + * `-DHAPTIC_VIBRATIONS + * If this flag is enabled, the vibrations for the game is also enabled. + */ + static final HAPTIC_VIBRATIONS:FeatureFlag = "HAPTIC_VIBRATIONS"; + + /** * `-DEXCLUDE_ARMV7` * If this flag is enabled, armv7 won't be compiled when building android. */ @@ -586,6 +592,9 @@ class Project extends HXProject { // Should be true only on mobile builds. TOUCH_CONTROLS.apply(this, isMobile()); + // Should be true only on mobile builds. + HAPTIC_VIBRATIONS.apply(this, isMobile()); + // Should be true only on non-mobile builds. // Only mobile builds support ads. DISABLE_ADMOB_ADS.apply(this, !isMobile()); diff --git a/source/funkin/mobile/ui/options/MobileControlsSchemeMenu.hx b/source/funkin/mobile/ui/options/MobileControlsSchemeMenu.hx index be4137b83..9286d03c2 100644 --- a/source/funkin/mobile/ui/options/MobileControlsSchemeMenu.hx +++ b/source/funkin/mobile/ui/options/MobileControlsSchemeMenu.hx @@ -9,7 +9,6 @@ import funkin.mobile.ui.options.objects.HitboxShowcase; import funkin.mobile.ui.FunkinHitbox; import funkin.mobile.util.TouchUtil; import funkin.mobile.util.SwipeUtil; -import funkin.util.MathUtil; import funkin.ui.MusicBeatSubState; import funkin.ui.AtlasText; import funkin.graphics.shaders.HSVShader; @@ -306,7 +305,7 @@ class MobileControlsSchemeMenu extends MusicBeatSubState } } - override function update(elapsed:Float) + public override function update(elapsed:Float):Void { super.update(elapsed); diff --git a/source/funkin/mobile/util/HapticUtil.hx b/source/funkin/mobile/util/HapticUtil.hx index 4c2267b1f..77f9de878 100644 --- a/source/funkin/mobile/util/HapticUtil.hx +++ b/source/funkin/mobile/util/HapticUtil.hx @@ -20,11 +20,15 @@ class HapticUtil * @param duration The time taken for a complete cycle. * @param amplitude The distance of movement of the wave from its original position. */ - public static function vibrate(period:Int, duration:Int, amplitude:Int = 0):Void + public static function vibrate(period:Int = Constants.DEFAULT_VIBRATION_PERIOD, duration:Int = Constants.DEFAULT_VIBRATION_DURATION, amplitude:Int = 0):Void { + #if HAPTIC_VIBRATIONS if (!Preferences.vibration) return; Haptic.vibrate(period, duration, amplitude); + #end + + return; } /** @@ -34,8 +38,9 @@ class HapticUtil * @param toValue End amplitude value. * @param duration Duration of the tween. */ - public static function increasingVibrate(fromValue:Float, toValue:Float, duration:Float = 1) + public static function increasingVibrate(fromValue:Float, toValue:Float, duration:Float = 1):Void { + #if HAPTIC_VIBRATIONS if (!Preferences.vibration) return; amplitudeTween = FlxTween.num(fromValue, toValue, duration, @@ -46,5 +51,8 @@ class HapticUtil }, function(num) { vibrate(0, Std.int(Constants.DEFAULT_VIBRATION_DURATION / 10), Std.int(num)); }); + #end + + return; } } diff --git a/source/funkin/play/GameOverSubState.hx b/source/funkin/play/GameOverSubState.hx index 58872ded8..21cde787e 100644 --- a/source/funkin/play/GameOverSubState.hx +++ b/source/funkin/play/GameOverSubState.hx @@ -19,10 +19,8 @@ import funkin.util.MathUtil; import funkin.effects.RetroCameraFade; import flixel.math.FlxPoint; import funkin.mobile.util.TouchUtil; -import openfl.utils.Assets; -#if mobile import funkin.mobile.util.HapticUtil; -#end +import openfl.utils.Assets; /** * A substate which renders over the PlayState when the player dies. @@ -517,8 +515,8 @@ class GameOverSubState extends MusicBeatSubState { blueballed = true; - #if mobile - HapticUtil.vibrate(500, 1000); + #if HAPTIC_VIBRATIONS + HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD * 5, Constants.DEFAULT_VIBRATION_DURATION * 10); #end if (Assets.exists(Paths.sound('gameplay/gameover/fnf_loss_sfx' + blueBallSuffix))) diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx index a3d91096f..e5428bfde 100644 --- a/source/funkin/play/PlayState.hx +++ b/source/funkin/play/PlayState.hx @@ -57,6 +57,7 @@ import funkin.util.SerializerUtil; import haxe.Int64; #if mobile import funkin.mobile.util.TouchUtil; +import funkin.mobile.util.HapticUtil; import funkin.mobile.ui.FunkinHitbox; #if NO_DISABLE_ADMOB_ADS import funkin.mobile.util.AdMobUtil; @@ -2639,6 +2640,11 @@ class PlayState extends MusicBeatSubState isComboBreak = Constants.JUDGEMENT_SHIT_COMBO_BREAK; } + #if HAPTIC_VIBRATIONS + // Vibration each note hit. + HapticUtil.vibrate(0, 10); + #end + // Send the note hit event. var event:HitNoteScriptEvent = new HitNoteScriptEvent(note, healthChange, score, daRating, isComboBreak, Highscore.tallies.combo + 1, noteDiff, daRating == 'sick'); diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index 75f43e5b3..43229493f 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -514,13 +514,13 @@ class ResultState extends MusicBeatSubState clearPercentLerp = clearPercentCounter.curNumber; FunkinSound.playOnce(Paths.sound('scrollMenu')); - #if mobile + #if HAPTIC_VIBRATIONS HapticUtil.vibrate(0, 10); #end } }, onComplete: _ -> { - #if mobile + #if HAPTIC_VIBRATIONS HapticUtil.vibrate(0, Constants.DEFAULT_VIBRATION_DURATION, Constants.MAX_VIBRATION_AMPLITUDE); #end @@ -725,6 +725,90 @@ class ResultState extends MusicBeatSubState // maskShaderSongName.frameUV = songName.frame.uv; } + #if HAPTIC_VIBRATIONS + private function handleAnimationVibrations() + { + switch (rank) + { + case ScoringRank.PERFECT | ScoringRank.PERFECT_GOLD: + for (atlas in characterAtlasAnimations) + { + @:nullSafety(Off) + switch (playerCharacterId) + { + // Feel the bed fun :freaky: + case "bf": + if (atlas.sprite.anim.curFrame > 87 && atlas.sprite.anim.curFrame % 5 == 0) HapticUtil.vibrate(0, 10, Constants.MAX_VIBRATION_AMPLITUDE); + + // GF slams into the wall. + if (atlas.sprite.anim.curFrame == 51) HapticUtil.vibrate(0, 10, Std.int(Constants.MAX_VIBRATION_AMPLITUDE / 3)); + + // 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); + + default: + // nothing. + } + } + + case ScoringRank.GREAT | ScoringRank.EXCELLENT: + for (atlas in characterAtlasAnimations) + { + switch (playerCharacterId) + { + // Pico explodes the targets with a rocket launcher. + case "pico": + if (atlas.sprite.anim.curFrame == 45) HapticUtil.vibrate(0, 10, Std.int(Constants.MAX_VIBRATION_AMPLITUDE / 3)); + + if (atlas.sprite.anim.curFrame == 50) HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION, + Constants.MAX_VIBRATION_AMPLITUDE); + + default: + // nothing. + } + } + + case ScoringRank.GOOD: + for (atlas in characterAtlasAnimations) + { + switch (playerCharacterId) + { + // Pico shooting the targets. + case "pico": + final frames:Null>> = [[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] + && atlas.sprite.anim.curFrame % 2 == 0) HapticUtil.vibrate(0, 10, Constants.MAX_VIBRATION_AMPLITUDE); + } + + default: + // nothing. + } + } + + case ScoringRank.SHIT: + for (atlas in characterAtlasAnimations) + { + 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); + + default: + // nothing. + } + } + } + } + #end + override function update(elapsed:Float):Void { // maskShaderSongName.swagSprX = songName.x; @@ -921,6 +1005,10 @@ class ResultState extends MusicBeatSubState } } + #if HAPTIC_VIBRATIONS + handleAnimationVibrations(); + #end + super.update(elapsed); } } diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 6d6cc68cb..673bdb95b 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -663,6 +663,9 @@ class Save public function hasBeatenLevel(levelId:String, ?difficultyList:Array):Bool { + // need it for testing (REMOVE IT ON RELEASE) + return true; + if (difficultyList == null) { difficultyList = ['easy', 'normal', 'hard']; diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index b268626c6..687c65eed 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -948,7 +948,7 @@ class FreeplayState extends MusicBeatSubState capsuleToRank.ranking.visible = false; capsuleToRank.fakeRanking.visible = false; - #if mobile + #if HAPTIC_VIBRATIONS HapticUtil.increasingVibrate(Constants.MIN_VIBRATION_AMPLITUDE, Constants.MAX_VIBRATION_AMPLITUDE, 0.6); #end @@ -1064,7 +1064,7 @@ class FreeplayState extends MusicBeatSubState FlxTween.tween(capsuleToRank, {"targetPos.x": originalPos.x, "targetPos.y": originalPos.y}, 0.5, {ease: FlxEase.expoOut}); new FlxTimer().start(0.5, _ -> { - #if mobile + #if HAPTIC_VIBRATIONS HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION, Constants.MAX_VIBRATION_AMPLITUDE); #end @@ -1340,6 +1340,10 @@ class FreeplayState extends MusicBeatSubState var hintTimer:Float = 0; + #if HAPTIC_VIBRATIONS + var allowPicoBulletsVibration:Bool = false; + #end + override function update(elapsed:Float):Void { super.update(elapsed); @@ -1481,6 +1485,11 @@ class FreeplayState extends MusicBeatSubState handleInputs(elapsed); if (dj != null) FlxG.watch.addQuick('dj-anim', dj.getCurrentAnimation()); + + #if HAPTIC_VIBRATIONS + // If the allowPicoBulletsVibration is true, trigger vibration each update (for pico shooting bullets animation). + if (allowPicoBulletsVibration) HapticUtil.vibrate(0, 10, Std.int(Constants.MAX_VIBRATION_AMPLITUDE / 3)); + #end } var _sub:Float = 0; @@ -2101,6 +2110,25 @@ class FreeplayState extends MusicBeatSubState backingCard?.confirm(); + #if HAPTIC_VIBRATIONS + // Start vibration after half of second. + new FlxTimer().start(0.5, function(tmr) { + switch (currentCharacterId) + { + // Toggles the bool that allows vibration on update. + case "pico": + allowPicoBulletsVibration = true; + new FlxTimer().start(0.5, function(tmr) { + allowPicoBulletsVibration = false; + }); + + // A single vibration. + default: + HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION * 5, Std.int(Constants.MAX_VIBRATION_AMPLITUDE / 3)); + } + }); + #end + new FlxTimer().start(styleData?.getStartDelay(), function(tmr:FlxTimer) { FunkinSound.emptyPartialQueue(); @@ -2205,6 +2233,11 @@ class FreeplayState extends MusicBeatSubState // switchBackingImage(daSongCapsule.freeplayData); } + + #if HAPTIC_VIBRATIONS + // Small vibrations every selection change. + HapticUtil.vibrate(0, 10, 10); + #end } public function playCurSongPreview(?daSongCapsule:SongMenuItem):Void diff --git a/source/funkin/ui/title/TitleState.hx b/source/funkin/ui/title/TitleState.hx index 1c8f5d82c..cc531c22f 100644 --- a/source/funkin/ui/title/TitleState.hx +++ b/source/funkin/ui/title/TitleState.hx @@ -374,8 +374,8 @@ class TitleState extends MusicBeatState { if (credGroup == null || textGroup == null) return; - #if mobile - HapticUtil.vibrate(Constants.DEFAULT_VIBRATION_PERIOD, Constants.DEFAULT_VIBRATION_DURATION); + #if HAPTIC_VIBRATIONS + HapticUtil.vibrate(); #end var coolText:AtlasText = new AtlasText(0, 0, text.trim(), AtlasFont.BOLD); diff --git a/source/funkin/util/Constants.hx b/source/funkin/util/Constants.hx index 4acb58582..89068a6cf 100644 --- a/source/funkin/util/Constants.hx +++ b/source/funkin/util/Constants.hx @@ -602,26 +602,24 @@ class Constants public static var GLOBAL_FREEPLAY_VICTORY_COUNTER:Int = 0; #end - #if mobile /** * Default period value for vibration. */ - public static final DEFAULT_VIBRATION_PERIOD:Int = 100; + public inline static final DEFAULT_VIBRATION_PERIOD:Int = 100; /** * Default duration value for vibration. */ - public static final DEFAULT_VIBRATION_DURATION:Int = 100; + public inline static final DEFAULT_VIBRATION_DURATION:Int = 100; /** * Min vibration amplitude. * If vibration's amplitude is less than 1 it will do nothing. */ - public static final MIN_VIBRATION_AMPLITUDE:Int = 1; + public inline static final MIN_VIBRATION_AMPLITUDE:Int = 1; /** * Max vibration amplitude. */ - public static final MAX_VIBRATION_AMPLITUDE:Int = 255; - #end + public inline static final MAX_VIBRATION_AMPLITUDE:Int = 255; }