diff --git a/CHANGELOG.md b/CHANGELOG.md index 22a36fa8e..a2031ba24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,8 +29,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implemented support for a new Instrumental Selector in Freeplay - Beating a Pico remix lets you use that instrumental when playing as Boyfriend - Added the first batch of Erect Stages! These graphical overhauls of the original stages will be used when playing Erect remixes and Pico remixes + - Week 1 Erect Stage + - Week 2 Erect Stage + - Week 3 Erect Stage + - Week 4 Erect Stage + - Week 5 Erect Stage + - Weekend 1 Erect Stage +- Implemented alternate animations and music for Pico in the results screen. + - These display on Pico remixes, as well as when playing Weekend 1. - Implemented support for scripted Note Kinds. You can use HScript define a different note style to display for these notes as well as custom behavior. (community feature by lemz1) -- Implemented a new Strumline Background option, to display a darkened background behind the strumline with your choice of opacity. - Implemented support for Numeric and Selector options in the Options menu. (community feature by FlooferLand) ## Changed - Girlfriend and Nene now perform previously unused animations when you achieve a large combo, or drop a large combo. diff --git a/art b/art index 0bb988c49..0dee03f11 160000 --- a/art +++ b/art @@ -1 +1 @@ -Subproject commit 0bb988c49788fd25a230b56dd9e4448838bc79c9 +Subproject commit 0dee03f11afc01c2883da223fa10405f7011dd33 diff --git a/assets b/assets index 350910d80..86a0a01e1 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 350910d8094853d11deb2ee27cacbe3ada27af32 +Subproject commit 86a0a01e188a80a51b88a0a4188609f2db452bab diff --git a/hmm.json b/hmm.json index 50b81bb85..d967a69b3 100644 --- a/hmm.json +++ b/hmm.json @@ -194,7 +194,7 @@ "name": "polymod", "type": "git", "dir": null, - "ref": "96cfc5fa693b017e47f7cb13b765cc68698fa6b6", + "ref": "0fbdf27fe124549730accd540cec8a183f8652c0", "url": "https://github.com/larsiusprime/polymod" }, { diff --git a/source/Main.hx b/source/Main.hx index 7f7d25235..724b118f8 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -67,14 +67,9 @@ class Main extends Sprite function init(?event:Event):Void { #if web - untyped js.Syntax.code(" - window.requestAnimationFrame = function(callback, element) { - var currTime = new Date().getTime(); - var timeToCall = 0; - var id = window.setTimeout(function() { callback(currTime + timeToCall); }, - timeToCall); - return id; - }"); + // set this variable (which is a function) from the lime version at lime/_internal/backend/html5/HTML5Application.hx + // The framerate cap will more thoroughly initialize via Preferences in InitState.hx + funkin.Preferences.lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame"); #end if (hasEventListener(Event.ADDED_TO_STAGE)) diff --git a/source/funkin/Preferences.hx b/source/funkin/Preferences.hx index b2050c6a2..daeded897 100644 --- a/source/funkin/Preferences.hx +++ b/source/funkin/Preferences.hx @@ -128,6 +128,48 @@ class Preferences return value; } + public static var unlockedFramerate(get, set):Bool; + + static function get_unlockedFramerate():Bool + { + return Save?.instance?.options?.unlockedFramerate; + } + + static function set_unlockedFramerate(value:Bool):Bool + { + if (value != Save.instance.options.unlockedFramerate) + { + #if web + toggleFramerateCap(value); + #end + } + + var save:Save = Save.instance; + save.options.unlockedFramerate = value; + save.flush(); + return value; + } + + #if web + // We create a haxe version of this just for readability. + // We use these to override `window.requestAnimationFrame` in Javascript to uncap the framerate / "animation" request rate + // Javascript is crazy since u can just do stuff like that lol + + public static function unlockedFramerateFunction(callback, element) + { + var currTime = Date.now().getTime(); + var timeToCall = 0; + var id = js.Browser.window.setTimeout(function() { + callback(currTime + timeToCall); + }, timeToCall); + return id; + } + + // Lime already implements their own little framerate cap, so we can just use that + // This also gets set in the init function in Main.hx, since we need to definitely override it + public static var lockedFramerateFunction = untyped js.Syntax.code("window.requestAnimationFrame"); + #end + /** * Loads the user's preferences from the save data and apply them. */ @@ -137,6 +179,17 @@ class Preferences FlxG.autoPause = Preferences.autoPause; // Apply the debugDisplay setting (enables the FPS and RAM display). toggleDebugDisplay(Preferences.debugDisplay); + #if web + toggleFramerateCap(Preferences.unlockedFramerate); + #end + } + + static function toggleFramerateCap(unlocked:Bool):Void + { + #if web + var framerateFunction = unlocked ? unlockedFramerateFunction : lockedFramerateFunction; + untyped js.Syntax.code("window.requestAnimationFrame = framerateFunction;"); + #end } static function toggleDebugDisplay(show:Bool):Void diff --git a/source/funkin/audio/FunkinSound.hx b/source/funkin/audio/FunkinSound.hx index 18a0b4da2..dae31cd07 100644 --- a/source/funkin/audio/FunkinSound.hx +++ b/source/funkin/audio/FunkinSound.hx @@ -340,6 +340,8 @@ class FunkinSound extends FlxSound implements ICloneable if (songMusicData != null) { Conductor.instance.mapTimeChanges(songMusicData.timeChanges); + + if (songMusicData.looped != null && params.loop == null) params.loop = songMusicData.looped; } else { diff --git a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx index f245afec7..952fa8b71 100644 --- a/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx +++ b/source/funkin/graphics/adobeanimate/FlxAtlasSprite.hx @@ -185,11 +185,12 @@ class FlxAtlasSprite extends FlxAnimate // Move to the first frame of the animation. // goToFrameLabel(id); trace('Playing animation $id'); - if (this.anim.symbolDictionary.exists(id) || (this.anim.getByName(id) != null)) + if ((id == null || id == "") || this.anim.symbolDictionary.exists(id) || (this.anim.getByName(id) != null)) { this.anim.play(id, restart, false, startFrame); - if (id == "Boyfriend DJ fist pump" || startFrame == 4) trace("PUMP COMMAND: " + anim.curFrame); + this.currentAnimation = anim.curSymbol.name; + fr = null; } // Only call goToFrameLabel if there is a frame label with that name. This prevents annoying warnings! @@ -316,6 +317,10 @@ class FlxAtlasSprite extends FlxAnimate { onAnimationComplete.dispatch(currentAnimation); } + else + { + onAnimationComplete.dispatch(''); + } } var prevFrames:Map = []; diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx index e0ac3579d..739df167d 100644 --- a/source/funkin/play/ResultState.hx +++ b/source/funkin/play/ResultState.hx @@ -418,8 +418,7 @@ class ResultState extends MusicBeatSubState { startingVolume: 1.0, overrideExisting: true, - restartTrack: true, - loop: rank.shouldMusicLoop() + restartTrack: true }); }); } @@ -429,8 +428,7 @@ class ResultState extends MusicBeatSubState { startingVolume: 1.0, overrideExisting: true, - restartTrack: true, - loop: rank.shouldMusicLoop() + restartTrack: true }); } }); diff --git a/source/funkin/play/scoring/Scoring.hx b/source/funkin/play/scoring/Scoring.hx index 6c9f9bd97..dae098638 100644 --- a/source/funkin/play/scoring/Scoring.hx +++ b/source/funkin/play/scoring/Scoring.hx @@ -577,19 +577,6 @@ enum abstract ScoringRank(String) } } - public function shouldMusicLoop():Bool - { - switch (abstract) - { - case PERFECT_GOLD | PERFECT | EXCELLENT | GREAT | GOOD: - return true; - case SHIT: - return false; - default: - return false; - } - } - public function getHorTextAsset() { switch (abstract) diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx index 1fa283b26..2bbda15c0 100644 --- a/source/funkin/save/Save.hx +++ b/source/funkin/save/Save.hx @@ -97,6 +97,7 @@ class Save autoPause: true, inputOffset: 0, audioVisualOffset: 0, + unlockedFramerate: false, controls: { @@ -407,6 +408,9 @@ class Save return data.unlocks.charactersSeen; } + /** + * Marks whether the player has seen the spotlight animation, which should only display once per save file ever. + */ public var oldChar(get, set):Bool; function get_oldChar():Bool @@ -416,7 +420,9 @@ class Save function set_oldChar(value:Bool):Bool { - return data.unlocks.oldChar = value; + data.unlocks.oldChar = value; + flush(); + return data.unlocks.oldChar; } /** @@ -425,7 +431,11 @@ class Save */ public function addCharacterSeen(character:String):Void { - if (!data.unlocks.charactersSeen.contains(character)) data.unlocks.charactersSeen.push(character); + if (!data.unlocks.charactersSeen.contains(character)) + { + data.unlocks.charactersSeen.push(character); + flush(); + } } /** @@ -1171,6 +1181,12 @@ typedef SaveDataOptions = */ var audioVisualOffset:Int; + /** + * If we want the framerate to be unlocked on HTML5. + * @default `false + */ + var unlockedFramerate:Bool; + var controls: { var p1: diff --git a/source/funkin/ui/charSelect/CharSelectSubState.hx b/source/funkin/ui/charSelect/CharSelectSubState.hx index bd55bfdd9..dd5b2a3a2 100644 --- a/source/funkin/ui/charSelect/CharSelectSubState.hx +++ b/source/funkin/ui/charSelect/CharSelectSubState.hx @@ -71,12 +71,17 @@ class CharSelectSubState extends MusicBeatSubState var availableChars:Map = new Map(); var pressedSelect:Bool = false; var selectTimer:FlxTimer = new FlxTimer(); + var selectSound:FunkinSound; var unlockSound:FunkinSound; + var lockedSound:FunkinSound; + var introSound:FunkinSound; + var staticSound:FunkinSound; + var charSelectCam:FunkinCamera; var selectedBizz:Array = [ - new DropShadowFilter(0, 0, 0xFFFFFF, 1, 2, 2, 21, 1, false, false, false), + new DropShadowFilter(0, 0, 0xFFFFFF, 1, 2, 2, 19, 1, false, false, false), new DropShadowFilter(5, 45, 0x000000, 1, 2, 2, 1, 1, false, false, false) ]; @@ -125,7 +130,7 @@ class CharSelectSubState extends MusicBeatSubState add(bg); var crowd:FlxAtlasSprite = new FlxAtlasSprite(0, 0, Paths.animateAtlas("charSelect/crowd")); - crowd.anim.play(""); + crowd.anim.play(); crowd.scrollFactor.set(0.3, 0.3); add(crowd); @@ -224,7 +229,7 @@ class CharSelectSubState extends MusicBeatSubState // FlxG.debugger.track(bfChill, "bf chill"); // FlxG.debugger.track(playerChill, "player"); // FlxG.debugger.track(nametag, "nametag"); - FlxG.debugger.track(selectSound, "selectSound"); + // FlxG.debugger.track(selectSound, "selectSound"); // FlxG.debugger.track(chooseDipshit, "choose dipshit"); // FlxG.debugger.track(barthing, "barthing"); // FlxG.debugger.track(fgBlur, "fgBlur"); @@ -289,6 +294,26 @@ class CharSelectSubState extends MusicBeatSubState FlxG.sound.defaultSoundGroup.add(unlockSound); FlxG.sound.list.add(unlockSound); + lockedSound = new FunkinSound(); + lockedSound.loadEmbedded(Paths.sound('CS_locked')); + lockedSound.pitch = 1; + + lockedSound.volume = 1.; + + FlxG.sound.defaultSoundGroup.add(lockedSound); + FlxG.sound.list.add(lockedSound); + + staticSound = new FunkinSound(); + staticSound.loadEmbedded(Paths.sound('static loop')); + staticSound.pitch = 1; + + staticSound.looped = true; + + staticSound.volume = 0.6; + + FlxG.sound.defaultSoundGroup.add(staticSound); + FlxG.sound.list.add(staticSound); + // playing it here to preload it. not doing this makes a super awkward pause at the end of the intro // TODO: probably make an intro thing for funkinSound itself that preloads the next audio? FunkinSound.playMusic('stayFunky', @@ -324,6 +349,9 @@ class CharSelectSubState extends MusicBeatSubState // FlxG.camera.follow(camFollow, LOCKON, 0.01); FlxG.camera.follow(camFollow, LOCKON); + var fadeShaderFilter:ShaderFilter = new ShaderFilter(fadeShader); + FlxG.camera.filters = [fadeShaderFilter]; + var temp:FlxSprite = new FlxSprite(); temp.loadGraphic(Paths.image('charSelect/placement')); add(temp); @@ -356,10 +384,24 @@ class CharSelectSubState extends MusicBeatSubState blackScreen.y = -(FlxG.height * 0.5); add(blackScreen); + introSound = new FunkinSound(); + introSound.loadEmbedded(Paths.sound('CS_Lights')); + introSound.pitch = 1; + introSound.volume = 0; + + FlxG.sound.defaultSoundGroup.add(introSound); + FlxG.sound.list.add(introSound); + openSubState(new IntroSubState()); subStateClosed.addOnce((_) -> { remove(blackScreen); - if (!Save.instance.oldChar) camera.flash(); + if (!Save.instance.oldChar) + { + camera.flash(); + + introSound.volume = 1; + introSound.play(true); + } checkNewChar(); Save.instance.oldChar = true; @@ -368,7 +410,7 @@ class CharSelectSubState extends MusicBeatSubState function checkNewChar():Void { - if (nonLocks.length > 0) selectTimer.start(0.5, (_) -> { + if (nonLocks.length > 0) selectTimer.start(2, (_) -> { unLock(); }); else @@ -503,7 +545,7 @@ class CharSelectSubState extends MusicBeatSubState updateIconPositions(); playerChillOut.onAnimationComplete.addOnce((_) -> if (_ == "death") { - sync = false; + // sync = false; playerChillOut.visible = false; playerChillOut.switchChar(char); }); @@ -513,6 +555,9 @@ class CharSelectSubState extends MusicBeatSubState { pressedSelect = false; @:bypassAccessor curChar = char; + + staticSound.stop(); + FunkinSound.playMusic('stayFunky', { startingVolume: 1, @@ -565,7 +610,7 @@ class CharSelectSubState extends MusicBeatSubState function syncAudio(elapsed:Float):Void { @:privateAccess - if (sync && !unlockSound.paused) + if (sync && unlockSound.time > 0) { // if (playerChillOut.anim.framerate > 0) // { @@ -576,12 +621,12 @@ class CharSelectSubState extends MusicBeatSubState playerChillOut.anim._tick = 0; if (syncLock != null) syncLock.anim._tick = 0; - if ((unlockSound.time - audioBizz) >= (delay * 1000)) + if ((unlockSound.time - audioBizz) >= ((delay) * 100)) { if (syncLock != null) syncLock.anim._tick = delay; playerChillOut.anim._tick = delay; - audioBizz += delay * 1000; + audioBizz += delay * 100; } } } @@ -728,8 +773,11 @@ class CharSelectSubState extends MusicBeatSubState cursorY = -1; } - if (availableChars.exists(getCurrentSelected()) && Save.instance.charactersSeen.contains(availableChars[getCurrentSelected()])) + if (autoFollow + && availableChars.exists(getCurrentSelected()) + && Save.instance.charactersSeen.contains(availableChars[getCurrentSelected()])) { + gfChill.visible = true; curChar = availableChars.get(getCurrentSelected()); if (!pressedSelect && controls.ACCEPT) @@ -746,7 +794,7 @@ class CharSelectSubState extends MusicBeatSubState FlxTween.tween(FlxG.sound.music, {pitch: 0.1}, 1, {ease: FlxEase.quadInOut}); FlxTween.tween(FlxG.sound.music, {volume: 0.0}, 1.5, {ease: FlxEase.quadInOut}); playerChill.playAnimation("select"); - gfChill.playAnimation("confirm"); + gfChill.playAnimation("confirm", true, false, true); pressedSelect = true; selectTimer.start(1.5, (_) -> { // pressedSelect = false; @@ -774,17 +822,22 @@ class CharSelectSubState extends MusicBeatSubState { ease: FlxEase.quartInOut, onComplete: (_) -> { - playerChill.playAnimation("idle", true, false, true); - gfChill.playAnimation("idle", true, false, true); + if (playerChill.getCurrentAnimation() == "deselect loop start" || playerChill.getCurrentAnimation() == "deselect") + { + playerChill.playAnimation("idle", true, false, true); + gfChill.playAnimation("idle", true, false, true); + } } }); selectTimer.cancel(); } } - else + else if (autoFollow) { curChar = "locked"; + gfChill.visible = false; + if (controls.ACCEPT) { cursorDenied.visible = true; @@ -792,6 +845,9 @@ class CharSelectSubState extends MusicBeatSubState cursorDenied.y = cursor.y - 4; playerChill.playAnimation("cannot select Label", true); + + lockedSound.play(true); + cursorDenied.animation.play("idle", true); cursorDenied.animation.finishCallback = (_) -> { cursorDenied.visible = false; @@ -948,7 +1004,7 @@ class CharSelectSubState extends MusicBeatSubState memb.scale.set(2.6, 2.6); } if (pressedSelect && memb.animation.curAnim.name == "idle") memb.animation.play("confirm"); - if (!pressedSelect && memb.animation.curAnim.name != "idle") + if (autoFollow && !pressedSelect && memb.animation.curAnim.name != "idle") { memb.animation.play("confirm", false, true); member.animation.finishCallback = (_) -> { @@ -981,6 +1037,10 @@ class CharSelectSubState extends MusicBeatSubState curChar = value; + if (value == "locked") staticSound.play(); + else + staticSound.stop(); + nametag.switchChar(value); playerChill.visible = false; playerChillOut.visible = true; diff --git a/source/funkin/ui/charSelect/IntroSubState.hx b/source/funkin/ui/charSelect/IntroSubState.hx index a0c44c225..2c2908473 100644 --- a/source/funkin/ui/charSelect/IntroSubState.hx +++ b/source/funkin/ui/charSelect/IntroSubState.hx @@ -17,8 +17,6 @@ class IntroSubState extends MusicBeatSubState { static final LIGHTS_VIDEO_PATH:String = Paths.stripLibrary(Paths.videos('introSelect')); - var introSound:FunkinSound = null; - public override function create():Void { if (Save.instance.oldChar) @@ -43,19 +41,10 @@ class IntroSubState extends MusicBeatSubState playVideoNative(LIGHTS_VIDEO_PATH); #end - // Im TOO lazy to even care, so uh, yep - FlxG.camera.zoom = 0.66666666666666666666666666666667; - vid.x = -(FlxG.width - (FlxG.width * FlxG.camera.zoom)); - vid.y = -((FlxG.height - (FlxG.height * FlxG.camera.zoom)) * 0.75); - - introSound = new FunkinSound(); - introSound.loadEmbedded(Paths.sound('CS_Lights')); - introSound.pitch = 1; - - FlxG.sound.defaultSoundGroup.add(introSound); - FlxG.sound.list.add(introSound); - - introSound.play(true); + // // Im TOO lazy to even care, so uh, yep + // FlxG.camera.zoom = 0.66666666666666666666666666666667; + // vid.x = -(FlxG.width - (FlxG.width * FlxG.camera.zoom)); + // vid.y = -((FlxG.height - (FlxG.height * FlxG.camera.zoom)) * 0.75); } #if html5 diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx index 23b9dd0bd..af0a9b841 100644 --- a/source/funkin/ui/freeplay/FreeplayState.hx +++ b/source/funkin/ui/freeplay/FreeplayState.hx @@ -358,10 +358,6 @@ class FreeplayState extends MusicBeatSubState if (availableDifficultiesForSong.length == 0) continue; songs.push(new FreeplaySongData(levelId, songId, song, displayedVariations)); - for (difficulty in availableDifficultiesForSong) - { - diffIdsTotal.pushUnique(difficulty); - } for (difficulty in unsuffixedDifficulties) { diffIdsTotal.pushUnique(difficulty); @@ -1789,12 +1785,13 @@ class FreeplayState extends MusicBeatSubState var songScore:Null = Save.instance.getSongScore(daSong.songId, suffixedDifficulty); intendedScore = songScore?.score ?? 0; intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes); - rememberedDifficulty = currentDifficulty; + rememberedDifficulty = suffixedDifficulty; } else { intendedScore = 0; intendedCompletion = 0.0; + rememberedDifficulty = currentDifficulty; } if (intendedCompletion == Math.POSITIVE_INFINITY || intendedCompletion == Math.NEGATIVE_INFINITY || Math.isNaN(intendedCompletion)) diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx index 4e416abeb..13d68da6d 100644 --- a/source/funkin/ui/mainmenu/MainMenuState.hx +++ b/source/funkin/ui/mainmenu/MainMenuState.hx @@ -437,7 +437,10 @@ class MainMenuState extends MusicBeatState if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.N) { @:privateAccess - funkin.save.Save.instance.data.unlocks.charactersSeen = ["bf"]; + { + funkin.save.Save.instance.data.unlocks.charactersSeen = ["bf"]; + funkin.save.Save.instance.data.unlocks.oldChar = false; + } } if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.E) diff --git a/source/funkin/ui/options/PreferencesMenu.hx b/source/funkin/ui/options/PreferencesMenu.hx index 5fbefceed..eb7b88792 100644 --- a/source/funkin/ui/options/PreferencesMenu.hx +++ b/source/funkin/ui/options/PreferencesMenu.hx @@ -72,6 +72,12 @@ class PreferencesMenu extends Page createPrefItemCheckbox('Auto Pause', 'Automatically pause the game when it loses focus', function(value:Bool):Void { Preferences.autoPause = value; }, Preferences.autoPause); + + #if web + createPrefItemCheckbox('Unlocked Framerate', 'Enable to unlock the framerate', function(value:Bool):Void { + Preferences.unlockedFramerate = value; + }, Preferences.unlockedFramerate); + #end } override function update(elapsed:Float):Void